diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2264f8d..8e8efbd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
### Fixed
- `new_icon` rule being ignored on notifications that had a raw icon
+- Do not replace format strings, which are in notification content
## Changed
- transient hints are now handled
diff --git a/docs/dunst.pod b/docs/dunst.pod
index d334ea0..5d3c677 100644
--- a/docs/dunst.pod
+++ b/docs/dunst.pod
@@ -324,6 +324,8 @@ equivalent notification attribute.
=item B<%n> progress value without any extra characters
+=item B<%%> Literal %
+
=back
If any of these exists in the format but hasn't been specified in the
diff --git a/dunstrc b/dunstrc
index 3b2857f..be0dd0a 100644
--- a/dunstrc
+++ b/dunstrc
@@ -123,6 +123,7 @@
# %I iconname (without its path)
# %p progress value if set ([ 0%] to [100%]) or nothing
# %n progress value if set without any extra characters
+ # %% Literal %
# Markup is allowed
format = "%s\n%b"
diff --git a/src/notification.c b/src/notification.c
index 621f7d7..68bf2eb 100644
--- a/src/notification.c
+++ b/src/notification.c
@@ -200,19 +200,31 @@ void notification_free(notification *n)
}
/*
- * Replace all occurrences of "needle" with a quoted "replacement",
- * according to the markup settings.
+ * Replace the two chars where **needle points
+ * with a quoted "replacement", according to the markup settings.
+ *
+ * The needle is a double pointer and gets updated upon return
+ * to point to the first char, which occurs after replacement.
+ *
*/
-char *notification_replace_format(const char *needle, const char *replacement,
- char *haystack, enum markup_mode markup_mode) {
- char *tmp;
- char *ret;
+void notification_replace_single_field(char **haystack, char **needle,
+ const char *replacement, enum markup_mode markup_mode) {
- tmp = markup_transform(g_strdup(replacement), markup_mode);
- ret = string_replace_all(needle, tmp, haystack);
- g_free(tmp);
+ assert(*needle[0] == '%');
+ // needle has to point into haystack (but not on the last char)
+ assert(*needle >= *haystack);
+ assert(*needle - *haystack < strlen(*haystack) - 1);
- return ret;
+ int pos = *needle - *haystack;
+
+ char *input = markup_transform(g_strdup(replacement), markup_mode);
+ *haystack = string_replace_at(*haystack, pos, 2, input);
+
+ // point the needle to the next char
+ // which was originally in haystack
+ *needle = *haystack + pos + strlen(input);
+
+ g_free(input);
}
char *notification_extract_markup_urls(char **str_ptr) {
@@ -307,31 +319,89 @@ int notification_init(notification *n, int id)
n->urls = notification_extract_markup_urls(&(n->body));
n->msg = string_replace_all("\\n", "\n", g_strdup(n->format));
- n->msg = notification_replace_format("%a", n->appname, n->msg,
- MARKUP_NO);
- n->msg = notification_replace_format("%s", n->summary, n->msg,
- n->markup);
- n->msg = notification_replace_format("%b", n->body, n->msg,
- n->markup);
- if (n->icon) {
- char *tmp = g_strdup(n->icon);
- n->msg = notification_replace_format("%I", basename(tmp),
- n->msg, MARKUP_NO);
- n->msg = notification_replace_format("%i", n->icon,
- n->msg, MARKUP_NO);
- g_free(tmp);
- }
+ /* replace all formatter */
+ for(char *substr = strchr(n->msg, '%');
+ substr;
+ substr = strchr(substr, '%')){
- if (n->progress) {
- char pg[10];
- sprintf(pg, "[%3d%%]", n->progress - 1);
- n->msg = string_replace_all("%p", pg, n->msg);
- sprintf(pg, "%d", n->progress - 1);
- n->msg = string_replace_all("%n", pg, n->msg);
- } else {
- n->msg = string_replace_all("%p", "", n->msg);
- n->msg = string_replace_all("%n", "", n->msg);
+ char pg[16];
+
+ switch(substr[1]){
+ case 'a':
+ notification_replace_single_field(
+ &n->msg,
+ &substr,
+ n->appname,
+ MARKUP_NO);
+ break;
+ case 's':
+ notification_replace_single_field(
+ &n->msg,
+ &substr,
+ n->summary,
+ n->markup);
+ break;
+ case 'b':
+ notification_replace_single_field(
+ &n->msg,
+ &substr,
+ n->body,
+ n->markup);
+ break;
+ case 'I':
+ notification_replace_single_field(
+ &n->msg,
+ &substr,
+ n->icon ? basename(n->icon) : "",
+ MARKUP_NO);
+ break;
+ case 'i':
+ notification_replace_single_field(
+ &n->msg,
+ &substr,
+ n->icon ? n->icon : "",
+ MARKUP_NO);
+ break;
+ case 'p':
+ if (n->progress)
+ sprintf(pg, "[%3d%%]", n->progress - 1);
+
+ notification_replace_single_field(
+ &n->msg,
+ &substr,
+ n->progress ? pg : "",
+ MARKUP_NO);
+ break;
+ case 'n':
+ if (n->progress)
+ sprintf(pg, "%d", n->progress - 1);
+
+ notification_replace_single_field(
+ &n->msg,
+ &substr,
+ n->progress ? pg : "",
+ MARKUP_NO);
+ break;
+ case '%':
+ notification_replace_single_field(
+ &n->msg,
+ &substr,
+ "%",
+ MARKUP_NO);
+ break;
+ case '\0':
+ fprintf(stderr, "WARNING: format_string has trailing %% character."
+ "To escape it use %%%%.");
+ break;
+ default:
+ fprintf(stderr, "WARNING: format_string %%%c"
+ " is unknown\n", substr[1]);
+ // shift substr pointer forward,
+ // as we can't interpret the format string
+ substr++;
+ break;
+ }
}
n->msg = g_strchomp(n->msg);
diff --git a/src/notification.h b/src/notification.h
index b653575..c0efef4 100644
--- a/src/notification.h
+++ b/src/notification.h
@@ -73,7 +73,7 @@ int notification_is_duplicate(const notification *a, const notification *b);
void notification_run_script(notification *n);
int notification_close(notification *n, int reason);
void notification_print(notification *n);
-char *notification_replace_format(const char *needle, const char *replacement, char *haystack, enum markup_mode markup);
+void notification_replace_single_field(char **haystack, char **needle, const char *replacement, enum markup_mode markup_mode);
void notification_update_text_to_render(notification *n);
int notification_get_ttl(notification *n);
int notification_get_age(notification *n);
diff --git a/src/utils.h b/src/utils.h
index 07bc5aa..9b46a1b 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -9,6 +9,9 @@ char *string_replace_char(char needle, char replacement, char *haystack);
char *string_replace_all(const char *needle, const char *replacement,
char *haystack);
+/* replace characters with at position of the string */
+char *string_replace_at(char *buf, int pos, int len, const char *repl);
+
/* replace needle with replacement in haystack */
char *string_replace(const char *needle, const char *replacement,
char *haystack);
diff --git a/test/notification.c b/test/notification.c
index 8f513c1..e5812dd 100644
--- a/test/notification.c
+++ b/test/notification.c
@@ -70,21 +70,28 @@ TEST test_notification_is_duplicate(void *notifications)
PASS();
}
-TEST test_notification_replace_format(void)
+TEST test_notification_replace_single_field(void)
{
char *str = g_malloc(128 * sizeof(char));
-
- strcpy(str, "Testing format replacement");
- ASSERT_STR_EQ("Testing text replacement", (str = notification_replace_format("format", "text", str, MARKUP_FULL)));
+ char *substr = NULL;
strcpy(str, "Markup %a preserved");
- ASSERT_STR_EQ("Markup and & is preserved", (str = notification_replace_format("%a", "and & is", str, MARKUP_FULL)));
+ substr = strchr(str, '%');
+ notification_replace_single_field(&str, &substr, "and & is", MARKUP_FULL);
+ ASSERT_STR_EQ("Markup and & is preserved", str);
+ ASSERT_EQ(26, substr - str);
strcpy(str, "Markup %a escaped");
- ASSERT_STR_EQ("Markup and & <i>is</i> escaped", (str = notification_replace_format("%a", "and & is", str, MARKUP_NO)));
+ substr = strchr(str, '%');
+ notification_replace_single_field(&str, &substr, "and & is", MARKUP_NO);
+ ASSERT_STR_EQ("Markup and & <i>is</i> escaped", str);
+ ASSERT_EQ(38, substr - str);
strcpy(str, "Markup %a");
- ASSERT_STR_EQ("Markup is removed and & escaped", (str = notification_replace_format("%a", "is removed and & escaped", str, MARKUP_STRIP)));
+ substr = strchr(str, '%');
+ notification_replace_single_field(&str, &substr, "is removed and & escaped", MARKUP_STRIP);
+ ASSERT_STR_EQ("Markup is removed and & escaped", str);
+ ASSERT_EQ(35, substr - str);
g_free(str);
PASS();
@@ -112,7 +119,7 @@ SUITE(suite_notification)
g_free(a);
g_free(b);
- RUN_TEST(test_notification_replace_format);
+ RUN_TEST(test_notification_replace_single_field);
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */