From f99794b2442d1d3fae48f675ca581723a10a9599 Mon Sep 17 00:00:00 2001 From: ranfdev Date: Wed, 25 Oct 2023 18:53:56 +0200 Subject: [PATCH] Add buttons on shell notification --- ntfy-daemon/src/models.rs | 1 + ntfy-daemon/src/system_client.rs | 1 + src/application.rs | 73 +++++++++++++++++++++++++++++++- src/widgets/message_row.rs | 59 +++----------------------- 4 files changed, 80 insertions(+), 54 deletions(-) diff --git a/ntfy-daemon/src/models.rs b/ntfy-daemon/src/models.rs index df17d9c..9424235 100644 --- a/ntfy-daemon/src/models.rs +++ b/ntfy-daemon/src/models.rs @@ -288,6 +288,7 @@ impl From for u8 { pub struct Notification { pub title: String, pub body: String, + pub actions: Vec, } pub trait NotificationProxy: Sync + Send { diff --git a/ntfy-daemon/src/system_client.rs b/ntfy-daemon/src/system_client.rs index d77f3cb..a7278fa 100644 --- a/ntfy-daemon/src/system_client.rs +++ b/ntfy-daemon/src/system_client.rs @@ -97,6 +97,7 @@ impl output_channel::Server for NotifyForwarder { .map(|x| x.as_str()) .unwrap_or("") .to_string(), + actions: msg.actions, }; info!("Showing notification"); diff --git a/src/application.rs b/src/application.rs index aaa92d0..4052469 100644 --- a/src/application.rs +++ b/src/application.rs @@ -10,7 +10,7 @@ use gtk::prelude::*; use gtk::{gdk, gio, glib}; use ntfy_daemon::models; use ntfy_daemon::ntfy_capnp::system_notifier; -use tracing::{debug, info, warn}; +use tracing::{debug, error, info, warn}; use crate::config::{APP_ID, PKGDATADIR, PROFILE, VERSION}; use crate::widgets::*; @@ -126,7 +126,59 @@ impl NotifyApplication { app.show_about_dialog(); }) .build(); - self.add_action_entries([action_quit, action_about]); + + let message_action = gio::ActionEntry::builder("message-action") + .parameter_type(Some(&glib::VariantTy::STRING)) + .activate(|app: &Self, _, params| { + let Some(params) = params else { + return; + }; + let Some(s) = params.str() else { + warn!("action is not a string"); + return; + }; + let Ok(action) = serde_json::from_str(s) else { + error!("invalid action json"); + return; + }; + app.handle_message_action(action); + }) + .build(); + self.add_action_entries([action_quit, action_about, message_action]); + } + + fn handle_message_action(&self, action: models::Action) { + match action { + models::Action::View { url, .. } => { + gtk::UriLauncher::builder().uri(url.clone()).build().launch( + gtk::Window::NONE, + gio::Cancellable::NONE, + |_| {}, + ); + } + models::Action::Http { + method, + url, + body, + headers, + .. + } => { + gio::spawn_blocking(move || { + let mut req = ureq::request(method.as_str(), url.as_str()); + for (k, v) in headers.iter() { + req = req.set(&k, &v); + } + let res = req.send(body.as_bytes()); + match res { + Err(e) => { + error!(error = ?e, "Error sending request"); + } + Ok(_) => {} + } + }); + } + _ => {} + } } // Sets up keyboard shortcuts @@ -192,6 +244,23 @@ impl NotifyApplication { rx.attach(None, move |n: models::Notification| { let gio_notif = gio::Notification::new(&n.title); gio_notif.set_body(Some(&n.body)); + + let action_name = |a| { + let json = serde_json::to_string(a).unwrap(); + gio::Action::print_detailed_name("app.message-action", Some(&json.into())) + }; + for a in n.actions.iter() { + match a { + models::Action::View { label, .. } => { + gio_notif.add_button(&label, &action_name(a)) + } + models::Action::Http { label, .. } => { + gio_notif.add_button(&label, &action_name(a)) + } + _ => {} + } + } + app.send_notification(None, &gio_notif); glib::ControlFlow::Continue }); diff --git a/src/widgets/message_row.rs b/src/widgets/message_row.rs index d81e312..8e83e79 100644 --- a/src/widgets/message_row.rs +++ b/src/widgets/message_row.rs @@ -1,11 +1,8 @@ use adw::prelude::*; use adw::subclass::prelude::*; use chrono::NaiveDateTime; -use gtk::{gio, glib}; +use gtk::glib; use ntfy_daemon::models; -use tracing::error; - -use crate::widgets::*; mod imp { use super::*; @@ -141,62 +138,20 @@ impl MessageRow { } fn build_action_btn(&self, action: models::Action) -> gtk::Button { let btn = gtk::Button::new(); - match action { + match &action { models::Action::View { label, url, .. } => { btn.set_label(&label); btn.set_tooltip_text(Some(&format!("Go to {url}"))); - btn.connect_clicked(move |_| { - gtk::UriLauncher::builder().uri(url.clone()).build().launch( - gtk::Window::NONE, - gio::Cancellable::NONE, - |_| {}, - ); - }); + btn.set_action_name(Some("app.message-action")); + btn.set_action_target_value(Some(&serde_json::to_string(&action).unwrap().into())); } models::Action::Http { - label, - method, - url, - body, - headers, - .. + label, method, url, .. } => { btn.set_label(&label); btn.set_tooltip_text(Some(&format!("Send HTTP {method} to {url}"))); - let (tx, rx) = glib::MainContext::channel(Default::default()); - let this = self.clone(); - btn.connect_clicked({ - let url = url.clone(); - let method = method.clone(); - move |_| { - let url = url.clone(); - let method = method.clone(); - let tx = tx.clone(); - let body = body.clone(); - let headers = headers.clone(); - gio::spawn_blocking(move || { - let mut req = ureq::request(method.as_str(), url.as_str()); - for (k, v) in headers.iter() { - req = req.set(&k, &v); - } - tx.send(req.send(body.as_bytes())).unwrap(); - }); - } - }); - rx.attach(Some(&glib::MainContext::default()), move |res| { - let method = method.clone(); - let url = url.clone(); - this.spawn_with_near_toast(async move { - match res { - Err(e) => { - error!(error = ?e, "Error sending request"); - Err(format!("Error sending HTTP {method} to {url}")) - } - Ok(_) => Ok(()), - } - }); - glib::ControlFlow::Continue - }); + btn.set_action_name(Some("app.message-action")); + btn.set_action_target_value(Some(&serde_json::to_string(&action).unwrap().into())); } models::Action::Broadcast { label, .. } => { btn.set_label(&label);