diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b94516..14b684b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ ### Added - `fullscreen` rule to hide notifications when a fullscreen window is active +- When new notifications arrive, but display is full, important notifications don't + have to wait for a timeout in a displayed notification (#541) +- ` more` notifications don't occupy space anymore, if there is only a single + notification waiting to get displayed. The notification gets displayed directly (#467) ## 1.3.2 - 2018-05-06 diff --git a/src/draw.c b/src/draw.c index 17f24b9..6d40a02 100644 --- a/src/draw.c +++ b/src/draw.c @@ -26,7 +26,7 @@ typedef struct { char *text; PangoAttrList *attr; cairo_surface_t *icon; - notification *n; + const notification *n; } colored_layout; window_x11 *win; @@ -245,7 +245,7 @@ static PangoLayout *layout_create(cairo_t *c) return layout; } -static colored_layout *layout_init_shared(cairo_t *c, notification *n) +static colored_layout *layout_init_shared(cairo_t *c, const notification *n) { colored_layout *cl = g_malloc(sizeof(colored_layout)); cl->l = layout_create(c); @@ -301,7 +301,7 @@ static colored_layout *layout_init_shared(cairo_t *c, notification *n) return cl; } -static colored_layout *layout_derive_xmore(cairo_t *c, notification *n, int qlen) +static colored_layout *layout_derive_xmore(cairo_t *c, const notification *n, int qlen) { colored_layout *cl = layout_init_shared(c, n); cl->text = g_strdup_printf("(%d more)", qlen); @@ -350,12 +350,10 @@ static GSList *create_layouts(cairo_t *c) int qlen = queues_length_waiting(); bool xmore_is_needed = qlen > 0 && settings.indicate_hidden; - notification *last = NULL; for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { notification *n = iter->data; - last = n; notification_update_text_to_render(n); @@ -371,7 +369,7 @@ static GSList *create_layouts(cairo_t *c) if (xmore_is_needed && settings.geometry.h != 1) { /* append xmore message as new message */ layouts = g_slist_append(layouts, - layout_derive_xmore(c, last, qlen)); + layout_derive_xmore(c, queues_get_head_waiting(), qlen)); } return layouts; diff --git a/src/notification.c b/src/notification.c index 6d609cc..badd82b 100644 --- a/src/notification.c +++ b/src/notification.c @@ -92,6 +92,11 @@ void notification_run_script(notification *n) if (!n->script || strlen(n->script) < 1) return; + if (n->script_run && !settings.always_run_script) + return; + + n->script_run = true; + const char *appname = n->appname ? n->appname : ""; const char *summary = n->summary ? n->summary : ""; const char *body = n->body ? n->body : ""; @@ -268,6 +273,8 @@ notification *notification_create(void) n->transient = false; n->progress = -1; + n->script_run = false; + n->fullscreen = FS_SHOW; return n; diff --git a/src/notification.h b/src/notification.h index ab21e3c..74ddcb6 100644 --- a/src/notification.h +++ b/src/notification.h @@ -77,6 +77,7 @@ typedef struct _notification { int dup_count; /**< amount of duplicate notifications stacked onto this */ int displayed_height; enum behavior_fullscreen fullscreen; //!< The instruction what to do with it, when desktop enters fullscreen + bool script_run; /**< Has the script been executed already? */ /* derived fields */ char *msg; /**< formatted message */ @@ -139,6 +140,9 @@ int notification_is_duplicate(const notification *a, const notification *b); /** * Run the script associated with the * given notification. + * + * If the script of the notification has been executed already and + * settings.always_run_script is not set, do nothing. */ void notification_run_script(notification *n); /** diff --git a/src/queues.c b/src/queues.c index b737eb7..2850fa4 100644 --- a/src/queues.c +++ b/src/queues.c @@ -30,7 +30,6 @@ 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; @@ -44,18 +43,20 @@ void queues_init(void) waiting = g_queue_new(); } -/* see queues.h */ -void queues_displayed_limit(unsigned int limit) -{ - displayed_limit = limit; -} - /* see queues.h */ const GList *queues_get_displayed(void) { return g_queue_peek_head_link(displayed); } +/* see queues.h */ +const notification *queues_get_head_waiting(void) +{ + if (waiting->length == 0) + return NULL; + return g_queue_peek_head(waiting); +} + /* see queues.h */ unsigned int queues_length_waiting(void) { @@ -74,6 +75,51 @@ unsigned int queues_length_history(void) return history->length; } +/** + * Swap two given queue elements. The element's data has to be a notification. + * + * @pre { elemA has to be part of queueA. } + * @pre { elemB has to be part of queueB. } + * + * @param queueA The queue, which elemB's data will get inserted + * @param elemA The element, which will get removed from queueA + * @param queueB The queue, which elemA's data will get inserted + * @param elemB The element, which will get removed from queueB + */ +static void queues_swap_notifications(GQueue *queueA, + GList *elemA, + GQueue *queueB, + GList *elemB) +{ + notification *toB = elemA->data; + notification *toA = elemB->data; + + g_queue_delete_link(queueA, elemA); + g_queue_delete_link(queueB, elemB); + + if (toA) + g_queue_insert_sorted(queueA, toA, notification_cmp_data, NULL); + if (toB) + g_queue_insert_sorted(queueB, toB, notification_cmp_data, NULL); +} + +/** + * Check if a notification is eligible to get shown. + * + * @param n The notification to check + * @param fullscreen True if a fullscreen window is currently active + * @param visible True if the notification is currently displayed + */ +static bool queues_notification_is_ready(const notification *n, bool fullscreen, bool visible) +{ + if (fullscreen && visible) + return n && n->fullscreen != FS_PUSHBACK; + else if (fullscreen && !visible) + return n && n->fullscreen == FS_SHOW; + else + return true; +} + /* see queues.h */ int queues_notification_insert(notification *n) { @@ -123,52 +169,31 @@ int queues_notification_insert(notification *n) */ static bool 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; + GQueue *allqueues[] = { displayed, waiting }; + for (int i = 0; i < sizeof(allqueues)/sizeof(GList*); i++) { + for (GList *iter = g_queue_peek_head_link(allqueues[i]); 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; + } + iter->data = n; + + n->dup_count = orig->dup_count; + signal_notification_closed(orig, 1); + + if ( allqueues[i] == displayed ) + n->start = time_monotonic_now(); + + notification_free(orig); + return true; } - - iter->data = n; - - n->start = time_monotonic_now(); - - n->dup_count = orig->dup_count; - - signal_notification_closed(orig, 1); - - notification_free(orig); - return true; - } - } - - 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; - } - iter->data = n; - - n->dup_count = orig->dup_count; - - signal_notification_closed(orig, 1); - - notification_free(orig); - return true; } } @@ -178,31 +203,26 @@ static bool queues_stack_duplicate(notification *n) /* see queues.h */ bool queues_notification_replace_id(notification *new) { + GQueue *allqueues[] = { displayed, waiting }; + for (int i = 0; i < sizeof(allqueues)/sizeof(GList*); i++) { + for (GList *iter = g_queue_peek_head_link(allqueues[i]); + iter; + iter = iter->next) { + notification *old = iter->data; + if (old->id == new->id) { + iter->data = new; + new->dup_count = old->dup_count; - 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_monotonic_now(); - new->dup_count = old->dup_count; - notification_run_script(new); - notification_free(old); - return true; - } - } + if ( allqueues[i] == displayed ) { + new->start = time_monotonic_now(); + notification_run_script(new); + } - 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; - notification_free(old); - return true; + notification_free(old); + return true; + } } + } return false; } @@ -212,24 +232,16 @@ void queues_notification_close_id(int id, enum reason 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); - target = n; - break; - } - } - - for (GList *iter = g_queue_peek_head_link(waiting); iter; - iter = iter->next) { - notification *n = iter->data; - if (n->id == id) { - assert(target == NULL); - g_queue_remove(waiting, n); - target = n; - break; + GQueue *allqueues[] = { displayed, waiting }; + for (int i = 0; i < sizeof(allqueues)/sizeof(GList*); i++) { + for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; + iter = iter->next) { + notification *n = iter->data; + if (n->id == id) { + g_queue_remove(allqueues[i], n); + target = n; + break; + } } } @@ -258,7 +270,7 @@ void queues_history_pop(void) n->redisplayed = true; n->start = 0; n->timeout = settings.sticky_history ? 0 : n->timeout; - g_queue_push_head(waiting, n); + g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); } /* see queues.h */ @@ -353,37 +365,70 @@ void queues_update(bool fullscreen) } } + int cur_displayed_limit; + if (settings.geometry.h == 0) + cur_displayed_limit = INT_MAX; + else if ( settings.indicate_hidden + && settings.geometry.h > 1 + && displayed->length + waiting->length > settings.geometry.h) + cur_displayed_limit = settings.geometry.h-1; + else + cur_displayed_limit = settings.geometry.h; + /* move notifications from queue to displayed */ GList *iter = g_queue_peek_head_link(waiting); - while (iter) { + while (displayed->length < cur_displayed_limit && iter) { notification *n = iter->data; GList *nextiter = iter->next; - if (displayed_limit > 0 && displayed->length >= displayed_limit) { - /* the list is full */ - break; - } - if (!n) return; - if (fullscreen - && (n->fullscreen == FS_DELAY || n->fullscreen == FS_PUSHBACK)) { + if (!queues_notification_is_ready(n, fullscreen, false)) { iter = nextiter; continue; } n->start = time_monotonic_now(); - - if (!n->redisplayed && n->script) { - notification_run_script(n); - } + notification_run_script(n); g_queue_delete_link(waiting, iter); g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL); iter = nextiter; } + + /* if necessary, push the overhanging notifications from displayed to waiting again */ + while (displayed->length > cur_displayed_limit) { + notification *n = g_queue_pop_tail(displayed); + g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); //TODO: actually it should be on the head if unsorted + } + + /* If displayed is actually full, let the more important notifications + * from waiting seep into displayed. + */ + if (settings.sort) { + GList *i_waiting, *i_displayed; + + while ( (i_waiting = g_queue_peek_head_link(waiting)) + && (i_displayed = g_queue_peek_tail_link(displayed))) { + + while (i_waiting && ! queues_notification_is_ready(i_waiting->data, fullscreen, true)) { + i_waiting = i_waiting->prev; + } + + if (i_waiting && notification_cmp(i_displayed->data, i_waiting->data) > 0) { + notification *todisp = i_waiting->data; + + todisp->start = time_monotonic_now(); + notification_run_script(todisp); + + queues_swap_notifications(displayed, i_displayed, waiting, i_waiting); + } else { + break; + } + } + } } /* see queues.h */ diff --git a/src/queues.h b/src/queues.h index 2bda3e8..1e185d7 100644 --- a/src/queues.h +++ b/src/queues.h @@ -18,14 +18,6 @@ */ void queues_init(void); -/** - * Set maximum notification count to display - * and store in displayed queue - * - * @param limit The maximum amount - */ -void queues_displayed_limit(unsigned int limit); - /** * Receive the current list of displayed notifications * @@ -33,6 +25,13 @@ void queues_displayed_limit(unsigned int limit); */ const GList *queues_get_displayed(void); +/** + * Get the highest notification in line + * + * @return a notification or NULL, if waiting is empty + */ +const notification *queues_get_head_waiting(void); + /** * Returns the current amount of notifications, * which are waiting to get displayed diff --git a/src/x11/x.c b/src/x11/x.c index 21eed84..bf50a73 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -516,17 +516,6 @@ struct geometry x_parse_geometry(const char *geom_str) geometry.negative_x = mask & XNegative; geometry.negative_y = mask & YNegative; - /* calculate maximum notification count and push information to queue */ - if (geometry.h == 0) { - queues_displayed_limit(0); - } else if (geometry.h == 1) { - queues_displayed_limit(1); - } else if (settings.indicate_hidden) { - queues_displayed_limit(geometry.h - 1); - } else { - queues_displayed_limit(geometry.h); - } - return geometry; }