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