Skip to main content

wayle_network/
monitoring.rs

1use std::sync::Arc;
2
3use tokio_stream::StreamExt;
4use tokio_util::sync::CancellationToken;
5use tracing::{debug, warn};
6use wayle_core::Property;
7use wayle_traits::{Reactive, ServiceMonitoring};
8use zbus::Connection;
9
10use super::{
11    core::settings::Settings,
12    discovery::NetworkServiceDiscovery,
13    error::Error,
14    proxy::manager::NetworkManagerProxy,
15    service::NetworkService,
16    types::connectivity::ConnectionType,
17    wifi::{LiveWifiParams, Wifi},
18    wired::{LiveWiredParams, Wired},
19};
20
21impl ServiceMonitoring for NetworkService {
22    type Error = Error;
23
24    async fn start_monitoring(&self) -> Result<(), Self::Error> {
25        spawn_primary_monitoring(
26            self.zbus_connection.clone(),
27            self.primary.clone(),
28            self.cancellation_token.child_token(),
29        )
30        .await?;
31
32        spawn_device_monitoring(
33            self.zbus_connection.clone(),
34            self.wifi.clone(),
35            self.wired.clone(),
36            self.settings.clone(),
37            self.cancellation_token.child_token(),
38        )
39        .await
40    }
41}
42
43async fn spawn_primary_monitoring(
44    connection: Connection,
45    primary: Property<ConnectionType>,
46    cancellation_token: CancellationToken,
47) -> Result<(), Error> {
48    let nm_proxy = NetworkManagerProxy::new(&connection)
49        .await
50        .map_err(Error::DbusError)?;
51
52    let initial_type = nm_proxy.primary_connection_type().await?;
53    update_primary_connection(&initial_type, &primary);
54
55    let mut type_changed = nm_proxy.receive_primary_connection_type_changed().await;
56
57    tokio::spawn(async move {
58        loop {
59            tokio::select! {
60                _ = cancellation_token.cancelled() => {
61                    debug!("NetworkMonitoring primary monitoring cancelled");
62                    return;
63                }
64                Some(change) = type_changed.next() => {
65                    if let Ok(nm_type) = change.get().await {
66                        debug!(nm_type = %nm_type, "Primary connection type changed");
67                        update_primary_connection(&nm_type, &primary);
68                    }
69                }
70            }
71        }
72    });
73
74    Ok(())
75}
76
77async fn spawn_device_monitoring(
78    connection: Connection,
79    wifi: Property<Option<Arc<Wifi>>>,
80    wired: Property<Option<Arc<Wired>>>,
81    settings: Arc<Settings>,
82    cancellation_token: CancellationToken,
83) -> Result<(), Error> {
84    let nm_proxy = NetworkManagerProxy::new(&connection)
85        .await
86        .map_err(Error::DbusError)?;
87
88    let mut device_added = nm_proxy.receive_device_added().await?;
89    let mut device_removed = nm_proxy.receive_device_removed().await?;
90
91    tokio::spawn(async move {
92        loop {
93            tokio::select! {
94                _ = cancellation_token.cancelled() => {
95                    debug!("NetworkMonitoring device monitoring cancelled");
96                    return;
97                }
98                Some(signal) = device_added.next() => {
99                    let Ok(args) = signal.args() else { continue };
100                    debug!(path = %args.device_path, "Network device added");
101
102                    try_initialize_wifi(&connection, &wifi, &settings, &cancellation_token).await;
103                    try_initialize_wired(&connection, &wired, &cancellation_token).await;
104                }
105                Some(signal) = device_removed.next() => {
106                    let Ok(args) = signal.args() else { continue };
107                    debug!(path = %args.device_path, "Network device removed");
108
109                    handle_wifi_removed(&args.device_path, &wifi);
110                    handle_wired_removed(&args.device_path, &wired);
111                }
112            }
113        }
114    });
115
116    Ok(())
117}
118
119async fn try_initialize_wifi(
120    connection: &Connection,
121    wifi: &Property<Option<Arc<Wifi>>>,
122    settings: &Arc<Settings>,
123    cancellation_token: &CancellationToken,
124) {
125    if wifi.get().is_some() {
126        return;
127    }
128
129    let Some(path) = NetworkServiceDiscovery::wifi_device_path(connection)
130        .await
131        .ok()
132        .flatten()
133    else {
134        return;
135    };
136
137    match Wifi::get_live(LiveWifiParams {
138        connection,
139        device_path: path.clone(),
140        cancellation_token,
141        settings: settings.clone(),
142    })
143    .await
144    {
145        Ok(new_wifi) => {
146            debug!(path = %path, "WiFi device initialized");
147            wifi.set(Some(new_wifi));
148        }
149        Err(err) => {
150            warn!(error = %err, path = %path, "Failed to initialize WiFi device");
151        }
152    }
153}
154
155async fn try_initialize_wired(
156    connection: &Connection,
157    wired: &Property<Option<Arc<Wired>>>,
158    cancellation_token: &CancellationToken,
159) {
160    if wired.get().is_some() {
161        return;
162    }
163
164    let Some(path) = NetworkServiceDiscovery::wired_device_path(connection)
165        .await
166        .ok()
167        .flatten()
168    else {
169        return;
170    };
171
172    match Wired::get_live(LiveWiredParams {
173        connection,
174        device_path: path.clone(),
175        cancellation_token,
176    })
177    .await
178    {
179        Ok(new_wired) => {
180            debug!(path = %path, "Wired device initialized");
181            wired.set(Some(new_wired));
182        }
183        Err(err) => {
184            warn!(error = %err, path = %path, "Failed to initialize wired device");
185        }
186    }
187}
188
189fn handle_wifi_removed(device_path: &str, wifi: &Property<Option<Arc<Wifi>>>) {
190    let Some(current) = wifi.get() else { return };
191
192    if current.device.core.object_path.as_str() == device_path {
193        debug!(path = %device_path, "WiFi device removed");
194        wifi.set(None);
195    }
196}
197
198fn handle_wired_removed(device_path: &str, wired: &Property<Option<Arc<Wired>>>) {
199    let Some(current) = wired.get() else { return };
200
201    if current.device.core.object_path.as_str() == device_path {
202        debug!(path = %device_path, "Wired device removed");
203        wired.set(None);
204    }
205}
206
207fn update_primary_connection(nm_type: &str, primary: &Property<ConnectionType>) {
208    let connection_type = ConnectionType::from_nm_type(nm_type);
209    debug!(?connection_type, "Primary connection type resolved");
210    primary.set(connection_type);
211}