From fcf7da253be333788f27f3c83df6454b6ca0b60b Mon Sep 17 00:00:00 2001 From: Krzysztofz01 Date: Sat, 7 Dec 2024 17:49:02 -0500 Subject: [PATCH] OpenFileManager for opening with the native file manager and optional file selection support Closes #3197 --- mkdocs-website/docs/en/changelog.md | 1 + v3/internal/fileexplorer/fileexplorer.go | 110 +++++++++++++++++++++-- v3/pkg/application/application.go | 7 +- 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/mkdocs-website/docs/en/changelog.md b/mkdocs-website/docs/en/changelog.md index 025d13231b1..31f69f59167 100644 --- a/mkdocs-website/docs/en/changelog.md +++ b/mkdocs-website/docs/en/changelog.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `app.OpenDirectory(dir string)` to open the system file explorer to the directory `dir` by [@leaanthony](https://github.com/leaanthony) +- `app.OpenFileManager(path string)` to open file manager to the path `path` with optional highlighting via `selectFile` by [@Krzysztofz01](https://github.com/Krzysztofz01)[@rcalixte](https://github.com/rcalixte) ### Fixed diff --git a/v3/internal/fileexplorer/fileexplorer.go b/v3/internal/fileexplorer/fileexplorer.go index 773d6ef1801..ef665c8e25e 100644 --- a/v3/internal/fileexplorer/fileexplorer.go +++ b/v3/internal/fileexplorer/fileexplorer.go @@ -1,24 +1,120 @@ package fileexplorer import ( + "bytes" + "errors" "fmt" + "os" "os/exec" + "path/filepath" "runtime" + "strings" + + ini "gopkg.in/ini.v1" ) -func Open(path string) error { - var cmd *exec.Cmd +type explorerBinArgs func(path string, selectFile bool) (string, []string, error) + +func OpenFileManager(path string, selectFile bool) error { + path = os.ExpandEnv(path) + if pathInfo, err := os.Stat(path); err != nil { + return fmt.Errorf("failed to access the specified path stat: %w", err) + } else { + selectFile = selectFile && !pathInfo.IsDir() + } + + var ( + explorerBinArgs explorerBinArgs + ignoreExitCode bool = false + ) switch runtime.GOOS { case "windows": - cmd = exec.Command("explorer", path) + explorerBinArgs = windowsExplorerBinArgs + // NOTE: Disabling the exit code check on Windows system. Workaround for explorer.exe + // exit code handling (https://github.com/microsoft/WSL/issues/6565) + ignoreExitCode = true case "darwin": - cmd = exec.Command("open", path) + explorerBinArgs = darwinExplorerBinArgs case "linux": - cmd = exec.Command("xdg-open", path) + explorerBinArgs = linuxExplorerBinArgs default: - return fmt.Errorf("unsupported platform") + return errors.New("unsupported platform") } - return cmd.Run() + explorerBin, explorerArgs, err := explorerBinArgs(path, selectFile) + if err != nil { + return fmt.Errorf("failed to determine the file explorer binary: %w", err) + } + + cmd := exec.Command(explorerBin, explorerArgs...) + cmd.Stdout = nil + cmd.Stderr = nil + + if err := cmd.Run(); err != nil { + if !ignoreExitCode { + return fmt.Errorf("failed to open the file explorer: %w", err) + } + } + return nil +} + +var windowsExplorerBinArgs explorerBinArgs = func(path string, selectFile bool) (string, []string, error) { + args := []string{} + if selectFile { + args = append(args, fmt.Sprintf("/select,\"%s\"", path)) + } else { + args = append(args, path) + } + return "explorer.exe", args, nil +} + +var darwinExplorerBinArgs explorerBinArgs = func(path string, selectFile bool) (string, []string, error) { + args := []string{} + if selectFile { + args = append(args, "-R") + } + + args = append(args, path) + return "open", args, nil +} + +var linuxExplorerBinArgs explorerBinArgs = func(path string, selectFile bool) (string, []string, error) { + fileManagerQuery := exec.Command("xdg-mime", "query", "default", "inode/directory") + buf := new(bytes.Buffer) + fileManagerQuery.Stdout = buf + fileManagerQuery.Stderr = nil + + if err := fileManagerQuery.Run(); err == nil { + var desktopFile string + xdgPath := strings.TrimSpace(os.Getenv("XDG_DATA_HOME")) + if xdgPath == "" { + desktopFile = os.Getenv("HOME") + "/.local/share/applications" + } + desktopFile = xdgPath + strings.TrimSpace((buf.String())) + + if _, err := os.Stat(desktopFile); err != nil { + desktopFile = "/usr/share/applications" + strings.TrimSpace((buf.String())) + } + + cfg, err := ini.Load(desktopFile) + if err != nil { + // Opting to fallback rather than fail + return linuxFallbackExplorerBinArgs(path, selectFile) + } + + exec := cfg.Section("Desktop Entry").Key("Exec").String() + args := strings.Split(exec, " ") + args = append(args, path) + + return args[0], args[1:], nil + } else { + return linuxFallbackExplorerBinArgs(path, selectFile) + } +} + +var linuxFallbackExplorerBinArgs explorerBinArgs = func(path string, selectFile bool) (string, []string, error) { + // NOTE: The linux fallback explorer opening is not supporting file selection + path = filepath.Dir(path) + return "xdg-open", []string{path}, nil } diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index 86480485a5e..2e002464bd1 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -5,7 +5,6 @@ import ( "embed" "encoding/json" "fmt" - "github.com/wailsapp/wails/v3/internal/fileexplorer" "io" "log" "log/slog" @@ -17,6 +16,8 @@ import ( "strings" "sync" + "github.com/wailsapp/wails/v3/internal/fileexplorer" + "github.com/wailsapp/wails/v3/internal/operatingsystem" "github.com/pkg/browser" @@ -1046,8 +1047,8 @@ func (a *App) Paths(selector Paths) []string { return pathdirs[selector] } -func (a *App) OpenDirectory(path string) error { +func (a *App) OpenDirectory(path string, selectFile bool) error { return InvokeSyncWithError(func() error { - return fileexplorer.Open(path) + return fileexplorer.OpenFileManager(path, selectFile) }) }