Intro 🚌
D-Bus is a message bus system and standard for inter-process communication, mostly used in Linux desktop applications. Both Qt and GLib have high-level abstractions for D-Bus communication, and many of the desktop services we rely on export D-Bus protocols. Also the omnipresent systemd can be only interfaced via D-Bus API. However, D-Bus has its shortcomings — namely a lack of documentation. In this article we’ll explore how to write our own D-Bus Service in Rust and connect it to our D-Bus client.
As as starter, if you want to get some practice on D-Bus, I recommend this tutorial and here you may like to refresh some D-Bus naming and concepts.
All the code lives in its own GitHub repository, so you can follow along and try yourself. Enjoy the trip!
Image credits to: Nubia Navarro
The Service
Our exposed service will be very simple: once called, it will keep track of how many times it has been called and last date/time. We can also pass our name as parameter, just to show how parameter passing works.
Thanks to a wonderful Rust crate, creating D-Bus service is rather easy. We opt to make it async, because… Why not ?
The core function is the trait implementation:
struct MyService {
call_count: u64,
call_timestamp: Option<DateTime<Local>>,
}
#[dbus_interface(name = "org.zbus.MyService")]
impl MyService {
async fn call_me(&mut self, name: &str) -> String {
let msg = match self.call_count {
0 => format!("Hi {}, this is the first time you call me!", name),
_ => format!(
"Hello {}, I have been called {} times, last was at {}",
name,
self.call_count,
self.call_timestamp
.expect("unable to get local time")
.to_rfc2822()
),
};
self.call_count += 1;
self.call_timestamp = Some(Local::now());
msg
}
}
Let’s try it out
Compile the whole project with
$ cargo build --release
Then you can run the service binary, which will block waiting for connections
$ target/release/service
With the service running, we can inspect and probe it using any D-Bus client, such as D-Feet or busctl
$ SVC=org.zbus.MyService
$ busctl --user call $SVC /org/zbus/MyService $SVC CallMe s "Andrea"
s "Hi Andrea, this is the first time you call me!"
$ busctl --user call $SVC /org/zbus/MyService $SVC CallMe s "Andrea"
s "Hello Andrea, I have been called 1 times, last was at Tue, 3 Oct 2023 18:08:16 +0200"
$ busctl --user call $SVC /org/zbus/MyService $SVC CallMe s "Andrea"
s "Hello Andrea, I have been called 2 times, last was at Tue, 3 Oct 2023 18:09:43 +0200"
You can notice the small s
character before the name parameter. This is the parameter type declaration, following the D-Bus type system
Well, our service seems working, we could stop here but let’s also implement …
The Client
A simple client is nice to have and will help a lot when we want to add functional testing to our project.
Also it makes us exercise a nice Cargo feature named workspaces
to host multiple binaries inside the same project.
Since source code is short we can paste as a whole:
use zbus::{dbus_proxy, Connection, Result};
#[dbus_proxy(
interface = "org.zbus.MyService",
default_service = "org.zbus.MyService",
default_path = "/org/zbus/MyService"
)]
trait MyService {
async fn call_me(&self, name: &str) -> Result<String>;
}
#[tokio::main]
async fn main() -> Result<()> {
let connection = Connection::session().await?;
// `dbus_proxy` macro creates `MyServiceProxy` based on `Notifications` trait.
let proxy = MyServiceProxy::new(&connection).await?;
let reply = proxy.call_me("Andrea").await?;
println!("{reply}");
Ok(())
}
You should have it already compiled, so with the service running, just issue a
$ target/release/client
"Hello Andrea, I have been called 3 times, last was at Tue, 3 Oct 2023 18:19:26 +0200"
Looks like we can park the bus for now and stop our journey here 😊
Image credits to Hans Middendorp
Conclusion
The D-Bus low-level API reference implementation and the D-Bus protocol have been heavily tested in the real world over several years, and are now “set in stone.” Future changes will either be compatible or versioned appropriately.
D-Bus is ~15y old technology, but still in use. Unfortunately many documents out there are sometime aging or misleading so it can be helpful to refresh it a bit and play with this message bus system. Happy Hacking!