Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
build/bin
node_modules
frontend/dist
frontend/dist/*
!frontend/dist/.gitkeep
frontend/coverage
# macOS
.DS_Store
Expand Down
Binary file added build/trayicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added frontend/dist/.gitkeep
Empty file.
15 changes: 15 additions & 0 deletions frontend/src/SettingsWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function SettingsWindow() {
const [exportFolder, setExportFolder] = useState("");
const [jpegQuality, setJpegQuality] = useState("auto");
const [enableBetaUpdates, setEnableBetaUpdates] = useState<boolean>(false);
const [residentMode, setResidentMode] = useState<boolean>(true);

// Frame Settings
const [aspectRatioPreset, setAspectRatioPreset] = useState<string>("4300:3618");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -237,6 +240,18 @@ function SettingsWindow() {
</label>
<small style={{ display: 'block', marginTop: '0.5rem', marginLeft: '1.5rem', color: 'var(--text-secondary)', textAlign: 'left' }}>Receive pre-release (beta) versions of ExifFrame automatically.</small>
</div>
<div className="input-group">
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer', fontWeight: 'normal', margin: 0, color: 'var(--text-primary)', fontSize: '0.85rem' }}>
<input
type="checkbox"
checked={residentMode}
onChange={(e) => setResidentMode(e.target.checked)}
style={{ margin: 0, width: 'auto', height: 'auto', cursor: 'pointer' }}
/>
Keep running in system tray
</label>
<small style={{ display: 'block', marginTop: '0.5rem', marginLeft: '1.5rem', color: 'var(--text-secondary)', textAlign: 'left' }}>When enabled, closing the window keeps ExifFrame running in the menu bar. Changes take effect after restarting the app.</small>
</div>
</div>
)}
{activeTab === 'frame' && (
Expand Down
62 changes: 61 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

frontend/dist がない状態で Go の型チェックが失敗しています。

Line 16 の //go:embed all:frontend/dist は対象ファイルが存在しないとコンパイル不能です。lint/CI の前に frontend build を実行するか、embed 対象が常に存在するようにしてください。

🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 16-16: pattern all:frontend/dist: no matching files found

(typecheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.go` at line 16, The go:embed directive in main.go is failing type checks
when frontend/dist is missing, so ensure the embedded path always exists before
build or lint. Update the build flow around the main package so frontend/dist is
generated or restored prior to compiling, and confirm the embed target used by
go:embed all:frontend/dist is present in CI and local checks.

Source: Linters/SAST tools

var assets embed.FS

var isQuitting atomic.Bool

func buildMenu(app *App) *application.Menu {
appMenu := application.NewMenu()

Expand Down Expand Up @@ -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",
Expand All @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ type Settings struct {

// Updater settings
EnableBetaUpdates bool `json:"enableBetaUpdates"`

// System tray settings
ResidentMode bool `json:"residentMode"`
}

var (
Expand Down Expand Up @@ -101,6 +104,7 @@ func init() {
VisibilityTemperature: true,
VisibilityTime: true,
EnableBetaUpdates: false,
ResidentMode: true,
}
loadSettings()
}
Expand Down
Loading