From 41c29f087ad905e049b0bdd68f92e8bd8ae48557 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Tue, 12 Aug 2025 17:32:14 -0400 Subject: [PATCH 1/3] Add a toggle to mute sounds (audio cues) --- cmd/goose/main.go | 5 +++++ cmd/goose/sound.go | 10 ++++++++++ cmd/goose/ui.go | 45 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/cmd/goose/main.go b/cmd/goose/main.go index 14bb076..29d5aff 100644 --- a/cmd/goose/main.go +++ b/cmd/goose/main.go @@ -132,6 +132,7 @@ type App struct { hideStaleIncoming bool loadingTurnData bool enableReminders bool // Whether to send daily reminder notifications + enableAudioCues bool // Whether to play audio cues for notifications } func main() { @@ -183,8 +184,12 @@ func main() { updateInterval: updateInterval, pendingTurnResults: make([]TurnResult, 0), enableReminders: true, + enableAudioCues: true, } + // Load saved settings + app.loadSettings() + log.Println("Initializing GitHub clients...") err = app.initClients(ctx) if err != nil { diff --git a/cmd/goose/sound.go b/cmd/goose/sound.go index de3aeb8..e6f2b17 100644 --- a/cmd/goose/sound.go +++ b/cmd/goose/sound.go @@ -52,6 +52,16 @@ func (app *App) initSoundCache() { // playSound plays a cached sound file using platform-specific commands. func (app *App) playSound(ctx context.Context, soundType string) { + // Check if audio cues are enabled + app.mu.RLock() + audioEnabled := app.enableAudioCues + app.mu.RUnlock() + + if !audioEnabled { + log.Printf("[SOUND] Sound playback skipped (audio cues disabled): %s", soundType) + return + } + log.Printf("[SOUND] Playing %s sound", soundType) // Ensure sounds are cached app.initSoundCache() diff --git a/cmd/goose/ui.go b/cmd/goose/ui.go index dedc356..e6b489d 100644 --- a/cmd/goose/ui.go +++ b/cmd/goose/ui.go @@ -287,18 +287,24 @@ func (app *App) addStaticMenuItems(ctx context.Context) { hideStaleItem.Check() } hideStaleItem.Click(func() { + app.mu.Lock() app.hideStaleIncoming = !app.hideStaleIncoming - if app.hideStaleIncoming { + hideStale := app.hideStaleIncoming + // Clear menu state to force rebuild + app.lastMenuState = nil + app.mu.Unlock() + + if hideStale { hideStaleItem.Check() } else { hideStaleItem.Uncheck() } + + // Save settings to disk + app.saveSettings() + // Toggle hide stale PRs setting // Force menu rebuild since hideStaleIncoming changed - app.mu.Lock() - // Clear menu state to force rebuild - app.lastMenuState = nil - app.mu.Unlock() app.rebuildMenu(ctx) }) @@ -323,11 +329,40 @@ func (app *App) addStaticMenuItems(ctx context.Context) { reminderItem.Uncheck() log.Println("[SETTINGS] Daily reminders disabled") } + + // Save settings to disk + app.saveSettings() }) // Add login item option (macOS only) addLoginItemUI(ctx, app) + // Audio cues + // Add 'Audio cues' option + audioItem := systray.AddMenuItem("Audio cues", "Play sounds for notifications") + app.mu.RLock() + if app.enableAudioCues { + audioItem.Check() + } + app.mu.RUnlock() + audioItem.Click(func() { + app.mu.Lock() + app.enableAudioCues = !app.enableAudioCues + enabled := app.enableAudioCues + app.mu.Unlock() + + if enabled { + audioItem.Check() + log.Println("[SETTINGS] Audio cues enabled") + } else { + audioItem.Uncheck() + log.Println("[SETTINGS] Audio cues disabled") + } + + // Save settings to disk + app.saveSettings() + }) + // Quit // Add 'Quit' option quitItem := systray.AddMenuItem("Quit", "") From 396271efe35cd3564f20475e68d15a1fc09cc7c7 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Tue, 12 Aug 2025 17:35:43 -0400 Subject: [PATCH 2/3] add settings.go --- cmd/goose/settings.go | 76 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 cmd/goose/settings.go diff --git a/cmd/goose/settings.go b/cmd/goose/settings.go new file mode 100644 index 0000000..ab763e1 --- /dev/null +++ b/cmd/goose/settings.go @@ -0,0 +1,76 @@ +// Package main - settings.go provides persistent settings storage. +package main + +import ( + "encoding/json" + "log" + "os" + "path/filepath" +) + +// Settings represents persistent user settings. +type Settings struct { + EnableAudioCues bool `json:"enable_audio_cues"` + EnableReminders bool `json:"enable_reminders"` + HideStale bool `json:"hide_stale"` +} + +// loadSettings loads settings from disk or returns defaults. +func (app *App) loadSettings() { + settingsPath := filepath.Join(app.cacheDir, "settings.json") + + data, err := os.ReadFile(settingsPath) + if err != nil { + if !os.IsNotExist(err) { + log.Printf("Failed to read settings: %v", err) + } + // Use defaults + app.enableAudioCues = true + app.enableReminders = true + app.hideStaleIncoming = true + return + } + + var settings Settings + if err := json.Unmarshal(data, &settings); err != nil { + log.Printf("Failed to parse settings: %v", err) + // Use defaults + app.enableAudioCues = true + app.enableReminders = true + app.hideStaleIncoming = true + return + } + + app.enableAudioCues = settings.EnableAudioCues + app.enableReminders = settings.EnableReminders + app.hideStaleIncoming = settings.HideStale + log.Printf("Loaded settings: audio_cues=%v, reminders=%v, hide_stale=%v", + app.enableAudioCues, app.enableReminders, app.hideStaleIncoming) +} + +// saveSettings saves current settings to disk. +func (app *App) saveSettings() { + app.mu.RLock() + settings := Settings{ + EnableAudioCues: app.enableAudioCues, + EnableReminders: app.enableReminders, + HideStale: app.hideStaleIncoming, + } + app.mu.RUnlock() + + settingsPath := filepath.Join(app.cacheDir, "settings.json") + + data, err := json.MarshalIndent(settings, "", " ") + if err != nil { + log.Printf("Failed to marshal settings: %v", err) + return + } + + if err := os.WriteFile(settingsPath, data, 0o600); err != nil { + log.Printf("Failed to save settings: %v", err) + return + } + + log.Printf("Saved settings: audio_cues=%v, reminders=%v, hide_stale=%v", + settings.EnableAudioCues, settings.EnableReminders, settings.HideStale) +} From 92900eb863b94ca248cb4b2998ffe72ff1c4cca1 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Tue, 12 Aug 2025 17:41:12 -0400 Subject: [PATCH 3/3] Persist settings in UserConfigDir --- cmd/goose/settings.go | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/cmd/goose/settings.go b/cmd/goose/settings.go index ab763e1..ebf06cb 100644 --- a/cmd/goose/settings.go +++ b/cmd/goose/settings.go @@ -15,9 +15,28 @@ type Settings struct { HideStale bool `json:"hide_stale"` } +// getSettingsDir returns the configuration directory for settings. +func getSettingsDir() (string, error) { + configDir, err := os.UserConfigDir() + if err != nil { + return "", err + } + return filepath.Join(configDir, "ready-to-review"), nil +} + // loadSettings loads settings from disk or returns defaults. func (app *App) loadSettings() { - settingsPath := filepath.Join(app.cacheDir, "settings.json") + settingsDir, err := getSettingsDir() + if err != nil { + log.Printf("Failed to get settings directory: %v", err) + // Use defaults + app.enableAudioCues = true + app.enableReminders = true + app.hideStaleIncoming = true + return + } + + settingsPath := filepath.Join(settingsDir, "settings.json") data, err := os.ReadFile(settingsPath) if err != nil { @@ -50,6 +69,12 @@ func (app *App) loadSettings() { // saveSettings saves current settings to disk. func (app *App) saveSettings() { + settingsDir, err := getSettingsDir() + if err != nil { + log.Printf("Failed to get settings directory: %v", err) + return + } + app.mu.RLock() settings := Settings{ EnableAudioCues: app.enableAudioCues, @@ -58,7 +83,13 @@ func (app *App) saveSettings() { } app.mu.RUnlock() - settingsPath := filepath.Join(app.cacheDir, "settings.json") + // Ensure directory exists + if err := os.MkdirAll(settingsDir, 0o700); err != nil { + log.Printf("Failed to create settings directory: %v", err) + return + } + + settingsPath := filepath.Join(settingsDir, "settings.json") data, err := json.MarshalIndent(settings, "", " ") if err != nil {