Note

This version of the book is based on zbus 2.0 API, which is currently in beta stages. For using the sample code in this book, you’ll need to explicitly depend on the latest beta.

The 1.0 version of this book is available here.


Introduction

zbus is a Rust crate for D-Bus. If you are not familiar with D-Bus, you should read what is D-Bus? first1. In short, zbus allows you to communicate from one program to another, using the D-Bus protocol. In other words, it’s an inter-process communication (IPC) solution. It is a very popular IPC solution on Linux and many Linux services (e.g systemd, NetworkManager) and desktop environments (e.g GNOME and KDE), rely on D-Bus for their IPC needs. There are many tools and implementations available, making it easy to interact with D-Bus programs from different languages and environments.

zbus is a 100% Rust-native implementation of the D-Bus protocol. It provides both an API to send and receive messages over a connection, as well as API to interact with peers through high-level concepts like method calls, signals and properties2. Thanks to the power of Rust macros, zbus is able to make interacting with D-Bus very easy.

zbus project provides two crates:

zvariant

D-Bus defines a marshalling format for its messages. The zvariant crate provides a serde-based API to serialize/deserialize Rust data types to/from this format. Outside of D-Bus context, a modified form of this format, GVariant is very commonly used for efficient storage of arbitrary data and is also supported by this crate.

zbus

The zbus crate provides the main API you will use to interact with D-Bus from Rust. It takes care of the establishment of a connection, the creation, sending and receiving of different kind of D-Bus messages (method calls, signals etc) for you.

zbus crate is currently Linux-specific3.

1

D-Bus is ~15y old, unfortunately many documents out there are sometime aging or misleading.

2

These concepts are explained in the following chapter.

3

Support for other OS exist, but it is not supported to the same extent. D-Bus clients in javascript (running from any browser) do exist though. And zbus may also be working from the browser sometime in the future too, thanks to Rust 🦀 and WebAssembly 🕸.

Some D-Bus concepts to help newcomers

Bus

A D-Bus “bus” is a kind of server that handles several connections in a bus-topology fashion. As such, it relays messages between connected endpoints, and allows to discover endpoints or sending broadcast messages (signals).

Typically, a Linux system has a system bus, and a session bus. The latter is per-user. It is also possible to have private buses or no bus at all (i-e direct peer-to-peer communication instead).

Bus name / service name

An endpoint can have various names, which allows to address messages to it on the bus. All endpoints are assigned a unique name by the bus at start. Since this name is not static, most services use something called a well-known bus name and typically it’s this name, that you’ll be concerned with.

An example would be the FreeDesktop Notifications Service that uses org.freedesktop.Notifications as its well-known bus name.

For further details on bus names, please refer to the Bus names chapter of the D-Bus specification.

Objects and Object paths

An object is akin to the concept of an object or an instance in many programming languages. All services expose at least one object on the bus and all clients interact with the service through these objects. These objects can be ephemeral or they could live as long as the service itself.

Every object is identified by a string, which is referred to as its path. An example of an object path is /org/freedesktop/Notifications, which identies the only object exposed by the FreeDesktop Notifications Service.

For further details on object paths, please refer to the Basic types chapter of the D-Bus specification.

Interfaces

An interface defines the API exposed by object on the bus. They are akin to the concept of interfaces in many programming languages and traits in Rust. Each object can (and typically do) provide multiple interfaces at the same time. A D-Bus interface can have methods, properties and signals.

While each interface of a service is identified by a unique name, its API is described by an XML description. It is mostly a machine-level detail. Most services can be queried for this description through a D-Bus standard introspection interface.

zbus provides convenient macro that implements the introspection interface for services, and helper to generate client-side Rust API, given an XML description. We’ll see both of these in action in the following chapters.

Good practices & API design

It is recommended to organise the service name, object paths and interface name by using fully-qualified domain names, in order to avoid potential conflicts.

Please read the D-Bus API Design Guidelines carefully for other similar considerations.

Onwards to implementation details & examples!

Establishing a connection

The first thing you will have to do is to connect to a D-Bus bus or to a D-Bus peer. This is the entry point of the zbus API.

Connection to the bus

To connect to the session bus (the per-user bus), simply call Connection::new_session(). It returns an instance of the connection (if all went well).

Similarly, to connect to the system bus (to communicate with services such as NetworkManager, BlueZ or PID1), use Connection::new_system().

Note: it is common for a D-Bus library to provide a “shared” connection to a bus for a process: all new_session() share the same underlying connection for example. At the time of this writing, zbus doesn’t do that.

Using a custom bus address

You may also specify a custom bus with Connection::new_for_address() which takes a D-Bus address as specified in the specification.

Peer to peer connection

Peer-to-peer connections are bus-less1, and the initial handshake protocol is a bit different. There is the notion of client & server endpoints, but that distinction doesn’t matter once the connection is established (both ends are equal, and can send any messages).

To create a bus-less peer-to-peer connection on Unix, you can make a socketpair() (or have a listening socket server, accepting multiple connections), and hand over the socket FDs to Connection::new_unix_server and Connection::new_unix_client for each side. After success, you can call the Connection methods to send and receive messages on both ends.

See the unix_p2p test in the zbus source code for a simple example.

1 Unless you implemented them, none of the bus methods will exist.

Writing a client proxy

In this chapter, we are going to see how to make low-level D-Bus method calls. Then we are going to dive in, and derive from a trait to make a convenient Rust binding. Finally, we will learn about xmlgen, a tool to help us generate a boilerplate trait from the XML of an introspected service.

To make this learning “hands-on”, we are going to call and bind the cross-desktop notification service (please refer to this reference document for further details on this API).

Let’s start by playing with the service from the shell, and notify the desktop with busctl1:

busctl --user call \
  org.freedesktop.Notifications \
  /org/freedesktop/Notifications \
  org.freedesktop.Notifications \
  Notify \
  susssasa\{sv\}i \
  "my-app" 0 "dialog-information" "A summary" "Some body" 0 0 5000

Note: busctl has very good auto-completion support in bash or zsh.

Running this command should pop-up a notification dialog on your desktop. If it does not, your desktop does not support the notification service, and this example will be less interactive. Nonetheless you can use a similar approach for other services.

This command shows us several aspects of the D-Bus communication:

  • --user: Connect to and use the user/session bus.

  • call: Send a method call message. (D-Bus also supports signals, error messages, and method replies)

  • destination: The name of the service (org.freedesktop.Notifications).

  • object path: Object/interface path (/org/freedesktop/Notifications).

  • interface: The interface name (methods are organized in interfaces, here org.freedesktop.Notifications, same name as the service).

  • method: The name of the method to call, Notify.

  • signature: That susssasa{sv}i means the method takes 8 arguments of various types. ‘s’, for example, is for a string. ‘as’ is for array of strings.

  • The method arguments.

See busctl man page for more details.

Low-level call from a zbus::Connection

zbus Connection has a call_method() method, which you can use directly:

use std::collections::HashMap;
use std::error::Error;

use zbus::Connection;
use zvariant::Value;

fn main() -> Result<(), Box<dyn Error>> {
    let connection = Connection::new_session()?;

    let m = connection.call_method(
        Some("org.freedesktop.Notifications"),
        "/org/freedesktop/Notifications",
        Some("org.freedesktop.Notifications"),
        "Notify",
        &("my-app", 0u32, "dialog-information", "A summary", "Some body",
          vec![""; 0], HashMap::<&str, &Value>::new(), 5000),
    )?;
    let reply: u32 = m.body().unwrap();
    dbg!(reply);
    Ok(())
}

Although this is already quite flexible, and handles various details for you (such as the message signature), it is also somewhat inconvenient and error-prone: you can easily miss arguments, or give arguments with the wrong type or other kind of errors (what would happen if you typed 0, instead of 0u32?).

Instead, we want to wrap this Notify D-Bus method in a Rust function. Let’s see how next.

Trait-derived proxy call

A trait declaration T with a dbus_proxy attribute will have a derived TProxy implemented thanks to procedural macros. The trait methods will have respective impl methods wrapping the D-Bus calls:

use std::collections::HashMap;
use std::error::Error;

use zbus::dbus_proxy;
use zvariant::Value;

#[dbus_proxy]
trait Notifications {
    /// Call the org.freedesktop.Notifications.Notify D-Bus method
    fn notify(&self,
              app_name: &str,
              replaces_id: u32,
              app_icon: &str,
              summary: &str,
              body: &str,
              actions: &[&str],
              hints: HashMap<&str, &Value<'_>>,
              expire_timeout: i32) -> zbus::Result<u32>;
}

