# D-Bus
`dbus/*` is a pure-Zig D-Bus implementation with both client and server
roles: connect to the session or system bus, call methods, export
objects, emit and subscribe to signals. One `[dbus/connect]` is one
connection (one socket to the bus daemon). It is routed on the owning
VM thread through the event loop, so exported handlers and subscription
closures run inline as Deft code, and method calls return futures that
integrate with `[await]`.
There is no libdbus dependency and no bus configuration to write — you
talk to the session bus by default, or the system bus with
`%{ :system true }`.
## Connections and Names
D-Bus separates connections from names:
- Every connection gets a unique name assigned by the bus (`:1.5`,
`:1.42`, …), readable with `[dbus/unique-name $conn]`. Unique names
belong to one connection and change on reconnect — they are useful
for addressing a specific peer, not for a stable service.
- A well-known name (`org.freedesktop.Notifications`,
`org.deft.myapp`) is a bus-level alias that at most one connection
owns at a time. Callers address the name, never the process, and
the bus routes every call to whoever currently owns it.
Any number of connections can be on the bus, and any number of them can
call a well-known name — but only one can be its primary owner.
That single-owner rule is the part that surprises people: two daemons
cannot both serve `org.freedesktop.Notifications`. The first one owns
it; the second is either queued behind it or refused, and every
`Notify` keeps going to the incumbent.
A connection loses its names when it disconnects (`dbus/disconnect`,
the GC finalizer, or process exit), at which point the bus promotes the
next queued requester.
## Requesting a Name
set conn [dbus/connect] set rc [await [dbus/register-name $conn "org.deft.myapp" 7]]
`[dbus/register-name $conn "name" ?flags]` returns a future resolving
to a numeric `RequestName` result:
| Result | Meaning |
|---|---|
| `1` | Primary owner — calls route here |
| `2` | In the queue — another owner exists |
| `3` | Exists and was not queued — another owner exists and `DO_NOT_QUEUE` was set |
| `4` | Already the owner |
The optional flags are a bitmask:
| Flag | Value | Meaning |
|---|---|---|
| `ALLOW_REPLACEMENT` | `1` | Another requester with `REPLACE_EXISTING` may take the name from us |
| `REPLACE_EXISTING` | `2` | Try to take the name from its current owner |
| `DO_NOT_QUEUE` | `4` | If the name can't be acquired, fail with result `3` instead of queueing |
Replacement is mutual. `REPLACE_EXISTING` only succeeds when the
current owner requested the name with `ALLOW_REPLACEMENT`. This is a
deliberate D-Bus security property (name hijacking was fixed in
CVE-2008-4311): a service that does not opt in cannot be displaced from
the outside, no matter what flags the newcomer passes. The only way to
take its name is for it to disconnect or exit.
Useful combinations:
| Flags | Behaviour |
|---|---|
| `0` | Classic service: first wins, latecomers queue silently |
| `4` | Fail fast: `3` when busy instead of queueing |
| `6` | Take over if allowed, otherwise report `3` with no silent queue |
| `7` | As `6`, plus allow a future daemon to replace us |
The distinction between "owned" and "busy" matters for anything that
consumes a shared name. Without `DO_NOT_QUEUE` a request can succeed
with result `2` and then wait forever behind an owner that never
exits — the connection looks healthy but receives nothing.
### Worked example: notification daemons
On a GNOME session, GNOME Shell's notification service owns
`org.freedesktop.Notifications` and requests it without
`ALLOW_REPLACEMENT`. A deft daemon asking with flags `6` or `7`
therefore gets result `3`, not a takeover — `notify-send` keeps going
to GNOME. The deft daemon can report who holds the name by calling the
bus directly:
[await [dbus/call $conn "org.freedesktop.DBus" "/org/freedesktop/DBus" \ "org.freedesktop.DBus" "GetNameOwner" "s" "org.freedesktop.Notifications"]] [await [dbus/call $conn "org.freedesktop.DBus" "/org/freedesktop/DBus" \ "org.freedesktop.DBus" "GetConnectionUnixProcessID" "s" $owner]]
To hand the name over, stop the incumbent (e.g. kill the process, or
mask its D-Bus activation) and re-request; a daemon that retries
periodically takes over the moment the name becomes free.
## Custom Names Between Deft Instances
Well-known names are just strings in the bus namespace, so deft
processes can define their own services — `org.deft.<app>`,
`com.example.<service>.<instance>`, whatever fits. This is the
interoperable path between separate OS processes and to/from foreign
programs.
A server registers its name and exports an interface:
# server.dft — run in its own deft process set conn [dbus/connect] set rc [await [dbus/register-name $conn "org.deft.notify.1" 7]] [dbus/export $conn "/org/deft/notify" "org.deft.Notify" %{ "Send" {|text| [handle-notification $text]} }] [vwait]
A client calls it:
# client.dft set conn [dbus/connect] [await [dbus/call $conn "org.deft.notify.1" "/org/deft/notify" \ "org.deft.Notify" "Send" "s" "hello"]]
Give each instance a distinct name (`org.deft.worker.1`,
`org.deft.worker.2`, …) when several must coexist; use flags `6`/`7` to
control replacements when they share one. Instances can discover each
other by asking the bus for existing names:
set names [await [dbus/call $conn "org.freedesktop.DBus" "/org/freedesktop/DBus" \ "org.freedesktop.DBus" "ListNames"]]
For fan-out instead of request/response, emit signals and subscribe to
them by match rule:
# publisher [dbus/emit $conn "/org/deft/notify" "org.deft.Notify" "Posted" "s" $text] # subscriber — closure receives %{ :sender :path :interface :member :args } [dbus/subscribe $conn "type='signal',interface='org.deft.Notify',member='Posted'" {|ev| [on-posted $ev~args]}]
## Same-Process Alternatives
If the "instances" are runtimes inside one process — TUI panels, child
runtimes from `runtime/spawn`, cluster workers — you almost never want
D-Bus. Those runtimes already share the platform layer:
- `pub/*` is a broker shared across the runtime hierarchy, with plain
value delivery — see Pubsub.
- `@rpc` + `rpc` gives one-to-one typed calls addressed by runtime name
— see RPC.
- `config/*` is the shared reactive key/value registry.
D-Bus is the right tool when the peers are separate OS processes, or
when a foreign client (a desktop shell, `notify-send`, a system
service) has to reach you by a well-known name.
## Lifecycle and Introspection
- A name is held until the connection closes. `dbus/disconnect`, the
resource's GC finalizer, and process exit all release every name the
connection owned.
- `[dbus/info $conn]` reports `%{ :state :unique-name :pending :subs :exports }`.
- `[dbus/unique-name $conn]` returns the per-connection `:1.x` name once
`HELLO` completes.
- The bus object (`org.freedesktop.DBus`, path `/org/freedesktop/DBus`)
answers `ListNames`, `GetNameOwner`, `NameHasOwner`, and
`GetConnectionUnixProcessID` — enough to build discovery and
diagnostics without extra dependencies.
- `NameOwnerChanged` is a bus signal: subscribe to it (with an `arg0`
match for the name you care about) to learn the moment a service
appears, disappears, or changes hands.
## Quick Reference
| Form | Purpose |
|---|---|
| `[dbus/connect ?opts]` | Session (default) or system (`:system true`) bus connection |
| `[dbus/call $c dest path iface method ?sig @args]` | Method call; `await` the future |
| `[dbus/send $c dest path iface method ?sig @args]` | Fire-and-forget call |
| `[dbus/register-name $c "name" ?flags]` | Request a well-known name; `await` the result code |
| `[dbus/export $c "path" "iface" %{ "Method" $fn }]` | Serve a method (server role) |
| `[dbus/emit $c "path" "iface" "Signal" ?sig @args]` | Broadcast a signal |
| `[dbus/subscribe $c "match rule" $closure]` | Run `$closure` on matching signals |
| `[dbus/disconnect $c]` | Close; releases owned names |