1 /* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
2 
3 /**
4  * @file src/queues.c
5  * @brief All important functions to handle the notification queues for
6  * history, entrance and currently displayed ones.
7  *
8  * Every method requires to have executed queues_init() at the start.
9  *
10  * A read only representation of the queue with the current notifications
11  * can get acquired by calling queues_get_displayed().
12  *
13  * When ending the program or resetting the queues, tear down the stack with
14  * queues_teardown(). (And reinit with queues_init() if needed.)
15  */
16 #include "queues.h"
17 
18 #include <assert.h>
19 #include <glib.h>
20 #include <stdio.h>
21 #include <string.h>
22 
23 #include "dunst.h"
24 #include "log.h"
25 #include "notification.h"
26 #include "settings.h"
27 #include "utils.h"
28 #include "output.h" // For checking if wayland is active.
29 
30 /* notification lists */
31 static GQueue *waiting   = NULL; /**< all new notifications get into here */
32 static GQueue *displayed = NULL; /**< currently displayed notifications */
33 static GQueue *history   = NULL; /**< history of displayed notifications */
34 
35 int next_notification_id = 1;
36 
37 static bool queues_stack_duplicate(struct notification *n);
38 static bool queues_stack_by_tag(struct notification *n);
39 
40 /* see queues.h */
queues_init(void)41 void queues_init(void)
42 {
43         history   = g_queue_new();
44         displayed = g_queue_new();
45         waiting   = g_queue_new();
46 }
47 
48 /* see queues.h */
queues_get_displayed(void)49 GList *queues_get_displayed(void)
50 {
51         return g_queue_peek_head_link(displayed);
52 }
53 
54 /* see queues.h */
queues_get_head_waiting(void)55 const struct notification *queues_get_head_waiting(void)
56 {
57         if (waiting->length == 0)
58                 return NULL;
59         return g_queue_peek_head(waiting);
60 }
61 
62 /* see queues.h */
queues_length_waiting(void)63 unsigned int queues_length_waiting(void)
64 {
65         return waiting->length;
66 }
67 
68 /* see queues.h */
queues_length_displayed(void)69 unsigned int queues_length_displayed(void)
70 {
71         return displayed->length;
72 }
73 
74 /* see queues.h */
queues_length_history(void)75 unsigned int queues_length_history(void)
76 {
77         return history->length;
78 }
79 
80 /**
81  * Swap two given queue elements. The element's data has to be a notification.
82  *
83  * @pre { elemA has to be part of queueA. }
84  * @pre { elemB has to be part of queueB. }
85  *
86  * @param queueA The queue, which elemB's data will get inserted
87  * @param elemA  The element, which will get removed from queueA
88  * @param queueB The queue, which elemA's data will get inserted
89  * @param elemB  The element, which will get removed from queueB
90  */
queues_swap_notifications(GQueue * queueA,GList * elemA,GQueue * queueB,GList * elemB)91 static void queues_swap_notifications(GQueue *queueA,
92                                       GList  *elemA,
93                                       GQueue *queueB,
94                                       GList  *elemB)
95 {
96         struct notification *toB = elemA->data;
97         struct notification *toA = elemB->data;
98 
99         g_queue_delete_link(queueA, elemA);
100         g_queue_delete_link(queueB, elemB);
101 
102         if (toA)
103                 g_queue_insert_sorted(queueA, toA, notification_cmp_data, NULL);
104         if (toB)
105                 g_queue_insert_sorted(queueB, toB, notification_cmp_data, NULL);
106 }
107 
108 /**
109  * Check if a notification is eligible to get shown.
110  *
111  * @param n      The notification to check
112  * @param status The current status of dunst
113  * @param shown  True if the notification is currently displayed
114  */
queues_notification_is_ready(const struct notification * n,struct dunst_status status,bool shown)115 static bool queues_notification_is_ready(const struct notification *n, struct dunst_status status, bool shown)
116 {
117         ASSERT_OR_RET(status.running, false);
118         if (status.fullscreen && shown)
119                 return n && n->fullscreen != FS_PUSHBACK;
120         else if (status.fullscreen && !shown)
121                 return n && n->fullscreen == FS_SHOW;
122         else
123                 return true;
124 }
125 
126 /**
127  * Check if a notification has timed out
128  *
129  * @param n the notification to check
130  * @param status the current status of dunst
131  * @retval true: the notification is timed out
132  * @retval false: otherwise
133  */
queues_notification_is_finished(struct notification * n,struct dunst_status status)134 static bool queues_notification_is_finished(struct notification *n, struct dunst_status status)
135 {
136         assert(n);
137 
138         if (n->skip_display && !n->redisplayed)
139                 return true;
140 
141         if (n->timeout == 0) // sticky
142                 return false;
143 
144         bool is_idle = status.fullscreen ? false : status.idle;
145 
146         /* don't timeout when user is idle */
147         if (is_idle && !n->transient) {
148                 n->start = time_monotonic_now();
149                 return false;
150         }
151 
152         /* remove old message */
153         if (time_monotonic_now() - n->start > n->timeout) {
154                 return true;
155         }
156 
157         return false;
158 }
159 
160 /* see queues.h */
queues_notification_insert(struct notification * n)161 int queues_notification_insert(struct notification *n)
162 {
163         /* do not display the message, if the message is empty */
164         if (STR_EMPTY(n->msg)) {
165                 if (settings.always_run_script) {
166                         notification_run_script(n);
167                 }
168                 LOG_M("Skipping notification: '%s' '%s'", n->body, n->summary);
169                 return 0;
170         }
171 
172         bool inserted = false;
173         if (n->id != 0) {
174                 if (!queues_notification_replace_id(n)) {
175                         // Requested id was not valid, but play nice and assign it anyway
176                         g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
177                 }
178                 inserted = true;
179         } else {
180                 n->id = ++next_notification_id;
181         }
182 
183         if (!inserted && STR_FULL(n->stack_tag) && queues_stack_by_tag(n))
184                 inserted = true;
185 
186         if (!inserted && settings.stack_duplicates && queues_stack_duplicate(n))
187                 inserted = true;
188 
189         if (!inserted)
190                 g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
191 
192         if (settings.print_notifications)
193                 notification_print(n);
194 
195         return n->id;
196 }
197 
198 /**
199  * Replaces duplicate notification and stacks it
200  *
201  * @retval true: notification got stacked
202  * @retval false: notification did not get stacked
203  */
queues_stack_duplicate(struct notification * n)204 static bool queues_stack_duplicate(struct notification *n)
205 {
206         GQueue *allqueues[] = { displayed, waiting };
207         for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
208                 for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter;
209                      iter = iter->next) {
210                         struct notification *orig = iter->data;
211                         if (notification_is_duplicate(orig, n)) {
212                                 /* If the progress differs, probably notify-send was used to update the notification
213                                  * So only count it as a duplicate, if the progress was not the same.
214                                  * */
215                                 if (orig->progress == n->progress) {
216                                         orig->dup_count++;
217                                 } else {
218                                         orig->progress = n->progress;
219                                 }
220                                 iter->data = n;
221 
222                                 n->dup_count = orig->dup_count;
223                                 signal_notification_closed(orig, 1);
224 
225                                 if (allqueues[i] == displayed)
226                                         n->start = time_monotonic_now();
227 
228                                 notification_unref(orig);
229                                 return true;
230                         }
231                 }
232         }
233 
234         return false;
235 }
236 
237 /**
238  * Replaces the first notification of the same stack_tag
239  *
240  * @retval true: notification got stacked
241  * @retval false: notification did not get stacked
242  */
queues_stack_by_tag(struct notification * new)243 static bool queues_stack_by_tag(struct notification *new)
244 {
245         GQueue *allqueues[] = { displayed, waiting };
246         for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
247                 for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter;
248                             iter = iter->next) {
249                         struct notification *old = iter->data;
250                         if (STR_FULL(old->stack_tag) && STR_EQ(old->stack_tag, new->stack_tag)) {
251                                 iter->data = new;
252                                 new->dup_count = old->dup_count;
253 
254                                 signal_notification_closed(old, 1);
255 
256                                 if (allqueues[i] == displayed) {
257                                         new->start = time_monotonic_now();
258                                         notification_run_script(new);
259                                 }
260 
261                                 notification_unref(old);
262                                 return true;
263                         }
264                 }
265         }
266         return false;
267 }
268 
269 /* see queues.h */
queues_notification_replace_id(struct notification * new)270 bool queues_notification_replace_id(struct notification *new)
271 {
272         GQueue *allqueues[] = { displayed, waiting };
273         for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
274                 for (GList *iter = g_queue_peek_head_link(allqueues[i]);
275                             iter;
276                             iter = iter->next) {
277                         struct notification *old = iter->data;
278                         if (old->id == new->id) {
279                                 iter->data = new;
280                                 new->dup_count = old->dup_count;
281 
282                                 if (allqueues[i] == displayed) {
283                                         new->start = time_monotonic_now();
284                                         notification_run_script(new);
285                                 }
286 
287                                 notification_unref(old);
288                                 return true;
289                         }
290                 }
291         }
292         return false;
293 }
294 
295 /* see queues.h */
queues_notification_close_id(int id,enum reason reason)296 void queues_notification_close_id(int id, enum reason reason)
297 {
298         struct notification *target = NULL;
299 
300         GQueue *allqueues[] = { displayed, waiting };
301         for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
302                 for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter;
303                      iter = iter->next) {
304                         struct notification *n = iter->data;
305                         if (n->id == id) {
306                                 g_queue_remove(allqueues[i], n);
307                                 target = n;
308                                 break;
309                         }
310                 }
311         }
312 
313         if (target) {
314                 //Don't notify clients if notification was pulled from history
315                 if (!target->redisplayed)
316                         signal_notification_closed(target, reason);
317                 queues_history_push(target);
318         }
319 }
320 
321 /* see queues.h */
queues_notification_close(struct notification * n,enum reason reason)322 void queues_notification_close(struct notification *n, enum reason reason)
323 {
324         assert(n != NULL);
325         queues_notification_close_id(n->id, reason);
326 }
327 
328 /* see queues.h */
queues_history_pop(void)329 void queues_history_pop(void)
330 {
331         if (g_queue_is_empty(history))
332                 return;
333 
334         struct notification *n = g_queue_pop_tail(history);
335         n->redisplayed = true;
336         n->timeout = settings.sticky_history ? 0 : n->timeout;
337         g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
338 }
339 
340 /* see queues.h */
queues_history_push(struct notification * n)341 void queues_history_push(struct notification *n)
342 {
343         if (!n->history_ignore) {
344                 if (settings.history_length > 0 && history->length >= settings.history_length) {
345                         struct notification *to_free = g_queue_pop_head(history);
346                         notification_unref(to_free);
347                 }
348 
349                 g_queue_push_tail(history, n);
350         } else {
351                 notification_unref(n);
352         }
353 }
354 
355 /* see queues.h */
queues_history_push_all(void)356 void queues_history_push_all(void)
357 {
358         while (displayed->length > 0) {
359                 queues_notification_close(g_queue_peek_head_link(displayed)->data, REASON_USER);
360         }
361 
362         while (waiting->length > 0) {
363                 queues_notification_close(g_queue_peek_head_link(waiting)->data, REASON_USER);
364         }
365 }
366 
367 /* see queues.h */
queues_update(struct dunst_status status)368 void queues_update(struct dunst_status status)
369 {
370         GList *iter, *nextiter;
371 
372         /* Move back all notifications, which aren't eligible to get shown anymore
373          * Will move the notifications back to waiting, if dunst isn't running or fullscreen
374          * and notifications is not eligible to get shown anymore */
375         iter = g_queue_peek_head_link(displayed);
376         while (iter) {
377                 struct notification *n = iter->data;
378                 nextiter = iter->next;
379 
380                 if (notification_is_locked(n)) {
381                         iter = nextiter;
382                         continue;
383                 }
384 
385                 if (n->marked_for_closure) {
386                         queues_notification_close(n, n->marked_for_closure);
387                         n->marked_for_closure = 0;
388                         iter = nextiter;
389                         continue;
390                 }
391 
392 
393                 if (queues_notification_is_finished(n, status)){
394                         queues_notification_close(n, REASON_TIME);
395                         iter = nextiter;
396                         continue;
397                 }
398 
399                 if (!queues_notification_is_ready(n, status, true)) {
400                         g_queue_delete_link(displayed, iter);
401                         g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
402                         iter = nextiter;
403                         continue;
404                 }
405 
406                 iter = nextiter;
407         }
408 
409         int cur_displayed_limit;
410         if (settings.geometry.h == 0)
411                 cur_displayed_limit = INT_MAX;
412         else if (   settings.indicate_hidden
413                  && settings.geometry.h > 1
414                  && displayed->length + waiting->length > settings.geometry.h)
415                 cur_displayed_limit = settings.geometry.h-1;
416         else
417                 cur_displayed_limit = settings.geometry.h;
418 
419         /* move notifications from queue to displayed */
420         iter = g_queue_peek_head_link(waiting);
421         while (displayed->length < cur_displayed_limit && iter) {
422                 struct notification *n = iter->data;
423                 nextiter = iter->next;
424 
425                 ASSERT_OR_RET(n,);
426 
427                 if (!queues_notification_is_ready(n, status, false)) {
428                         iter = nextiter;
429                         continue;
430                 }
431 
432                 n->start = time_monotonic_now();
433                 notification_run_script(n);
434 
435                 if (n->skip_display && !n->redisplayed) {
436                         queues_notification_close(n, REASON_USER);
437                 } else {
438                         g_queue_delete_link(waiting, iter);
439                         g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL);
440                 }
441 
442                 iter = nextiter;
443         }
444 
445         /* if necessary, push the overhanging notifications from displayed to waiting again */
446         while (displayed->length > cur_displayed_limit) {
447                 struct notification *n = g_queue_pop_tail(displayed);
448                 g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); //TODO: actually it should be on the head if unsorted
449         }
450 
451         /* If displayed is actually full, let the more important notifications
452          * from waiting seep into displayed.
453          */
454         if (settings.sort && displayed->length == cur_displayed_limit) {
455                 GList *i_waiting, *i_displayed;
456 
457                 while (   (i_waiting   = g_queue_peek_head_link(waiting))
458                        && (i_displayed = g_queue_peek_tail_link(displayed))) {
459 
460                         while (i_waiting && ! queues_notification_is_ready(i_waiting->data, status, false)) {
461                                 i_waiting = i_waiting->prev;
462                         }
463 
464                         if (i_waiting && notification_cmp(i_displayed->data, i_waiting->data) > 0) {
465                                 struct notification *todisp = i_waiting->data;
466 
467                                 todisp->start = time_monotonic_now();
468                                 notification_run_script(todisp);
469 
470                                 queues_swap_notifications(displayed, i_displayed, waiting, i_waiting);
471                         } else {
472                                 break;
473                         }
474                 }
475         }
476 }
477 
478 /* see queues.h */
queues_get_next_datachange(gint64 time)479 gint64 queues_get_next_datachange(gint64 time)
480 {
481         gint64 sleep = G_MAXINT64;
482 
483         for (GList *iter = g_queue_peek_head_link(displayed); iter;
484                         iter = iter->next) {
485                 struct notification *n = iter->data;
486                 gint64 ttl = n->timeout - (time - n->start);
487 
488                 if (n->timeout > 0) {
489                         if (ttl > 0)
490                                 sleep = MIN(sleep, ttl);
491                         else
492                                 // while we're processing, the notification already timed out
493                                 return 0;
494                 }
495 
496                 if (settings.show_age_threshold >= 0) {
497                         gint64 age = time - n->timestamp;
498 
499                         // sleep exactly until the next shift of the second happens
500                         if (age > settings.show_age_threshold - S2US(1))
501                                 sleep = MIN(sleep, (S2US(1) - (age % S2US(1))));
502                         else
503                                 sleep = MIN(sleep, settings.show_age_threshold - age);
504                 }
505         }
506 
507         return sleep != G_MAXINT64 ? sleep : -1;
508 }
509 
510 
511 
512 
513 /* see queues.h */
queues_get_by_id(int id)514 struct notification* queues_get_by_id(int id)
515 {
516         assert(id > 0);
517 
518         GQueue *recqueues[] = { displayed, waiting, history };
519         for (int i = 0; i < sizeof(recqueues)/sizeof(GQueue*); i++) {
520                 for (GList *iter = g_queue_peek_head_link(recqueues[i]); iter;
521                      iter = iter->next) {
522                         struct notification *cur = iter->data;
523                         if (cur->id == id)
524                                 return cur;
525                 }
526         }
527 
528         return NULL;
529 }
530 
531 /**
532  * Helper function for queues_teardown() to free a single notification
533  *
534  * @param data The notification to free
535  */
teardown_notification(gpointer data)536 static void teardown_notification(gpointer data)
537 {
538         struct notification *n = data;
539         notification_unref(n);
540 }
541 
542 /* see queues.h */
queues_teardown(void)543 void queues_teardown(void)
544 {
545         g_queue_free_full(history, teardown_notification);
546         history = NULL;
547         g_queue_free_full(displayed, teardown_notification);
548         displayed = NULL;
549         g_queue_free_full(waiting, teardown_notification);
550         waiting = NULL;
551 }
552 
553 
554 /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
555