Skip to content

D Bus API

locainin edited this page Jul 6, 2026 · 4 revisions

D-Bus API

This document describes the D-Bus surface for UnixNotis. The daemon exposes two interfaces on the user session bus: the Freedesktop.org notification interface and the UnixNotis control plane.

Stability policy

  • org.freedesktop.Notifications is the public compatibility surface.
  • com.unixnotis.Control is a privileged control plane intended for bundled UnixNotis clients such as noticenterctl, unixnotis-center, and unixnotis-popups.
  • Additive changes (new methods, fields, signals) are non-breaking for bundled clients.
  • Removing or changing existing methods, fields, or semantics is a breaking change.

org.freedesktop.Notifications

  • Bus name: org.freedesktop.Notifications
  • Object path: /org/freedesktop/Notifications
  • Interface: org.freedesktop.Notifications

This interface follows the Freedesktop.org notification specification, including Notify, CloseNotification, and notification capability reporting.

Methods:

  • GetCapabilities() -> (string[])
  • Notify(app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout) -> (u32)
  • CloseNotification(id: u32) -> ()
  • GetServerInformation() -> (name, vendor, version, spec_version)

Signals:

  • NotificationClosed(id: u32, reason: u32)
  • ActionInvoked(id: u32, action_key: string)

Reported capabilities:

  • actions
  • body
  • body-markup
  • icon-static
  • sound when a playback backend is available

GetServerInformation reports UnixNotis as both name and vendor, the package version as the server version, and notification spec version 1.2.

Notify replacement is ownership checked. A non-zero replaces_id preserves the ID only when it points to an existing notification owned by the same D-Bus sender or the same process lifetime. Otherwise UnixNotis assigns a new ID.

CloseNotification is also ownership checked. Unauthorized close requests are ignored instead of closing another app's notification.

Introspection

busctl --user introspect org.freedesktop.Notifications /org/freedesktop/Notifications

com.unixnotis.Control

  • Bus name: com.unixnotis.Control
  • Object path: /com/unixnotis/Control
  • Interface: com.unixnotis.Control

Only trusted UnixNotis binaries owned by the current desktop user are allowed to call control methods. Third-party automation should shell out to noticenterctl instead of calling this interface directly.

Authorization checks use the D-Bus sender identity supplied by the session bus. The daemon verifies:

  • the caller UID matches the daemon user
  • the caller executable name is one of the trusted UnixNotis binaries
  • the caller executable lives in the trusted install/build directory
  • in normal mode, the executable still matches the startup snapshot for that path

Trial mode relaxes the path check enough for local debug/release rebuilds and $HOME/.local/bin keybinds, but it still requires a trusted UnixNotis binary name and safe file metadata.

State

ControlState contains the daemon control-plane state:

  • dnd_enabled (bool): current Do Not Disturb state.
  • history_count (u32): total history entries.
  • inhibited (bool): true when popups are suppressed by an inhibitor.
  • inhibitor_count (u32): total active inhibitors.

Methods

  • GetState() -> (ControlState)
  • ListActive() -> (NotificationView[])
  • ListHistory() -> (NotificationView[])
  • GetActiveNotification(id: u32) -> (NotificationView[])
  • OpenPanel() -> ()
  • OpenPanelDebug(level: PanelDebugLevel) -> ()
  • ClosePanel() -> ()
  • TogglePanel() -> ()
  • SetDnd(enabled: bool) -> ()
  • ToggleDnd() -> ()
  • Dismiss(id: u32) -> ()
  • InvokeAction(id: u32, action_key: string) -> ()
  • ClearAll() -> ()
  • ClearActive() -> ()
  • ClearHistory() -> ()
  • Inhibit(reason: string, scope: u32) -> (u64)
  • Uninhibit(id: u64) -> ()
  • ListInhibitors() -> ([(u64, string, u32, string)])
  • MarkPanelReady() -> ()
  • MarkPanelNotReady() -> ()

GetActiveNotification returns either an empty array or one NotificationView. The add/update signals carry only the notification ID, so bundled UI clients use this method when they need to pull the current full payload.

Panel methods fail with unixnotis-center is unavailable until the center has subscribed to panel requests and called MarkPanelReady. The readiness methods are restricted to the bundled center process.

