tmisu

Notification to stdout daemon
git clone https://git.inz.fi/tmisu/
Log | Files | Refs | README | LICENSE

commit f96762c99192b2e3e89243685045f0e1b643bde2
parent 1b874b4f34e4ec9d23ab5e94f55ffe731b10750e
Author: Santtu Lakkala <inz@inz.fi>
Date:   Sat, 26 Feb 2022 13:47:09 +0200

Add support for running commands

Diffstat:
Msrc/tmisu.c | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
1 file changed, 157 insertions(+), 51 deletions(-)

diff --git a/src/tmisu.c b/src/tmisu.c @@ -7,12 +7,18 @@ #include <getopt.h> #include <time.h> #include <sys/poll.h> +#include <sys/wait.h> +#include <fcntl.h> #include <dbus/dbus.h> #include "tmisu.h" #include "output.h" +#define N_IF "org.freedesktop.Notifications" +#define N_SRV N_IF +#define N_PATH "/org/freedesktop/Notifications" + struct conf { enum output_format fmt; const char *delimiter; @@ -23,7 +29,9 @@ struct notification { DBusMessage *msg; dbus_uint32_t id; time_t expiry; - char *title; + const char *title; + const char *action; + pid_t child; }; #define MAX_WATCH 32 @@ -37,6 +45,7 @@ static int default_exp_timeout = 60; static int need_dispatch = 0; int use_json = 0; int changed = 1; +static const char *cmd = NULL; static time_t expirys[MAX_WATCH] = { 0 }; static DBusTimeout *timeouts[MAX_WATCH] = { 0 }; @@ -186,7 +195,12 @@ time_t timeout_next_expiry(void) static struct notification *notifications = NULL; static DBusConnection *connection = NULL; -const struct notification *notif_update(DBusMessage *msg, dbus_uint32_t id, const char *title, time_t expiry) +const struct notification *notif_update(DBusMessage *msg, + dbus_uint32_t id, + const char *title, + const char *body, + const char *action, + time_t expiry) { struct notification *i; @@ -197,28 +211,54 @@ const struct notification *notif_update(DBusMessage *msg, dbus_uint32_t id, cons if (!use_json) changed |= !!strcmp(title, i->title); - free(i->title); - if (use_json) - dbus_message_unref(i->msg); - i->title = strdup(title); + dbus_message_unref(i->msg); + if (cmd && i->child) + kill(i->child, SIGTERM); + + i->title = title; i->expiry = expiry; - if (use_json) - i->msg = dbus_message_ref(msg); + i->msg = dbus_message_ref(msg); + i->action = action; + + if (cmd) { + i->child = fork(); + switch (i->child) { + case -1: + fprintf(stderr, "fork() failed\n"); + i->child = 0; + break; + case 0: + close(STDIN_FILENO); + open("/dev/null", O_RDONLY); + close(STDOUT_FILENO); + open("/dev/null", O_WRONLY); + execlp(cmd, cmd, title, body, NULL); + exit(1); + break; + default: + break; + } + } return i; } -const struct notification *notif_add(DBusMessage *msg, const char *title, time_t expiry) +const struct notification *notif_add(DBusMessage *msg, + const char *title, + const char *body, + const char *action, + time_t expiry) { static dbus_uint32_t notification_id = 0; struct notification *nn = malloc(sizeof(*nn)); struct notification *ne; - nn->id = ++notification_id; - if (use_json) - nn->msg = dbus_message_ref(msg); - nn->title = strdup(title); + if (!(nn->id = ++notification_id)) + nn->id = ++notification_id; + nn->msg = dbus_message_ref(msg); + nn->title = title; nn->expiry = expiry; + nn->action = action; nn->next = NULL; if (notifications) { @@ -228,56 +268,87 @@ const struct notification *notif_add(DBusMessage *msg, const char *title, time_t notifications = nn; } + if (cmd) { + nn->child = fork(); + switch (nn->child) { + case -1: + fprintf(stderr, "fork() failed\n"); + nn->child = 0; + break; + case 0: + close(STDIN_FILENO); + open("/dev/null", O_RDONLY); + close(STDOUT_FILENO); + open("/dev/null", O_WRONLY); + execlp(cmd, cmd, title, body, NULL); + exit(1); + break; + default: + break; + } + } + changed = 1; return nn; } enum reason { - EXPIRED = 1, - DISMISSED = 2, - REQUEST = 3, - UNKNOWN = 4 + ACTION, + EXPIRED, + DISMISSED, + REQUEST, + UNKNOWN, }; -int notif_close(dbus_uint32_t id, dbus_uint32_t reason) +int notif_close(dbus_uint32_t id, pid_t pid, dbus_uint32_t reason) { + DBusMessage *sig; struct notification *n; if (!notifications) return 0; - if (notifications->id == id) { + if (notifications->id == id || notifications->child == pid) { n = notifications; notifications = n->next; - if (use_json) - dbus_message_unref(n->msg); - free(n->title); - free(n); } else { struct notification *i; - for (n = notifications; - n->next && n->next->id != id; n = n->next); - if (!n->next) + for (i = notifications; + i->next && i->next->id != id && i->next->child != pid; + i = i->next); + if (!i->next) return 0; - i = n->next; - n->next = n->next->next; - if (use_json) - dbus_message_unref(i->msg); - free(i->title); - free(i); + n = i->next; + i->next = i->next->next; } - DBusMessage *sig = dbus_message_new_signal("/", - "org.freedesktop.Notifications", - "NotificationClosed"); + if (reason == ACTION) { + if (!n->action) + n->action = ""; + sig = dbus_message_new_signal(N_PATH, N_IF, "ActionInvoked"); + dbus_message_append_args(sig, + DBUS_TYPE_UINT32, &n->id, + DBUS_TYPE_STRING, &n->action, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, sig, NULL); + dbus_message_unref(sig); + reason = DISMISSED; + } + + sig = dbus_message_new_signal(N_PATH, N_IF, "NotificationClosed"); dbus_message_append_args(sig, - DBUS_TYPE_UINT32, &id, + DBUS_TYPE_UINT32, &n->id, DBUS_TYPE_UINT32, &reason, DBUS_TYPE_INVALID); dbus_connection_send(connection, sig, NULL); dbus_message_unref(sig); + dbus_message_unref(n->msg); + if (cmd && n->child && n->child != pid) + kill(n->child, SIGTERM); + free(n); + changed = 1; return 1; @@ -307,7 +378,7 @@ void notif_check_expiry(void) n = i->next; if (i->expiry && i->expiry <= now) - notif_close(i->id, EXPIRED); + notif_close(i->id, -1, EXPIRED); } } @@ -354,7 +425,7 @@ DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *messag (void)user_data; if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { - static const char *notificationpath = "/org/freedesktop/Notifications"; + static const char *notificationpath = N_PATH; const char *path = dbus_message_get_path(message); size_t pl = strlen(path); @@ -380,7 +451,7 @@ DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *messag DBUS_TYPE_STRING, &(const char *){ INTROSPECTION_XML }, DBUS_TYPE_INVALID); } - } else if (dbus_message_is_method_call(message, "org.freedesktop.Notifications", "GetServerInformation")) { + } else if (dbus_message_is_method_call(message, N_IF, "GetServerInformation")) { reply = dbus_message_new_method_return(message); dbus_message_append_args(reply, DBUS_TYPE_STRING, &(const char *){ "tmisu" }, @@ -388,7 +459,7 @@ DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *messag DBUS_TYPE_STRING, &(const char *){ "1.0" }, DBUS_TYPE_STRING, &(const char *){ "1.2" }, DBUS_TYPE_INVALID); - } else if (dbus_message_is_method_call(message, "org.freedesktop.Notifications", "GetCapabilities")) { + } else if (dbus_message_is_method_call(message, N_IF, "GetCapabilities")) { DBusMessageIter i; DBusMessageIter j; @@ -398,10 +469,11 @@ DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *messag dbus_message_iter_append_basic(&j, DBUS_TYPE_STRING, &(const char *){ "body" }); dbus_message_iter_append_basic(&j, DBUS_TYPE_STRING, &(const char *){ "actions" }); dbus_message_iter_close_container(&i, &j); - } else if (dbus_message_is_method_call(message, "org.freedesktop.Notifications", "Notify")) { + } else if (dbus_message_is_method_call(message, N_IF, "Notify")) { const struct notification *n = NULL; struct notification_data d; DBusMessageIter iter; + const char *action = NULL; time_t expiry; if (!dbus_message_has_signature(message, "susssasa{sv}i")) @@ -424,6 +496,10 @@ DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *messag dbus_message_iter_next(&iter); dbus_message_iter_get_basic(&iter, &d.expiry_ms); + if (dbus_message_iter_get_arg_type(&d.actions) == + DBUS_TYPE_STRING) + dbus_message_iter_get_basic(&d.actions, &action); + if (d.expiry_ms < 0) d.expiry_ms = default_exp_timeout * 1000; if (d.expiry_ms) @@ -432,23 +508,32 @@ DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *messag expiry = 0; if (d.replaces) - n = notif_update(message, d.replaces, d.summary, expiry); + n = notif_update(message, + d.replaces, + d.summary, + d.body, + action, + expiry); if (!n) - n = notif_add(message, d.summary, expiry); + n = notif_add(message, + d.summary, + d.body, + action, + expiry); reply = dbus_message_new_method_return(message); // output_notification(message, n->id, cnf->fmt, cnf->delimiter); dbus_message_append_args(reply, DBUS_TYPE_UINT32, &n->id, DBUS_TYPE_INVALID); - } else if (dbus_message_is_method_call(message, "org.freedesktop.Notifications", "CloseNotification")) { + } else if (dbus_message_is_method_call(message, N_IF, "CloseNotification")) { if (!dbus_message_has_signature(message, "u")) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; dbus_uint32_t id; dbus_message_get_args(message, NULL, DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID); - if (notif_close(id, REQUEST)) + if (notif_close(id, -1, REQUEST)) reply = dbus_message_new_method_return(message); else reply = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, "Notification not found"); @@ -473,7 +558,7 @@ void sig_handler(int signal) int main(int argc, char **argv) { char argument; - while ((argument = getopt(argc, argv, "hjd:")) >= 0) { + while ((argument = getopt(argc, argv, "hjad:c:")) >= 0) { switch (argument) { case 'd': delimiter = optarg; @@ -489,6 +574,9 @@ int main(int argc, char **argv) { case 'j': use_json = 1; break; + case 'c': + cmd = optarg; + break; default: break; } @@ -522,17 +610,22 @@ int main(int argc, char **argv) { return 1; } - int result = dbus_bus_request_name(connection, "org.freedesktop.Notifications", DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE, NULL); + int result = dbus_bus_request_name(connection, + N_SRV, + DBUS_NAME_FLAG_REPLACE_EXISTING | + DBUS_NAME_FLAG_DO_NOT_QUEUE, NULL); if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { dbus_connection_close(connection); dbus_connection_unref(connection); - fprintf(stderr, "Could not acquire service name, is another notification daemon running?\n"); + fprintf(stderr, + "Could not acquire service name, " + "is another notification daemon running?\n"); return 1; } - dbus_bus_add_match(connection, "interface=org.freedesktop.Notifications,path=/org/freedesktop/Notifications,type=method_call", NULL); + dbus_bus_add_match(connection, "interface=" N_IF " ,path=" N_PATH ",type=method_call", NULL); dbus_bus_add_match(connection, "interface=org.freedesktop.DBus.Introspectable,method=Introspect,type=method_call", NULL); dbus_connection_add_filter(connection, handle_message, NULL, NULL); @@ -540,6 +633,7 @@ int main(int argc, char **argv) { signal(SIGTERM, sig_handler); signal(SIGUSR1, sig_handler); signal(SIGUSR2, sig_handler); + signal(SIGCHLD, sig_handler); pfdb[0].fd = sfd[0]; pfdb[0].events = POLLIN; @@ -585,10 +679,22 @@ int main(int argc, char **argv) { break; if (s == SIGUSR1) { if (notifications) - notif_close(notifications->id, DISMISSED); + notif_close(notifications->id, -1, DISMISSED); } else if (s == SIGUSR2) { while (notifications) - notif_close(notifications->id, DISMISSED); + notif_close(notifications->id, -1, DISMISSED); + } else if (s == SIGCHLD) { + int cstatus; + pid_t pid = waitpid(-1, &cstatus, WNOHANG); + + if (pid < 0) + fprintf(stderr, "waitpid() failed\n"); + else if (!pid) + fprintf(stderr, "spurious SIGCHLD\n"); + else if (WIFEXITED(cstatus) && WEXITSTATUS(cstatus) == 0) + notif_close(0, pid, ACTION); + else + notif_close(0, pid, DISMISSED); } signal(s, sig_handler); }