tmisu

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

tmisu.c (17407B)


      1 #define _POSIX_C_SOURCE 200809L
      2 #include <stdio.h>
      3 #include <string.h>
      4 #include <signal.h>
      5 #include <stdarg.h>
      6 #include <unistd.h>
      7 #include <stdlib.h>
      8 #include <time.h>
      9 #include <sys/poll.h>
     10 #include <sys/wait.h>
     11 #include <fcntl.h>
     12 #include <errno.h>
     13 
     14 #include <dbus/dbus.h>
     15 
     16 #include "tmisu.h"
     17 #include "output.h"
     18 #include "arg.h"
     19 
     20 #define N_IF "org.freedesktop.Notifications"
     21 #define N_SRV N_IF
     22 #define N_PATH "/org/freedesktop/Notifications"
     23 
     24 char *argv0;
     25 
     26 struct conf {
     27 	enum output_format fmt;
     28 	const char *delimiter;
     29 };
     30 
     31 struct notification {
     32 	struct notification *next;
     33 	struct notification_data d;
     34 	DBusMessage *msg;
     35 	dbus_uint32_t id;
     36 	time_t expiry;
     37 	pid_t child;
     38 };
     39 
     40 #define MAX_WATCH 32
     41 #define N(x) (sizeof(x) / sizeof(*(x)))
     42 static struct pollfd pfdb[MAX_WATCH + 1] = { 0 };
     43 static struct pollfd *pfds = pfdb + 1;
     44 static DBusWatch *watches[MAX_WATCH] = { 0 };
     45 static size_t nw = 1;
     46 static const char *delimiter = ", ";
     47 static int default_exp_timeout = 60;
     48 static int need_dispatch = 0;
     49 static struct notification *notifications = NULL;
     50 static DBusConnection *connection = NULL;
     51 
     52 int use_json = 0;
     53 static int changed = 1;
     54 static const char *cmd = NULL;
     55 
     56 static time_t expirys[MAX_WATCH] = { 0 };
     57 static DBusTimeout *timeouts[MAX_WATCH] = { 0 };
     58 static size_t nt = 0;
     59 
     60 static void groan(const char *fmt, ...);
     61 static void die(const char *fmt, ...);
     62 
     63 static short poll_flags(unsigned int dbf)
     64 {
     65 	return (dbf & DBUS_WATCH_READABLE ? POLLIN : 0) |
     66 		(dbf & DBUS_WATCH_WRITABLE ? POLLOUT : 0) |
     67 		(dbf & DBUS_WATCH_ERROR ? POLLERR : 0) |
     68 		(dbf & DBUS_WATCH_HANGUP ? POLLHUP : 0);
     69 }
     70 
     71 static unsigned int dw_flags(short pf)
     72 {
     73 	return (pf & POLLIN ? DBUS_WATCH_READABLE : 0) |
     74 		(pf & POLLOUT ? DBUS_WATCH_WRITABLE : 0) |
     75 		(pf & POLLERR ? DBUS_WATCH_ERROR : 0) |
     76 		(pf & POLLHUP ? DBUS_WATCH_HANGUP : 0);
     77 }
     78 
     79 static void toggle_watch(DBusWatch *watch, void *data)
     80 {
     81 	size_t i = (size_t)dbus_watch_get_data(watch);
     82 
     83 	(void)data;
     84 
     85 	if (dbus_watch_get_enabled(watch))
     86 		pfds[i].events = poll_flags(dbus_watch_get_flags(watch));
     87 	else
     88 		pfds[i].events = 0;
     89 }
     90 
     91 static void remove_watch(DBusWatch *watch, void *data)
     92 {
     93 	size_t i = (size_t)dbus_watch_get_data(watch);
     94 
     95 	(void)data;
     96 
     97 	memmove(&pfds[i], &pfds[i + 1], (nw - i - 1) * sizeof(*pfds));
     98 	memmove(&watches[i], &watches[i + 1], (nw - i - 1) * sizeof(*watches));
     99 
    100 	for (; i < nw - 1; i++)
    101 		dbus_watch_set_data(watches[i], (void *)i, NULL);
    102 	nw--;
    103 }
    104 
    105 static dbus_bool_t add_watch(DBusWatch *watch, void *data)
    106 {
    107 	(void)data;
    108 
    109 	if (nw == N(watches))
    110 		return FALSE;
    111 
    112 	watches[nw] = watch;
    113 	pfds[nw].fd = dbus_watch_get_unix_fd(watch);
    114 	dbus_watch_set_data(watch, (void *)nw++, NULL);
    115 
    116 	toggle_watch(watch, NULL);
    117 
    118 	return TRUE;
    119 }
    120 
    121 void toggle_timeout(DBusTimeout *timeout,
    122 		    void *data)
    123 {
    124 	size_t i = (size_t)dbus_timeout_get_data(timeout);
    125 
    126 	(void)data;
    127 
    128 	if (dbus_timeout_get_enabled(timeout))
    129 		expirys[i] = time(NULL) +
    130 			(dbus_timeout_get_interval(timeout) + 999) / 1000;
    131 	else
    132 		expirys[i] = 0;
    133 }
    134 
    135 static dbus_bool_t add_timeout(DBusTimeout *timeout,
    136 			       void *data)
    137 {
    138 	(void)data;
    139 
    140 	if (nt == N(timeouts))
    141 		return FALSE;
    142 
    143 	timeouts[nt] = timeout;
    144 	expirys[nt] = 0;
    145 	dbus_timeout_set_data(timeout, (void *)nt++, NULL);
    146 
    147 	toggle_timeout(timeout, NULL);
    148 
    149 	return TRUE;
    150 }
    151 
    152 void remove_timeout(DBusTimeout *timeout,
    153 		    void *data)
    154 {
    155 	size_t i = (size_t)dbus_timeout_get_data(timeout);
    156 
    157 	(void)data;
    158 
    159 	memmove(&timeouts[i], &timeouts[i + 1],
    160 		(nt - i - 1) * sizeof(*timeouts));
    161 	memmove(&expirys[i], &expirys[i + 1],
    162 		(nt - i - 1) * sizeof(*expirys));
    163 
    164 	for (; i < nt - 1; i++)
    165 		dbus_timeout_set_data(timeouts[i], (void *)i, NULL);
    166 	nt--;
    167 }
    168 
    169 static void dispatch_status(DBusConnection *connection,
    170 			    DBusDispatchStatus new_status,
    171 			    void           *data)
    172 {
    173 	(void)connection;
    174 	(void)data;
    175 	need_dispatch = (new_status == DBUS_DISPATCH_DATA_REMAINS);
    176 }
    177 
    178 void trigger_timeouts(void)
    179 {
    180 	size_t i;
    181 	time_t now = time(NULL);
    182 
    183 	for (i = 0; i < nt; i++) {
    184 		DBusTimeout *to;
    185 		if (expirys[i] > now)
    186 			continue;
    187 		to = timeouts[i];
    188 		dbus_timeout_handle(to);
    189 		if (i < nt && timeouts[i] != to)
    190 			i--;
    191 	}
    192 }
    193 
    194 time_t timeout_next_expiry(void)
    195 {
    196 	time_t rv = 0;
    197 	size_t i;
    198 
    199 	for (i = 0; i < nt; i++)
    200 		if (expirys[i] && (!rv || expirys[i] < rv))
    201 			rv = expirys[i];
    202 
    203 	return rv;
    204 }
    205 
    206 static void notif_spawn(struct notification *n)
    207 {
    208 	if (!cmd) {
    209 		n->child = 0;
    210 		return;
    211 	}
    212 
    213 	n->child = fork();
    214 	switch (n->child) {
    215 	case -1:
    216 		groan("fork() failed:");
    217 		n->child = 0;
    218 		break;
    219 	case 0:
    220 		close(STDIN_FILENO);
    221 		open("/dev/null", O_RDONLY);
    222 		close(STDOUT_FILENO);
    223 		open("/dev/null", O_WRONLY);
    224 		execlp(cmd, cmd, n->d.summary, n->d.body, NULL);
    225 		exit(1);
    226 		break;
    227 	default:
    228 		break;
    229 	}
    230 }
    231 
    232 const struct notification *notif_update(DBusMessage *msg,
    233 					struct notification_data *d,
    234 					time_t expiry)
    235 {
    236 	struct notification *i;
    237 
    238 	for (i = notifications; i && i->id != d->replaces; i = i->next);
    239 
    240 	if (!i)
    241 		return NULL;
    242 
    243 	changed |= use_json || strcmp(d->summary, i->d.summary);
    244 	dbus_message_unref(i->msg);
    245 	if (cmd && i->child)
    246 		kill(i->child, SIGTERM);
    247 
    248 	memcpy(&i->d, d, sizeof(i->d));
    249 	i->expiry = expiry;
    250 	i->msg = dbus_message_ref(msg);
    251 
    252 	notif_spawn(i);
    253 
    254 	return i;
    255 }
    256 
    257 const struct notification *notif_add(DBusMessage *msg,
    258 				     struct notification_data *d,
    259 				     time_t expiry)
    260 {
    261 	static dbus_uint32_t notification_id = 0;
    262 	struct notification *nn = malloc(sizeof(*nn));
    263 	struct notification *ne;
    264 
    265 	if (!(nn->id = ++notification_id))
    266 		nn->id = ++notification_id;
    267 	nn->msg = dbus_message_ref(msg);
    268 	memcpy(&nn->d, d, sizeof(nn->d));
    269 	nn->expiry = expiry;
    270 	nn->next = NULL;
    271 
    272 	if (notifications) {
    273 		for (ne = notifications; ne->next; ne = ne->next);
    274 		ne->next = nn;
    275 	} else {
    276 		notifications = nn;
    277 	}
    278 
    279 	notif_spawn(nn);
    280 
    281 	changed = 1;
    282 
    283 	return nn;
    284 }
    285 
    286 enum reason {
    287 	ACTION,
    288 	EXPIRED,
    289 	DISMISSED,
    290 	REQUEST,
    291 	UNKNOWN,
    292 };
    293 
    294 int notif_close(dbus_uint32_t id, pid_t pid, dbus_uint32_t reason)
    295 {
    296 	DBusMessage *sig;
    297 	struct notification *n;
    298 
    299 	if (!notifications)
    300 		return 0;
    301 	if (notifications->id == id || notifications->child == pid) {
    302 		n = notifications;
    303 		notifications = n->next;
    304 	} else {
    305 		struct notification *i;
    306 		for (i = notifications;
    307 		     i->next && i->next->id != id && i->next->child != pid;
    308 		     i = i->next);
    309 		if (!i->next)
    310 			return 0;
    311 		n = i->next;
    312 		i->next = i->next->next;
    313 	}
    314 
    315 	if (reason == ACTION) {
    316 		sig = dbus_message_new_signal(N_PATH, N_IF, "ActionInvoked");
    317 		dbus_message_append_args(sig,
    318 					 DBUS_TYPE_UINT32, &n->id,
    319 					 DBUS_TYPE_STRING,
    320 					 &(const char *){ "default" },
    321 					 DBUS_TYPE_INVALID);
    322 		dbus_connection_send(connection, sig, NULL);
    323 		dbus_message_unref(sig);
    324 		reason = DISMISSED;
    325 	}
    326 
    327 	sig = dbus_message_new_signal(N_PATH, N_IF, "NotificationClosed");
    328 	dbus_message_append_args(sig,
    329 				 DBUS_TYPE_UINT32, &n->id,
    330 				 DBUS_TYPE_UINT32, &reason,
    331 				 DBUS_TYPE_INVALID);
    332 
    333 	dbus_connection_send(connection, sig, NULL);
    334 	dbus_message_unref(sig);
    335 
    336 	dbus_message_unref(n->msg);
    337 	if (cmd && n->child && n->child != pid)
    338 		kill(n->child, SIGTERM);
    339 	free(n);
    340 
    341 	changed = 1;
    342 
    343 	return 1;
    344 }
    345 
    346 time_t notif_next_expiry(void)
    347 {
    348 	struct notification *i;
    349 	time_t rv = 0;
    350 
    351 	for (i = notifications; i; i = i->next) {
    352 		if (i->expiry &&
    353 		    (!rv || i->expiry < rv))
    354 			rv = i->expiry;
    355 	}
    356 
    357 	return rv;
    358 }
    359 
    360 void notif_check_expiry(void)
    361 {
    362 	time_t now = time(NULL);
    363 	struct notification *i;
    364 	struct notification *n;
    365 
    366 	for (i = notifications; i; i = n) {
    367 		n = i->next;
    368 
    369 		if (i->expiry && i->expiry <= now)
    370 			notif_close(i->id, -1, EXPIRED);
    371 	}
    372 }
    373 
    374 static void writ(const char *part, size_t plen, void *data)
    375 {
    376 	fwrite(part, 1, plen, data);
    377 }
    378 
    379 static void esc(const char *string, const char *escape,
    380 		void (*cb)(const char *part, size_t plen, void *data),
    381 		void *data) {
    382 	static char buffer[1024];
    383 	static const char *special = "\b\f\n\r\t";
    384 	static const char *sp_rep = "bfnrt";
    385 	size_t p = 0;
    386 	while (*string) {
    387 		while (*string && p < sizeof(buffer)) {
    388 			size_t len = strcspn(string, escape);
    389 			if (len > sizeof(buffer) - p)
    390 				len = sizeof(buffer) - p;
    391 			memcpy(buffer + p, string, len);
    392 			string += len;
    393 			p += len;
    394 			while (*string && strchr(escape, *string)) {
    395 				const char *sp;
    396 				buffer[p++] = '\\';
    397 				if ((sp = strchr(special, *string)))
    398 					buffer[p++] = sp_rep[sp - special];
    399 				else
    400 					buffer[p++] = *string;
    401 				string++;
    402 			}
    403 		}
    404 		cb(buffer, p, data);
    405 		p = 0;
    406 	}
    407 }
    408 
    409 void notif_dump(void)
    410 {
    411 	const char *sep = "";
    412 	struct notification *i;
    413 
    414 	if (use_json)
    415 		printf("[");
    416 	for (i = notifications; i; i = i->next) {
    417 		printf("%s", sep);
    418 		if (use_json)
    419 			output_notification(i->id, &i->d);
    420 		else
    421 			esc(i->d.summary, "\\\r\n\t\b\f", writ, stdout);
    422 		sep = delimiter;
    423 	}
    424 	if (use_json)
    425 		printf("]");
    426 	puts("");
    427 	fflush(stdout);
    428 }
    429 
    430 DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *message, void *user_data)
    431 {
    432 	DBusMessage *reply;
    433 
    434 	(void)user_data;
    435 
    436 	if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
    437 		static const char *notificationpath = N_PATH;
    438 		const char *path = dbus_message_get_path(message);
    439 		size_t pl = strlen(path);
    440 
    441 		if (strncmp(notificationpath, path, pl))
    442 			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    443 
    444 		if (pl == 1)
    445 			pl = 0;
    446 
    447 		if (notificationpath[pl] == '/') {
    448 			size_t npl = strcspn(notificationpath + pl + 1, "/");
    449 			char buffer[sizeof(INTROSPECTION_NODE_XML) + npl - 4];
    450 			sprintf(buffer, INTROSPECTION_NODE_XML, (int)npl, notificationpath + pl + 1);
    451 			reply = dbus_message_new_method_return(message);
    452 			dbus_message_append_args(reply,
    453 						 DBUS_TYPE_STRING, &(const char *){ buffer },
    454 						 DBUS_TYPE_INVALID);
    455 		} else if (notificationpath[pl]) {
    456 			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    457 		} else {
    458 			reply = dbus_message_new_method_return(message);
    459 			dbus_message_append_args(reply,
    460 						 DBUS_TYPE_STRING, &(const char *){ INTROSPECTION_XML },
    461 						 DBUS_TYPE_INVALID);
    462 		}
    463 	} else if (dbus_message_is_method_call(message, N_IF, "GetServerInformation")) {
    464 		reply = dbus_message_new_method_return(message);
    465 		dbus_message_append_args(reply,
    466 					 DBUS_TYPE_STRING, &(const char *){ "tmisu" },
    467 					 DBUS_TYPE_STRING, &(const char *){ "inz" },
    468 					 DBUS_TYPE_STRING, &(const char *){ "1.0" },
    469 					 DBUS_TYPE_STRING, &(const char *){ "1.2" },
    470 					 DBUS_TYPE_INVALID);
    471 	} else if (dbus_message_is_method_call(message, N_IF, "GetCapabilities")) {
    472 		DBusMessageIter i;
    473 		DBusMessageIter j;
    474 
    475 		reply = dbus_message_new_method_return(message);
    476 		dbus_message_iter_init_append(reply, &i);
    477 		dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &j);
    478 		dbus_message_iter_append_basic(&j, DBUS_TYPE_STRING, &(const char *){ "body" });
    479 		dbus_message_iter_append_basic(&j, DBUS_TYPE_STRING, &(const char *){ "actions" });
    480 		dbus_message_iter_close_container(&i, &j);
    481 	} else if (dbus_message_is_method_call(message, N_IF, "Notify")) {
    482 		const struct notification *n = NULL;
    483 		struct notification_data d;
    484 		DBusMessageIter iter;
    485 		time_t expiry;
    486 
    487 		if (!dbus_message_has_signature(message, "susssasa{sv}i"))
    488 			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    489 
    490 		dbus_message_iter_init(message, &iter);
    491 		dbus_message_iter_get_basic(&iter, &d.app_name);
    492 		dbus_message_iter_next(&iter);
    493 		dbus_message_iter_get_basic(&iter, &d.replaces);
    494 		dbus_message_iter_next(&iter);
    495 		dbus_message_iter_get_basic(&iter, &d.icon);
    496 		dbus_message_iter_next(&iter);
    497 		dbus_message_iter_get_basic(&iter, &d.summary);
    498 		dbus_message_iter_next(&iter);
    499 		dbus_message_iter_get_basic(&iter, &d.body);
    500 		dbus_message_iter_next(&iter);
    501 		dbus_message_iter_recurse(&iter, &d.actions);
    502 		dbus_message_iter_next(&iter);
    503 		dbus_message_iter_recurse(&iter, &d.hints);
    504 		dbus_message_iter_next(&iter);
    505 		dbus_message_iter_get_basic(&iter, &d.expiry_ms);
    506 
    507 		if (d.expiry_ms < 0)
    508 			d.expiry_ms = default_exp_timeout * 1000;
    509 		if (d.expiry_ms)
    510 			expiry = time(NULL) + (d.expiry_ms + 999) / 1000;
    511 		else
    512 			expiry = 0;
    513 
    514 		if (d.replaces)
    515 			n = notif_update(message,
    516 					 &d,
    517 					 expiry);
    518 		if (!n)
    519 			n = notif_add(message,
    520 				      &d,
    521 				      expiry);
    522 
    523 		reply = dbus_message_new_method_return(message);
    524 		// output_notification(message, n->id, cnf->fmt, cnf->delimiter);
    525 		dbus_message_append_args(reply,
    526 					 DBUS_TYPE_UINT32, &n->id,
    527 					 DBUS_TYPE_INVALID);
    528 	} else if (dbus_message_is_method_call(message, N_IF, "CloseNotification")) {
    529 		if (!dbus_message_has_signature(message, "u"))
    530 			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    531 		dbus_uint32_t id;
    532 		dbus_message_get_args(message, NULL,
    533 				      DBUS_TYPE_UINT32, &id,
    534 				      DBUS_TYPE_INVALID);
    535 		if (notif_close(id, -1, REQUEST))
    536 			reply = dbus_message_new_method_return(message);
    537 		else
    538 			reply = dbus_message_new_error(message,
    539 						       DBUS_ERROR_INVALID_ARGS,
    540 						       "No such id");
    541 	} else {
    542 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    543 	}
    544 
    545 	dbus_connection_send(connection, reply, NULL);
    546 	dbus_message_unref(reply);
    547 
    548 	return DBUS_HANDLER_RESULT_HANDLED;
    549 }
    550 
    551 int sfd[2];
    552 
    553 void sig_handler(int signal)
    554 {
    555 	if (write(sfd[1], &(char){ signal }, 1) != 1)
    556 		exit(1);
    557 }
    558 
    559 static void die(const char *fmt, ...)
    560 {
    561 	va_list args;
    562 	va_start(args, fmt);
    563 	vfprintf(stderr, fmt, args);
    564 	va_end(args);
    565 	if (*fmt && fmt[strlen(fmt) - 1] == ':')
    566 		fprintf(stderr, " %s", strerror(errno));
    567 	fputs("\n", stderr);
    568 	exit(1);
    569 }
    570 
    571 static void groan(const char *fmt, ...)
    572 {
    573 	va_list args;
    574 	va_start(args, fmt);
    575 	vfprintf(stderr, fmt, args);
    576 	va_end(args);
    577 	if (*fmt && fmt[strlen(fmt) - 1] == ':')
    578 		fprintf(stderr, " %s", strerror(errno));
    579 	fputs("\n", stderr);
    580 }
    581 
    582 static void usage(void)
    583 {
    584 	die("usage: %s [-d delim] [-j] [-c command]\n"
    585 	    "-h\tThis help\n"
    586 	    "-d\tDelimeter for default output style.\n"
    587 	    "-j\tUse JSON output style\n"
    588 	    "-c\tExecute command on notifications\n",
    589 	    argv0);
    590 }
    591 
    592 int main(int argc, char **argv)
    593 {
    594 	int r;
    595 
    596 	ARGBEGIN {
    597 	case 'd':
    598 		delimiter = EARGF(usage());
    599 		break;
    600 	case 'j':
    601 		use_json = 1;
    602 		break;
    603 	case 'c':
    604 		cmd = EARGF(usage());
    605 		break;
    606 	default:
    607 		usage();
    608 		break;
    609 	} ARGEND;
    610 
    611 
    612 	if (pipe(sfd))
    613 		die("Unable to create pipe:");
    614 
    615 	connection = dbus_bus_get_private(DBUS_BUS_SESSION, NULL);
    616 	if (!connection)
    617 		die("Failed to connect to session bus");
    618 
    619 	dbus_connection_set_watch_functions(connection,
    620 					    add_watch,
    621 					    remove_watch,
    622 					    toggle_watch,
    623 					    NULL, NULL);
    624 	dbus_connection_set_timeout_functions(connection,
    625 					      add_timeout,
    626 					      remove_timeout,
    627 					      toggle_timeout,
    628 					      NULL, NULL);
    629 	dbus_connection_set_dispatch_status_function(connection,
    630 						     dispatch_status,
    631 						     NULL, NULL);
    632 	dispatch_status(connection,
    633 			dbus_connection_get_dispatch_status(connection),
    634 			NULL);
    635 
    636 	r = dbus_bus_request_name(connection,
    637 				  N_SRV,
    638 				  DBUS_NAME_FLAG_REPLACE_EXISTING |
    639 				  DBUS_NAME_FLAG_DO_NOT_QUEUE, NULL);
    640 
    641 	if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
    642 		die("Could not acquire service name, "
    643 		    "is another notification daemon running?");
    644 
    645 	dbus_bus_add_match(connection, "interface=" N_IF " ,path=" N_PATH ","
    646 			   "type=method_call", NULL);
    647 	dbus_bus_add_match(connection,
    648 			   "interface=org.freedesktop.DBus.Introspectable,"
    649 			   "method=Introspect,type=method_call", NULL);
    650 	dbus_connection_add_filter(connection, handle_message, NULL, NULL);
    651 
    652 	signal(SIGINT, sig_handler);
    653 	signal(SIGTERM, sig_handler);
    654 	signal(SIGUSR1, sig_handler);
    655 	signal(SIGUSR2, sig_handler);
    656 	signal(SIGCHLD, sig_handler);
    657 
    658 	pfdb[0].fd = sfd[0];
    659 	pfdb[0].events = POLLIN;
    660 
    661 	for (;;) {
    662 		size_t i;
    663 		time_t nexp;
    664 		time_t texp;
    665 		int interval;
    666 
    667 		notif_check_expiry();
    668 		trigger_timeouts();
    669 
    670 		if (!need_dispatch) {
    671 			nexp = notif_next_expiry();
    672 			texp = timeout_next_expiry();
    673 
    674 			if (changed)
    675 				notif_dump();
    676 			changed = 0;
    677 
    678 			if (nexp && (!texp || nexp <= texp))
    679 				interval = (nexp - time(NULL)) * 1000;
    680 			else if (texp && (!nexp || texp < nexp))
    681 				interval = (texp - time(NULL)) * 1000;
    682 			else
    683 				interval = -1;
    684 		} else {
    685 			interval = 0;
    686 		}
    687 
    688 		int r = poll(pfdb, nw + 1, interval);
    689 
    690 		if (need_dispatch)
    691 			dbus_connection_dispatch(connection);
    692 
    693 		if (pfdb[0].revents) {
    694 			char s;
    695 			if (read(pfdb[0].fd, &s, 1) != 1)
    696 				break;
    697 
    698 			if (s == SIGINT || s == SIGTERM)
    699 				break;
    700 
    701 			if (s == SIGUSR1 || s == SIGUSR2) {
    702 				if (notifications)
    703 					notif_close(notifications->id, -1,
    704 						    s == SIGUSR1 ? DISMISSED :
    705 						    ACTION);
    706 			} else if (s == SIGCHLD) {
    707 				int cstatus;
    708 				pid_t pid = waitpid(-1, &cstatus, WNOHANG);
    709 
    710 				if (pid < 0)
    711 					groan("waitpid() failed:");
    712 				else if (!pid)
    713 					groan("Spurious SIGCHLD");
    714 				else if (WIFEXITED(cstatus) &&
    715 					 WEXITSTATUS(cstatus) == 0)
    716 					notif_close(0, pid, ACTION);
    717 				else
    718 					notif_close(0, pid, DISMISSED);
    719 			}
    720 			signal(s, sig_handler);
    721 		}
    722 
    723 		for (i = 0; i < nw && r; i++) {
    724 			if (!pfds[i].revents)
    725 				continue;
    726 			dbus_watch_handle(watches[i],
    727 					  dw_flags(pfds[i].revents));
    728 			toggle_watch(watches[i], NULL);
    729 			r--;
    730 		}
    731 	}
    732 
    733 	dbus_bus_release_name(connection,
    734 			      "org.freedesktop.Notifications", NULL);
    735 	dbus_connection_remove_filter(connection, handle_message, NULL);
    736 	dbus_bus_remove_match(connection,
    737 			      "interface=" N_IF ","
    738 			      "path=" N_PATH ","
    739 			      "type=method_call", NULL);
    740 	dbus_bus_remove_match(connection,
    741 			      "interface=org.freedesktop.DBus.Introspectable,"
    742 			      "method=Introspect,"
    743 			      "type=method_call", NULL);
    744 	dbus_connection_close(connection);
    745 	dbus_connection_unref(connection);
    746 
    747 	return 0;
    748 }