From 19b8343c669d9f57fde38849753c5682eea65d05 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Wed, 7 Jan 2026 01:59:16 -0500 Subject: [PATCH] Improve branding layout and fix GNOME icon --- author_module.go | 15 ++-- internal/utils/desktop_linux.go | 124 ++++++++++++++++++++++++++++++++ internal/utils/desktop_other.go | 6 ++ main.go | 2 + 4 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 internal/utils/desktop_linux.go create mode 100644 internal/utils/desktop_other.go diff --git a/author_module.go b/author_module.go index 5d42287..d28aa80 100644 --- a/author_module.go +++ b/author_module.go @@ -1044,6 +1044,8 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject { logoPreviewBorder, container.NewPadded(logoPreview), ) + previewMin := canvas.NewRectangle(color.NRGBA{A: 0}) + previewMin.SetMinSize(fyne.NewSize(220, 0)) updateBrandingTitle := func() {} menuPreviewSize := func() (int, int) { @@ -1275,11 +1277,14 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject { info := widget.NewLabel("DVD menus are generated using the VideoTools theme and IBM Plex Mono. Menu settings apply only to disc authoring.") info.Wrapping = fyne.TextWrapWord - logoPreviewGroup := container.NewVBox( - widget.NewLabel("Preview:"), - logoPreviewLabel, - logoPreviewBox, - logoPreviewSize, + logoPreviewGroup := container.NewMax( + previewMin, + container.NewVBox( + widget.NewLabel("Preview:"), + logoPreviewLabel, + logoPreviewBox, + logoPreviewSize, + ), ) logoButtonRow := container.NewHBox( diff --git a/internal/utils/desktop_linux.go b/internal/utils/desktop_linux.go new file mode 100644 index 0000000..b01c21c --- /dev/null +++ b/internal/utils/desktop_linux.go @@ -0,0 +1,124 @@ +//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) +} diff --git a/internal/utils/desktop_other.go b/internal/utils/desktop_other.go new file mode 100644 index 0000000..4c5c35a --- /dev/null +++ b/internal/utils/desktop_other.go @@ -0,0 +1,6 @@ +//go:build !linux + +package utils + +// EnsureLinuxDesktopEntry is a no-op on non-Linux platforms. +func EnsureLinuxDesktopEntry(appID, appName string) {} diff --git a/main.go b/main.go index 4ba7bfd..91df550 100644 --- a/main.go +++ b/main.go @@ -6530,6 +6530,8 @@ func runGUI() { // Initialize UI colors ui.SetColors(gridColor, textColor) + utils.EnsureLinuxDesktopEntry("com.leaktechnologies.videotools", "VideoTools") + a := app.NewWithID("com.leaktechnologies.videotools") // Always start with a clean slate: wipe any persisted app storage (queue or otherwise)