[client][ui] added accessible tray icons (#3335)

Added accessible tray icons with:
- dark mode support on Windows and Linux, kudos to @burgosz for the PoC
- template icon support on MacOS
Also added appropriate connecting status icons
This commit is contained in:
Karsa 2025-02-18 02:21:44 +01:00 committed by GitHub
parent 8fb5a9ce11
commit f67e56d3b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 149 additions and 33 deletions

View File

@ -53,7 +53,7 @@ nfpms:
contents:
- src: client/ui/netbird.desktop
dst: /usr/share/applications/netbird.desktop
- src: client/ui/netbird-systemtray-connected.png
- src: client/ui/netbird.png
dst: /usr/share/pixmaps/netbird.png
dependencies:
- netbird
@ -70,7 +70,7 @@ nfpms:
contents:
- src: client/ui/netbird.desktop
dst: /usr/share/applications/netbird.desktop
- src: client/ui/netbird-systemtray-connected.png
- src: client/ui/netbird.png
dst: /usr/share/pixmaps/netbird.png
dependencies:
- netbird

View File

@ -21,6 +21,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"fyne.io/systray"
"github.com/cenkalti/backoff/v4"
@ -90,6 +91,14 @@ func main() {
}
client := newServiceClient(daemonAddr, a, showSettings, showRoutes)
settingsChangeChan := make(chan fyne.Settings)
a.Settings().AddChangeListener(settingsChangeChan)
go func() {
for range settingsChangeChan {
client.updateIcon()
}
}()
if showSettings || showRoutes {
a.Run()
} else {
@ -106,46 +115,108 @@ func main() {
}
}
//go:embed netbird.ico
var iconAboutICO []byte
//go:embed netbird.png
var iconAboutPNG []byte
//go:embed netbird-systemtray-connected.ico
var iconConnectedICO []byte
//go:embed netbird-systemtray-connected.png
var iconConnectedPNG []byte
//go:embed netbird-systemtray-connected-macos.png
var iconConnectedMacOS []byte
//go:embed netbird-systemtray-connected-dark.ico
var iconConnectedDarkICO []byte
//go:embed netbird-systemtray-connected-dark.png
var iconConnectedDarkPNG []byte
//go:embed netbird-systemtray-disconnected.ico
var iconDisconnectedICO []byte
//go:embed netbird-systemtray-disconnected.png
var iconDisconnectedPNG []byte
//go:embed netbird-systemtray-disconnected-macos.png
var iconDisconnectedMacOS []byte
//go:embed netbird-systemtray-update-disconnected.ico
var iconUpdateDisconnectedICO []byte
//go:embed netbird-systemtray-update-disconnected.png
var iconUpdateDisconnectedPNG []byte
//go:embed netbird-systemtray-update-disconnected-macos.png
var iconUpdateDisconnectedMacOS []byte
//go:embed netbird-systemtray-update-disconnected-dark.ico
var iconUpdateDisconnectedDarkICO []byte
//go:embed netbird-systemtray-update-disconnected-dark.png
var iconUpdateDisconnectedDarkPNG []byte
//go:embed netbird-systemtray-update-connected.ico
var iconUpdateConnectedICO []byte
//go:embed netbird-systemtray-update-connected.png
var iconUpdateConnectedPNG []byte
//go:embed netbird-systemtray-update-cloud.ico
var iconUpdateCloudICO []byte
//go:embed netbird-systemtray-update-connected-macos.png
var iconUpdateConnectedMacOS []byte
//go:embed netbird-systemtray-update-cloud.png
var iconUpdateCloudPNG []byte
//go:embed netbird-systemtray-update-connected-dark.ico
var iconUpdateConnectedDarkICO []byte
//go:embed netbird-systemtray-update-connected-dark.png
var iconUpdateConnectedDarkPNG []byte
//go:embed netbird-systemtray-connecting.ico
var iconConnectingICO []byte
//go:embed netbird-systemtray-connecting.png
var iconConnectingPNG []byte
//go:embed netbird-systemtray-connecting-macos.png
var iconConnectingMacOS []byte
//go:embed netbird-systemtray-connecting-dark.ico
var iconConnectingDarkICO []byte
//go:embed netbird-systemtray-connecting-dark.png
var iconConnectingDarkPNG []byte
//go:embed netbird-systemtray-error.ico
var iconErrorICO []byte
//go:embed netbird-systemtray-error.png
var iconErrorPNG []byte
//go:embed netbird-systemtray-error-macos.png
var iconErrorMacOS []byte
//go:embed netbird-systemtray-error-dark.ico
var iconErrorDarkICO []byte
//go:embed netbird-systemtray-error-dark.png
var iconErrorDarkPNG []byte
type serviceClient struct {
ctx context.Context
addr string
conn proto.DaemonServiceClient
icAbout []byte
icConnected []byte
icDisconnected []byte
icUpdateConnected []byte
icUpdateDisconnected []byte
icUpdateCloud []byte
icConnecting []byte
icError []byte
// systray menu items
mStatus *systray.MenuItem
@ -214,20 +285,7 @@ func newServiceClient(addr string, a fyne.App, showSettings bool, showRoutes boo
update: version.NewUpdate(),
}
if runtime.GOOS == "windows" {
s.icConnected = iconConnectedICO
s.icDisconnected = iconDisconnectedICO
s.icUpdateConnected = iconUpdateConnectedICO
s.icUpdateDisconnected = iconUpdateDisconnectedICO
s.icUpdateCloud = iconUpdateCloudICO
} else {
s.icConnected = iconConnectedPNG
s.icDisconnected = iconDisconnectedPNG
s.icUpdateConnected = iconUpdateConnectedPNG
s.icUpdateDisconnected = iconUpdateDisconnectedPNG
s.icUpdateCloud = iconUpdateCloudPNG
}
s.setNewIcons()
if showSettings {
s.showSettingsUI()
@ -239,6 +297,63 @@ func newServiceClient(addr string, a fyne.App, showSettings bool, showRoutes boo
return s
}
func (s *serviceClient) setNewIcons() {
if runtime.GOOS == "windows" {
s.icAbout = iconAboutICO
if s.app.Settings().ThemeVariant() == theme.VariantDark {
s.icConnected = iconConnectedDarkICO
s.icDisconnected = iconDisconnectedICO
s.icUpdateConnected = iconUpdateConnectedDarkICO
s.icUpdateDisconnected = iconUpdateDisconnectedDarkICO
s.icConnecting = iconConnectingDarkICO
s.icError = iconErrorDarkICO
} else {
s.icConnected = iconConnectedICO
s.icDisconnected = iconDisconnectedICO
s.icUpdateConnected = iconUpdateConnectedICO
s.icUpdateDisconnected = iconUpdateDisconnectedICO
s.icConnecting = iconConnectingICO
s.icError = iconErrorICO
}
} else {
s.icAbout = iconAboutPNG
if s.app.Settings().ThemeVariant() == theme.VariantDark {
s.icConnected = iconConnectedDarkPNG
s.icDisconnected = iconDisconnectedPNG
s.icUpdateConnected = iconUpdateConnectedDarkPNG
s.icUpdateDisconnected = iconUpdateDisconnectedDarkPNG
s.icConnecting = iconConnectingDarkPNG
s.icError = iconErrorDarkPNG
} else {
s.icConnected = iconConnectedPNG
s.icDisconnected = iconDisconnectedPNG
s.icUpdateConnected = iconUpdateConnectedPNG
s.icUpdateDisconnected = iconUpdateDisconnectedPNG
s.icConnecting = iconConnectingPNG
s.icError = iconErrorPNG
}
}
}
func (s *serviceClient) updateIcon() {
s.setNewIcons()
s.updateIndicationLock.Lock()
if s.connected {
if s.isUpdateIconActive {
systray.SetTemplateIcon(iconUpdateConnectedMacOS, s.icUpdateConnected)
} else {
systray.SetTemplateIcon(iconConnectedMacOS, s.icConnected)
}
} else {
if s.isUpdateIconActive {
systray.SetTemplateIcon(iconUpdateDisconnectedMacOS, s.icUpdateDisconnected)
} else {
systray.SetTemplateIcon(iconDisconnectedMacOS, s.icDisconnected)
}
}
s.updateIndicationLock.Unlock()
}
func (s *serviceClient) showSettingsUI() {
// add settings window UI elements.
s.wSettings = s.app.NewWindow("NetBird Settings")
@ -376,8 +491,10 @@ func (s *serviceClient) login() error {
}
func (s *serviceClient) menuUpClick() error {
systray.SetTemplateIcon(iconConnectingMacOS, s.icConnecting)
conn, err := s.getSrvClient(defaultFailTimeout)
if err != nil {
systray.SetTemplateIcon(iconErrorMacOS, s.icError)
log.Errorf("get client: %v", err)
return err
}
@ -407,6 +524,7 @@ func (s *serviceClient) menuUpClick() error {
}
func (s *serviceClient) menuDownClick() error {
systray.SetTemplateIcon(iconConnectingMacOS, s.icConnecting)
conn, err := s.getSrvClient(defaultFailTimeout)
if err != nil {
log.Errorf("get client: %v", err)
@ -458,9 +576,9 @@ func (s *serviceClient) updateStatus() error {
s.connected = true
s.sendNotification = true
if s.isUpdateIconActive {
systray.SetIcon(s.icUpdateConnected)
systray.SetTemplateIcon(iconUpdateConnectedMacOS, s.icUpdateConnected)
} else {
systray.SetIcon(s.icConnected)
systray.SetTemplateIcon(iconConnectedMacOS, s.icConnected)
}
systray.SetTooltip("NetBird (Connected)")
s.mStatus.SetTitle("Connected")
@ -482,11 +600,9 @@ func (s *serviceClient) updateStatus() error {
s.isUpdateIconActive = s.update.SetDaemonVersion(status.DaemonVersion)
if !s.isUpdateIconActive {
if systrayIconState {
systray.SetIcon(s.icConnected)
s.mAbout.SetIcon(s.icConnected)
systray.SetTemplateIcon(iconConnectedMacOS, s.icConnected)
} else {
systray.SetIcon(s.icDisconnected)
s.mAbout.SetIcon(s.icDisconnected)
systray.SetTemplateIcon(iconDisconnectedMacOS, s.icDisconnected)
}
}
@ -517,9 +633,9 @@ func (s *serviceClient) updateStatus() error {
func (s *serviceClient) setDisconnectedStatus() {
s.connected = false
if s.isUpdateIconActive {
systray.SetIcon(s.icUpdateDisconnected)
systray.SetTemplateIcon(iconUpdateDisconnectedMacOS, s.icUpdateDisconnected)
} else {
systray.SetIcon(s.icDisconnected)
systray.SetTemplateIcon(iconDisconnectedMacOS, s.icDisconnected)
}
systray.SetTooltip("NetBird (Disconnected)")
s.mStatus.SetTitle("Disconnected")
@ -529,7 +645,7 @@ func (s *serviceClient) setDisconnectedStatus() {
}
func (s *serviceClient) onTrayReady() {
systray.SetIcon(s.icDisconnected)
systray.SetTemplateIcon(iconDisconnectedMacOS, s.icDisconnected)
systray.SetTooltip("NetBird")
// setup systray menu items
@ -554,7 +670,7 @@ func (s *serviceClient) onTrayReady() {
systray.AddSeparator()
s.mAbout = systray.AddMenuItem("About", "About")
s.mAbout.SetIcon(s.icDisconnected)
s.mAbout.SetIcon(s.icAbout)
versionString := normalizedVersion(version.NetbirdVersion())
s.mVersionUI = s.mAbout.AddSubMenuItem(fmt.Sprintf("GUI: %s", versionString), fmt.Sprintf("GUI Version: %s", versionString))
s.mVersionUI.Disable()
@ -771,9 +887,9 @@ func (s *serviceClient) onUpdateAvailable() {
s.isUpdateIconActive = true
if s.connected {
systray.SetIcon(s.icUpdateConnected)
systray.SetTemplateIcon(iconUpdateConnectedMacOS, s.icUpdateConnected)
} else {
systray.SetIcon(s.icUpdateDisconnected)
systray.SetTemplateIcon(iconUpdateDisconnectedMacOS, s.icUpdateDisconnected)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
client/ui/netbird.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB