Files
netbird/client/ui-wails/xembed_tray_linux.h
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

72 lines
3.0 KiB
C

#ifndef XEMBED_TRAY_H
#define XEMBED_TRAY_H
#include <X11/Xlib.h>
// xembed_default_screen wraps the DefaultScreen macro for CGo.
static inline int xembed_default_screen(Display *dpy) {
return DefaultScreen(dpy);
}
// xembed_find_tray returns the selection owner window for
// _NET_SYSTEM_TRAY_S{screen}, or 0 if no XEmbed tray manager exists.
Window xembed_find_tray(Display *dpy, int screen);
// xembed_get_icon_size queries _NET_SYSTEM_TRAY_ICON_SIZE from the tray
// manager window. Returns the size in pixels, or 0 if not set.
int xembed_get_icon_size(Display *dpy, Window tray_mgr);
// xembed_create_icon creates a tray icon window of the given size,
// sets _XEMBED_INFO, and returns the window ID.
// tray_mgr is the tray manager window; its _NET_SYSTEM_TRAY_VISUAL
// property is queried to obtain a 32-bit ARGB visual for transparency.
Window xembed_create_icon(Display *dpy, int screen, int size, Window tray_mgr);
// xembed_dock sends _NET_SYSTEM_TRAY_OPCODE SYSTEM_TRAY_REQUEST_DOCK
// to the tray manager to embed our icon window.
int xembed_dock(Display *dpy, Window tray_mgr, Window icon_win);
// xembed_draw_icon draws ARGB pixel data onto the icon window.
// data is in [A,R,G,B] byte order per pixel (SNI IconPixmap format).
// img_w, img_h are the source image dimensions.
// win_size is the target window dimension (square).
void xembed_draw_icon(Display *dpy, Window icon_win, int win_size,
const unsigned char *data, int img_w, int img_h);
// xembed_destroy_icon destroys the icon window.
void xembed_destroy_icon(Display *dpy, Window icon_win);
// xembed_poll_event processes pending X11 events. Returns:
// 0 = no actionable event
// 1 = left button press (out_x, out_y filled)
// 2 = right button press (out_x, out_y filled)
// 3 = expose (needs redraw)
// 4 = configure (resize; out_x=width, out_y=height)
// -1 = DestroyNotify on icon window (tray died)
int xembed_poll_event(Display *dpy, Window icon_win,
int *out_x, int *out_y);
// Callback type for menu item clicks. Called with the item's dbusmenu ID.
typedef void (*xembed_menu_click_cb)(int id);
// xembed_popup_menu builds and shows a GTK3 popup menu.
// items is an array of menu item descriptors, count is the number of items.
// cb is called (from the GTK main thread) when an item is clicked.
// x, y are root coordinates for positioning the popup.
// This must be called from the GTK main thread (use g_idle_add).
typedef struct {
int id; // dbusmenu item ID
const char *label; // display label (NULL for separator)
int enabled; // whether the item is clickable
int is_check; // whether this is a checkbox item
int checked; // checkbox state (0 or 1)
int is_separator;// 1 if this is a separator
} xembed_menu_item;
// Schedule a GTK popup menu on the main thread.
void xembed_show_popup_menu(xembed_menu_item *items, int count,
xembed_menu_click_cb cb, int x, int y);
#endif