Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
queues.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
8
9#include <assert.h>
10#include <glib.h>
11#include <stdio.h>
12#include <string.h>
13
14#include "queues.h"
15#include "dunst.h"
16#include "log.h"
17#include "notification.h"
18#include "settings.h"
19#include "utils.h"
20#include "rules.h"
21
22/* notification lists */
23static GQueue *waiting = NULL;
24static GQueue *displayed = NULL;
25static GQueue *history = NULL;
26
27int next_notification_id = 1;
28
29static bool queues_stack_duplicate(struct notification *n);
30static bool queues_stack_by_tag(struct notification *n);
31
32/* see queues.h */
33void queues_init(void)
34{
35 history = g_queue_new();
36 displayed = g_queue_new();
37 waiting = g_queue_new();
38}
39
40/* see queues.h */
42{
43 return g_queue_peek_head_link(displayed);
44}
45
46/* see queues.h */
48{
49 if (waiting->length == 0)
50 return NULL;
51 return g_queue_peek_head(waiting);
52}
53
54/* see queues.h */
55unsigned int queues_length_waiting(void)
56{
57 return waiting->length;
58}
59
60/* see queues.h */
61unsigned int queues_length_displayed(void)
62{
63 return displayed->length;
64}
65
66/* see queues.h */
67unsigned int queues_length_history(void)
68{
69 return history->length;
70}
71
72/* see queues.h */
74{
75 return g_queue_peek_head_link(history);
76}
77
89static void queues_swap_notifications(GQueue *queueA,
90 GList *elemA,
91 GQueue *queueB,
92 GList *elemB)
93{
94 struct notification *toB = elemA->data;
95 struct notification *toA = elemB->data;
96
97 g_queue_delete_link(queueA, elemA);
98 g_queue_delete_link(queueB, elemB);
99
100 if (toA)
101 g_queue_insert_sorted(queueA, toA, notification_cmp_data, NULL);
102 if (toB)
103 g_queue_insert_sorted(queueB, toB, notification_cmp_data, NULL);
104}
105
113static bool queues_notification_is_ready(const struct notification *n, struct dunst_status status, bool shown)
114{
115 if (status.pause_level > n->override_pause_level) {
116 return false;
117 }
118
119 if (status.fullscreen && shown)
120 return n && n->fullscreen != FS_PUSHBACK;
121 else if (status.fullscreen && !shown)
122 return n && n->fullscreen == FS_SHOW;
123 else
124 return true;
125}
126
136static bool queues_notification_is_finished(struct notification *n, struct dunst_status status, gint64 time)
137{
138 assert(n);
139
140 if (n->skip_display && !n->redisplayed)
141 return true;
142
143 if (n->timeout == 0) // sticky
144 return false;
145
146 bool is_idle = status.fullscreen ? false : status.idle;
147
148 /* don't timeout when user is idle */
149 if (is_idle && !n->transient) {
151 return false;
152 }
153
154 /* don't timeout when mouse is over the notification window */
155 if (status.mouse_over && !n->transient) {
157 return false;
158 }
159
160 /* remove old message */
161 if (time - n->start > n->timeout) {
162 return true;
163 }
164
165 return false;
166}
167
168/* see queues.h */
170{
171 /* do not display the message, if the message is empty */
172 if (STR_EMPTY(n->msg)) {
173 if (settings.always_run_script) {
175 }
176 LOG_M("Skipping notification: '%s' '%s'", STR_NN(n->body), STR_NN(n->summary));
177 return 0;
178 }
179
180 bool inserted = false;
181 if (n->id != 0) {
183 // Requested id was not valid, but play nice and assign it anyway
184 g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
185 }
186 inserted = true;
187 } else {
188 n->id = ++next_notification_id;
189 }
190
191 if (!inserted && STR_FULL(n->stack_tag) && queues_stack_by_tag(n))
192 inserted = true;
193
194 if (!inserted && settings.stack_duplicates && queues_stack_duplicate(n)) {
195 if (settings.sort == SORT_TYPE_UPDATE) {
196 g_queue_sort(displayed, notification_cmp_data, NULL);
197 }
198 inserted = true;
199 }
200
201 if (!inserted)
202 g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
203
204 // The icon is loaded lazily. this is skipped if the icon was transferred
205 if (!n->icon) {
207 }
208
209 if (print_notifications)
211
212 return n->id;
213}
214
221static bool queues_stack_duplicate(struct notification *new)
222{
223 gint64 modtime = -1;
224
225 GQueue *allqueues[] = { displayed, waiting };
226 for (size_t i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
227 for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) {
228 struct notification *old = iter->data;
229 if (notification_is_duplicate(old, new)) {
230
231 // Additional check to see if the icon was modified
232 // But only if the icon is from a file
233 //
234 if (old->icon && !new->icon_id && is_like_path(old->iconname)) {
235 if (modtime < 0)
236 modtime = modification_time(old->iconname);
237
238 // File was touched, check if the hash is the same
239 if (modtime > old->icon_time) {
240 notification_icon_replace_path(new, new->iconname);
241
242 if (!STR_EQ(new->icon_id, old->icon_id))
243 continue;
244 } else {
246 }
247 }
248
249 /* If the progress differs, probably notify-send was used to update the notification
250 * So only count it as a duplicate, if the progress was the same.
251 * */
252 if (old->progress == new->progress) {
253 old->dup_count++;
254 } else {
255 old->progress = new->progress;
256 }
257 iter->data = new;
258
259 new->dup_count = old->dup_count;
260 signal_notification_closed(old, 1);
261
262 if (allqueues[i] == displayed)
263 new->start = time_monotonic_now();
264
266 return true;
267 }
268 }
269 }
270
271 return false;
272}
273
280static bool queues_stack_by_tag(struct notification *new)
281{
282 GQueue *allqueues[] = { displayed, waiting };
283 for (size_t i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
284 for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) {
285 struct notification *old = iter->data;
286 if (STR_FULL(old->stack_tag) && STR_EQ(old->stack_tag, new->stack_tag)
287 && STR_EQ(old->appname, new->appname)) {
288 iter->data = new;
289 new->dup_count = old->dup_count;
290
291 bool replace = false;
292
293 // Transfer old icon when new:
294 // has no icon
295 // has the same name (not a path)
296 // has the same path and the modtime is older than the notification
297 if (old->icon) {
298 if (!new->iconname) {
299 replace = true;
300 } else if (STR_EQ(new->iconname, old->iconname)) {
301 replace = true;
302 if (is_like_path(old->iconname)) {
303 gint64 modtime = modification_time(old->iconname);
304 replace = modtime <= old->icon_time;
305 }
306 }
307 }
308
309 signal_notification_closed(old, 1);
310
311 if (allqueues[i] == displayed) {
312 new->start = time_monotonic_now();
314 }
315
316 if (replace)
318
320 return true;
321 }
322 }
323 }
324 return false;
325}
326
327/* see queues.h */
329{
330 GQueue *allqueues[] = { displayed, waiting };
331 for (size_t i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
332 for (GList *iter = g_queue_peek_head_link(allqueues[i]);
333 iter;
334 iter = iter->next) {
335 struct notification *old = iter->data;
336 if (old->id == new->id) {
337 iter->data = new;
338 new->dup_count = old->dup_count;
339
340 if (allqueues[i] == displayed) {
341 new->start = time_monotonic_now();
343 }
344
346 return true;
347 }
348 }
349 }
350 return false;
351}
352
353/* see queues.h */
355{
356 struct notification *target = NULL;
357
358 GQueue *allqueues[] = { displayed, waiting };
359 for (size_t i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) {
360 for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter;
361 iter = iter->next) {
362 struct notification *n = iter->data;
363 if (n->id == id) {
364 g_queue_remove(allqueues[i], n);
365 target = n;
366 break;
367 }
368 }
369 }
370
371 if (target) {
372 //Don't notify clients if notification was pulled from history
373 if (!target->redisplayed)
374 signal_notification_closed(target, reason);
375 queues_history_push(target);
376 }
377}
378
379/* see queues.h */
381{
382 assert(n != NULL);
384}
385
387{
388 assert(n != NULL);
391}
392
393static void queues_destroy_notification(struct notification *n, gpointer user_data)
394{
395 (void)user_data;
397}
398
399/* see queues.h */
401{
402 guint n = g_queue_get_length(history);
403 g_queue_foreach(history, (GFunc)queues_destroy_notification, NULL);
404 g_queue_clear(history);
405 return n;
406}
407
408/* see queues.h */
410{
411 if (g_queue_is_empty(history))
412 return;
413
414 struct notification *n = g_queue_pop_tail(history);
415 n->redisplayed = true;
416 n->timeout = settings.sticky_history ? 0 : n->timeout;
417 g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
418}
419
420/* see queues.h */
422{
423 struct notification *n = NULL;
424
425 if (g_queue_is_empty(history))
426 return;
427
428 // search through the history buffer
429 for (GList *iter = g_queue_peek_head_link(history); iter;
430 iter = iter->next) {
431 struct notification *cur = iter->data;
432 if (cur->id == id) {
433 n = cur;
434 break;
435 }
436 }
437
438 // must be a valid notification
439 if (n == NULL)
440 return;
441
442 g_queue_remove(history, n);
443 n->redisplayed = true;
444 n->timeout = settings.sticky_history ? 0 : n->timeout;
445 g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
446}
447
448/* see queues.h */
450{
451 if (!n->history_ignore) {
452 guint maxlen = settings.history_length;
453 if (settings.history_length > 0 && history->length >= maxlen) {
454 struct notification *to_free = g_queue_pop_head(history);
455 notification_unref(to_free);
456 }
457
458 g_queue_push_tail(history, n);
459 } else {
461 }
462}
463
464/* see queues.h */
466{
467 while (displayed->length > 0) {
468 queues_notification_close(g_queue_peek_head_link(displayed)->data, REASON_USER);
469 }
470
471 while (waiting->length > 0) {
472 queues_notification_close(g_queue_peek_head_link(waiting)->data, REASON_USER);
473 }
474}
475
476/* see queues.h */
478 struct notification *n = NULL;
479
480 if (g_queue_is_empty(history))
481 return false;
482
483 for (GList *iter = g_queue_peek_head_link(history); iter;
484 iter = iter->next) {
485 struct notification *cur = iter->data;
486 if (cur->id == id) {
487 n = cur;
488 break;
489 }
490 }
491
492 if (n == NULL)
493 return false;
494
495 g_queue_remove(history, n);
497 return true;
498}
499
500/* see queues.h */
501void queues_update(struct dunst_status status, gint64 time)
502{
503 GList *iter, *nextiter;
504
505 /* Move back all notifications, which aren't eligible to get shown anymore
506 * Will move the notifications back to waiting, if dunst isn't running or fullscreen
507 * and notifications is not eligible to get shown anymore */
508 iter = g_queue_peek_head_link(displayed);
509 while (iter) {
510 struct notification *n = iter->data;
511 nextiter = iter->next;
512
513 if (notification_is_locked(n)) {
514 iter = nextiter;
515 continue;
516 }
517
518 if (n->marked_for_closure) {
519 queues_notification_close(n, n->marked_for_closure);
520 n->marked_for_closure = 0;
521 iter = nextiter;
522 continue;
523 }
524
525 if (n->marked_for_removal) {
527 n->marked_for_removal = 0;
528 iter = nextiter;
529 continue;
530 }
531
532
533 if (queues_notification_is_finished(n, status, time)) {
535 iter = nextiter;
536 continue;
537 }
538
539 if (!queues_notification_is_ready(n, status, true)) {
540 g_queue_delete_link(displayed, iter);
541 g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL);
542 iter = nextiter;
543 continue;
544 }
545
546 iter = nextiter;
547 }
548
549 int cur_displayed_limit;
550 if (settings.notification_limit == 0)
551 cur_displayed_limit = INT_MAX;
552 else if ( settings.indicate_hidden
553 && settings.notification_limit > 1
554 && displayed->length + waiting->length > settings.notification_limit)
555 cur_displayed_limit = settings.notification_limit-1;
556 else
557 cur_displayed_limit = settings.notification_limit;
558
559 /* move notifications from queue to displayed */
560 iter = g_queue_peek_head_link(waiting);
561 while (displayed->length < cur_displayed_limit && iter) {
562 struct notification *n = iter->data;
563 nextiter = iter->next;
564
565 ASSERT_OR_RET(n,);
566
567 if (!queues_notification_is_ready(n, status, false)) {
568 iter = nextiter;
569 continue;
570 }
571
572 n->start = time;
574
575 if (n->skip_display && !n->redisplayed) {
577 } else {
578 g_queue_delete_link(waiting, iter);
579 g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL);
580 }
581
582 iter = nextiter;
583 }
584
585 /* if necessary, push the overhanging notifications from displayed to waiting again */
586 while (displayed->length > cur_displayed_limit) {
587 struct notification *n = g_queue_pop_tail(displayed);
588 g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); //TODO: actually it should be on the head if unsorted
589 }
590
591 /* If displayed is actually full, let the more important notifications
592 * from waiting seep into displayed.
593 */
594 if (settings.sort && displayed->length == cur_displayed_limit) {
595 GList *i_waiting, *i_displayed;
596
597 while ( (i_waiting = g_queue_peek_head_link(waiting))
598 && (i_displayed = g_queue_peek_tail_link(displayed))) {
599
600 while (i_waiting && ! queues_notification_is_ready(i_waiting->data, status, false)) {
601 i_waiting = i_waiting->prev;
602 }
603
604 if (i_waiting && notification_cmp(i_displayed->data, i_waiting->data) > 0) {
605 struct notification *todisp = i_waiting->data;
606
607 todisp->start = time;
609
610 queues_swap_notifications(displayed, i_displayed, waiting, i_waiting);
611 } else {
612 break;
613 }
614 }
615 }
616 signal_length_propertieschanged();
617}
618
619/* see queues.h */
620gint64 queues_get_next_datachange(gint64 time)
621{
622 gint64 wakeup_time = G_MAXINT64;
623 gint64 next_second = time + S2US(1) - (time % S2US(1));
624
625 for (GList *iter = g_queue_peek_head_link(displayed); iter;
626 iter = iter->next) {
627 struct notification *n = iter->data;
628 gint64 timeout_ts = n->start + n->timeout;
629
630 if (n->timeout > 0 && n->locked == 0) {
631 if (timeout_ts > time)
632 wakeup_time = MIN(wakeup_time, timeout_ts);
633 else
634 // while we're processing or while locked, the notification already timed out
635 return time;
636 }
637
638 if (settings.show_age_threshold >= 0) {
639 gint64 age = time - n->timestamp;
640
641 if (age > settings.show_age_threshold - S2US(1)) {
642 /* Notification age should be updated -- sleep
643 * until the next turn of second.
644 * This ensures that all notifications' ages
645 * will change at once, and that at most one
646 * update will occur each second for this
647 * purpose. */
648 wakeup_time = MIN(wakeup_time, next_second);
649 }
650 else
651 wakeup_time = MIN(wakeup_time, n->timestamp + settings.show_age_threshold);
652 }
653 }
654
655 return wakeup_time != G_MAXINT64 ? wakeup_time : -1;
656}
657
658
659
660
661/* see queues.h */
663{
664 assert(id > 0);
665
666 GQueue *recqueues[] = { displayed, waiting, history };
667 for (size_t i = 0; i < sizeof(recqueues)/sizeof(GQueue*); i++) {
668 for (GList *iter = g_queue_peek_head_link(recqueues[i]); iter;
669 iter = iter->next) {
670 struct notification *cur = iter->data;
671 if (cur->id == id)
672 return cur;
673 }
674 }
675
676 return NULL;
677}
678
680{
681 GQueue *recqueues[] = { displayed, waiting, history };
682 for (size_t i = 0; i < sizeof(recqueues)/sizeof(GQueue*); i++) {
683 for (GList *iter = g_queue_peek_head_link(recqueues[i]); iter;
684 iter = iter->next) {
685 struct notification *cur = iter->data;
686 if (cur->original) {
687 rule_apply(cur->original, cur, false);
688 }
689 rule_apply_all(cur);
690 }
691 }
692}
693
699static void teardown_notification(gpointer data)
700{
701 struct notification *n = data;
703}
704
705/* see queues.h */
707{
708 g_queue_free_full(history, teardown_notification);
709 history = NULL;
710 g_queue_free_full(displayed, teardown_notification);
711 displayed = NULL;
712 g_queue_free_full(waiting, teardown_notification);
713 waiting = NULL;
714}
reason
The reasons according to the notification spec.
Definition dbus.h:18
@ REASON_TIME
The notification timed out.
Definition dbus.h:20
@ REASON_USER
The user closed the notification.
Definition dbus.h:21
Main event loop logic.
Logging subsystem and helpers.
int notification_cmp_data(const void *va, const void *vb, void *data)
Wrapper for notification_cmp to match glib's compare functions signature.
int notification_cmp(const struct notification *a, const struct notification *b)
Helper function to compare two given notifications.
void notification_run_script(struct notification *n)
Run the script associated with the given notification.
void notification_icon_replace_path(struct notification *n, const char *new_icon)
Replace the current notification's icon with the icon specified by path.
void notification_transfer_icon(struct notification *from, struct notification *to)
Transfer the image surface of from to to.
void notification_print(const struct notification *n)
print a human readable representation of the given notification to stdout.
void notification_unref(struct notification *n)
Decrease the reference counter of the notification.
Notification type definitions.
@ FS_SHOW
Show the message when in fullscreen mode.
@ FS_PUSHBACK
When entering fullscreen mode, push the notification back to waiting.
int queues_notification_insert(struct notification *n)
Insert a fully initialized notification into queues.
Definition queues.c:169
static GQueue * history
history of displayed notifications
Definition queues.c:25
unsigned int queues_length_waiting(void)
Returns the current amount of notifications, which are waiting to get displayed.
Definition queues.c:55
void queues_notification_remove(struct notification *n, enum reason reason)
Remove the given notification from all queues.
Definition queues.c:386
static bool queues_stack_by_tag(struct notification *n)
Replaces the first notification of the same stack_tag.
Definition queues.c:280
void queues_init(void)
Initialise necessary queues.
Definition queues.c:33
static GQueue * waiting
all new notifications get into here
Definition queues.c:23
static void teardown_notification(gpointer data)
Helper function for queues_teardown() to free a single notification.
Definition queues.c:699
GList * queues_get_displayed(void)
Receive the current list of displayed notifications.
Definition queues.c:41
void queues_history_pop(void)
Pushes the latest notification of history to the displayed queue and removes it from history.
Definition queues.c:409
void queues_teardown(void)
Remove all notifications from all list and free the notifications.
Definition queues.c:706
static GQueue * displayed
currently displayed notifications
Definition queues.c:24
void queues_notification_close(struct notification *n, enum reason reason)
Close the given notification.
Definition queues.c:380
gint64 queues_get_next_datachange(gint64 time)
Calculate the distance to the next event, when an element in the queues changes.
Definition queues.c:620
void queues_reapply_all_rules(void)
Reapply all rules to the queue (used when reloading configs)
Definition queues.c:679
guint queues_history_clear(void)
Removes all notifications from history Returns the number of removed notifications.
Definition queues.c:400
unsigned int queues_length_history(void)
Returns the current amount of notifications, which are already in history.
Definition queues.c:67
static bool queues_notification_is_finished(struct notification *n, struct dunst_status status, gint64 time)
Check if a notification has timed out.
Definition queues.c:136
void queues_notification_close_id(gint id, enum reason reason)
Close the notification that has n->id == id.
Definition queues.c:354
static bool queues_notification_is_ready(const struct notification *n, struct dunst_status status, bool shown)
Check if a notification is eligible to get shown.
Definition queues.c:113
static bool queues_stack_duplicate(struct notification *n)
Replaces duplicate notification and stacks it.
Definition queues.c:221
unsigned int queues_length_displayed(void)
Returns the current amount of notifications, which are shown in the UI.
Definition queues.c:61
void queues_history_pop_by_id(gint id)
Pushes the latest notification found in the history buffer identified by it's assigned id.
Definition queues.c:421
void queues_history_push_all(void)
Push all waiting and displayed notifications to history.
Definition queues.c:465
bool queues_notification_replace_id(struct notification *new)
Replace the notification which matches the id field of the new notification.
Definition queues.c:328
static void queues_swap_notifications(GQueue *queueA, GList *elemA, GQueue *queueB, GList *elemB)
Swap two given queue elements.
Definition queues.c:89
struct notification * queues_get_head_waiting(void)
Get the highest notification in line.
Definition queues.c:47
void queues_history_push(struct notification *n)
Push a single notification to history The given notification has to be removed its queue.
Definition queues.c:449
void queues_update(struct dunst_status status, gint64 time)
Move inserted notifications from waiting queue to displayed queue and show them.
Definition queues.c:501
struct notification * queues_get_by_id(gint id)
Get the notification which has the given id in the displayed and waiting queue or NULL if not found.
Definition queues.c:662
GList * queues_get_history(void)
Recieve the list of all notifications encountered.
Definition queues.c:73
bool queues_history_remove_by_id(gint id)
Removes an notification identified by the given id from the history.
Definition queues.c:477
Queues for history, waiting and displayed notifications.
Rules managment and helpers.
Type definitions for settings.
char * stack_tag
stack notifications by tag
char * msg
formatted message
int locked
If non-zero the notification is locked.
char * iconname
plain icon information (may be a path or just a name) as recieved from dbus.
int skip_display
insert notification into history, skipping initial waiting and display
int progress
percentage (-1: undefined)
gint64 icon_time
Time of reception of the icon (or opening of the file in case of a path)
int history_ignore
push to history or free directly
cairo_surface_t * icon
The raw cached icon data used to draw.
guint8 marked_for_removal
If set, the notification is marked for removal in history.
gint64 timestamp
arrival time (in milliseconds)
char * icon_id
Plain icon information, which acts as the icon's id.
enum behavior_fullscreen fullscreen
The instruction what to do with it, when desktop enters fullscreen.
bool redisplayed
has been displayed before?
gint64 start
begin of current display (in milliseconds)
bool transient
timeout albeit user is idle
gint64 timeout
time to display (in milliseconds)
int dup_count
amount of duplicate notifications stacked onto this
gint64 modification_time(const char *path)
Get the modification time of the file at path.
Definition utils.c:341
gint64 time_monotonic_now(void)
Get the current monotonic time.
Definition utils.c:315
bool is_like_path(const char *string)
Check if the strings looks like a path.
Definition utils.c:495
String, time and other various helpers.
#define STR_EMPTY(s)
Test if a string is NULL or empty.
Definition utils.h:20
#define S2US(s)
Convert seconds into microseconds.
Definition utils.h:46
#define STR_EQ(a, b)
Test if string a and b contain the same chars.
Definition utils.h:26
#define STR_FULL(s)
Test if a string is non-NULL and not empty.
Definition utils.h:23
#define STR_NN(s)
Get a non null string from a possibly null one.
Definition utils.h:35
#define ASSERT_OR_RET(expr, val)
Assert that expr evaluates to true, if not return val.
Definition utils.h:42