Skip to content

Commit 30d3bbf

Browse files
committed
dbus: forget method, disconnecting and forgetting associated networks
1 parent e6cd26e commit 30d3bbf

7 files changed

Lines changed: 188 additions & 5 deletions

File tree

nmrs-core/src/dbus.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,4 +547,123 @@ impl NetworkManager {
547547
_ => "Unknown",
548548
}
549549
}
550+
551+
pub async fn forget(&self, ssid: &str) -> zbus::Result<()> {
552+
use std::collections::HashMap;
553+
use zvariant::{OwnedObjectPath, Value};
554+
555+
let nm = NMProxy::new(&self.conn).await?;
556+
557+
// 1) Disconnect any Wi-Fi device currently connected to this SSID
558+
let devices = nm.get_devices().await?;
559+
for dev_path in &devices {
560+
let dev = NMDeviceProxy::builder(&self.conn)
561+
.path(dev_path.clone())?
562+
.build()
563+
.await?;
564+
if dev.device_type().await? != 2 {
565+
continue;
566+
}
567+
568+
let wifi = NMWirelessProxy::builder(&self.conn)
569+
.path(dev_path.clone())?
570+
.build()
571+
.await?;
572+
if let Ok(ap_path) = wifi.active_access_point().await
573+
&& ap_path.as_str() != "/"
574+
{
575+
let ap = NMAccessPointProxy::builder(&self.conn)
576+
.path(ap_path.clone())?
577+
.build()
578+
.await?;
579+
if let Ok(bytes) = ap.ssid().await
580+
&& std::str::from_utf8(&bytes).ok() == Some(ssid)
581+
{
582+
let dev_proxy: zbus::Proxy<'_> = zbus::proxy::Builder::new(&self.conn)
583+
.destination("org.freedesktop.NetworkManager")?
584+
.path(dev_path.clone())?
585+
.interface("org.freedesktop.NetworkManager.Device")?
586+
.build()
587+
.await?;
588+
let _ = dev_proxy.call_method("Disconnect", &()).await;
589+
590+
for _ in 0..10 {
591+
if dev.state().await? == 30 {
592+
break;
593+
}
594+
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
595+
}
596+
}
597+
}
598+
}
599+
600+
// 2) Delete any saved profiles for this SSID
601+
let settings: zbus::Proxy<'_> = zbus::proxy::Builder::new(&self.conn)
602+
.destination("org.freedesktop.NetworkManager")?
603+
.path("/org/freedesktop/NetworkManager/Settings")?
604+
.interface("org.freedesktop.NetworkManager.Settings")?
605+
.build()
606+
.await?;
607+
608+
let list_reply = settings.call_method("ListConnections", &()).await?;
609+
let conns: Vec<OwnedObjectPath> = list_reply.body().deserialize()?;
610+
611+
let mut deleted_any = false;
612+
613+
for cpath in conns {
614+
let cproxy: zbus::Proxy<'_> = zbus::proxy::Builder::new(&self.conn)
615+
.destination("org.freedesktop.NetworkManager")?
616+
.path(cpath.clone())?
617+
.interface("org.freedesktop.NetworkManager.Settings.Connection")?
618+
.build()
619+
.await?;
620+
621+
if let Ok(id) = cproxy.get_property::<String>("Id").await
622+
&& id == ssid
623+
{
624+
let _ = cproxy.call_method("Delete", &()).await;
625+
deleted_any = true;
626+
continue;
627+
}
628+
629+
if let Ok(msg) = cproxy.call_method("GetSettings", &()).await {
630+
let body = msg.body();
631+
let settings_map: HashMap<String, HashMap<String, Value>> = body.deserialize()?;
632+
633+
if let Some(conn_sec) = settings_map.get("connection")
634+
&& let Some(Value::Str(id)) = conn_sec.get("id")
635+
&& id.as_str() == ssid
636+
{
637+
let _ = cproxy.call_method("Delete", &()).await;
638+
deleted_any = true;
639+
continue;
640+
}
641+
642+
if let Some(wifi_sec) = settings_map.get("802-11-wireless")
643+
&& let Some(Value::Array(arr)) = wifi_sec.get("ssid")
644+
{
645+
let mut raw = Vec::new();
646+
for v in arr.iter() {
647+
if let Ok(b) = u8::try_from(v.clone()) {
648+
raw.push(b);
649+
}
650+
}
651+
if std::str::from_utf8(&raw).ok() == Some(ssid) {
652+
let _ = cproxy.call_method("Delete", &()).await;
653+
deleted_any = true;
654+
continue;
655+
}
656+
}
657+
}
658+
}
659+
660+
if deleted_any {
661+
println!("Forgot and disconnected '{ssid}'");
662+
Ok(())
663+
} else {
664+
Err(zbus::Error::Failure(format!(
665+
"No saved connection for {ssid}"
666+
)))
667+
}
668+
}
550669
}

nmrs-core/src/models.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub struct Network {
1313
pub is_eap: bool,
1414
}
1515