fn main() -> Result<(), Box<dyn Error>> {
    let connection = zbus::Connection::new_session()?;

    let proxy = NotificationsProxy::new(&connection)?;
    let reply = proxy.notify("my-app", 0, "dialog-information", "A summary", "Some body",
                             &[], HashMap::new(), 5000)?;
    dbg!(reply);

    Ok(())
}

A TProxy has a few associated methods, such as new(connection), using the default associated service name and object path, and new_for(connection, service_name, object_path) if you need to specify something different.

This should help to avoid the kind of mistakes we saw earlier. It’s also a bit easier to use, thanks to Rust type inference. This makes it also possible to have higher-level types, they fit more naturally with the rest of the code. You can further document the D-Bus API or provide additional helpers.

Properties

Interfaces can have associated properties, which can be read or set with the org.freedesktop.DBus.Properties interface. Here again, the #[dbus_proxy] attribute comes to the rescue to help you. You can annotate a trait method to be a getter:


#![allow(unused)]
fn main() {
use zbus::{dbus_proxy, Result};

#[dbus_proxy]
trait MyInterface {
    #[dbus_proxy(property)]
    fn state(&self) -> Result<String>;
}
}

The state() method will translate to a "State" property Get call.

To set the property, prefix the name of the property with set_.

For a more real world example, let’s try and read two properties from systemd’s main service:

use std::error::Error;
use zbus::dbus_proxy;

#[dbus_proxy(
    interface = "org.freedesktop.systemd1.Manager",
    default_service = "org.freedesktop.systemd1",
    default_path = "/org/freedesktop/systemd1"
)]
trait SystemdManager {
    #[dbus_proxy(property)]
    fn architecture(&self) -> zbus::Result<String>;
    #[dbus_proxy(property)]
    fn environment(&self) -> zbus::Result<Vec<String>>;
}

fn main() -> Result<(), Box<dyn Error>> {
    let connection = zbus::Connection::new_session()?;

    let proxy = SystemdManagerProxy::new(&connection)?;
    println!("Host architecture: {}", proxy.architecture()?);
    println!("Environment:");
    for env in proxy.environment()? {
        println!("  {}", env);
    }

    Ok(())
}

You should get an output similar to this:

Host architecture: x86-64
Environment variables:
  HOME=/home/zeenix
  LANG=en_US.UTF-8
  LC_ADDRESS=de_DE.UTF-8
  LC_IDENTIFICATION=de_DE.UTF-8
  LC_MEASUREMENT=de_DE.UTF-8
  LC_MONETARY=de_DE.UTF-8
  LC_NAME=de_DE.UTF-8
  LC_NUMERIC=de_DE.UTF-8
  LC_PAPER=de_DE.UTF-8
  LC_TELEPHONE=de_DE.UTF-8
  LC_TIME=de_DE.UTF-8
  ...

Signals

Signals are like methods, except they don’t expect a reply. They are typically emitted by services to notify interested peers of any changes to the state of the service. zbus provides you with an API to register signal handler functions, and to receive and call them.

Let’s look at this API in action, with an example where we get our location from Geoclue:


#![allow(unused)]
fn main() {
use zbus::{Connection, dbus_proxy, Result};
use zvariant::{ObjectPath, OwnedObjectPath};

#[dbus_proxy(
    default_service = "org.freedesktop.GeoClue2",
    interface = "org.freedesktop.GeoClue2.Manager",
    default_path = "/org/freedesktop/GeoClue2/Manager"
)]
trait Manager {
    #[dbus_proxy(object = "Client")]
    /// The method normally returns an `ObjectPath`.
    /// With the object attribute, we can make it return a `ClientProxy` directly.
    fn get_client(&self);
}

#[dbus_proxy(
    default_service = "org.freedesktop.GeoClue2",
    interface = "org.freedesktop.GeoClue2.Client"
)]
trait Client {
    fn start(&self) -> Result<()>;
    fn stop(&self) -> Result<()>;

    #[dbus_proxy(property)]
    fn set_desktop_id(&mut self, id: &str) -> Result<()>;

    #[dbus_proxy(signal)]
    fn location_updated(&self, old: ObjectPath, new: ObjectPath) -> Result<()>;
}

#[dbus_proxy(
    default_service = "org.freedesktop.GeoClue2",
    interface = "org.freedesktop.GeoClue2.Location"
)]
trait Location {
    #[dbus_proxy(property)]
    fn latitude(&self) -> Result<f64>;
    #[dbus_proxy(property)]
    fn longitude(&self) -> Result<f64>;
}
let conn = Connection::new_system().unwrap();
let manager = ManagerProxy::new(&conn).unwrap();
let mut client = manager.get_client().unwrap();
// Gotta do this, sorry!
client.set_desktop_id("org.freedesktop.zbus").unwrap();

