diff --git a/src/dbus.c b/src/dbus.c index a2d93a6..b669b70 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -8,6 +8,7 @@ #include "dunst.h" #include "notification.h" +#include "queues.h" #include "settings.h" #include "utils.h" @@ -280,14 +281,20 @@ static void on_notify(GDBusConnection *connection, n->color_strings[ColFG] = fgcolor; n->color_strings[ColBG] = bgcolor; - int id = notification_init(n, replaces_id); - wake_up(); + notification_init(n); + int id = queues_notification_insert(n, replaces_id); GVariant *reply = g_variant_new("(u)", id); g_dbus_method_invocation_return_value(invocation, reply); g_dbus_connection_flush(connection, NULL, NULL, NULL); - run(NULL); + // The message got discarded + if (id == 0) { + notification_closed(n, 2); + notification_free(n); + } + + wake_up(); } static void on_close_notification(GDBusConnection *connection, @@ -297,7 +304,8 @@ static void on_close_notification(GDBusConnection *connection, { guint32 id; g_variant_get(parameters, "(u)", &id); - notification_close_by_id(id, 3); + queues_notification_close_id(id, 3); + wake_up(); g_dbus_method_invocation_return_value(invocation, NULL); g_dbus_connection_flush(connection, NULL, NULL, NULL); } diff --git a/src/dunst.c b/src/dunst.c index f4e6349..56f4213 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -16,6 +16,7 @@ #include "menu.h" #include "notification.h" #include "option_parser.h" +#include "queues.h" #include "settings.h" #include "x11/x.h" #include "x11/screen.h" @@ -35,175 +36,23 @@ typedef struct _x11_source { } 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; - - GList *iter = g_queue_peek_head_link(displayed); - while (iter) { - notification *n = iter->data; - - /* - * Update iter to the next item before we either exit the - * current iteration of the loop or potentially delete the - * notification which would invalidate the pointer. - */ - iter = iter->next; - - /* don't timeout when user is idle */ - if (x_is_idle() && !n->transient) { - n->start = g_get_monotonic_time(); - continue; - } - - /* skip hidden and sticky messages */ - if (n->start == 0 || n->timeout == 0) { - continue; - } - - /* remove old message */ - if (g_get_monotonic_time() - n->start > n->timeout) { - notification_close(n, 1); - } - } -} - -void update_lists() -{ - int limit; - - check_timeouts(); - - if (pause_display) { - while (displayed->length > 0) { - g_queue_insert_sorted(queue, g_queue_pop_head(displayed), - 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 = g_get_monotonic_time(); - 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); - } - - while (queue->length > 0) { - notification_close(g_queue_peek_head_link(queue)->data, 2); - } -} - -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); - - wake_up(); -} - -void history_push(notification *n) -{ - if (settings.history_length > 0 && history->length >= settings.history_length) { - notification *to_free = g_queue_pop_head(history); - notification_free(to_free); - } - - if (!n->history_ignore) - g_queue_push_tail(history, n); -} - void wake_up(void) { run(NULL); } -static gint64 get_sleep_time(void) -{ - gint64 time = g_get_monotonic_time(); - gint64 sleep = G_MAXINT64; - - for (GList *iter = g_queue_peek_head_link(displayed); iter; - iter = iter->next) { - notification *n = iter->data; - gint64 ttl = n->timeout - (time - n->start); - - if (n->timeout > 0) { - if (ttl > 0) - sleep = MIN(sleep, ttl); - else - // while we're processing, the notification already timed out - return 0; - } - - if (settings.show_age_threshold >= 0) { - gint64 age = time - n->timestamp; - - if (age > settings.show_age_threshold) - // sleep exactly until the next shift of the second happens - sleep = MIN(sleep, ((G_USEC_PER_SEC) - (age % (G_USEC_PER_SEC)))); - else if (ttl > settings.show_age_threshold) - sleep = MIN(sleep, settings.show_age_threshold); - } - } - - return sleep != G_MAXINT64 ? sleep : -1; -} - gboolean run(void *data) { - update_lists(); + queues_check_timeouts(x_is_idle()); + queues_update(); + static int timeout_cnt = 0; static gint64 next_timeout = 0; @@ -211,11 +60,11 @@ gboolean run(void *data) timeout_cnt--; } - if (displayed->length > 0 && !xctx.visible && !pause_display) { + if (queues_length_displayed() > 0 && !xctx.visible) { x_win_show(); } - if (xctx.visible && (pause_display || displayed->length == 0)) { + if (xctx.visible && queues_length_displayed() == 0) { x_win_hide(); } @@ -225,7 +74,7 @@ gboolean run(void *data) if (xctx.visible) { gint64 now = g_get_monotonic_time(); - gint64 sleep = get_sleep_time(); + gint64 sleep = queues_get_next_datachange(now); gint64 timeout_at = now + sleep; if (sleep >= 0) { @@ -243,7 +92,7 @@ gboolean run(void *data) gboolean pause_signal(gpointer data) { - pause_display = true; + queues_pause_on(); wake_up(); return G_SOURCE_CONTINUE; @@ -251,7 +100,7 @@ gboolean pause_signal(gpointer data) gboolean unpause_signal(gpointer data) { - pause_display = false; + queues_pause_off(); wake_up(); return G_SOURCE_CONTINUE; @@ -264,19 +113,11 @@ gboolean quit_signal(gpointer data) return G_SOURCE_CONTINUE; } -static void teardown_notification(gpointer data) -{ - notification *n = data; - notification_free(n); -} - static void teardown(void) { regex_teardown(); - g_queue_free_full(history, teardown_notification); - g_queue_free_full(displayed, teardown_notification); - g_queue_free_full(queue, teardown_notification); + teardown_queues(); x_free(); } @@ -284,9 +125,7 @@ static void teardown(void) int dunst_main(int argc, char *argv[]) { - history = g_queue_new(); - displayed = g_queue_new(); - queue = g_queue_new(); + queues_init(); cmdline_load(argc, argv); @@ -319,7 +158,9 @@ int dunst_main(int argc, char *argv[]) n->timeout = 10 * G_USEC_PER_SEC; n->markup = MARKUP_NO; n->urgency = LOW; - notification_init(n, 0); + notification_init(n); + queues_notification_insert(n, 0); + // we do not call wakeup now, wake_up does not work here yet } mainloop = g_main_loop_new(NULL, FALSE); diff --git a/src/dunst.h b/src/dunst.h index 6982bef..9fa6a7e 100644 --- a/src/dunst.h +++ b/src/dunst.h @@ -16,11 +16,7 @@ #define ColFG 1 #define ColBG 0 -extern GQueue *queue; -extern GQueue *displayed; -extern GQueue *history; extern GSList *rules; -extern bool pause_display; extern const char *color_strings[3][3]; /* return id of notification */ @@ -30,10 +26,7 @@ void wake_up(void); int dunst_main(int argc, char *argv[]); void check_timeouts(void); -void history_pop(void); -void history_push(notification *n); void usage(int exit_status); -void move_all_to_history(void); void print_version(void); char *extract_urls(const char *str); void context_menu(void); diff --git a/src/menu.c b/src/menu.c index 6bd2940..6344c55 100644 --- a/src/menu.c +++ b/src/menu.c @@ -16,6 +16,7 @@ #include "dunst.h" #include "settings.h" #include "notification.h" +#include "queues.h" #include "utils.h" static bool is_initialized = false; @@ -136,7 +137,7 @@ void invoke_action(const char *action) int appname_len = strlen(appname_begin) - 1; // remove ] int action_len = strlen(action) - appname_len - 3; // remove space, [, ] - for (GList *iter = g_queue_peek_head_link(displayed); iter; + for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { notification *n = iter->data; if (g_str_has_prefix(appname_begin, n->appname) && strlen(n->appname) == appname_len) { @@ -188,7 +189,7 @@ void context_menu(void) } char *dmenu_input = NULL; - for (GList *iter = g_queue_peek_head_link(displayed); iter; + for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { notification *n = iter->data; diff --git a/src/notification.c b/src/notification.c index ef37eb0..145e0c4 100644 --- a/src/notification.c +++ b/src/notification.c @@ -19,10 +19,10 @@ #include "menu.h" #include "rules.h" #include "settings.h" +#include "queues.h" #include "utils.h" #include "x11/x.h" -int next_notification_id = 1; /* * print a human readable representation @@ -279,30 +279,19 @@ void notification_init_defaults(notification *n) } /* - * Initialize the given notification and add it to - * the queue. Replace notification with id if id > 0. + * Initialize the given notification * * n should be a pointer to a notification allocated with * notification_create, it is undefined behaviour to pass a notification * allocated some other way. */ -int notification_init(notification *n, int id) +void notification_init(notification *n) { assert(n != NULL); //Prevent undefined behaviour by initialising required fields notification_init_defaults(n); - if (strcmp("DUNST_COMMAND_PAUSE", n->summary) == 0) { - pause_display = true; - return 0; - } - - if (strcmp("DUNST_COMMAND_RESUME", n->summary) == 0) { - pause_display = false; - return 0; - } - n->script = NULL; n->text_to_render = NULL; @@ -422,64 +411,8 @@ int notification_init(notification *n, int id) n->msg = buffer; } - if (id == 0) { - n->id = ++next_notification_id; - } else { - n->id = id; - } - n->dup_count = 0; - /* check if n is a duplicate */ - if (settings.stack_duplicates) { - for (GList *iter = g_queue_peek_head_link(queue); iter; - iter = iter->next) { - notification *orig = iter->data; - if (notification_is_duplicate(orig, n)) { - /* If the progress differs this was probably intended to replace the notification - * but notify-send was used. So don't increment dup_count in this case - */ - if (orig->progress == n->progress) { - orig->dup_count++; - } else { - orig->progress = n->progress; - } - /* notifications that differ only in progress hints should be expected equal, - * but we want the latest message, with the latest hint value - */ - g_free(orig->msg); - orig->msg = g_strdup(n->msg); - notification_free(n); - wake_up(); - return orig->id; - } - } - - for (GList *iter = g_queue_peek_head_link(displayed); iter; - iter = iter->next) { - notification *orig = iter->data; - if (notification_is_duplicate(orig, n)) { - /* notifications that differ only in progress hints should be expected equal, - * but we want the latest message, with the latest hint value - */ - g_free(orig->msg); - orig->msg = g_strdup(n->msg); - /* If the progress differs this was probably intended to replace the notification - * but notify-send was used. So don't increment dup_count in this case - */ - if (orig->progress == n->progress) { - orig->dup_count++; - } else { - orig->progress = n->progress; - } - orig->start = g_get_monotonic_time(); - notification_free(n); - wake_up(); - return orig->id; - } - } - } - /* urgency > CRIT -> array out of range */ n->urgency = n->urgency > CRIT ? CRIT : n->urgency; @@ -505,17 +438,6 @@ int notification_init(notification *n, int id) n->first_render = true; - if (strlen(n->msg) == 0) { - notification_close(n, 2); - if (settings.always_run_script) { - notification_run_script(n); - } - printf("skipping notification: %s %s\n", n->body, n->summary); - } else { - if (id == 0 || !notification_replace_by_id(n)) - g_queue_insert_sorted(queue, n, notification_cmp_data, NULL); - } - char *tmp = g_strconcat(n->summary, " ", n->body, NULL); char *tmp_urls = extract_urls(tmp); @@ -538,102 +460,6 @@ int notification_init(notification *n, int id) } g_free(tmp); - - if (settings.print_notifications) - notification_print(n); - - return n->id; -} - -/* - * Close the notification that has id. - * - * reasons: - * -1 -> notification is a replacement, no NotificationClosed signal emitted - * 1 -> the notification expired - * 2 -> the notification was dismissed by the user_data - * 3 -> The notification was closed by a call to CloseNotification - */ -int notification_close_by_id(int id, int reason) -{ - notification *target = NULL; - - for (GList *iter = g_queue_peek_head_link(displayed); iter; - iter = iter->next) { - notification *n = iter->data; - if (n->id == id) { - g_queue_remove(displayed, n); - history_push(n); - target = n; - break; - } - } - - for (GList *iter = g_queue_peek_head_link(queue); iter; - iter = iter->next) { - notification *n = iter->data; - if (n->id == id) { - g_queue_remove(queue, n); - history_push(n); - target = n; - break; - } - } - - if (reason > 0 && reason < 4 && target != NULL) { - notification_closed(target, reason); - } - - wake_up(); - return reason; -} - -/* - * Close the given notification. SEE notification_close_by_id. - */ -int notification_close(notification *n, int reason) -{ - assert(n != NULL); - return notification_close_by_id(n->id, reason); -} - -/* - * Replace the notification which matches the id field of - * the new notification. The given notification is inserted - * right in the same position as the old notification. - * - * Returns true, if a matching notification has been found - * and is replaced. Else false. - */ -bool notification_replace_by_id(notification *new) -{ - - for (GList *iter = g_queue_peek_head_link(displayed); - iter; - iter = iter->next) { - notification *old = iter->data; - if (old->id == new->id) { - iter->data = new; - new->start = time(NULL); - new->dup_count = old->dup_count; - notification_run_script(new); - history_push(old); - return true; - } - } - - for (GList *iter = g_queue_peek_head_link(queue); - iter; - iter = iter->next) { - notification *old = iter->data; - if (old->id == new->id) { - iter->data = new; - new->dup_count = old->dup_count; - history_push(old); - return true; - } - } - return false; } void notification_update_text_to_render(notification *n) diff --git a/src/notification.h b/src/notification.h index edd051b..e5250c1 100644 --- a/src/notification.h +++ b/src/notification.h @@ -62,15 +62,12 @@ typedef struct _notification { } notification; notification *notification_create(void); -int notification_init(notification *n, int id); +void notification_init(notification *n); void notification_free(notification *n); -int notification_close_by_id(int id, int reason); -bool notification_replace_by_id(notification *n); int notification_cmp(const void *a, const void *b); int notification_cmp_data(const void *a, const void *b, void *data); int notification_is_duplicate(const notification *a, const notification *b); void notification_run_script(notification *n); -int notification_close(notification *n, int reason); void notification_print(notification *n); void notification_replace_single_field(char **haystack, char **needle, const char *replacement, enum markup_mode markup_mode); void notification_update_text_to_render(notification *n); diff --git a/src/queues.c b/src/queues.c new file mode 100644 index 0000000..c6316a8 --- /dev/null +++ b/src/queues.c @@ -0,0 +1,381 @@ +/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ + +#include "queues.h" + +#include +#include +#include +#include + +#include "dbus.h" +#include "notification.h" +#include "settings.h" + +/* notification lists */ +static GQueue *waiting = NULL; /* all new notifications get into here */ +static GQueue *displayed = NULL; /* currently displayed notifications */ +static GQueue *history = NULL; /* history of displayed notifications */ + +unsigned int displayed_limit = 0; +int next_notification_id = 1; +bool pause_displayed = false; + +static int queues_stack_duplicate(notification *n); + +void queues_init(void) +{ + history = g_queue_new(); + displayed = g_queue_new(); + waiting = g_queue_new(); +} + +void queues_displayed_limit(unsigned int limit) +{ + displayed_limit = limit; +} + +/* misc getter functions */ +const GList *queues_get_displayed() +{ + return g_queue_peek_head_link(displayed); +} +unsigned int queues_length_waiting() +{ + return waiting->length; +} +unsigned int queues_length_displayed() +{ + return displayed->length; +} +unsigned int queues_length_history() +{ + return history->length; +} + +int queues_notification_insert(notification *n, int replaces_id) +{ + /* do not display the message, if the message is empty */ + if (strlen(n->msg) == 0) { + if (settings.always_run_script) { + notification_run_script(n); + } + printf("skipping notification: %s %s\n", n->body, n->summary); + return 0; + } + /* Do not insert the message if it's a command */ + if (strcmp("DUNST_COMMAND_PAUSE", n->summary) == 0) { + pause_displayed = true; + return 0; + } + if (strcmp("DUNST_COMMAND_RESUME", n->summary) == 0) { + pause_displayed = false; + return 0; + } + + if (replaces_id == 0) { + + if (settings.stack_duplicates) { + int stacked = queues_stack_duplicate(n); + if (stacked > 0) { + // notification got stacked + return stacked; + } + } + + n->id = ++next_notification_id; + + g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); + + } else { + n->id = replaces_id; + if (!queues_notification_replace_id(n)) + g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); + } + + if (settings.print_notifications) + notification_print(n); + + return n->id; +} + +/* + * Replaces duplicate notification and stacks it + * + * Returns the notification id of the stacked notification + * Returns -1 if not notification could be stacked + */ +static int queues_stack_duplicate(notification *n) +{ + for (GList *iter = g_queue_peek_head_link(displayed); iter; + iter = iter->next) { + notification *orig = iter->data; + if (notification_is_duplicate(orig, n)) { + /* If the progress differs, probably notify-send was used to update the notification + * So only count it as a duplicate, if the progress was not the same. + * */ + if (orig->progress == n->progress) { + orig->dup_count++; + } else { + orig->progress = n->progress; + } + orig->start = g_get_monotonic_time(); + g_free(orig->msg); + orig->msg = g_strdup(n->msg); + notification_free(n); + return orig->id; + } + } + + for (GList *iter = g_queue_peek_head_link(waiting); iter; + iter = iter->next) { + notification *orig = iter->data; + if (notification_is_duplicate(orig, n)) { + /* If the progress differs, probably notify-send was used to update the notification + * So only count it as a duplicate, if the progress was not the same. + * */ + if (orig->progress == n->progress) { + orig->dup_count++; + } else { + orig->progress = n->progress; + } + g_free(orig->msg); + orig->msg = g_strdup(n->msg); + notification_free(n); + return orig->id; + } + } + + return -1; +} + +bool queues_notification_replace_id(notification *new) +{ + + for (GList *iter = g_queue_peek_head_link(displayed); + iter; + iter = iter->next) { + notification *old = iter->data; + if (old->id == new->id) { + iter->data = new; + new->start = time(NULL); + new->dup_count = old->dup_count; + notification_run_script(new); + queues_history_push(old); + return true; + } + } + + for (GList *iter = g_queue_peek_head_link(waiting); + iter; + iter = iter->next) { + notification *old = iter->data; + if (old->id == new->id) { + iter->data = new; + new->dup_count = old->dup_count; + queues_history_push(old); + return true; + } + } + return false; +} + +int queues_notification_close_id(int id, int reason) +{ + notification *target = NULL; + + for (GList *iter = g_queue_peek_head_link(displayed); iter; + iter = iter->next) { + notification *n = iter->data; + if (n->id == id) { + g_queue_remove(displayed, n); + queues_history_push(n); + target = n; + break; + } + } + + for (GList *iter = g_queue_peek_head_link(waiting); iter; + iter = iter->next) { + notification *n = iter->data; + if (n->id == id) { + g_queue_remove(waiting, n); + queues_history_push(n); + target = n; + break; + } + } + + if (reason > 0 && reason < 4 && target != NULL) { + notification_closed(target, reason); + } + + return reason; +} + +int queues_notification_close(notification *n, int reason) +{ + assert(n != NULL); + return queues_notification_close_id(n->id, reason); +} + +void queues_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(waiting, n); +} + +void queues_history_push(notification *n) +{ + if (settings.history_length > 0 && history->length >= settings.history_length) { + notification *to_free = g_queue_pop_head(history); + notification_free(to_free); + } + + if (!n->history_ignore) + g_queue_push_tail(history, n); +} + +void queues_history_push_all(void) +{ + while (displayed->length > 0) { + queues_notification_close(g_queue_peek_head_link(displayed)->data, 2); + } + + while (waiting->length > 0) { + queues_notification_close(g_queue_peek_head_link(waiting)->data, 2); + } +} + +void queues_check_timeouts(bool idle) +{ + /* nothing to do */ + if (displayed->length == 0) + return; + + GList *iter = g_queue_peek_head_link(displayed); + while (iter) { + notification *n = iter->data; + + /* + * Update iter to the next item before we either exit the + * current iteration of the loop or potentially delete the + * notification which would invalidate the pointer. + */ + iter = iter->next; + + /* don't timeout when user is idle */ + if (idle && !n->transient) { + n->start = g_get_monotonic_time(); + continue; + } + + /* skip hidden and sticky messages */ + if (n->start == 0 || n->timeout == 0) { + continue; + } + + /* remove old message */ + if (g_get_monotonic_time() - n->start > n->timeout) { + queues_notification_close(n, 1); + } + } +} + +void queues_update() +{ + if (pause_displayed) { + while (displayed->length > 0) { + g_queue_insert_sorted( + waiting, g_queue_pop_head(displayed), notification_cmp_data, NULL); + } + return; + } + + /* move notifications from queue to displayed */ + while (waiting->length > 0) { + + if (displayed_limit > 0 && displayed->length >= displayed_limit) { + /* the list is full */ + break; + } + + notification *n = g_queue_pop_head(waiting); + + if (!n) + return; + + n->start = g_get_monotonic_time(); + + if (!n->redisplayed && n->script) { + notification_run_script(n); + } + + g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL); + } +} + +gint64 queues_get_next_datachange(gint64 time) +{ + gint64 sleep = G_MAXINT64; + + for (GList *iter = g_queue_peek_head_link(displayed); iter; + iter = iter->next) { + notification *n = iter->data; + gint64 ttl = n->timeout - (time - n->start); + + if (n->timeout > 0) { + if (ttl > 0) + sleep = MIN(sleep, ttl); + else + // while we're processing, the notification already timed out + return 0; + } + + if (settings.show_age_threshold >= 0) { + gint64 age = time - n->timestamp; + + if (age > settings.show_age_threshold) + // sleep exactly until the next shift of the second happens + sleep = MIN(sleep, ((G_USEC_PER_SEC) - (age % (G_USEC_PER_SEC)))); + else if (ttl > settings.show_age_threshold) + sleep = MIN(sleep, settings.show_age_threshold); + } + } + + return sleep != G_MAXINT64 ? sleep : -1; +} + +void queues_pause_on(void) +{ + pause_displayed = true; +} + +void queues_pause_off(void) +{ + pause_displayed = false; +} + +bool queues_pause_status(void) +{ + return pause_displayed; +} + +static void teardown_notification(gpointer data) +{ + notification *n = data; + notification_free(n); +} + +void teardown_queues(void) +{ + g_queue_free_full(history, teardown_notification); + g_queue_free_full(displayed, teardown_notification); + g_queue_free_full(waiting, teardown_notification); +} +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/queues.h b/src/queues.h new file mode 100644 index 0000000..8cec5fc --- /dev/null +++ b/src/queues.h @@ -0,0 +1,132 @@ +/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ + +#ifndef DUNST_QUEUE_H +#define DUNST_QUEUE_H + +#include "notification.h" + +/* + * Initialise neccessary queues + */ +void queues_init(void); + +/* + * Set maximum notification count to display + * and store in displayed queue + */ +void queues_displayed_limit(unsigned int limit); + +/* + * Return read only list of notifications + */ +const GList *queues_get_displayed(); + +/* + * Returns the current amount of notifications, + * which are shown, waiting or already in history + */ +unsigned int queues_length_waiting(); +unsigned int queues_length_displayed(); +unsigned int queues_length_history(); + +/* + * Insert a fully initialized notification into queues + * Respects stack_duplicates, and notification replacement + * + * If replaces_id != 0, n replaces notification with id replaces_id + * If replaces_id == 0, n gets occupies a new position + * + * Returns the assigned notification id + * If returned id == 0, the message was dismissed + */ +int queues_notification_insert(notification *n, int replaces_id); + +/* + * Replace the notification which matches the id field of + * the new notification. The given notification is inserted + * right in the same position as the old notification. + * + * Returns true, if a matching notification has been found + * and is replaced. Else false. + */ +bool queues_notification_replace_id(notification *new); + +/* + * Close the notification that has n->id == id + * + * Sends a signal and pushes it automatically to history. + * + * After closing, call wake_up to synchronize the queues with the UI + * (which closes the notification on screen) + * + * reasons: + * -1 -> notification is a replacement, no NotificationClosed signal emitted + * 1 -> the notification expired + * 2 -> the notification was dismissed by the user_data + * 3 -> The notification was closed by a call to CloseNotification + */ +int queues_notification_close_id(int id, int reason); + +/* Close the given notification. SEE queues_notification_close_id. */ +int queues_notification_close(notification *n, int reason); + +/* + * Pushed the latest notification of history to the displayed queue + * and removes it from history + */ +void queues_history_pop(void); + +/* + * Push a single notification to history + * The given notification has to be removed its queue + */ +void queues_history_push(notification *n); + +/* + * Push all waiting and displayed notifications to history + */ +void queues_history_push_all(void); + +/* + * Check timeout of each notification and close it, if neccessary + */ +void queues_check_timeouts(bool idle); + +/* + * Move inserted notifications from waiting queue to displayed queue + * and show them. In displayed queue, the amount of elements is limited + * to the amount set via queues_displayed_limit + */ +void queues_update(); + +/* + * Return the distance to the next event in the queue, + * which forces an update visible to the user + * + * This may be: + * + * - notification hits timeout + * - notification's age second changes + * - notification's age threshold is hit + */ +gint64 queues_get_next_datachange(gint64 time); + +/* + * Pause queue-management of dunst + * pause_on = paused (no notifications displayed) + * pause_off = running + * + * Calling update_lists is neccessary + */ +void queues_pause_on(void); +void queues_pause_off(void); +bool queues_pause_status(void); + +/* + * Remove all notifications from all lists + * and free the notifications + */ +void teardown_queues(void); + +#endif +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/x11/x.c b/src/x11/x.c index ce84229..3647222 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -29,6 +29,7 @@ #include "src/markup.h" #include "src/notification.h" #include "src/settings.h" +#include "src/queues.h" #include "src/utils.h" #include "screen.h" @@ -532,11 +533,11 @@ static GSList *r_create_layouts(cairo_t *c) { GSList *layouts = NULL; - int qlen = g_list_length(g_queue_peek_head_link(queue)); + int qlen = queues_length_waiting(); bool xmore_is_needed = qlen > 0 && settings.indicate_hidden; notification *last = NULL; - for (GList *iter = g_queue_peek_head_link(displayed); + for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { notification *n = iter->data; @@ -842,6 +843,7 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, case ButtonRelease: if (ev.xbutton.window == xctx.win) { x_handle_click(ev); + wake_up(); } break; case KeyPress: @@ -852,29 +854,32 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, && XLookupKeysym(&ev.xkey, 0) == settings.close_ks.sym && settings.close_ks.mask == state) { - if (displayed) { - notification *n = g_queue_peek_head(displayed); - if (n) - notification_close(n, 2); + const GList *displayed = queues_get_displayed(); + if (displayed && displayed->data) { + queues_notification_close(displayed->data, 2); + wake_up(); } } if (settings.history_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.history_ks.sym && settings.history_ks.mask == state) { - history_pop(); + queues_history_pop(); + wake_up(); } if (settings.close_all_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.close_all_ks.sym && settings.close_all_ks.mask == state) { - move_all_to_history(); + queues_history_push_all(); + wake_up(); } if (settings.context_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.context_ks.sym && settings.context_ks.mask == state) { context_menu(); + wake_up(); } break; case FocusIn: @@ -910,7 +915,7 @@ bool x_is_idle(void) static void x_handle_click(XEvent ev) { if (ev.xbutton.button == Button3) { - move_all_to_history(); + queues_history_push_all(); return; } @@ -919,7 +924,7 @@ static void x_handle_click(XEvent ev) int y = settings.separator_height; notification *n = NULL; int first = true; - for (GList *iter = g_queue_peek_head_link(displayed); iter; + for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { n = iter->data; if (ev.xbutton.y > y && ev.xbutton.y < y + n->displayed_height) @@ -932,7 +937,7 @@ static void x_handle_click(XEvent ev) if (n) { if (ev.xbutton.button == Button1) - notification_close(n, 2); + queues_notification_close(n, 2); else notification_do_action(n); } @@ -1008,6 +1013,17 @@ void x_setup(void) &xctx.geometry.x, &xctx.geometry.y, &xctx.geometry.w, &xctx.geometry.h); + /* calculate maximum notification count and push information to queue */ + if (xctx.geometry.h == 0) { + queues_displayed_limit(0); + } else if (xctx.geometry.h == 1) { + queues_displayed_limit(1); + } else if (settings.indicate_hidden) { + queues_displayed_limit(xctx.geometry.h - 1); + } else { + queues_displayed_limit(xctx.geometry.h); + } + xctx.screensaver_info = XScreenSaverAllocInfo(); init_screens(); @@ -1109,7 +1125,7 @@ static void x_win_setup(void) void x_win_show(void) { /* window is already mapped or there's nothing to show */ - if (xctx.visible || g_queue_is_empty(displayed)) { + if (xctx.visible || queues_length_displayed() == 0) { return; }