Skip to main content

crabshell_services/
network.rs

1//! Network devices and Wi-Fi networks from NetworkManager, on the system
2//! bus.
3
4#[cfg(test)]
5mod tests;
6
7use std::cell::RefCell;
8use std::cmp::Ordering;
9use std::collections::btree_map::Entry;
10use std::collections::{BTreeMap, HashMap};
11use std::rc::Rc;
12use std::sync::Once;
13
14use crabshell_reactive::{ReadSignal, Signal};
15use crabshell_ui::Cx;
16use zbus::{Array, Connection, Message, ObjectPath, OwnedObjectPath, Variant};
17
18use crate::Shared;
19use crate::dbus::{self, CALL_TIMEOUT, Daemon, OBJECT_MANAGER, PROPERTIES, Props, set_changed};
20
21const NM: &str = "org.freedesktop.NetworkManager";
22const NM_PATH: &str = "/org/freedesktop/NetworkManager";
23/// Where NetworkManager's object manager is.
24const ROOT: &str = "/org/freedesktop";
25const DEVICE: &str = "org.freedesktop.NetworkManager.Device";
26const WIRELESS: &str = "org.freedesktop.NetworkManager.Device.Wireless";
27const ACCESS_POINT: &str = "org.freedesktop.NetworkManager.AccessPoint";
28const ACTIVE: &str = "org.freedesktop.NetworkManager.Connection.Active";
29
30/// The most devices kept, and the most active connections, about one a
31/// device. A host running containers has a virtual device for each.
32const MAX_DEVICES: usize = 64;
33/// The most access points kept: a scan in a city finds hundreds.
34const MAX_ACCESS_POINTS: usize = 512;
35/// The longest SSID 802.11 allows, in bytes.
36const MAX_SSID: usize = 32;
37
38/// NetworkManager's `DeviceType` for loopback, left out of the list.
39const LOOPBACK: u32 = 32;
40/// NetworkManager's `State` for a device it doesn't manage, left out.
41const UNMANAGED: u32 = 10;
42const ACTIVATED: u32 = 100;
43/// The `Flags` bit for an access point that asks for a key, as WEP does.
44const PRIVACY: u32 = 0x1;
45/// The WPA and RSN key managements that ask for a key: a password,
46/// 802.1X, SAE and Suite B 192.
47const KEY_MGMT_KEYED: u32 = 0x100 | 0x200 | 0x400 | 0x2000;
48/// The most objects kept, so a flood can't take the memory.
49const MAX_OBJECTS: usize = 4096;
50
51/// What a network device is, from NetworkManager's `DeviceType`.
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub enum NetworkKind {
54  /// A cable.
55  Ethernet,
56  /// Wi-Fi.
57  Wifi,
58  /// Anything else, such as a bridge or a VPN's tunnel.
59  Other,
60}
61
62/// How a network device is doing, from NetworkManager's `State`.
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum NetworkState {
65  /// Connected.
66  Connected,
67  /// On its way to connected: from preparing to waiting on secondary
68  /// connections.
69  Connecting,
70  /// Ready but not connected, going down, or failed to connect.
71  Disconnected,
72  /// Not ready, as with Wi-Fi off or no cable in, or a state not known.
73  Unavailable,
74}
75
76/// A network device NetworkManager manages, other than loopback.
77#[derive(Clone, Debug, PartialEq)]
78pub struct NetworkDevice {
79  /// NetworkManager's object path for it, which names it while it exists.
80  pub path: String,
81  /// Its interface, such as `wlan0`.
82  pub interface: String,
83  /// What it is.
84  pub kind: NetworkKind,
85  /// How it is doing.
86  pub state: NetworkState,
87  /// The name of its active connection, or `""`.
88  pub connection: String,
89}
90
91impl NetworkDevice {
92  fn from_props(path: String, props: &Props, objects: &BTreeMap<String, Object>) -> NetworkDevice {
93    let connection = dbus::get::<&ObjectPath>(props, "ActiveConnection")
94      .and_then(|at| objects.get(at.as_str())?.active.as_ref())
95      .map_or(String::new(), |active| dbus::text(active, "Id"));
96    NetworkDevice {
97      path,
98      interface: dbus::text(props, "Interface"),
99      kind: match dbus::get::<u32>(props, "DeviceType") {
100        Some(1) => NetworkKind::Ethernet,
101        Some(2) => NetworkKind::Wifi,
102        _ => NetworkKind::Other,
103      },
104      state: match dbus::get::<u32>(props, "State") {
105        Some(ACTIVATED) => NetworkState::Connected,
106        Some(40..=90) => NetworkState::Connecting,
107        Some(30 | 110 | 120) => NetworkState::Disconnected,
108        _ => NetworkState::Unavailable,
109      },
110      connection,
111    }
112  }
113}
114
115/// A Wi-Fi network a device sees.
116#[derive(Clone, Debug, PartialEq)]
117pub struct WifiNetwork {
118  /// Its name, at most 32 bytes, with bytes that aren't UTF-8 shown as
119  /// `�`.
120  pub ssid: String,
121  /// How strong its strongest access point is, from 0 to 100.
122  pub strength: u8,
123  /// Whether it asks for a key.
124  pub secured: bool,
125  /// Whether the device is connected to it, through any of its access
126  /// points.
127  pub connected: bool,
128  /// The path of the device that sees it.
129  pub device: String,
130  /// The path of its strongest access point.
131  pub access_point: String,
132}
133
134/// Networking from NetworkManager, from `cx.network()`. Cloning it is
135/// cheap.
136#[derive(Clone)]
137pub struct Network {
138  service: Option<Rc<NetworkService>>,
139  wifi_enabled: ReadSignal<bool>,
140  devices: ReadSignal<Vec<NetworkDevice>>,
141  wifi_networks: ReadSignal<Vec<WifiNetwork>>,
142}
143
144impl Network {
145  /// Whether Wi-Fi is on. `false` while NetworkManager isn't running.
146  pub fn wifi_enabled(&self) -> ReadSignal<bool> {
147    self.wifi_enabled
148  }
149
150  /// Every device but loopback and those NetworkManager doesn't manage,
151  /// in NetworkManager's numbering. Empty while it isn't running.
152  pub fn devices(&self) -> ReadSignal<Vec<NetworkDevice>> {
153    self.devices
154  }
155
156  /// The Wi-Fi networks each device sees, one per name and device,
157  /// strongest first. Hidden networks are left out. Empty while
158  /// NetworkManager isn't running.
159  pub fn wifi_networks(&self) -> ReadSignal<Vec<WifiNetwork>> {
160    self.wifi_networks
161  }
162
163  /// Turns Wi-Fi on or off.
164  pub fn set_wifi_enabled(&self, enabled: bool) {
165    let body = (NM, "WirelessEnabled", Variant::from(enabled));
166    self.call(NM_PATH, PROPERTIES, "Set", body);
167  }
168
169  /// Asks the Wi-Fi device at `device`, its path, to look for networks.
170  /// NetworkManager refuses a scan right after another, and that is
171  /// logged.
172  pub fn scan(&self, device: &str) {
173    let options = HashMap::<&str, Variant<'static>>::new();
174    self.call(device, WIRELESS, "RequestScan", options);
175  }
176
177  /// Connects `network`'s device to it. Without a password,
178  /// NetworkManager uses a connection saved for it, and refuses if it has
179  /// none. With one, it saves a new WPA connection and uses that. A
180  /// refusal is logged, never the password.
181  pub fn connect(&self, network: &WifiNetwork, password: Option<&str>) {
182    let paths = (
183      ObjectPath::try_from(network.device.clone()),
184      ObjectPath::try_from(network.access_point.clone()),
185    );
186    let (Ok(device), Ok(at)) = paths else {
187      return tracing::info!(network.ssid, "no such Wi-Fi network; nothing sent");
188    };
189    let Some(password) = password else {
190      return self.activate(network, device, at);
191    };
192    // The access point's own bytes: `ssid` may have lost some.
193    let ssid = self
194      .service
195      .as_ref()
196      .and_then(|service| {
197        let state = service.state.borrow();
198        let object = state.as_ref()?.objects.get(&network.access_point)?;
199        Some(ssid(object.access_point.as_ref()?))
200      })
201      .unwrap_or_else(|| network.ssid.clone().into_bytes());
202    let wireless = HashMap::from([("ssid", Variant::from(ssid))]);
203    // NetworkManager picks the key management, WPA2 or WPA3, from the
204    // access point.
205    let security = HashMap::from([("psk", Variant::from(password.to_owned()))]);
206    let settings = HashMap::from([
207      ("802-11-wireless", wireless),
208      ("802-11-wireless-security", security),
209    ]);
210    let body = (settings, device, at);
211    self.call(NM_PATH, NM, "AddAndActivateConnection", body);
212  }
213
214  /// Connects through a saved connection NetworkManager picks, and for
215  /// an open network with none, through a new one.
216  fn activate(&self, network: &WifiNetwork, device: ObjectPath<'static>, at: ObjectPath<'static>) {
217    let Some(service) = &self.service else {
218      return;
219    };
220    let connection = service
221      .state
222      .borrow()
223      .as_ref()
224      .and_then(|s| s.connection.clone());
225    let Some(connection) = connection else {
226      return tracing::info!("NetworkManager isn't running; nothing sent");
227    };
228    let (shared, open) = (Rc::clone(&service.shared), !network.secured);
229    let ssid = network.ssid.clone().into_bytes();
230    let ssid = self
231      .service
232      .as_ref()
233      .and_then(|service| {
234        let state = service.state.borrow();
235        let object = state.as_ref()?.objects.get(&network.access_point)?;
236        Some(self::ssid(object.access_point.as_ref()?))
237      })
238      .unwrap_or(ssid);
239    service.shared.spawn(async move {
240      let pick = ObjectPath::from_static_str_unchecked("/");
241      let body = (pick, &device, &at);
242      let call = connection.call_method(Some(NM), NM_PATH, Some(NM), "ActivateConnection", &body);
243      let Err(error) = shared.timeout(CALL_TIMEOUT, call).await else {
244        return;
245      };
246      if !open {
247        return tracing::warn!(%error, "no saved connection for that network");
248      }
249      let wireless = HashMap::from([("ssid", Variant::from(ssid))]);
250      let settings = HashMap::from([("802-11-wireless", wireless)]);
251      let body = (settings, &device, &at);
252      let method = "AddAndActivateConnection";
253      let call = connection.call_method(Some(NM), NM_PATH, Some(NM), method, &body);
254      if let Err(error) = shared.timeout(CALL_TIMEOUT, call).await {
255        tracing::warn!(%error, method, "NetworkManager refused it");
256      }
257    });
258  }
259
260  /// Disconnects the device at `device`, until it is connected again.
261  pub fn disconnect(&self, device: &str) {
262    self.call(device, DEVICE, "Disconnect", ());
263  }
264
265  /// Calls `method` on NetworkManager's object at `path`, and logs a
266  /// refusal, without the body.
267  fn call<B>(&self, path: &str, interface: &'static str, method: &'static str, body: B)
268  where
269    B: serde::Serialize + zbus::DynamicType + 'static,
270  {
271    let Some(service) = &self.service else {
272      return;
273    };
274    let Some(connection) = service
275      .state
276      .borrow()
277      .as_ref()
278      .and_then(|state| state.connection.clone())
279    else {
280      return tracing::info!(method, "NetworkManager isn't running; nothing sent");
281    };
282    let (shared, path) = (Rc::clone(&service.shared), path.to_owned());
283    service.shared.spawn(async move {
284      let call = connection.call_method(Some(NM), path.as_str(), Some(interface), method, &body);
285      if let Err(error) = shared.timeout(CALL_TIMEOUT, call).await {
286        tracing::warn!(%error, path, method, "NetworkManager refused it");
287      }
288    });
289  }
290}
291
292/// `cx.network()`.
293pub(crate) fn network(cx: &Cx) -> Network {
294  let Some(service) = cx.service::<NetworkService>() else {
295    static LOGGED: Once = Once::new();
296    LOGGED.call_once(|| {
297      tracing::warn!("cx.network() needs the services the shell provides; it stays empty");
298    });
299    return Network {
300      service: None,
301      wifi_enabled: cx.signal(false).read_only(),
302      devices: cx.signal(Vec::new()).read_only(),
303      wifi_networks: cx.signal(Vec::new()).read_only(),
304    };
305  };
306  service.start()
307}
308
309/// NetworkManager for one shell, followed from the first ask.
310pub(crate) struct NetworkService {
311  shared: Rc<Shared>,
312  state: RefCell<Option<State>>,
313}
314
315struct State {
316  wifi_enabled: Signal<bool>,
317  devices: Signal<Vec<NetworkDevice>>,
318  wifi_networks: Signal<Vec<WifiNetwork>>,
319  /// NetworkManager's objects that this service reads, by path.
320  objects: BTreeMap<String, Object>,
321  /// The bus, while NetworkManager runs, for the methods.
322  connection: Option<Connection>,
323  generation: u64,
324}
325
326/// The interfaces this service reads on one of NetworkManager's objects.
327/// An object is kept while it has one other than `wireless`.
328#[derive(Default)]
329struct Object {
330  manager: Option<Props>,
331  device: Option<Props>,
332  wireless: Option<Props>,
333  access_point: Option<Props>,
334  active: Option<Props>,
335}
336
337impl Object {
338  /// Where `interface`'s properties go, or `None` for one this service
339  /// doesn't read.
340  fn slot(&mut self, interface: &str) -> Option<&mut Option<Props>> {
341    match interface {
342      NM => Some(&mut self.manager),
343      DEVICE => Some(&mut self.device),
344      WIRELESS => Some(&mut self.wireless),
345      ACCESS_POINT => Some(&mut self.access_point),
346      ACTIVE => Some(&mut self.active),
347      _ => None,
348    }
349  }
350
351  fn kept(&self) -> bool {
352    self.manager.is_some()
353      || self.device.is_some()
354      || self.access_point.is_some()
355      || self.active.is_some()
356  }
357}
358
359/// Orders NetworkManager's paths by the number that ends them, so
360/// `Devices/2` comes before `Devices/10`.
361fn by_number(a: &str, b: &str) -> Ordering {
362  (a.len(), a).cmp(&(b.len(), b))
363}
364
365/// An access point's SSID, cut to 802.11's 32 bytes. Empty for one that
366/// isn't an `ay`.
367fn ssid(props: &Props) -> Vec<u8> {
368  let bytes = dbus::get::<&Array>(props, "Ssid");
369  let bytes = bytes.iter().flat_map(|bytes| bytes.inner());
370  let bytes = bytes.map_while(|byte| match byte {
371    Variant::U8(byte) => Some(*byte),
372    _ => None,
373  });
374  bytes.take(MAX_SSID).collect()
375}
376
377/// The object paths in the `ao` property `name`.
378fn paths<'a>(props: &'a Props, name: &str) -> impl Iterator<Item = &'a str> {
379  let paths = dbus::get::<&Array>(props, name);
380  paths
381    .into_iter()
382    .flat_map(|paths| paths.inner())
383    .filter_map(|path| match path {
384      Variant::ObjectPath(path) => Some(path.as_str()),
385      _ => None,
386    })
387}
388
389/// Gives the object at `path` `interfaces`. Past [`MAX_OBJECTS`] a new
390/// one is left out; which are shown is decided as they are.
391fn add(objects: &mut BTreeMap<String, Object>, path: String, interfaces: HashMap<String, Props>) {
392  if objects.len() >= MAX_OBJECTS && !objects.contains_key(&path) {
393    return tracing::debug!(path, "too many network objects; leaving one out");
394  }
395  let mut object = objects.remove(&path).unwrap_or_default();
396  for (interface, props) in interfaces {
397    if let Some(slot) = object.slot(&interface) {
398      *slot = Some(props);
399    }
400  }
401  if object.kept() {
402    objects.insert(path, object);
403  }
404}
405
406/// Whether the access point `point` asks for a key: WEP, or WPA with a
407/// password, 802.1X or SAE. Enhanced Open (OWE) encrypts without one.
408fn secured(point: &Props) -> bool {
409  let flags = |name| dbus::get::<u32>(point, name).unwrap_or(0);
410  let keyed = (flags("WpaFlags") | flags("RsnFlags")) & KEY_MGMT_KEYED != 0;
411  let wep = flags("Flags") & PRIVACY != 0 && flags("WpaFlags") == 0 && flags("RsnFlags") == 0;
412  keyed || wep
413}
414
415/// The properties this service reads, by interface: a change to any
416/// other, such as an access point's `LastSeen` on each scan, shows
417/// nothing new.
418fn read(interface: &str, name: &str) -> bool {
419  match interface {
420    NM => name == "WirelessEnabled",
421    DEVICE => matches!(
422      name,
423      "Interface" | "DeviceType" | "State" | "ActiveConnection"
424    ),
425    WIRELESS => matches!(name, "AccessPoints" | "ActiveAccessPoint"),
426    ACCESS_POINT => matches!(
427      name,
428      "Ssid" | "Strength" | "Flags" | "WpaFlags" | "RsnFlags"
429    ),
430    ACTIVE => name == "Id",
431    _ => false,
432  }
433}
434
435/// The networks each Wi-Fi device in `objects` sees: the strongest access
436/// point per SSID and device, strongest first.
437fn wifi_networks(objects: &BTreeMap<String, Object>) -> Vec<WifiNetwork> {
438  let mut best = BTreeMap::<(&str, Vec<u8>), WifiNetwork>::new();
439  for (path, object) in objects {
440    let (Some(device), Some(wireless)) = (&object.device, &object.wireless) else {
441      continue;
442    };
443    let on = dbus::get::<u32>(device, "State") == Some(ACTIVATED);
444    let active = dbus::get::<&ObjectPath>(wireless, "ActiveAccessPoint");
445    let active = active.map(|at| at.as_str());
446    for at in paths(wireless, "AccessPoints") {
447      let Some(point) = objects
448        .get(at)
449        .and_then(|object| object.access_point.as_ref())
450      else {
451        continue;
452      };
453      let ssid = ssid(point);
454      // Hidden: empty, or NUL bytes standing in for its length.
455      if ssid.iter().all(|byte| *byte == 0) {
456        continue;
457      }
458      let network = WifiNetwork {
459        ssid: String::from_utf8_lossy(&ssid).into_owned(),
460        strength: dbus::get::<u8>(point, "Strength").unwrap_or(0).min(100),
461        secured: secured(point),
462        connected: on && active == Some(at),
463        device: path.clone(),
464        access_point: at.to_owned(),
465      };
466      match best.entry((path, ssid)) {
467        Entry::Vacant(entry) => {
468          entry.insert(network);
469        }
470        Entry::Occupied(mut entry) => {
471          let connected = entry.get().connected || network.connected;
472          if network.strength > entry.get().strength {
473            entry.insert(network);
474          }
475          entry.get_mut().connected = connected;
476        }
477      }
478    }
479  }
480  let mut networks = best.into_values().collect::<Vec<_>>();
481  networks.sort_by(|a, b| {
482    let ties = || (&a.ssid, &a.device).cmp(&(&b.ssid, &b.device));
483    b.strength.cmp(&a.strength).then_with(ties)
484  });
485  networks.truncate(MAX_ACCESS_POINTS);
486  networks
487}
488
489impl NetworkService {
490  pub(crate) fn new(shared: Rc<Shared>) -> NetworkService {
491    NetworkService {
492      shared,
493      state: RefCell::new(None),
494    }
495  }
496
497  fn start(self: &Rc<Self>) -> Network {
498    if let Some(state) = &*self.state.borrow() {
499      return Network {
500        service: Some(Rc::clone(self)),
501        wifi_enabled: state.wifi_enabled.read_only(),
502        devices: state.devices.read_only(),
503        wifi_networks: state.wifi_networks.read_only(),
504      };
505    }
506    let root = &self.shared.root;
507    let wifi_enabled = root.signal(false);
508    let (devices, wifi_networks) = (root.signal(Vec::new()), root.signal(Vec::new()));
509    *self.state.borrow_mut() = Some(State {
510      wifi_enabled,
511      devices,
512      wifi_networks,
513      objects: BTreeMap::new(),
514      connection: None,
515      generation: 0,
516    });
517    match self.shared.config.system_bus.clone() {
518      Some(address) => {
519        let follow = dbus::follow(Rc::clone(&self.shared), Rc::clone(self), address);
520        self.shared.spawn(follow);
521      }
522      None => tracing::info!("no system bus, so there is no networking"),
523    }
524    Network {
525      service: Some(Rc::clone(self)),
526      wifi_enabled: wifi_enabled.read_only(),
527      devices: devices.read_only(),
528      wifi_networks: wifi_networks.read_only(),
529    }
530  }
531
532  /// Shows Wi-Fi, the devices and the networks as they are now. Each is
533  /// set only if it changed, since a scan changes much that none shows.
534  fn publish(&self) {
535    // Set apart from the borrow, since effects may ask for more.
536    let shown = self.state.borrow().as_ref().map(|state| {
537      let objects = &state.objects;
538      let enabled = objects
539        .get(NM_PATH)
540        .and_then(|object| dbus::get(object.manager.as_ref()?, "WirelessEnabled"))
541        .unwrap_or(false);
542      let mut devices = objects
543        .iter()
544        .filter_map(|(path, object)| {
545          let props = object.device.as_ref()?;
546          let kind = dbus::get::<u32>(props, "DeviceType");
547          let state = dbus::get::<u32>(props, "State");
548          if kind == Some(LOOPBACK) || state == Some(UNMANAGED) {
549            return None;
550          }
551          Some(NetworkDevice::from_props(path.clone(), props, objects))
552        })
553        .collect::<Vec<_>>();
554      devices.sort_by(|a, b| by_number(&a.path, &b.path));
555      devices.truncate(MAX_DEVICES);
556      let shown = (enabled, devices, wifi_networks(objects));
557      (
558        state.wifi_enabled,
559        state.devices,
560        state.wifi_networks,
561        shown,
562      )
563    });
564    if let Some((enabled_signal, devices_signal, networks_signal, shown)) = shown {
565      let (enabled, devices, networks) = shown;
566      set_changed(enabled_signal, enabled);
567      set_changed(devices_signal, devices);
568      set_changed(networks_signal, networks);
569    }
570  }
571}
572
573impl Daemon for NetworkService {
574  const NAME: &'static str = NM;
575
576  /// Only what this service reads: NetworkManager also broadcasts IP and
577  /// DHCP settings, statistics and saved connections, which would each
578  /// wake the shell for nothing.
579  fn rules() -> Vec<String> {
580    let mut rules = vec![format!(
581      "type='signal',sender='{NM}',path='{ROOT}',interface='{OBJECT_MANAGER}'"
582    )];
583    rules.extend(
584      [NM, DEVICE, WIRELESS, ACCESS_POINT, ACTIVE].map(|interface| {
585        format!(
586          "type='signal',sender='{NM}',interface='{PROPERTIES}',member='PropertiesChanged',\
587         arg0='{interface}'"
588        )
589      }),
590    );
591    rules
592  }
593
594  fn alive(&self) -> bool {
595    let state = self.state.borrow();
596    state
597      .as_ref()
598      .is_some_and(|state| state.devices.try_with(|_| ()).is_some())
599  }
600
601  fn clear(&self) -> u64 {
602    let generation = {
603      let mut state = self.state.borrow_mut();
604      let Some(state) = state.as_mut() else {
605        return 0;
606      };
607      state.objects.clear();
608      state.connection = None;
609      state.generation += 1;
610      state.generation
611    };
612    self.publish();
613    generation
614  }
615
616  async fn read(self: Rc<Self>, connection: Connection, generation: u64) {
617    let call = connection.call_method(
618      Some(NM),
619      ROOT,
620      Some(OBJECT_MANAGER),
621      "GetManagedObjects",
622      &(),
623    );
624    let read = async {
625      let reply = self.shared.timeout(CALL_TIMEOUT, call).await?;
626      reply
627        .body()
628        .deserialize::<HashMap<OwnedObjectPath, HashMap<String, Props>>>()
629    };
630    let managed = match read.await {
631      Ok(managed) => managed,
632      Err(error) => return tracing::warn!(%error, "couldn't read NetworkManager"),
633    };
634    // By number, so the caps keep the oldest objects, the same every time.
635    let mut managed = managed
636      .into_iter()
637      .map(|(path, interfaces)| (path.as_str().to_owned(), interfaces))
638      .collect::<Vec<_>>();
639    managed.sort_by(|(a, _), (b, _)| by_number(a, b));
640    {
641      let mut state = self.state.borrow_mut();
642      let Some(state) = state
643        .as_mut()
644        .filter(|state| state.generation == generation)
645      else {
646        return;
647      };
648      state.objects.clear();
649      for (path, interfaces) in managed {
650        add(&mut state.objects, path, interfaces);
651      }
652      state.connection = Some(connection);
653    }
654    self.publish();
655  }
656
657  fn signal(self: &Rc<Self>, _: &Connection, message: &Message) {
658    if let Some((path, interface, changed, dropped)) = dbus::properties_changed(message) {
659      {
660        let mut state = self.state.borrow_mut();
661        let props = state
662          .as_mut()
663          .and_then(|state| state.objects.get_mut(&path))
664          .and_then(|object| object.slot(&interface)?.as_mut());
665        let Some(props) = props else {
666          return;
667        };
668        let shown = changed
669          .keys()
670          .chain(&dropped)
671          .any(|name| read(&interface, name));
672        dbus::apply(props, changed, dropped);
673        if !shown {
674          return;
675        }
676      }
677      return self.publish();
678    }
679    let header = message.header();
680    if header.interface().map(|interface| interface.as_str()) != Some(OBJECT_MANAGER) {
681      return;
682    }
683    let body = message.body();
684    {
685      let mut state = self.state.borrow_mut();
686      let Some(state) = state.as_mut() else {
687        return;
688      };
689      match header.member().map(|member| member.as_str()) {
690        Some("InterfacesAdded") => {
691          let Ok((path, interfaces)) =
692            body.deserialize::<(OwnedObjectPath, HashMap<String, Props>)>()
693          else {
694            return;
695          };
696          add(&mut state.objects, path.as_str().to_owned(), interfaces);
697        }
698        Some("InterfacesRemoved") => {
699          let Ok((path, interfaces)) = body.deserialize::<(OwnedObjectPath, Vec<String>)>() else {
700            return;
701          };
702          let Some(object) = state.objects.get_mut(path.as_str()) else {
703            return;
704          };
705          for interface in interfaces {
706            if let Some(slot) = object.slot(&interface) {
707              *slot = None;
708            }
709          }
710          if !object.kept() {
711            state.objects.remove(path.as_str());
712          }
713        }
714        _ => return,
715      }
716    }
717    self.publish();
718  }
719}