Skip to main content

wayle_network/core/settings/
mod.rs

1mod controls;
2mod monitoring;
3mod types;
4
5use std::{collections::HashMap, sync::Arc};
6
7use controls::SettingsController;
8use derive_more::Debug;
9use futures::{Stream, StreamExt, future::join_all};
10use tokio_util::sync::CancellationToken;
11use tracing::warn;
12pub(crate) use types::{LiveSettingsParams, SettingsParams};
13use wayle_core::{Property, unwrap_dbus};
14use wayle_traits::{ModelMonitoring, Reactive};
15use zbus::{
16    Connection,
17    zvariant::{OwnedObjectPath, OwnedValue},
18};
19
20use super::{
21    access_point::types::Ssid,
22    settings_connection::{ConnectionSettings, ConnectionSettingsParams},
23};
24use crate::{
25    error::Error, proxy::settings::SettingsProxy, types::flags::NMSettingsAddConnection2Flags,
26};
27
28/// Connection Settings Profile Manager.
29///
30/// The Settings interface allows clients to view and administrate
31/// the connections stored and used by NetworkManager.
32#[derive(Debug, Clone)]
33pub struct Settings {
34    #[debug(skip)]
35    pub(crate) zbus_connection: Connection,
36    #[debug(skip)]
37    pub(crate) cancellation_token: Option<CancellationToken>,
38    /// List of object paths of available network connection profiles.
39    pub connections: Property<Vec<ConnectionSettings>>,
40    /// The machine hostname stored in persistent configuration.
41    pub hostname: Property<String>,
42    /// If true, adding and modifying connections is supported.
43    pub can_modify: Property<bool>,
44    /// The version of the settings. This is incremented whenever the profile
45    /// changes and can be used to detect concurrent modifications. Since: 1.44
46    pub version_id: Property<u64>,
47}
48
49impl Reactive for Settings {
50    type Context<'a> = SettingsParams<'a>;
51    type LiveContext<'a> = LiveSettingsParams<'a>;
52    type Error = Error;
53
54    async fn get(params: Self::Context<'_>) -> Result<Self, Self::Error> {
55        Self::from_connection(params.zbus_connection, None).await
56    }
57
58    async fn get_live(params: Self::LiveContext<'_>) -> Result<Arc<Self>, Self::Error> {
59        let settings = Self::from_connection(
60            params.zbus_connection,
61            Some(params.cancellation_token.child_token()),
62        )
63        .await?;
64        let settings = Arc::new(settings);
65
66        settings.clone().start_monitoring().await?;
67
68        Ok(settings)
69    }
70}
71
72impl Settings {
73    /// List the saved network connections known to NetworkManager.
74    ///
75    /// # Returns
76    ///
77    /// List of connection object paths.
78    ///
79    /// # Errors
80    ///
81    /// Returns `NetworkError::DbusError` if the DBus operation fails.
82    pub async fn list_connections(&self) -> Result<Vec<OwnedObjectPath>, Error> {
83        SettingsController::list_connections(&self.zbus_connection).await
84    }
85
86    /// Retrieve the object path of a connection, given that connection's UUID.
87    ///
88    /// # Arguments
89    ///
90    /// * `uuid` - The UUID to find the connection object path for.
91    ///
92    /// # Returns
93    ///
94    /// The connection's object path.
95    ///
96    /// # Errors
97    ///
98    /// Returns `NetworkError::DbusError` if the DBus operation fails or connection not found.
99    pub async fn get_connection_by_uuid(&self, uuid: &str) -> Result<OwnedObjectPath, Error> {
100        SettingsController::get_connection_by_uuid(&self.zbus_connection, uuid).await
101    }
102
103    /// Add new connection and save it to disk.
104    ///
105    /// This operation does not start the network connection unless
106    /// (1) device is idle and able to connect to the network described
107    ///     by the new connection AND
108    /// (2) the connection is allowed to be started automatically.
109    ///
110    /// # Arguments
111    ///
112    /// * `connection` - Connection settings and properties.
113    ///
114    /// # Returns
115    ///
116    /// Object path of the new connection that was just added.
117    ///
118    /// # Errors
119    ///
120    /// Returns `NetworkError::DbusError` if the DBus operation fails.
121    pub async fn add_connection(
122        &self,
123        connection: HashMap<String, HashMap<String, OwnedValue>>,
124    ) -> Result<OwnedObjectPath, Error> {
125        SettingsController::add_connection(&self.zbus_connection, connection).await
126    }
127
128    /// Add new connection but do not save it to disk immediately.
129    ///
130    /// This operation does not start the network connection unless (1) device is idle
131    /// and able to connect to the network described by the new connection, and (2) the
132    /// connection is allowed to be started automatically. Use the 'Save' method on the
133    /// connection to save these changes to disk.
134    ///
135    /// # Arguments
136    ///
137    /// * `connection` - Connection settings and properties.
138    ///
139    /// # Returns
140    ///
141    /// Object path of the new connection that was just added.
142    ///
143    /// # Errors
144    ///
145    /// Returns `NetworkError::DbusError` if the DBus operation fails.
146    pub async fn add_connection_unsaved(
147        &self,
148        connection: HashMap<String, HashMap<String, OwnedValue>>,
149    ) -> Result<OwnedObjectPath, Error> {
150        SettingsController::add_connection_unsaved(&self.zbus_connection, connection).await
151    }
152
153    /// Add a new connection profile.
154    ///
155    /// AddConnection2 is an alternative to AddConnection and AddConnectionUnsaved.
156    /// The new variant can do everything that the older variants could, and more.
157    /// Its behavior is extensible via extra flags and args arguments.
158    ///
159    /// # Arguments
160    ///
161    /// * `settings` - Connection configuration as nested hashmaps. The outer map keys are
162    ///   setting names like "connection", "802-3-ethernet", "ipv4", etc. The inner maps
163    ///   contain the properties for each setting.
164    /// * `flags` - Control how the connection is stored:
165    ///   - `TO_DISK`: Persist the connection to disk
166    ///   - `IN_MEMORY`: Keep the connection in memory only
167    ///   - `BLOCK_AUTOCONNECT`: Prevent automatic connection until manually activated
168    /// * `args` - Additional arguments:
169    ///   - `"plugin"`: Specify storage backend like "keyfile" or "ifcfg-rh" (Since 1.38)
170    /// # Returns
171    ///
172    /// Returns a tuple containing:
173    /// - The DBus object path of the newly created connection
174    /// - A result dictionary (currently empty but reserved for future use)
175    ///
176    /// # Errors
177    ///
178    /// Returns `NetworkError::DbusError` if the DBus operation fails.
179    pub async fn add_connection2(
180        &self,
181        settings: HashMap<String, HashMap<String, OwnedValue>>,
182        flags: NMSettingsAddConnection2Flags,
183        args: HashMap<String, OwnedValue>,
184    ) -> Result<(OwnedObjectPath, HashMap<String, OwnedValue>), Error> {
185        SettingsController::add_connection2(&self.zbus_connection, settings, flags, args).await
186    }
187
188    /// Loads or reloads the indicated connections from disk.
189    ///
190    /// You should call this after making changes directly to an on-disk
191    /// connection file to make sure that NetworkManager sees the changes.
192    /// As with AddConnection(), this operation does not necessarily start
193    /// the network connection.
194    ///
195    /// # Arguments
196    ///
197    /// * `filenames` - Array of paths to on-disk connection profiles in directories monitored by NetworkManager
198    ///
199    /// # Returns
200    ///
201    /// Returns a tuple containing:
202    /// - `status`: Success or failure of the operation as a whole. True if NetworkManager
203    ///   at least tried to load the indicated connections, even if it did not succeed.
204    ///   False if an error occurred before trying to load the connections (eg, permission denied).
205    /// - `failures`: Paths of connection files that could not be loaded
206    ///
207    /// # Errors
208    ///
209    /// Returns `NetworkError::DbusError` if the DBus operation fails.
210    pub async fn load_connections(
211        &self,
212        filenames: Vec<String>,
213    ) -> Result<(bool, Vec<String>), Error> {
214        SettingsController::load_connections(&self.zbus_connection, filenames).await
215    }
216
217    /// Tells NetworkManager to reload all connection files from disk.
218    ///
219    /// Reloads all connection files from disk, including noticing any
220    /// added or deleted connection files.
221    ///
222    /// # Returns
223    ///
224    /// This always returns true.
225    ///
226    /// # Errors
227    ///
228    /// Returns `NetworkError::DbusError` if the DBus operation fails.
229    pub async fn reload_connections(&self) -> Result<bool, Error> {
230        SettingsController::reload_connections(&self.zbus_connection).await
231    }
232
233    /// Save the hostname to persistent configuration.
234    ///
235    /// # Arguments
236    ///
237    /// * `hostname` - The hostname to save to persistent configuration.
238    ///   If blank, the persistent hostname is cleared.
239    ///
240    /// # Errors
241    ///
242    /// Returns `NetworkError::OperationFailed` if the operations fails.
243    pub async fn save_hostname(&self, hostname: &str) -> Result<(), Error> {
244        SettingsController::save_hostname(&self.zbus_connection, hostname).await
245    }
246
247    /// Saved connection profiles matching the given SSID.
248    ///
249    /// A single SSID may have multiple profiles with different configurations.
250    pub fn connections_for_ssid(&self, ssid: &Ssid) -> Vec<ConnectionSettings> {
251        self.connections
252            .get()
253            .into_iter()
254            .filter(|connection| connection.matches_ssid(ssid))
255            .collect()
256    }
257
258    /// Deletes all saved connection profiles for the given SSID.
259    ///
260    /// Individual profile deletion errors are logged but do not stop
261    /// remaining deletions.
262    pub async fn delete_connections_for_ssid(&self, ssid: &Ssid) {
263        for connection in self.connections_for_ssid(ssid) {
264            if let Err(err) = connection.delete().await {
265                warn!(error = %err, "failed to delete saved wifi profile");
266            }
267        }
268    }
269
270    /// Reactive stream of saved connections for the given SSID.
271    ///
272    /// Emits whenever connections are added, removed, or modified
273    /// for the specified SSID.
274    pub fn connections_for_ssid_monitored(
275        &self,
276        ssid: Ssid,
277    ) -> impl Stream<Item = Vec<ConnectionSettings>> + '_ {
278        self.connections.watch().map(move |connections| {
279            connections
280                .into_iter()
281                .filter(|connection| connection.matches_ssid(&ssid))
282                .collect()
283        })
284    }
285
286    async fn from_connection(
287        zbus_connection: &Connection,
288        cancellation_token: Option<CancellationToken>,
289    ) -> Result<Self, Error> {
290        let settings_proxy = SettingsProxy::new(zbus_connection).await?;
291
292        let (connections, hostname, can_modify, version_id) = tokio::join!(
293            settings_proxy.connections(),
294            settings_proxy.hostname(),
295            settings_proxy.can_modify(),
296            settings_proxy.version_id()
297        );
298
299        let connection_paths = unwrap_dbus!(connections);
300
301        let connection_futures = connection_paths.into_iter().map(|path| {
302            ConnectionSettings::get(ConnectionSettingsParams {
303                connection: zbus_connection,
304                path,
305            })
306        });
307
308        let connection_list: Vec<ConnectionSettings> = join_all(connection_futures)
309            .await
310            .into_iter()
311            .flatten()
312            .collect();
313
314        Ok(Self {
315            zbus_connection: zbus_connection.clone(),
316            cancellation_token,
317            connections: Property::new(connection_list),
318            hostname: Property::new(unwrap_dbus!(hostname)),
319            can_modify: Property::new(unwrap_dbus!(can_modify)),
320            version_id: Property::new(unwrap_dbus!(version_id)),
321        })
322    }
323
324    /// Emitted when a new connection has been added.
325    ///
326    /// # Errors
327    /// Returns error if D-Bus proxy creation fails.
328    pub async fn new_connection_signal(
329        &self,
330    ) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
331        let proxy = SettingsProxy::new(&self.zbus_connection).await?;
332        let stream = proxy.receive_new_connection().await?;
333
334        Ok(stream
335            .filter_map(|signal| async move { signal.args().ok().map(|args| args.connection) }))
336    }
337
338    /// Emitted when a connection is no longer available.
339    ///
340    /// # Errors
341    /// Returns error if D-Bus proxy creation fails.
342    pub async fn connection_removed_signal(
343        &self,
344    ) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
345        let proxy = SettingsProxy::new(&self.zbus_connection).await?;
346        let stream = proxy.receive_connection_removed().await?;
347
348        Ok(stream
349            .filter_map(|signal| async move { signal.args().ok().map(|args| args.connection) }))
350    }
351}