wayle_network/core/settings_connection/
mod.rs1mod controls;
2mod monitoring;
3mod types;
4
5use std::{collections::HashMap, sync::Arc};
6
7use controls::ConnectionSettingsControls;
8use derive_more::Debug;
9use futures::{Stream, StreamExt};
10use tokio_util::sync::CancellationToken;
11pub(crate) use types::{ConnectionSettingsParams, LiveConnectionSettingsParams};
12use wayle_core::{Property, unwrap_dbus};
13use wayle_traits::{ModelMonitoring, Reactive};
14use zbus::{
15 Connection,
16 zvariant::{self, OwnedObjectPath, OwnedValue},
17};
18
19use super::access_point::types::Ssid;
20use crate::{
21 error::Error,
22 proxy::settings::connection::SettingsConnectionProxy,
23 types::{connectivity::ConnectionType, flags::NMConnectionSettingsFlags},
24};
25
26#[derive(Debug, Clone)]
30pub struct ConnectionSettings {
31 #[debug(skip)]
32 pub(crate) connection: Connection,
33 #[debug(skip)]
34 pub(crate) cancellation_token: Option<CancellationToken>,
35 pub object_path: OwnedObjectPath,
37
38 pub id: Property<String>,
40
41 pub uuid: Property<String>,
43
44 pub connection_type: Property<ConnectionType>,
46
47 pub wifi_ssid: Property<Option<Ssid>>,
49
50 pub unsaved: Property<bool>,
52
53 pub flags: Property<NMConnectionSettingsFlags>,
55
56 pub filename: Property<String>,
58}
59
60impl Reactive for ConnectionSettings {
61 type Context<'a> = ConnectionSettingsParams<'a>;
62 type LiveContext<'a> = LiveConnectionSettingsParams<'a>;
63 type Error = Error;
64
65 async fn get(params: Self::Context<'_>) -> Result<Self, Self::Error> {
66 Self::from_path(params.connection, params.path, None).await
67 }
68
69 async fn get_live(params: Self::LiveContext<'_>) -> Result<Arc<Self>, Self::Error> {
70 let properties = Self::fetch_properties(params.connection, ¶ms.path).await?;
71 let settings = Arc::new(Self::from_props(
72 params.path.clone(),
73 properties,
74 params.connection,
75 Some(params.cancellation_token.child_token()),
76 ));
77
78 settings.clone().start_monitoring().await?;
79
80 Ok(settings)
81 }
82}
83
84impl PartialEq for ConnectionSettings {
85 fn eq(&self, other: &Self) -> bool {
86 self.object_path == other.object_path
87 }
88}
89
90impl ConnectionSettings {
91 pub async fn update(
101 &self,
102 properties: HashMap<String, HashMap<String, OwnedValue>>,
103 ) -> Result<(), Error> {
104 ConnectionSettingsControls::update(&self.connection, &self.object_path, properties).await
105 }
106
107 pub async fn update_unsaved(
122 &self,
123 properties: HashMap<String, HashMap<String, OwnedValue>>,
124 ) -> Result<(), Error> {
125 ConnectionSettingsControls::update_unsaved(&self.connection, &self.object_path, properties)
126 .await
127 }
128
129 pub async fn delete(&self) -> Result<(), Error> {
135 ConnectionSettingsControls::delete(&self.connection, &self.object_path).await
136 }
137
138 pub async fn get_settings(
148 &self,
149 ) -> Result<HashMap<String, HashMap<String, OwnedValue>>, Error> {
150 ConnectionSettingsControls::get_settings(&self.connection, &self.object_path).await
151 }
152
153 pub async fn get_secrets(
168 &self,
169 setting_name: &str,
170 ) -> Result<HashMap<String, HashMap<String, OwnedValue>>, Error> {
171 ConnectionSettingsControls::get_secrets(&self.connection, &self.object_path, setting_name)
172 .await
173 }
174
175 pub async fn clear_secrets(&self) -> Result<(), Error> {
181 ConnectionSettingsControls::clear_secrets(&self.connection, &self.object_path).await
182 }
183
184 pub async fn save(&self) -> Result<(), Error> {
193 ConnectionSettingsControls::save(&self.connection, &self.object_path).await
194 }
195
196 pub async fn update2(
219 &self,
220 settings: HashMap<String, HashMap<String, OwnedValue>>,
221 flags: u32,
222 args: HashMap<String, OwnedValue>,
223 ) -> Result<HashMap<String, OwnedValue>, Error> {
224 ConnectionSettingsControls::update2(
225 &self.connection,
226 &self.object_path,
227 settings,
228 flags,
229 args,
230 )
231 .await
232 }
233
234 pub(crate) fn matches_ssid(&self, ssid: &Ssid) -> bool {
236 self.wifi_ssid
237 .get()
238 .as_ref()
239 .is_some_and(|stored| stored == ssid)
240 }
241
242 async fn from_path(
243 connection: &Connection,
244 path: OwnedObjectPath,
245 cancellation_token: Option<CancellationToken>,
246 ) -> Result<Self, Error> {
247 let properties = Self::fetch_properties(connection, &path).await?;
248 Ok(Self::from_props(
249 path,
250 properties,
251 connection,
252 cancellation_token,
253 ))
254 }
255
256 async fn fetch_properties(
257 connection: &Connection,
258 path: &OwnedObjectPath,
259 ) -> Result<SettingsConnectionProperties, Error> {
260 let proxy = SettingsConnectionProxy::new(connection, path)
261 .await
262 .map_err(Error::DbusError)?;
263
264 let (unsaved, flags, filename, settings) = tokio::join!(
265 proxy.unsaved(),
266 proxy.flags(),
267 proxy.filename(),
268 proxy.get_settings()
269 );
270
271 let (id, uuid, connection_type, wifi_ssid) = match settings {
272 Ok(ref settings_map) => extract_identity(settings_map),
273 Err(err) => {
274 tracing::debug!("cannot fetch GetSettings for {:?}: {}", path, err);
275 (String::new(), String::new(), ConnectionType::None, None)
276 }
277 };
278
279 Ok(SettingsConnectionProperties {
280 unsaved: unwrap_dbus!(unsaved, path),
281 flags: unwrap_dbus!(flags, path),
282 filename: unwrap_dbus!(filename, path),
283 id,
284 uuid,
285 connection_type,
286 wifi_ssid,
287 })
288 }
289
290 fn from_props(
291 path: OwnedObjectPath,
292 props: SettingsConnectionProperties,
293 connection: &Connection,
294 cancellation_token: Option<CancellationToken>,
295 ) -> Self {
296 Self {
297 connection: connection.clone(),
298 cancellation_token,
299 object_path: path,
300 id: Property::new(props.id),
301 uuid: Property::new(props.uuid),
302 connection_type: Property::new(props.connection_type),
303 wifi_ssid: Property::new(props.wifi_ssid),
304 unsaved: Property::new(props.unsaved),
305 flags: Property::new(NMConnectionSettingsFlags::from_bits_truncate(props.flags)),
306 filename: Property::new(props.filename),
307 }
308 }
309
310 pub async fn properties_changed_signal(
315 &self,
316 ) -> Result<impl Stream<Item = HashMap<String, OwnedValue>>, Error> {
317 let proxy = SettingsConnectionProxy::new(&self.connection, &self.object_path).await?;
318 let stream = proxy.receive_properties_changed().await?;
319
320 Ok(stream
321 .filter_map(|signal| async move { signal.args().ok().map(|args| args.properties) }))
322 }
323
324 pub async fn updated_signal(&self) -> Result<impl Stream<Item = ()>, Error> {
329 let proxy = SettingsConnectionProxy::new(&self.connection, &self.object_path).await?;
330 let stream = proxy.receive_updated().await?;
331
332 Ok(stream.filter_map(|_signal| async move { Some(()) }))
333 }
334
335 pub async fn removed_signal(&self) -> Result<impl Stream<Item = ()>, Error> {
340 let proxy = SettingsConnectionProxy::new(&self.connection, &self.object_path).await?;
341 let stream = proxy.receive_removed().await?;
342
343 Ok(stream.filter_map(|_signal| async move { Some(()) }))
344 }
345}
346
347fn extract_identity(
348 settings_map: &HashMap<String, HashMap<String, OwnedValue>>,
349) -> (String, String, ConnectionType, Option<Ssid>) {
350 let connection_group = settings_map.get("connection");
351
352 let id = connection_group
353 .and_then(|conn| conn.get("id"))
354 .and_then(|val| String::try_from(val.clone()).ok())
355 .unwrap_or_default();
356
357 let uuid = connection_group
358 .and_then(|conn| conn.get("uuid"))
359 .and_then(|val| String::try_from(val.clone()).ok())
360 .unwrap_or_default();
361
362 let connection_type = connection_group
363 .and_then(|conn| conn.get("type"))
364 .and_then(|val| String::try_from(val.clone()).ok())
365 .map(|type_str| ConnectionType::from_nm_type(&type_str))
366 .unwrap_or(ConnectionType::None);
367
368 let wifi_ssid = settings_map
369 .get("802-11-wireless")
370 .and_then(|wireless| wireless.get("ssid"))
371 .and_then(|val| val.downcast_ref::<zvariant::Array>().ok())
372 .and_then(|arr| <Vec<u8>>::try_from(arr).ok())
373 .map(Ssid::new);
374
375 (id, uuid, connection_type, wifi_ssid)
376}
377
378struct SettingsConnectionProperties {
379 unsaved: bool,
380 flags: u32,
381 filename: String,
382 id: String,
383 uuid: String,
384 connection_type: ConnectionType,
385 wifi_ssid: Option<Ssid>,
386}