From 8086262a8bb6294cb8b4b5dbcf3ca3ecf6b69dcd Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 4 Apr 2023 18:42:21 -0700 Subject: [PATCH 1/6] add system tray + close to tray support (no settigs UI yet) --- backend/config.go | 4 ++++ main.go | 9 +++++++-- ui/mainwindow.go | 32 +++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/backend/config.go b/backend/config.go index 3ef76ca..413acb3 100644 --- a/backend/config.go +++ b/backend/config.go @@ -20,6 +20,8 @@ type AppConfig struct { WindowWidth int WindowHeight int LastCheckedVersion string + EnableSystemTray bool + CloseToSystemTray bool } type AlbumPageConfig struct { @@ -87,6 +89,8 @@ func DefaultConfig(appVersionTag string) *Config { WindowWidth: 1000, WindowHeight: 800, LastCheckedVersion: appVersionTag, + EnableSystemTray: true, + CloseToSystemTray: false, }, AlbumPage: AlbumPageConfig{ TracklistColumns: []string{"Artist", "Time", "Plays", "Favorite"}, diff --git a/main.go b/main.go index a0ccf0c..5d22edf 100644 --- a/main.go +++ b/main.go @@ -57,10 +57,15 @@ func main() { mainWindow.Window.SetCloseIntercept(func() { myApp.Config.Application.WindowHeight = int(mainWindow.Canvas().Size().Height) myApp.Config.Application.WindowWidth = int(mainWindow.Canvas().Size().Width) - mainWindow.Window.Close() + if myApp.Config.Application.CloseToSystemTray && + mainWindow.HaveSystemTray() { + mainWindow.Window.Hide() + } else { + fyneApp.Quit() + } }) fyneApp.Run() - // shutdown tasks + log.Println("Running shutdown tasks...") myApp.Shutdown() } diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 1536c67..83e7b90 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -40,7 +40,8 @@ type MainWindow struct { BrowsingPane *browsing.BrowsingPane BottomPanel *BottomPanel - container *fyne.Container + haveSystemTray bool + container *fyne.Container } var ( @@ -54,6 +55,9 @@ func NewMainWindow(fyneApp fyne.App, appName, appVersion string, app *backend.Ap BrowsingPane: browsing.NewBrowsingPane(app), } + if app.Config.Application.EnableSystemTray { + m.SetupSystemTrayMenu(appName, fyneApp) + } m.Controller = &controller.Controller{ AppVersion: appVersion, MainWindow: m.Window, @@ -123,6 +127,32 @@ func NewMainWindow(fyneApp fyne.App, appName, appVersion string, app *backend.Ap return m } +func (m *MainWindow) SetupSystemTrayMenu(appName string, fyneApp fyne.App) { + if desk, ok := fyneApp.(desktop.App); ok { + menu := fyne.NewMenu(appName, + fyne.NewMenuItem("Play/Pause", func() { + m.App.Player.PlayPause() + }), + fyne.NewMenuItem("Previous", func() { + m.App.Player.SeekBackOrPrevious() + }), + fyne.NewMenuItem("Next", func() { + m.App.Player.SeekNext() + }), + fyne.NewMenuItemSeparator(), + fyne.NewMenuItem("Show", func() { + m.Show() + })) + desk.SetSystemTrayMenu(menu) + desk.SetSystemTrayIcon(res.ResAppicon250Png) + m.haveSystemTray = true + } +} + +func (m *MainWindow) HaveSystemTray() bool { + return m.haveSystemTray +} + func (m *MainWindow) ShowNewVersionDialog(appName, versionTag string) { contentStr := fmt.Sprintf("A new version of %s (%s) is available", appName, versionTag) From 43616f4f318b6220c9577c0076a48e87446b4a7f Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 5 Apr 2023 08:47:50 -0700 Subject: [PATCH 2/6] add UI for system tray settings --- ui/dialogs/settingsdialog.go | 49 ++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/ui/dialogs/settingsdialog.go b/ui/dialogs/settingsdialog.go index f5a1f6d..a266557 100644 --- a/ui/dialogs/settingsdialog.go +++ b/ui/dialogs/settingsdialog.go @@ -12,7 +12,9 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) @@ -28,6 +30,7 @@ type SettingsDialog struct { config *backend.Config audioDevices []player.AudioDevice + promptText *widget.RichText content fyne.CanvasObject } @@ -41,8 +44,9 @@ func NewSettingsDialog(config *backend.Config, audioDeviceList []player.AudioDev s.createGeneralTab(), s.createPlaybackTab(), ) + s.promptText = widget.NewRichTextWithText("") s.content = container.NewVBox(tabs, widget.NewSeparator(), - container.NewHBox(layout.NewSpacer(), widget.NewButton("Close", func() { + container.NewHBox(s.promptText, layout.NewSpacer(), widget.NewButton("Close", func() { if s.OnDismiss != nil { s.OnDismiss() } @@ -52,6 +56,28 @@ func NewSettingsDialog(config *backend.Config, audioDeviceList []player.AudioDev } func (s *SettingsDialog) createGeneralTab() *container.TabItem { + closeToTray := widget.NewCheckWithData("Close to system tray", + binding.BindBool(&s.config.Application.CloseToSystemTray)) + if !s.config.Application.EnableSystemTray { + closeToTray.Disable() + } + systemTrayEnable := widget.NewCheck("Enable system tray", func(val bool) { + s.config.Application.EnableSystemTray = val + // TODO: see https://github.com/fyne-io/fyne/issues/3788 + // Once Fyne supports removing/hiding an existing system tray menu, + // the restart required prompt can be removed and this dialog + // can expose a callback for the Controller to show/hide the system tray menu. + s.setRestartRequired() + if val { + closeToTray.Enable() + } else { + closeToTray.Disable() + } + }) + systemTrayEnable.Checked = s.config.Application.EnableSystemTray + + // Scrobble settings + twoDigitValidator := func(text string, r rune) bool { return unicode.IsDigit(r) && len(text) < 2 } @@ -122,6 +148,10 @@ func (s *SettingsDialog) createGeneralTab() *container.TabItem { scrobbleEnabled.Checked = s.config.Scrobbling.Enabled return container.NewTabItem("General", container.NewVBox( + systemTrayEnable, + closeToTray, + s.newSectionSeparator(), + widget.NewRichText(&widget.TextSegment{Text: "Scrobbling", Style: boldStyle}), scrobbleEnabled, container.NewHBox( @@ -217,7 +247,8 @@ func (s *SettingsDialog) createPlaybackTab() *container.TabItem { widget.NewLabel("Audio device"), container.NewBorder(nil, nil, nil, util.NewHSpace(70), deviceSelect), layout.NewSpacer(), container.NewHBox(audioExclusive, layout.NewSpacer()), )), - container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15}, widget.NewSeparator()), + s.newSectionSeparator(), + widget.NewRichText(&widget.TextSegment{Text: "ReplayGain", Style: boldStyle}), container.New(layout.NewFormLayout(), widget.NewLabel("ReplayGain mode"), container.NewGridWithColumns(2, replayGainSelect), @@ -227,6 +258,20 @@ func (s *SettingsDialog) createPlaybackTab() *container.TabItem { )) } +func (s *SettingsDialog) setRestartRequired() { + ts := s.promptText.Segments[0].(*widget.TextSegment) + if ts.Text != "" { + return + } + ts.Text = "Restart required" + ts.Style.ColorName = theme.ColorNameError + s.promptText.Refresh() +} + +func (s *SettingsDialog) newSectionSeparator() fyne.CanvasObject { + return container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15}, widget.NewSeparator()) +} + func (s *SettingsDialog) onReplayGainSettingsChanged() { if s.OnReplayGainSettingsChanged != nil { s.OnReplayGainSettingsChanged() From f7b381c22caa7fcc10e7a41693012fd1e712295b Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 6 Apr 2023 18:27:12 -0700 Subject: [PATCH 3/6] add Hide option to system tray menu --- ui/mainwindow.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 83e7b90..296aeb9 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -131,18 +131,18 @@ func (m *MainWindow) SetupSystemTrayMenu(appName string, fyneApp fyne.App) { if desk, ok := fyneApp.(desktop.App); ok { menu := fyne.NewMenu(appName, fyne.NewMenuItem("Play/Pause", func() { - m.App.Player.PlayPause() + _ = m.App.Player.PlayPause() }), fyne.NewMenuItem("Previous", func() { - m.App.Player.SeekBackOrPrevious() + _ = m.App.Player.SeekBackOrPrevious() }), fyne.NewMenuItem("Next", func() { - m.App.Player.SeekNext() + _ = m.App.Player.SeekNext() }), fyne.NewMenuItemSeparator(), - fyne.NewMenuItem("Show", func() { - m.Show() - })) + fyne.NewMenuItem("Show", m.Window.Show), + fyne.NewMenuItem("Hide", m.Window.Hide), + ) desk.SetSystemTrayMenu(menu) desk.SetSystemTrayIcon(res.ResAppicon250Png) m.haveSystemTray = true From 565c9e98a42dd390c2848196417d6269a1918f60 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 6 Apr 2023 18:41:49 -0700 Subject: [PATCH 4/6] fix race condition on exit: disable UI playback state callbacks --- backend/app.go | 3 ++- backend/playbackmanager.go | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/backend/app.go b/backend/app.go index cd2e58c..a8d81b0 100644 --- a/backend/app.go +++ b/backend/app.go @@ -151,7 +151,8 @@ func (a *App) LoginToDefaultServer(string) error { } func (a *App) Shutdown() { - a.Player.Stop() + a.PlaybackManager.DisableCallbacks() + a.Player.Stop() // will trigger scrobble check a.Config.LocalPlayback.Volume = a.Player.GetVolume() a.cancel() a.Player.Destroy() diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 1af7591..c2b4c14 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -27,6 +27,7 @@ type PlaybackManager struct { playTimeStopwatch util.Stopwatch curTrackTime float64 + callbacksDisabled bool playQueue []*subsonic.Child nowPlayingIdx int64 @@ -99,6 +100,12 @@ func (p *PlaybackManager) IsSeeking() bool { return p.player.IsSeeking() } +// Should only be called before quitting. +// Disables playback state callbacks being sent +func (p *PlaybackManager) DisableCallbacks() { + p.callbacksDisabled = true +} + // Gets the curently playing song, if any. func (p *PlaybackManager) NowPlaying() *subsonic.Child { if len(p.playQueue) == 0 || p.player.GetStatus().State == player.Stopped { @@ -310,6 +317,9 @@ func (p *PlaybackManager) sendNowPlayingScrobble() { } func (p *PlaybackManager) invokeOnSongChangeCallbacks() { + if p.callbacksDisabled { + return + } for _, cb := range p.onSongChange { cb(p.NowPlaying(), p.lastScrobbled) } @@ -337,6 +347,9 @@ func (p *PlaybackManager) startPollTimePos() { } func (p *PlaybackManager) doUpdateTimePos() { + if p.callbacksDisabled { + return + } s := p.player.GetStatus() for _, cb := range p.onPlayTimeUpdate { cb(s.TimePos, s.Duration) From 936794382209344c88a8772f04606fe9b7dcdf32 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 6 Apr 2023 19:14:42 -0700 Subject: [PATCH 5/6] update fyne base for MacOS re-show window fix --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b5bdea1..68fa1e9 100644 --- a/go.mod +++ b/go.mod @@ -45,4 +45,4 @@ require ( honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect ) -replace fyne.io/fyne/v2 v2.3.3 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230331041414-b548301c117c +replace fyne.io/fyne/v2 v2.3.3 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230407015007-c8e2aa472ae4 diff --git a/go.sum b/go.sum index bff4200..4216cad 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7h github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230331041414-b548301c117c h1:w3Q+TShgmyoTBeWoYEZC5yflP15bkLxAzHWSmxjHQXs= -github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230331041414-b548301c117c/go.mod h1:MABZ23XXF2K24hl3rva+Di/UbMpibBMAO+qDtDCSe8k= +github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230407015007-c8e2aa472ae4 h1:YtnAxHdI0Q9Sjo3jBKZpAsxQO+WZ37S/LvxF85lCS78= +github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230407015007-c8e2aa472ae4/go.mod h1:MABZ23XXF2K24hl3rva+Di/UbMpibBMAO+qDtDCSe8k= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0= github.com/dweymouth/go-subsonic v0.0.0-20230210044542-537b9238299b h1:8JbTKYDdQg6JKu7ZDbEaVbXxutc3pRF58yNvTVMBHec= From 1b1bc84e900b98093ca6b7f957d6d44dd1b65b50 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 6 Apr 2023 19:49:03 -0700 Subject: [PATCH 6/6] update changelog with system tray --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc5eb49..4896193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - [#107](https://github.com/dweymouth/supersonic/issues/107) Add playback settings dropdown to choose output audio device - [#119](https://github.com/dweymouth/supersonic/issues/119) Add file path column to tracklist view +- [#117](https://github.com/dweymouth/supersonic/issues/117) Add (optional) system tray menu and close to tray support ### Fixes - **todo-commithash** Don't show update available prompt if the found version is the same as the running app version