From 0d2af884bcdba390f3b4a080fd42610cec164b30 Mon Sep 17 00:00:00 2001 From: Techmore Date: Thu, 29 Oct 2026 10:05:43 -0400 Subject: [PATCH] Tailscale app: gear button and settings sheet for each VPN Each VPN row gets a settings gear (emblem-system-symbolic, tooltip and accessible label) that opens a sheet: server, username, password, IPsec pre-shared key, Send all traffic over VPN connection, Remember passwords (on by default). Secret boxes start empty; empty keeps the stored value. Saves with NetworkManager's D-Bus Update2, so secrets never appear on a command line or in a log; a blank secret that can't be read back stops the save instead of wiping it. Checked against a throwaway VPN with fake secrets, then deleted it. Co-Authored-By: Claude Opus 5.5 --- rust/mseries-tailscale/README.md | 7 + .../mseries-tailscale/docs/vpn-edit-light.png | Bin 0 -> 39833 bytes rust/mseries-tailscale/docs/vpn-row-light.png | Bin 0 -> 34712 bytes rust/mseries-tailscale/src/cli.rs | 6 +- rust/mseries-tailscale/src/lib.rs | 1 + rust/mseries-tailscale/src/main.rs | 39 +++- rust/mseries-tailscale/src/model.rs | 12 + rust/mseries-tailscale/src/nm.rs | 116 ++++++++++ rust/mseries-tailscale/src/ui/mod.rs | 1 + rust/mseries-tailscale/src/ui/vpn_edit.rs | 215 ++++++++++++++++++ rust/mseries-tailscale/src/ui/window.rs | 56 ++++- 11 files changed, 436 insertions(+), 17 deletions(-) create mode 100644 rust/mseries-tailscale/docs/vpn-edit-light.png create mode 100644 rust/mseries-tailscale/docs/vpn-row-light.png create mode 100644 rust/mseries-tailscale/src/nm.rs create mode 100644 rust/mseries-tailscale/src/ui/vpn_edit.rs diff --git a/rust/mseries-tailscale/README.md b/rust/mseries-tailscale/README.md index 453345c..542242c 100644 --- a/rust/mseries-tailscale/README.md +++ b/rust/mseries-tailscale/README.md @@ -25,6 +25,13 @@ A native Pantheon window for Tailscale. It replaces the old launcher that opened **Send all traffic over VPN connection** switch (`ipv4.never-default` / `ipv6.never-default` = no). The card says which mode is active; a connected VPN shows **Reconnect** after the mode changes. **Add VPN…** opens Network settings, where new VPNs are created. +- **VPN settings (gear button):** each VPN row has a gear that opens an edit sheet: server, username, password, + IPsec pre-shared key, "Send all traffic over VPN connection" and **Remember passwords** (on by default; stored + system-wide, readable only by root; off means ask every time). Secret boxes start empty; empty keeps what is + stored. Saving goes through NetworkManager's D-Bus `Update2`, so secrets never appear on a command line or in a + log. A connected VPN offers Reconnect afterwards. + + ![VPN row](docs/vpn-row-light.png) ![VPN settings sheet](docs/vpn-edit-light.png) - **Devices:** every device on the tailnet, online first, with IP, OS and when it was last seen. Search with Ctrl+F. Click a row or the copy button to copy its IP; the terminal button copies `ssh ` (the MagicDNS name, e.g. `ssh sdolbec-ser8`). diff --git a/rust/mseries-tailscale/docs/vpn-edit-light.png b/rust/mseries-tailscale/docs/vpn-edit-light.png new file mode 100644 index 0000000000000000000000000000000000000000..89f964424e9fcab53a01b296e62883a0282ad4cf GIT binary patch literal 39833 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 literal 0 HcmV?d00001 diff --git a/rust/mseries-tailscale/src/cli.rs b/rust/mseries-tailscale/src/cli.rs index 43cde0e..3c2f9ac 100644 --- a/rust/mseries-tailscale/src/cli.rs +++ b/rust/mseries-tailscale/src/cli.rs @@ -108,7 +108,11 @@ pub async fn run_program(argv: &[&str], limit_secs: u64) -> Result<(bool, String let unsaved = std::fs::read_to_string(dir.join("unsaved-passwords.txt")).unwrap_or_default(); let uuid = argv.last().copied().unwrap_or_default(); let flags = if unsaved.lines().any(|l| l == uuid) { 2 } else { 0 }; - Ok((true, format!("password-flags = {flags}, ipsec-psk-flags = {flags}"), String::new())) + let data = format!( + "gateway = vpn.example.com, ipsec-enabled = yes, ipsec-psk-flags = {flags}, \ + password-flags = {flags}, user = name@example.com" + ); + Ok((true, data, String::new())) } _ => Ok((false, String::new(), "Error: not allowed in fixture mode".into())), }; diff --git a/rust/mseries-tailscale/src/lib.rs b/rust/mseries-tailscale/src/lib.rs index 6b37bd7..de38713 100644 --- a/rust/mseries-tailscale/src/lib.rs +++ b/rust/mseries-tailscale/src/lib.rs @@ -3,5 +3,6 @@ pub mod cli; pub mod model; +pub mod nm; pub mod strings; pub mod ui; diff --git a/rust/mseries-tailscale/src/main.rs b/rust/mseries-tailscale/src/main.rs index 8444bd6..8c7ec85 100644 --- a/rust/mseries-tailscale/src/main.rs +++ b/rust/mseries-tailscale/src/main.rs @@ -52,6 +52,21 @@ fn main() -> glib::ExitCode { win.window.connect_destroy(move |_| { s.borrow_mut().take(); }); + // MSERIES_TAILSCALE_SNAPSHOT_EDIT=1 opens the first VPN's edit sheet and renders it to -sheet.png. + if std::env::var("MSERIES_TAILSCALE_SNAPSHOT_EDIT").as_deref() == Ok("1") { + let w = win.clone(); + glib::timeout_add_local_once(std::time::Duration::from_millis(800), move || w.open_first_vpn_sheet()); + let w = win.clone(); + if let Ok(path) = std::env::var("MSERIES_TAILSCALE_SNAPSHOT") { + let delay: u64 = + std::env::var("MSERIES_TAILSCALE_SNAPSHOT_DELAY").ok().and_then(|d| d.parse().ok()).unwrap_or(2500); + glib::timeout_add_local_once(std::time::Duration::from_millis(delay.saturating_sub(300)), move || { + if let Some(sheet) = w.sheet() { + render(&sheet, &path.replace(".png", "-sheet.png")); + } + }); + } + } maybe_snapshot(app, &win.window); *slot.borrow_mut() = Some(win); }); @@ -73,16 +88,7 @@ fn maybe_snapshot(app: >k::Application, window: >k::ApplicationWindow) { let window = window.clone(); let app = app.clone(); glib::timeout_add_local_once(std::time::Duration::from_millis(delay), move || { - let paintable = gtk::WidgetPaintable::new(Some(&window)); - let snapshot = gtk::Snapshot::new(); - paintable.snapshot(&snapshot, window.width() as f64, window.height() as f64); - match (snapshot.to_node(), window.native().and_then(|n| n.renderer())) { - (Some(node), Some(renderer)) => match renderer.render_texture(&node, None).save_to_png(&path) { - Ok(()) => eprintln!("snapshot written to {path}"), - Err(e) => eprintln!("snapshot failed: {e}"), - }, - _ => eprintln!("nothing to render for the snapshot"), - } + render(window.upcast_ref(), &path); app.quit(); }); } @@ -100,3 +106,16 @@ fn find_switch(widget: >k::Widget) -> Option { } None } + +fn render(window: >k::Window, path: &str) { + let paintable = gtk::WidgetPaintable::new(Some(window)); + let snapshot = gtk::Snapshot::new(); + paintable.snapshot(&snapshot, window.width() as f64, window.height() as f64); + match (snapshot.to_node(), window.native().and_then(|n| n.renderer())) { + (Some(node), Some(renderer)) => match renderer.render_texture(&node, None).save_to_png(path) { + Ok(()) => eprintln!("snapshot written to {path}"), + Err(e) => eprintln!("snapshot failed: {e}"), + }, + _ => eprintln!("nothing to render for the snapshot"), + } +} diff --git a/rust/mseries-tailscale/src/model.rs b/rust/mseries-tailscale/src/model.rs index c0397a0..5f3cedb 100644 --- a/rust/mseries-tailscale/src/model.rs +++ b/rust/mseries-tailscale/src/model.rs @@ -369,6 +369,15 @@ pub fn parse_vpn_list(text: &str) -> Vec { .collect() } +/// `nmcli -g vpn.data` ("key = value, key = value") as a map. Values with ", " inside are not expected here. +pub fn parse_vpn_data(text: &str) -> std::collections::HashMap { + text.trim() + .split(", ") + .filter_map(|kv| kv.split_once('=')) + .map(|(k, v)| (k.trim().to_string(), v.trim().to_string())) + .collect() +} + #[cfg(test)] mod vpn_tests { use super::*; @@ -385,6 +394,9 @@ mod vpn_tests { assert!(!secrets_saved("password-flags = 0, ipsec-psk-flags = 2")); assert!(secrets_saved("password-flags = 0, ipsec-psk-flags = 0, user = a")); assert!(secrets_saved("")); + let d = parse_vpn_data("gateway = vpn.example.com, ipsec-enabled = yes, password-flags = 0, user = a@b.org\n"); + assert_eq!(d.get("gateway").map(String::as_str), Some("vpn.example.com")); + assert_eq!(d.get("user").map(String::as_str), Some("a@b.org")); assert!(needs_secrets("Error: Connection activation failed: Secrets were required, but not provided")); } } diff --git a/rust/mseries-tailscale/src/nm.rs b/rust/mseries-tailscale/src/nm.rs new file mode 100644 index 0000000..9064057 --- /dev/null +++ b/rust/mseries-tailscale/src/nm.rs @@ -0,0 +1,116 @@ +//! Saves a VPN connection through NetworkManager's D-Bus API (`Settings.Connection.Update2`). +//! +//! Secrets travel only over the system bus, never on a command line (where `ps` shows them), and are never +//! printed or logged. A blank secret field keeps the stored value: it is read back with `GetSecrets` and +//! written again, because `Update2` replaces the whole connection. + +use gtk::gio; +use gtk::glib::{self, Variant, prelude::*}; +use std::collections::HashMap; + +const NM: &str = "org.freedesktop.NetworkManager"; +const SETTINGS_PATH: &str = "/org/freedesktop/NetworkManager/Settings"; +const CONNECTION_IFACE: &str = "org.freedesktop.NetworkManager.Settings.Connection"; + +type Settings = HashMap>; + +/// What the edit sheet changes. `None` secrets keep what is stored. +pub struct VpnEdit { + pub server_key: String, + pub server: String, + pub user_key: String, + pub user: String, + pub password: Option, + pub psk: Option, + pub all_traffic: bool, + /// Store secrets (flag 0) or ask every time (flag 2). + pub remember: bool, +} + +async fn call( + bus: &gio::DBusConnection, + path: &str, + iface: &str, + method: &str, + args: Option<&Variant>, +) -> Result { + bus.call_future(Some(NM), path, iface, method, args, None, gio::DBusCallFlags::NONE, 30_000).await +} + +fn friendly(e: &glib::Error) -> String { + let m = e.message(); + if m.contains("NotAuthorized") || m.contains("not authorized") || m.contains("Insufficient privileges") { + "You didn't approve the change.".into() + } else { + // D-Bus errors carry the remote error name first; keep the human part. + m.rsplit(": ").next().unwrap_or(m).to_string() + } +} + +pub async fn save_vpn(uuid: &str, edit: VpnEdit) -> Result<(), String> { + let bus = gio::bus_get_future(gio::BusType::System).await.map_err(|e| friendly(&e))?; + let found = call( + &bus, + SETTINGS_PATH, + "org.freedesktop.NetworkManager.Settings", + "GetConnectionByUuid", + Some(&(uuid,).to_variant()), + ) + .await + .map_err(|e| friendly(&e))?; + let path: glib::variant::ObjectPath = found.child_value(0).get().ok_or("NetworkManager gave no connection")?; + let path = path.as_str().to_string(); + + let current = call(&bus, &path, CONNECTION_IFACE, "GetSettings", None).await.map_err(|e| friendly(&e))?; + let mut settings: Settings = current.child_value(0).get().ok_or("Couldn't read the VPN's settings")?; + + let vpn = settings.entry("vpn".into()).or_default(); + let mut data: HashMap = vpn.get("data").and_then(|v| v.get()).unwrap_or_default(); + let flag = if edit.remember { "0" } else { "2" }; + data.insert(edit.server_key.clone(), edit.server.trim().to_string()); + data.insert(edit.user_key.clone(), edit.user.trim().to_string()); + data.insert("password-flags".into(), flag.into()); + let has_psk = data.contains_key("ipsec-psk-flags") || data.get("ipsec-enabled").is_some_and(|v| v == "yes"); + if has_psk { + data.insert("ipsec-psk-flags".into(), flag.into()); + } + + // Keep stored secrets for blank fields: read them back (they never leave this process). + let mut secrets: HashMap = HashMap::new(); + if edit.remember && (edit.password.is_none() || (has_psk && edit.psk.is_none())) { + // Never wipe a stored secret by accident: if it can't be read back, stop and ask for it. + let stored = + call(&bus, &path, CONNECTION_IFACE, "GetSecrets", Some(&("vpn",).to_variant())).await.map_err(|_| { + "The saved password couldn't be read back. Type the password and key, then save.".to_string() + })?; + let stored: Settings = stored.child_value(0).get().unwrap_or_default(); + if let Some(s) = + stored.get("vpn").and_then(|v| v.get("secrets")).and_then(|v| v.get::>()) + { + secrets = s; + } + } + if edit.remember { + if let Some(p) = edit.password { + secrets.insert("password".into(), p); + } + if has_psk && let Some(k) = edit.psk { + secrets.insert("ipsec-psk".into(), k); + } + } else { + secrets.clear(); + } + vpn.insert("data".into(), data.to_variant()); + vpn.insert("secrets".into(), secrets.to_variant()); + + // macOS's "Send all traffic over VPN connection" is never-default = false. + for family in ["ipv4", "ipv6"] { + settings.entry(family.into()).or_default().insert("never-default".into(), (!edit.all_traffic).to_variant()); + } + + // Update2(settings, flags = TO_DISK, args = {}). + let args = (settings.to_variant(), 1u32, HashMap::::new().to_variant()); + let packed = Variant::tuple_from_iter([args.0, args.1.to_variant(), args.2]); + call(&bus, &path, CONNECTION_IFACE, "Update2", Some(&packed)).await.map_err(|e| friendly(&e))?; + Ok(()) +} diff --git a/rust/mseries-tailscale/src/ui/mod.rs b/rust/mseries-tailscale/src/ui/mod.rs index 14bbd0b..2976d6e 100644 --- a/rust/mseries-tailscale/src/ui/mod.rs +++ b/rust/mseries-tailscale/src/ui/mod.rs @@ -1,3 +1,4 @@ //! The Tailscale window: a status card with the connect switch, exit node, the tailnet's devices and settings. +pub mod vpn_edit; pub mod window; diff --git a/rust/mseries-tailscale/src/ui/vpn_edit.rs b/rust/mseries-tailscale/src/ui/vpn_edit.rs new file mode 100644 index 0000000..c989e68 --- /dev/null +++ b/rust/mseries-tailscale/src/ui/vpn_edit.rs @@ -0,0 +1,215 @@ +//! Edit sheet for one NetworkManager VPN: server, username, password, IPsec pre-shared key, "Send all traffic +//! over VPN connection", and whether to remember the passwords. Secret fields start empty; empty keeps what is +//! stored. Saving goes through `nm::save_vpn` (D-Bus), so secrets are never on a command line or in a log. + +use crate::cli; +use crate::model; +use crate::nm::{self, VpnEdit}; +use crate::strings::tr; +use gtk::{glib, prelude::*}; +use mseries_pantheon_kit::{chrome, set_accessible_label}; +use std::rc::Rc; + +/// Opens the sheet over `parent`. `on_saved(active)` runs after a successful save. +pub fn open( + parent: >k::Window, + toast: &granite::Toast, + vpn: &model::Vpn, + on_saved: impl Fn(bool) + 'static, +) -> gtk::Window { + let sheet = gtk::Window::builder() + .transient_for(parent) + .modal(true) + .destroy_with_parent(true) + .title(format!("{} {}", vpn.name, tr("Settings"))) + .default_width(460) + .resizable(false) + .build(); + sheet.add_css_class("mseries-tailscale"); + + let header = gtk::HeaderBar::builder().show_title_buttons(false).build(); + let cancel = gtk::Button::with_label(&tr("Cancel")); + let save = gtk::Button::with_label(&tr("Save")); + save.add_css_class(granite::CssClassSUGGESTED); + save.set_sensitive(false); + header.pack_start(&cancel); + header.pack_end(&save); + let title = gtk::Label::new(Some(&vpn.name)); + title.add_css_class("title"); + header.set_title_widget(Some(&title)); + sheet.set_titlebar(Some(&header)); + + let server = gtk::Entry::builder().hexpand(true).build(); + let user = gtk::Entry::builder().hexpand(true).build(); + let password = gtk::PasswordEntry::builder().show_peek_icon(true).hexpand(true).build(); + let psk = gtk::PasswordEntry::builder().show_peek_icon(true).hexpand(true).build(); + set_accessible_label(&server, &tr("Server")); + set_accessible_label(&user, &tr("Username")); + set_accessible_label(&password, &tr("Password")); + set_accessible_label(&psk, &tr("Pre-shared key")); + let all = gtk::Switch::builder().valign(gtk::Align::Center).active(vpn.all_traffic).build(); + let remember = gtk::Switch::builder().valign(gtk::Align::Center).active(true).build(); + set_accessible_label(&all, &tr("Send all traffic over VPN connection")); + set_accessible_label(&remember, &tr("Remember passwords")); + + let grid = gtk::Grid::builder().row_spacing(9).column_spacing(12).build(); + let mut row = 0; + let mut add = |label: &str, widget: >k::Widget| { + let l = gtk::Label::builder().label(label).xalign(1.0).build(); + l.add_css_class(granite::CssClassDIM); + grid.attach(&l, 0, row, 1, 1); + grid.attach(widget, 1, row, 1, 1); + row += 1; + }; + add(&tr("Server"), server.upcast_ref()); + add(&tr("Username"), user.upcast_ref()); + add(&tr("Password"), password.upcast_ref()); + add(&tr("Pre-shared key"), psk.upcast_ref()); + let psk_row = row - 1; + + let note = gtk::Label::builder().xalign(0.0).wrap(true).build(); + note.add_css_class(granite::CssClassDIM); + note.add_css_class(granite::CssClassSMALL); + + let switches = gtk::Box::builder().orientation(gtk::Orientation::Vertical).spacing(9).build(); + switches.add_css_class(granite::CssClassCARD); + switches.add_css_class("mseries-tailscale-card"); + switches.append(&row_with( + &tr("Send all traffic over VPN connection"), + &tr("Off sends only the VPN's own networks through it"), + &all, + )); + switches.append(>k::Separator::new(gtk::Orientation::Horizontal)); + switches.append(&row_with( + &tr("Remember passwords"), + &tr("Saved on this computer, readable only by the system. Off asks every time."), + &remember, + )); + + let error = gtk::Label::builder().xalign(0.0).wrap(true).visible(false).build(); + error.add_css_class(granite::CssClassERROR); + + let body = gtk::Box::builder() + .orientation(gtk::Orientation::Vertical) + .spacing(15) + .margin_top(18) + .margin_bottom(18) + .margin_start(18) + .margin_end(18) + .build(); + body.append(&grid); + body.append(¬e); + body.append(&switches); + body.append(&error); + sheet.set_child(Some(&body)); + + // Fill in the non-secret fields; secrets stay empty (empty keeps the stored value). + let keys = Rc::new(std::cell::RefCell::new(("gateway".to_string(), "user".to_string(), false))); + { + let (server, user, password, psk, note, save, keys, grid) = ( + server.clone(), + user.clone(), + password.clone(), + psk.clone(), + note.clone(), + save.clone(), + keys.clone(), + grid.clone(), + ); + let uuid = vpn.uuid.clone(); + glib::spawn_future_local(async move { + let data = match cli::run_program(&["nmcli", "-g", "vpn.data", "connection", "show", &uuid], 8).await { + Ok((true, out, _)) => model::parse_vpn_data(&out), + _ => Default::default(), + }; + let server_key = if data.contains_key("remote") { "remote" } else { "gateway" }; + let user_key = if data.contains_key("username") { "username" } else { "user" }; + let has_psk = data.contains_key("ipsec-psk-flags") || data.get("ipsec-enabled").is_some_and(|v| v == "yes"); + server.set_text(data.get(server_key).map(String::as_str).unwrap_or_default()); + user.set_text(data.get(user_key).map(String::as_str).unwrap_or_default()); + let saved = + model::secrets_saved(&data.iter().map(|(k, v)| format!("{k} = {v}")).collect::>().join(", ")); + let hint = if saved { tr("Saved. Leave empty to keep.") } else { tr("Not saved yet") }; + password.set_property("placeholder-text", &hint); + psk.set_property("placeholder-text", &hint); + for col in 0..2 { + if let Some(w) = grid.child_at(col, psk_row) { + w.set_visible(has_psk); + } + } + note.set_label(&if saved { + tr("Type a new password or key only to change it.") + } else { + tr("Type the password and pre-shared key once. They will be remembered.") + }); + *keys.borrow_mut() = (server_key.into(), user_key.into(), has_psk); + save.set_sensitive(true); + }); + } + + let s = sheet.clone(); + cancel.connect_clicked(move |_| s.close()); + let keyc = gtk::EventControllerKey::new(); + let s = sheet.clone(); + keyc.connect_key_pressed(move |_, key, _, _| { + if key == gtk::gdk::Key::Escape { + s.close(); + return glib::Propagation::Stop; + } + glib::Propagation::Proceed + }); + sheet.add_controller(keyc); + + let on_saved = Rc::new(on_saved); + let (uuid, active, toast) = (vpn.uuid.clone(), vpn.active, toast.clone()); + let s = sheet.clone(); + save.connect_clicked(move |button| { + let (server_key, user_key, has_psk) = keys.borrow().clone(); + let secret = |e: >k::PasswordEntry| Some(e.text().to_string()).filter(|t| !t.is_empty()); + let edit = VpnEdit { + server_key, + server: server.text().to_string(), + user_key, + user: user.text().to_string(), + password: secret(&password), + psk: if has_psk { secret(&psk) } else { None }, + all_traffic: all.is_active(), + remember: remember.is_active(), + }; + button.set_sensitive(false); + error.set_visible(false); + let (uuid, s, button, error, toast, on_saved) = + (uuid.clone(), s.clone(), button.clone(), error.clone(), toast.clone(), on_saved.clone()); + glib::spawn_future_local(async move { + match nm::save_vpn(&uuid, edit).await { + Ok(()) => { + chrome::show_toast(&toast, &tr("VPN settings saved")); + on_saved(active); + s.close(); + } + Err(message) => { + error.set_label(&message); + error.set_visible(true); + button.set_sensitive(true); + } + } + }); + }); + + sheet.present(); + sheet +} + +fn row_with(title: &str, description: &str, control: >k::Switch) -> gtk::Box { + let label = gtk::Label::builder().label(title).xalign(0.0).wrap(true).build(); + let sub = gtk::Label::builder().label(description).xalign(0.0).wrap(true).build(); + sub.add_css_class(granite::CssClassDIM); + sub.add_css_class(granite::CssClassSMALL); + let text = gtk::Box::builder().orientation(gtk::Orientation::Vertical).spacing(2).hexpand(true).build(); + text.append(&label); + text.append(&sub); + let row = gtk::Box::builder().spacing(12).build(); + row.append(&text); + row.append(control); + row +} diff --git a/rust/mseries-tailscale/src/ui/window.rs b/rust/mseries-tailscale/src/ui/window.rs index 8cd01cd..2ce6dda 100644 --- a/rust/mseries-tailscale/src/ui/window.rs +++ b/rust/mseries-tailscale/src/ui/window.rs @@ -65,6 +65,7 @@ pub struct AppWindow { busy: Cell, refreshing: Cell, network: RefCell>, + sheet: RefCell>, poll: RefCell>, } @@ -334,6 +335,7 @@ impl AppWindow { busy: Cell::new(false), refreshing: Cell::new(false), network: RefCell::new(None), + sheet: RefCell::new(None), poll: RefCell::new(None), }); @@ -883,11 +885,22 @@ impl AppWindow { fn vpn_card(self: &Rc, vpn: &model::Vpn, needs_reconnect: bool) -> gtk::Box { let card = card_box(); let on = gtk::Switch::builder().valign(gtk::Align::Center).active(vpn.active).state(vpn.active).build(); + let gear = icon_button("emblem-system-symbolic", &format!("{} {}", vpn.name, tr("Settings"))); + let controls = gtk::Box::builder().spacing(6).valign(gtk::Align::Center).build(); + controls.append(&gear); + controls.append(&on); + set_accessible_label(&on, &format!("{} {}", tr("Connect"), vpn.name)); card.append(&setting_row( &vpn.name, Some(&if vpn.active { tr("Connected") } else { tr("Not connected") }), - &on, + &controls, )); + let (weak, edit_vpn) = (Rc::downgrade(self), vpn.clone()); + gear.connect_clicked(move |_| { + if let Some(this) = weak.upgrade() { + this.open_vpn_sheet(&edit_vpn); + } + }); card.append(>k::Separator::new(gtk::Orientation::Horizontal)); let all = gtk::Switch::builder().valign(gtk::Align::Center).active(vpn.all_traffic).state(vpn.all_traffic).build(); @@ -900,9 +913,7 @@ impl AppWindow { if !vpn.secrets_saved { let line = gtk::Box::builder().spacing(12).build(); let note = gtk::Label::builder() - .label(tr( - "Passwords aren't saved, so connecting from here can't work. Save them once in the VPN's settings.", - )) + .label(tr("Passwords aren't saved, so connecting from here can't work. Save them once.")) .xalign(0.0) .hexpand(true) .wrap(true) @@ -914,10 +925,10 @@ impl AppWindow { line.append(&save); card.append(>k::Separator::new(gtk::Orientation::Horizontal)); card.append(&line); - let (weak, uuid) = (Rc::downgrade(self), vpn.uuid.clone()); + let (weak, edit_vpn) = (Rc::downgrade(self), vpn.clone()); save.connect_clicked(move |_| { if let Some(this) = weak.upgrade() { - this.edit_vpn(&uuid); + this.open_vpn_sheet(&edit_vpn); } }); } @@ -996,6 +1007,39 @@ impl AppWindow { card } + pub fn open_vpn_sheet(self: &Rc, vpn: &model::Vpn) { + let weak = Rc::downgrade(self); + let uuid = vpn.uuid.clone(); + let sheet = super::vpn_edit::open(self.window.upcast_ref(), &self.toast, vpn, move |active| { + if let Some(this) = weak.upgrade() { + if active { + // New settings apply on the next connect; offer Reconnect on the card. + this.vpn_pending.borrow_mut().insert(uuid.clone()); + } + this.vpn_key.borrow_mut().clear(); + this.refresh(); + } + }); + *self.sheet.borrow_mut() = Some(sheet); + } + + /// The first VPN, for fixture screenshots of the edit sheet. + pub fn open_first_vpn_sheet(self: &Rc) { + let this = self.clone(); + glib::spawn_future_local(async move { + if let Ok((true, out, _)) = + cli::run_program(&["nmcli", "-t", "-f", "NAME,UUID,TYPE,ACTIVE", "connection", "show"], 8).await + && let Some(vpn) = model::parse_vpn_list(&out).into_iter().next() + { + this.open_vpn_sheet(&vpn); + } + }); + } + + pub fn sheet(&self) -> Option { + self.sheet.borrow().clone() + } + /// Opens NetworkManager's editor for one connection, where "Store the password for all users" lives. fn edit_vpn(&self, uuid: &str) { if let Err(e) = gio::Subprocess::newv(