From 892fad5d2c6c7558655a555e9b2afbf328bbb3b3 Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Wed, 14 Jan 2026 11:48:35 -0500 Subject: [PATCH 1/5] refactor: add device type registry system for extensible device type support Introduces a trait-based registry system that allows querying device type capabilities without exposing new enum variants. This enables future device type additions without API breakage. - Add 'DeviceInfo` trait for device type metadata - Implement registry with 9 device types (Ethernet, WiFi, Bridge, etc.) - Registry provides display names, connection types, and capability queries - Thread-safe initialization with `OnceLock` --- nmrs/src/types/device_type_registry.rs | 375 +++++++++++++++++++++++++ nmrs/src/types/mod.rs | 1 + 2 files changed, 376 insertions(+) create mode 100644 nmrs/src/types/device_type_registry.rs diff --git a/nmrs/src/types/device_type_registry.rs b/nmrs/src/types/device_type_registry.rs new file mode 100644 index 00000000..761f4fb7 --- /dev/null +++ b/nmrs/src/types/device_type_registry.rs @@ -0,0 +1,375 @@ +//! Device type registry for extensible device type support. +//! +//! This module provides a trait-based system for registering and working with +//! different network device types. It enables adding new device types without +//! breaking the public API. + +use std::collections::HashMap; +use std::sync::OnceLock; + +/// Trait for device type-specific behavior. +/// +/// Implement this trait to add support for a new device type. +/// The trait provides metadata about the device type and type-specific +/// operations that may be needed. +pub trait DeviceTypeInfo: Send + Sync { + /// Returns the NetworkManager D-Bus constant for this device type. + fn nm_type_code(&self) -> u32; + + /// Returns the human-readable name of this device type. + fn display_name(&self) -> &'static str; + + /// Returns the NetworkManager connection type string. + /// + /// This is used when creating connections for this device type. + /// Examples: "802-11-wireless", "802-3-ethernet", "wireguard", "bluetooth" + fn connection_type(&self) -> &'static str; + + /// Returns whether this device type supports scanning for networks. + fn supports_scanning(&self) -> bool { + false + } + + /// Returns whether this device type requires an access point or similar target. + fn requires_specific_object(&self) -> bool { + false + } + + /// Returns whether this device type can be globally enabled/disabled. + fn has_global_enabled_state(&self) -> bool { + false + } +} + +/// WiFi device type implementation. +struct WifiDeviceType; + +impl DeviceTypeInfo for WifiDeviceType { + fn nm_type_code(&self) -> u32 { + 2 + } + + fn display_name(&self) -> &'static str { + "Wi-Fi" + } + + fn connection_type(&self) -> &'static str { + "802-11-wireless" + } + + fn supports_scanning(&self) -> bool { + true + } + + fn requires_specific_object(&self) -> bool { + true + } + + fn has_global_enabled_state(&self) -> bool { + true + } +} + +/// Ethernet device type implementation. +struct EthernetDeviceType; + +impl DeviceTypeInfo for EthernetDeviceType { + fn nm_type_code(&self) -> u32 { + 1 + } + + fn display_name(&self) -> &'static str { + "Ethernet" + } + + fn connection_type(&self) -> &'static str { + "802-3-ethernet" + } +} + +/// WiFi P2P device type implementation. +struct WifiP2PDeviceType; + +impl DeviceTypeInfo for WifiP2PDeviceType { + fn nm_type_code(&self) -> u32 { + 30 + } + + fn display_name(&self) -> &'static str { + "Wi-Fi P2P" + } + + fn connection_type(&self) -> &'static str { + "wifi-p2p" + } + + fn supports_scanning(&self) -> bool { + true + } +} + +/// Loopback device type implementation. +struct LoopbackDeviceType; + +impl DeviceTypeInfo for LoopbackDeviceType { + fn nm_type_code(&self) -> u32 { + 32 + } + + fn display_name(&self) -> &'static str { + "Loopback" + } + + fn connection_type(&self) -> &'static str { + "loopback" + } +} + +/// Bridge device type implementation. +struct BridgeDeviceType; + +impl DeviceTypeInfo for BridgeDeviceType { + fn nm_type_code(&self) -> u32 { + 13 + } + + fn display_name(&self) -> &'static str { + "Bridge" + } + + fn connection_type(&self) -> &'static str { + "bridge" + } +} + +/// Bond device type implementation. +struct BondDeviceType; + +impl DeviceTypeInfo for BondDeviceType { + fn nm_type_code(&self) -> u32 { + 12 + } + + fn display_name(&self) -> &'static str { + "Bond" + } + + fn connection_type(&self) -> &'static str { + "bond" + } +} + +/// VLAN device type implementation. +struct VlanDeviceType; + +impl DeviceTypeInfo for VlanDeviceType { + fn nm_type_code(&self) -> u32 { + 11 + } + + fn display_name(&self) -> &'static str { + "VLAN" + } + + fn connection_type(&self) -> &'static str { + "vlan" + } +} + +/// TUN/TAP device type implementation. +struct TunDeviceType; + +impl DeviceTypeInfo for TunDeviceType { + fn nm_type_code(&self) -> u32 { + 16 + } + + fn display_name(&self) -> &'static str { + "TUN" + } + + fn connection_type(&self) -> &'static str { + "tun" + } +} + +/// WireGuard device type implementation. +struct WireGuardDeviceType; + +impl DeviceTypeInfo for WireGuardDeviceType { + fn nm_type_code(&self) -> u32 { + 29 + } + + fn display_name(&self) -> &'static str { + "WireGuard" + } + + fn connection_type(&self) -> &'static str { + "wireguard" + } +} + +/// Global registry of device types. +/// +/// This registry maps NetworkManager type codes to device type information. +/// It's populated once at first access and remains immutable thereafter. +static DEVICE_TYPE_REGISTRY: OnceLock>> = OnceLock::new(); + +/// Initializes and returns the device type registry. +fn registry() -> &'static HashMap> { + DEVICE_TYPE_REGISTRY.get_or_init(|| { + let mut map: HashMap> = HashMap::new(); + + let types: Vec> = vec![ + Box::new(EthernetDeviceType), + Box::new(WifiDeviceType), + Box::new(WifiP2PDeviceType), + Box::new(LoopbackDeviceType), + Box::new(BridgeDeviceType), + Box::new(BondDeviceType), + Box::new(VlanDeviceType), + Box::new(TunDeviceType), + Box::new(WireGuardDeviceType), + ]; + + for type_info in types { + map.insert(type_info.nm_type_code(), type_info); + } + + map + }) +} + +/// Looks up device type information by NetworkManager type code. +/// +/// Returns `None` if the device type is not recognized. +pub fn get_device_type_info(code: u32) -> Option<&'static dyn DeviceTypeInfo> { + registry().get(&code).map(|b| &**b) +} + +/// Returns the display name for a device type code. +/// +/// If the code is not recognized, returns a generic "Other(N)" string. +pub fn display_name_for_code(code: u32) -> String { + get_device_type_info(code) + .map(|info| info.display_name().to_string()) + .unwrap_or_else(|| format!("Other({})", code)) +} + +/// Returns the connection type string for a device type code. +/// +/// Returns `None` if the device type is not recognized or doesn't +/// have an associated connection type. +pub fn connection_type_for_code(code: u32) -> Option<&'static str> { + get_device_type_info(code).map(|info| info.connection_type()) +} + +/// Returns whether a device type supports scanning. +pub fn supports_scanning(code: u32) -> bool { + get_device_type_info(code) + .map(|info| info.supports_scanning()) + .unwrap_or(false) +} + +/// Returns whether a device type requires a specific object (like an AP). +pub fn requires_specific_object(code: u32) -> bool { + get_device_type_info(code) + .map(|info| info.requires_specific_object()) + .unwrap_or(false) +} + +/// Returns whether a device type has a global enabled state. +pub fn has_global_enabled_state(code: u32) -> bool { + get_device_type_info(code) + .map(|info| info.has_global_enabled_state()) + .unwrap_or(false) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn wifi_type_info() { + let info = get_device_type_info(2).expect("WiFi should be registered"); + assert_eq!(info.nm_type_code(), 2); + assert_eq!(info.display_name(), "Wi-Fi"); + assert_eq!(info.connection_type(), "802-11-wireless"); + assert!(info.supports_scanning()); + assert!(info.requires_specific_object()); + assert!(info.has_global_enabled_state()); + } + + #[test] + fn ethernet_type_info() { + let info = get_device_type_info(1).expect("Ethernet should be registered"); + assert_eq!(info.nm_type_code(), 1); + assert_eq!(info.display_name(), "Ethernet"); + assert_eq!(info.connection_type(), "802-3-ethernet"); + assert!(!info.supports_scanning()); + assert!(!info.requires_specific_object()); + } + + #[test] + fn wireguard_type_info() { + let info = get_device_type_info(29).expect("WireGuard should be registered"); + assert_eq!(info.nm_type_code(), 29); + assert_eq!(info.display_name(), "WireGuard"); + assert_eq!(info.connection_type(), "wireguard"); + } + + #[test] + fn unknown_device_type() { + let info = get_device_type_info(999); + assert!(info.is_none()); + } + + #[test] + fn display_name_for_unknown() { + let name = display_name_for_code(999); + assert_eq!(name, "Other(999)"); + } + + #[test] + fn wifi_supports_scanning() { + assert!(supports_scanning(2)); + assert!(!supports_scanning(1)); + } + + #[test] + fn wifi_requires_specific_object() { + assert!(requires_specific_object(2)); + assert!(!requires_specific_object(1)); + } + + #[test] + fn wifi_has_global_enabled_state() { + assert!(has_global_enabled_state(2)); + assert!(!has_global_enabled_state(1)); + } + + #[test] + fn all_registered_types_have_connection_type() { + for code in [1u32, 2, 11, 12, 13, 16, 29, 30, 32] { + let conn_type = connection_type_for_code(code); + assert!( + conn_type.is_some(), + "Device type {} should have a connection type", + code + ); + } + } + + #[test] + fn registry_is_consistent() { + let reg = registry(); + for (code, type_info) in reg.iter() { + assert_eq!( + *code, + type_info.nm_type_code(), + "Registry key must match type code" + ); + } + } +} diff --git a/nmrs/src/types/mod.rs b/nmrs/src/types/mod.rs index 08731cf7..4825b774 100644 --- a/nmrs/src/types/mod.rs +++ b/nmrs/src/types/mod.rs @@ -3,3 +3,4 @@ //! This module contains NetworkManager constants and type definitions. pub(crate) mod constants; +pub(crate) mod device_type_registry; From 93e9214edba6ea24f2edbddf2e3397f5aa19b6ba Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Wed, 14 Jan 2026 11:48:48 -0500 Subject: [PATCH 2/5] refactor: add capability query methods to DeviceType Extends DeviceType with methods that consult the device type registry for `Other(_)` variants. This allows users to query capabilities of device types not explicitly enumerated in the DeviceType enum. - Add `supports_scanning()`` method - Add `requires_specific_object()` method - Add `has_global_enabled_state()`` method - Add `connection_type_str()` method - Add `to_code()` method - Enhance Display to show registry names for `Other(_)` variants - Add comprehensive test coverage for new methods --- nmrs/src/api/models.rs | 197 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 190 insertions(+), 7 deletions(-) diff --git a/nmrs/src/api/models.rs b/nmrs/src/api/models.rs index af096f41..b2d76135 100644 --- a/nmrs/src/api/models.rs +++ b/nmrs/src/api/models.rs @@ -534,12 +534,8 @@ pub struct EapOptions { /// ```rust /// use nmrs::ConnectionOptions; /// -/// // Basic auto-connect -/// let opts = ConnectionOptions { -/// autoconnect: true, -/// autoconnect_priority: None, -/// autoconnect_retries: None, -/// }; +/// // Basic auto-connect (using defaults) +/// let opts = ConnectionOptions::default(); /// /// // High-priority connection with retry limit /// let opts_priority = ConnectionOptions { @@ -565,6 +561,22 @@ pub struct ConnectionOptions { pub autoconnect_retries: Option, } +impl Default for ConnectionOptions { + /// Returns the default connection options. + /// + /// Defaults: + /// - `autoconnect`: `true` + /// - `autoconnect_priority`: `None` (uses NetworkManager's default of 0) + /// - `autoconnect_retries`: `None` (unlimited retries) + fn default() -> Self { + Self { + autoconnect: true, + autoconnect_priority: None, + autoconnect_retries: None, + } + } +} + /// Wi-Fi connection security types. /// /// Represents the authentication method for connecting to a WiFi network. @@ -835,6 +847,8 @@ pub struct VpnConnectionInfo { /// NetworkManager device types. /// /// Represents the type of network hardware managed by NetworkManager. +/// This enum uses a registry-based system to support adding new device +/// types without breaking the API. #[derive(Debug, Clone, PartialEq)] pub enum DeviceType { /// Wired Ethernet device. @@ -846,9 +860,81 @@ pub enum DeviceType { /// Loopback device (localhost). Loopback, /// Unknown or unsupported device type with raw code. + /// + /// Use the methods on `DeviceType` to query capabilities of unknown device types, + /// which will consult the internal device type registry. Other(u32), } +impl DeviceType { + /// Returns whether this device type supports network scanning. + /// + /// Currently only WiFi and WiFi P2P devices support scanning. + /// For unknown device types, consults the internal device type registry. + pub fn supports_scanning(&self) -> bool { + match self { + Self::Wifi | Self::WifiP2P => true, + Self::Other(code) => crate::types::device_type_registry::supports_scanning(*code), + _ => false, + } + } + + /// Returns whether this device type requires a specific object (like an access point). + /// + /// WiFi devices require an access point to connect to, while Ethernet can connect + /// without a specific target. + /// For unknown device types, consults the internal device type registry. + pub fn requires_specific_object(&self) -> bool { + match self { + Self::Wifi | Self::WifiP2P => true, + Self::Other(code) => { + crate::types::device_type_registry::requires_specific_object(*code) + } + _ => false, + } + } + + /// Returns whether this device type has a global enabled/disabled state. + /// + /// WiFi has a global radio killswitch that can enable/disable all WiFi devices. + /// For unknown device types, consults the internal device type registry. + pub fn has_global_enabled_state(&self) -> bool { + match self { + Self::Wifi => true, + Self::Other(code) => { + crate::types::device_type_registry::has_global_enabled_state(*code) + } + _ => false, + } + } + + /// Returns the NetworkManager connection type string for this device. + /// + /// This is used when creating connection profiles for this device type. + /// For unknown device types, consults the internal device type registry. + pub fn connection_type_str(&self) -> &'static str { + match self { + Self::Ethernet => "802-3-ethernet", + Self::Wifi => "802-11-wireless", + Self::WifiP2P => "wifi-p2p", + Self::Loopback => "loopback", + Self::Other(code) => crate::types::device_type_registry::connection_type_for_code(*code) + .unwrap_or("generic"), + } + } + + /// Returns the raw NetworkManager type code for this device. + pub fn to_code(&self) -> u32 { + match self { + Self::Ethernet => 1, + Self::Wifi => 2, + Self::WifiP2P => 30, + Self::Loopback => 32, + Self::Other(code) => *code, + } + } +} + /// NetworkManager device states. /// /// Represents the current operational state of a network device. @@ -1218,7 +1304,9 @@ impl Display for DeviceType { DeviceType::Wifi => write!(f, "Wi-Fi"), DeviceType::WifiP2P => write!(f, "Wi-Fi P2P"), DeviceType::Loopback => write!(f, "Loopback"), - DeviceType::Other(v) => write!(f, "Other({v})"), + DeviceType::Other(v) => { + write!(f, "{}", crate::types::device_type_registry::display_name_for_code(*v)) + } } } } @@ -1289,6 +1377,15 @@ mod tests { assert_eq!(DeviceType::from(0), DeviceType::Other(0)); } + #[test] + fn device_type_from_u32_registry_types() { + assert_eq!(DeviceType::from(11), DeviceType::Other(11)); + assert_eq!(DeviceType::from(12), DeviceType::Other(12)); + assert_eq!(DeviceType::from(13), DeviceType::Other(13)); + assert_eq!(DeviceType::from(16), DeviceType::Other(16)); + assert_eq!(DeviceType::from(29), DeviceType::Other(29)); + } + #[test] fn device_type_display() { assert_eq!(format!("{}", DeviceType::Ethernet), "Ethernet"); @@ -1298,6 +1395,92 @@ mod tests { assert_eq!(format!("{}", DeviceType::Other(42)), "Other(42)"); } + #[test] + fn device_type_display_registry() { + assert_eq!(format!("{}", DeviceType::Other(13)), "Bridge"); + assert_eq!(format!("{}", DeviceType::Other(12)), "Bond"); + assert_eq!(format!("{}", DeviceType::Other(11)), "VLAN"); + assert_eq!(format!("{}", DeviceType::Other(16)), "TUN"); + assert_eq!(format!("{}", DeviceType::Other(29)), "WireGuard"); + } + + #[test] + fn device_type_supports_scanning() { + assert!(DeviceType::Wifi.supports_scanning()); + assert!(DeviceType::WifiP2P.supports_scanning()); + assert!(!DeviceType::Ethernet.supports_scanning()); + assert!(!DeviceType::Loopback.supports_scanning()); + } + + #[test] + fn device_type_supports_scanning_registry() { + assert!(DeviceType::Other(30).supports_scanning()); + assert!(!DeviceType::Other(13).supports_scanning()); + assert!(!DeviceType::Other(29).supports_scanning()); + } + + #[test] + fn device_type_requires_specific_object() { + assert!(DeviceType::Wifi.requires_specific_object()); + assert!(DeviceType::WifiP2P.requires_specific_object()); + assert!(!DeviceType::Ethernet.requires_specific_object()); + assert!(!DeviceType::Loopback.requires_specific_object()); + } + + #[test] + fn device_type_requires_specific_object_registry() { + assert!(DeviceType::Other(2).requires_specific_object()); + assert!(!DeviceType::Other(1).requires_specific_object()); + assert!(!DeviceType::Other(29).requires_specific_object()); + } + + #[test] + fn device_type_has_global_enabled_state() { + assert!(DeviceType::Wifi.has_global_enabled_state()); + assert!(!DeviceType::Ethernet.has_global_enabled_state()); + assert!(!DeviceType::WifiP2P.has_global_enabled_state()); + } + + #[test] + fn device_type_has_global_enabled_state_registry() { + assert!(DeviceType::Other(2).has_global_enabled_state()); + assert!(!DeviceType::Other(1).has_global_enabled_state()); + } + + #[test] + fn device_type_connection_type_str() { + assert_eq!(DeviceType::Ethernet.connection_type_str(), "802-3-ethernet"); + assert_eq!(DeviceType::Wifi.connection_type_str(), "802-11-wireless"); + assert_eq!(DeviceType::WifiP2P.connection_type_str(), "wifi-p2p"); + assert_eq!(DeviceType::Loopback.connection_type_str(), "loopback"); + } + + #[test] + fn device_type_connection_type_str_registry() { + assert_eq!(DeviceType::Other(13).connection_type_str(), "bridge"); + assert_eq!(DeviceType::Other(12).connection_type_str(), "bond"); + assert_eq!(DeviceType::Other(11).connection_type_str(), "vlan"); + assert_eq!(DeviceType::Other(29).connection_type_str(), "wireguard"); + } + + #[test] + fn device_type_to_code() { + assert_eq!(DeviceType::Ethernet.to_code(), 1); + assert_eq!(DeviceType::Wifi.to_code(), 2); + assert_eq!(DeviceType::WifiP2P.to_code(), 30); + assert_eq!(DeviceType::Loopback.to_code(), 32); + assert_eq!(DeviceType::Other(999).to_code(), 999); + } + + #[test] + fn device_type_to_code_registry() { + assert_eq!(DeviceType::Other(11).to_code(), 11); + assert_eq!(DeviceType::Other(12).to_code(), 12); + assert_eq!(DeviceType::Other(13).to_code(), 13); + assert_eq!(DeviceType::Other(16).to_code(), 16); + assert_eq!(DeviceType::Other(29).to_code(), 29); + } + #[test] fn device_state_from_u32_all_variants() { assert_eq!(DeviceState::from(10), DeviceState::Unmanaged); From 70acc80fd33fb4407748961840055dac9f453680 Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Wed, 14 Jan 2026 11:48:54 -0500 Subject: [PATCH 3/5] refactor: add settings proxy helper functions Extracts repeated Settings proxy construction patterns into reusable helpers, reducing code duplication and improving maintainability. - Add `settings_proxy()` helper for Settings interface - Add `connection_settings_proxy()` helper for connection settings - Apply helpers in `connection_settings.rs` and `vpn.rs` --- nmrs/src/core/connection_settings.rs | 23 ++++--------------- nmrs/src/core/vpn.rs | 13 +++-------- nmrs/src/util/utils.rs | 33 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/nmrs/src/core/connection_settings.rs b/nmrs/src/core/connection_settings.rs index f4c1bbfa..f711307d 100644 --- a/nmrs/src/core/connection_settings.rs +++ b/nmrs/src/core/connection_settings.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use zbus::Connection; use zvariant::{OwnedObjectPath, Value}; -use crate::util::utils::nm_proxy; +use crate::util::utils::{connection_settings_proxy, settings_proxy}; use crate::Result; /// Finds the D-Bus path of a saved connection by SSID. @@ -23,23 +23,13 @@ pub(crate) async fn get_saved_connection_path( conn: &Connection, ssid: &str, ) -> Result> { - let settings = nm_proxy( - conn, - "/org/freedesktop/NetworkManager/Settings", - "org.freedesktop.NetworkManager.Settings", - ) - .await?; + let settings = settings_proxy(conn).await?; let reply = settings.call_method("ListConnections", &()).await?; let conns: Vec = reply.body().deserialize()?; for cpath in conns { - let cproxy = nm_proxy( - conn, - cpath.as_str(), - "org.freedesktop.NetworkManager.Settings.Connection", - ) - .await?; + let cproxy = connection_settings_proxy(conn, cpath.clone()).await?; let msg = cproxy.call_method("GetSettings", &()).await?; let body = msg.body(); @@ -69,12 +59,7 @@ pub(crate) async fn has_saved_connection(conn: &Connection, ssid: &str) -> Resul /// Calls the Delete method on the connection settings object. /// This permanently removes the saved connection from NetworkManager. pub(crate) async fn delete_connection(conn: &Connection, conn_path: OwnedObjectPath) -> Result<()> { - let cproxy = nm_proxy( - conn, - conn_path.clone(), - "org.freedesktop.NetworkManager.Settings.Connection", - ) - .await?; + let cproxy = connection_settings_proxy(conn, conn_path.clone()).await?; cproxy.call_method("Delete", &()).await?; debug!("Deleted connection: {}", conn_path.as_str()); diff --git a/nmrs/src/core/vpn.rs b/nmrs/src/core/vpn.rs index 03cf025c..d0cd7db8 100644 --- a/nmrs/src/core/vpn.rs +++ b/nmrs/src/core/vpn.rs @@ -21,7 +21,7 @@ use crate::api::models::{ use crate::builders::build_wireguard_connection; use crate::core::state_wait::wait_for_connection_activation; use crate::dbus::{NMActiveConnectionProxy, NMProxy}; -use crate::util::utils::{extract_connection_state_reason, nm_proxy}; +use crate::util::utils::{extract_connection_state_reason, nm_proxy, settings_proxy}; use crate::Result; /// Connects to a WireGuard connection. @@ -61,14 +61,7 @@ pub(crate) async fn connect_vpn(conn: &Connection, creds: VpnCredentials) -> Res let settings = build_wireguard_connection(&creds, &opts)?; - // Use Settings API to add connection first, then activate separately - // This avoids NetworkManager's device validation when using add_and_activate_connection - let settings_api = nm_proxy( - conn, - "/org/freedesktop/NetworkManager/Settings", - "org.freedesktop.NetworkManager.Settings", - ) - .await?; + let settings_api = settings_proxy(conn).await?; debug!("Adding connection via Settings API"); let add_reply = settings_api @@ -718,7 +711,7 @@ pub(crate) async fn get_vpn_info(conn: &Connection, name: &str) -> Result None, })?; let prefix = addr_map.get("prefix").and_then(|v| match v { - zvariant::Value::U32(p) => Some(*p), + zvariant::Value::U32(p) => Some(p), _ => None, })?; Some(format!("{}/{}", address, prefix)) diff --git a/nmrs/src/util/utils.rs b/nmrs/src/util/utils.rs index 3dc09318..1c90abf9 100644 --- a/nmrs/src/util/utils.rs +++ b/nmrs/src/util/utils.rs @@ -168,6 +168,39 @@ where .await?) } +/// Helper to create a Settings proxy. +/// +/// Creates a proxy for the NetworkManager Settings interface at the standard path. +/// This is used to list, add, and manage saved connection profiles. +pub(crate) async fn settings_proxy(conn: &Connection) -> Result> { + nm_proxy( + conn, + "/org/freedesktop/NetworkManager/Settings", + "org.freedesktop.NetworkManager.Settings", + ) + .await +} + +/// Helper to create a Settings.Connection proxy for a specific connection. +/// +/// Creates a proxy for a specific saved connection object. +/// This is used to get/update connection settings or delete the connection. +pub(crate) async fn connection_settings_proxy<'a, P>( + conn: &'a Connection, + connection_path: P, +) -> Result> +where + P: TryInto, + P::Error: Into, +{ + nm_proxy( + conn, + connection_path, + "org.freedesktop.NetworkManager.Settings.Connection", + ) + .await +} + /// Attempts to extract the actual state reason from an active connection. /// /// NetworkManager only provides reason codes via StateChanged signals, not as From d88b60ba273e1badd7feec15e17124aa2b07975c Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Wed, 14 Jan 2026 11:49:26 -0500 Subject: [PATCH 4/5] chore: update `Cargo.lock` --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 061a77a6..1d1b2f27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -938,7 +938,7 @@ dependencies = [ [[package]] name = "nmrs" -version = "1.3.0" +version = "1.3.5" dependencies = [ "futures", "futures-timer", From 52ec0e872c6eb63ac7c38524e9dffd3a57527db8 Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Wed, 14 Jan 2026 11:51:59 -0500 Subject: [PATCH 5/5] chore: formatting and nix sha update --- nmrs/src/api/models.rs | 12 +++++++++--- package.nix | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/nmrs/src/api/models.rs b/nmrs/src/api/models.rs index b2d76135..9cbe4976 100644 --- a/nmrs/src/api/models.rs +++ b/nmrs/src/api/models.rs @@ -918,8 +918,10 @@ impl DeviceType { Self::Wifi => "802-11-wireless", Self::WifiP2P => "wifi-p2p", Self::Loopback => "loopback", - Self::Other(code) => crate::types::device_type_registry::connection_type_for_code(*code) - .unwrap_or("generic"), + Self::Other(code) => { + crate::types::device_type_registry::connection_type_for_code(*code) + .unwrap_or("generic") + } } } @@ -1305,7 +1307,11 @@ impl Display for DeviceType { DeviceType::WifiP2P => write!(f, "Wi-Fi P2P"), DeviceType::Loopback => write!(f, "Loopback"), DeviceType::Other(v) => { - write!(f, "{}", crate::types::device_type_registry::display_name_for_code(*v)) + write!( + f, + "{}", + crate::types::device_type_registry::display_name_for_code(*v) + ) } } } diff --git a/package.nix b/package.nix index eba1c273..032d0c2f 100644 --- a/package.nix +++ b/package.nix @@ -19,7 +19,7 @@ rustPlatform.buildRustPackage { src = ./.; - cargoHash = "sha256-2+EaId/l0WIf93e1gcBu7nhF2yxxMYg8bFSAIYLlo8Q="; + cargoHash = "sha256-ruRkHWJotIJyiSAGINqNeSZ5DI/bHi+FPsEhVJOqP00="; nativeBuildInputs = [ pkg-config