Skip to main content

tauri_plugin_network_manager/
desktop.rs

1use std::collections::HashMap;
2use std::sync::mpsc;
3use tauri::{plugin::PluginApi, AppHandle, Runtime};
4use uuid::Uuid;
5use zbus::names::InterfaceName;
6use zbus::zvariant::Value;
7
8use crate::error::Result;
9use crate::models::*;
10use crate::nm_helpers::NetworkManagerHelpers;
11
12impl<R: Runtime> VSKNetworkManager<'static, R> {
13    fn vpn_type_from_service_type(service_type: &str) -> VpnType {
14        match service_type {
15            "org.freedesktop.NetworkManager.openvpn" => VpnType::OpenVpn,
16            "org.freedesktop.NetworkManager.wireguard" => VpnType::WireGuard,
17            "org.freedesktop.NetworkManager.l2tp" => VpnType::L2tp,
18            "org.freedesktop.NetworkManager.pptp" => VpnType::Pptp,
19            "org.freedesktop.NetworkManager.sstp" => VpnType::Sstp,
20            "org.freedesktop.NetworkManager.strongswan" => VpnType::Ikev2,
21            "org.freedesktop.NetworkManager.fortisslvpn" => VpnType::Fortisslvpn,
22            "org.freedesktop.NetworkManager.openconnect" => VpnType::OpenConnect,
23            _ => VpnType::Generic,
24        }
25    }
26
27    fn service_type_from_vpn_type(vpn_type: &VpnType) -> &'static str {
28        match vpn_type {
29            VpnType::OpenVpn => "org.freedesktop.NetworkManager.openvpn",
30            VpnType::WireGuard => "org.freedesktop.NetworkManager.wireguard",
31            VpnType::L2tp => "org.freedesktop.NetworkManager.l2tp",
32            VpnType::Pptp => "org.freedesktop.NetworkManager.pptp",
33            VpnType::Sstp => "org.freedesktop.NetworkManager.sstp",
34            VpnType::Ikev2 => "org.freedesktop.NetworkManager.strongswan",
35            VpnType::Fortisslvpn => "org.freedesktop.NetworkManager.fortisslvpn",
36            VpnType::OpenConnect => "org.freedesktop.NetworkManager.openconnect",
37            VpnType::Generic => "org.freedesktop.NetworkManager.vpnc",
38        }
39    }
40
41    fn vpn_state_from_active_state(state: u32) -> VpnConnectionState {
42        match state {
43            1 => VpnConnectionState::Connecting,
44            2 => VpnConnectionState::Connected,
45            3 => VpnConnectionState::Disconnecting,
46            4 => VpnConnectionState::Disconnected,
47            _ => VpnConnectionState::Unknown,
48        }
49    }
50
51    fn extract_string_from_dict(
52        dict: &HashMap<String, zbus::zvariant::OwnedValue>,
53        key: &str,
54    ) -> Option<String> {
55        let value = dict.get(key)?.to_owned();
56        <zbus::zvariant::Value<'_> as Clone>::clone(&value)
57            .downcast::<String>()
58    }
59
60    fn extract_bool_from_dict(
61        dict: &HashMap<String, zbus::zvariant::OwnedValue>,
62        key: &str,
63    ) -> Option<bool> {
64        let value = dict.get(key)?.to_owned();
65        <zbus::zvariant::Value<'_> as Clone>::clone(&value)
66            .downcast::<bool>()
67    }
68
69    fn string_map_from_section(
70        settings: &HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
71        section_name: &str,
72    ) -> HashMap<String, String> {
73        let mut out = HashMap::new();
74        let section = match settings.get(section_name) {
75            Some(v) => v,
76            None => return out,
77        };
78
79        for (k, v) in section {
80            let value = <zbus::zvariant::Value<'_> as Clone>::clone(v);
81            if let Some(s) = value.downcast::<String>() {
82                out.insert(k.clone(), s);
83            }
84        }
85        out
86    }
87
88    fn list_connection_paths(&self) -> Result<Vec<zbus::zvariant::OwnedObjectPath>> {
89        let settings_proxy = zbus::blocking::Proxy::new(
90            &self.connection,
91            "org.freedesktop.NetworkManager",
92            "/org/freedesktop/NetworkManager/Settings",
93            "org.freedesktop.NetworkManager.Settings",
94        )?;
95
96        let connections: Vec<zbus::zvariant::OwnedObjectPath> =
97            settings_proxy.call("ListConnections", &())?;
98        Ok(connections)
99    }
100
101    fn get_connection_settings(
102        &self,
103        conn_path: &zbus::zvariant::OwnedObjectPath,
104    ) -> Result<HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>> {
105        let conn_proxy = zbus::blocking::Proxy::new(
106            &self.connection,
107            "org.freedesktop.NetworkManager",
108            conn_path.as_str(),
109            "org.freedesktop.NetworkManager.Settings.Connection",
110        )?;
111
112        let settings: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>> =
113            conn_proxy.call("GetSettings", &())?;
114        Ok(settings)
115    }
116
117    fn find_connection_path_by_uuid(
118        &self,
119        uuid: &str,
120    ) -> Result<zbus::zvariant::OwnedObjectPath> {
121        let connections = self.list_connection_paths()?;
122
123        for conn_path in connections {
124            let settings = self.get_connection_settings(&conn_path)?;
125            let dict = match settings.get("connection") {
126                Some(v) => v,
127                None => continue,
128            };
129
130            if let Some(conn_uuid) = Self::extract_string_from_dict(&dict, "uuid") {
131                if conn_uuid == uuid {
132                    return Ok(conn_path);
133                }
134            }
135        }
136
137        Err(crate::error::NetworkError::VpnProfileNotFound(uuid.to_string()))
138    }
139
140    fn vpn_profile_from_settings(
141        &self,
142        settings: &HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
143    ) -> Option<VpnProfile> {
144        let connection_dict = settings.get("connection")?;
145
146        let conn_type = Self::extract_string_from_dict(connection_dict, "type")?;
147        if conn_type != "vpn" {
148            return None;
149        }
150
151        let uuid = Self::extract_string_from_dict(connection_dict, "uuid")?;
152        let id = Self::extract_string_from_dict(connection_dict, "id")
153            .unwrap_or_else(|| uuid.clone());
154        let interface_name = Self::extract_string_from_dict(connection_dict, "interface-name");
155        let autoconnect = Self::extract_bool_from_dict(connection_dict, "autoconnect")
156            .unwrap_or(false);
157
158        let vpn_settings = Self::string_map_from_section(settings, "vpn");
159        let service_type = vpn_settings
160            .get("service-type")
161            .map(|s| s.as_str())
162            .unwrap_or("unknown");
163
164        Some(VpnProfile {
165            id,
166            uuid,
167            vpn_type: Self::vpn_type_from_service_type(service_type),
168            interface_name,
169            autoconnect,
170            editable: true,
171            last_error: None,
172        })
173    }
174
175    /// Get WiFi icon based on signal strength
176    fn get_wifi_icon(strength: u8) -> String {
177        match strength {
178            0..=25 => "network-wireless-signal-weak-symbolic".to_string(),
179            26..=50 => "network-wireless-signal-ok-symbolic".to_string(),
180            51..=75 => "network-wireless-signal-good-symbolic".to_string(),
181            76..=100 => "network-wireless-signal-excellent-symbolic".to_string(),
182            _ => "network-wireless-signal-none-symbolic".to_string(),
183        }
184    }
185
186    fn get_wired_icon(is_connected: bool) -> String {
187        if is_connected {
188            "network-wired-symbolic".to_string()
189        } else {
190            "network-offline-symbolic".to_string()
191        }
192    }
193
194    /// Create a new VSKNetworkManager instance
195    pub async fn new(app: AppHandle<R>) -> Result<Self> {
196        let connection = zbus::blocking::Connection::system()?;
197        let proxy = zbus::blocking::fdo::PropertiesProxy::builder(&connection)
198            .destination("org.freedesktop.NetworkManager")?
199            .path("/org/freedesktop/NetworkManager")?
200            .build()?;
201
202        Ok(Self {
203            connection,
204            proxy,
205            app,
206        })
207    }
208
209    pub fn get_current_network_state(&self) -> Result<NetworkInfo> {
210        // Get active connections
211        let active_connections_variant = self.proxy.get(
212            InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
213            "ActiveConnections",
214        )?;
215
216        // If no active connections, return default
217        match active_connections_variant.downcast_ref() {
218            Some(Value::Array(arr)) if !arr.is_empty() => {
219                // Get the first active connection path
220                match arr[0] {
221                    zbus::zvariant::Value::ObjectPath(ref path) => {
222                        // Get devices for this connection
223                        // Crear un proxy de propiedades para obtener las propiedades
224                        let properties_proxy =
225                            zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
226                                .destination("org.freedesktop.NetworkManager")?
227                                .path(path)?
228                                .build()?;
229
230                        let devices_variant = properties_proxy.get(
231                            InterfaceName::from_static_str_unchecked(
232                                "org.freedesktop.NetworkManager.Connection.Active",
233                            ),
234                            "Devices",
235                        )?;
236
237                        // Get the first device (if available)
238                        let device_path = match devices_variant.downcast_ref() {
239                            Some(Value::Array(device_arr)) if !device_arr.is_empty() => {
240                                match device_arr[0] {
241                                    zbus::zvariant::Value::ObjectPath(ref dev_path) => {
242                                        dev_path.clone()
243                                    }
244                                    _ => return Ok(NetworkInfo::default()),
245                                }
246                            }
247                            _ => return Ok(NetworkInfo::default()),
248                        };
249
250                        // Retrieve connection details
251                        // Crear un proxy de propiedades para el dispositivo
252                        let device_properties_proxy =
253                            zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
254                                .destination("org.freedesktop.NetworkManager")?
255                                .path(&device_path)?
256                                .build()?;
257
258                        let connection_type = device_properties_proxy.get(
259                            InterfaceName::from_static_str_unchecked(
260                                "org.freedesktop.NetworkManager.Device",
261                            ),
262                            "DeviceType",
263                        )?;
264
265                        let state_variant = properties_proxy.get(
266                            InterfaceName::from_static_str_unchecked(
267                                "org.freedesktop.NetworkManager.Connection.Active",
268                            ),
269                            "State",
270                        )?;
271
272                        let is_connected = match state_variant.downcast_ref() {
273                            Some(zbus::zvariant::Value::U32(state)) => *state == 2, // 2 = ACTIVATED
274                            _ => false,
275                        };
276
277                        // Determine connection type
278                        let connection_type_str = match connection_type.downcast_ref() {
279                            Some(zbus::zvariant::Value::U32(device_type)) => match device_type {
280                                1 => "Ethernet".to_string(),
281                                2 => "WiFi".to_string(),
282                                _ => "Unknown".to_string(),
283                            },
284                            _ => "Unknown".to_string(),
285                        };
286
287                        // Default network info
288                        let mut network_info = NetworkInfo {
289                            name: "Unknown".to_string(),
290                            ssid: "Unknown".to_string(),
291                            connection_type: connection_type_str.clone(),
292                            icon: "network-offline-symbolic".to_string(),
293                            ip_address: "0.0.0.0".to_string(),
294                            mac_address: "00:00:00:00:00:00".to_string(),
295                            signal_strength: 0,
296                            security_type: WiFiSecurityType::None,
297                            is_connected: is_connected && NetworkManagerHelpers::has_internet_connectivity(&self.proxy)?,
298                        };
299
300                        let hw_address_variant = device_properties_proxy.get(
301                            InterfaceName::from_static_str_unchecked(
302                                "org.freedesktop.NetworkManager.Device",
303                            ),
304                            "HwAddress",
305                        )?;
306
307                        network_info.mac_address = match hw_address_variant.downcast_ref() {
308                            Some(zbus::zvariant::Value::Str(s)) => s.to_string(),
309                            _ => "00:00:00:00:00:00".to_string(),
310                        };
311
312                        // For WiFi networks, get additional details
313                        if connection_type_str == "WiFi" {
314                            // Get active access point
315                            // Crear un proxy de propiedades para el dispositivo inalámbrico
316                            let wireless_properties_proxy =
317                                zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
318                                    .destination("org.freedesktop.NetworkManager")?
319                                    .path(&device_path)?
320                                    .build()?;
321
322                            let active_ap_path = wireless_properties_proxy.get(
323                                InterfaceName::from_static_str_unchecked(
324                                    "org.freedesktop.NetworkManager.Device.Wireless",
325                                ),
326                                "ActiveAccessPoint",
327                            )?;
328
329                            if let Some(zbus::zvariant::Value::ObjectPath(ap_path)) =
330                                active_ap_path.downcast_ref()
331                            {
332                                let _ap_proxy = zbus::blocking::Proxy::new(
333                                    &self.connection,
334                                    "org.freedesktop.NetworkManager",
335                                    ap_path,
336                                    "org.freedesktop.NetworkManager.AccessPoint",
337                                )?;
338
339                                // Get SSID
340                                // Crear un proxy de propiedades para el punto de acceso
341                                let ap_properties_proxy =
342                                    zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
343                                        .destination("org.freedesktop.NetworkManager")?
344                                        .path(ap_path)?
345                                        .build()?;
346
347                                let ssid_variant = ap_properties_proxy.get(
348                                    InterfaceName::from_static_str_unchecked(
349                                        "org.freedesktop.NetworkManager.AccessPoint",
350                                    ),
351                                    "Ssid",
352                                )?;
353
354                                network_info.ssid = match ssid_variant.downcast_ref() {
355                                    Some(zbus::zvariant::Value::Array(ssid_bytes)) => {
356                                        // Convertir el array de bytes a una cadena UTF-8
357                                        let bytes: Vec<u8> = ssid_bytes
358                                            .iter()
359                                            .filter_map(|v| {
360                                                if let zbus::zvariant::Value::U8(b) = v {
361                                                    Some(*b)
362                                                } else {
363                                                    None
364                                                }
365                                            })
366                                            .collect();
367
368                                        String::from_utf8_lossy(&bytes).to_string()
369                                    }
370                                    _ => "Unknown".to_string(),
371                                };
372                                network_info.name = network_info.ssid.clone();
373
374                                // Get signal strength
375                                let strength_variant = ap_properties_proxy.get(
376                                    InterfaceName::from_static_str_unchecked(
377                                        "org.freedesktop.NetworkManager.AccessPoint",
378                                    ),
379                                    "Strength",
380                                )?;
381
382                                network_info.signal_strength = match strength_variant.downcast_ref()
383                                {
384                                    Some(zbus::zvariant::Value::U8(s)) => *s,
385                                    _ => 0,
386                                };
387
388                                // Update icon based on signal strength
389                                network_info.icon =
390                                    Self::get_wifi_icon(network_info.signal_strength);
391
392                                // Determine security type using helper
393                                network_info.security_type = NetworkManagerHelpers::detect_security_type(&ap_properties_proxy)?;
394                            }
395                        } else {
396                            // This is a wired connection
397                            network_info.icon = Self::get_wired_icon(network_info.is_connected);
398                        }
399                        // Get IP configuration
400                        let ip4_config_path = device_properties_proxy.get(
401                            InterfaceName::from_static_str_unchecked(
402                                "org.freedesktop.NetworkManager.Device",
403                            ),
404                            "Ip4Config",
405                        )?;
406
407                        // Retrieve IP address if available
408                        if let Some(zbus::zvariant::Value::ObjectPath(config_path)) =
409                            ip4_config_path.downcast_ref()
410                        {
411                            // Crear un proxy de propiedades para la configuración IP
412                            let ip_config_properties_proxy =
413                                zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
414                                    .destination("org.freedesktop.NetworkManager")?
415                                    .path(config_path)?
416                                    .build()?;
417
418                            let addresses_variant = ip_config_properties_proxy.get(
419                                InterfaceName::from_static_str_unchecked(
420                                    "org.freedesktop.NetworkManager.IP4Config",
421                                ),
422                                "Addresses",
423                            )?;
424
425                            if let Some(Value::Array(addr_arr)) = addresses_variant.downcast_ref() {
426                                if let Some(Value::Array(ip_tuple)) = addr_arr.first() {
427                                    if ip_tuple.len() >= 1 {
428                                        if let Value::U32(ip_int) = &ip_tuple[0] {
429                                            use std::net::Ipv4Addr;
430                                            network_info.ip_address =
431                                                Ipv4Addr::from((*ip_int).to_be()).to_string();
432                                        }
433                                    }
434                                }
435                            }
436                        }
437
438                        Ok(network_info)
439                    }
440                    _ => Ok(NetworkInfo::default()),
441                }
442            }
443            _ => Ok(NetworkInfo::default()),
444        }
445    }
446
447    /// List available WiFi networks
448    pub fn list_wifi_networks(&self) -> Result<Vec<NetworkInfo>> {
449        // Get all devices
450        let devices_variant = self.proxy.get(
451            InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
452            "Devices",
453        )?;
454
455        let mut networks = Vec::new();
456        let current_network = self.get_current_network_state()?;
457
458        if let Some(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
459            // Iterate over devices in the array
460            let device_values = devices.get();
461            for device in device_values {
462                if let zbus::zvariant::Value::ObjectPath(ref device_path) = device {
463                    // Create a device proxy
464                    let device_props =
465                        zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
466                            .destination("org.freedesktop.NetworkManager")?
467                            .path(device_path)?
468                            .build()?;
469
470                    // Check if this is a wireless device
471                    let device_type_variant = device_props.get(
472                        InterfaceName::from_static_str_unchecked(
473                            "org.freedesktop.NetworkManager.Device",
474                        ),
475                        "DeviceType",
476                    )?;
477
478                    // DeviceType 2 is WiFi
479                    if let Some(zbus::zvariant::Value::U32(device_type)) =
480                        device_type_variant.downcast_ref()
481                    {
482                        if device_type == &2u32 {
483                            let mac_address = match device_props
484                                .get(
485                                    InterfaceName::from_static_str_unchecked(
486                                        "org.freedesktop.NetworkManager.Device",
487                                    ),
488                                    "HwAddress",
489                                )?
490                                .downcast_ref()
491                            {
492                                Some(zbus::zvariant::Value::Str(s)) => s.to_string(),
493                                _ => "00:00:00:00:00:00".to_string(),
494                            };
495
496                            // This is a WiFi device, get its access points
497                            let wireless_props =
498                                zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
499                                    .destination("org.freedesktop.NetworkManager")?
500                                    .path(device_path)?
501                                    .build()?;
502
503                            let access_points_variant = wireless_props.get(
504                                InterfaceName::from_static_str_unchecked(
505                                    "org.freedesktop.NetworkManager.Device.Wireless",
506                                ),
507                                "AccessPoints",
508                            )?;
509
510                            if let Some(zbus::zvariant::Value::Array(aps)) =
511                                access_points_variant.downcast_ref()
512                            {
513                                // Iterate over access points
514                                let ap_values = aps.get();
515                                for ap in ap_values {
516                                    if let zbus::zvariant::Value::ObjectPath(ref ap_path) = ap {
517                                        let ap_props = zbus::blocking::fdo::PropertiesProxy::builder(
518                                            &self.connection,
519                                        )
520                                        .destination("org.freedesktop.NetworkManager")?
521                                        .path(ap_path)?
522                                        .build()?;
523
524                                        // Obtener SSID
525                                        let ssid_variant = ap_props.get(
526                                            InterfaceName::from_static_str_unchecked(
527                                                "org.freedesktop.NetworkManager.AccessPoint",
528                                            ),
529                                            "Ssid",
530                                        )?;
531
532                                        let ssid = match ssid_variant.downcast_ref() {
533                                            Some(zbus::zvariant::Value::Array(ssid_bytes)) => {
534                                                // Convertir el array de bytes a una cadena UTF-8
535                                                let bytes: Vec<u8> = ssid_bytes
536                                                    .iter()
537                                                    .filter_map(|v| {
538                                                        if let zbus::zvariant::Value::U8(b) = v {
539                                                            Some(*b)
540                                                        } else {
541                                                            None
542                                                        }
543                                                    })
544                                                    .collect();
545
546                                                String::from_utf8_lossy(&bytes).to_string()
547                                            }
548                                            _ => "Unknown".to_string(),
549                                        };
550
551                                        // Obtener fuerza de señal
552                                        let strength_variant = ap_props.get(
553                                            InterfaceName::from_static_str_unchecked(
554                                                "org.freedesktop.NetworkManager.AccessPoint",
555                                            ),
556                                            "Strength",
557                                        )?;
558
559                                        let strength = match strength_variant.downcast_ref() {
560                                            Some(zbus::zvariant::Value::U8(s)) => *s,
561                                            _ => 0,
562                                        };
563
564                                        // Determine security type using helper
565                                        let security_type = NetworkManagerHelpers::detect_security_type(&ap_props)?;
566
567                                        let is_connected = current_network.ssid == ssid;
568
569                                        let network_info = NetworkInfo {
570                                            name: ssid.clone(),
571                                            ssid,
572                                            connection_type: "wifi".to_string(),
573                                            icon: Self::get_wifi_icon(strength),
574                                            ip_address: if is_connected {
575                                                current_network.ip_address.clone()
576                                            } else {
577                                                "0.0.0.0".to_string()
578                                            },
579                                            mac_address: mac_address.clone(),
580                                            signal_strength: strength,
581                                            security_type,
582                                            is_connected,
583                                        };
584
585                                        if !networks.iter().any(|n: &NetworkInfo| n.ssid == network_info.ssid) {
586                                            networks.push(network_info);
587                                        }
588                                    }
589                                }
590                            }
591                        }
592                    }
593                }
594            }
595        }
596
597        // Sort networks by signal strength (descending)
598        networks.sort_by(|a, b| b.signal_strength.cmp(&a.signal_strength));
599
600        Ok(networks)
601    }
602
603    /// Request an explicit WiFi scan through NetworkManager and return a fresh list.
604    pub fn rescan_wifi(&self) -> Result<Vec<NetworkInfo>> {
605        let devices_variant = self.proxy.get(
606            InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
607            "Devices",
608        )?;
609
610        let mut wifi_device_found = false;
611        let mut requested_scan = false;
612
613        if let Some(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
614            for device in devices.get() {
615                if let zbus::zvariant::Value::ObjectPath(ref device_path) = device {
616                    let device_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
617                        .destination("org.freedesktop.NetworkManager")?
618                        .path(device_path)?
619                        .build()?;
620
621                    let device_type_variant = device_props.get(
622                        InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager.Device"),
623                        "DeviceType",
624                    )?;
625
626                    if let Some(zbus::zvariant::Value::U32(device_type)) = device_type_variant.downcast_ref() {
627                        if *device_type == 2 {
628                            wifi_device_found = true;
629
630                            let wireless_proxy = zbus::blocking::Proxy::new(
631                                &self.connection,
632                                "org.freedesktop.NetworkManager",
633                                device_path.as_str(),
634                                "org.freedesktop.NetworkManager.Device.Wireless",
635                            )?;
636
637                            let options: HashMap<String, zbus::zvariant::OwnedValue> = HashMap::new();
638                            if wireless_proxy.call::<_, _, ()>("RequestScan", &(options,)).is_ok() {
639                                requested_scan = true;
640                            }
641                        }
642                    }
643                }
644            }
645        }
646
647        if !wifi_device_found {
648            return Err(crate::error::NetworkError::OperationError(
649                "No wireless device available for scanning".to_string(),
650            ));
651        }
652
653        if !requested_scan {
654            return Err(crate::error::NetworkError::OperationError(
655                "Failed to request WiFi scan on available wireless devices".to_string(),
656            ));
657        }
658
659        self.list_wifi_networks()
660    }
661
662    /// Connect to a WiFi network
663    pub fn connect_to_wifi(&self, config: WiFiConnectionConfig) -> Result<()> {
664        // Log connection attempt
665        log::debug!("connect_to_wifi called: ssid='{}' security={:?} username={:?}",
666                  config.ssid, config.security_type, config.username);
667
668        // Create connection settings
669        let mut connection_settings = HashMap::new();
670        let mut wifi_settings = HashMap::new();
671        let mut security_settings = HashMap::new();
672
673        // Set connection name and type
674        let mut connection = HashMap::new();
675        connection.insert("id".to_string(), Value::from(config.ssid.clone()));
676        connection.insert("type".to_string(), Value::from("802-11-wireless"));
677        connection_settings.insert("connection".to_string(), connection);
678
679        // Set WiFi settings
680        wifi_settings.insert("ssid".to_string(), Value::from(config.ssid.clone()));
681        wifi_settings.insert("mode".to_string(), Value::from("infrastructure"));
682
683        // Set security settings based on security type
684        match config.security_type {
685            WiFiSecurityType::None => {
686                // No security settings needed
687            }
688            WiFiSecurityType::Wep => {
689                security_settings.insert("key-mgmt".to_string(), Value::from("none"));
690                if let Some(password) = config.password.clone() {
691                    security_settings.insert("wep-key0".to_string(), Value::from(password));
692                }
693            }
694            WiFiSecurityType::WpaPsk => {
695                security_settings.insert("key-mgmt".to_string(), Value::from("wpa-psk"));
696                if let Some(password) = config.password.clone() {
697                    security_settings.insert("psk".to_string(), Value::from(password));
698                }
699            }
700            WiFiSecurityType::WpaEap => {
701                security_settings.insert("key-mgmt".to_string(), Value::from("wpa-eap"));
702                if let Some(password) = config.password.clone() {
703                    security_settings.insert("password".to_string(), Value::from(password));
704                }
705                if let Some(username) = config.username.clone() {
706                    security_settings.insert("identity".to_string(), Value::from(username));
707                }
708            }
709            WiFiSecurityType::Wpa2Psk => {
710                security_settings.insert("key-mgmt".to_string(), Value::from("wpa-psk"));
711                security_settings.insert("proto".to_string(), Value::from("rsn"));
712                if let Some(password) = config.password.clone() {
713                    security_settings.insert("psk".to_string(), Value::from(password));
714                }
715            }
716            WiFiSecurityType::Wpa3Psk => {
717                security_settings.insert("key-mgmt".to_string(), Value::from("sae"));
718                if let Some(password) = config.password.clone() {
719                    security_settings.insert("psk".to_string(), Value::from(password));
720                }
721            }
722        }
723
724        connection_settings.insert("802-11-wireless".to_string(), wifi_settings);
725        connection_settings.insert("802-11-wireless-security".to_string(), security_settings);
726
727        // Log constructed settings for debugging
728        log::trace!("connection_settings: {:#?}", connection_settings);
729
730        // Crear un proxy para NetworkManager
731        let nm_proxy = zbus::blocking::Proxy::new(
732            &self.connection,
733            "org.freedesktop.NetworkManager",
734            "/org/freedesktop/NetworkManager",
735            "org.freedesktop.NetworkManager",
736        )?;
737
738        // Llamar al método AddAndActivateConnection (trace result)
739        let call_result: zbus::Result<(zbus::zvariant::OwnedObjectPath, zbus::zvariant::OwnedObjectPath)> = nm_proxy.call("AddAndActivateConnection", &(connection_settings, "/", "/"));
740
741        match call_result {
742            Ok((conn_path, active_path)) => {
743                log::info!(
744                    "AddAndActivateConnection succeeded for ssid='{}' conn='{}' active='{}'",
745                    config.ssid,
746                    conn_path.as_str(),
747                    active_path.as_str()
748                );
749            }
750            Err(e) => {
751                log::error!(
752                    "AddAndActivateConnection failed for ssid='{}': {:?}",
753                    config.ssid,
754                    e
755                );
756                return Err(e.into());
757            }
758        }
759
760        log::debug!("connect_to_wifi finished for ssid='{}'", config.ssid);
761
762        Ok(())
763    }
764
765    /// Toggle network state
766    pub fn toggle_network_state(&self, enabled: bool) -> Result<bool> {
767        let nm_proxy = zbus::blocking::Proxy::new(
768            &self.connection,
769            "org.freedesktop.NetworkManager",
770            "/org/freedesktop/NetworkManager",
771            "org.freedesktop.NetworkManager",
772        )?;
773
774        nm_proxy.set_property("NetworkingEnabled", enabled)?;
775
776        let current_state: bool = nm_proxy.get_property("NetworkingEnabled")?;
777        Ok(current_state)
778    }
779
780    /// Get wireless enabled state
781    pub fn get_wireless_enabled(&self) -> Result<bool> {
782        let nm_proxy = zbus::blocking::Proxy::new(
783            &self.connection,
784            "org.freedesktop.NetworkManager",
785            "/org/freedesktop/NetworkManager",
786            "org.freedesktop.NetworkManager",
787        )?;
788        Ok(nm_proxy.get_property("WirelessEnabled")?)
789    }
790
791    /// Set wireless enabled state
792    pub fn set_wireless_enabled(&self, enabled: bool) -> Result<()> {
793        let nm_proxy = zbus::blocking::Proxy::new(
794            &self.connection,
795            "org.freedesktop.NetworkManager",
796            "/org/freedesktop/NetworkManager",
797            "org.freedesktop.NetworkManager",
798        )?;
799        nm_proxy.set_property("WirelessEnabled", enabled)?;
800        Ok(())
801    }
802
803    /// Check if wireless device is available
804    pub fn is_wireless_available(&self) -> Result<bool> {
805         // Get all devices
806        let devices_variant = self.proxy.get(
807            InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
808            "Devices",
809        )?;
810
811        if let Some(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
812            let device_values = devices.get();
813            for device in device_values {
814                if let zbus::zvariant::Value::ObjectPath(ref device_path) = device {
815                     let device_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
816                            .destination("org.freedesktop.NetworkManager")?
817                            .path(device_path)?
818                            .build()?;
819                    
820                    let device_type_variant = device_props.get(
821                        InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager.Device"),
822                        "DeviceType",
823                    )?;
824                    
825                    if let Some(zbus::zvariant::Value::U32(device_type)) = device_type_variant.downcast_ref() {
826                        if device_type == &2u32 { // 2 = WiFi
827                            return Ok(true);
828                        }
829                    }
830                }
831            }
832        }
833        Ok(false)
834    }
835
836    /// Listen for network changes
837    pub fn listen_network_changes(&self) -> Result<mpsc::Receiver<NetworkInfo>> {
838        let (tx, rx) = mpsc::channel();
839        let connection_clone = self.connection.clone();
840        let app_handle = self.app.clone();
841
842        // Crear un hilo para escuchar los cambios de red
843        std::thread::spawn(move || {
844            match zbus::blocking::Connection::system() {
845                Ok(conn) => {
846                    // Proxy para el objeto raíz, interfaz DBus.Properties
847                    if let Ok(proxy) = zbus::blocking::Proxy::new(
848                        &conn,
849                        "org.freedesktop.NetworkManager",
850                        "/org/freedesktop/NetworkManager",
851                        "org.freedesktop.NetworkManager",
852                    ) {
853                        if let Ok(mut signal) = proxy.receive_signal("StateChanged") {
854                            while let Some(_msg) = signal.next() {
855                                let network_manager = VSKNetworkManager {
856                                    connection: connection_clone.clone(),
857                                    proxy: zbus::blocking::fdo::PropertiesProxy::builder(
858                                        &connection_clone,
859                                    )
860                                    .destination("org.freedesktop.NetworkManager")
861                                    .unwrap()
862                                    .path("/org/freedesktop/NetworkManager")
863                                    .unwrap()
864                                    .build()
865                                    .unwrap(),
866                                    app: app_handle.clone(),
867                                };
868
869                                if let Ok(network_info) =
870                                    network_manager.get_current_network_state()
871                                {
872                                    if tx.send(network_info).is_err() {
873                                        break;
874                                    }
875                                }
876                            }
877                        }
878                    }
879                }
880                Err(e) => {
881                    eprintln!(
882                        "Error al conectar con D-Bus para escuchar cambios de red: {:?}",
883                        e
884                    );
885                }
886            }
887        });
888
889        Ok(rx)
890    }
891
892    /// Disconnect from the current WiFi network
893    pub fn disconnect_from_wifi(&self) -> Result<()> {
894        // Obtener el estado actual de la red para identificar la conexión activa
895        let _current_state = self.get_current_network_state()?;
896
897        // Crear un proxy para NetworkManager
898        let nm_proxy = zbus::blocking::Proxy::new(
899            &self.connection,
900            "org.freedesktop.NetworkManager",
901            "/org/freedesktop/NetworkManager",
902            "org.freedesktop.NetworkManager",
903        )?;
904
905        // Obtener las conexiones activas
906        let active_connections_variant: zbus::zvariant::OwnedValue = self.proxy.get(
907            InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
908            "ActiveConnections",
909        )?;
910
911        // Convertir el valor a un vector de ObjectPath
912        let active_connections = match active_connections_variant.downcast_ref() {
913            Some(zbus::zvariant::Value::Array(arr)) => arr
914                .iter()
915                .filter_map(|v| match v {
916                    zbus::zvariant::Value::ObjectPath(path) => {
917                        Some(zbus::zvariant::OwnedObjectPath::from(path.to_owned()))
918                    }
919                    _ => None,
920                })
921                .collect::<Vec<zbus::zvariant::OwnedObjectPath>>(),
922            _ => Vec::new(),
923        };
924
925        if !active_connections.is_empty() {
926            nm_proxy.call::<_, _, ()>("DeactivateConnection", &(active_connections[0].as_str()))?;
927            Ok(())
928        } else {
929            Ok(())
930        }
931    }
932
933    /// Get the list of saved WiFi networks
934    pub fn get_saved_wifi_networks(&self) -> Result<Vec<NetworkInfo>> {
935        // Crear un proxy para el servicio de configuración de NetworkManager
936        let settings_proxy = zbus::blocking::Proxy::new(
937            &self.connection,
938            "org.freedesktop.NetworkManager",
939            "/org/freedesktop/NetworkManager/Settings",
940            "org.freedesktop.NetworkManager.Settings",
941        )?;
942
943        // Obtener todas las conexiones guardadas
944        let connections: Vec<zbus::zvariant::OwnedObjectPath> =
945            settings_proxy.call("ListConnections", &())?;
946        let mut saved_networks = Vec::new();
947
948        // Procesar cada conexión guardada
949        for conn_path in connections {
950            // Crear un proxy para cada conexión
951            let conn_proxy = zbus::blocking::Proxy::new(
952                &self.connection,
953                "org.freedesktop.NetworkManager",
954                conn_path.as_str(),
955                "org.freedesktop.NetworkManager.Settings.Connection",
956            )?;
957
958            // Obtener la configuración de la conexión como un HashMap
959            let settings: std::collections::HashMap<String, zbus::zvariant::OwnedValue> =
960                conn_proxy.call("GetSettings", &())?;
961
962            // Verificar si es una conexión WiFi
963            if let Some(connection) = settings.get("connection") {
964                let connection_value = connection.to_owned();
965                let connection_dict =
966                    match <zbus::zvariant::Value<'_> as Clone>::clone(&connection_value)
967                        .downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>(
968                    ) {
969                        Some(dict) => dict,
970                        _ => continue,
971                    };
972
973                // Verificar el tipo de conexión
974                if let Some(conn_type) = connection_dict.get("type") {
975                    let conn_type_value = conn_type.to_owned();
976                    let conn_type_str =
977                        match <zbus::zvariant::Value<'_> as Clone>::clone(&conn_type_value)
978                            .downcast::<String>()
979                        {
980                            Some(s) => s,
981                            _ => continue,
982                        };
983
984                    // Si es una conexión WiFi, extraer la información
985                    if conn_type_str == "802-11-wireless" {
986                        let mut network_info = NetworkInfo::default();
987                        network_info.connection_type = "wifi".to_string();
988
989                        // Obtener el nombre de la conexión
990                        if let Some(id) = connection_dict.get("id") {
991                            let id_value = id.to_owned();
992                            if let Some(name) =
993                                <zbus::zvariant::Value<'_> as Clone>::clone(&id_value)
994                                    .downcast::<String>()
995                            {
996                                network_info.name = name;
997                            }
998                        }
999
1000                        // Obtener el SSID
1001                        if let Some(wireless) = settings.get("802-11-wireless") {
1002                            let wireless_value = wireless.to_owned();
1003                            let wireless_dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&wireless_value).downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>() {
1004                                Some(dict) => dict,
1005                                _ => continue,
1006                            };
1007
1008                            if let Some(ssid) = wireless_dict.get("ssid") {
1009                                let ssid_value = ssid.to_owned();
1010                                if let Some(ssid_bytes) =
1011                                    <zbus::zvariant::Value<'_> as Clone>::clone(&ssid_value)
1012                                        .downcast::<Vec<u8>>()
1013                                {
1014                                    if let Ok(ssid_str) = String::from_utf8(ssid_bytes) {
1015                                        network_info.ssid = ssid_str;
1016                                    }
1017                                }
1018                            }
1019                        }
1020
1021                        // Determinar el tipo de seguridad
1022                        if let Some(security) = settings.get("802-11-wireless-security") {
1023                            let security_value = security.to_owned();
1024                            let security_dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&security_value).downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>() {
1025                                Some(dict) => dict,
1026                                _ => {
1027                                    network_info.security_type = WiFiSecurityType::None;
1028                                    saved_networks.push(network_info);
1029                                    continue;
1030                                },
1031                            };
1032
1033                            if let Some(key_mgmt) = security_dict.get("key-mgmt") {
1034                                let key_mgmt_value = key_mgmt.to_owned();
1035                                if let Some(key_mgmt_str) =
1036                                    <zbus::zvariant::Value<'_> as Clone>::clone(&key_mgmt_value)
1037                                        .downcast::<String>()
1038                                {
1039                                    match key_mgmt_str.as_str() {
1040                                        "none" => {
1041                                            network_info.security_type = WiFiSecurityType::None
1042                                        }
1043                                        "wpa-psk" => {
1044                                            network_info.security_type = WiFiSecurityType::WpaPsk
1045                                        }
1046                                        "wpa-eap" => {
1047                                            network_info.security_type = WiFiSecurityType::WpaEap
1048                                        }
1049                                        _ => network_info.security_type = WiFiSecurityType::None,
1050                                    }
1051                                }
1052                            }
1053                        } else {
1054                            network_info.security_type = WiFiSecurityType::None;
1055                        }
1056
1057                        // Agregar a la lista de redes guardadas
1058                        saved_networks.push(network_info);
1059                    }
1060                }
1061            }
1062        }
1063
1064        Ok(saved_networks)
1065    }
1066
1067    /// Delete a saved WiFi connection by SSID
1068    pub fn delete_wifi_connection(&self, ssid: &str) -> Result<bool> {
1069        // Crear un proxy para el servicio de configuración de NetworkManager
1070        let settings_proxy = zbus::blocking::Proxy::new(
1071            &self.connection,
1072            "org.freedesktop.NetworkManager",
1073            "/org/freedesktop/NetworkManager/Settings",
1074            "org.freedesktop.NetworkManager.Settings",
1075        )?;
1076
1077        // Obtener todas las conexiones guardadas
1078        let connections: Vec<zbus::zvariant::OwnedObjectPath> =
1079            settings_proxy.call("ListConnections", &())?;
1080
1081        // Procesar cada conexión guardada
1082        for conn_path in connections {
1083            // Crear un proxy para cada conexión
1084            let conn_proxy = zbus::blocking::Proxy::new(
1085                &self.connection,
1086                "org.freedesktop.NetworkManager",
1087                conn_path.as_str(),
1088                "org.freedesktop.NetworkManager.Settings.Connection",
1089            )?;
1090
1091            // Obtener la configuración de la conexión como un HashMap
1092            let settings: std::collections::HashMap<String, zbus::zvariant::OwnedValue> =
1093                conn_proxy.call("GetSettings", &())?;
1094
1095            // Verificar si es una conexión WiFi
1096            if let Some(connection) = settings.get("connection") {
1097                let connection_value = connection.to_owned();
1098                let connection_dict =
1099                    match <zbus::zvariant::Value<'_> as Clone>::clone(&connection_value)
1100                        .downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>(
1101                    ) {
1102                        Some(dict) => dict,
1103                        _ => continue,
1104                    };
1105
1106                // Verificar el tipo de conexión
1107                if let Some(conn_type) = connection_dict.get("type") {
1108                    let conn_type_value = conn_type.to_owned();
1109                    let conn_type_str =
1110                        match <zbus::zvariant::Value<'_> as Clone>::clone(&conn_type_value)
1111                            .downcast::<String>()
1112                        {
1113                            Some(s) => s,
1114                            _ => continue,
1115                        };
1116
1117                    // Si es una conexión WiFi, verificar el SSID
1118                    if conn_type_str == "802-11-wireless" {
1119                        if let Some(wireless) = settings.get("802-11-wireless") {
1120                            let wireless_value = wireless.to_owned();
1121                            let wireless_dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&wireless_value).downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>() {
1122                                Some(dict) => dict,
1123                                _ => continue,
1124                            };
1125
1126                            if let Some(ssid_value) = wireless_dict.get("ssid") {
1127                                let ssid_owned = ssid_value.to_owned();
1128                                if let Some(ssid_bytes) =
1129                                    <zbus::zvariant::Value<'_> as Clone>::clone(&ssid_owned)
1130                                        .downcast::<Vec<u8>>()
1131                                {
1132                                    if let Ok(conn_ssid_str) = String::from_utf8(ssid_bytes) {
1133                                        // Si el SSID coincide, eliminar la conexión
1134                                        if conn_ssid_str == ssid {
1135                                            conn_proxy.call::<_, _, ()>("Delete", &())?;
1136                                            return Ok(true);
1137                                        }
1138                                    }
1139                                }
1140                            }
1141                        }
1142                    }
1143                }
1144            }
1145        }
1146
1147        // No se encontró ninguna conexión con el SSID especificado
1148        Ok(false)
1149    }
1150
1151    /// List saved VPN profiles from NetworkManager settings.
1152    pub fn list_vpn_profiles(&self) -> Result<Vec<VpnProfile>> {
1153        let connections = self.list_connection_paths()?;
1154        let mut profiles = Vec::new();
1155
1156        for conn_path in connections {
1157            let settings = self.get_connection_settings(&conn_path)?;
1158            if let Some(profile) = self.vpn_profile_from_settings(&settings) {
1159                profiles.push(profile);
1160            }
1161        }
1162
1163        profiles.sort_by(|a, b| a.id.cmp(&b.id));
1164        Ok(profiles)
1165    }
1166
1167    /// Get current VPN status from active connections.
1168    pub fn get_vpn_status(&self) -> Result<VpnStatus> {
1169        let active_connections_variant = self.proxy.get(
1170            InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
1171            "ActiveConnections",
1172        )?;
1173
1174        let mut status = VpnStatus::default();
1175
1176        if let Some(zbus::zvariant::Value::Array(arr)) = active_connections_variant.downcast_ref() {
1177            for value in arr.iter() {
1178                let active_path = match value {
1179                    zbus::zvariant::Value::ObjectPath(path) => path,
1180                    _ => continue,
1181                };
1182
1183                let active_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1184                    .destination("org.freedesktop.NetworkManager")?
1185                    .path(active_path)?
1186                    .build()?;
1187
1188                let conn_type_variant = active_props.get(
1189                    InterfaceName::from_static_str_unchecked(
1190                        "org.freedesktop.NetworkManager.Connection.Active",
1191                    ),
1192                    "Type",
1193                )?;
1194
1195                let conn_type = match conn_type_variant.downcast_ref() {
1196                    Some(zbus::zvariant::Value::Str(v)) => v.to_string(),
1197                    _ => continue,
1198                };
1199
1200                if conn_type != "vpn" {
1201                    continue;
1202                }
1203
1204                let state_variant = active_props.get(
1205                    InterfaceName::from_static_str_unchecked(
1206                        "org.freedesktop.NetworkManager.Connection.Active",
1207                    ),
1208                    "State",
1209                )?;
1210                let state = match state_variant.downcast_ref() {
1211                    Some(zbus::zvariant::Value::U32(v)) => *v,
1212                    _ => 0,
1213                };
1214
1215                let id_variant = active_props.get(
1216                    InterfaceName::from_static_str_unchecked(
1217                        "org.freedesktop.NetworkManager.Connection.Active",
1218                    ),
1219                    "Id",
1220                )?;
1221                let uuid_variant = active_props.get(
1222                    InterfaceName::from_static_str_unchecked(
1223                        "org.freedesktop.NetworkManager.Connection.Active",
1224                    ),
1225                    "Uuid",
1226                )?;
1227
1228                status.state = Self::vpn_state_from_active_state(state);
1229                status.active_profile_name = match id_variant.downcast_ref() {
1230                    Some(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1231                    _ => None,
1232                };
1233                status.active_profile_id = status.active_profile_name.clone();
1234                status.active_profile_uuid = match uuid_variant.downcast_ref() {
1235                    Some(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1236                    _ => None,
1237                };
1238
1239                let ip4_config_variant = active_props.get(
1240                    InterfaceName::from_static_str_unchecked(
1241                        "org.freedesktop.NetworkManager.Connection.Active",
1242                    ),
1243                    "Ip4Config",
1244                )?;
1245
1246                if let Some(zbus::zvariant::Value::ObjectPath(ip4_path)) =
1247                    ip4_config_variant.downcast_ref()
1248                {
1249                    if ip4_path.as_str() != "/" {
1250                        let ip4_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1251                            .destination("org.freedesktop.NetworkManager")?
1252                            .path(ip4_path)?
1253                            .build()?;
1254
1255                        if let Ok(gateway_variant) = ip4_props.get(
1256                            InterfaceName::from_static_str_unchecked(
1257                                "org.freedesktop.NetworkManager.IP4Config",
1258                            ),
1259                            "Gateway",
1260                        ) {
1261                            status.gateway = match gateway_variant.downcast_ref() {
1262                                Some(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1263                                _ => None,
1264                            };
1265                        }
1266                    }
1267                }
1268
1269                return Ok(status);
1270            }
1271        }
1272
1273        Ok(status)
1274    }
1275
1276    /// Connect a VPN profile by UUID.
1277    pub fn connect_vpn(&self, uuid: String) -> Result<()> {
1278        let current_status = self.get_vpn_status()?;
1279        if current_status.state == VpnConnectionState::Connected
1280            && current_status.active_profile_uuid.as_deref() == Some(uuid.as_str())
1281        {
1282            return Err(crate::error::NetworkError::VpnAlreadyConnected(uuid));
1283        }
1284
1285        let conn_path = self.find_connection_path_by_uuid(&uuid)?;
1286
1287        let nm_proxy = zbus::blocking::Proxy::new(
1288            &self.connection,
1289            "org.freedesktop.NetworkManager",
1290            "/org/freedesktop/NetworkManager",
1291            "org.freedesktop.NetworkManager",
1292        )?;
1293
1294        let activate_result: zbus::Result<zbus::zvariant::OwnedObjectPath> =
1295            nm_proxy.call("ActivateConnection", &(conn_path.as_str(), "/", "/"));
1296
1297        match activate_result {
1298            Ok(_) => Ok(()),
1299            Err(e) => {
1300                let msg = e.to_string().to_lowercase();
1301                if msg.contains("secret") || msg.contains("authentication") {
1302                    Err(crate::error::NetworkError::VpnAuthFailed(e.to_string()))
1303                } else {
1304                    Err(crate::error::NetworkError::VpnActivationFailed(e.to_string()))
1305                }
1306            }
1307        }
1308    }
1309
1310    /// Disconnect VPN by UUID or disconnect active VPN if UUID is not provided.
1311    pub fn disconnect_vpn(&self, uuid: Option<String>) -> Result<()> {
1312        let active_connections_variant = self.proxy.get(
1313            InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
1314            "ActiveConnections",
1315        )?;
1316
1317        let mut target_active_connection: Option<zbus::zvariant::OwnedObjectPath> = None;
1318
1319        if let Some(zbus::zvariant::Value::Array(arr)) = active_connections_variant.downcast_ref() {
1320            for value in arr.iter() {
1321                let active_path = match value {
1322                    zbus::zvariant::Value::ObjectPath(path) => {
1323                        zbus::zvariant::OwnedObjectPath::from(path.to_owned())
1324                    }
1325                    _ => continue,
1326                };
1327
1328                let active_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1329                    .destination("org.freedesktop.NetworkManager")?
1330                    .path(active_path.as_str())?
1331                    .build()?;
1332
1333                let conn_type_variant = active_props.get(
1334                    InterfaceName::from_static_str_unchecked(
1335                        "org.freedesktop.NetworkManager.Connection.Active",
1336                    ),
1337                    "Type",
1338                )?;
1339                let conn_type = match conn_type_variant.downcast_ref() {
1340                    Some(zbus::zvariant::Value::Str(v)) => v.to_string(),
1341                    _ => continue,
1342                };
1343
1344                if conn_type != "vpn" {
1345                    continue;
1346                }
1347
1348                if let Some(target_uuid) = uuid.as_deref() {
1349                    let uuid_variant = active_props.get(
1350                        InterfaceName::from_static_str_unchecked(
1351                            "org.freedesktop.NetworkManager.Connection.Active",
1352                        ),
1353                        "Uuid",
1354                    )?;
1355                    let active_uuid = match uuid_variant.downcast_ref() {
1356                        Some(zbus::zvariant::Value::Str(v)) => v.to_string(),
1357                        _ => continue,
1358                    };
1359
1360                    if active_uuid == target_uuid {
1361                        target_active_connection = Some(active_path.clone());
1362                        break;
1363                    }
1364                } else {
1365                    target_active_connection = Some(active_path.clone());
1366                    break;
1367                }
1368            }
1369        }
1370
1371        let target_active_connection = match target_active_connection {
1372            Some(path) => path,
1373            None => {
1374                if let Some(target_uuid) = uuid {
1375                    return Err(crate::error::NetworkError::VpnProfileNotFound(target_uuid));
1376                }
1377                return Err(crate::error::NetworkError::VpnNotActive);
1378            }
1379        };
1380
1381        let nm_proxy = zbus::blocking::Proxy::new(
1382            &self.connection,
1383            "org.freedesktop.NetworkManager",
1384            "/org/freedesktop/NetworkManager",
1385            "org.freedesktop.NetworkManager",
1386        )?;
1387
1388        nm_proxy.call::<_, _, ()>(
1389            "DeactivateConnection",
1390            &(target_active_connection.as_str(),),
1391        )?;
1392        Ok(())
1393    }
1394
1395    /// Create a new VPN profile in NetworkManager settings.
1396    pub fn create_vpn_profile(&self, config: VpnCreateConfig) -> Result<VpnProfile> {
1397        if config.id.trim().is_empty() {
1398            return Err(crate::error::NetworkError::VpnInvalidConfig(
1399                "id is required".to_string(),
1400            ));
1401        }
1402
1403        let uuid = Uuid::new_v4().to_string();
1404        let mut connection_section = HashMap::new();
1405        connection_section.insert("id".to_string(), Value::from(config.id.clone()));
1406        connection_section.insert("uuid".to_string(), Value::from(uuid.clone()));
1407        connection_section.insert("type".to_string(), Value::from("vpn"));
1408        connection_section.insert(
1409            "autoconnect".to_string(),
1410            Value::from(config.autoconnect.unwrap_or(false)),
1411        );
1412
1413        let mut vpn_section = HashMap::new();
1414        vpn_section.insert(
1415            "service-type".to_string(),
1416            Value::from(Self::service_type_from_vpn_type(&config.vpn_type)),
1417        );
1418
1419        if let Some(username) = config.username {
1420            vpn_section.insert("user-name".to_string(), Value::from(username));
1421        }
1422        if let Some(gateway) = config.gateway {
1423            vpn_section.insert("remote".to_string(), Value::from(gateway));
1424        }
1425        if let Some(ca_cert_path) = config.ca_cert_path {
1426            vpn_section.insert("ca".to_string(), Value::from(ca_cert_path));
1427        }
1428        if let Some(user_cert_path) = config.user_cert_path {
1429            vpn_section.insert("cert".to_string(), Value::from(user_cert_path));
1430        }
1431        if let Some(private_key_path) = config.private_key_path {
1432            vpn_section.insert("key".to_string(), Value::from(private_key_path));
1433        }
1434        if let Some(private_key_password) = config.private_key_password {
1435            vpn_section.insert("key-password".to_string(), Value::from(private_key_password));
1436        }
1437        if let Some(custom_settings) = config.settings {
1438            for (k, v) in custom_settings {
1439                vpn_section.insert(k, Value::from(v));
1440            }
1441        }
1442
1443        let mut vpn_secrets_section = HashMap::new();
1444        if let Some(password) = config.password {
1445            vpn_secrets_section.insert("password".to_string(), Value::from(password));
1446        }
1447        if let Some(custom_secrets) = config.secrets {
1448            for (k, v) in custom_secrets {
1449                vpn_secrets_section.insert(k, Value::from(v));
1450            }
1451        }
1452
1453        let mut settings: HashMap<String, HashMap<String, Value>> = HashMap::new();
1454        settings.insert("connection".to_string(), connection_section);
1455        settings.insert("vpn".to_string(), vpn_section);
1456        if !vpn_secrets_section.is_empty() {
1457            settings.insert("vpn-secrets".to_string(), vpn_secrets_section);
1458        }
1459
1460        let settings_proxy = zbus::blocking::Proxy::new(
1461            &self.connection,
1462            "org.freedesktop.NetworkManager",
1463            "/org/freedesktop/NetworkManager/Settings",
1464            "org.freedesktop.NetworkManager.Settings",
1465        )?;
1466
1467        let _created_path: zbus::zvariant::OwnedObjectPath =
1468            settings_proxy.call("AddConnection", &(settings,))?;
1469
1470        Ok(VpnProfile {
1471            id: config.id,
1472            uuid,
1473            vpn_type: config.vpn_type,
1474            interface_name: None,
1475            autoconnect: config.autoconnect.unwrap_or(false),
1476            editable: true,
1477            last_error: None,
1478        })
1479    }
1480
1481    /// Update an existing VPN profile by UUID.
1482    pub fn update_vpn_profile(&self, config: VpnUpdateConfig) -> Result<VpnProfile> {
1483        let conn_path = self.find_connection_path_by_uuid(&config.uuid)?;
1484        let existing_settings = self.get_connection_settings(&conn_path)?;
1485
1486        let existing_profile = self
1487            .vpn_profile_from_settings(&existing_settings)
1488            .ok_or_else(|| crate::error::NetworkError::VpnProfileNotFound(config.uuid.clone()))?;
1489
1490        let existing_vpn_settings = Self::string_map_from_section(&existing_settings, "vpn");
1491        let existing_vpn_secrets = Self::string_map_from_section(&existing_settings, "vpn-secrets");
1492
1493        // Start from the full current settings map to preserve unrelated sections
1494        // (IPv4/IPv6, routes, DNS, permissions, proxy, etc.).
1495        let mut settings: HashMap<String, HashMap<String, Value>> = HashMap::new();
1496        for (section_name, dict) in &existing_settings {
1497            let mut section_map: HashMap<String, Value> = HashMap::new();
1498            for (k, v) in dict {
1499                section_map.insert(k.clone(), <zbus::zvariant::Value<'_> as Clone>::clone(v));
1500            }
1501            settings.insert(section_name.clone(), section_map);
1502        }
1503
1504        let connection_section = settings
1505            .entry("connection".to_string())
1506            .or_insert_with(HashMap::new);
1507        connection_section.insert(
1508            "id".to_string(),
1509            Value::from(config.id.clone().unwrap_or(existing_profile.id.clone())),
1510        );
1511        connection_section.insert("uuid".to_string(), Value::from(config.uuid.clone()));
1512        connection_section.insert("type".to_string(), Value::from("vpn"));
1513        connection_section.insert(
1514            "autoconnect".to_string(),
1515            Value::from(config.autoconnect.unwrap_or(existing_profile.autoconnect)),
1516        );
1517
1518        let service_type = existing_vpn_settings
1519            .get("service-type")
1520            .cloned()
1521            .unwrap_or_else(|| {
1522                Self::service_type_from_vpn_type(&existing_profile.vpn_type).to_string()
1523            });
1524
1525        let vpn_section = settings
1526            .entry("vpn".to_string())
1527            .or_insert_with(HashMap::new);
1528        vpn_section.insert("service-type".to_string(), Value::from(service_type));
1529
1530        let mut merged_settings = existing_vpn_settings;
1531        if let Some(username) = config.username {
1532            merged_settings.insert("user-name".to_string(), username);
1533        }
1534        if let Some(gateway) = config.gateway {
1535            merged_settings.insert("remote".to_string(), gateway);
1536        }
1537        if let Some(ca_cert_path) = config.ca_cert_path {
1538            merged_settings.insert("ca".to_string(), ca_cert_path);
1539        }
1540        if let Some(user_cert_path) = config.user_cert_path {
1541            merged_settings.insert("cert".to_string(), user_cert_path);
1542        }
1543        if let Some(private_key_path) = config.private_key_path {
1544            merged_settings.insert("key".to_string(), private_key_path);
1545        }
1546        if let Some(private_key_password) = config.private_key_password {
1547            merged_settings.insert("key-password".to_string(), private_key_password);
1548        }
1549        if let Some(custom_settings) = config.settings {
1550            for (k, v) in custom_settings {
1551                merged_settings.insert(k, v);
1552            }
1553        }
1554
1555        for (k, v) in merged_settings {
1556            vpn_section.insert(k, Value::from(v));
1557        }
1558
1559        let mut merged_secrets = existing_vpn_secrets;
1560        if let Some(password) = config.password {
1561            merged_secrets.insert("password".to_string(), password);
1562        }
1563        if let Some(custom_secrets) = config.secrets {
1564            for (k, v) in custom_secrets {
1565                merged_secrets.insert(k, v);
1566            }
1567        }
1568
1569        if merged_secrets.is_empty() {
1570            settings.remove("vpn-secrets");
1571        } else {
1572            let vpn_secrets_section = settings
1573                .entry("vpn-secrets".to_string())
1574                .or_insert_with(HashMap::new);
1575            vpn_secrets_section.clear();
1576            for (k, v) in merged_secrets {
1577                vpn_secrets_section.insert(k, Value::from(v));
1578            }
1579        }
1580
1581        let conn_proxy = zbus::blocking::Proxy::new(
1582            &self.connection,
1583            "org.freedesktop.NetworkManager",
1584            conn_path.as_str(),
1585            "org.freedesktop.NetworkManager.Settings.Connection",
1586        )?;
1587        conn_proxy.call::<_, _, ()>("Update", &(settings,))?;
1588
1589        let updated_settings = self.get_connection_settings(&conn_path)?;
1590        self.vpn_profile_from_settings(&updated_settings)
1591            .ok_or_else(|| crate::error::NetworkError::VpnProfileNotFound(config.uuid))
1592    }
1593
1594    /// Delete a VPN profile by UUID.
1595    pub fn delete_vpn_profile(&self, uuid: String) -> Result<()> {
1596        let conn_path = self.find_connection_path_by_uuid(&uuid)?;
1597
1598        let conn_proxy = zbus::blocking::Proxy::new(
1599            &self.connection,
1600            "org.freedesktop.NetworkManager",
1601            conn_path.as_str(),
1602            "org.freedesktop.NetworkManager.Settings.Connection",
1603        )?;
1604
1605        conn_proxy.call::<_, _, ()>("Delete", &())?;
1606        Ok(())
1607    }
1608}
1609
1610#[cfg(test)]
1611mod tests {
1612    use super::*;
1613
1614    #[test]
1615    fn vpn_state_deactivated_maps_to_disconnected() {
1616        assert_eq!(
1617            VSKNetworkManager::<tauri::Wry>::vpn_state_from_active_state(4),
1618            VpnConnectionState::Disconnected
1619        );
1620    }
1621}
1622
1623/// Initialize the network manager plugin
1624pub async fn init(
1625    app: &AppHandle<tauri::Wry>,
1626    _api: PluginApi<tauri::Wry, ()>,
1627) -> Result<VSKNetworkManager<'static, tauri::Wry>> {
1628    Ok(VSKNetworkManager::new(app.clone()).await?)
1629}