From 6b414bebc9c23ea54dfcedcf6ad870054a3fead1 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sun, 13 Dec 2020 00:17:10 -0800 Subject: [PATCH] tweak updating checking * Allow injecting some initial output to new panes * Have the update checker set this new-pane-banner to a short upsell to let the user know there is an update. * Refactor toast notifications into their own crate * Have the update checker call a new stub function that triggers a toast notification with an URL... but it does nothing because the rust ecosystem doesn't support this on macos yet and I'm writing this code there --- Cargo.lock | 11 ++++- mux/src/lib.rs | 15 ++++++- wezterm-gui/Cargo.toml | 6 +-- wezterm-gui/src/gui/mod.rs | 1 + wezterm-gui/src/main.rs | 44 +------------------ wezterm-gui/src/update.rs | 63 ++++++++++++++++++++++++++- wezterm-toast-notification/Cargo.toml | 16 +++++++ wezterm-toast-notification/src/lib.rs | 50 +++++++++++++++++++++ 8 files changed, 153 insertions(+), 53 deletions(-) create mode 100644 wezterm-toast-notification/Cargo.toml create mode 100644 wezterm-toast-notification/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 73b96cc17..3c8302beb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4494,7 +4494,6 @@ dependencies = [ "metrics", "mlua", "mux", - "notify-rust", "open", "openssl", "ordered-float", @@ -4527,9 +4526,9 @@ dependencies = [ "wezterm-font", "wezterm-gui-subcommands", "wezterm-term", + "wezterm-toast-notification", "winapi 0.3.9", "window", - "winrt-notification", ] [[package]] @@ -4602,6 +4601,14 @@ dependencies = [ "url", ] +[[package]] +name = "wezterm-toast-notification" +version = "0.1.0" +dependencies = [ + "notify-rust", + "winrt-notification", +] + [[package]] name = "winapi" version = "0.2.8" diff --git a/mux/src/lib.rs b/mux/src/lib.rs index 8f37ff209..8d9dcd50c 100644 --- a/mux/src/lib.rs +++ b/mux/src/lib.rs @@ -45,6 +45,7 @@ pub struct Mux { domains: RefCell>>, domains_by_name: RefCell>>, subscribers: RefCell bool>>>, + banner: RefCell>, } /// This function bounces the data over to the main thread to feed to @@ -121,7 +122,7 @@ fn accumulator(pane_id: PaneId, dead: &Arc, rx: Receiver>) { /// blocking reads from the pty (non-blocking reads are not portable to /// all platforms and pty/tty types) and relay the data to the `accumulator` /// function above that this function spawns a new thread. -fn read_from_pane_pty(pane_id: PaneId, mut reader: Box) { +fn read_from_pane_pty(pane_id: PaneId, banner: Option, mut reader: Box) { const BUFSIZE: usize = 4 * 1024; let mut buf = [0; BUFSIZE]; @@ -138,6 +139,10 @@ fn read_from_pane_pty(pane_id: PaneId, mut reader: Box) { } }); + if let Some(banner) = banner { + tx.send(banner.into_bytes()).ok(); + } + while !dead.load(Ordering::Relaxed) { match reader.read(&mut buf) { Ok(size) if size == 0 => { @@ -232,6 +237,7 @@ impl Mux { domains_by_name: RefCell::new(domains_by_name), domains: RefCell::new(domains), subscribers: RefCell::new(HashMap::new()), + banner: RefCell::new(None), } } @@ -316,7 +322,8 @@ impl Mux { .insert(pane.pane_id(), Rc::clone(pane)); let reader = pane.reader()?; let pane_id = pane.pane_id(); - thread::spawn(move || read_from_pane_pty(pane_id, reader)); + let banner = self.banner.borrow().clone(); + thread::spawn(move || read_from_pane_pty(pane_id, banner, reader)); Ok(()) } @@ -526,6 +533,10 @@ impl Mux { self.prune_dead_windows(); } + + pub fn set_banner(&self, banner: Option) { + *self.banner.borrow_mut() = banner; + } } #[derive(Debug, Error)] diff --git a/wezterm-gui/Cargo.toml b/wezterm-gui/Cargo.toml index c7110bd00..a6f0d8f42 100644 --- a/wezterm-gui/Cargo.toml +++ b/wezterm-gui/Cargo.toml @@ -64,12 +64,9 @@ wezterm-client = { path = "../wezterm-client" } wezterm-font = { path = "../wezterm-font" } wezterm-gui-subcommands = { path = "../wezterm-gui-subcommands" } wezterm-term = { path = "../term", features=["use_serde"] } +wezterm-toast-notification = { path = "../wezterm-toast-notification" } window = { path = "../window", features=["opengl", "wayland"]} -[target.'cfg(not(windows))'.dependencies] -# show a notification -notify-rust = "4" - [target."cfg(windows)".dependencies] shared_library = "0.1" uds_windows = "0.1" @@ -82,7 +79,6 @@ winapi = { version = "0.3", features = [ "synchapi", "winsock2", ]} -winrt-notification = "0.2" [features] default = ["vendor_openssl"] diff --git a/wezterm-gui/src/gui/mod.rs b/wezterm-gui/src/gui/mod.rs index 2ef2862bd..0390a89da 100644 --- a/wezterm-gui/src/gui/mod.rs +++ b/wezterm-gui/src/gui/mod.rs @@ -19,6 +19,7 @@ mod utilsprites; pub use selection::SelectionMode; pub use termwindow::set_window_class; pub use termwindow::TermWindow; +pub use termwindow::ICON_DATA; pub struct GuiFrontEnd { connection: Rc, diff --git a/wezterm-gui/src/main.rs b/wezterm-gui/src/main.rs index 4066ed626..f4447075f 100644 --- a/wezterm-gui/src/main.rs +++ b/wezterm-gui/src/main.rs @@ -13,6 +13,7 @@ use std::sync::Arc; use structopt::StructOpt; use wezterm_client::domain::{ClientDomain, ClientDomainConfig}; use wezterm_gui_subcommands::*; +use wezterm_toast_notification::*; mod gui; mod markdown; @@ -295,49 +296,8 @@ fn run_terminal_gui(config: config::ConfigHandle, opts: StartCommand) -> anyhow: gui.run_forever() } -fn toast_notification(title: &str, message: &str) { - #[cfg(not(windows))] - { - #[allow(unused_mut)] - let mut notif = notify_rust::Notification::new(); - notif.summary(title).body(message); - - #[cfg(not(target_os = "macos"))] - { - // Stay on the screen until dismissed - notif.hint(notify_rust::Hint::Resident(true)); - } - - notif - // timeout isn't respected on macos - .timeout(0) - .show() - .ok(); - } - - #[cfg(windows)] - { - let title = title.to_owned(); - let message = message.to_owned(); - - // We need to be in a different thread from the caller - // in case we get called in the guts of a windows message - // loop dispatch and are unable to pump messages - std::thread::spawn(move || { - use winrt_notification::Toast; - - Toast::new(Toast::POWERSHELL_APP_ID) - .title(&title) - .text1(&message) - .duration(winrt_notification::Duration::Long) - .show() - .ok(); - }); - } -} - fn fatal_toast_notification(title: &str, message: &str) { - toast_notification(title, message); + persistent_toast_notification(title, message); // We need a short delay otherwise the notification // will not show #[cfg(windows)] diff --git a/wezterm-gui/src/update.rs b/wezterm-gui/src/update.rs index 14afc8f23..b357e102c 100644 --- a/wezterm-gui/src/update.rs +++ b/wezterm-gui/src/update.rs @@ -1,3 +1,4 @@ +use crate::gui::ICON_DATA; use anyhow::anyhow; use config::configuration; use config::wezterm_version; @@ -13,7 +14,11 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; use termwiz::cell::{AttributeChange, Hyperlink, Underline}; +use termwiz::escape::csi::{Cursor, Sgr}; +use termwiz::escape::osc::{ITermDimension, ITermFileData, ITermProprietary}; +use termwiz::escape::{OneBased, OperatingSystemCommand, CSI}; use termwiz::surface::{Change, CursorVisibility}; +use wezterm_toast_notification::*; #[derive(Debug, Deserialize, Clone)] pub struct Release { @@ -198,7 +203,7 @@ fn show_update_available(release: Release) { Change::Attribute(AttributeChange::Hyperlink(Some(Arc::new(Hyperlink::new( install, ))))), - format!("Version {} is now available!\r\n", release.tag_name).into(), + format!("\r\nVersion {} is now available!\r\n", release.tag_name).into(), Change::Attribute(AttributeChange::Hyperlink(None)), Change::Attribute(AttributeChange::Underline(Underline::None)), format!("(this is version {})\r\n", wezterm_version()).into(), @@ -276,7 +281,61 @@ fn update_checker() { if let Ok(latest) = get_latest_release_info() { let current = wezterm_version(); if latest.tag_name.as_str() > current || force_ui { - log::trace!( + let url = format!( + "https://wezfurlong.org/wezterm/changelog.html#{}", + latest.tag_name + ); + + promise::spawn::spawn_into_main_thread({ + let url = url.clone(); + async move { + let mux = crate::Mux::get().unwrap(); + let icon = ITermFileData { + name: None, + size: Some(ICON_DATA.len()), + width: ITermDimension::Automatic, + height: ITermDimension::Cells(2), + preserve_aspect_ratio: true, + inline: true, + data: ICON_DATA.to_vec(), + }; + let icon = OperatingSystemCommand::ITermProprietary( + ITermProprietary::File(Box::new(icon)), + ); + let top_line_pos = CSI::Cursor(Cursor::CharacterAndLinePosition { + line: OneBased::new(1), + col: OneBased::new(6), + }); + let second_line_pos = CSI::Cursor(Cursor::CharacterAndLinePosition { + line: OneBased::new(2), + col: OneBased::new(6), + }); + let link_on = + OperatingSystemCommand::SetHyperlink(Some(Hyperlink::new(url))); + let underline_on = CSI::Sgr(Sgr::Underline(Underline::Single)); + let underline_off = CSI::Sgr(Sgr::Underline(Underline::None)); + let link_off = OperatingSystemCommand::SetHyperlink(None); + mux.set_banner(Some(format!( + "{}{}WezTerm Update Available\r\n{}{}{}Click to see what's new{}{}\r\n", + icon, + top_line_pos, + second_line_pos, + link_on, + underline_on, + underline_off, + link_off, + ))); + } + }) + .detach(); + + persistent_toast_notification_with_click_to_open_url( + "WezTerm Update Available", + "Click to see what's new", + &url, + ); + + log::info!( "latest release {} is newer than current build {}", latest.tag_name, current diff --git a/wezterm-toast-notification/Cargo.toml b/wezterm-toast-notification/Cargo.toml new file mode 100644 index 000000000..6b3f79352 --- /dev/null +++ b/wezterm-toast-notification/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "wezterm-toast-notification" +version = "0.1.0" +authors = ["Wez Furlong "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[target.'cfg(not(windows))'.dependencies] +# show a notification +notify-rust = "4" + +[target.'cfg(windows)'.dependencies] +winrt-notification = "0.2" diff --git a/wezterm-toast-notification/src/lib.rs b/wezterm-toast-notification/src/lib.rs new file mode 100644 index 000000000..c0a7139f5 --- /dev/null +++ b/wezterm-toast-notification/src/lib.rs @@ -0,0 +1,50 @@ +pub fn persistent_toast_notification_with_click_to_open_url( + _title: &str, + _message: &str, + _url: &str, +) { + // Do nothing for now; none of the rust-accesible toast + // notifications let us manage clicking on the notification + // persistent_toast_notification(title, message); +} + +pub fn persistent_toast_notification(title: &str, message: &str) { + #[cfg(not(windows))] + { + #[allow(unused_mut)] + let mut notif = notify_rust::Notification::new(); + notif.summary(title).body(message); + + #[cfg(not(target_os = "macos"))] + { + // Stay on the screen until dismissed + notif.hint(notify_rust::Hint::Resident(true)); + } + + notif + // timeout isn't respected on macos + .timeout(0) + .show() + .ok(); + } + + #[cfg(windows)] + { + let title = title.to_owned(); + let message = message.to_owned(); + + // We need to be in a different thread from the caller + // in case we get called in the guts of a windows message + // loop dispatch and are unable to pump messages + std::thread::spawn(move || { + use winrt_notification::Toast; + + Toast::new(Toast::POWERSHELL_APP_ID) + .title(&title) + .text1(&message) + .duration(winrt_notification::Duration::Long) + .show() + .ok(); + }); + } +}