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