[desktop] support for windows notifications

This commit is contained in:
Vladimir Stoilov 2024-05-17 11:11:48 +03:00
parent ff488351e4
commit 99b84d3f46
No known key found for this signature in database
GPG key ID: 2F190B67A43A81AF
3 changed files with 112 additions and 57 deletions
desktop/tauri/src-tauri

View file

@ -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"

View file

@ -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"

View file

@ -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:
}