Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
rules.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
8
9#include "rules.h"
10
11#include <fnmatch.h>
12#include <glib.h>
13#include <stddef.h>
14#include <regex.h>
15
16#include "dunst.h"
17#include "utils.h"
18#include "settings_data.h"
19#include "log.h"
20
21GSList *rules = NULL;
22
23// NOTE: Internal, only for rule_apply(...)
24
25#define RULE_APPLY2(nprop, rprop, defval) \
26 if (r->rprop != (defval)) { \
27 if (save && n->original->rprop == (defval)) \
28 n->original->rprop = n->nprop; \
29 n->nprop = r->rprop; \
30 }
31
32#define RULE_APPLY(prop, defval) RULE_APPLY2(prop, prop, defval)
33
34/*
35 * Apply rule to notification.
36 * If save is true the original value will be saved in the notification.
37 */
38void rule_apply(struct rule *r, struct notification *n, bool save)
39{
40 if (save) notification_keep_original(n);
41
42 RULE_APPLY2(dbus_timeout, override_dbus_timeout, -1);
43 RULE_APPLY2(transient, set_transient, -1);
44
45 RULE_APPLY(timeout, -1);
46 RULE_APPLY(urgency, URG_NONE);
47 RULE_APPLY(fullscreen, FS_NULL);
48 RULE_APPLY(history_ignore, -1);
49 RULE_APPLY(skip_display, -1);
50 RULE_APPLY(word_wrap, -1);
51 RULE_APPLY(ellipsize, -1);
52 RULE_APPLY(alignment, -1);
53 RULE_APPLY(hide_text, -1);
54 RULE_APPLY(progress_bar_alignment, -1);
55 RULE_APPLY(min_icon_size, -1);
56 RULE_APPLY(max_icon_size, -1);
57 RULE_APPLY(markup, MARKUP_NULL);
58 RULE_APPLY(icon_position, -1);
59 RULE_APPLY(override_pause_level, -1);
60
61 if (COLOR_VALID(r->fg)) {
62 if (save && !COLOR_VALID(n->original->fg)) n->original->fg = n->colors.fg;
63 n->colors.fg = r->fg;
64 }
65 if (COLOR_VALID(r->bg)) {
66 if (save && !COLOR_VALID(n->original->bg)) n->original->bg = n->colors.bg;
67 n->colors.bg = r->bg;
68 }
69 if (r->highlight != NULL) {
70 if (save && n->original->highlight == NULL) {
71 n->original->highlight = gradient_acquire(n->colors.highlight);
72 }
73 gradient_release(n->colors.highlight);
74 n->colors.highlight = gradient_acquire(r->highlight);
75 }
76 if (COLOR_VALID(r->fc)) {
77 if (save && !COLOR_VALID(n->original->fc)) n->original->fc = n->colors.frame;
78 n->colors.frame = r->fc;
79 }
80 if (r->action_name) {
81 if (save && n->original->action_name == NULL)
82 n->original->action_name = n->default_action_name;
83 else
84 g_free(n->default_action_name);
85
86 n->default_action_name = g_strdup(r->action_name);
87 }
88 if (r->set_category) {
89 if (save && n->original->set_category == NULL)
90 n->original->set_category = n->category;
91 else
92 g_free(n->category);
93
94 n->category = g_strdup(r->set_category);
95 }
96 if (r->default_icon) {
97 if (save && n->original->default_icon == NULL)
98 n->original->default_icon = n->default_icon_name;
99 else
100 g_free(n->default_icon_name);
101
102 n->default_icon_name = g_strdup(r->default_icon);
103 }
104 if (r->set_stack_tag) {
105 if (save && n->original->set_stack_tag == NULL)
106 n->original->set_stack_tag = n->stack_tag;
107 else
108 g_free(n->stack_tag);
109
110 n->stack_tag = g_strdup(r->set_stack_tag);
111 }
112 if (r->new_icon) {
113 if (save && n->original->new_icon == NULL)
114 n->original->new_icon = g_strdup(n->iconname);
115
116 // FIXME This is not efficient when the icon is replaced
117 // multiple times for the same notification. To fix this, a
118 // separate variable is needed to track if the icon is
119 // replaced, like in 86cbc1d34bb0f551461dbd466cd9e4860ae01817.
120 notification_icon_replace_path(n, r->new_icon);
121 n->receiving_raw_icon = false;
122 }
123 if (r->format != NULL) {
124 if (save && n->original->format == NULL)
125 n->original->format = n->format;
126 else
127 g_free(n->format);
128
129 n->format = g_strdup(r->format);
130 }
131 if (r->script) {
132 if (save && n->original->script == NULL)
133 n->original->script = n->script_count > 0
134 ? g_strdup(n->scripts[0])
135 : g_strdup(r->script);
136
137 n->scripts = g_renew(char *, n->scripts, n->script_count + 2);
138 n->scripts[n->script_count] = g_strdup(r->script);
139 n->scripts[n->script_count + 1] = NULL;
140 n->script_count++;
141 }
142}
143
144void rule_print(const struct rule *r)
145{
146 printf("{\n");
147 printf("\tname: '%s'\n", STR_NN(r->name));
148 printf("\tenabled: %d\n", r->enabled);
149
150 // filters
151 if (r->appname != NULL) printf("\tappname: '%s'\n", r->appname);
152 if (r->summary != NULL) printf("\tsummary: '%s'\n", r->summary);
153 if (r->body != NULL) printf("\tbody: '%s'\n", r->body);
154 if (r->icon != NULL) printf("\ticon: '%s'\n", r->icon);
155 if (r->category != NULL) printf("\tcategory: '%s'\n", r->category);
156 if (r->msg_urgency != URG_NONE) printf("\tmsg_urgency: '%s'\n", notification_urgency_to_string(r->msg_urgency));
157 if (r->stack_tag != NULL) printf("\tstack_tag: '%s'\n", r->stack_tag);
158 if (r->desktop_entry != NULL) printf("\tdesktop_entry: '%s'\n", r->desktop_entry);
159 if (r->match_dbus_timeout != -1) printf("\tmatch_dbus_timeout: %"G_GINT64_FORMAT"\n", r->match_dbus_timeout);
160 if (r->match_transient != -1) printf("\tmatch_transient: %d\n", r->match_transient);
161
162 // modifiers
163 if (r->timeout != -1) printf("\ttimeout: %"G_GINT64_FORMAT"\n", r->timeout);
164 if (r->override_dbus_timeout != -1) printf("\toverride_dbus_timeout: %"G_GINT64_FORMAT"\n", r->override_dbus_timeout);
165 if (r->markup != -1) printf("\tmarkup: %d\n", r->markup);
166 if (r->action_name != NULL) printf("\taction_name: '%s'\n", r->action_name);
167 if (r->urgency != URG_NONE) printf("\turgency: '%s'\n", notification_urgency_to_string(r->urgency));
168 if (r->history_ignore != -1) printf("\thistory_ignore: %d\n", r->history_ignore);
169 if (r->set_transient != -1) printf("\tset_transient: %d\n", r->set_transient);
170 if (r->skip_display != -1) printf("\tskip_display: %d\n", r->skip_display);
171 if (r->word_wrap != -1) printf("\tword_wrap: %d\n", r->word_wrap);
172 if (r->ellipsize != -1) printf("\tellipsize: %d\n", r->ellipsize);
173 if (r->alignment != -1) printf("\talignment: %d\n", r->alignment);
174 if (r->hide_text != -1) printf("\thide_text: %d\n", r->hide_text);
175 if (r->icon_position != -1) printf("\ticon_position: %d\n", r->icon_position);
176 if (r->min_icon_size != -1) printf("\tmin_icon_size: %d\n", r->min_icon_size);
177 if (r->max_icon_size != -1) printf("\tmax_icon_size: %d\n", r->max_icon_size);
178 if (r->override_pause_level != -1) printf("\toverride_pause_level: %d\n", r->override_pause_level);
179 if (r->new_icon != NULL) printf("\tnew_icon: '%s'\n", r->new_icon);
180 if (r->default_icon != NULL) printf("\tdefault_icon: '%s'\n", r->default_icon);
181
182 char buf[10];
183 if (COLOR_VALID(r->fg)) printf("\tfg: %s\n", color_to_string(r->fg, buf));
184 if (COLOR_VALID(r->bg)) printf("\tbg: %s\n", color_to_string(r->bg, buf));
185 if (COLOR_VALID(r->fc)) printf("\tframe: %s\n", color_to_string(r->fc, buf));
186
187 char *grad = gradient_to_string(r->highlight);
188 printf("\thighlight: %s\n", STR_NN(grad));
189 g_free(grad);
190
191 if (r->set_category != NULL) printf("\tset_category: '%s'\n", r->set_category);
192 if (r->format != NULL) printf("\tformat: '%s'\n", r->format);
193 if (r->script != NULL) printf("\tscript: '%s'\n", r->script);
194 if (r->fullscreen != FS_NULL) printf("\tfullscreen: %s\n", enum_to_string_fullscreen(r->fullscreen));
195 if (r->progress_bar_alignment != -1) printf("\tprogress_bar_alignment: %d\n", r->progress_bar_alignment);
196 if (r->set_stack_tag != NULL) printf("\tset_stack_tag: %s\n", r->set_stack_tag);
197 printf("}\n");
198}
199
200/*
201 * Check all rules if they match n and apply.
202 */
203void rule_apply_all(struct notification *n)
204{
205 for (GSList *iter = rules; iter; iter = iter->next) {
206 struct rule *r = iter->data;
207 if (rule_matches_notification(r, n)) {
208 rule_apply(r, n, true);
209 }
210 }
211}
212
213bool rule_apply_special_filters(struct rule *r, const char *name) {
214 if (is_deprecated_section(name)) // shouldn't happen, but just in case
215 return false;
216
217 if (strcmp(name, "global") == 0) {
218 // no filters for global section
219 return true;
220 }
221 if (strcmp(name, "urgency_low") == 0) {
222 r->msg_urgency = URG_LOW;
223 return true;
224 }
225 if (strcmp(name, "urgency_normal") == 0) {
226 r->msg_urgency = URG_NORM;
227 return true;
228 }
229 if (strcmp(name, "urgency_critical") == 0) {
230 r->msg_urgency = URG_CRIT;
231 return true;
232 }
233
234 return false;
235}
236
237struct rule *rule_new(const char *name)
238{
239 struct rule *r = g_malloc0(sizeof(struct rule));
240 *r = empty_rule;
241 rules = g_slist_insert(rules, r, -1);
242 r->name = g_strdup(name);
243 if (is_special_section(name)) {
244 bool success = rule_apply_special_filters(r, name);
245 if (!success) {
246 LOG_M("Could not apply special filters for section %s", name);
247 }
248 }
249 return r;
250}
251
252void rule_free(struct rule *r)
253{
254 if (r == NULL || r == &empty_rule)
255 return;
256
257 g_free(r->name);
258 g_free(r->appname);
259 g_free(r->summary);
260 g_free(r->body);
261 g_free(r->icon);
262 g_free(r->category);
263 g_free(r->stack_tag);
264 g_free(r->desktop_entry);
265
266 g_free(r->action_name);
267 g_free(r->new_icon);
268 g_free(r->default_icon);
269 gradient_release(r->highlight);
270
271 g_free(r->set_category);
272 g_free(r->format);
273 g_free(r->script);
274 g_free(r->set_stack_tag);
275
276 g_free(r);
277}
278
279
280static inline bool rule_field_matches_string(const char *value, const char *pattern)
281{
282 if (settings.enable_regex) {
283 if (STR_EMPTY(pattern)) {
284 return true;
285 }
286 if (!value) {
287 return false;
288 }
289 regex_t regex;
290
291 // TODO compile each regex only once
292 int err = regcomp(&regex, pattern, REG_NEWLINE | REG_EXTENDED | REG_NOSUB);
293 if (err) {
294 size_t err_size = regerror(err, &regex, NULL, 0);
295 char *err_buf = g_malloc(err_size);
296 regerror(err, &regex, err_buf, err_size);
297 LOG_W("%s: \"%s\"", err_buf, pattern);
298 g_free(err_buf);
299 return false;
300 }
301
302 while (true) {
303 if (regexec(&regex, value, 0, NULL, 0))
304 break;
305 regfree(&regex);
306 return true;
307 }
308 regfree(&regex);
309 return false;
310 } else {
311 return !pattern || (value && !fnmatch(pattern, value, 0));
312 }
313}
314
315/*
316 * Check whether rule should be applied to n.
317 */
318bool rule_matches_notification(struct rule *r, struct notification *n)
319{
320 return r->enabled
321 && (r->msg_urgency == URG_NONE || r->msg_urgency == n->urgency)
322 && (r->match_dbus_timeout < 0 || (r->match_dbus_timeout == n->dbus_timeout))
323 && (r->match_transient == -1 || (r->match_transient == n->transient))
324 && rule_field_matches_string(n->appname, r->appname)
325 && rule_field_matches_string(n->desktop_entry, r->desktop_entry)
326 && rule_field_matches_string(n->summary, r->summary)
327 && rule_field_matches_string(n->body, r->body)
328 && rule_field_matches_string(n->iconname, r->icon)
329 && rule_field_matches_string(n->category, r->category)
330 && rule_field_matches_string(n->stack_tag, r->stack_tag);
331}
332
336struct rule *get_rule(const char* name) {
337 for (GSList *iter = rules; iter; iter = iter->next) {
338 struct rule *r = iter->data;
339 if (r->name && STR_EQ(r->name, name))
340 return r;
341 }
342 return NULL;
343}
344
348bool rule_offset_is_modifying(const size_t offset) {
349 const size_t first_action = offsetof(struct rule, timeout);
350 const size_t last_action = offsetof(struct rule, set_stack_tag);
351 return (offset >= first_action) && (offset <= last_action);
352}
353
357bool rule_offset_is_filter(const size_t offset) {
358 const size_t first_filter = offsetof(struct rule, appname);
359 return (offset >= first_filter) && !rule_offset_is_modifying(offset);
360}
char * color_to_string(struct color c, char buf[10])
Stringify a color struct to a RRGGBBAA string.
Definition draw.c:68
Main event loop logic.
Logging subsystem and helpers.
void notification_icon_replace_path(struct notification *n, const char *new_icon)
Replace the current notification's icon with the icon specified by path.
const char * enum_to_string_fullscreen(enum behavior_fullscreen in)
Return the string representation for fullscreen behavior.
urgency
Representing the urgencies according to the notification spec.
@ URG_NONE
Urgency not set (invalid)
@ URG_NORM
Normal urgency.
@ URG_CRIT
Critical urgency.
@ URG_LOW
Low urgency.
@ FS_NULL
Invalid value.
struct rule * get_rule(const char *name)
Check if a rule exists with that name.
Definition rules.c:336
struct rule * rule_new(const char *name)
Allocate a new rule with given name.
Definition rules.c:237
bool rule_offset_is_modifying(const size_t offset)
see rules.h
Definition rules.c:348
bool rule_offset_is_filter(const size_t offset)
see rules.h
Definition rules.c:357
Rules managment and helpers.
List of all the valid settings and values.
char * stack_tag
stack notifications by tag
bool receiving_raw_icon
Still waiting for raw icon to be received.
char * desktop_entry
The desktop entry hint sent via every GApplication.
char * iconname
plain icon information (may be a path or just a name) as recieved from dbus.
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 dbus_timeout
time to display (in milliseconds) (set by dbus)
Definition rules.h:20
bool is_deprecated_section(const char *s)
This function tells if a section is deprecated.
Definition utils.c:407
bool is_special_section(const char *s)
Some sections are handled differently in dunst.
Definition utils.c:398
String, time and other various helpers.
#define STR_EMPTY(s)
Test if a string is NULL or empty.
Definition utils.h:20
#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