use async_io::block_on;
use std::{
convert::{TryFrom, TryInto},
future::ready,
};
use zvariant::{ObjectPath, OwnedValue, Value};
use crate::{azync, Connection, Message, Result};
use crate::fdo;
/// A client-side interface proxy.
///
/// A `Proxy` is a helper to interact with an interface on a remote object.
///
/// # Example
///
/// ```
/// use std::result::Result;
/// use std::error::Error;
/// use zbus::{Connection, Proxy};
///
/// fn main() -> Result<(), Box<dyn Error>> {
/// let connection = Connection::new_session()?;
/// let p = Proxy::new(
/// &connection,
/// "org.freedesktop.DBus",
/// "/org/freedesktop/DBus",
/// "org.freedesktop.DBus",
/// )?;
/// // owned return value
/// let _id: String = p.call("GetId", &())?;
/// // borrowed return value
/// let _id: &str = p.call_method("GetId", &())?.body()?;
/// Ok(())
/// }
/// ```
///
/// # Note
///
/// It is recommended to use the [`dbus_proxy`] macro, which provides a more convenient and
/// type-safe *façade* `Proxy` derived from a Rust trait.
///
/// ## Current limitations:
///
/// At the moment, `Proxy` doesn't:
///
/// * cache properties
/// * track the current name owner
/// * prevent auto-launching
///
/// [`dbus_proxy`]: attr.dbus_proxy.html
#[derive(Debug)]
pub struct Proxy<'a> {
conn: Connection,
azync: azync::Proxy<'a>,
}
impl<'a> Proxy<'a> {
/// Create a new `Proxy` for the given destination/path/interface.
pub fn new(
conn: &Connection,
destination: &'a str,
path: impl TryInto<ObjectPath<'a>, Error = zvariant::Error>,
interface: &'a str,
) -> Result<Self> {
let proxy = azync::Proxy::new(conn.inner(), destination, path, interface)?;
Ok(Self {
conn: conn.clone(),
azync: proxy,
})
}
/// Create a new `Proxy` for the given destination/path/interface, taking ownership of all
/// passed arguments.
pub fn new_owned(
conn: Connection,
destination: String,
path: impl TryInto<ObjectPath<'static>, Error = zvariant::Error>,
interface: String,
) -> Result<Self> {
let proxy =
azync::Proxy::new_owned(conn.clone().into_inner(), destination, path, interface)?;
Ok(Self { conn, azync: proxy })
}
/// Get a reference to the associated connection.
pub fn connection(&self) -> &Connection {
&self.conn
}
/// Get a reference to the destination service name.
pub fn destination(&self) -> &str {
self.azync.destination()
}
/// Get a reference to the object path.
pub fn path(&self) -> &ObjectPath<'_> {
self.azync.path()
}
/// Get a reference to the interface.
pub fn interface(&self) -> &str {
self.azync.interface()
}
/// Introspect the associated object, and return the XML description.
///
/// See the [xml](xml/index.html) module for parsing the result.
pub fn introspect(&self) -> fdo::Result<String> {
block_on(self.azync.introspect())
}
/// Get the property `property_name`.
///
/// Effectively, call the `Get` method of the `org.freedesktop.DBus.Properties` interface.
pub fn get_property<T>(&self, property_name: &str) -> fdo::Result<T>
where
T: TryFrom<OwnedValue>,
{
block_on(self.azync.get_property(property_name))
}
/// Set the property `property_name`.
///
/// Effectively, call the `Set` method of the `org.freedesktop.DBus.Properties` interface.
pub fn set_property<'t, T: 't>(&self, property_name: &str, value: T) -> fdo::Result<()>
where
T: Into<Value<'t>>,
{
block_on(self.azync.set_property(property_name, value))
}
/// Call a method and return the reply.
///
/// Typically, you would want to use [`call`] method instead. Use this method if you need to
/// deserialize the reply message manually (this way, you can avoid the memory
/// allocation/copying, by deserializing the reply to an unowned type).
///
/// [`call`]: struct.Proxy.html#method.call
pub fn call_method<B>(&self, method_name: &str, body: &B) -> Result<Message>
where
B: serde::ser::Serialize + zvariant::Type,
{
block_on(self.azync.call_method(method_name, body))
}
/// Call a method and return the reply body.
///
/// Use [`call_method`] instead if you need to deserialize the reply manually/separately.
///
/// [`call_method`]: struct.Proxy.html#method.call_method
pub fn call<B, R>(&self, method_name: &str, body: &B) -> Result<R>
where
B: serde::ser::Serialize + zvariant::Type,
R: serde::de::DeserializeOwned + zvariant::Type,
{
block_on(self.azync.call(method_name, body))
}
/// Register a handler for signal named `signal_name`.
///
/// Once a handler is successfully registered, call [`Self::next_signal`] to wait for the next
/// signal to arrive and be handled by its registered handler.
///
/// If the associated connnection is to a bus, a match rule is added for the signal on the bus
/// so that the bus sends us the signals.
///
/// ### Errors
///
/// This method can fail if addition of the relevant match rule on the bus fails. You can
/// safely `unwrap` the `Result` if you're certain that associated connnection is not a bus
/// connection.
pub fn connect_signal<H>(&self, signal_name: &'static str, mut handler: H) -> fdo::Result<()>
where
H: FnMut(&Message) -> Result<()> + Send + 'static,
{
block_on(
self.azync
.connect_signal(signal_name, move |msg| Box::pin(ready(handler(msg)))),
)
}
/// Deregister the handler for the signal named `signal_name`.
///
/// If the associated connnection is to a bus, the match rule is removed for the signal on the
/// bus so that the bus stops sending us the signal. This method returns `Ok(true)` if a
/// handler was registered for `signal_name` and was removed by this call; `Ok(false)`
/// otherwise.
///
/// ### Errors
///
/// This method can fail if removal of the relevant match rule on the bus fails. You can
/// safely `unwrap` the `Result` if you're certain that associated connnection is not a bus
/// connection.
pub fn disconnect_signal(&self, signal_name: &'static str) -> fdo::Result<bool> {
block_on(self.azync.disconnect_signal(signal_name))
}
/// Receive and handle the next incoming signal on the associated connection.
///
/// This method will wait for signal messages on the associated connection and call any
/// handlers registered through the [`Self::connect_signal`] method. Signal handlers can be
/// registered and deregistered from another threads during the call to this method.
///
/// If the signal message was handled by a handler, `Ok(None)` is returned. Otherwise, the
/// received message is returned.
pub fn next_signal(&self) -> Result<Option<Message>> {
block_on(self.azync.next_signal())
}
/// Handle the provided signal message.
///
/// Call any handlers registered through the [`Self::connect_signal`] method for the provided
/// signal message.
///
/// If no errors are encountered, `Ok(true)` is returned if a handler was found and called for,
/// the signal; `Ok(false)` otherwise.
pub fn handle_signal(&self, msg: &Message) -> Result<bool> {
block_on(self.azync.handle_signal(msg))
}
/// Get a reference to the underlying async Proxy.
pub fn inner(&self) -> &azync::Proxy<'_> {
&self.azync
}
/// Get the underlying async Proxy, consuming `self`.
pub fn into_inner(self) -> azync::Proxy<'a> {
self.azync
}
pub(crate) fn has_signal_handler(&self, signal_name: &str) -> bool {
block_on(self.azync.has_signal_handler(signal_name))
}
}
impl<'asref, 'p: 'asref> std::convert::AsRef<Proxy<'asref>> for Proxy<'p> {
fn as_ref(&self) -> &Proxy<'asref> {
&self
}
}
impl<'p, 'a: 'p> From<azync::Proxy<'a>> for Proxy<'p> {
fn from(proxy: azync::Proxy<'a>) -> Self {
Self {
conn: proxy.connection().clone().into(),
azync: proxy,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
#[test]
fn signal() {
// Register a well-known name with the session bus and ensure we get the appropriate
// signals called for that.
let conn = Connection::new_session().unwrap();
let owner_change_signaled = Arc::new(Mutex::new(false));
let name_acquired_signaled = Arc::new(Mutex::new(false));
let proxy = Proxy::new(
&conn,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
)
.unwrap();
let well_known = "org.freedesktop.zbus.ProxySignalTest";
let unique_name = conn.unique_name().unwrap().to_string();
{
let well_known = well_known.clone();
let signaled = owner_change_signaled.clone();
proxy
.connect_signal("NameOwnerChanged", move |m| {
let (name, _, new_owner) = m.body::<(&str, &str, &str)>()?;
if name != well_known {
// Meant for the other testcase then
return Ok(());
}
assert_eq!(new_owner, unique_name);
*signaled.lock().unwrap() = true;
Ok(())
})
.unwrap();
}
{
let signaled = name_acquired_signaled.clone();
// `NameAcquired` is emitted twice, first when the unique name is assigned on
// connection and secondly after we ask for a specific name.
proxy
.connect_signal("NameAcquired", move |m| {
if m.body::<&str>()? == well_known {
*signaled.lock().unwrap() = true;
}
Ok(())
})
.unwrap();
}
fdo::DBusProxy::new(&conn)
.unwrap()
.request_name(&well_known, fdo::RequestNameFlags::ReplaceExisting.into())
.unwrap();
loop {
proxy.next_signal().unwrap();
if *owner_change_signaled.lock().unwrap() && *name_acquired_signaled.lock().unwrap() {
break;
}
}
}
}