diff --git a/.gitignore b/.gitignore index 94a26b5..03ec120 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ build/bin node_modules -frontend/dist +frontend/dist/* +!frontend/dist/.gitkeep frontend/coverage # macOS .DS_Store diff --git a/build/trayicon.png b/build/trayicon.png new file mode 100644 index 0000000..4a5042d Binary files /dev/null and b/build/trayicon.png differ diff --git a/frontend/dist/.gitkeep b/frontend/dist/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/SettingsWindow.tsx b/frontend/src/SettingsWindow.tsx index 02ddd47..e87c02b 100644 --- a/frontend/src/SettingsWindow.tsx +++ b/frontend/src/SettingsWindow.tsx @@ -19,6 +19,7 @@ function SettingsWindow() { const [exportFolder, setExportFolder] = useState(""); const [jpegQuality, setJpegQuality] = useState("auto"); const [enableBetaUpdates, setEnableBetaUpdates] = useState(false); + const [residentMode, setResidentMode] = useState(true); // Frame Settings const [aspectRatioPreset, setAspectRatioPreset] = useState("4300:3618"); @@ -48,6 +49,7 @@ function SettingsWindow() { setExportFolder(s.exportFolder || ""); setJpegQuality(s.jpegQuality || "auto"); setEnableBetaUpdates(s.enableBetaUpdates ?? false); + setResidentMode(s.residentMode ?? true); setAspectRatioPreset(s.aspectRatioPreset || "4300:3618"); setCustomRatioW(s.customRatioW || 4300); @@ -105,6 +107,7 @@ function SettingsWindow() { s.exportFolder = exportFolder; s.jpegQuality = jpegQuality; s.enableBetaUpdates = enableBetaUpdates; + s.residentMode = residentMode; s.aspectRatioPreset = aspectRatioPreset; s.customRatioW = customRatioW; s.customRatioH = customRatioH; @@ -237,6 +240,18 @@ function SettingsWindow() { Receive pre-release (beta) versions of ExifFrame automatically. +
+ + When enabled, closing the window keeps ExifFrame running in the menu bar. Changes take effect after restarting the app. +
)} {activeTab === 'frame' && ( diff --git a/main.go b/main.go index 4cd40c9..247356a 100644 --- a/main.go +++ b/main.go @@ -4,14 +4,20 @@ import ( "embed" "log" "runtime" + "sync/atomic" "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/events" ) +//go:embed build/trayicon.png +var trayIcon []byte + //go:embed all:frontend/dist var assets embed.FS +var isQuitting atomic.Bool + func buildMenu(app *App) *application.Menu { appMenu := application.NewMenu() @@ -77,6 +83,11 @@ func main() { handler := NewImageHandler(appStruct) appStruct.handler = handler + // Read resident mode setting (read once at startup; changes require restart). + settingsMu.RLock() + residentMode := currentSettings.ResidentMode + settingsMu.RUnlock() + app := application.New(application.Options{ Name: "ExifFrame", Description: "ExifFrame", @@ -88,7 +99,11 @@ func main() { Middleware: handler.Middleware, }, Mac: application.MacOptions{ - ApplicationShouldTerminateAfterLastWindowClosed: true, + ApplicationShouldTerminateAfterLastWindowClosed: !residentMode, + }, + ShouldQuit: func() bool { + isQuitting.Store(true) + return true }, }) app.Menu.SetApplicationMenu(buildMenu(appStruct)) @@ -119,6 +134,51 @@ func main() { } }) + // --- System Tray (Resident Mode) --- + if residentMode { + // Intercept window close: hide instead of destroy. + win.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if isQuitting.Load() { + return + } + win.Hide() + e.Cancel() + }) + + // Build tray right-click menu. + trayMenu := application.NewMenu() + trayMenu.Add("Show ExifFrame").OnClick(func(ctx *application.Context) { + win.Show() + win.Focus() + }) + trayMenu.Add("Preferences...").OnClick(func(ctx *application.Context) { + appStruct.OpenSettingsWindow() + }) + trayMenu.AddSeparator() + trayMenu.Add("Quit ExifFrame").OnClick(func(ctx *application.Context) { + application.Get().Quit() + }) + + systray := app.SystemTray.New() + if runtime.GOOS == "darwin" { + systray.SetTemplateIcon(trayIcon) // Auto-adapts to dark/light mode on macOS + } else { + systray.SetIcon(trayIcon) + } + systray.SetMenu(trayMenu) + systray.SetTooltip("ExifFrame") + + // Left-click toggles main window visibility. + systray.OnClick(func() { + if win.IsVisible() { + win.Hide() + } else { + win.Show() + win.Focus() + } + }) + } + err := app.Run() if err != nil { log.Fatal("Error:", err) diff --git a/settings.go b/settings.go index 2bfe1c7..e916ad1 100644 --- a/settings.go +++ b/settings.go @@ -55,6 +55,9 @@ type Settings struct { // Updater settings EnableBetaUpdates bool `json:"enableBetaUpdates"` + + // System tray settings + ResidentMode bool `json:"residentMode"` } var ( @@ -101,6 +104,7 @@ func init() { VisibilityTemperature: true, VisibilityTime: true, EnableBetaUpdates: false, + ResidentMode: true, } loadSettings() }