diff --git a/desktop/tauri/src-tauri/Cargo.lock b/desktop/tauri/src-tauri/Cargo.lock index 603a1709..0cc0e0db 100644 --- a/desktop/tauri/src-tauri/Cargo.lock +++ b/desktop/tauri/src-tauri/Cargo.lock @@ -210,6 +210,7 @@ dependencies = [ "tauri-plugin-os", "tauri-plugin-shell", "tauri-plugin-single-instance", + "tauri-winrt-notification 0.3.0", "thiserror", "tokio", "tokio-websockets", @@ -4158,7 +4159,7 @@ dependencies = [ "log", "mac-notification-sys", "serde", - "tauri-winrt-notification", + "tauri-winrt-notification 0.2.1", "zbus", ] @@ -6964,7 +6965,7 @@ dependencies = [ "serde_repr", "tauri", "tauri-plugin", - "tauri-winrt-notification", + "tauri-winrt-notification 0.2.1", "thiserror", "time", "url", @@ -7158,6 +7159,17 @@ dependencies = [ "windows-version", ] +[[package]] +name = "tauri-winrt-notification" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13966ea9e4bd4a3b86c332a93b70cc129a950e31c5f2212014c7ee5ebd110884" +dependencies = [ + "quick-xml", + "windows 0.56.0", + "windows-version", +] + [[package]] name = "tempfile" version = "3.10.1" diff --git a/desktop/tauri/src-tauri/Cargo.toml b/desktop/tauri/src-tauri/Cargo.toml index cfe0010a..8af22c69 100644 --- a/desktop/tauri/src-tauri/Cargo.toml +++ b/desktop/tauri/src-tauri/Cargo.toml @@ -61,6 +61,7 @@ gio-sys = "0.18.1" [target.'cfg(target_os = "windows")'.dependencies] windows-service = "0.6.0" windows = { version = "0.54.0", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } +tauri-winrt-notification = "0.3.0" [dev-dependencies] which = "6.0.0" diff --git a/desktop/tauri/src-tauri/src/portmaster/notifications.rs b/desktop/tauri/src-tauri/src/portmaster/notifications.rs index 88472639..bf851230 100644 --- a/desktop/tauri/src-tauri/src/portmaster/notifications.rs +++ b/desktop/tauri/src-tauri/src/portmaster/notifications.rs @@ -3,9 +3,7 @@ use crate::portapi::message::*; use crate::portapi::models::notification::*; use crate::portapi::types::*; use log::error; -use notify_rust; use serde_json::json; -#[allow(unused_imports)] use tauri::async_runtime; pub async fn notification_handler(cli: PortAPI) { @@ -34,59 +32,7 @@ pub async fn notification_handler(cli: PortAPI) { if n.selected_action_id != "" { return; } - - // TODO(ppacher): keep a reference of open notifications and close them - // if the user reacted inside the UI: - - let mut notif = notify_rust::Notification::new(); - notif.body(&n.message); - notif.timeout(notify_rust::Timeout::Never); // TODO(ppacher): use n.expires to calculate the timeout. - notif.summary(&n.title); - notif.icon("portmaster"); - - for action in n.actions { - notif.action(&action.id, &action.text); - } - - #[cfg(target_os = "linux")] - { - let cli_clone = cli.clone(); - async_runtime::spawn(async move { - let res = notif.show(); - match res { - Ok(handle) => { - handle.wait_for_action(|action| { - match action { - "__closed" => { - // timeout - } - - value => { - let value = value.to_string().clone(); - - async_runtime::spawn(async move { - let _ = cli_clone - .request(Request::Update( - key, - Payload::JSON( - json!({ - "SelectedActionID": value - }) - .to_string(), - ), - )) - .await; - }); - } - } - }) - } - Err(err) => { - error!("failed to display notification: {}", err); - } - } - }); - } + show_notification(&cli, key, n).await; } Err(err) => match err { ParseError::JSON(err) => { @@ -101,3 +47,99 @@ pub async fn notification_handler(cli: PortAPI) { } } } + +#[cfg(target_os = "linux")] +pub async fn show_notification(cli: &PortAPI, key: String, n: Notification) { + let mut notif = notify_rust::Notification::new(); + notif.body(&n.message); + notif.timeout(notify_rust::Timeout::Never); // TODO(ppacher): use n.expires to calculate the timeout. + notif.summary(&n.title); + notif.icon("portmaster"); + + for action in n.actions { + notif.action(&action.id, &action.text); + } + + { + let cli_clone = cli.clone(); + async_runtime::spawn(async move { + let res = notif.show(); + // TODO(ppacher): keep a reference of open notifications and close them + // if the user reacted inside the UI: + match res { + Ok(handle) => { + handle.wait_for_action(|action| { + match action { + "__closed" => { + // timeout + } + + value => { + let value = value.to_string().clone(); + + async_runtime::spawn(async move { + let _ = cli_clone + .request(Request::Update( + key, + Payload::JSON( + json!({ + "SelectedActionID": value + }) + .to_string(), + ), + )) + .await; + }); + } + } + }) + } + Err(err) => { + error!("failed to display notification: {}", err); + } + } + }); + } +} + +#[cfg(target_os = "windows")] +pub async fn show_notification(cli: &PortAPI, key: String, n: Notification) { + use tauri_winrt_notification::{Duration, Sound, Toast}; + + let mut toast = Toast::new("io.safing.portmaster") + .title(&n.title) + .text1(&n.message) + .sound(Some(Sound::Default)) + .duration(Duration::Long); + + for action in n.actions { + toast = toast.add_button(&action.text, &action.id); + } + { + let cli = cli.clone(); + toast = toast.on_activated(move |action| -> windows::core::Result<()> { + if let Some(value) = action { + let cli = cli.clone(); + let key = key.clone(); + async_runtime::spawn(async move { + let _ = cli + .request(Request::Update( + key, + Payload::JSON( + json!({ + "SelectedActionID": value + }) + .to_string(), + ), + )) + .await; + }); + } + // TODO(vladimir): If Action is None, the user clicked on the notification. Focus on the UI. + Ok(()) + }); + } + toast.show().expect("unable to send notification"); + // TODO(vladimir): keep a reference of open notifications and close them + // if the user reacted inside the UI: +}