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.add_match_no_cb(&mr.match_str()).await.unwrap(); 108 let service_name = self.service_name.clone(); 109 let context = self.context.clone(); 110 self.conn.start_receive( 111 mr, 112 Box::new(move |msg, _conn| { 113 // We use [`dbus::nonblock::SyncConnection::set_signal_match_mode`] with 114 // `match_all` = true, so we should always return true from this closure to 115 // indicate that we have handled the message and not let other handlers handle this 116 // signal. 117 if let (Some(name), Some(old_owner), Some(new_owner)) = 118 msg.get3::<String, String, String>() 119 { 120 // Appearance/disappearance of unrelated service, ignore since we are not 121 // interested. 122 if name != service_name { 123 return true; 124 } 125 126 if old_owner == "" && new_owner != "" { 127 context.lock().unwrap().set_owner(new_owner.clone()); 128 on_available(); 129 } else if old_owner != "" && new_owner == "" { 130 context.lock().unwrap().unset_owner(); 131 on_unavailable(); 132 } else { 133 log::warn!( 134 "Invalid NameOwnerChanged with old_owner = {} and new_owner = {}", 135 old_owner, 136 new_owner 137 ); 138 } 139 } 140 true 141 }), 142 ); 143 } 144 145 /// 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>, )146 pub async fn start_watch( 147 &self, 148 on_available: Box<dyn Fn() + Send>, 149 on_unavailable: Box<dyn Fn() + Send>, 150 ) { 151 if let Some(owner) = self.get_dbus_owner_of_service().await { 152 self.context.lock().unwrap().set_owner(owner.clone()); 153 on_available(); 154 } 155 156 // Monitor service appearing and disappearing. 157 self.monitor_name_owner_changed( 158 Box::new(move || { 159 on_available(); 160 }), 161 Box::new(move || { 162 on_unavailable(); 163 }), 164 ) 165 .await; 166 } 167 168 /// Watches the appearance of an interface of a service, and the disappearance of the service. 169 /// 170 /// Doesn't take into account the disappearance of the interface itself. At the moment assuming 171 /// 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>, )172 pub async fn start_watch_interface( 173 &mut self, 174 interface: String, 175 on_available: Box<dyn Fn(dbus::Path<'static>) + Send>, 176 on_unavailable: Box<dyn Fn() + Send>, 177 ) { 178 if let Some(owner) = self.get_dbus_owner_of_service().await { 179 self.context.lock().unwrap().set_owner(owner.clone()); 180 if let Some(path) = self.get_path_of_interface(interface.clone()).await { 181 on_available(path); 182 } 183 } 184 185 // Monitor service disappearing. 186 self.monitor_name_owner_changed( 187 Box::new(move || { 188 // Don't trigger on_available() yet because we rely on interface added. 189 }), 190 Box::new(move || { 191 on_unavailable(); 192 }), 193 ) 194 .await; 195 196 // Monitor interface appearing. 197 let mr = MatchRule::new_signal(DBUS_OBJMGR_INTERFACE, DBUS_INTERFACES_ADDED); 198 self.conn.add_match_no_cb(&mr.match_str()).await.unwrap(); 199 let context = self.context.clone(); 200 self.conn.start_receive( 201 mr, 202 Box::new(move |msg, _conn| { 203 // We use [`dbus::nonblock::SyncConnection::set_signal_match_mode`] with 204 // `match_all` = true, so we should always return true from this closure to 205 // indicate that we have handled the message and not let other handlers handle this 206 // signal. 207 let (object_path, interfaces) = 208 msg.get2::<dbus::Path, dbus::arg::Dict<String, dbus::arg::PropMap, _>>(); 209 let interfaces: Vec<String> = interfaces.unwrap().map(|e| e.0).collect(); 210 if interfaces.contains(&interface) { 211 if let (Some(sender), Some(owner)) = 212 (msg.sender(), context.lock().unwrap().service_owner.as_ref()) 213 { 214 // The signal does not come from the service we are watching, ignore. 215 if &sender.to_string() != owner { 216 log::debug!( 217 "Detected interface {} added but sender is {}, expected {}.", 218 interface, 219 sender, 220 owner 221 ); 222 return true; 223 } 224 on_available(object_path.unwrap().into_static()); 225 } 226 } 227 228 true 229 }), 230 ); 231 } 232 } 233