Files
netbird/client/ui/LINUX-TRAY.md
Zoltán Papp 2af67c7023 Ship a legacy GTK3 NetBird UI package alongside the GTK4 one
Wails v3 builds on GTK4/WebKitGTK 6.0 by default, but distros that don't
ship WebKitGTK 6.0 yet (Ubuntu 22.04, Debian 12, RHEL 9, Fedora <=39)
can't install the GTK4 packages. Add a parallel legacy build via the
-tags gtk3 path (removed upstream in Wails v3.1).

The project's own XEmbed tray host links GTK4 and uses GTK4-only
popup-menu APIs, so it can't compile against GTK3. Rather than port the
~150-line C menu layer, exclude it on gtk3 builds (!gtk3 constraint on
xembed_host_linux.go + xembed_tray_linux.c) and add a pure-Go stub
(xembed_host_gtk3_linux.go) where xembedTrayAvailable() returns false.
The watcher probe then exits immediately, so the in-process XEmbed
fallback is absent on gtk3 builds -- the tray still works on desktops
that ship their own StatusNotifierWatcher, only the minimal-WM fallback
is unavailable.

goreleaser gains a netbird-ui-gtk3 build plus deb/rpm nfpm blocks (same
netbird-ui package name, GTK3/WebKit2GTK 4.1 deps) routed to dedicated
legacy repo paths. The release_ui CI job installs both dev stacks on the
runner so goreleaser builds both Linux variants natively.
2026-06-09 19:06:35 +02:00

1.9 KiB

Linux tray support (StatusNotifierWatcher + XEmbed)

Minimal WMs (Fluxbox, OpenBox, i3, dwm, vanilla GNOME without the AppIndicator extension) don't ship a StatusNotifierWatcher, so tray icons using libayatana-appindicator / freedesktop StatusNotifier silently fail. main.go calls startStatusNotifierWatcher() before NewTray so the Wails systray's RegisterStatusNotifierItem call hits the in-process watcher we control.

  • tray_watcher_linux.go — owns org.kde.StatusNotifierWatcher on the session bus if no other process has it. Safe to call unconditionally.
  • xembed_host_linux.go + xembed_tray_linux.{c,h} — when an XEmbed tray (_NET_SYSTEM_TRAY_S0) is available, also start an in-process XEmbed host that bridges the SNI icon into the XEmbed tray. Reads IconPixmap over D-Bus, draws via cairo+X11, polls for clicks, fetches com.canonical.dbusmenu.GetLayout for the popup menu, fires com.canonical.dbusmenu.Event on click.

Build is gated on linux && !386; the 386 build (no cgo) and non-Linux builds use the tray_watcher_other.go no-op.

Legacy GTK3 build (-tags gtk3)

The XEmbed host (xembed_host_linux.go + xembed_tray_linux.{c,h}) hard-links GTK4 and uses GTK4-only popup-menu APIs (GdkSurface, GtkEventControllerFocus, gtk_window_set_child, gdk_display_get_monitorsGListModel, …), so it cannot compile against GTK3. On the legacy -tags gtk3 build those files are excluded (//go:build … && !gtk3) and xembed_host_gtk3_linux.go provides a pure-Go stub where xembedTrayAvailable() returns false. The watcher probe then exits immediately, so the in-process XEmbed fallback is absent on GTK3 builds — the tray works only where the desktop ships its own StatusNotifierWatcher (KDE, GNOME+AppIndicator, Cinnamon/xapp, XFCE), not on minimal WMs. Rather than port the ~150-line C menu layer to GTK3 we accept this gap; -tags gtk3 is removed upstream in Wails v3.1.