From 5dc87cb70e122eba232e5c4d3406d2e03c6a8fc6 Mon Sep 17 00:00:00 2001 From: Techmore Date: Thu, 29 Oct 2026 10:28:05 -0400 Subject: [PATCH] Tailscale app: Connecting state at once, timer in the header bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turning a VPN on now shows a spinner and Connecting... in its row immediately and locks the switch until nmcli answers. The header bar subtitle shows "BFS · Connecting..." and then "BFS · 00:12:34"; the row has the same timer. Times use two-digit hours. Co-Authored-By: Claude Opus 5.5 --- rust/mseries-tailscale/README.md | 5 ++ .../docs/vpn-header-states.png | Bin 0 -> 58588 bytes rust/mseries-tailscale/src/net.rs | 6 +- rust/mseries-tailscale/src/ui/window.rs | 53 +++++++++++++++++- 4 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 rust/mseries-tailscale/docs/vpn-header-states.png diff --git a/rust/mseries-tailscale/README.md b/rust/mseries-tailscale/README.md index 18cdbae..d9c686b 100644 --- a/rust/mseries-tailscale/README.md +++ b/rust/mseries-tailscale/README.md @@ -40,6 +40,11 @@ A native Pantheon window for Tailscale. It replaces the old launcher that opened Connecting…, "Dropped: on, but not carrying any traffic", or "Failed: " (from NetworkManager, pppd and IPsec logs), with Reconnect. "Internet traffic now goes through …" names the interface the kernel really uses (`ip route get`, policy routing included). +- **Connecting and timer in the header:** turning a VPN on shows a spinner and "Connecting…" in its row at once, + with the switch locked until there is an answer; the header bar's subtitle reads "BFS · Connecting…", then + "BFS · 00:12:34" (the same timer is in the row). A drop or failure stops the timer and the row says why. + + ![Header states](docs/vpn-header-states.png) - **Timer and traffic:** each connected VPN, and Tailscale, shows how long it has been up (kept across app restarts, reset on reconnect) and bytes down/up. - **Two VPNs at once:** a banner when another tunnel (WiFiman's `utun`, any WireGuard/tun/PPP device) is up next diff --git a/rust/mseries-tailscale/docs/vpn-header-states.png b/rust/mseries-tailscale/docs/vpn-header-states.png new file mode 100644 index 0000000000000000000000000000000000000000..74c723fd2c127b782aef10924480348f126e21fb GIT binary patch literal 58588 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∨Z_|7iKQ4T&*nPU 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 Fpccg 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 xZD String { } } -/// "0:04:09", "12:30:00". +/// "00:04:09", "12:30:00". pub fn duration_text(secs: i64) -> String { let s = secs.max(0); - format!("{}:{:02}:{:02}", s / 3600, (s / 60) % 60, s % 60) + format!("{:02}:{:02}:{:02}", s / 3600, (s / 60) % 60, s % 60) } /// A friendly name for a tunnel interface that is not one of the listed VPNs. @@ -160,7 +160,7 @@ mod tests { assert_eq!(bytes_text(999), "999 B"); assert_eq!(bytes_text(4_307_842), "4.3 MB"); assert_eq!(bytes_text(48_300_000), "48 MB"); - assert_eq!(duration_text(249), "0:04:09"); + assert_eq!(duration_text(249), "00:04:09"); assert_eq!(duration_text(45_000), "12:30:00"); assert_eq!(tunnel_owner("utun"), "WiFiman (Ubiquiti Teleport)"); } diff --git a/rust/mseries-tailscale/src/ui/window.rs b/rust/mseries-tailscale/src/ui/window.rs index 3bcb90e..38ada1a 100644 --- a/rust/mseries-tailscale/src/ui/window.rs +++ b/rust/mseries-tailscale/src/ui/window.rs @@ -59,6 +59,8 @@ pub struct AppWindow { route_now: gtk::Label, sessions: Sessions, ts_timer: gtk::Label, + header_sub: gtk::Label, + vpn_names: RefCell>, remote_list: gtk::Box, remote_key: RefCell, ticker: RefCell>, @@ -99,6 +101,17 @@ impl AppWindow { set_accessible_label(&refresh, &tr("Refresh")); header.pack_start(&refresh); header.pack_end(&chrome::primary_menu(&tr("About Tailscale"), &tr("Quit"))); + // Title with a live subtitle: "BFS · 00:12:34" while a VPN is up, "BFS · Connecting…" while it starts. + let header_title = gtk::Label::new(Some(&tr("Tailscale"))); + header_title.add_css_class("title"); + let header_sub = gtk::Label::builder().visible(false).build(); + header_sub.add_css_class(granite::CssClassSMALL); + header_sub.add_css_class(granite::CssClassNUMERIC); + header_sub.add_css_class(granite::CssClassDIM); + let titles_box = gtk::Box::builder().orientation(gtk::Orientation::Vertical).valign(gtk::Align::Center).build(); + titles_box.append(&header_title); + titles_box.append(&header_sub); + header.set_title_widget(Some(&titles_box)); window.set_titlebar(Some(&header)); // ---- Status card ------------------------------------------------------------------------------------ @@ -366,6 +379,8 @@ impl AppWindow { route_now, sessions: Sessions::load(), ts_timer, + header_sub, + vpn_names: RefCell::new(HashMap::new()), remote_list, remote_key: RefCell::new(String::new()), ticker: RefCell::new(None), @@ -991,6 +1006,7 @@ impl AppWindow { self.sessions.stop(&vpn.uuid); } self.vpn_live.borrow_mut().insert(vpn.uuid.clone(), live.clone()); + self.vpn_names.borrow_mut().insert(vpn.uuid.clone(), model::display_name(&vpn.name)); lives.push(live); } self.sessions.save(); @@ -1090,6 +1106,8 @@ impl AppWindow { controls.append(&gear); controls.append(&on); set_accessible_label(&on, &format!("{} {name}", tr("Connect"))); + // No second click until the first one has an answer. + on.set_sensitive(!matches!(live, Live::Connecting)); let (status_text, status_class) = match live { Live::Connected(iface) => (format!("{} · {iface}", tr("Connected")), Some(granite::CssClassSUCCESS)), Live::Connecting => (tr("Connecting…"), None), @@ -1218,8 +1236,18 @@ impl AppWindow { } let (weak, uuid) = (Rc::downgrade(self), vpn.uuid.clone()); - on.connect_state_set(move |_, active| { + let desc = row.first_child().and_then(|t| t.last_child()).and_downcast::(); + let controls_now = controls.clone(); + on.connect_state_set(move |switch, active| { if let Some(this) = weak.upgrade() { + // Instant feedback: spinner, "Connecting…", switch locked until nmcli answers. + switch.set_sensitive(false); + controls_now.prepend(>k::Spinner::builder().spinning(true).valign(gtk::Align::Center).build()); + if let Some(desc) = &desc { + desc.set_label(&if active { tr("Connecting…") } else { tr("Disconnecting…") }); + desc.remove_css_class(granite::CssClassERROR); + desc.remove_css_class(granite::CssClassSUCCESS); + } let verb = if active { "up" } else { "down" }; let done = if active { tr("Connected to") } else { tr("Disconnected from") }; this.vpn_failures.borrow_mut().remove(&uuid); @@ -1255,6 +1283,29 @@ impl AppWindow { t.bytes.set_label(&format!("↓ {} ↑ {}", net::bytes_text(rx), net::bytes_text(tx))); } } + let names = self.vpn_names.borrow(); + let mut parts: Vec = Vec::new(); + for t in self.vpn_ticks.borrow().iter() { + if let (Some(start), Some(name)) = (self.sessions.started(&t.key), names.get(&t.key)) { + parts.push(format!("{name} · {}", net::duration_text(now - start))); + } + } + let connecting: HashSet = self + .vpn_live + .borrow() + .iter() + .filter(|(_, l)| matches!(l, Live::Connecting)) + .map(|(u, _)| u.clone()) + .chain(self.vpn_connecting.borrow().iter().cloned()) + .collect(); + for uuid in &connecting { + if let Some(name) = names.get(uuid) { + parts.push(format!("{name} · {}", tr("Connecting…"))); + } + } + drop(names); + self.header_sub.set_visible(!parts.is_empty()); + self.header_sub.set_label(&parts.join(" ")); match self.sessions.started("tailscale") { Some(start) if self.status_is_running() => { self.ts_timer.set_label(&format!("{} {}", tr("Connected for"), net::duration_text(now - start)));