stash/internal/desktop/desktop_platform_windows.go
1509x 9a1b1fb718
[Feature] Reveal file in system file manager from file info panel (#6587)
* Add reveal in file manager button to file info panel

Adds a folder icon button next to the path field in the Scene, Image,
and Gallery file info panels. Clicking it calls a new GraphQL mutation
that opens the file's enclosing directory in the system file manager
(Finder on macOS, Explorer on Windows, xdg-open on Linux).

Also fixes the existing revealInFileManager implementations which were
constructing exec.Command but never calling Run(), making them no-ops:
- darwin: add Run() to open -R
- windows: add Run() and fix flag from \select to /select,<path>
- linux: implement with xdg-open on the parent directory
- desktop.go: use os.Stat instead of FileExists so folders work too

* Disallow reveal operation if request not from loopback
---------
Co-authored-by: 1509x <1509x@users.noreply.github.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2026-02-23 12:51:35 +11:00

93 lines
2.3 KiB
Go

//go:build windows
// +build windows
package desktop
import (
"os"
"os/exec"
"syscall"
"unsafe"
"github.com/go-toast/toast"
"github.com/stashapp/stash/pkg/logger"
"golang.org/x/sys/windows/svc"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
user32 = syscall.NewLazyDLL("user32.dll")
)
func isService() bool {
result, err := svc.IsWindowsService()
if err != nil {
logger.Errorf("Encountered error checking if running as Windows service: %s", err.Error())
return false
}
return result
}
// Detect if windows golang executable file is running via double click or from cmd/shell terminator
// https://stackoverflow.com/questions/8610489/distinguish-if-program-runs-by-clicking-on-the-icon-typing-its-name-in-the-cons?rq=1
// https://github.com/shirou/w32/blob/master/kernel32.go
// https://github.com/kbinani/win/blob/master/kernel32.go#L3268
// win.GetConsoleProcessList(new(uint32), win.DWORD(2))
// from https://gist.github.com/yougg/213250cc04a52e2b853590b06f49d865
func isDoubleClickLaunched() bool {
lp := kernel32.NewProc("GetConsoleProcessList")
if lp != nil {
var pids [2]uint32
var maxCount uint32 = 2
ret, _, _ := lp.Call(uintptr(unsafe.Pointer(&pids)), uintptr(maxCount))
if ret > 1 {
return false
}
}
return true
}
func hideConsole() {
const SW_HIDE = 0
h := getConsoleWindow()
lp := user32.NewProc("ShowWindow")
// don't want to check for errors and can't prevent dogsled
_, _, _ = lp.Call(h, SW_HIDE) //nolint:dogsled
}
func getConsoleWindow() uintptr {
lp := kernel32.NewProc("GetConsoleWindow")
ret, _, _ := lp.Call()
return ret
}
func isServerDockerized() bool {
return false
}
func sendNotification(notificationTitle string, notificationText string) {
notification := toast.Notification{
AppID: "Stash",
Title: notificationTitle,
Message: notificationText,
Icon: getIconPath(),
Actions: []toast.Action{{
Type: "protocol",
Label: "Open Stash",
Arguments: getServerURL(""),
}},
}
err := notification.Push()
if err != nil {
logger.Errorf("Error creating Windows notification: %s", err.Error())
}
}
func revealInFileManager(path string, _ os.FileInfo) error {
c := exec.Command(`explorer`, `/select,`, path)
logger.Debugf("Running: %s", c.String())
// explorer seems to return an error code even when it works, so ignore the error
_ = c.Run()
return nil
}