client
    .connect_location_updated(move |_old, new| {
        let location = LocationProxy::new_for_path(&conn, new.as_str())?;
        println!(
            "Latitude: {}\nLongitude: {}",
            location.latitude()?,
            location.longitude()?,
        );

        Ok(())
    })
    .unwrap();

client.start().unwrap();

// Wait till there is a signal that was handled.
while client.next_signal().unwrap().is_some() {}
}

While the Geoclue’s D-Bus API is a bit involved, we still ended-up with a not-so-complicated (~60 LOC) code for getting our location. As you may’ve notice, we use a blocking call to wait for a signal on one proxy. This works fine but in the real world, you would typically have many proxies and you’d want to wait for signals from them all at once. Not to worry, zbus provides a way to wait on multiple proxies at once as well.

Let’s make use of SignalReceiver and zbus::fdo API to make sure the client is actually started by watching for Active property (that we must set to be able to get location from Geoclue) actually getting set:


#![allow(unused)]
fn main() {
use zbus::{Connection, dbus_proxy, Result};
use zvariant::{ObjectPath, OwnedObjectPath};

#[dbus_proxy(
    default_service = "org.freedesktop.GeoClue2",
    interface = "org.freedesktop.GeoClue2.Manager",
    default_path = "/org/freedesktop/GeoClue2/Manager"
)]
trait Manager {
    #[dbus_proxy(object = "Client")]
    fn get_client(&self);
}

#[dbus_proxy(interface = "org.freedesktop.GeoClue2.Client")]
trait Client {
    fn start(&self) -> Result<()>;

    #[dbus_proxy(property)]
    fn set_desktop_id(&mut self, id: &str) -> Result<()>;

    #[dbus_proxy(signal)]
    fn location_updated(&self, old: ObjectPath, new: ObjectPath) -> Result<()>;
}

#[dbus_proxy(
    default_service = "org.freedesktop.GeoClue2",
    interface = "org.freedesktop.GeoClue2.Location"
)]
trait Location {
    #[dbus_proxy(property)]
    fn latitude(&self) -> Result<f64>;
    #[dbus_proxy(property)]
    fn longitude(&self) -> Result<f64>;
}

let conn = Connection::new_system().unwrap();
let manager = ManagerProxy::new(&conn).unwrap();
let mut client = manager.get_client().unwrap();
// Gotta do this, sorry!
client.set_desktop_id("org.freedesktop.zbus").unwrap();

// Everything else remains the same before this point.

let conn_clone = conn.clone();
client.connect_location_updated(move |_old, new| {
    let location = LocationProxy::new_for(
        &conn_clone,
        "org.freedesktop.GeoClue2",
        new.as_str(),
    )?;
    println!(
        "Latitude: {}\nLongitude: {}",
        location.latitude()?,
        location.longitude()?,
    );

    Ok(())
}).unwrap();

let props = zbus::fdo::PropertiesProxy::new_for(
    &conn,
    "org.freedesktop.GeoClue2",
    client.path(),
).unwrap();
props.connect_properties_changed(|iface, changed, _| {
    for (name, value) in changed.iter() {
        println!("{}.{} changed to `{:?}`", iface, name, value);
    }

    Ok(())
}).unwrap();

let mut receiver = zbus::SignalReceiver::new(conn);
receiver.receive_for(&client).unwrap();
receiver.receive_for(&props).unwrap();

client.start().unwrap();

// 3 signals will be emitted, that we handle
let mut num_handled = 0;
while num_handled < 3 {
    if receiver.next_signal().unwrap().is_none() {
        num_handled += 1;
    }
}
}

Generating the trait from an XML interface

The zbus_xmlgen crate provides a developer-friendly tool, that can generate Rust traits from a given D-Bus introspection XML for you.

Note: This tool should not be considered a drop-in Rust-specific replacement for similar tools available for low-level languages, such as gdbus-codegen. Unlike those tools, this is only meant as a starting point to generate the code, once. In many cases, you will want to tweak the generated code.

The tool can be used to generate rust code directly from a D-Bus service running on our system:

zbus-xmlgen --session \
  org.freedesktop.Notifications \
  /org/freedesktop/Notifications

