1 /// Utility to watch the presence of a D-Bus services and interfaces. 2 use dbus::channel::MatchingReceiver; 3 use dbus::message::MatchRule; 4 use dbus::nonblock::stdintf::org_freedesktop_dbus::ObjectManager; 5 use dbus::nonblock::SyncConnection; 6 use std::sync::{Arc, Mutex}; 7 use std::time::Duration; 8 9 const DBUS_SERVICE: &str = "org.freedesktop.DBus"; 10 const DBUS_INTERFACE: &str = "org.freedesktop.DBus"; 11 const DBUS_OBJMGR_INTERFACE: &str = "org.freedesktop.DBus.ObjectManager"; 12 const DBUS_GET_NAME_OWNER: &str = "GetNameOwner"; 13 const DBUS_NAME_OWNER_CHANGED: &str = "NameOwnerChanged"; 14 const DBUS_INTERFACES_ADDED: &str = "InterfacesAdded"; 15 const DBUS_PATH: &str = "/org/freedesktop/DBus"; 16 const DBUS_TIMEOUT: Duration = Duration::from_secs(2); 17 18 struct ServiceWatcherContext { 19 // The service name to watch. 20 service_name: String, 21 // The owner D-Bus address if exists. 22 service_owner: Option<String>, 23 } 24 25 impl ServiceWatcherContext { new(service_name: String) -> Self26 fn new(service_name: String) -> Self { 27 Self { service_name, service_owner: None } 28 } 29 set_owner(&mut self, owner: String)30 fn set_owner(&mut self, owner: String) { 31 log::debug!("setting owner of {} to {}", self.service_name, owner); 32 self.service_owner = Some(owner); 33 } 34 unset_owner(&mut self)35 fn unset_owner(&mut self) { 36 log::debug!("unsetting owner of {}", self.service_name); 37 self.service_owner = None; 38 } 39 } 40 41 pub struct ServiceWatcher { 42 conn: Arc<SyncConnection>, 43 service_name: String, 44 context: Arc<Mutex<ServiceWatcherContext>>, 45 } 46 47 impl ServiceWatcher { new(conn: Arc<SyncConnection>, service_name: String) -> Self48 pub fn new(conn: Arc<SyncConnection>, service_name: String) -> Self { 49 ServiceWatcher { 50 conn, 51 service_name: service_name.clone(), 52 context: Arc::new(Mutex::new(ServiceWatcherContext::new(service_name))), 53 } 54 } 55 56 // Returns the owner D-Bus address of the named service. get_dbus_owner_of_service(&self) -> Option<String>57 async fn get_dbus_owner_of_service(&self) -> Option<String> { 58 let dbus_proxy = 59 dbus::nonblock::Proxy::new(DBUS_SERVICE, DBUS_PATH, DBUS_TIMEOUT, self.conn.clone()); 60 61 let service_owner: Result<(String,), dbus::Error> = dbus_proxy 62 .method_call(DBUS_INTERFACE, DBUS_GET_NAME_OWNER, (self.service_name.clone(),)) 63 .await; 64 65 match service_owner { 66 Err(e) => { 67 log::debug!("Getting service owner failed: {}", e); 68 None 69 } 70 Ok((owner,)) => { 71 log::debug!("Got service owner {} = {}", self.service_name, owner); 72 Some(owner) 73 } 74 } 75 } 76 77 // Returns the object path if the service exports an object having the specified interface. get_path_of_interface(&self, interface: String) -> Option<dbus::Path<'static>>78 async fn get_path_of_interface(&self, interface: String) -> Option<dbus::Path<'static>> { 79 let service_proxy = dbus::nonblock::Proxy::new( 80 self.service_name.clone(), 81 "/", 82 DBUS_TIMEOUT, 83 self.conn.clone(), 84 ); 85 86 let objects = service_proxy.get_managed_objects().await; 87 88 match objects { 89 Err(e) => { 90 log::debug!("Failed getting managed objects: {}", e); 91 None 92 } 93 Ok(objects) => objects 94 .into_iter() 95 .find(|(_key, value)| value.contains_key(&interface)) 96 .map(|(key, _value)| key), 97 } 98 } 99 100 // Monitors the service appearance or disappearance and keeps the owner address updated. monitor_name_owner_changed( &self, on_available: Box<dyn Fn() + Send>, on_unavailable: Box<dyn Fn() + Send>, )101 async fn monitor_name_owner_changed( 102 &self, 103 on_available: Box<dyn Fn() + Send>, 104 on_unavailable: Box<dyn Fn() + Send>, 105 ) { 106 let mr = MatchRule::new_signal(DBUS_INTERFACE, DBUS_NAME_OWNER_CHANGED); 107 self.conn 108 .add_match_no_cb(&mr.match_str()) 109 .await 110 .expect("Unable to add match to D-Bus for monitoring service owner change"); 111 let service_name = self.service_name.clone(); 112 let context = self.context.clone(); 113 self.conn.start_receive( 114 mr, 115 Box::new(move |msg, _conn| { 116 // We use [`dbus::nonblock::SyncConnection::set_signal_match_mode`] with 117 // `match_all` = true, so we should always return true from this closure to 118 // indicate that we have handled the message and not let other handlers handle this 119 // signal. 120 if let (Some(name), Some(old_owner), Some(new_owner)) = 121 msg.get3::<String, String, String>() 122 { 123 // Appearance/disappearance of unrelated service, ignore since we are not 124 // interested. 125 if name != service_name { 126 return true; 127 } 128 129 if old_owner.is_empty() && !new_owner.is_empty() { 130 context.lock().unwrap().set_owner(new_owner.clone()); 131 on_available(); 132 } else if !old_owner.is_empty() && new_owner.is_empty() { 133 context.lock().unwrap().unset_owner(); 134 on_unavailable(); 135 } else { 136 log::warn!( 137 "Invalid NameOwnerChanged with old_owner = {} and new_owner = {}", 138 old_owner, 139 new_owner 140 ); 141 } 142 } 143 true 144 }), 145 ); 146 } 147 148 /// Watches appearance and disappearance of a D-Bus service by the name. start_watch( &self, on_available: Box<dyn Fn() + Send>, on_unavailable: Box<dyn Fn() + Send>, )149 pub async fn start_watch( 150 &self, 151 on_available: Box<dyn Fn() + Send>, 152 on_unavailable: Box<dyn Fn() + Send>, 153 ) { 154 if let Some(owner) = self.get_dbus_owner_of_service().await { 155 self.context.lock().unwrap().set_owner(owner.clone()); 156 on_available(); 157 } 158 159 // Monitor service appearing and disappearing. 160 self.monitor_name_owner_changed( 161 Box::new(move || { 162 on_available(); 163 }), 164 Box::new(move || { 165 on_unavailable(); 166 }), 167 ) 168 .await; 169 } 170 171 /// Watches the appearance of an interface of a service, and the disappearance of the service. 172 /// 173 /// Doesn't take into account the disappearance of the interface itself. At the moment assuming 174 /// interfaces do not disappear as long as the service is alive. start_watch_interface( &mut self, interface: String, on_available: Box<dyn Fn(dbus::Path<'static>) + Send>, on_unavailable: Box<dyn Fn() + Send>, )175 pub async fn start_watch_interface( 176 &mut self, 177 interface: String, 178 on_available: Box<dyn Fn(dbus::Path<'static>) + Send>, 179 on_unavailable: Box<dyn Fn() + Send>, 180 ) { 181 if let Some(owner) = self.get_dbus_owner_of_service().await { 182 self.context.lock().unwrap().set_owner(owner.clone()); 183 if let Some(path) = self.get_path_of_interface(interface.clone()).await { 184 on_available(path); 185 } 186 } 187 188 // Monitor service disappearing. 189 self.monitor_name_owner_changed( 190 Box::new(move || { 191 // Don't trigger on_available() yet because we rely on interface added. 192 }), 193 Box::new(move || { 194 on_unavailable(); 195 }), 196 ) 197 .await; 198 199 // Monitor interface appearing. 200 let mr = MatchRule::new_signal(DBUS_OBJMGR_INTERFACE, DBUS_INTERFACES_ADDED); 201 self.conn 202 .add_match_no_cb(&mr.match_str()) 203 .await 204 .expect("Unable to add match to D-Bus for monitoring interface changes"); 205 let context = self.context.clone(); 206 self.conn.start_receive( 207 mr, 208 Box::new(move |msg, _conn| { 209 // We use [`dbus::nonblock::SyncConnection::set_signal_match_mode`] with 210 // `match_all` = true, so we should always return true from this closure to 211 // indicate that we have handled the message and not let other handlers handle this 212 // signal. 213 let (object_path, interfaces) = 214 msg.get2::<dbus::Path, dbus::arg::Dict<String, dbus::arg::PropMap, _>>(); 215 let interfaces: Vec<String> = interfaces.unwrap().map(|e| e.0).collect(); 216 if interfaces.contains(&interface) { 217 if let (Some(sender), Some(owner)) = 218 (msg.sender(), context.lock().unwrap().service_owner.as_ref()) 219 { 220 // The signal does not come from the service we are watching, ignore. 221 if &sender.to_string() != owner { 222 log::debug!( 223 "Detected interface {} added but sender is {}, expected {}.", 224 interface, 225 sender, 226 owner 227 ); 228 return true; 229 } 230 on_available(object_path.unwrap().into_static()); 231 } 232 } 233 234 true 235 }), 236 ); 237 } 238 } 239