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