From c692d222d737a6a391fb67ccc2ffc801b484e2f0 Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Thu, 6 Sep 2018 08:36:28 +0200 Subject: [PATCH 1/6] Save the notification's scriptrun in script_run field --- src/notification.c | 7 +++++++ src/notification.h | 4 ++++ src/queues.c | 5 +---- 3 files changed, 12 insertions(+), 4 deletions(-) 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..017a4f4 100644 --- a/src/queues.c +++ b/src/queues.c @@ -374,10 +374,7 @@ void queues_update(bool fullscreen) } 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); From ec26403a06404c8be6f8cb48ecf4bd56d8d942a8 Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Thu, 6 Sep 2018 08:40:28 +0200 Subject: [PATCH 2/6] Let important notifications seep into full display queue When the display queue had is full and a new notification would come in, new notification wouldn't get shown until a currently displayed notification would timeout. Even if the notification would have been shown on top of the displayed queue. So e.g. if the displayed queue would have been filled with "normal" urgency notifications, an incoming "urgent" notification would have been delayed. To let those more important notifications through, the tail of displayed and head of waiting are swapped on every update if necessary. --- CHANGELOG.md | 2 ++ src/queues.c | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b94516..71f89c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### 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) ## 1.3.2 - 2018-05-06 diff --git a/src/queues.c b/src/queues.c index 017a4f4..600a3e7 100644 --- a/src/queues.c +++ b/src/queues.c @@ -258,7 +258,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 */ @@ -381,6 +381,40 @@ void queues_update(bool fullscreen) iter = nextiter; } + + /* 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 ( fullscreen + && i_waiting + && i_waiting->data + && ((notification*) i_waiting->data)->fullscreen != FS_SHOW) { + i_waiting = i_waiting->prev; + } + + if (i_waiting && notification_cmp(i_displayed->data, i_waiting->data) > 0) { + notification *towait = i_displayed->data; + notification *todisp = i_waiting->data; + + g_queue_delete_link(displayed, i_displayed); + g_queue_delete_link(waiting, i_waiting); + + todisp->start = time_monotonic_now(); + notification_run_script(todisp); + + g_queue_insert_sorted(displayed, todisp, notification_cmp_data, NULL); + g_queue_insert_sorted(waiting, towait, notification_cmp_data, NULL); + } else { + break; + } + } + } } /* see queues.h */ From f14b0b2b4a091d03ff5031450951104132f70a7a Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Thu, 6 Sep 2018 10:26:09 +0200 Subject: [PATCH 3/6] Show xmore layout only when needed For the case, that there is a single notification waiting in queue, the xmore layout is an annoying setting. --- CHANGELOG.md | 2 ++ src/queues.c | 25 +++++++++++++++++-------- src/queues.h | 8 -------- src/x11/x.c | 11 ----------- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f89c1..14b684b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - `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/queues.c b/src/queues.c index 600a3e7..dbca601 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,12 +43,6 @@ 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) { @@ -353,13 +346,23 @@ 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) { notification *n = iter->data; GList *nextiter = iter->next; - if (displayed_limit > 0 && displayed->length >= displayed_limit) { + if (displayed->length >= cur_displayed_limit) { /* the list is full */ break; } @@ -382,6 +385,12 @@ void queues_update(bool fullscreen) 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. */ diff --git a/src/queues.h b/src/queues.h index 2bda3e8..4f4dc64 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 * 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; } From 4c26ab442a061ef7fd093608778e1bdcb57bf133 Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Thu, 6 Sep 2018 12:07:09 +0200 Subject: [PATCH 4/6] Use the first waiting notification to generate xmore Using the last notification from displayed creates confusion about nonexisting notifications. --- src/draw.c | 10 ++++------ src/queues.c | 8 ++++++++ src/queues.h | 7 +++++++ 3 files changed, 19 insertions(+), 6 deletions(-) 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/queues.c b/src/queues.c index dbca601..d6a17a9 100644 --- a/src/queues.c +++ b/src/queues.c @@ -49,6 +49,14 @@ 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) { diff --git a/src/queues.h b/src/queues.h index 4f4dc64..1e185d7 100644 --- a/src/queues.h +++ b/src/queues.h @@ -25,6 +25,13 @@ void queues_init(void); */ 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 From 34e97b3e942495a3d02d05facdf087f69d4a1712 Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Wed, 12 Sep 2018 09:48:27 +0200 Subject: [PATCH 5/6] Optimize freshly introduced queues_update code --- src/queues.c | 67 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/queues.c b/src/queues.c index d6a17a9..3f4d1d3 100644 --- a/src/queues.c +++ b/src/queues.c @@ -75,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) { @@ -366,20 +411,14 @@ void queues_update(bool fullscreen) /* 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->length >= cur_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; } @@ -408,25 +447,17 @@ void queues_update(bool fullscreen) while ( (i_waiting = g_queue_peek_head_link(waiting)) && (i_displayed = g_queue_peek_tail_link(displayed))) { - while ( fullscreen - && i_waiting - && i_waiting->data - && ((notification*) i_waiting->data)->fullscreen != FS_SHOW) { + 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 *towait = i_displayed->data; notification *todisp = i_waiting->data; - g_queue_delete_link(displayed, i_displayed); - g_queue_delete_link(waiting, i_waiting); - todisp->start = time_monotonic_now(); notification_run_script(todisp); - g_queue_insert_sorted(displayed, todisp, notification_cmp_data, NULL); - g_queue_insert_sorted(waiting, towait, notification_cmp_data, NULL); + queues_swap_notifications(displayed, i_displayed, waiting, i_waiting); } else { break; } From e1ee87b5d399300bb0a668228ecc0762c4651cba Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Wed, 12 Sep 2018 18:14:15 +0200 Subject: [PATCH 6/6] When traversing both queues, traverse in one loop On actions, where waiting and displayed has to get traversed, traverse them both in a single loop. Code duplication isn't necessary anymore. --- src/queues.c | 136 +++++++++++++++++++-------------------------------- 1 file changed, 51 insertions(+), 85 deletions(-) diff --git a/src/queues.c b/src/queues.c index 3f4d1d3..2850fa4 100644 --- a/src/queues.c +++ b/src/queues.c @@ -169,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; } } @@ -224,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; } @@ -258,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; + } } }