Alternatively you can also get the XML interface from a different source and use it to generate the interface code. Some packages may also provide the XML directly as an installed file, allowing it to be queried using pkg-config, for example.

We can fetch the XML interface of the notification service, using the --xml-interface option of the busctl1 command. This option was introduced to busctl in systemd v243.

busctl --user --xml-interface introspect \
  org.freedesktop.Notifications \
  /org/freedesktop/Notifications

You should get a similar output:

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
                      "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <!-- other interfaces omitted -->
  <interface name="org.freedesktop.Notifications">
    <method name="Notify">
      <arg type="s" name="arg_0" direction="in">
      </arg>
      <arg type="u" name="arg_1" direction="in">
      </arg>
      <arg type="s" name="arg_2" direction="in">
      </arg>
      <arg type="s" name="arg_3" direction="in">
      </arg>
      <arg type="s" name="arg_4" direction="in">
      </arg>
      <arg type="as" name="arg_5" direction="in">
      </arg>
      <arg type="a{sv}" name="arg_6" direction="in">
      </arg>
      <arg type="i" name="arg_7" direction="in">
      </arg>
      <arg type="u" name="arg_8" direction="out">
      </arg>
    </method>
    <method name="CloseNotification">
      <arg type="u" name="arg_0" direction="in">
      </arg>
    </method>
    <method name="GetCapabilities">
      <arg type="as" name="arg_0" direction="out">
      </arg>
    </method>
    <method name="GetServerInformation">
      <arg type="s" name="arg_0" direction="out">
      </arg>
      <arg type="s" name="arg_1" direction="out">
      </arg>
      <arg type="s" name="arg_2" direction="out">
      </arg>
      <arg type="s" name="arg_3" direction="out">
      </arg>
    </method>
    <signal name="NotificationClosed">
      <arg type="u" name="arg_0">
      </arg>
      <arg type="u" name="arg_1">
      </arg>
    </signal>
    <signal name="ActionInvoked">
      <arg type="u" name="arg_0">
      </arg>
      <arg type="s" name="arg_1">
      </arg>
    </signal>
  </interface>
</node>

Save the output to a notify.xml file. Then call:

zbus-xmlgen notify.xml

This will give back effortlessly the corresponding Rust traits boilerplate code:


#![allow(unused)]
fn main() {
use zbus::dbus_proxy;

#[dbus_proxy(interface = "org.freedesktop.Notifications")]
trait Notifications {
    /// CloseNotification method
    fn close_notification(&self, arg_0: u32) -> zbus::Result<()>;

    /// GetCapabilities method
    fn get_capabilities(&self) -> zbus::Result<Vec<String>>;

    /// GetServerInformation method
    fn get_server_information(&self) -> zbus::Result<(String, String, String, String)>;

    /// Notify method
    fn notify(
        &self,
        arg_0: &str,
        arg_1: u32,
        arg_2: &str,
        arg_3: &str,
        arg_4: &str,
        arg_5: &[&str],
        arg_6: std::collections::HashMap<&str, zvariant::Value<'_>>,
        arg_7: i32,
    ) -> zbus::Result<u32>;

    /// ActionInvoked signal
    #[dbus_proxy(signal)]
    fn action_invoked(&self, arg_0: u32, arg_1: &str) -> zbus::Result<()>;

    /// NotificationClosed signal
    #[dbus_proxy(signal)]
    fn notification_closed(&self, arg_0: u32, arg_1: u32) -> zbus::Result<()>;
}
}

It should be usable as such. But you may as well improve a bit the naming of the arguments, use better types (using BitFlags, structs or other custom types), add extra documentation, and other functions to make the binding more pleasing to use from Rust.

For example, the generated GetServerInformation method can be improved to a nicer version:


#![allow(unused)]
fn main() {
use serde::{Serialize, Deserialize};
use zvariant::derive::Type;
use zbus::dbus_proxy;

#[derive(Debug, Type, Serialize, Deserialize)]
pub struct ServerInformation {
    /// The product name of the server.
    pub name: String,

    /// The vendor name. For example "KDE," "GNOME," "freedesktop.org" or "Microsoft".
    pub vendor: String,

    /// The server's version number.
    pub version: String,

    /// The specification version the server is compliant with.
    pub spec_version: String,
}

trait Notifications {
    /// Get server information.
    ///
    /// This message returns the information on the server.
    fn get_server_information(&self) -> zbus::Result<ServerInformation>;
}
}

