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.

FAQ

How to use a struct as a dictionary?

Since the use of a dictionary, specifically one with strings as keys and variants as value (i-e a{sv}) is very common in the D-Bus world and use of HashMaps isn’t as convenient and type-safe as a struct, you might find yourself wanting to use a struct as a dictionary.

zvariant provides convenient macros for making this possible: TypeDict, SerializeDict and DeserializeDict. Here is a simple example:


#![allow(unused)]
fn main() {
use zbus::{
    dbus_proxy, dbus_interface, fdo::Result,
    zvariant::{DeserializeDict, SerializeDict, TypeDict},
};

#[derive(DeserializeDict, SerializeDict, TypeDict)]
struct Dictionary {
    field1: u16,
    #[zvariant(rename = "another-name")]
    field2: i64,
    optional_field: Option<String>,
}

#[dbus_proxy(
    interface = "org.zbus.DictionaryGiver",
    default_path = "/org/zbus/DictionaryGiver",
    default_service = "org.zbus.DictionaryGiver",
)]
trait DictionaryGiver {
    fn give_me(&self) -> Result<Dictionary>;
}

struct DictionaryGiverInterface;

#[dbus_interface(interface = "org.zbus.DictionaryGiver")]
impl DictionaryGiverInterface {
    fn give_me(&self) -> Result<Dictionary> {
        Ok(Dictionary {
            field1: 1,
            field2: 4,
            optional_field: Some(String::from("blah")),
        })
    }
}
}

Why do async tokio API calls from interface methods not work?

Many of the tokio (and tokio-based) APIs assume the tokio runtime to be driving the async machinery and since by default, zbus runs the ObjectServer in its own internal runtime thread, it’s not possible to use these APIs from interface methods.

Not to worry, though! There is a very easy way around this unfortunate issue:

  • Disable the internal runtime thread.
  • Launch a tokio task to tick the internal runtime.

Here is an example:

use tokio::{
    io::AsyncReadExt,
    sync::mpsc::{channel, Sender},
};
use zbus::{
    dbus_interface,
    fdo::{self, Result},
};

struct OurInterface(Sender<()>);

#[dbus_interface(interface = "org.fdo.OurInterface")]
impl OurInterface {
    pub async fn quit(&self) -> fdo::Result<()> {
        self.0
            .send(())
            .await
            .map_err(|_| fdo::Error::Failed("shouldn't happen".to_string()))
    }

    pub async fn read_file(&self, path: &str) -> fdo::Result<String> {
        let mut file = tokio::fs::File::open(path)
            .await
            .map_err(|_| fdo::Error::FileNotFound(format!("Failed to open {}", path)))?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)
            .await
            .map_err(|_| fdo::Error::Failed(format!("Failed to read {}", path)))?;

        Ok(contents)
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let (sender, mut receiver) = channel::<()>(1);
    let conn = zbus::ConnectionBuilder::session()?
        .serve_at("/our", OurInterface(sender))?
        .name("org.fdo.OurInterface")?
        .internal_executor(false)
        .build()
        .await?;

    tokio::spawn(async move {
        loop {
            conn.executor().tick().await;
        }
    });

    receiver.recv().await.unwrap();

    Ok(())
}