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.
This commit is contained in:
Zoltán Papp
2026-06-09 19:06:35 +02:00
parent 96d31c3a5a
commit 2af67c7023
8 changed files with 163 additions and 2 deletions
+2
View File
@@ -123,6 +123,8 @@ Key conventions: `tray.*` / `notify.*` (Go-side), `common.* / connect.* / nav.*
The in-process `StatusNotifierWatcher` + XEmbed host that lets the tray work on minimal WMs is detailed in `LINUX-TRAY.md` (sibling). Touch that doc when modifying `tray_watcher_linux.go` / `xembed_host_linux.go` / `xembed_tray_linux.{c,h}`.
**Legacy `-tags gtk3` build:** Wails v3 defaults to GTK4/WebKitGTK 6.0; the legacy GTK3/WebKit2GTK 4.1 path (`-tags gtk3`, for Ubuntu 22.04 / Debian 12 / RHEL 9 / Fedora ≤39, removed upstream in Wails v3.1) is shipped as a second `netbird-ui` package built via `EXTRA_TAGS=gtk3` / a separate goreleaser lane. `xembed_host_linux.go` + `xembed_tray_linux.{c,h}` are GTK4-only (`//go:build … && !gtk3`); on gtk3 builds `xembed_host_gtk3_linux.go` stubs them out (`xembedTrayAvailable()` → false), so the minimal-WM XEmbed fallback is **absent on gtk3** (tray still works on SNI-capable desktops). Keep the C files' `//go:build` constraints in sync with the Go file.
## Wails Dialogs (frontend, `@wailsio/runtime`)
The app no longer uses native `@wailsio/runtime` `Dialogs.*` message boxes — errors go through the custom Error window (see below), confirmations through the in-app `useConfirm()` modal. `WAILS-DIALOGS.md` (sibling) is retained only as reference for the native API surface and the Go-side frameless-window pattern, should a native file picker (`OpenFile`/`SaveFile`) ever be needed.
+4
View File
@@ -6,3 +6,7 @@ Minimal WMs (Fluxbox, OpenBox, i3, dwm, vanilla GNOME without the AppIndicator e
- `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_monitors`→`GListModel`, …), 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.
+15
View File
@@ -13,6 +13,21 @@ WebView.
Windows `tcp://127.0.0.1:41731`)
- Linux only: `libwebkitgtk-6.0-dev`, `libgtk-4-dev`, `libsoup-3.0-dev`
### Legacy GTK3 build
Wails v3 builds on GTK4 / WebKitGTK 6.0 by default. Distros that don't ship
WebKitGTK 6.0 yet (Ubuntu 22.04, Debian 12, RHEL 9, Fedora ≤ 39) need the
legacy GTK3 / WebKit2GTK 4.1 build, produced with `-tags gtk3` (e.g.
`task build EXTRA_TAGS=gtk3`). It needs `libgtk-3-dev` + `libwebkit2gtk-4.1-dev`
instead of the GTK4 libs above. `-tags gtk3` is removed upstream in Wails v3.1.
> **Tray limitation:** the GTK3 build drops the in-process XEmbed
> `StatusNotifierWatcher` (its menu layer is GTK4-only — see
> [`LINUX-TRAY.md`](LINUX-TRAY.md) and `xembed_host_gtk3_linux.go`). The tray
> still works on desktops that ship their own watcher (KDE, GNOME+AppIndicator,
> Cinnamon/xapp, XFCE, …); only the minimal-WM fallback (Fluxbox/OpenBox/i3/dwm)
> is unavailable on GTK3 packages.
## Develop without rebuilding
```bash
+40
View File
@@ -0,0 +1,40 @@
//go:build linux && gtk3 && !(linux && 386)
package main
import (
"errors"
"github.com/godbus/dbus/v5"
)
// The legacy GTK3 / WebKit2GTK 4.1 build (-tags gtk3) drops the in-process
// XEmbed StatusNotifierWatcher entirely. The real implementation
// (xembed_host_linux.go + xembed_tray_linux.c) links GTK4 and uses GTK4-only
// popup-menu APIs that have no drop-in GTK3 equivalent, so rather than port the
// C layer we stub the host out on gtk3 builds. The tray still works on every
// desktop that ships its own StatusNotifierWatcher (KDE, GNOME+AppIndicator,
// Cinnamon/xapp, XFCE, …); only the minimal-WM fallback (Fluxbox/OpenBox/i3/
// dwm/vanilla GNOME) is unavailable on gtk3 packages. See LINUX-TRAY.md.
// xembedHost is a placeholder so the package compiles on gtk3 builds; the real
// type (with X11/GTK4 state) lives in xembed_host_linux.go. It is never
// instantiated here because xembedTrayAvailable always reports false.
type xembedHost struct{}
// run satisfies the call in tray_watcher_linux.go; unreachable on gtk3 because
// newXembedHost never returns a non-nil host.
func (*xembedHost) run() {}
// xembedTrayAvailable always reports false on gtk3 builds, so the watcher probe
// loop in startStatusNotifierWatcher exits immediately and newXembedHost is
// never reached. recenter_linux.go's predicate becomes a harmless no-op too.
func xembedTrayAvailable() bool {
return false
}
// newXembedHost exists only to satisfy the reference in tray_watcher_linux.go;
// it is unreachable because xembedTrayAvailable returns false on gtk3.
func newXembedHost(conn *dbus.Conn, busName string, objPath dbus.ObjectPath) (*xembedHost, error) {
return nil, errors.New("xembed host unsupported on gtk3 build")
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build linux && !(linux && 386)
//go:build linux && !gtk3 && !(linux && 386)
package main
+2
View File
@@ -1,3 +1,5 @@
//go:build linux && !gtk3 && !(linux && 386)
#include "xembed_tray_linux.h"
#include <X11/Xatom.h>