//go:build windows package services import ( "fmt" "os" "os/exec" "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 taskName 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) // 使用文件名作为注册表键名和任务名 baseName := strings.TrimSuffix(filepath.Base(w.execPath), filepath.Ext(w.execPath)) w.registryKey = baseName w.taskName = baseName + "_Startup" return nil } // createTaskSchedulerEntry 创建任务计划程序条目 func (w *WindowsStartupImpl) createTaskSchedulerEntry() error { // 创建任务计划程序条目的XML内容 taskXML := fmt.Sprintf(` %s startup task true InteractiveToken HighestAvailable IgnoreNew false false true false false true false true true false false false PT0S 7 %s %s `, w.taskName, w.execPath, w.workingDir) // 创建临时XML文件 tempFile := filepath.Join(os.TempDir(), w.taskName+".xml") if err := os.WriteFile(tempFile, []byte(taskXML), 0644); err != nil { return fmt.Errorf("failed to create task XML file: %w", err) } defer os.Remove(tempFile) // 清理临时文件 // 使用schtasks命令创建任务 cmd := exec.Command("schtasks", "/create", "/tn", w.taskName, "/xml", tempFile, "/f") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to create scheduled task: %w, output: %s", err, string(output)) } return nil } // deleteTaskSchedulerEntry 删除任务计划程序条目 func (w *WindowsStartupImpl) deleteTaskSchedulerEntry() error { cmd := exec.Command("schtasks", "/delete", "/tn", w.taskName, "/f") output, err := cmd.CombinedOutput() if err != nil { // 如果任务不存在 if strings.Contains(string(output), "cannot find") || strings.Contains(string(output), "does not exist") { return nil } return fmt.Errorf("failed to delete scheduled task: %w, output: %s", err, string(output)) } 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 } // 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 { if enabled { // 优先使用任务计划程序方式,可以绕过UAC限制 if err := w.createTaskSchedulerEntry(); err != nil { // 如果任务计划程序失败,回退到注册表方式 return w.setRegistryStartup(true) } return nil } else { // 删除任务计划程序条目 if err := w.deleteTaskSchedulerEntry(); err != nil { w.logger.Error("Failed to delete scheduled task", "error", err) } // 删除注册表条目 if err := w.setRegistryStartup(false); err != nil { w.logger.Error("Failed to remove registry startup entry", "error", err) } return nil } } // setRegistryStartup 设置注册表启动项 func (w *WindowsStartupImpl) setRegistryStartup(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) } 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 { return fmt.Errorf("startup command verification failed: expected %s, got %s", startupCmd, value) } } else { if err := key.DeleteValue(w.registryKey); err != nil { if err != registry.ErrNotExist { return fmt.Errorf("failed to remove startup entry: %w", err) } } } return nil }