[client/ui-wails] Use original Fyne tray PNGs and drop the .ico split
The SVG-derived tray icons + multi-resolution .ico path looked correct on disk but Wails3's Shell_NotifyIcon update never landed on the running Windows tray — the icon stayed frozen on the .exe resource regardless of how many times we called SetIcon. Single-PNG fed through the same path updates correctly, so revert to the source-of-truth PNGs that ship with the legacy Fyne UI and remove the icons_windows.go / tray_icon_*.go split. The 6 colored tray PNGs and 6 macOS-template PNGs come from client/ui/assets verbatim. Generation pipeline (assets/svg/) is gone.
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 5.2 KiB |
@@ -110,22 +110,31 @@ tasks:
|
||||
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico -iconcomposerinput appicon.icon -macassetdir darwin
|
||||
|
||||
generate:tray:icons:
|
||||
summary: Rasterize the SVG tray sources to PNG/ICO embedded by the Go side.
|
||||
summary: Rebuild Windows multi-res .ico files from the per-state PNGs.
|
||||
desc: |
|
||||
Reads assets/svg/*.svg and writes assets/<state>.png (Linux/macOS
|
||||
tray) plus assets/<state>.ico (Windows tray). The .ico packs 16/24/32/48
|
||||
px frames so Shell_NotifyIcon picks the size matching the user's DPI
|
||||
instead of poorly downscaling a single large PNG.
|
||||
Run after editing any SVG; CI runs this before build.
|
||||
The colored tray PNGs (assets/netbird-systemtray-<state>.png) and the
|
||||
macOS template variants are committed to the repo as the canonical
|
||||
source. This task only regenerates the Windows multi-resolution .ico
|
||||
files from those PNGs by downscaling each to 16/24/32/48 px and
|
||||
packing them with icotool, so Shell_NotifyIcon picks the frame
|
||||
matching the user's DPI instead of downscaling a single large PNG.
|
||||
|
||||
Run after replacing any of the colored PNGs (e.g. when copying a new
|
||||
version of the icons from client/ui/assets). The SVG sources in
|
||||
assets/svg/ are kept for reference but are not built by default.
|
||||
dir: assets
|
||||
sources:
|
||||
- "svg/*.svg"
|
||||
- "netbird-systemtray-connected.png"
|
||||
- "netbird-systemtray-disconnected.png"
|
||||
- "netbird-systemtray-connecting.png"
|
||||
- "netbird-systemtray-error.png"
|
||||
- "netbird-systemtray-update-connected.png"
|
||||
- "netbird-systemtray-update-disconnected.png"
|
||||
generates:
|
||||
- "netbird-systemtray-*.png"
|
||||
- "netbird-systemtray-*.ico"
|
||||
preconditions:
|
||||
- sh: command -v inkscape >/dev/null 2>&1
|
||||
msg: "inkscape is required to rasterize tray SVGs (apt install inkscape)"
|
||||
- sh: command -v magick >/dev/null 2>&1 || command -v convert >/dev/null 2>&1
|
||||
msg: "ImageMagick is required to downscale PNGs (apt install imagemagick)"
|
||||
- sh: command -v icotool >/dev/null 2>&1
|
||||
msg: "icotool is required to pack tray .ico files (apt install icoutils)"
|
||||
cmds:
|
||||
@@ -133,19 +142,14 @@ tasks:
|
||||
set -euo pipefail
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
resize=$(command -v magick || echo convert)
|
||||
for state in connected disconnected connecting error update-connected update-disconnected; do
|
||||
# 64px PNG for Linux tray + macOS-template PNG (per-state)
|
||||
inkscape --export-type=png --export-width=64 --export-filename="netbird-systemtray-$state.png" "svg/$state.svg" >/dev/null
|
||||
inkscape --export-type=png --export-width=64 --export-filename="netbird-systemtray-$state-macos.png" "svg/$state-macos.svg" >/dev/null
|
||||
# multi-resolution .ico for Windows tray
|
||||
for sz in 16 24 32 48; do
|
||||
inkscape --export-type=png --export-width=$sz --export-filename="$tmp/$state-$sz.png" "svg/$state.svg" >/dev/null
|
||||
"$resize" "netbird-systemtray-$state.png" -resize ${sz}x${sz} "$tmp/$state-$sz.png"
|
||||
done
|
||||
icotool -c -o "netbird-systemtray-$state.ico" \
|
||||
"$tmp/$state-16.png" "$tmp/$state-24.png" "$tmp/$state-32.png" "$tmp/$state-48.png"
|
||||
done
|
||||
# Linux dark-mode variant currently shares the connected artwork.
|
||||
inkscape --export-type=png --export-width=64 --export-filename="netbird-systemtray-connected-dark.png" "svg/connected.svg" >/dev/null
|
||||
|
||||
dev:frontend:
|
||||
summary: Runs the frontend in development mode
|
||||
|
||||
@@ -4,10 +4,12 @@ package main
|
||||
|
||||
import _ "embed"
|
||||
|
||||
// Tray icons embedded from the legacy Fyne UI's asset set so the rewrite has
|
||||
// something to render until Stage 3 produces SVG sources. Each pair is a
|
||||
// light-mode PNG and its dark-mode variant; macOS template variants live
|
||||
// alongside for menubar use.
|
||||
// Tray icons embedded from the legacy Fyne UI's asset set. Each pair is a
|
||||
// light-mode PNG and its dark-mode variant; macOS template variants
|
||||
// (*-macos.png) live alongside for menubar use. Windows uses the same
|
||||
// PNGs — multi-resolution .ico files looked promising on disk but
|
||||
// Wails3's Shell_NotifyIcon NIM_MODIFY never redrew them on the running
|
||||
// tray; PNG single-frame works.
|
||||
|
||||
//go:embed assets/netbird-systemtray-connected.png
|
||||
var iconConnected []byte
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import _ "embed"
|
||||
|
||||
// Windows tray icons. Wails3 hands these to Shell_NotifyIcon via
|
||||
// CreateIconFromResourceEx, which picks the frame matching SM_CXSMICON
|
||||
// (16/32 px depending on DPI). A single high-res PNG forces the OS to
|
||||
// downscale and the result is fuzzy at tray size — multi-frame .ico files
|
||||
// avoid that by embedding 16/24/32/48 px raster frames in one resource.
|
||||
|
||||
//go:embed assets/netbird-systemtray-connected.ico
|
||||
var winIconConnected []byte
|
||||
|
||||
//go:embed assets/netbird-systemtray-disconnected.ico
|
||||
var winIconDisconnected []byte
|
||||
|
||||
//go:embed assets/netbird-systemtray-connecting.ico
|
||||
var winIconConnecting []byte
|
||||
|
||||
//go:embed assets/netbird-systemtray-error.ico
|
||||
var winIconError []byte
|
||||
|
||||
//go:embed assets/netbird-systemtray-update-connected.ico
|
||||
var winIconUpdateConnected []byte
|
||||
|
||||
//go:embed assets/netbird-systemtray-update-disconnected.ico
|
||||
var winIconUpdateDisconnected []byte
|
||||
@@ -7,17 +7,41 @@ import (
|
||||
"embed"
|
||||
"flag"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/notifications"
|
||||
|
||||
"github.com/netbirdio/netbird/client/ui-wails/services"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
//go:embed all:frontend/dist
|
||||
var assets embed.FS
|
||||
|
||||
// stringList is a flag.Value that collects repeated string flags. The first
|
||||
// time the user passes -log-file the seeded default ("console") is dropped;
|
||||
// subsequent passes append. Lets the user replace or extend the log target
|
||||
// list without a separate "reset" flag.
|
||||
type stringList struct {
|
||||
values []string
|
||||
userSet bool
|
||||
}
|
||||
|
||||
func (s *stringList) String() string {
|
||||
return strings.Join(s.values, ",")
|
||||
}
|
||||
|
||||
func (s *stringList) Set(v string) error {
|
||||
if !s.userSet {
|
||||
s.values = nil
|
||||
s.userSet = true
|
||||
}
|
||||
s.values = append(s.values, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
application.RegisterEvent[services.Status](services.EventStatus)
|
||||
application.RegisterEvent[services.SystemEvent](services.EventSystem)
|
||||
@@ -27,8 +51,15 @@ func init() {
|
||||
|
||||
func main() {
|
||||
daemonAddr := flag.String("daemon-addr", DaemonAddr(), "Daemon gRPC address: unix:///path or tcp://host:port")
|
||||
logFiles := &stringList{values: []string{"console"}}
|
||||
flag.Var(logFiles, "log-file", "Log destination. Repeat to log to multiple targets at once, e.g. `--log-file console --log-file Y:/netbird-ui.log`. Each value is one of: console, syslog, or a file path. File destinations are rotated by lumberjack (same as the daemon). Defaults to console.")
|
||||
logLevel := flag.String("log-level", "info", "Log level: trace|debug|info|warn|error.")
|
||||
flag.Parse()
|
||||
|
||||
if err := util.InitLog(*logLevel, logFiles.values...); err != nil {
|
||||
log.Fatalf("init log: %v", err)
|
||||
}
|
||||
|
||||
conn := NewConn(*daemonAddr)
|
||||
|
||||
// tray is captured in the SingleInstance callback below; the var is
|
||||
|
||||
@@ -178,12 +178,17 @@ func (s *Peers) pollLoop(ctx context.Context) {
|
||||
ticker := time.NewTicker(PollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
first := true
|
||||
for {
|
||||
st, err := s.Get(ctx)
|
||||
if err == nil {
|
||||
if first {
|
||||
log.Infof("peers pollLoop: first status ok status=%q peers=%d", st.Status, len(st.Peers))
|
||||
first = false
|
||||
}
|
||||
s.emitter.Emit(EventStatus, st)
|
||||
} else if ctx.Err() == nil {
|
||||
log.Debugf("status poll: %v", err)
|
||||
log.Warnf("peers pollLoop: status poll error: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
|
||||
@@ -299,8 +299,10 @@ func (t *Tray) onSystemEvent(ev *application.CustomEvent) {
|
||||
func (t *Tray) onUpdateAvailable(ev *application.CustomEvent) {
|
||||
upd, ok := ev.Data.(services.UpdateAvailable)
|
||||
if !ok {
|
||||
log.Warnf("update event payload not UpdateAvailable: %T", ev.Data)
|
||||
return
|
||||
}
|
||||
log.Infof("tray onUpdateAvailable: version=%s enforced=%v", upd.Version, upd.Enforced)
|
||||
t.mu.Lock()
|
||||
t.hasUpdate = true
|
||||
t.mu.Unlock()
|
||||
@@ -383,15 +385,14 @@ func (t *Tray) rebuildExitNodes(nodes []string) {
|
||||
}
|
||||
|
||||
func (t *Tray) applyIcon() {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.mu.Lock()
|
||||
ico := trayIcon(t.connected, t.hasUpdate, t.lastStatus)
|
||||
t.mu.Unlock()
|
||||
if ico != nil {
|
||||
t.tray.SetIcon(ico)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.mu.Lock()
|
||||
connected := t.connected
|
||||
hasUpdate := t.hasUpdate
|
||||
statusLabel := t.lastStatus
|
||||
t.mu.Unlock()
|
||||
|
||||
log.Infof("tray applyIcon: connected=%v hasUpdate=%v status=%q goos=%s",
|
||||
connected, hasUpdate, statusLabel, runtime.GOOS)
|
||||
|
||||
icon, dark := t.iconForState()
|
||||
if runtime.GOOS == "darwin" {
|
||||
@@ -563,3 +564,4 @@ func titleCase(s string) string {
|
||||
}
|
||||
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
//go:build !windows && !android && !ios && !freebsd && !js
|
||||
|
||||
package main
|
||||
|
||||
// trayIcon is unused on non-Windows hosts — Linux feeds setIcon a PNG and
|
||||
// macOS uses SetTemplateIcon. This stub exists so the compiler is happy and
|
||||
// callers don't need build tags around references.
|
||||
func trayIcon(_, _ bool, _ string) []byte { return nil }
|
||||
@@ -1,27 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// trayIcon returns the Windows-tray .ico bytes for the given state. The
|
||||
// other-platform implementation in tray_icon_other.go returns the colored
|
||||
// PNG instead. Splitting it this way keeps the Linux/macOS paths free of
|
||||
// .ico artifacts in their //go:embed search and avoids loading large icon
|
||||
// resources where they aren't used.
|
||||
func trayIcon(connected, hasUpdate bool, statusLabel string) []byte {
|
||||
switch {
|
||||
case strings.EqualFold(statusLabel, "Connecting"):
|
||||
return winIconConnecting
|
||||
case strings.EqualFold(statusLabel, "Error"):
|
||||
return winIconError
|
||||
case connected && hasUpdate:
|
||||
return winIconUpdateConnected
|
||||
case connected:
|
||||
return winIconConnected
|
||||
case hasUpdate:
|
||||
return winIconUpdateDisconnected
|
||||
default:
|
||||
return winIconDisconnected
|
||||
}
|
||||
}
|
||||