Signals

  • NotificationAdded(id: u32, show_popup: bool)
  • NotificationUpdated(id: u32, show_popup: bool)
  • NotificationClosed(id: u32, reason: CloseReason)
  • StateChanged(state: ControlState)
  • PopupGateChanged(gate: PopupGateState)
  • SnapshotInvalidated()
  • InhibitorsChanged(active: bool, count: u32)
  • PanelRequested(request: PanelRequest)

show_popup is already filtered through DND and inhibitor state. When it is false, the notification may still be stored for the panel or history depending on configuration and rules.

SnapshotInvalidated means clients should refresh their local active/history lists from the daemon. It is emitted after broad changes such as clear-all where a compact signal is cheaper than sending every row again.

PopupGateChanged contains only the fields that affect toast visibility:

  • dnd_enabled (bool)
  • inhibited (bool)

This lets popup clients avoid refreshing on panel-only state changes.

Data types

NotificationView contains the UI-facing notification fields:

  • id (u32)
  • app_name (string)
  • summary (string)
  • body (string)
  • actions (Action[])
  • urgency (u8: 0 low, 1 normal, 2 critical)
  • is_transient (bool)
  • image (NotificationImage)

Action contains:

  • key (string)
  • label (string)

NotificationImage contains:

  • has_image_data (bool)
  • image_data with width, height, rowstride, has_alpha, bits_per_sample, channels, data
  • image_path (string)
  • icon_name (string)

Image data is bounded before it reaches UI clients. Large raw image payloads are rejected, and list/history views avoid carrying raw image bytes.

CloseReason values follow the notification spec:

  • 1: expired
  • 2: dismissed by the user
  • 3: closed by a D-Bus call
  • 4: undefined

PanelAction values:

  • 0: open
  • 1: close
  • 2: toggle

PanelDebugLevel values:

  • 0: off
  • 1: critical
  • 2: warn
  • 3: info
  • 4: verbose

Inhibit semantics

  • Inhibit returns a unique token for the caller.
  • Inhibitors are owned by the unique bus name (for example :1.42).
  • Uninhibit only succeeds for the owning connection. Owner mismatch returns an access denied error.
  • Unknown inhibitor IDs are treated as a no-op.
  • When the owning client disconnects, its inhibitors are removed automatically.
  • Active inhibitors are capped at 128 to keep memory use bounded.
  • Reasons are sanitized before storage and listing.

Scope values

scope is a u32 with the following meaning:

  • 0: inhibit all notification output.
  • 1: inhibit popup rendering.

Additional values are reserved for future expansion. Values without a supported popup-suppression bit are rejected as unsupported. Values that include the popup bit are normalized to the supported popup scope.

Popup suppression mode

The configuration section inhibit.mode controls how inhibited notifications are handled:

  • no_popups (default): notifications are stored and emitted, but popups are suppressed while inhibited.
  • drop_all: notifications are ignored while inhibited (no store, no popups).

DND persistence

The daemon persists DND state to:

  • $XDG_STATE_HOME/unixnotis/state.json, or
  • $HOME/.local/state/unixnotis/state.json if XDG_STATE_HOME is unset.

Writes are atomic (temp + rename) and best-effort durable across power loss.

DND affects popup and sound gating. Critical urgency notifications are still allowed to show popups and play sound while DND is enabled. Inhibitors are stricter than DND and suppress popups and sound.

Notification payload limits

Incoming Freedesktop notifications are sanitized before they are stored or exposed through the control interface:

  • app name: 256 bytes
  • app icon: 1024 bytes
  • summary: 1024 bytes
  • body: 16 KiB
  • category: 256 bytes
  • action pairs: 8
  • action key: 128 bytes
  • action label: 256 bytes
  • stored hint entries: 16
  • hint key: 64 bytes
  • string hint value: 2048 bytes

Stored hints are limited to values UnixNotis actually uses: sound-name, sound-file, category, transient, resident, suppress-sound, and urgency.

Very long unbroken text runs are folded so one notification cannot force extreme widget width.

Client guidance

  • Treat this interface as privileged and implementation-coupled.
  • Subscribe to control signals before issuing GetState/ListActive/ListHistory so match rules are installed early and events are buffered during the seed round.
  • Prefer noticenterctl for scripts, keybinds, and third-party integrations. Direct D-Bus calls from unrelated binaries are expected to be rejected.

Introspection

busctl --user introspect com.unixnotis.Control /com/unixnotis/Control

Clone this wiki locally