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.
Blocking API
While zbus API being primarily asynchronous (since 2.0) is a great thing, it could easily feel daunting for simple use cases. Not to worry! In the spirit of “ease” being a primary goal of zbus, it provides blocking wrapper types, under the blocking module.
Establishing a connection
The only difference to that of asynchronous Connection
API is that you use
blocking::Connection
type instead. This type’s API is almost identical to that of Connection
,
except all its methods are blocking. One notable difference is that there is no equivalent of
futures::sink::Sink
implementation provided. There is however [blocking::MessageIterator
]
type, that implements std::iter::Iterator
.
Client
Similar to blocking::Connection
, you use blocking::Proxy
type. Its constructors require
blocking::Connection
instead of Connection
. Moreover, dbus_proxy
macro generates a
blocking::Proxy
wrapper for you as well. Let’s convert the last example in the previous chapter,
to use the blocking connection and proxy:
#![allow(unused)] fn main() { use zbus::{blocking::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::system().unwrap(); let manager = ManagerProxyBlocking::new(&conn).unwrap(); let mut client = manager.get_client().unwrap(); // Gotta do this, sorry! client.set_desktop_id("org.freedesktop.zbus").unwrap(); let mut location_updated = client.receive_location_updated().unwrap(); client.start().unwrap(); // Wait for the signal. let signal = location_updated.next().unwrap(); let args = signal.args().unwrap(); let location = LocationProxyBlocking::builder(&conn) .path(args.new()) .unwrap() .build() .unwrap(); println!( "Latitude: {}\nLongitude: {}", location.latitude().unwrap(), location.longitude().unwrap(), ); }
As you can see, nothing changed in the dbus_proxy
usage here and the rest largely remained the
same as well. One difference that’s not obvious is that the blocking API for receiving signals,
implement std::iter::Iterator
trait instead of [futrues::stream::Stream
].
Watching for properties
That’s almost the same as receiving signals:
use std::{thread::sleep, time::Duration}; use zbus::{blocking::Connection, dbus_proxy, Result}; #[dbus_proxy( interface = "org.freedesktop.systemd1.Manager", default_service = "org.freedesktop.systemd1", default_path = "/org/freedesktop/systemd1" )] trait SystemdManager { #[dbus_proxy(property)] fn log_level(&self) -> zbus::Result<String>; } fn main() -> Result<()> { let connection = Connection::session()?; let proxy = SystemdManagerProxyBlocking::new(&connection)?; let v = proxy.receive_log_level_changed().next().unwrap(); println!("LogLevel changed: {:?}", v.get()); Ok(()) }
Server
Similarly here, you’d use [blocking::ObjectServer
] that is associated with every
blocking::Connection
instance. While there is no blocking version of Interface
,
dbus_interface
allows you to write non-async methods.
Note: Even though you can write non-async methods, these methods are still called from an async
context. Therefore, you can not use blocking API that’s a wrapper of an async API. This is not a
limitation of zbus but rather a well-known general problem in the Rust async/await world.
The blocking
crate provides an easy way around this problem.
use std::error::Error; use zbus::{blocking::{ObjectServer, ConnectionBuilder}, dbus_interface, fdo, SignalContext}; use event_listener::Event; struct Greeter { name: String, done: Event, } #[dbus_interface(name = "org.zbus.MyGreeter1")] impl Greeter { fn say_hello(&self, name: &str) -> String { format!("Hello {}!", name) } // Rude! fn go_away(&self) { self.done.notify(1); } /// 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)] async fn greeted_everyone(ctxt: &SignalContext<'_>) -> zbus::Result<()>; } fn main() -> Result<(), Box<dyn Error>> { let greeter = Greeter { name: "GreeterName".to_string(), done: event_listener::Event::new(), }; let done_listener = greeter.done.listen(); let _ = ConnectionBuilder::session()? .name("org.zbus.MyGreeter")? .serve_at("/org/zbus/MyGreeter", greeter)? .build()?; done_listener.wait(); Ok(()) }