//go:build windows package files import ( "context" "fmt" "os/exec" "path/filepath" "strings" ) var ErrSelectionCanceled = context.Canceled type commandRunner interface { Run(context.Context, string, ...string) ([]byte, error) } type execRunner struct{} func (execRunner) Run(ctx context.Context, name string, args ...string) ([]byte, error) { return exec.CommandContext(ctx, name, args...).Output() } type windowsDirectoryPicker struct{ runner commandRunner } func NewDirectoryPicker() DirectoryPicker { return windowsDirectoryPicker{runner: execRunner{}} } func newDirectoryPickerWithRunner(runner commandRunner) windowsDirectoryPicker { return windowsDirectoryPicker{runner: runner} } func (p windowsDirectoryPicker) ChooseDirectory(ctx context.Context) (string, error) { if err := ctx.Err(); err != nil { return "", err } output, err := p.runner.Run(ctx, "powershell.exe", "-NoProfile", "-NonInteractive", "-STA", "-WindowStyle", "Hidden", "-Command", folderDialogScript) if err != nil { if ctxErr := ctx.Err(); ctxErr != nil { return "", ctxErr } return "", fmt.Errorf("open directory picker: %w", err) } selected := strings.TrimSpace(string(output)) if selected == "" { return "", ErrSelectionCanceled } if !filepath.IsAbs(selected) { return "", fmt.Errorf("directory picker returned a non-absolute path") } return filepath.Clean(selected), nil } const folderDialogScript = `[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) Add-Type -AssemblyName System.Windows.Forms $dialog = New-Object System.Windows.Forms.FolderBrowserDialog $dialog.Description = '选择浏览器 User Data Dir' $dialog.ShowNewFolderButton = $true if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { [Console]::Out.Write($dialog.SelectedPath) }`