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}