Files
netbird/client/ui/LINUX-TRAY.md
Zoltán Papp b0f08645c3 fix(ui): install X11 error handler so tray races don't kill the UI
Xlib's default protocol-error handler calls exit() on any async X error,
so a tray race (tray manager window dying between find and dock, XMoveWindow
on a popup the WM already destroyed) would take the whole UI process down.

Install process-global logging no-op handlers via XSetErrorHandler/
XSetIOErrorHandler after every XOpenDisplay. Since the handler slot is
process-global and GDK init can clobber it, additionally wrap the popup
code's raw Xlib calls on GDK's Display in gdk_x11_display_error_trap_push/
pop_ignored, which is independent of the global handler.
2026-06-15 12:26:53 +02:00

2.1 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.

X error handling. Xlib's default protocol-error handler calls exit(), so a single async X error from a tray race (tray manager window dying between xembed_find_tray and xembed_dock; XMoveWindow on a popup the WM already destroyed) would take the whole UI process down. xembed_install_error_handlers() (xembed_tray_linux.c) installs process-global logging no-op handlers via XSetErrorHandler/XSetIOErrorHandler; it's idempotent and called from the Go side after every XOpenDisplay (newXembedHost, xembedTrayAvailable). Because the handler slot is process-global, GTK's GDK init can clobber it (whoever installs last wins) — so the popup code's raw Xlib calls on GDK's Display (x11_move_window, the submenu XTranslateCoordinates) are additionally wrapped in GDK's own gdk_x11_display_error_trap_push/_pop_ignored, which is independent of the global handler. (Those GDK X11 backend calls warn -Wdeprecated-declarations in GTK4, like the existing gdk_x11_surface_get_xid/gdk_x11_display_get_xdisplay calls — expected, still functional.)

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