VideoTools/internal/utils/desktop_linux.go

125 lines
3.1 KiB
Go

//go:build linux
package utils
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"git.leaktechnologies.dev/stu/VideoTools/internal/logging"
)
// EnsureLinuxDesktopEntry installs a user-level desktop entry and icon so GNOME can
// associate the running app with a stable icon.
func EnsureLinuxDesktopEntry(appID, appName string) {
iconPath := findLinuxIconPath()
if iconPath == "" {
logging.Debug(logging.CatUI, "desktop entry skipped: icon not found")
return
}
exe, err := os.Executable()
if err != nil || exe == "" {
logging.Debug(logging.CatUI, "desktop entry skipped: executable path unavailable")
return
}
home, err := os.UserHomeDir()
if err != nil || home == "" {
logging.Debug(logging.CatUI, "desktop entry skipped: home dir unavailable")
return
}
iconDir := filepath.Join(home, ".local", "share", "icons", "hicolor", "256x256", "apps")
if err := os.MkdirAll(iconDir, 0755); err != nil {
logging.Debug(logging.CatUI, "desktop entry skipped: create icon dir failed: %v", err)
return
}
iconTarget := filepath.Join(iconDir, fmt.Sprintf("%s.png", appID))
if err := copyFileIfDifferent(iconPath, iconTarget); err != nil {
logging.Debug(logging.CatUI, "desktop entry skipped: icon copy failed: %v", err)
return
}
desktopDir := filepath.Join(home, ".local", "share", "applications")
if err := os.MkdirAll(desktopDir, 0755); err != nil {
logging.Debug(logging.CatUI, "desktop entry skipped: create desktop dir failed: %v", err)
return
}
desktopTarget := filepath.Join(desktopDir, fmt.Sprintf("%s.desktop", appID))
desktopContents := fmt.Sprintf(`[Desktop Entry]
Name=%s
Exec=%s
Icon=%s
Type=Application
Categories=AudioVideo;Video;Utility;
Terminal=false
StartupWMClass=%s
`, appName, exe, appID, appID)
if err := writeFileIfDifferent(desktopTarget, desktopContents); err != nil {
logging.Debug(logging.CatUI, "desktop entry skipped: write failed: %v", err)
return
}
}
func findLinuxIconPath() string {
candidates := []string{
filepath.Join("assets", "logo", "VT_Icon.png"),
}
if exe, err := os.Executable(); err == nil {
dir := filepath.Dir(exe)
candidates = append(candidates, filepath.Join(dir, "assets", "logo", "VT_Icon.png"))
}
for _, p := range candidates {
if _, err := os.Stat(p); err == nil {
return p
}
}
return ""
}
func copyFileIfDifferent(src, dst string) error {
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
if dstInfo, err := os.Stat(dst); err == nil {
if srcInfo.Size() == dstInfo.Size() && srcInfo.ModTime().Equal(dstInfo.ModTime()) {
return nil
}
}
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
if _, err := io.Copy(out, in); err != nil {
out.Close()
return err
}
if err := out.Close(); err != nil {
return err
}
return os.Chtimes(dst, srcInfo.ModTime(), srcInfo.ModTime())
}
func writeFileIfDifferent(path, contents string) error {
if existing, err := os.ReadFile(path); err == nil {
if strings.TrimSpace(string(existing)) == strings.TrimSpace(contents) {
return nil
}
}
return os.WriteFile(path, []byte(contents), 0644)
}