🐛 Fixed bug

This commit is contained in:
2025-06-21 15:03:35 +08:00
parent 145b868a44
commit 1153c0a652
22 changed files with 646 additions and 309 deletions

View File

@@ -17,6 +17,7 @@ type ServiceManager struct {
dialogService *DialogService
trayService *TrayService
keyBindingService *KeyBindingService
startupService *StartupService
logger *log.LoggerService
}
@@ -49,6 +50,9 @@ func NewServiceManager() *ServiceManager {
// 初始化快捷键服务
keyBindingService := NewKeyBindingService(logger)
// 初始化开机启动服务
startupService := NewStartupService(configService, logger)
// 使用新的配置通知系统设置热键配置变更监听
err := configService.SetHotkeyChangeCallback(func(enable bool, hotkey *models.HotkeyCombo) error {
return hotkeyService.UpdateHotkey(enable, hotkey)
@@ -83,6 +87,7 @@ func NewServiceManager() *ServiceManager {
dialogService: dialogService,
trayService: trayService,
keyBindingService: keyBindingService,
startupService: startupService,
logger: logger,
}
}
@@ -98,6 +103,7 @@ func (sm *ServiceManager) GetServices() []application.Service {
application.NewService(sm.dialogService),
application.NewService(sm.trayService),
application.NewService(sm.keyBindingService),
application.NewService(sm.startupService),
}
}
@@ -130,3 +136,8 @@ func (sm *ServiceManager) GetTrayService() *TrayService {
func (sm *ServiceManager) GetKeyBindingService() *KeyBindingService {
return sm.keyBindingService
}
// GetStartupService 获取开机启动服务实例
func (sm *ServiceManager) GetStartupService() *StartupService {
return sm.startupService
}

View File

@@ -0,0 +1,89 @@
//go:build darwin
package services
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/mac"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// DarwinStartupImpl macOS 平台开机启动实现
type DarwinStartupImpl struct {
logger *log.LoggerService
disabled bool
appPath string
appName string
}
// newStartupImplementation 创建平台特定的开机启动实现
func newStartupImplementation(logger *log.LoggerService) StartupImplementation {
return &DarwinStartupImpl{
logger: logger,
}
}
// Initialize 初始化 macOS 实现
func (d *DarwinStartupImpl) Initialize() error {
if mac.GetBundleID() == "" {
d.disabled = true
return nil
}
exe, _ := os.Executable()
binName := filepath.Base(exe)
if !strings.HasSuffix(exe, "/Contents/MacOS/"+binName) {
d.disabled = true
return nil
}
d.appPath = strings.TrimSuffix(exe, "/Contents/MacOS/"+binName)
d.appName = strings.TrimSuffix(filepath.Base(d.appPath), ".app")
return nil
}
// SetEnabled 设置开机启动状态
func (d *DarwinStartupImpl) SetEnabled(enabled bool) error {
if d.disabled {
return fmt.Errorf("app is not properly packaged as .app bundle, cannot set startup")
}
var command string
if enabled {
command = fmt.Sprintf(
`tell application "System Events" to make login item at end with properties {name: "%s",path:"%s", hidden:false}`,
d.appName, d.appPath,
)
} else {
command = fmt.Sprintf(
`tell application "System Events" to delete login item "%s"`,
d.appName,
)
}
cmd := exec.Command("osascript", "-e", command)
output, err := cmd.CombinedOutput()
if err != nil {
if strings.Contains(string(output), "not allowed") || strings.Contains(string(output), "permission") {
return fmt.Errorf("accessibility permission required: go to System Preferences > Security & Privacy > Privacy > Accessibility")
}
return fmt.Errorf("failed to set login item: %w", err)
}
// 简单验证:重新查询登录项
if enabled {
checkCmd := exec.Command("osascript", "-e", `tell application "System Events" to get the name of every login item`)
checkOutput, _ := checkCmd.CombinedOutput()
if !strings.Contains(string(checkOutput), d.appName) {
return fmt.Errorf("login item verification failed")
}
}
return nil
}

View File

@@ -0,0 +1,110 @@
//go:build linux
package services
import (
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// LinuxStartupImpl Linux 平台开机启动实现
type LinuxStartupImpl struct {
logger *log.LoggerService
autostartDir string
execPath string
appName string
}
// desktopEntry 桌面条目模板数据
type desktopEntry struct {
Name string
Cmd string
Comment string
}
const desktopEntryTemplate = `[Desktop Entry]
Name={{.Name}}
Comment={{.Comment}}
Type=Application
Exec={{.Cmd}}
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
`
// newStartupImplementation 创建平台特定的开机启动实现
func newStartupImplementation(logger *log.LoggerService) StartupImplementation {
return &LinuxStartupImpl{
logger: logger,
}
}
// Initialize 初始化 Linux 实现
func (l *LinuxStartupImpl) Initialize() error {
homeDir, _ := os.UserHomeDir()
l.autostartDir = filepath.Join(homeDir, ".config", "autostart")
// 检查是否有桌面环境
if os.Getenv("DISPLAY") == "" && os.Getenv("WAYLAND_DISPLAY") == "" {
return fmt.Errorf("no desktop environment detected, cannot set startup")
}
if err := os.MkdirAll(l.autostartDir, 0755); err != nil {
return fmt.Errorf("failed to create autostart directory: %w", err)
}
execPath, _ := os.Executable()
l.execPath = execPath
l.appName = filepath.Base(execPath)
return nil
}
// getDesktopFilePath 获取桌面文件路径
func (l *LinuxStartupImpl) getDesktopFilePath() string {
filename := fmt.Sprintf("%s-autostart.desktop", l.appName)
return filepath.Join(l.autostartDir, filename)
}
// SetEnabled 设置开机启动状态
func (l *LinuxStartupImpl) SetEnabled(enabled bool) error {
desktopFile := l.getDesktopFilePath()
if !enabled {
os.Remove(desktopFile)
return nil
}
if err := l.createDesktopFile(desktopFile); err != nil {
return fmt.Errorf("failed to create autostart file: %w", err)
}
// 验证文件是否创建成功
if _, err := os.Stat(desktopFile); err != nil {
return fmt.Errorf("autostart file verification failed: %w", err)
}
return nil
}
// createDesktopFile 创建桌面文件
func (l *LinuxStartupImpl) createDesktopFile(filename string) error {
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer file.Close()
tmpl, _ := template.New("desktopEntry").Parse(desktopEntryTemplate)
data := desktopEntry{
Name: l.appName,
Cmd: l.execPath,
Comment: fmt.Sprintf("Autostart service for %s", l.appName),
}
return tmpl.Execute(file, data)
}

View File

@@ -0,0 +1,53 @@
package services
import (
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// StartupService 开机启动服务
type StartupService struct {
configService *ConfigService
logger *log.LoggerService
impl StartupImplementation
initError error
}
// StartupImplementation 开机启动实现接口
type StartupImplementation interface {
SetEnabled(enabled bool) error
Initialize() error
}
// NewStartupService 创建开机启动服务实例
func NewStartupService(configService *ConfigService, logger *log.LoggerService) *StartupService {
service := &StartupService{
configService: configService,
logger: logger,
impl: newStartupImplementation(logger),
}
// 初始化平台特定实现
service.initError = service.impl.Initialize()
return service
}
// SetEnabled 设置开机启动状态
func (s *StartupService) SetEnabled(enabled bool) error {
// 检查初始化是否成功
if s.initError != nil {
return s.initError
}
// 设置系统开机启动
if err := s.impl.SetEnabled(enabled); err != nil {
return err
}
// 更新配置文件
if s.configService != nil {
s.configService.Set("general.startAtLogin", enabled)
}
return nil
}

View File

@@ -0,0 +1,151 @@
//go:build windows
package services
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/wailsapp/wails/v3/pkg/services/log"
"golang.org/x/sys/windows/registry"
)
// WindowsStartupImpl Windows 平台开机启动实现
type WindowsStartupImpl struct {
logger *log.LoggerService
registryKey string
execPath string
workingDir string
batchFile string
}
// newStartupImplementation 创建平台特定的开机启动实现
func newStartupImplementation(logger *log.LoggerService) StartupImplementation {
return &WindowsStartupImpl{
logger: logger,
}
}
// Initialize 初始化 Windows 实现
func (w *WindowsStartupImpl) Initialize() error {
exePath, err := os.Executable()
if err != nil {
return fmt.Errorf("failed to get executable path: %w", err)
}
// 获取绝对路径并规范化
absPath, err := filepath.Abs(exePath)
if err != nil {
return fmt.Errorf("failed to get absolute path: %w", err)
}
// 转换为Windows标准路径格式
w.execPath = filepath.ToSlash(absPath)
w.execPath = strings.ReplaceAll(w.execPath, "/", "\\")
// 获取工作目录(可执行文件所在目录)
w.workingDir = filepath.Dir(w.execPath)
// 使用文件名作为注册表键名
w.registryKey = strings.TrimSuffix(filepath.Base(w.execPath), filepath.Ext(w.execPath))
// 批处理文件路径(放在临时目录)
tempDir := os.TempDir()
w.batchFile = filepath.Join(tempDir, w.registryKey+"_startup.bat")
return nil
}
// openRegistryKey 打开注册表键
func (w *WindowsStartupImpl) openRegistryKey() (registry.Key, error) {
key, err := registry.OpenKey(
registry.CURRENT_USER,
`Software\Microsoft\Windows\CurrentVersion\Run`,
registry.ALL_ACCESS,
)
if err != nil {
return 0, fmt.Errorf("failed to open registry key: %w", err)
}
return key, nil
}
// createBatchFile 创建批处理文件
func (w *WindowsStartupImpl) createBatchFile() error {
// 批处理文件内容
batchContent := fmt.Sprintf(`@echo off
cd /d "%s"
start "" "%s"
`, w.workingDir, w.execPath)
// 写入批处理文件
if err := os.WriteFile(w.batchFile, []byte(batchContent), 0644); err != nil {
return fmt.Errorf("failed to create batch file: %w", err)
}
return nil
}
// deleteBatchFile 删除批处理文件
func (w *WindowsStartupImpl) deleteBatchFile() {
if _, err := os.Stat(w.batchFile); err == nil {
os.Remove(w.batchFile)
}
}
// buildStartupCommand 构建启动命令
func (w *WindowsStartupImpl) buildStartupCommand() (string, error) {
// 尝试直接使用可执行文件路径
execPath := w.execPath
if strings.Contains(execPath, " ") {
execPath = `"` + execPath + `"`
}
// 首先尝试直接路径,如果有问题再使用批处理文件
return execPath, nil
}
// SetEnabled 设置开机启动状态
func (w *WindowsStartupImpl) SetEnabled(enabled bool) error {
key, err := w.openRegistryKey()
if err != nil {
return fmt.Errorf("failed to access registry: %w", err)
}
defer key.Close()
if enabled {
startupCmd, err := w.buildStartupCommand()
if err != nil {
return fmt.Errorf("failed to build startup command: %w", err)
}
w.logger.Info("Setting Windows startup", "command", startupCmd)
if err := key.SetStringValue(w.registryKey, startupCmd); err != nil {
return fmt.Errorf("failed to set startup entry: %w", err)
}
// 验证设置是否成功
if value, _, err := key.GetStringValue(w.registryKey); err != nil {
return fmt.Errorf("startup entry verification failed: %w", err)
} else if value != startupCmd {
w.logger.Error("Startup command verification mismatch", "expected", startupCmd, "actual", value)
}
w.logger.Info("Windows startup enabled successfully")
} else {
// 删除批处理文件(如果存在)
w.deleteBatchFile()
if err := key.DeleteValue(w.registryKey); err != nil {
// 如果键不存在,这不是错误
if err != registry.ErrNotExist {
return fmt.Errorf("failed to remove startup entry: %w", err)
}
}
w.logger.Info("Windows startup disabled successfully")
}
return nil
}