5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 19:39:59 +08:00
wails/v3/internal/fileexplorer/fileexplorer.go
Rick Calixte d03f4ce54a
OpenFileManager for opening with the native file manager and optional file selection support (#3937)
OpenFileManager for opening with the native file manager and optional
file selection support

Closes #3197

Co-authored-by: Krzysztofz01 <krzysztof.zon2001@gmail.com>
2024-12-13 21:37:18 +11:00

170 lines
4.3 KiB
Go

package fileexplorer
import (
"bytes"
"context"
"errors"
"fmt"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
ini "gopkg.in/ini.v1"
)
type explorerBinArgs func(path string, selectFile bool) (string, []string, error)
func OpenFileManager(path string, selectFile bool) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
path = os.ExpandEnv(path)
path = filepath.Clean(path)
absPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("failed to resolve the absolute path: %w", err)
}
path = absPath
if pathInfo, err := os.Stat(path); err != nil {
return fmt.Errorf("failed to access the specified path: %w", err)
} else {
selectFile = selectFile && !pathInfo.IsDir()
}
var (
explorerBinArgs explorerBinArgs
ignoreExitCode bool = false
)
switch runtime.GOOS {
case "windows":
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":
explorerBinArgs = darwinExplorerBinArgs
case "linux":
explorerBinArgs = linuxExplorerBinArgs
default:
return errors.New("unsupported platform: " + runtime.GOOS)
}
explorerBin, explorerArgs, err := explorerBinArgs(path, selectFile)
if err != nil {
return fmt.Errorf("failed to determine the file explorer binary: %w", err)
}
cmd := exec.CommandContext(ctx, 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) {
// Map of field codes to their replacements
var fieldCodes = map[string]string{
"%d": "",
"%D": "",
"%n": "",
"%N": "",
"%v": "",
"%m": "",
"%f": path,
"%F": path,
"%u": pathToURI(path),
"%U": pathToURI(path),
}
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 {
return linuxFallbackExplorerBinArgs(path, selectFile)
}
desktopFile, err := findDesktopFile(strings.TrimSpace((buf.String())))
if err != nil {
return linuxFallbackExplorerBinArgs(path, selectFile)
}
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()
for fieldCode, replacement := range fieldCodes {
exec = strings.ReplaceAll(exec, fieldCode, replacement)
}
args := strings.Fields(exec)
if !strings.Contains(strings.Join(args, " "), path) {
args = append(args, path)
}
return args[0], args[1:], nil
}
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
}
func pathToURI(path string) string {
absPath, err := filepath.Abs(path)
if err != nil {
return path
}
return "file://" + url.PathEscape(absPath)
}
func findDesktopFile(xdgFileName string) (string, error) {
paths := []string{
filepath.Join(os.Getenv("XDG_DATA_HOME"), "applications"),
filepath.Join(os.Getenv("HOME"), ".local", "share", "applications"),
"/usr/share/applications",
}
for _, path := range paths {
desktopFile := filepath.Join(path, xdgFileName)
if _, err := os.Stat(desktopFile); err == nil {
return desktopFile, nil
}
}
err := fmt.Errorf("desktop file not found: %s", xdgFileName)
return "", err
}