Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
notification.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
8
9#include <assert.h>
10#include <errno.h>
11#include <glib.h>
12#include <libgen.h>
13#include <stdbool.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <sys/wait.h>
18#include <sys/stat.h>
19#include <unistd.h>
20
21#include "notification.h"
22#include "dbus.h"
23#include "dunst.h"
24#include "icon.h"
25#include "log.h"
26#include "markup.h"
27#include "menu.h"
28#include "queues.h"
29#include "utils.h"
30#include "draw.h"
31#include "icon-lookup.h"
32#include "settings_data.h"
33
34static void notification_extract_urls(struct notification *n);
35static void notification_format_message(struct notification *n);
36
37/* see notification.h */
39{
40 switch (in) {
41 case FS_SHOW: return "show";
42 case FS_DELAY: return "delay";
43 case FS_PUSHBACK: return "pushback";
44 case FS_NULL: return "(null)";
45 default:
46 LOG_E("Invalid %s enum value in %s:%d", "fullscreen", __FILE__, __LINE__);
47 break;
48 }
49}
50
52 gint refcount;
53};
54
55/* see notification.h */
56void notification_print(const struct notification *n)
57{
58 //TODO: use logging info for this
59 printf("{\n");
60 printf("\tappname: '%s'\n", STR_NN(n->appname));
61 printf("\tsummary: '%s'\n", STR_NN(n->summary));
62 printf("\tbody: '%s'\n", STR_NN(n->body));
63 printf("\ticon: '%s'\n", STR_NN(n->iconname));
64 printf("\traw_icon: %s\n", (n->icon_id && STR_EMPTY(n->iconname)) ? "true" : "false");
65 printf("\ticon_id: '%s'\n", STR_NN(n->icon_id));
66 printf("\ticon_time: %"G_GINT64_FORMAT"\n", n->icon_time);
67 printf("\tdesktop_entry: '%s'\n", n->desktop_entry ? n->desktop_entry : "");
68 printf("\tcategory: '%s'\n", STR_NN(n->category));
69 printf("\ttimeout: %"G_GINT64_FORMAT"\n", n->timeout/1000);
70 printf("\tstart: %"G_GINT64_FORMAT"\n", n->start);
71 printf("\ttimestamp: %"G_GINT64_FORMAT"\n", n->timestamp);
72 printf("\turgency: %s\n", notification_urgency_to_string(n->urgency));
73 printf("\ttransient: %d\n", n->transient);
74 printf("\tformatted: '%s'\n", STR_NN(n->msg));
75 char buf[10];
76 printf("\tfg: %s\n", STR_NN(color_to_string(n->colors.fg, buf)));
77 printf("\tbg: %s\n", STR_NN(color_to_string(n->colors.bg, buf)));
78 printf("\tframe: %s\n", STR_NN(color_to_string(n->colors.frame, buf)));
79
80 char *grad = gradient_to_string(n->colors.highlight);
81 printf("\thighlight: %s\n", STR_NN(grad));
82 g_free(grad);
83
84 printf("\tfullscreen: %s\n", enum_to_string_fullscreen(n->fullscreen));
85 printf("\tformat: '%s'\n", STR_NN(n->format));
86 printf("\tprogress: %d\n", n->progress);
87 printf("\tstack_tag: '%s'\n", (n->stack_tag ? n->stack_tag : ""));
88 printf("\tid: %d\n", n->id);
89 if (n->urls) {
90 char *urls = string_replace_all("\n", "\t\t\n", g_strdup(n->urls));
91 printf("\turls:\n");
92 printf("\t{\n");
93 printf("\t\t'%s'\n", STR_NN(urls));
94 printf("\t}\n");
95 g_free(urls);
96 } else {
97 printf("\turls: {}\n");
98 }
99 if (g_hash_table_size(n->actions) == 0) {
100 printf("\tactions: {}\n");
101 } else {
102 gpointer p_key, p_value;
103 GHashTableIter iter;
104 g_hash_table_iter_init(&iter, n->actions);
105 printf("\tactions: {\n");
106 while (g_hash_table_iter_next(&iter, &p_key, &p_value))
107 printf("\t\t\"%s\": \"%s\"\n", (char*)p_key, STR_NN((char*)p_value));
108 printf("\t}\n");
109 }
110 printf("\tscript_count: %d\n", n->script_count);
111 if (n->script_count > 0) {
112 printf("\tscripts: ");
113 for (int i = 0; i < n->script_count; i++) {
114 printf("'%s' ", STR_NN(n->scripts[i]));
115 }
116 printf("\n");
117 }
118 printf("}\n");
119 fflush(stdout);
120}
121
122/* see notification.h */
124{
125 if (n->script_run && !settings.always_run_script)
126 return;
127
128 n->script_run = true;
129
130 const char *appname = n->appname ? n->appname : "";
131 const char *summary = n->summary ? n->summary : "";
132 const char *body = n->body ? n->body : "";
133 const char *icon = n->iconname ? n->iconname : "";
134
135 const char *urgency = notification_urgency_to_string(n->urgency);
136
137 for(int i = 0; i < n->script_count; i++) {
138
139 const char *script = n->scripts[i];
140
141 if (STR_EMPTY(script))
142 continue;
143
144 int pid1 = fork();
145
146 if (pid1) {
147 int status;
148 waitpid(pid1, &status, 0);
149 } else {
150 // second fork to prevent zombie processes
151 int pid2 = fork();
152 if (pid2) {
153 exit(0);
154 } else {
155 // Set environment variables
156 gchar *n_id_str = g_strdup_printf("%i", n->id);
157 gchar *n_progress_str = g_strdup_printf("%i", n->progress);
158 gchar *n_timeout_str = g_strdup_printf("%"G_GINT64_FORMAT, n->timeout/1000);
159 gchar *n_timestamp_str = g_strdup_printf("%"G_GINT64_FORMAT, n->timestamp / 1000);
160 safe_setenv("DUNST_APP_NAME", appname);
161 safe_setenv("DUNST_SUMMARY", summary);
162 safe_setenv("DUNST_BODY", body);
163 safe_setenv("DUNST_ICON_PATH", n->icon_path);
164 safe_setenv("DUNST_URGENCY", urgency);
165 safe_setenv("DUNST_ID", n_id_str);
166 safe_setenv("DUNST_PROGRESS", n_progress_str);
167 safe_setenv("DUNST_CATEGORY", n->category);
168 safe_setenv("DUNST_STACK_TAG", n->stack_tag);
169 safe_setenv("DUNST_URLS", n->urls);
170 safe_setenv("DUNST_TIMEOUT", n_timeout_str);
171 safe_setenv("DUNST_TIMESTAMP", n_timestamp_str);
172 safe_setenv("DUNST_DESKTOP_ENTRY", n->desktop_entry);
173
174 execlp(script,
175 script,
176 appname,
177 summary,
178 body,
179 icon,
180 urgency,
181 (char *)NULL);
182
183 LOG_W("Unable to run script %s: %s", n->scripts[i], strerror(errno));
184 exit(EXIT_FAILURE);
185 }
186 }
187 }
188}
189
190/*
191 * Helper function to convert an urgency to a string
192 */
193const char *notification_urgency_to_string(const enum urgency urgency)
194{
195 switch (urgency) {
196 case URG_NONE:
197 return "NONE";
198 case URG_LOW:
199 return "LOW";
200 case URG_NORM:
201 return "NORMAL";
202 case URG_CRIT:
203 return "CRITICAL";
204 default:
205 return "UNDEF";
206 }
207}
208
209/* see notification.h */
210int notification_cmp(const struct notification *a, const struct notification *b)
211{
212 const struct notification *a_order;
213 const struct notification *b_order;
214 if(settings.sort == SORT_TYPE_UPDATE && settings.origin & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM){
215 a_order = b;
216 b_order = a;
217 } else {
218 a_order = a;
219 b_order = b;
220 }
221
222 if(settings.sort == SORT_TYPE_URGENCY_ASCENDING){
223 if(a_order->urgency != b_order->urgency){
224 return a_order->urgency - b_order->urgency;
225 }
226 } else if (settings.sort == SORT_TYPE_URGENCY_DESCENDING) {
227 if(a_order->urgency != b_order->urgency){
228 return b_order->urgency - a_order->urgency;
229 }
230 } else if(settings.sort == SORT_TYPE_UPDATE){
231 return b_order->timestamp - a_order->timestamp;
232 }
233
234 return a_order->id - b_order->id;
235}
236
237/* see notification.h */
238int notification_cmp_data(const void *va, const void *vb, void *data)
239{
240 (void)data;
241
242 struct notification *a = (struct notification *) va;
243 struct notification *b = (struct notification *) vb;
244
245 return notification_cmp(a, b);
246}
247
248bool notification_is_duplicate(const struct notification *a, const struct notification *b)
249{
250 return STR_EQ(a->appname, b->appname)
251 && STR_EQ(a->summary, b->summary)
252 && STR_EQ(a->body, b->body)
253 && a->urgency == b->urgency
254 && (a->icon_position == ICON_OFF || b->icon_position == ICON_OFF
255 || (a->icon_id && b->icon_id ? STR_EQ(a->icon_id, b->icon_id)
256 : (a->iconname && b->iconname ? STR_EQ(a->iconname, b->iconname) : 1)));
257}
258
259bool notification_is_locked(struct notification *n) {
260 assert(n);
261
262 return g_atomic_int_get(&n->locked) != 0;
263}
264
265struct notification* notification_lock(struct notification *n) {
266 assert(n);
267
268 g_atomic_int_set(&n->locked, 1);
270
271 return n;
272}
273
274struct notification* notification_unlock(struct notification *n) {
275 assert(n);
276
277 g_atomic_int_set(&n->locked, 0);
279
280 return n;
281}
282
283static void notification_private_free(NotificationPrivate *p)
284{
285 g_free(p);
286}
287
288/* see notification.h */
290{
291 assert(n->priv->refcount > 0);
292 return g_atomic_int_get(&n->priv->refcount);
293}
294
295/* see notification.h */
297{
298 assert(n->priv->refcount > 0);
299 g_atomic_int_inc(&n->priv->refcount);
300}
301
302/* see notification.h */
304{
305 ASSERT_OR_RET(n,);
306
307 assert(n->priv->refcount > 0);
308 if (!g_atomic_int_dec_and_test(&n->priv->refcount))
309 return;
310
311 if (n->original)
312 rule_free(n->original);
313
314 g_free(n->dbus_client);
315 g_free(n->appname);
316 g_free(n->summary);
317 g_free(n->body);
318 g_free(n->category);
319 g_free(n->desktop_entry);
320
321 g_free(n->icon_id);
322 g_free(n->iconname);
323 g_free(n->icon_path);
324 g_free(n->default_icon_name);
325
326 g_hash_table_unref(n->actions);
327 g_free(n->default_action_name);
328
329 if (n->icon)
330 cairo_surface_destroy(n->icon);
331
332 notification_private_free(n->priv);
333
334 gradient_release(n->colors.highlight);
335
336 g_free(n->format);
337 g_strfreev(n->scripts);
338 g_free(n->stack_tag);
339
340 g_free(n->msg);
341 g_free(n->text_to_render);
342 g_free(n->urls);
343
344 g_free(n);
345}
346
348{
349 // Transfer icon surface
350 to->icon = from->icon;
351 to->icon_id = from->icon_id;
352 to->icon_time = from->icon_time;
353
354 // Prevent the surface being freed by the old notification
355 from->icon = NULL;
356 from->icon_id = NULL;
357}
358
359void notification_icon_replace_path(struct notification *n, const char *new_icon)
360{
361 ASSERT_OR_RET(n && n->icon_position != ICON_OFF,);
362 ASSERT_OR_RET(new_icon,);
363
364 if (n->iconname && n->icon && STR_EQ(n->iconname, new_icon))
365 return;
366
367 // make sure it works, even if n->iconname is passed as new_icon
368 if (n->iconname != new_icon) {
369 g_free(n->iconname);
370 n->iconname = g_strdup(new_icon);
371 }
372
373 cairo_surface_destroy(n->icon);
374 n->icon = NULL;
375 g_clear_pointer(&n->icon_id, g_free);
376
377 g_free(n->icon_path);
379 if (n->icon_path) {
380 GdkPixbuf *pixbuf = get_pixbuf_from_file(n->icon_path, &n->icon_id,
382 draw_get_scale());
383 if (pixbuf) {
384 n->icon = gdk_pixbuf_to_cairo_surface(pixbuf);
385 n->icon_time = time_now();
386 g_object_unref(pixbuf);
387 } else {
388 LOG_W("Failed to load icon from path: '%s'", n->icon_path);
389 }
390 }
391}
392
393void notification_icon_replace_data(struct notification *n, GVariant *new_icon)
394{
395 ASSERT_OR_RET(n && n->icon_position != ICON_OFF,);
396 ASSERT_OR_RET(new_icon,);
397
398 cairo_surface_destroy(n->icon);
399 n->icon = NULL;
400 g_clear_pointer(&n->icon_id, g_free);
401
402 GdkPixbuf *icon = icon_get_for_data(new_icon, &n->icon_id,
403 draw_get_scale(), n->min_icon_size, n->max_icon_size);
404 n->icon = gdk_pixbuf_to_cairo_surface(icon);
405 if (icon) {
406 n->icon_time = time_now();
407 g_object_unref(icon);
408 }
409}
410
411void notification_replace_format(struct notification *n, const char *format)
412{
413 g_free(n->format);
414 n->format = g_strdup(format);
415}
416
417/* see notification.h */
419 char **needle,
420 const char *replacement,
421 enum markup_mode markup_mode)
422{
423
424 assert(*needle[0] == '%');
425 // needle has to point into haystack (but not on the last char)
426 assert(*needle >= *haystack);
427 assert(*needle - *haystack < strlen(*haystack) - 1);
428
429 int pos = *needle - *haystack;
430
431 char *input = markup_transform(g_strdup(replacement), markup_mode);
432 *haystack = string_replace_at(*haystack, pos, 2, input);
433
434 // point the needle to the next char
435 // which was originally in haystack
436 *needle = *haystack + pos + strlen(input);
437
438 g_free(input);
439}
440
441static NotificationPrivate *notification_private_create(void)
442{
443 NotificationPrivate *priv = g_malloc0(sizeof(NotificationPrivate));
444 g_atomic_int_set(&priv->refcount, 1);
445
446 return priv;
447}
448
449/* see notification.h */
451{
452 struct notification *n = g_malloc0(sizeof(struct notification));
453
454 n->priv = notification_private_create();
455
456 /* Unparameterized default values */
457 n->first_render = true;
458 n->markup = MARKUP_FULL;
459 n->format = g_strdup(settings.format);
460
462
463 n->urgency = URG_NORM;
464 n->timeout = -1;
465 n->dbus_timeout = -1;
466
467 n->transient = false;
468 n->progress = -1;
469 n->word_wrap = true;
470 n->ellipsize = PANGO_ELLIPSIZE_MIDDLE;
471 n->alignment = PANGO_ALIGN_LEFT;
472 n->progress_bar_alignment = PANGO_ALIGN_CENTER;
473 n->hide_text = false;
474 n->icon_position = ICON_LEFT;
475 n->min_icon_size = 32;
476 n->max_icon_size = 32;
477 n->receiving_raw_icon = false;
478
479 struct color invalid = COLOR_UNINIT;
480 n->colors.fg = invalid;
481 n->colors.bg = invalid;
482 n->colors.frame = invalid;
483 n->colors.highlight = NULL;
484
485 n->script_run = false;
486 n->dbus_valid = false;
487
488 n->fullscreen = FS_SHOW;
489
490 n->original = NULL;
491
492 n->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
493 n->default_action_name = g_strdup("default");
494
495 n->script_count = 0;
496 return n;
497}
498
499/* see notification.h */
501{
502 /* default to empty string to avoid further NULL faults */
503 n->appname = n->appname ? n->appname : g_strdup("unknown");
504 n->summary = n->summary ? n->summary : g_strdup("");
505 n->body = n->body ? n->body : g_strdup("");
506 n->category = n->category ? n->category : g_strdup("");
507
508 /* sanitize urgency */
509 if (n->urgency < URG_MIN)
510 n->urgency = URG_LOW;
511 if (n->urgency > URG_MAX)
512 n->urgency = URG_CRIT;
513
514 /* Timeout processing */
515 if (n->timeout < 0)
516 n->timeout = settings.timeouts[n->urgency];
517
518 /* Color hints */
519 struct notification_colors defcolors;
520 switch (n->urgency) {
521 case URG_LOW:
522 defcolors = settings.colors_low;
523 break;
524 case URG_NORM:
525 defcolors = settings.colors_norm;
526 break;
527 case URG_CRIT:
528 defcolors = settings.colors_crit;
529 break;
530 default:
531 g_error("Unhandled urgency type: %d", n->urgency);
532 }
533 if (!COLOR_VALID(n->colors.fg)) n->colors.fg = defcolors.fg;
534 if (!COLOR_VALID(n->colors.bg)) n->colors.bg = defcolors.bg;
535 if (!COLOR_VALID(n->colors.frame)) n->colors.frame = defcolors.frame;
536
537 if (!GRADIENT_VALID(n->colors.highlight)) {
538 gradient_release(n->colors.highlight);
539 n->colors.highlight = gradient_acquire(defcolors.highlight);
540 }
541
542 /* Sanitize misc hints */
543 if (n->progress < 0)
544 n->progress = -1;
545
546 n->override_pause_level = 0;
547
548 /* Process rules */
549 rule_apply_all(n);
550
551 if (g_str_has_prefix(n->summary, "DUNST_COMMAND_")) {
552 const char *msg = "DUNST_COMMAND_* has been removed, please switch to dunstctl. See #830 for more details. https://github.com/dunst-project/dunst/pull/830";
553 LOG_W("%s", msg);
554 n->body = string_append(n->body, msg, "\n");
555 }
556
557 /* Icon handling */
558 if (STR_EMPTY(n->iconname))
559 g_clear_pointer(&n->iconname, g_free);
560 if (!n->icon && !n->iconname && n->default_icon_name) {
561 n->iconname = g_strdup(n->default_icon_name);
562 }
563 if (!n->icon && !n->iconname)
564 n->iconname = g_strdup(settings.icons[n->urgency]);
565
566 /* UPDATE derived fields */
567 notification_extract_urls(n);
568 notification_format_message(n);
569
570 /* Update timeout: dbus_timeout has priority over timeout */
571 if (n->dbus_timeout >= 0)
572 n->timeout = n->dbus_timeout;
573
574}
575
576static void notification_format_message(struct notification *n)
577{
578 g_clear_pointer(&n->msg, g_free);
579
580 n->msg = string_replace_all("\\n", "\n", g_strdup(n->format));
581 n->msg = string_replace_all("\\t", "\t", n->msg);
582
583 /* replace all formatter */
584 for(char *substr = strchr(n->msg, '%');
585 substr && *substr;
586 substr = strchr(substr, '%')) {
587
588 char pg[16];
589 char *icon_tmp;
590
591 switch(substr[1]) {
592 case 'a':
594 &n->msg,
595 &substr,
596 n->appname,
597 MARKUP_NO);
598 break;
599 case 's':
601 &n->msg,
602 &substr,
603 n->summary,
604 MARKUP_NO);
605 break;
606 case 'b':
608 &n->msg,
609 &substr,
610 n->body,
611 n->markup);
612 break;
613 case 'c':
615 &n->msg,
616 &substr,
617 n->category,
618 MARKUP_NO);
619 break;
620 case 'S':
622 &n->msg,
623 &substr,
624 n->stack_tag ? n->stack_tag : "",
625 MARKUP_NO);
626 break;
627 case 'I':
628 icon_tmp = g_strdup(n->iconname);
630 &n->msg,
631 &substr,
632 icon_tmp ? basename(icon_tmp) : "",
633 MARKUP_NO);
634 g_free(icon_tmp);
635 break;
636 case 'i':
638 &n->msg,
639 &substr,
640 n->iconname ? n->iconname : "",
641 MARKUP_NO);
642 break;
643 case 'p':
644 if (n->progress != -1)
645 sprintf(pg, "[%3d%%]", n->progress);
646
648 &n->msg,
649 &substr,
650 n->progress != -1 ? pg : "",
651 MARKUP_NO);
652 break;
653 case 'n':
654 if (n->progress != -1)
655 sprintf(pg, "%d", n->progress);
656
658 &n->msg,
659 &substr,
660 n->progress != -1 ? pg : "",
661 MARKUP_NO);
662 break;
663 case '%':
665 &n->msg,
666 &substr,
667 "%",
668 MARKUP_NO);
669 break;
670 case '\0':
671 LOG_W("format_string has trailing %% character. "
672 "To escape it use %%%%.");
673 substr++;
674 break;
675 default:
676 LOG_W("format_string %%%c is unknown.", substr[1]);
677 // shift substr pointer forward,
678 // as we can't interpret the format string
679 substr++;
680 break;
681 }
682 }
683
684 n->msg = g_strchomp(n->msg);
685
686 /* truncate overlong messages */
687 if (strnlen(n->msg, DUNST_NOTIF_MAX_CHARS + 1) > DUNST_NOTIF_MAX_CHARS) {
688 char * buffer = g_strndup(n->msg, DUNST_NOTIF_MAX_CHARS);
689 g_free(n->msg);
690 n->msg = buffer;
691 }
692}
693
694static void notification_extract_urls(struct notification *n)
695{
696 g_clear_pointer(&n->urls, g_free);
697
698 char *urls_in = string_append(g_strdup(n->summary), n->body, " ");
699
700 char *urls_a = NULL;
701 char *urls_img = NULL;
702 markup_strip_a(&urls_in, &urls_a);
703 markup_strip_img(&urls_in, &urls_img);
704 // remove links and images first to not confuse
705 // plain urls extraction
706 char *urls_text = extract_urls(urls_in);
707
708 n->urls = string_append(n->urls, urls_a, "\n");
709 n->urls = string_append(n->urls, urls_img, "\n");
710 n->urls = string_append(n->urls, urls_text, "\n");
711
712 g_free(urls_in);
713 g_free(urls_a);
714 g_free(urls_img);
715 g_free(urls_text);
716}
717
718
719void notification_update_text_to_render(struct notification *n)
720{
721 g_clear_pointer(&n->text_to_render, g_free);
722
723 char *buf = NULL;
724
725 char *msg = g_strchomp(n->msg);
726
727 /* print dup_count and msg */
728 if ((n->dup_count > 0 && !settings.hide_duplicate_count)
729 && (g_hash_table_size(n->actions) || n->urls) && settings.show_indicators) {
730 buf = g_strdup_printf("(%d%s%s) %s",
731 n->dup_count,
732 g_hash_table_size(n->actions) ? "A" : "",
733 n->urls ? "U" : "", msg);
734 } else if ((g_hash_table_size(n->actions) || n->urls) && settings.show_indicators) {
735 buf = g_strdup_printf("(%s%s) %s",
736 g_hash_table_size(n->actions) ? "A" : "",
737 n->urls ? "U" : "", msg);
738 } else if (n->dup_count > 0 && !settings.hide_duplicate_count) {
739 buf = g_strdup_printf("(%d) %s", n->dup_count, msg);
740 } else {
741 buf = g_strdup(msg);
742 }
743
744 /* print age */
745 gint64 hours, minutes, seconds;
746 // Timestamp is floored to the second for display purposes -- see queues.c
747 gint64 t_delta = time_monotonic_now() - (n->timestamp - n->timestamp % S2US(1));
748
749 if (settings.show_age_threshold >= 0
750 && t_delta >= settings.show_age_threshold) {
751 hours = US2S(t_delta) / 3600;
752 minutes = US2S(t_delta) / 60 % 60;
753 seconds = US2S(t_delta) % 60;
754
755 char *new_buf;
756 if (hours > 0) {
757 new_buf = g_strdup_printf("%s (%"G_GINT64_FORMAT"h %"G_GINT64_FORMAT"m %"G_GINT64_FORMAT"s old)",
758 buf, hours, minutes, seconds);
759 } else if (minutes > 0) {
760 new_buf = g_strdup_printf("%s (%"G_GINT64_FORMAT"m %"G_GINT64_FORMAT"s old)",
761 buf, minutes, seconds);
762 } else {
763 new_buf = g_strdup_printf("%s (%"G_GINT64_FORMAT"s old)",
764 buf, seconds);
765 }
766
767 g_free(buf);
768 buf = new_buf;
769 }
770
771 n->text_to_render = buf;
772}
773
774/* see notification.h */
776{
777 assert(n->default_action_name);
778
779 if (g_hash_table_size(n->actions)) {
780 if (g_hash_table_contains(n->actions, n->default_action_name)) {
781 signal_action_invoked(n, n->default_action_name);
782 return;
783 }
784 if (strcmp(n->default_action_name, "default") == 0 && g_hash_table_size(n->actions) == 1) {
785 GList *keys = g_hash_table_get_keys(n->actions);
786 signal_action_invoked(n, keys->data);
787 g_list_free(keys);
788 return;
789 }
791
792 } else if (n->urls) {
793 // Try urls otherwise
795 }
796}
797
798/* see notification.h */
800{
801 if (!n->urls) {
802 LOG_W("There are no URL's in this notification");
803 return;
804 }
805 if (strstr(n->urls, "\n"))
807 else
808 open_browser(n->urls);
809}
810
811/* see notification.h */
813{
814 GList *notifications = NULL;
815 notifications = g_list_append(notifications, n);
816 notification_lock(n);
817
818 context_menu_for(notifications);
819}
820
822 g_hash_table_remove_all(n->actions);
823}
824
825void notification_keep_original(struct notification *n)
826{
827 if (n->original) return;
828 n->original = g_malloc0(sizeof(struct rule));
829 *n->original = empty_rule;
830 n->original->name = g_strdup("original");
831}
DBus support and implementation of the Desktop Notifications Specification.
char * color_to_string(struct color c, char buf[10])
Stringify a color struct to a RRGGBBAA string.
Definition draw.c:68
Layout and render notifications.
Main event loop logic.
Recursive icon lookup in theme directories.
char * get_path_from_icon_name(const char *iconname, int size)
Retrieve a path from an icon name.
Definition icon.c:249
GdkPixbuf * icon_get_for_data(GVariant *data, char **id, double dpi_scale, int min_size, int max_size)
Convert a GVariant like described in GdkPixbuf, scaled according to settings.
Definition icon.c:313
GdkPixbuf * get_pixbuf_from_file(const char *filename, char **id, int min_size, int max_size, double scale)
Retrieve an icon by its full filepath, scaled according to settings.
Definition icon.c:213
Notification images loading.
Logging subsystem and helpers.
#define LOG_E
Prefix message with "[<source path>:<function name>:<line number>] ".
Definition log.h:42
void markup_strip_img(char **str, char **urls)
Remove img-tags of a string.
Definition markup.c:144
void markup_strip_a(char **str, char **urls)
Remove HTML hyperlinks of a string.
Definition markup.c:69
char * markup_transform(char *str, enum markup_mode markup_mode)
Transform the string in accordance with markup_mode and settings.ignore_newline
Definition markup.c:317
Markup handling for notifications body.
void context_menu_for(GList *notifications)
Open the context menu that lets the user select urls/actions/etc for the specified notifications.
Definition menu.c:326
char * extract_urls(const char *to_match)
Extract all urls from the given string.
Definition menu.c:72
Context menu for actions and helpers.
void notification_do_action(struct notification *n)
If the notification has an action named n->default_action_name or there is only one action and n->def...
void notification_ref(struct notification *n)
Increase the reference counter of the notification.
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_invalidate_actions(struct notification *n)
Remove all client action data from the notification.
void notification_open_url(struct notification *n)
If the notification has exactly one url, invoke it.
void notification_run_script(struct notification *n)
Run the script associated with the given notification.
gint notification_refcount_get(struct notification *n)
Retrieve the current reference count of the 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_init(struct notification *n)
Sanitize values of notification, apply all matching rules and generate derived fields.
void notification_transfer_icon(struct notification *from, struct notification *to)
Transfer the image surface of from to to.
void notification_open_context_menu(struct notification *n)
Open the context menu for the notification.
void notification_replace_single_field(char **haystack, char **needle, const char *replacement, enum markup_mode markup_mode)
Replace the two chars where **needle points with a quoted "replacement", according to the markup sett...
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.
const char * enum_to_string_fullscreen(enum behavior_fullscreen in)
Return the string representation for fullscreen behavior.
struct notification * notification_create(void)
Create notification struct and initialise all fields with either.
void notification_icon_replace_data(struct notification *n, GVariant *new_icon)
Replace the current notification's icon with the raw icon given in the GVariant.
Notification type definitions.
urgency
Representing the urgencies according to the notification spec.
@ URG_NONE
Urgency not set (invalid)
@ URG_NORM
Normal urgency.
@ URG_MIN
Minimum value, useful for boundary checking.
@ URG_CRIT
Critical urgency.
@ URG_LOW
Low urgency.
@ URG_MAX
Maximum value, useful for boundary checking.
behavior_fullscreen
@ FS_SHOW
Show the message when in fullscreen mode.
@ FS_PUSHBACK
When entering fullscreen mode, push the notification back to waiting.
@ FS_DELAY
Delay the notification until leaving fullscreen mode.
@ FS_NULL
Invalid value.
Queues for history, waiting and displayed notifications.
@ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM
the bottom edge of the anchor rectangle
Definition settings.h:71
List of all the valid settings and values.
Definition draw.h:48
char * stack_tag
stack notifications by tag
char * icon_path
Full path to the notification's icon.
bool receiving_raw_icon
Still waiting for raw icon to be received.
PangoAlignment progress_bar_alignment
Horizontal alignment of the progress bar.
int min_icon_size
Minimum icon size.
bool script_run
Has the script been executed already?
char * desktop_entry
The desktop entry hint sent via every GApplication.
enum icon_position icon_position
Icon position (enum left,right,top,off).
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 progress
percentage (-1: undefined)
gint64 icon_time
Time of reception of the icon (or opening of the file in case of a path)
int max_icon_size
Maximum icon size.
cairo_surface_t * icon
The raw cached icon data used to draw.
gint64 timestamp
arrival time (in milliseconds)
char * text_to_render
formatted message (with age and action indicators)
char * urls
urllist delimited by '\n'
bool first_render
markup has been rendered before?
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.
gint64 start
begin of current display (in milliseconds)
bool transient
timeout albeit user is idle
char * default_icon_name
The icon that is used when no other icon is available.
char * default_action_name
The name of the action to be invoked on do_action.
gint64 timeout
time to display (in milliseconds)
gint64 dbus_timeout
time to display (in milliseconds) (set by dbus)
int dup_count
amount of duplicate notifications stacked onto this
Definition rules.h:20
gint64 time_monotonic_now(void)
Get the current monotonic time.
Definition utils.c:315
bool safe_setenv(const char *key, const char *value)
Try to set an environment variable safely.
Definition utils.c:365
char * string_replace_at(char *buf, int pos, int len, const char *repl)
Replace a substring inside a string with another string.
Definition utils.c:37
gint64 time_now(void)
Get the current real time.
Definition utils.c:333
char * string_replace_all(const char *needle, const char *replacement, char *haystack)
Replace all occurences of a substring.
Definition utils.c:66
char * string_append(char *a, const char *b, const char *sep)
Append b to string a, then concatenate both with sep (if they are non-empty).
Definition utils.c:92
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_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
#define US2S(s)
Convert microseconds into seconds.
Definition utils.h:49