dunst/dunst.c
2013-03-15 16:06:40 +00:00

384 lines
10 KiB
C

/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#define _GNU_SOURCE
#define XLIB_ILLEGAL_ACCESS
#include <assert.h>
#include <unistd.h>
#include <time.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <fnmatch.h>
#include <sys/time.h>
#include <math.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <glib.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#ifdef XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#include <X11/extensions/scrnsaver.h>
#include "dunst.h"
#include "x.h"
#include "dbus.h"
#include "utils.h"
#include "rules.h"
#include "notification.h"
#include "option_parser.h"
#include "settings.h"
#define LENGTH(X) (sizeof X / sizeof X[0])
#ifndef VERSION
#define VERSION "version info needed"
#endif
#define MSG 1
#define INFO 2
#define DEBUG 3
typedef struct _x11_source {
GSource source;
Display *dpy;
Window w;
} x11_source_t;
/* index of colors fit to urgency level */
bool pause_display = false;
GMainLoop *mainloop = NULL;
/* notification lists */
GQueue *queue = NULL; /* all new notifications get into here */
GQueue *displayed = NULL; /* currently displayed notifications */
GQueue *history = NULL; /* history of displayed notifications */
GSList *rules = NULL;
/* misc funtions */
void check_timeouts(void)
{
/* nothing to do */
if (displayed->length == 0)
return;
for (GList * iter = g_queue_peek_head_link(displayed); iter;
iter = iter->next) {
notification *n = iter->data;
/* don't timeout when user is idle */
if (x_is_idle()) {
n->start = time(NULL);
continue;
}
/* skip hidden and sticky messages */
if (n->start == 0 || n->timeout == 0) {
continue;
}
/* remove old message */
if (difftime(time(NULL), n->start) > n->timeout) {
/* close_notification may conflict with iter, so restart */
notification_close(n, 1);
check_timeouts();
return;
}
}
}
void update_lists()
{
int limit;
check_timeouts();
if (pause_display) {
while (displayed->length > 0) {
g_queue_insert_sorted(queue, g_queue_pop_head(queue),
notification_cmp_data, NULL);
}
return;
}
if (xctx.geometry.h == 0) {
limit = 0;
} else if (xctx.geometry.h == 1) {
limit = 1;
} else if (settings.indicate_hidden) {
limit = xctx.geometry.h - 1;
} else {
limit = xctx.geometry.h;
}
/* move notifications from queue to displayed */
while (queue->length > 0) {
if (limit > 0 && displayed->length >= limit) {
/* the list is full */
break;
}
notification *n = g_queue_pop_head(queue);
if (!n)
return;
n->start = time(NULL);
if (!n->redisplayed && n->script) {
notification_run_script(n);
}
g_queue_insert_sorted(displayed, n, notification_cmp_data,
NULL);
}
}
void move_all_to_history()
{
while (displayed->length > 0) {
notification_close(g_queue_peek_head_link(displayed)->data, 2);
}
notification *n = g_queue_pop_head(queue);
while (n) {
g_queue_push_tail(history, n);
n = g_queue_pop_head(queue);
}
}
void history_pop(void)
{
if (g_queue_is_empty(history))
return;
notification *n = g_queue_pop_tail(history);
n->redisplayed = true;
n->start = 0;
n->timeout = settings.sticky_history ? 0 : n->timeout;
g_queue_push_head(queue, n);
if (!xctx.visible) {
wake_up();
}
}
void wake_up(void)
{
run(NULL);
}
static int get_sleep_time(void)
{
if (settings.show_age_threshold == 0) {
/* we need to update every second */
return 1000;
}
bool have_ttl = false;
int min_ttl = 0;
int max_age = 0;
for (GList *iter = g_queue_peek_head_link(displayed); iter;
iter = iter->next) {
notification *n = iter->data;
max_age = MAX(max_age, notification_get_age(n));
int ttl = notification_get_ttl(n);
if (ttl > 0) {
if (have_ttl) {
min_ttl = MIN(min_ttl, ttl);
} else {
min_ttl = ttl;
have_ttl = true;
}
}
}
int min_timeout;
int show_age_timeout = settings.show_age_threshold - max_age;
if (show_age_timeout < 1) {
return 1000;
}
if (!have_ttl) {
min_timeout = show_age_timeout;
} else {
min_timeout = MIN(show_age_timeout, min_ttl);
}
/* show_age_timeout might be negative */
if (min_timeout < 1) {
return 1000;
} else {
/* add 501 milliseconds to make sure we wake are in the second
* after the next notification times out. Otherwise we'll wake
* up, but the notification won't get closed until we get woken
* up again (which might be multiple seconds later */
return min_timeout * 1000 + 501;
}
}
gboolean run(void *data)
{
update_lists();
static int timeout_cnt = 0;
static int next_timeout = 0;
if (data) {
timeout_cnt--;
}
if (displayed->length > 0 && !xctx.visible) {
x_win_show();
}
if (displayed->length == 0 && xctx.visible) {
x_win_hide();
}
if (xctx.visible) {
x_win_draw();
}
if (xctx.visible) {
int now = time(NULL) * 1000;
int sleep = get_sleep_time();
if (sleep > 0) {
int timeout_at = now + sleep;
if (timeout_cnt == 0 || timeout_at < next_timeout) {
g_timeout_add(sleep, run, mainloop);
next_timeout = timeout_at;
timeout_cnt++;
}
}
}
/* always return false to delete timers */
return false;
}
int main(int argc, char *argv[])
{
history = g_queue_new();
displayed = g_queue_new();
queue = g_queue_new();
cmdline_load(argc, argv);
if (cmdline_get_bool("-v/-version", false, "Print version")
|| cmdline_get_bool("--version", false, "Print version")) {
print_version();
}
char *cmdline_config_path;
cmdline_config_path =
cmdline_get_string("-conf/-config", NULL,
"Path to configuration file");
load_settings(cmdline_config_path);
if (cmdline_get_bool("-h/-help", false, "Print help")
|| cmdline_get_bool("--help", false, "Print help")) {
usage(EXIT_SUCCESS);
}
int owner_id = initdbus();
x_setup();
signal(SIGUSR1, pause_signal_handler);
signal(SIGUSR2, pause_signal_handler);
if (settings.startup_notification) {
notification *n = malloc(sizeof(notification));
n->appname = "dunst";
n->summary = "startup";
n->body = "dunst is up and running";
n->progress = 0;
n->timeout = 10;
n->urgency = LOW;
n->icon = NULL;
n->msg = NULL;
n->dbus_client = NULL;
n->color_strings[0] = NULL;
n->color_strings[1] = NULL;
n->actions = NULL;
n->urls = NULL;
notification_init(n, 0);
}
mainloop = g_main_loop_new(NULL, FALSE);
GPollFD dpy_pollfd = { xctx.dpy->fd,
G_IO_IN | G_IO_HUP | G_IO_ERR, 0
};
GSourceFuncs x11_source_funcs = {
x_mainloop_fd_prepare,
x_mainloop_fd_check,
x_mainloop_fd_dispatch,
NULL,
NULL,
NULL
};
GSource *x11_source =
g_source_new(&x11_source_funcs, sizeof(x11_source_t));
((x11_source_t *) x11_source)->dpy = xctx.dpy;
((x11_source_t *) x11_source)->w = xctx.win;
g_source_add_poll(x11_source, &dpy_pollfd);
g_source_attach(x11_source, NULL);
run(NULL);
g_main_loop_run(mainloop);
dbus_tear_down(owner_id);
return 0;
}
void pause_signal_handler(int sig)
{
if (sig == SIGUSR1) {
pause_display = true;
}
if (sig == SIGUSR2) {
pause_display = false;
}
signal(sig, pause_signal_handler);
}
void usage(int exit_status)
{
fputs("usage:\n", stderr);
char *us = cmdline_create_usage();
fputs(us, stderr);
fputs("\n", stderr);
exit(exit_status);
}
void print_version(void)
{
printf
("Dunst - A customizable and lightweight notification-daemon %s\n",
VERSION);
exit(EXIT_SUCCESS);
}
/* vim: set ts=8 sw=8 tw=0: */