1 //! This crate provides tools to automatically project generic API to D-Bus RPC.
2 //!
3 //! For D-Bus projection to work automatically, the API needs to follow certain restrictions.
4 
5 use dbus::channel::MatchingReceiver;
6 use dbus::message::MatchRule;
7 use dbus::nonblock::SyncConnection;
8 use dbus::strings::BusName;
9 
10 use std::collections::HashMap;
11 use std::sync::{Arc, Mutex};
12 
13 /// A D-Bus "NameOwnerChanged" handler that continuously monitors client disconnects.
14 pub struct DisconnectWatcher {
15     callbacks: Arc<Mutex<HashMap<BusName<'static>, Vec<Box<dyn Fn() + Send>>>>>,
16 }
17 
18 impl DisconnectWatcher {
19     /// Creates a new DisconnectWatcher with empty callbacks.
new() -> DisconnectWatcher20     pub fn new() -> DisconnectWatcher {
21         DisconnectWatcher { callbacks: Arc::new(Mutex::new(HashMap::new())) }
22     }
23 }
24 
25 impl DisconnectWatcher {
26     /// Adds a client address to be monitored for disconnect events.
add(&mut self, address: BusName<'static>, callback: Box<dyn Fn() + Send>)27     pub fn add(&mut self, address: BusName<'static>, callback: Box<dyn Fn() + Send>) {
28         if !self.callbacks.lock().unwrap().contains_key(&address) {
29             self.callbacks.lock().unwrap().insert(address.clone(), vec![]);
30         }
31 
32         (*self.callbacks.lock().unwrap().get_mut(&address).unwrap()).push(callback);
33     }
34 
35     /// Sets up the D-Bus handler that monitors client disconnects.
setup_watch(&mut self, conn: Arc<SyncConnection>)36     pub async fn setup_watch(&mut self, conn: Arc<SyncConnection>) {
37         let mr = MatchRule::new_signal("org.freedesktop.DBus", "NameOwnerChanged");
38 
39         conn.add_match_no_cb(&mr.match_str()).await.unwrap();
40         let callbacks_map = self.callbacks.clone();
41         conn.start_receive(
42             mr,
43             Box::new(move |msg, _conn| {
44                 // The args are "address", "old address", "new address".
45                 // https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-name-owner-changed
46                 let (addr, old, new) = msg.get3::<String, String, String>();
47 
48                 if addr.is_none() || old.is_none() || new.is_none() {
49                     return true;
50                 }
51 
52                 if old.unwrap().eq("") || !new.unwrap().eq("") {
53                     return true;
54                 }
55 
56                 // If old address exists but new address is empty, that means that client is
57                 // disconnected. So call the registered callbacks to be notified of this client
58                 // disconnect.
59                 let addr = BusName::new(addr.unwrap()).unwrap().into_static();
60                 if !callbacks_map.lock().unwrap().contains_key(&addr) {
61                     return true;
62                 }
63 
64                 for callback in &callbacks_map.lock().unwrap()[&addr] {
65                     callback();
66                 }
67 
68                 callbacks_map.lock().unwrap().remove(&addr);
69 
70                 true
71             }),
72         );
73     }
74 }
75 
76 #[macro_export]
77 macro_rules! impl_dbus_arg_enum {
78     ($enum_type:ty) => {
79         impl DBusArg for $enum_type {
80             type DBusType = i32;
81             fn from_dbus(
82                 data: i32,
83                 _conn: Arc<SyncConnection>,
84                 _remote: BusName<'static>,
85                 _disconnect_watcher: Arc<Mutex<dbus_projection::DisconnectWatcher>>,
86             ) -> Result<$enum_type, Box<dyn Error>> {
87                 match <$enum_type>::from_i32(data) {
88                     Some(x) => Ok(x),
89                     None => Err(Box::new(DBusArgError::new(String::from(format!(
90                         "error converting {} to {}",
91                         data,
92                         stringify!($enum_type)
93                     ))))),
94                 }
95             }
96 
97             fn to_dbus(data: $enum_type) -> Result<i32, Box<dyn Error>> {
98                 return Ok(data.to_i32().unwrap());
99             }
100         }
101     };
102 }
103