diff --git a/CHANGELOG.md b/CHANGELOG.md index 65ddfc3b..3394c0ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,9 @@ `notify-focus-inhibit`. * `${icon}`, `${urgency}`,`${action-name}` and `${action-label}` added to the `desktop-notifications.command` template. +* `desktop-notifications.close` option, defining what to execute when + an application wants to close an existing notification (via an + OSC-99 escape sequence). [1707]: https://codeberg.org/dnkl/foot/issues/1707 [1738]: https://codeberg.org/dnkl/foot/issues/1738 diff --git a/config.c b/config.c index 664474ca..409469ef 100644 --- a/config.c +++ b/config.c @@ -1110,6 +1110,9 @@ parse_section_desktop_notifications(struct context *ctx) if (streq(key, "command")) return value_to_spawn_template( ctx, &conf->desktop_notifications.command); + else if (streq(key, "close")) + return value_to_spawn_template( + ctx, &conf->desktop_notifications.close); else if (streq(key, "inhibit-when-focused")) return value_to_bool( ctx, &conf->desktop_notifications.inhibit_when_focused); @@ -3186,6 +3189,9 @@ config_load(struct config *conf, const char *conf_path, .command = { .argv = {.args = NULL}, }, + .close = { + .argv = {.args = NULL}, + }, .inhibit_when_focused = true, }, @@ -3481,6 +3487,8 @@ config_clone(const struct config *old) spawn_template_clone(&conf->bell.command, &old->bell.command); spawn_template_clone(&conf->desktop_notifications.command, &old->desktop_notifications.command); + spawn_template_clone(&conf->desktop_notifications.close, + &old->desktop_notifications.close); for (size_t i = 0; i < ALEN(conf->fonts); i++) config_font_list_clone(&conf->fonts[i], &old->fonts[i]); @@ -3563,6 +3571,7 @@ config_free(struct config *conf) spawn_template_free(&conf->bell.command); free(conf->scrollback.indicator.text); spawn_template_free(&conf->desktop_notifications.command); + spawn_template_free(&conf->desktop_notifications.close); for (size_t i = 0; i < ALEN(conf->fonts); i++) config_font_list_destroy(&conf->fonts[i]); free(conf->server_socket_path); diff --git a/config.h b/config.h index b3688f28..20fea3fc 100644 --- a/config.h +++ b/config.h @@ -340,6 +340,7 @@ struct config { struct { struct config_spawn_template command; + struct config_spawn_template close; bool inhibit_when_focused; } desktop_notifications; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 72e6d052..195baafe 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -513,6 +513,18 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35 Default: _notify-send --wait --app-name ${app-id} --icon ${icon} --urgency ${urgency} --action ${action-name}=${action-label} --print-id -- ${title} ${body}_. +*close* + Command to execute to close an existing notification. + + _${id}_ is expanded to the ID of the notification that should be + closed. For example: + + fyi --close ${id} + + Closing a notification is only supported by the Kitty Desktop + Notification protocol, OSC-99. + + Default: _not set_ *inhibit-when-focused* Boolean. If enabled, foot will not display notifications if the diff --git a/foot.ini b/foot.ini index 46b2d5f0..cead651a 100644 --- a/foot.ini +++ b/foot.ini @@ -48,6 +48,7 @@ [desktop-notifications] # command=notify-send --wait --app-name ${app-id} --icon ${icon} --urgency ${urgency} --action ${action-name}=${action-label} --print-id -- ${title} ${body} +# close="" # inhibit-when-focused=yes diff --git a/notify.c b/notify.c index 68ba83f3..fa547126 100644 --- a/notify.c +++ b/notify.c @@ -339,6 +339,51 @@ notify_notify(struct terminal *term, struct notification *notif) return true; } +void +notify_close(struct terminal *term, const char *id) +{ + LOG_DBG("close notification %s", id); + + if (term->conf->desktop_notifications.close.argv.args == NULL) + return; + + tll_foreach(term->active_notifications, it) { + const struct notification *notif = &it->item; + if (notif->id == 0 || !streq(notif->id, id)) + continue; + + if (notif->external_id == 0) + return; + + char **argv = NULL; + size_t argc = 0; + + char external_id[16]; + xsnprintf(external_id, sizeof(external_id), "%u", notif->external_id); + + if (!spawn_expand_template( + &term->conf->desktop_notifications.close, 1, + (const char *[]){"id"}, + (const char *[]){external_id}, + &argc, &argv)) + { + return; + } + + int devnull = open("/dev/null", O_RDONLY); + spawn( + term->reaper, NULL, argv, devnull, -1, -1, + NULL, (void *)term, NULL); + + if (devnull >= 0) + close(devnull); + + for (size_t i = 0; i < argc; i++) + free(argv[i]); + free(argv); + } +} + static void add_icon(struct notification_icon *icon, const char *id, const char *symbolic_name, const uint8_t *data, size_t data_sz) diff --git a/notify.h b/notify.h index e19c4fcd..a20c2e51 100644 --- a/notify.h +++ b/notify.h @@ -65,6 +65,7 @@ struct notification_icon { }; bool notify_notify(struct terminal *term, struct notification *notif); +void notify_close(struct terminal *term, const char *id); void notify_free(struct terminal *term, struct notification *notif); void notify_icon_add(struct terminal *term, const char *id, diff --git a/osc.c b/osc.c index 1da32447..2eaed869 100644 --- a/osc.c +++ b/osc.c @@ -590,6 +590,7 @@ kitty_notification(struct terminal *term, char *string) enum { PAYLOAD_TITLE, PAYLOAD_BODY, + PAYLOAD_CLOSE, PAYLOAD_ICON, } payload_type = PAYLOAD_TITLE; /* The 'p' parameter */ @@ -672,6 +673,8 @@ kitty_notification(struct terminal *term, char *string) payload_type = PAYLOAD_TITLE; else if (streq(value, "body")) payload_type = PAYLOAD_BODY; + else if (streq(value, "close")) + payload_type = PAYLOAD_CLOSE; else if (streq(value, "icon")) payload_type = PAYLOAD_ICON; else if (streq(value, "?")) { @@ -687,7 +690,7 @@ kitty_notification(struct terminal *term, char *string) char reply[128]; int n = xsnprintf( reply, sizeof(reply), - "\033]99;i=%s:p=?;p=title,body,icon:a=focus,report:o=%s:u=0,1,2:c=1%s", + "\033]99;i=%s:p=?;p=title,body,close,icon:a=focus,report:o=%s:u=0,1,2:c=1%s", id, when_str, terminator); term_to_slave(term, reply, n); @@ -815,6 +818,10 @@ kitty_notification(struct terminal *term, char *string) break; } + case PAYLOAD_CLOSE: + /* Ignore payload */ + break; + case PAYLOAD_ICON: if (notif->icon_data == NULL) { notif->icon_data = (uint8_t *)payload; @@ -847,14 +854,18 @@ kitty_notification(struct terminal *term, char *string) notif->icon_data_sz = 0; } - /* - * Show notification. - * - * The checks for title|body is to handle notifications that - * only load icon data into the icon cache - */ - if (notif->title != NULL || notif->body != NULL) { - notify_notify(term, notif); + if (payload_type == PAYLOAD_CLOSE) { + notify_close(term, notif->id); + } else { + /* + * Show notification. + * + * The checks for title|body is to handle notifications that + * only load icon data into the icon cache + */ + if (notif->title != NULL || notif->body != NULL) { + notify_notify(term, notif); + } } tll_foreach(term->kitty_notifications, it) {