//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 mechanisms; 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 }