1. Overview
The TrayIcon module implements BastionGuard’s system tray integration using the StatusNotifierItem (SNI) specification (commonly used by KDE/Plasma and compatible hosts), and exposes an application menu through the DBusMenu interface (com.canonical.dbusmenu).
The implementation is written in modern C++ and is built around systemd sd-bus for D-Bus communication, while UI callbacks are safely marshaled onto the GTK main loop using GLib main context invocation.
- sd-bus – owns the D-Bus connection and exports SNI + DBusMenu objects
- GLib main loop – dispatches menu actions back onto the UI thread
- GdkPixbuf – loads/scales icons and converts to ARGB32 payloads for SNI pixmaps
- i18n – menu labels use
_()viaglib/gi18n.h
2. D-Bus Interfaces and Exported Objects
2.1 Exported Paths
/StatusNotifierItem– implementsorg.kde.StatusNotifierItem/Menu– implementscom.canonical.dbusmenu
2.2 StatusNotifierItem (org.kde.StatusNotifierItem)
The SNI object is exported using an sd-bus vtable (sni_vtable) and provides:
- Methods:
Activate(ii)– primary activation (e.g., left click)ContextMenu(ii)– context menu request (implemented as no-op return)SecondaryActivate(ii)– secondary activation (implemented as no-op return)
- Properties:
Category– constantApplicationStatusId–BastionGuardTitle,Status,IconNameIconPixmap– encoded asa(iiay)when a pixmap is availableToolTip– encoded as(sa(iiay)ss)Menu– constant object path/Menu
- Signals:
NewIcon,NewTitle,NewStatus,NewToolTip
2.3 DBusMenu (com.canonical.dbusmenu)
The menu object is exported via dbusmenu_vtable and provides a minimal but compatible DBusMenu implementation.
- Methods:
GetLayout(i i as) → u(ia{sv}av)– returns menu tree and revisionGetChildren(i) → ai– returns the children IDs of a menu nodeGetGroupProperties(a i a s) → a(ia{sv})– batch property queryGetProperty(i s) → v– single property queryGetStatus() → s– returnsnormalEvent(isvu)– dispatches a single menu event to the UI threadAboutToShow(i) → b– indicates whether content needs refresh (returns false)EventGroup(a(isvu)) → ai– group event dispatch (used by some hosts)AboutToShowGroup(ai) → aiai– group “about to show” handshake
- Property:
Version– constant3
- Signals:
LayoutUpdated(ui)ItemsPropertiesUpdated(a(ia{sv}) a(ias))
3. Service Naming, Bus Acquisition, and Registration
3.1 Unique Service Name
The module generates a unique well-known D-Bus service name using PID and a random suffix:
org.kde.StatusNotifierItem-<pid>-<random_4_digits>
3.2 Startup Sequence
acquire_bus()– opens a user bus connection viasd_bus_open_user()request_name()– acquires the well-known name withsd_bus_request_name()export_objects()– exports vtables for SNI and DBusMenu- spawns
bus_thread_to:- register to watcher (
RegisterStatusNotifierItem) - emit an initial
LayoutUpdatedsignal (after a short delay) - enter the D-Bus processing loop
- register to watcher (
3.3 Watcher Registration
The tray icon registers itself by calling:
org.kde.StatusNotifierWatcher.RegisterStatusNotifierItem(service_name)
Diagnostic logs are printed to stderr (e.g., success/failure plus service name).
4. Threading Model and UI Dispatch
4.1 Dedicated Bus Thread
D-Bus traffic is handled in a dedicated thread (bus_thread_) that repeatedly calls:
sd_bus_process()– processes queued messagessd_bus_wait()– waits with a bounded timeout to reduce CPU usage
4.2 UI Main Context Binding
The module binds a GLib main context (typically the default GTK main context) via:
bind_ui_context()– storesg_main_context_ref(g_main_context_default())
4.3 Safe Cross-thread Callback Invocation
Menu actions (Show/Settings/Quit) are dispatched to the UI thread using:
g_main_context_invoke(ui_ctx_, invoke_std_function, new std::function<void()>(...))
The helper invoke_std_function() executes the function and deletes it, ensuring safe lifetime management for deferred calls.
5. Menu Structure and Actions
5.1 Menu Item IDs
The menu uses fixed integer IDs (defined in the header) with a root node:
MENU_ROOT– parent nodeMENU_SHOW– “Show/Maximize window”MENU_SETTINGS– “Settings”MENU_QUIT– “Quit”
5.2 Localized Labels
Labels are produced via label_for_menu_id() and translated with _(). Example labels:
- Show/Maximize window
- Settings
- Quit
5.3 Layout Construction
GetLayout returns a revision and a tree. When depth != 0, it emits the children as variants containing (ia{sv}av) entries with properties:
label(string)enabled(boolean)
5.4 Event Handling
Hosts may trigger either Event or EventGroup. Both paths map the menu item ID to a callback:
on_activate_cb_– show/maximize main windowon_settings_cb_– open settings UIon_quit_cb_– request application shutdown
6. Icon and Tooltip Management
6.1 Icon via Theme Name
set_icon(icon_name) sets IconName (defaulting to security-high) and emits NewIcon.
6.2 Icon via Pixmap (PNG file)
set_icon_file(png_path, target_px) loads a PNG using gdk_pixbuf_new_from_file(), optionally scales it, and converts the pixel data to the SNI IconPixmap format:
- Ensures RGBA availability (
gdk_pixbuf_add_alpha) - Builds ARGB32 words (A,R,G,B) per pixel
- Encodes as big-endian (via
htobe32) - Stores bytes into
pix_rgba_fora(iiay)serialization
When a pixmap is available, IconName is intentionally returned as an empty string to prefer the pixmap. Failures disable pixmap mode and still emit NewIcon for host refresh.
6.3 Tooltip and Title
set_tooltip(text)updates tooltip and emitsNewToolTipset_title(title)updates title and emitsNewTitle
7. Lifecycle Management
7.1 Start/Stop Behavior
start()is idempotent and guarded by an atomic flag (running_)stop()stops the bus loop, joins the bus thread (unless called from within it), unexports objects and releases the bus
7.2 Export/Unexport Safety
Exported vtable slots are stored and later released via sd_bus_slot_unref() to ensure clean teardown.
8. Logging and Diagnostics
The module logs extensively to stderr using TI_LOG and direct std::fprintf, including:
- watcher registration outcome
- menu group event traces (IDs, event types, timestamps)
- warnings when UI context is not bound
- icon loading failures and fallback behavior
9. Security and Robustness Considerations
Atomic state: running state is guarded by an atomic flag to prevent double-start/double-stop races
Privilege separation: all tray operations run in user scope (user D-Bus)
Thread safety: D-Bus thread never touches GTK widgets directly; UI actions are marshaled via g_main_context_invoke()
Host compatibility: implements both Event and EventGroup to satisfy different SNI/DBusMenu hosts
Graceful fallback: if UI context is missing, activation attempts can bind the default context to avoid a hard failure