TrayIcon

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 _() via glib/gi18n.h

2. D-Bus Interfaces and Exported Objects

2.1 Exported Paths

  • /StatusNotifierItem – implements org.kde.StatusNotifierItem
  • /Menu – implements com.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 – constant ApplicationStatus
    • IdBastionGuard
    • Title, Status, IconName
    • IconPixmap – encoded as a(iiay) when a pixmap is available
    • ToolTip – 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 revision
    • GetChildren(i) → ai – returns the children IDs of a menu node
    • GetGroupProperties(a i a s) → a(ia{sv}) – batch property query
    • GetProperty(i s) → v – single property query
    • GetStatus() → s – returns normal
    • Event(isvu) – dispatches a single menu event to the UI thread
    • AboutToShow(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 – constant 3
  • 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

  1. acquire_bus() – opens a user bus connection via sd_bus_open_user()
  2. request_name() – acquires the well-known name with sd_bus_request_name()
  3. export_objects() – exports vtables for SNI and DBusMenu
  4. spawns bus_thread_ to:
    • register to watcher (RegisterStatusNotifierItem)
    • emit an initial LayoutUpdated signal (after a short delay)
    • enter the D-Bus processing loop

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 messages
  • sd_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() – stores g_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 node
  • MENU_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 window
  • on_settings_cb_ – open settings UI
  • on_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_ for a(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 emits NewToolTip
  • set_title(title) updates title and emits NewTitle

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