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:
M | src/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);
}