You can learn more from the zbus-ify binding of PolicyKit, for example, which was implemented starting from the xmlgen output.

There you have it, a Rust-friendly binding for your D-Bus service!

1

busctl is part of systemd.

Writing a server interface

Let see how to provide a server method “SayHello”, to greet a client.

Taking a service name

As we know from the chapter on D-Bus concepts, each connection on the bus is given a unique name (such as “:1.27”). This could be all you need, depending on your use case, and the design of your D-Bus API. However, typically services use a service name (aka well-known name) so peers (clients, in this context) can easily discover them.

In this example, that is exactly what we’re going to do and request the bus for the service name of our choice. To achieve that, we will we call the RequestName method on the bus, using zbus::fdo module:

use std::error::Error;

use zbus::Connection;
use zbus::fdo;

fn main() -> std::result::Result<(), Box<dyn Error>> {
    let connection = Connection::new_session()?;

    fdo::DBusProxy::new(&connection)?.request_name(
        "org.zbus.MyGreeter",
        fdo::RequestNameFlags::ReplaceExisting.into(),
    )?;

    loop {}
}

We can check our service is running and is associated with the service name:

$ busctl --user list | grep zbus
org.zbus.MyGreeter                             412452 server            elmarco :1.396        user@1000.service -       -

⚠ Hang on

This example is not handling incoming messages yet. Any attempt to call the server will time out (including the shell completion!).

Handling low-level messages

At the low-level, you can handle method calls by checking the incoming messages manually.

Let’s write a SayHello method, that takes a string as argument, and reply with a “hello” greeting by replacing the loop above with this code:

fn main() -> Result<(), Box<dyn std::error::Error>> {
   let connection = zbus::Connection::new_session()?;
   zbus::fdo::DBusProxy::new(&connection)?.request_name(
       "org.zbus.MyGreeter",
       zbus::fdo::RequestNameFlags::ReplaceExisting.into(),
   )?;

loop {
    let msg = connection.receive_message()?;
    let msg_header = msg.header()?;
    dbg!(&msg);

    match msg_header.message_type()? {
        zbus::MessageType::MethodCall => {
            // real code would check msg_header path(), interface() and member()
            // handle invalid calls, introspection, errors etc
            let arg: &str = msg.body()?;
            connection.reply(&msg, &(format!("Hello {}!", arg)))?;
        }
        _ => continue,
    }
}
}

And check if it works as expected:

$ busctl --user call org.zbus.MyGreeter /org/zbus/MyGreeter org.zbus.MyGreeter1 SayHello s "zbus"
s "Hello zbus!"

This is the crust of low-level message handling. It should give you all the flexibility you ever need, but it is also easy to get it wrong. Fortunately, zbus has a simpler solution to offer.

Using the ObjectServer

One can write an impl with a set of methods and let the dbus_interface procedural macro write the D-Bus details for us. It will dispatch all the incoming method calls to their respective handlers, and implicilty handle introspection requests. It also has support for properties and signal emission.

Let see how to use it:

use std::error::Error;
use zbus::{dbus_interface, fdo};

struct Greeter {
    name: String
}

#[dbus_interface(name = "org.zbus.MyGreeter1")]
impl Greeter {
    fn say_hello(&self, name: &str) -> String {
        format!("Hello {}!", name)
    }

    /// A "GreeterName" property.
    #[dbus_interface(property)]
    fn greeter_name(&self) -> &str {
        &self.name
    }

    /// A setter for the "GreeterName" property.
    ///
    /// Additionally, a `greeter_name_changed` method has been generated for you if you need to 
    /// notify listeners that "GreeterName" was updated. It will be automatically called when
    /// using this setter.
    #[dbus_interface(property)]
    fn set_greeter_name(&mut self, name: String) {
        self.name = name;
    }

    /// A signal; the implementation is provided by the macro.
    #[dbus_interface(signal)]
    fn greeted_everyone(&self) -> zbus::Result<()>;
}

fn main() -> Result<(), Box<dyn Error>> {
    let connection = zbus::Connection::new_session()?;
    fdo::DBusProxy::new(&connection)?.request_name(
        "org.zbus.MyGreeter",
        fdo::RequestNameFlags::ReplaceExisting.into(),
    )?;

    let mut object_server = zbus::ObjectServer::new(&connection);
    let greeter = Greeter { name: "GreeterName".to_string() };
    object_server.at("/org/zbus/MyGreeter", greeter)?;
    loop {
        if let Err(err) = object_server.try_handle_next() {
            eprintln!("{}", err);
        }
    }
}

