From b5e00c43c7bc335c163f38358add9abc2c43467d Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Wed, 3 Dec 2014 11:13:45 +0100 Subject: [PATCH] Fix markup handling. The current "allow_markup" setting will simply strip any markup from the final notification, which includes formatting elements. On top of that, literal [<>&..] symbols are not quoted before are being passed onto pango in several places, resulting in stray error messages. This patch fixes allow_markup to correctly strip markup only from the incoming notification, not from the format. You might also want to treat incoming messages as literal text (supplied by un-aware programs), in which case you need to properly quote the text before it's processed by pango. A new setting is introduced, called "plain_text", which forces incoming messages to be treated literally. allow_markup/plain_text are complimentary to each other. The new rule actions allow to narrow down the handling to a specific block, achieving notification Zen. The following is done in this patch: - Fix ruleset initialization in config.def.h. - Introduce new allow_markup/plain_text actions in the rules. - Fix handling of allow_markup to strip markup from summary/body only, preserving format's markup. - Fix broken string functions (string_replace_all didn't handle recursive replacements correctly). - Fix quoting of other literal fields (icon name/appname). - Fix handling of ignore_newline as well (applied only on summary/body). - Dunstrc update with the same previous defaults. --- config.def.h | 15 ++++---- dbus.c | 3 ++ dunst.c | 2 ++ dunstrc | 5 ++- notification.c | 95 +++++++++++++++++++++++++++++++++++++------------- notification.h | 5 ++- rules.c | 7 +++- rules.h | 2 ++ settings.c | 7 +++- settings.h | 1 + utils.c | 59 ++++++++++++++++++++----------- x.c | 2 +- 12 files changed, 145 insertions(+), 58 deletions(-) diff --git a/config.def.h b/config.def.h index 65e36d8..fe5b79f 100644 --- a/config.def.h +++ b/config.def.h @@ -2,6 +2,7 @@ char *font = "-*-terminus-medium-r-*-*-16-*-*-*-*-*-*-*"; bool allow_markup = false; +bool plain_text = true; char *normbgcolor = "#1793D1"; char *normfgcolor = "#DDDDDD"; char *critbgcolor = "#ffaaaa"; @@ -90,11 +91,11 @@ keyboard_shortcut context_ks = {.str = "none", rule_t default_rules[] = { /* name can be any unique string. It is used to identify the rule in dunstrc to override it there */ - /* name, appname, summary, body, icon, category, msg_urgency, timeout, urgency, fg, bg, format, script */ - { "empty", NULL, NULL, NULL, NULL, NULL, -1, -1, -1, NULL, NULL, NULL, NULL}, - /* { "rule1", "notify-send", NULL, NULL, NULL, NULL, -1, -1, -1, NULL, NULL, "%s %b", NULL }, */ - /* { "rule2", "Pidgin", "*says*, NULL, NULL, NULL, -1, -1, CRITICAL, NULL, NULL, NULL, NULL }, */ - /* { "rule3", "Pidgin", "*signed on*", NULL, NULL, NULL, -1, -1, LOW, NULL, NULL, NULL, NULL }, */ - /* { "rule4", "Pidgin", "*signed off*", NULL, NULL, NULL, -1, -1, LOW, NULL, NULL, NULL, NULL }, */ - /* { "rule5", NULL, "*foobar*", NULL, NULL, NULL, -1, -1, -1, NULL, "#00FF00", NULL, NULL }, */ + /* name, appname, summary, body, icon, category, msg_urgency, timeout, urgency, allow_markup, plain_text, new_icon, fg, bg, format, script */ + { "empty", NULL, NULL, NULL, NULL, NULL, -1, -1, -1, -1, -1, NULL, NULL, NULL, NULL, NULL}, + /* { "rule1", "notify-send", NULL, NULL, NULL, NULL, -1, -1, -1, -1, -1, NULL, NULL, NULL, "%s %b", NULL }, */ + /* { "rule2", "Pidgin", "*says*, NULL, NULL, NULL, -1, -1, CRITICAL, -1, -1, NULL, NULL, NULL, NULL, NULL }, */ + /* { "rule3", "Pidgin", "*signed on*", NULL, NULL, NULL, -1, -1, LOW, -1, -1, NULL, NULL, NULL, NULL, NULL }, */ + /* { "rule4", "Pidgin", "*signed off*", NULL, NULL, NULL, -1, -1, LOW, -1, -1, NULL, NULL, NULL, NULL, NULL }, */ + /* { "rule5", NULL, "*foobar*", NULL, NULL, NULL, -1, -1, -1, -1, -1, NULL, NULL, "#00FF00", NULL, NULL }, */ }; diff --git a/dbus.c b/dbus.c index 2080639..fd5b0d8 100644 --- a/dbus.c +++ b/dbus.c @@ -9,6 +9,7 @@ #include "dbus.h" #include "notification.h" #include "utils.h" +#include "settings.h" GDBusConnection *dbus_conn; @@ -283,6 +284,8 @@ static void onNotify(GDBusConnection * connection, n->body = body; n->icon = icon; n->timeout = timeout; + n->allow_markup = settings.allow_markup; + n->plain_text = settings.plain_text; n->progress = (progress < 0 || progress > 100) ? 0 : progress + 1; n->urgency = urgency; n->category = category; diff --git a/dunst.c b/dunst.c index 493803d..d8bf621 100644 --- a/dunst.c +++ b/dunst.c @@ -327,6 +327,8 @@ int main(int argc, char *argv[]) n->body = strdup("dunst is up and running"); n->progress = 0; n->timeout = 10; + n->allow_markup = false; + n->plain_text = true; n->urgency = LOW; n->icon = NULL; n->category = NULL; diff --git a/dunstrc b/dunstrc index e28b1c4..6eaeb1c 100644 --- a/dunstrc +++ b/dunstrc @@ -1,7 +1,7 @@ [global] font = Monospace 8 - # Allow a small subset of html markup: + # Allow a small subset of html markup in notifications and formats: # bold # italic # strikethrough @@ -13,6 +13,9 @@ # message. allow_markup = yes + # Treat incoming notifications as plain text + plain_text = no + # The format of the message. Possible variables are: # %a appname # %s summary diff --git a/notification.c b/notification.c index ecc8b6b..ede3622 100644 --- a/notification.c +++ b/notification.c @@ -161,8 +161,7 @@ void notification_free(notification * n) /* * Strip any markup from text */ - -char *notification_fix_markup(char *str) +char *notification_strip_markup(char *str) { char *replace_buf, *start, *end; @@ -210,9 +209,62 @@ char *notification_fix_markup(char *str) } } return str; - } + /* + * Quote a text string for rendering with pango + */ +char *notification_quote_markup(char *str) +{ + if (str == NULL) { + return NULL; + } + + str = string_replace_all("&", "&", str); + str = string_replace_all("\"", """, str); + str = string_replace_all("'", "'", str); + str = string_replace_all("<", "<", str); + str = string_replace_all(">", ">", str); + + return str; +} + + /* + * Replace all occurrences of "needle" with a quoted "replacement", + * according to the allow_markup/plain_text settings. + */ +char *notification_replace_format(const char *needle, const char *replacement, + char *haystack, bool allow_markup, + bool plain_text) { + char* tmp; + char* ret; + + if (plain_text) { + tmp = strdup(replacement); + tmp = string_replace_all("\\n", "\n", tmp); + if (settings.ignore_newline) { + tmp = string_replace_all("\n", " ", tmp); + } + tmp = notification_quote_markup(tmp); + ret = string_replace_all(needle, tmp, haystack); + free(tmp); + } else if (!allow_markup) { + tmp = strdup(replacement); + if (!settings.ignore_newline) { + tmp = string_replace_all("
", "\n", tmp); + tmp = string_replace_all("
", "\n", tmp); + tmp = string_replace_all("
", "\n", tmp); + } + tmp = notification_strip_markup(tmp); + tmp = notification_quote_markup(tmp); + ret = string_replace_all(needle, tmp, haystack); + free(tmp); + } else { + ret = string_replace_all(needle, replacement, haystack); + } + + return ret; +} char *notification_extract_markup_urls(char **str_ptr) { char *start, *end, *replace_buf, *str, *urls = NULL, *url, *index_buf; @@ -259,7 +311,6 @@ char *notification_extract_markup_urls(char **str_ptr) { */ int notification_init(notification * n, int id) { - if (n == NULL) return -1; @@ -282,35 +333,29 @@ int notification_init(notification * n, int id) n->urls = notification_extract_markup_urls(&(n->body)); - n->msg = string_replace("%a", n->appname, g_strdup(n->format)); - n->msg = string_replace("%s", n->summary, n->msg); + n->msg = string_replace_all("\\n", "\n", g_strdup(n->format)); + n->msg = notification_replace_format("%a", n->appname, n->msg, + false, true); + n->msg = notification_replace_format("%s", n->summary, n->msg, + n->allow_markup, n->plain_text); + n->msg = notification_replace_format("%b", n->body, n->msg, + n->allow_markup, n->plain_text); + if (n->icon) { - n->msg = string_replace("%I", basename(n->icon), n->msg); - n->msg = string_replace("%i", n->icon, n->msg); + n->msg = notification_replace_format("%I", basename(n->icon), + n->msg, false, true); + n->msg = notification_replace_format("%i", n->icon, + n->msg, false, true); } - n->msg = string_replace("%b", n->body, n->msg); + if (n->progress) { char pg[10]; sprintf(pg, "[%3d%%]", n->progress - 1); - n->msg = string_replace("%p", pg, n->msg); + n->msg = string_replace_all("%p", pg, n->msg); } else { - n->msg = string_replace("%p", "", n->msg); + n->msg = string_replace_all("%p", "", n->msg); } - if (!settings.allow_markup) - n->msg = notification_fix_markup(n->msg); - else if (!settings.ignore_newline) { - n->msg = string_replace("
", "\n", n->msg); - n->msg = string_replace("
", "\n", n->msg); - } - - while (strstr(n->msg, "\\n") != NULL) - n->msg = string_replace("\\n", "\n", n->msg); - - if (settings.ignore_newline) - while (strstr(n->msg, "\n") != NULL) - n->msg = string_replace("\n", " ", n->msg); - n->msg = g_strstrip(n->msg); if (id == 0) { diff --git a/notification.h b/notification.h index ba5d4a5..71422ea 100644 --- a/notification.h +++ b/notification.h @@ -27,6 +27,8 @@ typedef struct _notification { time_t timestamp; int timeout; int urgency; + bool allow_markup; + bool plain_text; bool redisplayed; /* has been displayed before? */ int id; int dup_count; @@ -49,7 +51,8 @@ int notification_cmp_data(const void *a, const void *b, void *data); void notification_run_script(notification * n); int notification_close(notification * n, int reason); void notification_print(notification * n); -char *notification_fix_markup(char *str); +char *notification_strip_markup(char *str); +char *notification_quote_markup(char *str); void notification_update_text_to_render(notification *n); int notification_get_ttl(notification *n); int notification_get_age(notification *n); diff --git a/rules.c b/rules.c index 6934601..8687f07 100644 --- a/rules.c +++ b/rules.c @@ -15,6 +15,10 @@ void rule_apply(rule_t * r, notification * n) n->timeout = r->timeout; if (r->urgency != -1) n->urgency = r->urgency; + if (r->allow_markup != -1) + n->allow_markup = r->allow_markup; + if (r->plain_text != -1) + n->plain_text = r->plain_text; if (r->new_icon) n->icon = r->new_icon; if (r->fg) @@ -54,6 +58,8 @@ void rule_init(rule_t * r) r->msg_urgency = -1; r->timeout = -1; r->urgency = -1; + r->allow_markup = -1; + r->plain_text = -1; r->new_icon = NULL; r->fg = NULL; r->bg = NULL; @@ -65,7 +71,6 @@ void rule_init(rule_t * r) */ bool rule_matches_notification(rule_t * r, notification * n) { - return ((!r->appname || !fnmatch(r->appname, n->appname, 0)) && (!r->summary || !fnmatch(r->summary, n->summary, 0)) && (!r->body || !fnmatch(r->body, n->body, 0)) diff --git a/rules.h b/rules.h index 71796c6..f7fc734 100644 --- a/rules.h +++ b/rules.h @@ -19,6 +19,8 @@ typedef struct _rule_t { /* actions */ int timeout; int urgency; + int allow_markup; + int plain_text; char *new_icon; char *fg; char *bg; diff --git a/settings.c b/settings.c index e982fcf..531dd1f 100644 --- a/settings.c +++ b/settings.c @@ -85,7 +85,10 @@ void load_settings(char *cmdline_config_path) "The font dunst should use."); settings.allow_markup = option_get_bool("global", "allow_markup", "-markup", allow_markup, - "Allow markups."); + "Allow markups in notifications/formats."); + settings.plain_text = + option_get_bool("global", "plain_text", "-plain", plain_text, + "Treat incoming notifications as plain text."); settings.format = option_get_string("global", "format", "-format", format, "The format template for the notifictions"); @@ -352,6 +355,8 @@ void load_settings(char *cmdline_config_path) r->body = ini_get_string(cur_section, "body", r->body); r->icon = ini_get_string(cur_section, "icon", r->icon); r->timeout = ini_get_int(cur_section, "timeout", r->timeout); + r->allow_markup = ini_get_bool(cur_section, "allow_markup", r->allow_markup); + r->plain_text = ini_get_bool(cur_section, "plain_text", r->plain_text); r->urgency = ini_get_urgency(cur_section, "urgency", r->urgency); r->msg_urgency = ini_get_urgency(cur_section, "msg_urgency", r->msg_urgency); r->fg = ini_get_string(cur_section, "foreground", r->fg); diff --git a/settings.h b/settings.h index 26a049f..bebecb2 100644 --- a/settings.h +++ b/settings.h @@ -4,6 +4,7 @@ typedef struct _settings { bool print_notifications; bool allow_markup; + bool plain_text; bool stack_duplicates; char *font; char *normbgcolor; diff --git a/utils.c b/utils.c index 2f590ff..0ab4b3a 100644 --- a/utils.c +++ b/utils.c @@ -17,39 +17,56 @@ char *string_replace_char(char needle, char replacement, char *haystack) { return haystack; } -char *string_replace_all(const char *needle, const char *replacement, - char *haystack) +char *string_replace_at(char *buf, int pos, int len, const char *repl) { - char *start; - start = strstr(haystack, needle); - while (start != NULL) { - haystack = string_replace(needle, replacement, haystack); - start = strstr(haystack, needle); - } - return haystack; + char *tmp; + int size, buf_len, repl_len; + + buf_len = strlen(buf); + repl_len = strlen(repl); + size = (buf_len - len) + repl_len + 1; + tmp = malloc(size); + + memcpy(tmp, buf, pos); + memcpy(tmp + pos, repl, repl_len); + memcpy(tmp + pos + repl_len, buf + pos + len, buf_len - (pos + len) + 1); + + free(buf); + return tmp; } -char *string_replace(const char *needle, const char *replacement, - char *haystack) +char *string_replace(const char *needle, const char *replacement, char *haystack) { - char *tmp, *start; - int size; + char *start; start = strstr(haystack, needle); if (start == NULL) { return haystack; } - size = (strlen(haystack) - strlen(needle)) + strlen(replacement) + 1; - tmp = calloc(sizeof(char), size); - memset(tmp, '\0', size); + return string_replace_at(haystack, (start - haystack), strlen(needle), replacement); +} - strncpy(tmp, haystack, start - haystack); - tmp[start - haystack] = '\0'; +char *string_replace_all(const char *needle, const char *replacement, + char *haystack) +{ + char *start; + int needle_pos; + int needle_len, repl_len; - sprintf(tmp + strlen(tmp), "%s%s", replacement, start + strlen(needle)); - free(haystack); + needle_len = strlen(needle); + if (needle_len == 0) { + return haystack; + } - return tmp; + start = strstr(haystack, needle); + repl_len = strlen(replacement); + + while (start != NULL) { + needle_pos = start - haystack; + haystack = string_replace_at(haystack, needle_pos, needle_len, replacement); + start = strstr(haystack + needle_pos + repl_len, needle); + } + return haystack; } char *string_append(char *a, const char *b, const char *sep) diff --git a/x.c b/x.c index 62ecde9..06301e5 100644 --- a/x.c +++ b/x.c @@ -382,7 +382,7 @@ static colored_layout *r_create_layout_from_notification(cairo_t *c, notificatio pango_layout_set_attributes(cl->l, cl->attr); } else { /* remove markup and display plain message instead */ - n->text_to_render = notification_fix_markup(n->text_to_render); + n->text_to_render = notification_strip_markup(n->text_to_render); cl->text = NULL; cl->attr = NULL; pango_layout_set_text(cl->l, n->text_to_render, -1);