blob: 7d948a730927aed5372d88082d0b880446c63360 [file] [log] [blame]
//! This crate provides tools to automatically project generic API to D-Bus RPC.
//!
//! For D-Bus projection to work automatically, the API needs to follow certain restrictions.
use dbus::channel::MatchingReceiver;
use dbus::message::MatchRule;
use dbus::nonblock::SyncConnection;
use dbus::strings::BusName;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
/// A D-Bus "NameOwnerChanged" handler that continuously monitors client disconnects.
pub struct DisconnectWatcher {
callbacks: Arc<Mutex<HashMap<BusName<'static>, Vec<Box<dyn Fn() + Send>>>>>,
}
impl DisconnectWatcher {
/// Creates a new DisconnectWatcher with empty callbacks.
pub fn new() -> DisconnectWatcher {
DisconnectWatcher { callbacks: Arc::new(Mutex::new(HashMap::new())) }
}
}
impl DisconnectWatcher {
/// Adds a client address to be monitored for disconnect events.
pub fn add(&mut self, address: BusName<'static>, callback: Box<dyn Fn() + Send>) {
if !self.callbacks.lock().unwrap().contains_key(&address) {
self.callbacks.lock().unwrap().insert(address.clone(), vec![]);
}
(*self.callbacks.lock().unwrap().get_mut(&address).unwrap()).push(callback);
}
/// Sets up the D-Bus handler that monitors client disconnects.
pub async fn setup_watch(&mut self, conn: Arc<SyncConnection>) {
let mr = MatchRule::new_signal("org.freedesktop.DBus", "NameOwnerChanged");
conn.add_match_no_cb(&mr.match_str()).await.unwrap();
let callbacks_map = self.callbacks.clone();
conn.start_receive(
mr,
Box::new(move |msg, _conn| {
// The args are "address", "old address", "new address".
// https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-name-owner-changed
let (addr, old, new) = msg.get3::<String, String, String>();
if addr.is_none() || old.is_none() || new.is_none() {
return true;
}
if old.unwrap().eq("") || !new.unwrap().eq("") {
return true;
}
// If old address exists but new address is empty, that means that client is
// disconnected. So call the registered callbacks to be notified of this client
// disconnect.
let addr = BusName::new(addr.unwrap()).unwrap().into_static();
if !callbacks_map.lock().unwrap().contains_key(&addr) {
return true;
}
for callback in &callbacks_map.lock().unwrap()[&addr] {
callback();
}
callbacks_map.lock().unwrap().remove(&addr);
true
}),
);
}
}
#[macro_export]
macro_rules! impl_dbus_arg_enum {
($enum_type:ty) => {
impl DBusArg for $enum_type {
type DBusType = i32;
fn from_dbus(
data: i32,
_conn: Arc<SyncConnection>,
_remote: BusName<'static>,
_disconnect_watcher: Arc<Mutex<dbus_projection::DisconnectWatcher>>,
) -> Result<$enum_type, Box<dyn Error>> {
match <$enum_type>::from_i32(data) {
Some(x) => Ok(x),
None => Err(Box::new(DBusArgError::new(String::from(format!(
"error converting {} to {}",
data,
stringify!($enum_type)
))))),
}
}
fn to_dbus(data: $enum_type) -> Result<i32, Box<dyn Error>> {
return Ok(data.to_i32().unwrap());
}
}
};
}