Files
netbird/client/ui-wails
Zoltán Papp 2b272e74c8 [client/ui-wails] In-process StatusNotifierWatcher + XEmbed tray bridge
Wails3's Linux systray hands the icon off to whatever process owns
org.kde.StatusNotifierWatcher on the session bus. Bare WMs (Fluxbox,
OpenBox, i3, dwm, sway, vanilla GNOME without the AppIndicator
extension) ship no watcher, so the icon registration silently fails
and the tray never appears — leaving a tray-only app like NetBird
unreachable.

Add a Linux-only watcher fallback that claims the watcher name when
nobody else does, plus an XEmbed bridge so legacy X11 system trays
(_NET_SYSTEM_TRAY_S0) can still render the icon. Both no-op on other
platforms via build tags.

Pieces:
- tray_watcher_linux.go: claims org.kde.StatusNotifierWatcher on a
  private session bus, exports the bare RegisterStatusNotifierItem /
  RegisterStatusNotifierHost surface, and spins up an XEmbed host per
  registered SNI item.
- xembed_host_linux.go: per-item event loop. Polls X11 events with a
  50ms ticker, listens for the SNI NewIcon signal, dispatches Activate
  / context menu through dbusmenu (com.canonical.dbusmenu).
- xembed_tray_linux.{c,h}: the X11/cairo native bits. Window is created
  with CopyFromParent visual + ParentRelative background so transparent
  pixels show the toolbar beneath instead of solid black on 24-bit
  trays. cairo paints the IconPixmap with OVER blending so per-pixel
  alpha is honoured against the parent-relative base. GTK3 owns the
  context-menu popup; menu items round-trip through dbusmenu Event.
- tray_linux.go: forces WEBKIT_DISABLE_DMABUF_RENDERER=1 in init() so
  developers running `task dev` / launching the binary directly get the
  same software rendering path the .desktop launcher already enables;
  the deb/rpm Exec wrapper covers installed users.
- tray_watcher_other.go and xembed_host_other.go: build-tag stubs so
  main.go's startStatusNotifierWatcher() compiles on every platform.
- main.go: calls startStatusNotifierWatcher() before NewTray so the
  Wails systray's RegisterStatusNotifierItem call hits a watcher we
  control on bare WMs.
- build/linux/netbird-ui.desktop: regenerated by `task build` to wrap
  the dev launcher's Exec line with the WEBKIT_DISABLE_DMABUF_RENDERER
  env, matching what the tray_linux.go init does at runtime.

Adapted from work originally prototyped on the prototype/ui-wails branch.

Tested on Fluxbox (Debian 13): the icon appears in the slit/toolbar with
the toolbar's background showing through transparent pixels, left-click
opens the window, right-click brings up the GTK popup of the dbusmenu
items.
2026-05-06 16:47:35 +02:00
..

NetBird desktop UI (Wails3 + React)

Replaces client/ui (Fyne). One binary on Windows / macOS / Linux, talks to the NetBird daemon over gRPC, renders a React frontend in a WebView.

Prerequisites

  • Go ≥ 1.25, Node ≥ 20, pnpm (corepack enable && corepack prepare pnpm@latest --activate)
  • wails3 CLI: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
  • task: go install github.com/go-task/task/v3/cmd/task@latest
  • A running NetBird daemon (default: unix:///var/run/netbird.sock, Windows tcp://127.0.0.1:41731)
  • Linux only: libwebkit2gtk-4.1-dev, libgtk-3-dev, libayatana-appindicator3-dev

Develop without rebuilding

cd client/ui-wails
task dev

task dev runs Vite (port 9245) + the Go binary + a *.go watcher. Frontend edits hot-reload instantly. Go edits trigger a rebuild and relaunch. Pass daemon flags after --:

task dev -- --daemon-addr=tcp://127.0.0.1:41731

For pure UI work (no native window, fastest loop):

cd frontend && pnpm dev

Production build

task build

Output in bin/. Frontend assets are embedded into the binary.

Cross-compile Windows from Linux

Install the mingw-w64 toolchain once:

sudo apt install gcc-mingw-w64-x86-64           # Debian/Ubuntu
sudo dnf install mingw64-gcc                    # Fedora
sudo pacman -S mingw-w64-gcc                    # Arch

Then:

CGO_ENABLED=1 task windows:build

Produces bin/netbird-ui.exe. macOS cross-compile from Linux is not supported (signing and notarization need a real Mac).

Windows console build (logs in the terminal)

Default windows:build links the binary as a Windows GUI app, which detaches from the launching console — logrus output, fmt.Println, and panics go nowhere visible. To debug tray/event/daemon issues:

CGO_ENABLED=1 task windows:build:console

Produces bin/netbird-ui-console.exe. Run it from cmd.exe / PowerShell / Windows Terminal and stdout/stderr land in that terminal. Same flag works on a native Windows build (drop the CGO_ENABLED=1 if your toolchain already has it set).

Regenerating bindings

When a Go service signature changes:

wails3 generate bindings

task dev does this automatically on *.go save.

Tray icons

Source SVGs live in assets/svg/ (state.svg + state-macos.svg). After editing any SVG, rasterize to the PNGs the Go side embeds:

task common:generate:tray:icons

Requires Inkscape. Commit the resulting assets/*.png files alongside the SVG change so CI doesn't need Inkscape installed.