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)?;
56 let v: &zbus::zvariant::Value<'_> = value;
57 v.downcast_ref::<String>().ok()
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)?;
65 let v: &zbus::zvariant::Value<'_> = value;
66 v.downcast_ref::<bool>().ok()
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 val: &zbus::zvariant::Value<'_> = v;
81 if let Ok(s) = val.downcast_ref::<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 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_icon_for(connection_type: &str, is_connected: bool) -> String {
192 if !is_connected {
193 return "network-offline-symbolic".to_string();
194 }
195
196 match connection_type {
197 "Ethernet" => "network-wired-symbolic".to_string(),
198 "Modem" => "network-cellular-connected-symbolic".to_string(),
199 "VPN" | "WireGuard" => "network-vpn-symbolic".to_string(),
200 _ => "network-transmit-receive-symbolic".to_string(),
201 }
202 }
203
204 pub fn new(app: AppHandle<R>) -> Result<Self> {
206 let connection = zbus::blocking::Connection::system()?;
207 let proxy = zbus::blocking::fdo::PropertiesProxy::builder(&connection)
208 .destination("org.freedesktop.NetworkManager")?
209 .path("/org/freedesktop/NetworkManager")?
210 .build()?;
211
212 Ok(Self {
213 connection,
214 proxy,
215 app,
216 })
217 }
218
219 fn primary_connection_path(&self) -> Option<zbus::zvariant::OwnedObjectPath> {
229 let primary = self
230 .proxy
231 .get(
232 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
233 "PrimaryConnection",
234 )
235 .ok()?;
236
237 match primary.downcast_ref() {
238 Ok(Value::ObjectPath(path)) if path.as_str() != "/" => Some(path.into()),
241 _ => None,
242 }
243 }
244
245 pub fn get_current_network_state(&self) -> Result<NetworkInfo> {
246 let active_connections_variant = self.proxy.get(
248 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
249 "ActiveConnections",
250 )?;
251
252 let primary = self.primary_connection_path();
253
254 match active_connections_variant.downcast_ref() {
256 Ok(Value::Array(arr)) if !arr.is_empty() => {
257 let chosen = match (&primary, &arr[0]) {
258 (Some(path), _) => path.as_ref().to_owned(),
259 (None, Value::ObjectPath(path)) => path.to_owned(),
260 _ => return Ok(NetworkInfo::default()),
261 };
262 let chosen = Value::ObjectPath(chosen);
263
264 match chosen {
265 zbus::zvariant::Value::ObjectPath(ref path) => {
266 let properties_proxy =
269 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
270 .destination("org.freedesktop.NetworkManager")?
271 .path(path)?
272 .build()?;
273
274 let devices_variant = properties_proxy.get(
275 InterfaceName::from_static_str_unchecked(
276 "org.freedesktop.NetworkManager.Connection.Active",
277 ),
278 "Devices",
279 )?;
280
281 let device_path = match devices_variant.downcast_ref() {
283 Ok(Value::Array(device_arr)) if !device_arr.is_empty() => {
284 match device_arr[0] {
285 zbus::zvariant::Value::ObjectPath(ref dev_path) => {
286 dev_path.clone()
287 }
288 _ => return Ok(NetworkInfo::default()),
289 }
290 }
291 _ => return Ok(NetworkInfo::default()),
292 };
293
294 let device_properties_proxy =
297 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
298 .destination("org.freedesktop.NetworkManager")?
299 .path(&device_path)?
300 .build()?;
301
302 let connection_type = device_properties_proxy.get(
303 InterfaceName::from_static_str_unchecked(
304 "org.freedesktop.NetworkManager.Device",
305 ),
306 "DeviceType",
307 )?;
308
309 let state_variant = properties_proxy.get(
310 InterfaceName::from_static_str_unchecked(
311 "org.freedesktop.NetworkManager.Connection.Active",
312 ),
313 "State",
314 )?;
315
316 let is_connected = match state_variant.downcast_ref() {
317 Ok(zbus::zvariant::Value::U32(state)) => state == 2, _ => false,
319 };
320
321 let connection_type_str = match connection_type.downcast_ref() {
326 Ok(zbus::zvariant::Value::U32(device_type)) => match device_type {
327 1 => "Ethernet".to_string(),
328 2 => "WiFi".to_string(),
329 8 => "Modem".to_string(),
330 16 => "VPN".to_string(),
331 29 => "WireGuard".to_string(),
332 _ => "Unknown".to_string(),
333 },
334 _ => "Unknown".to_string(),
335 };
336
337 let mut network_info = NetworkInfo {
339 name: "Unknown".to_string(),
340 ssid: "Unknown".to_string(),
341 connection_type: connection_type_str.clone(),
342 icon: "network-offline-symbolic".to_string(),
343 ip_address: "0.0.0.0".to_string(),
344 mac_address: "00:00:00:00:00:00".to_string(),
345 signal_strength: 0,
346 security_type: WiFiSecurityType::None,
347 is_connected: is_connected && NetworkManagerHelpers::has_internet_connectivity(&self.proxy)?,
348 };
349
350 let hw_address_variant = device_properties_proxy.get(
351 InterfaceName::from_static_str_unchecked(
352 "org.freedesktop.NetworkManager.Device",
353 ),
354 "HwAddress",
355 )?;
356
357 network_info.mac_address = match hw_address_variant.downcast_ref() {
358 Ok(zbus::zvariant::Value::Str(s)) => s.to_string(),
359 _ => "00:00:00:00:00:00".to_string(),
360 };
361
362 if connection_type_str == "WiFi" {
364 let wireless_properties_proxy =
367 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
368 .destination("org.freedesktop.NetworkManager")?
369 .path(&device_path)?
370 .build()?;
371
372 let active_ap_path = wireless_properties_proxy.get(
373 InterfaceName::from_static_str_unchecked(
374 "org.freedesktop.NetworkManager.Device.Wireless",
375 ),
376 "ActiveAccessPoint",
377 )?;
378
379 if let Ok(zbus::zvariant::Value::ObjectPath(ap_path)) =
380 active_ap_path.downcast_ref()
381 {
382 let _ap_proxy = zbus::blocking::Proxy::new(
383 &self.connection,
384 "org.freedesktop.NetworkManager",
385 &ap_path,
386 "org.freedesktop.NetworkManager.AccessPoint",
387 )?;
388
389 let ap_properties_proxy =
392 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
393 .destination("org.freedesktop.NetworkManager")?
394 .path(ap_path.as_str())?
395 .build()?;
396
397 let ssid_variant = ap_properties_proxy.get(
398 InterfaceName::from_static_str_unchecked(
399 "org.freedesktop.NetworkManager.AccessPoint",
400 ),
401 "Ssid",
402 )?;
403
404 network_info.ssid = match ssid_variant.downcast_ref() {
405 Ok(v) => NetworkManagerHelpers::ssid_from_value(&v),
406 _ => "Unknown".to_string(),
407 };
408 network_info.name = network_info.ssid.clone();
409
410 let strength_variant = ap_properties_proxy.get(
412 InterfaceName::from_static_str_unchecked(
413 "org.freedesktop.NetworkManager.AccessPoint",
414 ),
415 "Strength",
416 )?;
417
418 network_info.signal_strength = match strength_variant.downcast_ref()
419 {
420 Ok(zbus::zvariant::Value::U8(s)) => s,
421 _ => 0,
422 };
423
424 network_info.icon =
426 Self::get_wifi_icon(network_info.signal_strength);
427
428 network_info.security_type = NetworkManagerHelpers::detect_security_type(&ap_properties_proxy)?;
430 }
431 } else {
432 network_info.icon =
435 Self::get_icon_for(&connection_type_str, network_info.is_connected);
436 }
437 let ip4_config_path = device_properties_proxy.get(
439 InterfaceName::from_static_str_unchecked(
440 "org.freedesktop.NetworkManager.Device",
441 ),
442 "Ip4Config",
443 )?;
444
445 if let Ok(zbus::zvariant::Value::ObjectPath(config_path)) =
447 ip4_config_path.downcast_ref()
448 {
449 let ip_config_properties_proxy =
451 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
452 .destination("org.freedesktop.NetworkManager")?
453 .path(config_path)?
454 .build()?;
455
456 let addresses_variant = ip_config_properties_proxy.get(
457 InterfaceName::from_static_str_unchecked(
458 "org.freedesktop.NetworkManager.IP4Config",
459 ),
460 "Addresses",
461 )?;
462
463 if let Ok(Value::Array(addr_arr)) = addresses_variant.downcast_ref() {
464 if let Some(Value::Array(ip_tuple)) = addr_arr.first() {
465 if ip_tuple.len() >= 1 {
466 if let Value::U32(ip_int) = &ip_tuple[0] {
467 use std::net::Ipv4Addr;
468 network_info.ip_address =
469 Ipv4Addr::from((*ip_int).to_be()).to_string();
470 }
471 }
472 }
473 }
474 }
475
476 Ok(network_info)
477 }
478 _ => Ok(NetworkInfo::default()),
479 }
480 }
481 _ => Ok(NetworkInfo::default()),
482 }
483 }
484
485 fn connected_wifi_ssid(&self) -> Result<Option<String>> {
488 let devices = self.wireless_device_paths()?;
489 for device_path in &devices {
490 let wireless_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
491 .destination("org.freedesktop.NetworkManager")?
492 .path(device_path.as_str())?
493 .build()?;
494
495 let active_ap = wireless_props.get(
496 InterfaceName::from_static_str_unchecked(
497 "org.freedesktop.NetworkManager.Device.Wireless",
498 ),
499 "ActiveAccessPoint",
500 )?;
501
502 if let Ok(zbus::zvariant::Value::ObjectPath(ap_path)) = active_ap.downcast_ref() {
503 if ap_path.as_str() == "/" {
504 continue;
505 }
506 let ap_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
507 .destination("org.freedesktop.NetworkManager")?
508 .path(ap_path.as_str())?
509 .build()?;
510
511 let ssid_variant = ap_props.get(
512 InterfaceName::from_static_str_unchecked(
513 "org.freedesktop.NetworkManager.AccessPoint",
514 ),
515 "Ssid",
516 )?;
517
518 if let Ok(v) = ssid_variant.downcast_ref() {
519 let ssid = NetworkManagerHelpers::ssid_from_value(&v);
520 if !ssid.is_empty() && ssid != "Unknown" {
521 return Ok(Some(ssid));
522 }
523 }
524 }
525 }
526 Ok(None)
527 }
528
529 pub fn list_wifi_networks(&self) -> Result<Vec<NetworkInfo>> {
531 let devices_variant = self.proxy.get(
532 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
533 "Devices",
534 )?;
535
536 let mut networks = Vec::new();
537 let connected_ssid = self.connected_wifi_ssid()?;
538
539 if let Ok(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
540 for device in devices.iter() {
541 if let zbus::zvariant::Value::ObjectPath(ref device_path) = device {
542 let device_props =
543 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
544 .destination("org.freedesktop.NetworkManager")?
545 .path(device_path)?
546 .build()?;
547
548 let device_type_variant = device_props.get(
549 InterfaceName::from_static_str_unchecked(
550 "org.freedesktop.NetworkManager.Device",
551 ),
552 "DeviceType",
553 )?;
554
555 if let Ok(zbus::zvariant::Value::U32(device_type)) =
556 device_type_variant.downcast_ref()
557 {
558 if device_type == 2u32 {
559 let mac_address = match device_props
560 .get(
561 InterfaceName::from_static_str_unchecked(
562 "org.freedesktop.NetworkManager.Device",
563 ),
564 "HwAddress",
565 )?
566 .downcast_ref()
567 {
568 Ok(zbus::zvariant::Value::Str(s)) => s.to_string(),
569 _ => "00:00:00:00:00:00".to_string(),
570 };
571
572 let wireless_props =
573 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
574 .destination("org.freedesktop.NetworkManager")?
575 .path(device_path)?
576 .build()?;
577
578 let access_points_variant = wireless_props.get(
579 InterfaceName::from_static_str_unchecked(
580 "org.freedesktop.NetworkManager.Device.Wireless",
581 ),
582 "AccessPoints",
583 )?;
584
585 if let Ok(zbus::zvariant::Value::Array(aps)) =
586 access_points_variant.downcast_ref()
587 {
588 for ap in aps.iter() {
589 if let zbus::zvariant::Value::ObjectPath(ref ap_path) = ap {
590 let ap_props = zbus::blocking::fdo::PropertiesProxy::builder(
591 &self.connection,
592 )
593 .destination("org.freedesktop.NetworkManager")?
594 .path(ap_path)?
595 .build()?;
596
597 let ssid_variant = ap_props.get(
598 InterfaceName::from_static_str_unchecked(
599 "org.freedesktop.NetworkManager.AccessPoint",
600 ),
601 "Ssid",
602 )?;
603
604 let ssid = match ssid_variant.downcast_ref() {
605 Ok(v) => NetworkManagerHelpers::ssid_from_value(&v),
606 _ => "Unknown".to_string(),
607 };
608
609 let strength_variant = ap_props.get(
610 InterfaceName::from_static_str_unchecked(
611 "org.freedesktop.NetworkManager.AccessPoint",
612 ),
613 "Strength",
614 )?;
615
616 let strength = match strength_variant.downcast_ref() {
617 Ok(zbus::zvariant::Value::U8(s)) => s,
618 _ => 0,
619 };
620
621 let security_type = NetworkManagerHelpers::detect_security_type(&ap_props)?;
622
623 let is_connected = connected_ssid.as_deref() == Some(ssid.as_str());
624
625 let network_info = NetworkInfo {
626 name: ssid.clone(),
627 ssid,
628 connection_type: "wifi".to_string(),
629 icon: Self::get_wifi_icon(strength),
630 ip_address: "0.0.0.0".to_string(),
631 mac_address: mac_address.clone(),
632 signal_strength: strength,
633 security_type,
634 is_connected,
635 };
636
637 if !networks.iter().any(|n: &NetworkInfo| n.ssid == network_info.ssid) {
638 networks.push(network_info);
639 }
640 }
641 }
642 }
643 }
644 }
645 }
646 }
647 }
648
649 networks.sort_by(|a, b| b.signal_strength.cmp(&a.signal_strength));
650 Ok(networks)
651 }
652
653 fn wireless_device_paths(&self) -> Result<Vec<zbus::zvariant::OwnedObjectPath>> {
655 let devices_variant = self.proxy.get(
656 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
657 "Devices",
658 )?;
659
660 let mut paths = Vec::new();
661 if let Ok(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
662 for device in devices.iter() {
663 if let zbus::zvariant::Value::ObjectPath(device_path) = device {
664 let device_props =
665 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
666 .destination("org.freedesktop.NetworkManager")?
667 .path(device_path.as_str())?
668 .build()?;
669
670 let device_type_variant = device_props.get(
671 InterfaceName::from_static_str_unchecked(
672 "org.freedesktop.NetworkManager.Device",
673 ),
674 "DeviceType",
675 )?;
676
677 if let Ok(zbus::zvariant::Value::U32(device_type)) =
678 device_type_variant.downcast_ref()
679 {
680 if device_type == 2u32 {
681 let owned = zbus::zvariant::OwnedObjectPath::try_from(
682 device_path.as_str(),
683 )
684 .unwrap();
685 paths.push(owned);
686 }
687 }
688 }
689 }
690 }
691 Ok(paths)
692 }
693
694 pub fn rescan_wifi(&self) -> Result<Vec<NetworkInfo>> {
696 let devices = self.wireless_device_paths()?;
697
698 if devices.is_empty() {
699 return Err(crate::error::NetworkError::OperationError(
700 "No wireless device available for scanning".to_string(),
701 ));
702 }
703
704 for device_path in &devices {
705 let wireless_proxy = zbus::blocking::Proxy::new(
706 &self.connection,
707 "org.freedesktop.NetworkManager",
708 device_path.as_str(),
709 "org.freedesktop.NetworkManager.Device.Wireless",
710 )?;
711
712 let options: HashMap<String, zbus::zvariant::OwnedValue> = HashMap::new();
713 if wireless_proxy.call::<_, _, ()>("RequestScan", &(options,)).is_ok() {
714 return self.list_wifi_networks();
715 }
716 }
717
718 Err(crate::error::NetworkError::OperationError(
719 "Failed to request WiFi scan on available wireless devices".to_string(),
720 ))
721 }
722
723 pub fn connect_to_wifi(&self, config: WiFiConnectionConfig) -> Result<()> {
725 log::debug!("connect_to_wifi called: ssid='{}' security={:?} username={:?}",
727 config.ssid, config.security_type, config.username);
728
729 let mut connection_settings = HashMap::new();
731 let mut wifi_settings = HashMap::new();
732 let mut security_settings = HashMap::new();
733
734 let mut connection = HashMap::new();
736 connection.insert("id".to_string(), Value::from(config.ssid.clone()));
737 connection.insert("type".to_string(), Value::from("802-11-wireless"));
738 connection_settings.insert("connection".to_string(), connection);
739
740 wifi_settings.insert("ssid".to_string(), Value::from(config.ssid.as_bytes().to_vec()));
742 wifi_settings.insert("mode".to_string(), Value::from("infrastructure"));
743
744 match config.security_type {
746 WiFiSecurityType::None => {
747 }
749 WiFiSecurityType::Wep => {
750 security_settings.insert("key-mgmt".to_string(), Value::from("none"));
751 if let Some(password) = config.password.clone() {
752 security_settings.insert("wep-key0".to_string(), Value::from(password));
753 }
754 }
755 WiFiSecurityType::WpaPsk => {
756 security_settings.insert("key-mgmt".to_string(), Value::from("wpa-psk"));
757 if let Some(password) = config.password.clone() {
758 security_settings.insert("psk".to_string(), Value::from(password));
759 }
760 }
761 WiFiSecurityType::WpaEap => {
762 security_settings.insert("key-mgmt".to_string(), Value::from("wpa-eap"));
763 if let Some(password) = config.password.clone() {
764 security_settings.insert("password".to_string(), Value::from(password));
765 }
766 if let Some(username) = config.username.clone() {
767 security_settings.insert("identity".to_string(), Value::from(username));
768 }
769 }
770 WiFiSecurityType::Wpa2Psk => {
771 security_settings.insert("key-mgmt".to_string(), Value::from("wpa-psk"));
772 security_settings.insert("proto".to_string(), Value::from(vec!["rsn"]));
773 if let Some(password) = config.password.clone() {
774 security_settings.insert("psk".to_string(), Value::from(password));
775 }
776 }
777 WiFiSecurityType::Wpa3Psk => {
778 security_settings.insert("key-mgmt".to_string(), Value::from("sae"));
779 if let Some(password) = config.password.clone() {
780 security_settings.insert("psk".to_string(), Value::from(password));
781 }
782 }
783 }
784
785 connection_settings.insert("802-11-wireless".to_string(), wifi_settings);
786 if !security_settings.is_empty() {
787 connection_settings.insert("802-11-wireless-security".to_string(), security_settings);
788 }
789
790 log::debug!("connection_settings: {:#?}", connection_settings);
792
793 let nm_proxy = zbus::blocking::Proxy::new(
795 &self.connection,
796 "org.freedesktop.NetworkManager",
797 "/org/freedesktop/NetworkManager",
798 "org.freedesktop.NetworkManager",
799 )?;
800
801 let any_path = zbus::zvariant::OwnedObjectPath::try_from("/").unwrap();
803 let call_result: zbus::Result<(zbus::zvariant::OwnedObjectPath, zbus::zvariant::OwnedObjectPath)> = nm_proxy.call("AddAndActivateConnection", &(connection_settings, &any_path, &any_path));
804
805 match call_result {
806 Ok((conn_path, active_path)) => {
807 log::info!(
808 "AddAndActivateConnection succeeded for ssid='{}' conn='{}' active='{}'",
809 config.ssid,
810 conn_path.as_str(),
811 active_path.as_str()
812 );
813 }
814 Err(e) => {
815 log::error!(
816 "AddAndActivateConnection failed for ssid='{}': {:?}",
817 config.ssid,
818 e
819 );
820 return Err(e.into());
821 }
822 }
823
824 log::debug!("connect_to_wifi finished for ssid='{}'", config.ssid);
825
826 Ok(())
827 }
828
829 pub fn toggle_network_state(&self, enabled: bool) -> Result<bool> {
831 let nm_proxy = zbus::blocking::Proxy::new(
832 &self.connection,
833 "org.freedesktop.NetworkManager",
834 "/org/freedesktop/NetworkManager",
835 "org.freedesktop.NetworkManager",
836 )?;
837
838 nm_proxy.set_property("NetworkingEnabled", enabled)?;
839
840 let current_state: bool = nm_proxy.get_property("NetworkingEnabled")?;
841 Ok(current_state)
842 }
843
844 pub fn get_wireless_enabled(&self) -> Result<bool> {
846 let nm_proxy = zbus::blocking::Proxy::new(
847 &self.connection,
848 "org.freedesktop.NetworkManager",
849 "/org/freedesktop/NetworkManager",
850 "org.freedesktop.NetworkManager",
851 )?;
852 Ok(nm_proxy.get_property("WirelessEnabled")?)
853 }
854
855 pub fn set_wireless_enabled(&self, enabled: bool) -> Result<()> {
857 let nm_proxy = zbus::blocking::Proxy::new(
858 &self.connection,
859 "org.freedesktop.NetworkManager",
860 "/org/freedesktop/NetworkManager",
861 "org.freedesktop.NetworkManager",
862 )?;
863 nm_proxy.set_property("WirelessEnabled", enabled)?;
864 Ok(())
865 }
866
867 pub fn is_wireless_available(&self) -> Result<bool> {
869 Ok(!self.wireless_device_paths()?.is_empty())
870 }
871
872 pub fn listen_network_changes(&self) -> Result<mpsc::Receiver<NetworkInfo>> {
874 let (tx, rx) = mpsc::channel();
875 let connection_clone = self.connection.clone();
876 let app_handle = self.app.clone();
877
878 std::thread::spawn(move || {
879 if let Ok(proxy) = zbus::blocking::Proxy::new(
880 &connection_clone,
881 "org.freedesktop.NetworkManager",
882 "/org/freedesktop/NetworkManager",
883 "org.freedesktop.NetworkManager",
884 ) {
885 if let Ok(mut signal) = proxy.receive_signal("StateChanged") {
886 let proxy_props = zbus::blocking::fdo::PropertiesProxy::builder(
887 &connection_clone,
888 )
889 .destination("org.freedesktop.NetworkManager")
890 .unwrap()
891 .path("/org/freedesktop/NetworkManager")
892 .unwrap()
893 .build()
894 .unwrap();
895
896 while let Some(_msg) = signal.next() {
897 let network_manager = VSKNetworkManager {
898 connection: connection_clone.clone(),
899 proxy: proxy_props.clone(),
900 app: app_handle.clone(),
901 };
902
903 if let Ok(network_info) =
904 network_manager.get_current_network_state()
905 {
906 if tx.send(network_info).is_err() {
907 break;
908 }
909 }
910 }
911 }
912 }
913 });
914
915 Ok(rx)
916 }
917
918 pub fn disconnect_from_wifi(&self) -> Result<()> {
920 let nm_proxy = zbus::blocking::Proxy::new(
921 &self.connection,
922 "org.freedesktop.NetworkManager",
923 "/org/freedesktop/NetworkManager",
924 "org.freedesktop.NetworkManager",
925 )?;
926
927 let active_connections_variant: zbus::zvariant::OwnedValue = self.proxy.get(
929 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
930 "ActiveConnections",
931 )?;
932
933 let active_connections = match active_connections_variant.downcast_ref() {
935 Ok(zbus::zvariant::Value::Array(arr)) => arr
936 .iter()
937 .filter_map(|v| match v {
938 zbus::zvariant::Value::ObjectPath(path) => {
939 Some(zbus::zvariant::OwnedObjectPath::from(path.to_owned()))
940 }
941 _ => None,
942 })
943 .collect::<Vec<zbus::zvariant::OwnedObjectPath>>(),
944 _ => Vec::new(),
945 };
946
947 if !active_connections.is_empty() {
948 nm_proxy.call::<_, _, ()>("DeactivateConnection", &(&active_connections[0],))?;
949 Ok(())
950 } else {
951 Ok(())
952 }
953 }
954
955 pub fn get_saved_wifi_networks(&self) -> Result<Vec<NetworkInfo>> {
957 let settings_proxy = zbus::blocking::Proxy::new(
959 &self.connection,
960 "org.freedesktop.NetworkManager",
961 "/org/freedesktop/NetworkManager/Settings",
962 "org.freedesktop.NetworkManager.Settings",
963 )?;
964
965 let connections: Vec<zbus::zvariant::OwnedObjectPath> =
967 settings_proxy.call("ListConnections", &())?;
968 let mut saved_networks = Vec::new();
969
970 for conn_path in connections {
972 let conn_proxy = zbus::blocking::Proxy::new(
974 &self.connection,
975 "org.freedesktop.NetworkManager",
976 conn_path.as_str(),
977 "org.freedesktop.NetworkManager.Settings.Connection",
978 )?;
979
980 let settings: std::collections::HashMap<String, zbus::zvariant::OwnedValue> =
982 conn_proxy.call("GetSettings", &())?;
983
984 if let Some(connection) = settings.get("connection") {
986 let connection_dict = match connection.downcast_ref::<Value>()
987 .and_then(|v| v.downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>())
988 {
989 Ok(dict) => dict,
990 _ => continue,
991 };
992
993 if let Some(conn_type) = connection_dict.get("type") {
995 let v_type: &zbus::zvariant::Value<'_> = conn_type;
996 let conn_type_str = match v_type.downcast_ref::<String>() {
997 Ok(s) => s,
998 _ => continue,
999 };
1000
1001 if conn_type_str == "802-11-wireless" {
1003 let mut network_info = NetworkInfo::default();
1004 network_info.connection_type = "wifi".to_string();
1005
1006 if let Some(id) = connection_dict.get("id") {
1008 let v_id: &zbus::zvariant::Value<'_> = id;
1009 if let Ok(name) = v_id.downcast_ref::<String>() {
1010 network_info.name = name;
1011 }
1012 }
1013
1014 if let Some(wireless) = settings.get("802-11-wireless") {
1016 let wireless_dict = match wireless.downcast_ref::<Value>()
1017 .and_then(|v| v.downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>())
1018 {
1019 Ok(dict) => dict,
1020 _ => continue,
1021 };
1022
1023 if let Some(ssid) = wireless_dict.get("ssid") {
1024 let v_ssid: &zbus::zvariant::Value<'_> = ssid;
1025 network_info.ssid = NetworkManagerHelpers::ssid_from_value(v_ssid);
1026 }
1027 }
1028
1029 if let Some(security) = settings.get("802-11-wireless-security") {
1031 let security_dict = match security.downcast_ref::<Value>()
1032 .and_then(|v| v.downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>())
1033 {
1034 Ok(dict) => dict,
1035 _ => {
1036 network_info.security_type = WiFiSecurityType::None;
1037 saved_networks.push(network_info);
1038 continue;
1039 },
1040 };
1041
1042 if let Some(key_mgmt) = security_dict.get("key-mgmt") {
1043 let v_km: &zbus::zvariant::Value<'_> = key_mgmt;
1044 if let Ok(key_mgmt_str) = v_km.downcast_ref::<String>() {
1045 match key_mgmt_str.as_str() {
1046 "none" => {
1047 network_info.security_type = WiFiSecurityType::None
1048 }
1049 "wpa-psk" => {
1050 network_info.security_type = WiFiSecurityType::WpaPsk
1051 }
1052 "wpa-eap" => {
1053 network_info.security_type = WiFiSecurityType::WpaEap
1054 }
1055 _ => network_info.security_type = WiFiSecurityType::None,
1056 }
1057 }
1058 }
1059 } else {
1060 network_info.security_type = WiFiSecurityType::None;
1061 }
1062
1063 saved_networks.push(network_info);
1065 }
1066 }
1067 }
1068 }
1069
1070 Ok(saved_networks)
1071 }
1072
1073 pub fn delete_wifi_connection(&self, ssid: &str) -> Result<bool> {
1075 let settings_proxy = zbus::blocking::Proxy::new(
1077 &self.connection,
1078 "org.freedesktop.NetworkManager",
1079 "/org/freedesktop/NetworkManager/Settings",
1080 "org.freedesktop.NetworkManager.Settings",
1081 )?;
1082
1083 let connections: Vec<zbus::zvariant::OwnedObjectPath> =
1085 settings_proxy.call("ListConnections", &())?;
1086
1087 for conn_path in connections {
1089 let conn_proxy = zbus::blocking::Proxy::new(
1091 &self.connection,
1092 "org.freedesktop.NetworkManager",
1093 conn_path.as_str(),
1094 "org.freedesktop.NetworkManager.Settings.Connection",
1095 )?;
1096
1097 let settings: std::collections::HashMap<String, zbus::zvariant::OwnedValue> =
1099 conn_proxy.call("GetSettings", &())?;
1100
1101 if let Some(connection) = settings.get("connection") {
1103 let connection_dict = match connection.downcast_ref::<Value>()
1104 .and_then(|v| v.downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>())
1105 {
1106 Ok(dict) => dict,
1107 _ => continue,
1108 };
1109
1110 if let Some(conn_type) = connection_dict.get("type") {
1112 let v_type: &zbus::zvariant::Value<'_> = conn_type;
1113 let conn_type_str = match v_type.downcast_ref::<String>() {
1114 Ok(s) => s,
1115 _ => continue,
1116 };
1117
1118 if conn_type_str == "802-11-wireless" {
1120 if let Some(wireless) = settings.get("802-11-wireless") {
1121 let wireless_dict = match wireless.downcast_ref::<Value>()
1122 .and_then(|v| v.downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>())
1123 {
1124 Ok(dict) => dict,
1125 _ => continue,
1126 };
1127
1128 if let Some(ssid_value) = wireless_dict.get("ssid") {
1129 let v_ssid: &zbus::zvariant::Value<'_> = ssid_value;
1130 let conn_ssid_str = NetworkManagerHelpers::ssid_from_value(v_ssid);
1131 if conn_ssid_str == ssid {
1133 conn_proxy.call::<_, _, ()>("Delete", &())?;
1134 return Ok(true);
1135 }
1136 }
1137 }
1138 }
1139 }
1140 }
1141 }
1142
1143 Ok(false)
1145 }
1146
1147 pub fn list_vpn_profiles(&self) -> Result<Vec<VpnProfile>> {
1149 let connections = self.list_connection_paths()?;
1150 let mut profiles = Vec::new();
1151
1152 for conn_path in connections {
1153 let settings = self.get_connection_settings(&conn_path)?;
1154 if let Some(profile) = self.vpn_profile_from_settings(&settings) {
1155 profiles.push(profile);
1156 }
1157 }
1158
1159 profiles.sort_by(|a, b| a.id.cmp(&b.id));
1160 Ok(profiles)
1161 }
1162
1163 pub fn get_vpn_status(&self) -> Result<VpnStatus> {
1165 let active_connections_variant = self.proxy.get(
1166 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
1167 "ActiveConnections",
1168 )?;
1169
1170 let mut status = VpnStatus::default();
1171
1172 if let Ok(zbus::zvariant::Value::Array(arr)) = active_connections_variant.downcast_ref() {
1173 for value in arr.iter() {
1174 let active_path = match value {
1175 zbus::zvariant::Value::ObjectPath(path) => path,
1176 _ => continue,
1177 };
1178
1179 let active_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1180 .destination("org.freedesktop.NetworkManager")?
1181 .path(active_path)?
1182 .build()?;
1183
1184 let conn_type_variant = active_props.get(
1185 InterfaceName::from_static_str_unchecked(
1186 "org.freedesktop.NetworkManager.Connection.Active",
1187 ),
1188 "Type",
1189 )?;
1190
1191 let conn_type = match conn_type_variant.downcast_ref() {
1192 Ok(zbus::zvariant::Value::Str(v)) => v.to_string(),
1193 _ => continue,
1194 };
1195
1196 if conn_type != "vpn" {
1197 continue;
1198 }
1199
1200 let state_variant = active_props.get(
1201 InterfaceName::from_static_str_unchecked(
1202 "org.freedesktop.NetworkManager.Connection.Active",
1203 ),
1204 "State",
1205 )?;
1206 let state = match state_variant.downcast_ref() {
1207 Ok(zbus::zvariant::Value::U32(v)) => v,
1208 _ => 0,
1209 };
1210
1211 let id_variant = active_props.get(
1212 InterfaceName::from_static_str_unchecked(
1213 "org.freedesktop.NetworkManager.Connection.Active",
1214 ),
1215 "Id",
1216 )?;
1217 let uuid_variant = active_props.get(
1218 InterfaceName::from_static_str_unchecked(
1219 "org.freedesktop.NetworkManager.Connection.Active",
1220 ),
1221 "Uuid",
1222 )?;
1223
1224 status.state = Self::vpn_state_from_active_state(state);
1225 status.active_profile_name = match id_variant.downcast_ref() {
1226 Ok(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1227 _ => None,
1228 };
1229 status.active_profile_id = status.active_profile_name.clone();
1230 status.active_profile_uuid = match uuid_variant.downcast_ref() {
1231 Ok(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1232 _ => None,
1233 };
1234
1235 let ip4_config_variant = active_props.get(
1236 InterfaceName::from_static_str_unchecked(
1237 "org.freedesktop.NetworkManager.Connection.Active",
1238 ),
1239 "Ip4Config",
1240 )?;
1241
1242 if let Ok(zbus::zvariant::Value::ObjectPath(ip4_path)) =
1243 ip4_config_variant.downcast_ref()
1244 {
1245 if ip4_path.as_str() != "/" {
1246 let ip4_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1247 .destination("org.freedesktop.NetworkManager")?
1248 .path(ip4_path)?
1249 .build()?;
1250
1251 if let Ok(gateway_variant) = ip4_props.get(
1252 InterfaceName::from_static_str_unchecked(
1253 "org.freedesktop.NetworkManager.IP4Config",
1254 ),
1255 "Gateway",
1256 ) {
1257 status.gateway = match gateway_variant.downcast_ref() {
1258 Ok(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1259 _ => None,
1260 };
1261 }
1262 }
1263 }
1264
1265 return Ok(status);
1266 }
1267 }
1268
1269 Ok(status)
1270 }
1271
1272 pub fn connect_vpn(&self, uuid: String) -> Result<()> {
1274 let current_status = self.get_vpn_status()?;
1275 if current_status.state == VpnConnectionState::Connected
1276 && current_status.active_profile_uuid.as_deref() == Some(uuid.as_str())
1277 {
1278 return Err(crate::error::NetworkError::VpnAlreadyConnected(uuid));
1279 }
1280
1281 let conn_path = self.find_connection_path_by_uuid(&uuid)?;
1282
1283 let nm_proxy = zbus::blocking::Proxy::new(
1284 &self.connection,
1285 "org.freedesktop.NetworkManager",
1286 "/org/freedesktop/NetworkManager",
1287 "org.freedesktop.NetworkManager",
1288 )?;
1289
1290 let any_path = zbus::zvariant::OwnedObjectPath::try_from("/").unwrap();
1291 let activate_result: zbus::Result<zbus::zvariant::OwnedObjectPath> =
1292 nm_proxy.call("ActivateConnection", &(&conn_path, &any_path, &any_path));
1293
1294 match activate_result {
1295 Ok(_) => Ok(()),
1296 Err(e) => {
1297 let msg = e.to_string().to_lowercase();
1298 if msg.contains("secret") || msg.contains("authentication") {
1299 Err(crate::error::NetworkError::VpnAuthFailed(e.to_string()))
1300 } else {
1301 Err(crate::error::NetworkError::VpnActivationFailed(e.to_string()))
1302 }
1303 }
1304 }
1305 }
1306
1307 pub fn disconnect_vpn(&self, uuid: Option<String>) -> Result<()> {
1309 let active_connections_variant = self.proxy.get(
1310 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
1311 "ActiveConnections",
1312 )?;
1313
1314 let mut target_active_connection: Option<zbus::zvariant::OwnedObjectPath> = None;
1315
1316 if let Ok(zbus::zvariant::Value::Array(arr)) = active_connections_variant.downcast_ref() {
1317 for value in arr.iter() {
1318 let active_path = match value {
1319 zbus::zvariant::Value::ObjectPath(path) => {
1320 zbus::zvariant::OwnedObjectPath::from(path.to_owned())
1321 }
1322 _ => continue,
1323 };
1324
1325 let active_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1326 .destination("org.freedesktop.NetworkManager")?
1327 .path(active_path.as_str())?
1328 .build()?;
1329
1330 let conn_type_variant = active_props.get(
1331 InterfaceName::from_static_str_unchecked(
1332 "org.freedesktop.NetworkManager.Connection.Active",
1333 ),
1334 "Type",
1335 )?;
1336 let conn_type = match conn_type_variant.downcast_ref() {
1337 Ok(zbus::zvariant::Value::Str(v)) => v.to_string(),
1338 _ => continue,
1339 };
1340
1341 if conn_type != "vpn" {
1342 continue;
1343 }
1344
1345 if let Some(target_uuid) = uuid.as_deref() {
1346 let uuid_variant = active_props.get(
1347 InterfaceName::from_static_str_unchecked(
1348 "org.freedesktop.NetworkManager.Connection.Active",
1349 ),
1350 "Uuid",
1351 )?;
1352 let active_uuid = match uuid_variant.downcast_ref() {
1353 Ok(zbus::zvariant::Value::Str(v)) => v.to_string(),
1354 _ => continue,
1355 };
1356
1357 if active_uuid == target_uuid {
1358 target_active_connection = Some(active_path.clone());
1359 break;
1360 }
1361 } else {
1362 target_active_connection = Some(active_path.clone());
1363 break;
1364 }
1365 }
1366 }
1367
1368 let target_active_connection = match target_active_connection {
1369 Some(path) => path,
1370 None => {
1371 if let Some(target_uuid) = uuid {
1372 return Err(crate::error::NetworkError::VpnProfileNotFound(target_uuid));
1373 }
1374 return Err(crate::error::NetworkError::VpnNotActive);
1375 }
1376 };
1377
1378 let nm_proxy = zbus::blocking::Proxy::new(
1379 &self.connection,
1380 "org.freedesktop.NetworkManager",
1381 "/org/freedesktop/NetworkManager",
1382 "org.freedesktop.NetworkManager",
1383 )?;
1384
1385 nm_proxy.call::<_, _, ()>(
1386 "DeactivateConnection",
1387 &(target_active_connection.as_str(),),
1388 )?;
1389 Ok(())
1390 }
1391
1392 pub fn create_vpn_profile(&self, config: VpnCreateConfig) -> Result<VpnProfile> {
1394 if config.id.trim().is_empty() {
1395 return Err(crate::error::NetworkError::VpnInvalidConfig(
1396 "id is required".to_string(),
1397 ));
1398 }
1399
1400 let uuid = Uuid::new_v4().to_string();
1401 let mut connection_section = HashMap::new();
1402 connection_section.insert("id".to_string(), Value::from(config.id.clone()));
1403 connection_section.insert("uuid".to_string(), Value::from(uuid.clone()));
1404 connection_section.insert("type".to_string(), Value::from("vpn"));
1405 connection_section.insert(
1406 "autoconnect".to_string(),
1407 Value::from(config.autoconnect.unwrap_or(false)),
1408 );
1409
1410 let mut vpn_section = HashMap::new();
1411 vpn_section.insert(
1412 "service-type".to_string(),
1413 Value::from(Self::service_type_from_vpn_type(&config.vpn_type)),
1414 );
1415
1416 if let Some(username) = config.username {
1417 vpn_section.insert("user-name".to_string(), Value::from(username));
1418 }
1419 if let Some(gateway) = config.gateway {
1420 vpn_section.insert("remote".to_string(), Value::from(gateway));
1421 }
1422 if let Some(ca_cert_path) = config.ca_cert_path {
1423 vpn_section.insert("ca".to_string(), Value::from(ca_cert_path));
1424 }
1425 if let Some(user_cert_path) = config.user_cert_path {
1426 vpn_section.insert("cert".to_string(), Value::from(user_cert_path));
1427 }
1428 if let Some(private_key_path) = config.private_key_path {
1429 vpn_section.insert("key".to_string(), Value::from(private_key_path));
1430 }
1431 if let Some(private_key_password) = config.private_key_password {
1432 vpn_section.insert("key-password".to_string(), Value::from(private_key_password));
1433 }
1434 if let Some(custom_settings) = config.settings {
1435 for (k, v) in custom_settings {
1436 vpn_section.insert(k, Value::from(v));
1437 }
1438 }
1439
1440 let mut vpn_secrets_section = HashMap::new();
1441 if let Some(password) = config.password {
1442 vpn_secrets_section.insert("password".to_string(), Value::from(password));
1443 }
1444 if let Some(custom_secrets) = config.secrets {
1445 for (k, v) in custom_secrets {
1446 vpn_secrets_section.insert(k, Value::from(v));
1447 }
1448 }
1449
1450 let mut settings: HashMap<String, HashMap<String, Value>> = HashMap::new();
1451 settings.insert("connection".to_string(), connection_section);
1452 settings.insert("vpn".to_string(), vpn_section);
1453 if !vpn_secrets_section.is_empty() {
1454 settings.insert("vpn-secrets".to_string(), vpn_secrets_section);
1455 }
1456
1457 let settings_proxy = zbus::blocking::Proxy::new(
1458 &self.connection,
1459 "org.freedesktop.NetworkManager",
1460 "/org/freedesktop/NetworkManager/Settings",
1461 "org.freedesktop.NetworkManager.Settings",
1462 )?;
1463
1464 let _created_path: zbus::zvariant::OwnedObjectPath =
1465 settings_proxy.call("AddConnection", &(settings,))?;
1466
1467 Ok(VpnProfile {
1468 id: config.id,
1469 uuid,
1470 vpn_type: config.vpn_type,
1471 interface_name: None,
1472 autoconnect: config.autoconnect.unwrap_or(false),
1473 editable: true,
1474 last_error: None,
1475 })
1476 }
1477
1478 pub fn update_vpn_profile(&self, config: VpnUpdateConfig) -> Result<VpnProfile> {
1480 let conn_path = self.find_connection_path_by_uuid(&config.uuid)?;
1481 let existing_settings = self.get_connection_settings(&conn_path)?;
1482
1483 let existing_profile = self
1484 .vpn_profile_from_settings(&existing_settings)
1485 .ok_or_else(|| crate::error::NetworkError::VpnProfileNotFound(config.uuid.clone()))?;
1486
1487 let existing_vpn_settings = Self::string_map_from_section(&existing_settings, "vpn");
1488 let existing_vpn_secrets = Self::string_map_from_section(&existing_settings, "vpn-secrets");
1489
1490 let mut settings: HashMap<String, HashMap<String, Value>> = HashMap::new();
1493 for (section_name, dict) in &existing_settings {
1494 let mut section_map: HashMap<String, Value> = HashMap::new();
1495 for (k, v) in dict {
1496 if let Ok(val) = v.downcast_ref::<Value>() {
1497 section_map.insert(k.clone(), val);
1498 }
1499 }
1500 settings.insert(section_name.clone(), section_map);
1501 }
1502
1503 let connection_section = settings
1504 .entry("connection".to_string())
1505 .or_insert_with(HashMap::new);
1506 connection_section.insert(
1507 "id".to_string(),
1508 Value::from(config.id.clone().unwrap_or(existing_profile.id.clone())),
1509 );
1510 connection_section.insert("uuid".to_string(), Value::from(config.uuid.clone()));
1511 connection_section.insert("type".to_string(), Value::from("vpn"));
1512 connection_section.insert(
1513 "autoconnect".to_string(),
1514 Value::from(config.autoconnect.unwrap_or(existing_profile.autoconnect)),
1515 );
1516
1517 let service_type = existing_vpn_settings
1518 .get("service-type")
1519 .cloned()
1520 .unwrap_or_else(|| {
1521 Self::service_type_from_vpn_type(&existing_profile.vpn_type).to_string()
1522 });
1523
1524 let vpn_section = settings
1525 .entry("vpn".to_string())
1526 .or_insert_with(HashMap::new);
1527 vpn_section.insert("service-type".to_string(), Value::from(service_type));
1528
1529 let mut merged_settings = existing_vpn_settings;
1530 if let Some(username) = config.username {
1531 merged_settings.insert("user-name".to_string(), username);
1532 }
1533 if let Some(gateway) = config.gateway {
1534 merged_settings.insert("remote".to_string(), gateway);
1535 }
1536 if let Some(ca_cert_path) = config.ca_cert_path {
1537 merged_settings.insert("ca".to_string(), ca_cert_path);
1538 }
1539 if let Some(user_cert_path) = config.user_cert_path {
1540 merged_settings.insert("cert".to_string(), user_cert_path);
1541 }
1542 if let Some(private_key_path) = config.private_key_path {
1543 merged_settings.insert("key".to_string(), private_key_path);
1544 }
1545 if let Some(private_key_password) = config.private_key_password {
1546 merged_settings.insert("key-password".to_string(), private_key_password);
1547 }
1548 if let Some(custom_settings) = config.settings {
1549 for (k, v) in custom_settings {
1550 merged_settings.insert(k, v);
1551 }
1552 }
1553
1554 for (k, v) in merged_settings {
1555 vpn_section.insert(k, Value::from(v));
1556 }
1557
1558 let mut merged_secrets = existing_vpn_secrets;
1559 if let Some(password) = config.password {
1560 merged_secrets.insert("password".to_string(), password);
1561 }
1562 if let Some(custom_secrets) = config.secrets {
1563 for (k, v) in custom_secrets {
1564 merged_secrets.insert(k, v);
1565 }
1566 }
1567
1568 if merged_secrets.is_empty() {
1569 settings.remove("vpn-secrets");
1570 } else {
1571 let vpn_secrets_section = settings
1572 .entry("vpn-secrets".to_string())
1573 .or_insert_with(HashMap::new);
1574 vpn_secrets_section.clear();
1575 for (k, v) in merged_secrets {
1576 vpn_secrets_section.insert(k, Value::from(v));
1577 }
1578 }
1579
1580 let conn_proxy = zbus::blocking::Proxy::new(
1581 &self.connection,
1582 "org.freedesktop.NetworkManager",
1583 conn_path.as_str(),
1584 "org.freedesktop.NetworkManager.Settings.Connection",
1585 )?;
1586 conn_proxy.call::<_, _, ()>("Update", &(settings,))?;
1587
1588 let updated_settings = self.get_connection_settings(&conn_path)?;
1589 self.vpn_profile_from_settings(&updated_settings)
1590 .ok_or_else(|| crate::error::NetworkError::VpnProfileNotFound(config.uuid))
1591 }
1592
1593 pub fn delete_vpn_profile(&self, uuid: String) -> Result<()> {
1595 let conn_path = self.find_connection_path_by_uuid(&uuid)?;
1596
1597 let conn_proxy = zbus::blocking::Proxy::new(
1598 &self.connection,
1599 "org.freedesktop.NetworkManager",
1600 conn_path.as_str(),
1601 "org.freedesktop.NetworkManager.Settings.Connection",
1602 )?;
1603
1604 conn_proxy.call::<_, _, ()>("Delete", &())?;
1605 Ok(())
1606 }
1607}
1608
1609#[cfg(test)]
1610mod tests {
1611 use super::*;
1612
1613 #[test]
1614 fn vpn_state_deactivated_maps_to_disconnected() {
1615 assert_eq!(
1616 VSKNetworkManager::<tauri::Wry>::vpn_state_from_active_state(4),
1617 VpnConnectionState::Disconnected
1618 );
1619 }
1620}
1621
1622pub fn init(
1624 app: &AppHandle<tauri::Wry>,
1625 _api: PluginApi<tauri::Wry, ()>,
1626) -> Result<VSKNetworkManager<'static, tauri::Wry>> {
1627 Ok(VSKNetworkManager::new(app.clone())?)
1628}