From d6f19b51ccd760c712896aebc869022d2a573e17 Mon Sep 17 00:00:00 2001 From: luxick Date: Tue, 6 Jan 2026 14:06:27 +0100 Subject: [PATCH] Refactor openlocation feature --- internal/openfolder/open_linux.go | 78 +++++++++++++++++++++++++++ internal/openfolder/open_other.go | 12 +++++ internal/openfolder/open_windows.go | 14 +++++ main.go | 82 +---------------------------- 4 files changed, 106 insertions(+), 80 deletions(-) create mode 100644 internal/openfolder/open_linux.go create mode 100644 internal/openfolder/open_other.go create mode 100644 internal/openfolder/open_windows.go diff --git a/internal/openfolder/open_linux.go b/internal/openfolder/open_linux.go new file mode 100644 index 0000000..b770974 --- /dev/null +++ b/internal/openfolder/open_linux.go @@ -0,0 +1,78 @@ +//go:build linux + +package openfolder + +import ( + "net/url" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/godbus/dbus/v5" +) + +func OpenLocation(path string) error { + if isKDESession() { + if err := openFolderKDEDBus(path); err == nil { + return nil + } + // Fallback: launching dolphin directly typically forwards to an existing + // instance (opening a tab) and avoids portal/session-restore oddities. + if err := exec.Command("dolphin", path).Start(); err == nil { + return nil + } + } + + return exec.Command("xdg-open", path).Start() +} + +func isKDESession() bool { + // Plasma sets XDG_CURRENT_DESKTOP=KDE. Some setups provide multiple entries. + cd := strings.ToLower(strings.TrimSpace(os.Getenv("XDG_CURRENT_DESKTOP"))) + return strings.Contains(cd, "kde") +} + +// openFolderKDEDBus asks the running file manager (Dolphin on Plasma) to show +// the folder via D-Bus. This tends to reuse an existing Dolphin window and open +// a new tab instead of spawning a new window and restoring unrelated session +// tabs. +// +// Note: On Plasma Wayland, reliably forcing the window to the foreground is +// gated by XDG activation tokens; we do a best-effort activation call but it may +// be ignored by the compositor. +func openFolderKDEDBus(path string) error { + conn, err := dbus.SessionBus() + if err != nil { + return err + } + defer conn.Close() + + uri := (&url.URL{Scheme: "file", Path: filepath.ToSlash(path)}).String() + obj := conn.Object("org.freedesktop.FileManager1", dbus.ObjectPath("/org/freedesktop/FileManager1")) + call := obj.Call("org.freedesktop.FileManager1.ShowFolders", 0, []string{uri}, "") + if call.Err != nil { + return call.Err + } + + // Best-effort activation of an existing Dolphin window. + if dolphinSvc, _ := findFirstDBusName(conn, "org.kde.dolphin-"); dolphinSvc != "" { + app := conn.Object(dolphinSvc, dbus.ObjectPath("/org/kde/dolphin")) + _ = app.Call("org.freedesktop.Application.Activate", 0, map[string]dbus.Variant{}).Err + } + + return nil +} + +func findFirstDBusName(conn *dbus.Conn, prefix string) (string, error) { + var names []string + if err := conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&names); err != nil { + return "", err + } + for _, n := range names { + if strings.HasPrefix(n, prefix) { + return n, nil + } + } + return "", nil +} diff --git a/internal/openfolder/open_other.go b/internal/openfolder/open_other.go new file mode 100644 index 0000000..8acd469 --- /dev/null +++ b/internal/openfolder/open_other.go @@ -0,0 +1,12 @@ +//go:build !linux && !windows + +package openfolder + +import ( + "fmt" + "runtime" +) + +func OpenLocation(path string) error { + return fmt.Errorf("unsupported OS: %s", runtime.GOOS) +} diff --git a/internal/openfolder/open_windows.go b/internal/openfolder/open_windows.go new file mode 100644 index 0000000..d40ae32 --- /dev/null +++ b/internal/openfolder/open_windows.go @@ -0,0 +1,14 @@ +//go:build windows + +package openfolder + +import ( + "os/exec" + "strings" +) + +func OpenLocation(path string) error { + // explorer requires backslashes. + p := strings.ReplaceAll(path, "/", "\\") + return exec.Command("explorer.exe", p).Start() +} diff --git a/main.go b/main.go index e5777bc..1e4be4d 100644 --- a/main.go +++ b/main.go @@ -10,15 +10,13 @@ import ( "log" "net" "net/http" - "net/url" "os" - "os/exec" "path/filepath" "runtime" "strings" "time" - "github.com/godbus/dbus/v5" + "luxtools-client/internal/openfolder" ) type allowList []string @@ -125,7 +123,7 @@ func main() { return } - if err := openFolder(target); err != nil { + if err := openfolder.OpenLocation(target); err != nil { errLog.Printf("/open open-failed method=%s path=%q normalized=%q err=%v dur=%s", r.Method, rawPath, target, err, time.Since(start)) writeJSON(w, http.StatusInternalServerError, openResponse{OK: false, Message: err.Error()}) return @@ -262,82 +260,6 @@ func isAllowed(path string, allowed []string) bool { return false } -func openFolder(path string) error { - switch runtime.GOOS { - case "windows": - // explorer requires backslashes. - p := strings.ReplaceAll(path, "/", "\\") - cmd := exec.Command("explorer.exe", p) - return cmd.Start() - case "linux": - if isKDESession() { - if err := openFolderKDEDBus(path); err == nil { - return nil - } - // Fallback: launching dolphin directly typically forwards to an existing - // instance (opening a tab) and avoids portal/session-restore oddities. - if err := exec.Command("dolphin", path).Start(); err == nil { - return nil - } - } - - cmd := exec.Command("xdg-open", path) - return cmd.Start() - default: - return fmt.Errorf("unsupported OS: %s", runtime.GOOS) - } -} - -func isKDESession() bool { - // Plasma sets XDG_CURRENT_DESKTOP=KDE. Some setups provide multiple entries. - cd := strings.ToLower(strings.TrimSpace(os.Getenv("XDG_CURRENT_DESKTOP"))) - return strings.Contains(cd, "kde") -} - -// openFolderKDEDBus asks the running file manager (Dolphin on Plasma) to show -// the folder via D-Bus. This tends to reuse an existing Dolphin window and open -// a new tab instead of spawning a new window and restoring unrelated session -// tabs. -// -// Note: On Plasma Wayland, reliably forcing the window to the foreground is -// gated by XDG activation tokens; we do a best-effort activation call but it may -// be ignored by the compositor. -func openFolderKDEDBus(path string) error { - conn, err := dbus.SessionBus() - if err != nil { - return err - } - defer conn.Close() - - uri := (&url.URL{Scheme: "file", Path: filepath.ToSlash(path)}).String() - obj := conn.Object("org.freedesktop.FileManager1", dbus.ObjectPath("/org/freedesktop/FileManager1")) - call := obj.Call("org.freedesktop.FileManager1.ShowFolders", 0, []string{uri}, "") - if call.Err != nil { - return call.Err - } - - // Best-effort activation of an existing Dolphin window. - if dolphinSvc, _ := findFirstDBusName(conn, "org.kde.dolphin-"); dolphinSvc != "" { - app := conn.Object(dolphinSvc, dbus.ObjectPath("/org/kde/dolphin")) - _ = app.Call("org.freedesktop.Application.Activate", 0, map[string]dbus.Variant{}).Err - } - - return nil -} - -func findFirstDBusName(conn *dbus.Conn, prefix string) (string, error) { - var names []string - if err := conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&names); err != nil { - return "", err - } - for _, n := range names { - if strings.HasPrefix(n, prefix) { - return n, nil - } - } - return "", nil -} - func writeJSON(w http.ResponseWriter, status int, resp openResponse) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status)