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(())
}