(check it works with the same busctl command as last time)

This time, we can also introspect the server:

$ busctl --user introspect org.zbus.MyGreeter /org/zbus/MyGreeter
NAME                                TYPE      SIGNATURE RESULT/VALUE FLAGS
org.freedesktop.DBus.Introspectable interface -         -            -
.Introspect                         method    -         s            -
org.freedesktop.DBus.Peer           interface -         -            -
.GetMachineId                       method    -         s            -
.Ping                               method    -         -            -
org.freedesktop.DBus.Properties     interface -         -            -
.Get                                method    ss        v            -
.GetAll                             method    s         a{sv}        -
.Set                                method    ssv       -            -
.PropertiesChanged                  signal    sa{sv}as  -            -
org.zbus.MyGreeter1                 interface -         -            -
.SayHello                           method    s         s            -

Easy-peasy!

Note: As you must have noticed, your code needed to run a loop to continuously read incoming messages (register the associated FD for input in poll/select to avoid blocking). This is because at the time of the this writing (pre-1.0), zbus neither provides an event loop API, nor any integration with other event loop implementations. We are evaluating different options to make this easier, especially with async support.

Sending signals

Sending signals at an arbitrary point in any time is equally easy with the object server. The with method allows you to run any closure for a given interface. Let’s emit a signal defined by our interface:

use std::error::Error;
use zbus::dbus_interface;

struct Greeter {
    name: String
}

#[dbus_interface(name = "org.zbus.MyGreeter1")]
impl Greeter {
    #[dbus_interface(property)]
    fn greeter_name(&self) -> &str {
        &self.name
    }

    #[dbus_interface(property)]
    fn set_greeter_name(&mut self, name: String) {
        self.name = name;
    }
}

fn main() -> Result<(), Box<dyn Error>> {
let connection = zbus::Connection::new_session()?;
let mut object_server = zbus::ObjectServer::new(&connection);
object_server.with("/org/zbus/MyGreeter", |iface: &Greeter| {
    iface.greeter_name_changed()
})?;
Ok(())
}

Let’s Go Asynchronous

Not only does zbus also provides with asynchronous API, most of the synchronous API you saw in action already, is in fact just thin wrappers around its asynchronous counterpart. Since you’re now already familiar the synchronous API, in this chapter we’ll focus on making the earlier code samples, asynchronous.

Establishing a connection

The only difference to that of synchronous Connection API is that you use azync::Connection type instead. This type’s API is almost identical to that of Connection, except its asynchronous. Moreover, it also provides a futures::stream::Stream and futures::sink::Sink implementations to conveniently receive and send messages, respectively for the times when low-level API is more appropriate for your use case.

Client

Similar to Connection, you use azync::Proxy type. Its constructors require azync::Connection instead of Connection. Moreover, dbus_proxy macro generates an azync::Proxy wrapper for you as well. Let’s convert the last example in the previous chapter, to use the asynchronous connection and proxy:


#![allow(unused)]
fn main() {
use futures_util::future::FutureExt;
use zbus::{azync::Connection, dbus_proxy, Result};
use zvariant::{ObjectPath, OwnedObjectPath};

async_io::block_on(run()).unwrap();

async fn run() -> Result<()> {
    #[dbus_proxy(
        default_service = "org.freedesktop.GeoClue2",
        interface = "org.freedesktop.GeoClue2.Manager",
        default_path = "/org/freedesktop/GeoClue2/Manager"
    )]
    trait Manager {
        #[dbus_proxy(object = "Client")]
        fn get_client(&self);
    }

    #[dbus_proxy(
        default_service = "org.freedesktop.GeoClue2",
        interface = "org.freedesktop.GeoClue2.Client"
    )]
    trait Client {
        fn start(&self) -> Result<()>;
        fn stop(&self) -> Result<()>;

        #[dbus_proxy(property)]
        fn set_desktop_id(&mut self, id: &str) -> Result<()>;

        #[dbus_proxy(signal)]
        fn location_updated(&self, old: ObjectPath, new: ObjectPath) -> Result<()>;
    }

    #[dbus_proxy(
        default_service = "org.freedesktop.GeoClue2",
        interface = "org.freedesktop.GeoClue2.Location"
    )]
    trait Location {
        #[dbus_proxy(property)]
        fn latitude(&self) -> Result<f64>;
        #[dbus_proxy(property)]
        fn longitude(&self) -> Result<f64>;
    }
    let conn = Connection::new_system().await?;
    let manager = AsyncManagerProxy::new(&conn)?;
    let mut client = manager.get_client().await?;
    // Gotta do this, sorry!
    client.set_desktop_id("org.freedesktop.zbus").await?;

    client
        .connect_location_updated(move |_old, new| {
            let new = new.to_string();
            let conn = conn.clone();

            async move {
                let location = AsyncLocationProxy::new_for_path(&conn, new)?;
                println!(
                    "Latitude: {}\nLongitude: {}",
                    location.latitude().await?,
                    location.longitude().await?,
                );

                Ok(())
            }
            .boxed()
        })
        .await?;

    client.start().await?;

    // Wait till there is a signal that was handled.
    while client.next_signal().await?.is_some() {}

    Ok(())
}
}

