diff --git a/Makefile b/Makefile index 72f254e..cc67ecf 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ APP_BUNDLE = $(APP_NAME).app GO = go CGO_ENABLED = 1 GIT_HASH = $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) -VERSION = 0.2.6-$(GIT_HASH) +VERSION = 0.3.2-$(GIT_HASH) LDFLAGS = -s -w -X lmvpn/internal/version.Version=$(VERSION) .PHONY: all build app run daemon clean vet tidy fmt icon diff --git a/internal/ui/app.go b/internal/ui/app.go index 2adc280..1b7f0a2 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -112,13 +112,26 @@ func Run() { hideDockIcon() a.window.Hide() } else { - a.fyneApp.Quit() + a.quit() } }) a.window.ShowAndRun() } +// quit disconnects any active VPN session (via the daemon) and then +// quits the application. The daemon process itself remains running. +func (a *App) quit() { + a.mu.Lock() + client := a.ipcClient + a.ipcClient = nil + a.mu.Unlock() + if client != nil { + _ = ipc.SendStop(client) + } + a.fyneApp.Quit() +} + // loadProfiles loads all profiles from the database and populates the // selector. func (a *App) loadProfiles() { @@ -218,18 +231,17 @@ func (a *App) onResetDB() { // Reset UI state. a.currentProfile = nil a.loadProfiles() - a.stateLabel.SetText(i18n.T("StateDisconnected")) - a.ipLabel.SetText(i18n.T("IpNone")) - a.ip6Label.SetText(i18n.T("Ip6None")) - a.uptimeLabel.SetText(i18n.T("UptimeNone")) - a.rxV4Label.SetText(i18n.T("RxV4Zero")) - a.txV4Label.SetText(i18n.T("TxV4Zero")) - a.rxV6Label.SetText(i18n.T("RxV6Zero")) - a.txV6Label.SetText(i18n.T("TxV6Zero")) - a.rxTotalLabel.SetText(i18n.T("RxTotalZero")) - a.txTotalLabel.SetText(i18n.T("TxTotalZero")) - a.connectBtn.Enable() - a.disconnectBtn.Disable() + a.stateLabel.SetText(i18n.T("StateDisconnected")) + a.ipLabel.SetText(i18n.T("IpNone")) + a.ip6Label.SetText(i18n.T("Ip6None")) + a.uptimeLabel.SetText(i18n.T("UptimeNone")) + a.rxV4Label.SetText(i18n.T("RxV4Zero")) + a.txV4Label.SetText(i18n.T("TxV4Zero")) + a.rxV6Label.SetText(i18n.T("RxV6Zero")) + a.txV6Label.SetText(i18n.T("TxV6Zero")) + a.rxTotalLabel.SetText(i18n.T("RxTotalZero")) + a.txTotalLabel.SetText(i18n.T("TxTotalZero")) + a.setConnButtons(true, false) }, a.window).Show() } @@ -296,12 +308,10 @@ func (a *App) rebuildUI() { // Restore connection state. if wasConnected { - a.connectBtn.Disable() - a.disconnectBtn.Enable() + a.setConnButtons(false, true) a.stateLabel.SetText(i18n.T("StateConnected")) } else { - a.connectBtn.Enable() - a.disconnectBtn.Disable() + a.setConnButtons(true, false) a.stateLabel.SetText(i18n.T("StateDisconnected")) } diff --git a/internal/ui/tray.go b/internal/ui/tray.go index 6af9d2e..a1457cd 100644 --- a/internal/ui/tray.go +++ b/internal/ui/tray.go @@ -46,24 +46,33 @@ func (a *App) setupTray() { autoItem, enItem, zhItem, ) - menu := fyne.NewMenu(i18n.T("WindowTitle"), - fyne.NewMenuItem(i18n.T("TrayShowWindow"), func() { - showDockIcon() - a.window.Show() - a.window.RequestFocus() - }), - fyne.NewMenuItemSeparator(), - fyne.NewMenuItem(i18n.T("TrayConnect"), func() { + connectItem := fyne.NewMenuItem(i18n.T("TrayConnect"), func() { a.onConnect() - }), - fyne.NewMenuItem(i18n.T("TrayDisconnect"), func() { + }) + disconnectItem := fyne.NewMenuItem(i18n.T("TrayDisconnect"), func() { a.onDisconnect() - }), + }) + if a.connectBtn != nil { + connectItem.Disabled = a.connectBtn.Disabled() + } + if a.disconnectBtn != nil { + disconnectItem.Disabled = a.disconnectBtn.Disabled() + } + + menu := fyne.NewMenu(i18n.T("WindowTitle"), + fyne.NewMenuItem(i18n.T("TrayShowWindow"), func() { + showDockIcon() + a.window.Show() + a.window.RequestFocus() + }), + fyne.NewMenuItemSeparator(), + connectItem, + disconnectItem, fyne.NewMenuItemSeparator(), langItem, fyne.NewMenuItemSeparator(), fyne.NewMenuItem(i18n.T("TrayQuit"), func() { - a.fyneApp.Quit() + a.quit() }), ) deskApp.SetSystemTrayMenu(menu) diff --git a/internal/ui/view.go b/internal/ui/view.go index 174d07a..fa3d3e0 100644 --- a/internal/ui/view.go +++ b/internal/ui/view.go @@ -19,6 +19,22 @@ func showError(title, message string, parent fyne.Window) { dialog.NewCustom(title, i18n.T("BtnOK"), widget.NewLabel(message), parent).Show() } +// setConnButtons enables/disables the connect and disconnect buttons +// and refreshes the tray menu to match. +func (a *App) setConnButtons(connectEnabled, disconnectEnabled bool) { + if connectEnabled { + a.connectBtn.Enable() + } else { + a.connectBtn.Disable() + } + if disconnectEnabled { + a.disconnectBtn.Enable() + } else { + a.disconnectBtn.Disable() + } + a.setupTray() +} + // buildMainWindow creates the main application window layout. func (a *App) buildMainWindow() fyne.CanvasObject { // Profile selector. @@ -78,8 +94,7 @@ func (a *App) onConnect() { return } - a.connectBtn.Disable() - a.disconnectBtn.Enable() + a.setConnButtons(false, true) a.stateLabel.SetText(i18n.T("StateConnecting")) go func() { @@ -89,8 +104,7 @@ func (a *App) onConnect() { fyne.Do(func() { showError(i18n.T("DlgDaemonError"), err.Error(), a.window) a.stateLabel.SetText(i18n.T("StateDisconnected")) - a.connectBtn.Enable() - a.disconnectBtn.Disable() + a.setConnButtons(true, false) }) return } @@ -107,8 +121,7 @@ func (a *App) onConnect() { i18n.T("DlgCredentialErrorMsg"), a.window) a.stateLabel.SetText(i18n.T("StateDisconnected")) - a.connectBtn.Enable() - a.disconnectBtn.Disable() + a.setConnButtons(true, false) }) return } @@ -137,8 +150,7 @@ func (a *App) onConnect() { fyne.Do(func() { showError(i18n.T("DlgIPCError"), err.Error(), a.window) a.stateLabel.SetText(i18n.T("StateDisconnected")) - a.connectBtn.Enable() - a.disconnectBtn.Disable() + a.setConnButtons(true, false) }) return } @@ -157,8 +169,7 @@ func (a *App) onDisconnect() { return } _ = ipc.SendStop(client) - a.disconnectBtn.Disable() - a.connectBtn.Enable() + a.setConnButtons(true, false) a.stateLabel.SetText(i18n.T("StateDisconnected")) } @@ -189,8 +200,7 @@ func (a *App) eventLoop() { a.txV6Label.SetText(i18n.T("TxV6Zero")) a.rxTotalLabel.SetText(i18n.T("RxTotalZero")) a.txTotalLabel.SetText(i18n.T("TxTotalZero")) - a.connectBtn.Enable() - a.disconnectBtn.Disable() + a.setConnButtons(true, false) } }) return @@ -222,26 +232,21 @@ func (a *App) applyState(state string) { switch stats.State(state) { case stats.StateConnected: a.stateLabel.SetText(i18n.T("StateConnected")) - a.connectBtn.Disable() - a.disconnectBtn.Enable() + a.setConnButtons(false, true) case stats.StateConnecting: a.stateLabel.SetText(i18n.T("StateConnecting")) - a.connectBtn.Disable() - a.disconnectBtn.Enable() + a.setConnButtons(false, true) case stats.StateReconnecting: a.stateLabel.SetText(i18n.T("StateReconnecting")) - a.connectBtn.Disable() - a.disconnectBtn.Enable() + a.setConnButtons(false, true) case stats.StateDisconnected: a.stateLabel.SetText(i18n.T("StateDisconnected")) a.ipLabel.SetText(i18n.T("IpNone")) a.ip6Label.SetText(i18n.T("Ip6None")) - a.connectBtn.Enable() - a.disconnectBtn.Disable() + a.setConnButtons(true, false) case stats.StateError: a.stateLabel.SetText(i18n.T("StateError")) - a.connectBtn.Enable() - a.disconnectBtn.Disable() + a.setConnButtons(true, false) } }