🐛 Fixed bug
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
89
internal/services/startup_darwin.go
Normal file
89
internal/services/startup_darwin.go
Normal 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
|
||||
}
|
110
internal/services/startup_linux.go
Normal file
110
internal/services/startup_linux.go
Normal 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)
|
||||
}
|
53
internal/services/startup_service.go
Normal file
53
internal/services/startup_service.go
Normal 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
|
||||
}
|
151
internal/services/startup_windows.go
Normal file
151
internal/services/startup_windows.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user