Add buttons on shell notification
This commit is contained in:
@ -288,6 +288,7 @@ impl From<Status> for u8 {
|
|||||||
pub struct Notification {
|
pub struct Notification {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
|
pub actions: Vec<Action>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NotificationProxy: Sync + Send {
|
pub trait NotificationProxy: Sync + Send {
|
||||||
|
|||||||
@ -97,6 +97,7 @@ impl output_channel::Server for NotifyForwarder {
|
|||||||
.map(|x| x.as_str())
|
.map(|x| x.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
actions: msg.actions,
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Showing notification");
|
info!("Showing notification");
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use gtk::prelude::*;
|
|||||||
use gtk::{gdk, gio, glib};
|
use gtk::{gdk, gio, glib};
|
||||||
use ntfy_daemon::models;
|
use ntfy_daemon::models;
|
||||||
use ntfy_daemon::ntfy_capnp::system_notifier;
|
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::config::{APP_ID, PKGDATADIR, PROFILE, VERSION};
|
||||||
use crate::widgets::*;
|
use crate::widgets::*;
|
||||||
@ -126,7 +126,59 @@ impl NotifyApplication {
|
|||||||
app.show_about_dialog();
|
app.show_about_dialog();
|
||||||
})
|
})
|
||||||
.build();
|
.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
|
// Sets up keyboard shortcuts
|
||||||
@ -192,6 +244,23 @@ impl NotifyApplication {
|
|||||||
rx.attach(None, move |n: models::Notification| {
|
rx.attach(None, move |n: models::Notification| {
|
||||||
let gio_notif = gio::Notification::new(&n.title);
|
let gio_notif = gio::Notification::new(&n.title);
|
||||||
gio_notif.set_body(Some(&n.body));
|
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);
|
app.send_notification(None, &gio_notif);
|
||||||
glib::ControlFlow::Continue
|
glib::ControlFlow::Continue
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use gtk::{gio, glib};
|
use gtk::glib;
|
||||||
use ntfy_daemon::models;
|
use ntfy_daemon::models;
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
use crate::widgets::*;
|
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -141,62 +138,20 @@ impl MessageRow {
|
|||||||
}
|
}
|
||||||
fn build_action_btn(&self, action: models::Action) -> gtk::Button {
|
fn build_action_btn(&self, action: models::Action) -> gtk::Button {
|
||||||
let btn = gtk::Button::new();
|
let btn = gtk::Button::new();
|
||||||
match action {
|
match &action {
|
||||||
models::Action::View { label, url, .. } => {
|
models::Action::View { label, url, .. } => {
|
||||||
btn.set_label(&label);
|
btn.set_label(&label);
|
||||||
btn.set_tooltip_text(Some(&format!("Go to {url}")));
|
btn.set_tooltip_text(Some(&format!("Go to {url}")));
|
||||||
btn.connect_clicked(move |_| {
|
btn.set_action_name(Some("app.message-action"));
|
||||||
gtk::UriLauncher::builder().uri(url.clone()).build().launch(
|
btn.set_action_target_value(Some(&serde_json::to_string(&action).unwrap().into()));
|
||||||
gtk::Window::NONE,
|
|
||||||
gio::Cancellable::NONE,
|
|
||||||
|_| {},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
models::Action::Http {
|
models::Action::Http {
|
||||||
label,
|
label, method, url, ..
|
||||||
method,
|
|
||||||
url,
|
|
||||||
body,
|
|
||||||
headers,
|
|
||||||
..
|
|
||||||
} => {
|
} => {
|
||||||
btn.set_label(&label);
|
btn.set_label(&label);
|
||||||
btn.set_tooltip_text(Some(&format!("Send HTTP {method} to {url}")));
|
btn.set_tooltip_text(Some(&format!("Send HTTP {method} to {url}")));
|
||||||
let (tx, rx) = glib::MainContext::channel(Default::default());
|
btn.set_action_name(Some("app.message-action"));
|
||||||
let this = self.clone();
|
btn.set_action_target_value(Some(&serde_json::to_string(&action).unwrap().into()));
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
models::Action::Broadcast { label, .. } => {
|
models::Action::Broadcast { label, .. } => {
|
||||||
btn.set_label(&label);
|
btn.set_label(&label);
|
||||||
|
|||||||
Reference in New Issue
Block a user