16+
#[derive(Debug, Clone, Serialize, Deserialize)]
1617
pub struct NetworkInfo {
1718
pub ssid: String,
1819
pub bssid: String,

nmrs-ui/src/style.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,19 @@ label.network-poor { color: #ef4444; }
168168
.wifi-open {
169169
color: white;
170170
}
171+
172+
.loading-spinner {
173+
margin-top: 12px;
174+
margin-bottom: 12px;
175+
opacity: 0.6;
176+
}
177+
178+
.forget-button {
179+
font-size: 0.85em;
180+
opacity: 0.7;
181+
padding: 2px 6px;
182+
border-radius: 6px;
183+
}
184+
.forget-button:hover {
185+
opacity: 1;
186+
}

nmrs-ui/src/ui/header.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub fn build_header(
3737
let wifi_switch = Switch::new();
3838
wifi_switch.set_valign(gtk::Align::Center);
3939
header.pack_end(&wifi_switch);
40+
wifi_switch.set_size_request(24, 24);
4041

4142
header.pack_end(&status);
4243

@@ -48,6 +49,7 @@ pub fn build_header(
4849
let stack_clone = stack.clone();
4950

5051
glib::MainContext::default().spawn_local(async move {
52+
stack_clone.set_visible_child_name("loading");
5153
clear_children(&list_container_clone);
5254

5355
match NetworkManager::new().await {
@@ -56,12 +58,15 @@ pub fn build_header(
5658
wifi_switch_clone.set_active(enabled);
5759

5860
if enabled {
59-
status_clone.set_text("");
61+
status_clone.set_text("Scanning...");
62+
let _ = nm.scan_networks().await;
63+
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
6064
match nm.list_networks().await {
6165
Ok(nets) => {
6266
let list: ListBox =
6367
networks::networks_view(&nets, &pw, &stack_clone);
6468
list_container_clone.append(&list);
69+
stack_clone.set_visible_child_name("networks");
6570
}
6671
Err(err) => {
6772
status_clone
@@ -117,6 +122,7 @@ pub fn build_header(
117122
let list: ListBox =
118123
networks::networks_view(&nets, &pw, &stack_inner);
119124
list_container_clone.append(&list);
125+
stack_inner.set_visible_child_name("networks");
120126
}
121127
Err(err) => {
122128
status_clone

nmrs-ui/src/ui/mod.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ pub mod networks;
55

66
use gtk::prelude::*;
77
use gtk::{
8-
Application, ApplicationWindow, Box as GtkBox, Label, Orientation, ScrolledWindow, Stack,
8+
Application, ApplicationWindow, Box as GtkBox, Label, Orientation, ScrolledWindow, Spinner,
9+
Stack,
910
};
1011

1112
pub fn build_ui(app: &Application) {
@@ -18,8 +19,19 @@ pub fn build_ui(app: &Application) {
1819
let list_container = GtkBox::new(Orientation::Vertical, 0);
1920

2021
let stack = Stack::new();
22+
23+
let spinner = Spinner::new();
24+
spinner.set_halign(gtk::Align::Center);
25+
spinner.set_valign(gtk::Align::Center);
26+
spinner.set_property("width-request", 24i32);
27+
spinner.set_property("height-request", 24i32);
28+
spinner.add_css_class("loading-spinner");
29+
spinner.start();
30+
31+
stack.add_named(&spinner, Some("loading"));
32+
stack.set_visible_child_name("loading");
33+
2134
stack.add_named(&list_container, Some("networks"));
22-
stack.set_visible_child_name("networks");
2335

2436
let header = header::build_header(&status, &list_container, &win, &stack);
2537
vbox.append(&header);

nmrs-ui/src/ui/network_page.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use glib::clone;
22
use gtk::prelude::*;
33
use gtk::{Align, Box, Button, Image, Label, Orientation};
4+
use nmrs_core::NetworkManager;
45
use nmrs_core::models::NetworkInfo;
56

6-
pub fn network_page(info: &NetworkInfo, stack: &gtk::Stack) -> Box {
7+
pub fn network_page(info: NetworkInfo, stack: &gtk::Stack) -> Box {
78
let container = Box::new(Orientation::Vertical, 12);
89
container.add_css_class("network-page");
910

@@ -27,8 +28,36 @@ pub fn network_page(info: &NetworkInfo, stack: &gtk::Stack) -> Box {
2728
icon.set_pixel_size(24);
2829
let title = Label::new(Some(&info.ssid));
2930
title.add_css_class("network-title");
31+
32+
let spacer = Box::new(Orientation::Horizontal, 0);
33+
spacer.set_hexpand(true);
34+
35+
let forget_btn = Button::with_label("Forget");
36+
forget_btn.add_css_class("forget-button");
37+
forget_btn.set_halign(Align::End);
38+
forget_btn.set_valign(Align::Center);
39+
forget_btn.set_cursor_from_name(Some("pointer"));
40+
41+
forget_btn.connect_clicked(clone!(
42+
#[strong]
43+
info,
44+
#[weak]
45+
stack,
46+
move |_| {
47+
let ssid = info.ssid.clone();
48+
glib::MainContext::default().spawn_local(async move {
49+
if let Ok(nm) = NetworkManager::new().await {
50+
let _ = nm.forget(&ssid).await;
51+
stack.set_visible_child_name("networks");
52+
}
53+
});
54+
}
55+
));
56+
3057
header.append(&icon);
3158
header.append(&title);
59+
header.append(&spacer);
60+
header.append(&forget_btn);
3261
container.append(&header);
3362

3463
// Basic info section

nmrs-ui/src/ui/networks.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ pub fn networks_view(
7474
if let Ok(nm) = NetworkManager::new().await
7575
&& let Ok(details) = nm.show_details(&net_data).await
7676
{
77-
let container = network_page(&details, &stack);
77+
let container = network_page(details, &stack);
7878

7979
if let Some(old) = stack.child_by_name("details") {
8080
stack.remove(&old);

0 commit comments

Comments
 (0)