A Rust API for D-Bus communication. The goal is to provide a safe and simple high- and low-level API akin to GDBus, that doesn't depend on C libraries.
The project is divided into three main crates:
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-specific1.
Status: Stable2.
The best way to get started with zbus is the book, where we start with basic D-Bus concepts and explain with code samples, how zbus makes D-Bus easy.
This code display a notification on your Freedesktop.org-compatible OS:
use std::collections::HashMap;
use std::error::Error;
use zbus::dbus_proxy;
use zvariant::Value;
#[dbus_proxy(
interface = "org.freedesktop.Notifications",
default_service = "org.freedesktop.Notifications",
default_path = "/org/freedesktop/Notifications"
)]
trait Notifications {
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::session()?;
// `dbus_proxy` macro creates `NotificationProxy` based on `Notifications` trait.
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 simple service that politely greets whoever calls its SayHello
method:
use std::error::Error;
use zbus::{dbus_interface, fdo, ObjectServer, Connection};
struct Greeter {
count: u64
}
#[dbus_interface(name = "org.zbus.MyGreeter1")]
impl Greeter {
fn say_hello(&mut self, name: &str) -> String {
self.count += 1;
format!("Hello {}! I have been called: {}", name, self.count)
}
}
fn main() -> Result<(), Box<dyn Error>> {
let connection = Connection::session()?;
let mut object_server = ObjectServer::new(&connection)
.request_name("org.zbus.MyGreeter")?;
let mut greeter = Greeter { count: 0 };
object_server.at("/org/zbus/MyGreeter", greeter)?;
loop {
if let Err(err) = object_server.try_handle_next() {
eprintln!("{}", err);
}
}
}
You can use the following command to test it:
$ busctl --user call org.zbus.MyGreeter /org/zbus/MyGreeter org.zbus.MyGreeter1 SayHello s "Maria"
Hello Maria!
s
Runtime-agnostic async/await-compatible API for both (not so) low-level message handling and high-level client-side proxy is also provided. High-level server-side API coming soon.
zbus is runtime-agnostic and should work out of the box with different Rust async runtimes. However, in order to achieve that, zbus spawns a thread per connection to handle various internal tasks. If that is something you would like to avoid, you need to:
- disable the
internal-executor
feature (which is a default feature). - Ensure the internal executor keeps ticking continuously.
This crate provides API for encoding/decoding of data to/from D-Bus wire format. This binary wire format is simple and very efficient and hence useful outside of D-Bus context as well. A modified form of this format, GVariant is very commonly used for efficient storage of arbitrary data and is also supported by this crate.
Status: Stable.
use std::collections::HashMap;
use byteorder::LE;
use zvariant::{from_slice, to_bytes};
use zvariant::EncodingContext as Context;
// All serialization and deserialization API, needs a context.
let ctxt = Context::<LE>::new_dbus(0);
// You can also use the more efficient GVariant format:
// let ctxt = Context::<LE>::new_gvariant(0);
// i16
let encoded = to_bytes(ctxt, &42i16).unwrap();
let decoded: i16 = from_slice(&encoded, ctxt).unwrap();
assert_eq!(decoded, 42);
// strings
let encoded = to_bytes(ctxt, &"hello").unwrap();
let decoded: &str = from_slice(&encoded, ctxt).unwrap();
assert_eq!(decoded, "hello");
// tuples
let t = ("hello", 42i32, true);
let encoded = to_bytes(ctxt, &t).unwrap();
let decoded: (&str, i32, bool) = from_slice(&encoded, ctxt).unwrap();
assert_eq!(decoded, t);
// Vec
let v = vec!["hello", "world!"];
let encoded = to_bytes(ctxt, &v).unwrap();
let decoded: Vec<&str> = from_slice(&encoded, ctxt).unwrap();
assert_eq!(decoded, v);
// Dictionary
let mut map: HashMap<i64, &str> = HashMap::new();
map.insert(1, "123");
map.insert(2, "456");
let encoded = to_bytes(ctxt, &map).unwrap();
let decoded: HashMap<i64, &str> = from_slice(&encoded, ctxt).unwrap();
assert_eq!(decoded[&1], "123");
assert_eq!(decoded[&2], "456");
This crate provides a derive macro to easily implement Type
trait on structs and enums.
Status: Stable.
use zvariant::{EncodingContext, from_slice, to_bytes};
use zvariant::{derive::Type, Type};
use serde::{Deserialize, Serialize};
use byteorder::LE;
#[derive(Deserialize, Serialize, Type, PartialEq, Debug)]
struct Struct<'s> {
field1: u16,
field2: i64,
field3: &'s str,
}
assert_eq!(Struct::signature(), "(qxs)");
let s = Struct {
field1: 42,
field2: i64::max_value(),
field3: "hello",
};
let ctxt = EncodingContext::<LE>::new_dbus(0);
let encoded = to_bytes(ctxt, &s).unwrap();
let decoded: Struct = from_slice(&encoded, ctxt).unwrap();
assert_eq!(decoded, s);
Apart from the three crates described above, zbus project also provides a few other crates:
This crate provides the convenient zbus macros that we already saw in action in the sample code
above. However, zbus
crate re-exports the macros for your convenience so you do not need to use
this crate directly.
Status: Stable.
A crate to interact with PolicyKit, a toolkit for defining and handling authorizations. It is used for allowing unprivileged processes to speak to privileged processes.
Status: Stable.
use zbus::Connection;
use zbus_polkit::policykit1::*;
let connection = Connection::system().unwrap();
let proxy = AuthorityProxy::new(&connection).unwrap();
let subject = Subject::new_for_owner(std::process::id(), None, None).unwrap();
let result = proxy.check_authorization(
&subject,
"org.zbus.BeAwesome",
std::collections::HashMap::new(),
CheckAuthorizationFlags::AllowUserInteraction.into(),
"",
);
A binary crate that provides a developer tool to generate Rust code from D-Bus XML interface descriptions. It can be used to generate the code directly from a running D-Bus system, session or other service, or using a preexisting XML file for input.
Status: Stable.
$ cargo install zbus_xmlgen
$ zbus-xmlgen --system org.freedesktop.login1 /org/freedesktop/login1
$ zbus-xmlgen --session org.freedesktop.ScreenSaver /org/freedesktop/ScreenSaver
$ zbus-xmlgen --address unix:abstract=/home/user/.cache/ibus/dbus-fpxKwgbJ org.freedesktop.IBus /org/freedesktop/IBus
$ zbus-xmlgen interface.xml
This crate provides collection of types for various D-Bus bus names.
This is used by zbus
(and in future by zbus_macros
as well) crate. Other D-Bus crates are also
encouraged to use this API in the spirit of cooperation. :)
Status: Stable.
If you need help in using these crates, are looking for ways to contribute, or just want to hang out
with the cool kids, please come chat with us in the
#zbus:matrix.org
Matrix room. If something doesn't seem
right, please file an issue.
All crates are currently Unix-only and will fail to build on non-unix. This is hopefully a temporary limitation. Moreover, integration tests of zbus crate currently require a session bus running on the build host.
MIT license LICENSE-MIT
Footnotes
-
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 🕸. ↩
-
We might have to change the API but zbus follows semver convention so your code won't just break out of the blue. Just make sure you depend on a specific major version of zbus. ↩