As you can see, nothing changed in the dbus_proxy usage here and the rest largely remained the same as well.

Receiving multiple signals, simultaneously

The asynchronous API also doesn’t include an equivalent of SignalReceiver. This is because futures crate (and others) already provide a rich API to combine asynchronous operations in various ways. Let’s see that in action by converting the above example again to receive multiple signals on different proxies:


#![allow(unused)]
fn main() {
use futures_util::future::FutureExt;
use zbus::{azync::Connection, dbus_proxy, Result};
use zvariant::{ObjectPath, OwnedObjectPath};

async_io::block_on(run()).unwrap();

async fn run() -> Result<()> {
    #[dbus_proxy(
        default_service = "org.freedesktop.GeoClue2",
        interface = "org.freedesktop.GeoClue2.Manager",
        default_path = "/org/freedesktop/GeoClue2/Manager"
    )]
    trait Manager {
        #[dbus_proxy(object = "Client")]
        fn get_client(&self);
    }

    #[dbus_proxy(
        default_service = "org.freedesktop.GeoClue2",
        interface = "org.freedesktop.GeoClue2.Client"
    )]
    trait Client {
        fn start(&self) -> Result<()>;
        fn stop(&self) -> Result<()>;

        #[dbus_proxy(property)]
        fn set_desktop_id(&mut self, id: &str) -> Result<()>;

        #[dbus_proxy(signal)]
        fn location_updated(&self, old: ObjectPath, new: ObjectPath) -> Result<()>;
    }

    #[dbus_proxy(
        default_service = "org.freedesktop.GeoClue2",
        interface = "org.freedesktop.GeoClue2.Location"
    )]
    trait Location {
        #[dbus_proxy(property)]
        fn latitude(&self) -> Result<f64>;
        #[dbus_proxy(property)]
        fn longitude(&self) -> Result<f64>;
    }
    let conn = Connection::new_system().await?;
    let manager = AsyncManagerProxy::new(&conn)?;
    let mut client = manager.get_client().await?;

	// Everything else remains the same before this point.
    client.set_desktop_id("org.freedesktop.zbus").await?;

    let props = zbus::fdo::AsyncPropertiesProxy::new_for(
        &conn,
        "org.freedesktop.GeoClue2",
        client.path(),
    )?;
    props
        .connect_properties_changed(move |iface, changed, _| {
            for (name, value) in changed.iter() {
                println!("{}.{} changed to `{:?}`", iface, name, value);
            }

            async { Ok(()) }.boxed()
        })
        .await?;

    client
        .connect_location_updated(move |_old, new| {
            let new = new.to_string();
            let conn = conn.clone();

            async move {
                let location = AsyncLocationProxy::new_for_path(&conn, new)?;
                println!(
                    "Latitude: {}\nLongitude: {}",
                    location.latitude().await?,
                    location.longitude().await?,
                );

                Ok(())
            }
            .boxed()
        })
        .await?;

    client.start().await?;

    futures_util::try_join!(
        async {
            while props.next_signal().await?.is_some() {}

            Ok::<(),zbus::Error >(())
        },
        async {
            while client.next_signal().await?.is_some() {}

            // No need to specify type of Result each time
            Ok(())
        }
    )?;

  Ok(())
}
}

Server

No high-level server-side API are provided yet. Rest assured, it’s very high on our priority list. Stay tuned!

Contributors

Here is a list of the contributors who have helped improving zbus. Big shout-out to them!

If you feel you’re missing from this list, feel free to add yourself in a PR.