diff --git a/docs/dunst.pod b/docs/dunst.pod index 76e9833..c9e063e 100644 --- a/docs/dunst.pod +++ b/docs/dunst.pod @@ -611,9 +611,9 @@ matched. =item B Notifications can be matched for any of the following attributes: appname, -summary, body, icon, category, match_transient and msg_urgency where each is -the respective notification attribute to be matched and 'msg_urgency' is the -urgency of the notification, it is named so to not conflict with trying to +summary, body, icon, category, match_transient, msg_urgency, and stack_tag where +each is the respective notification attribute to be matched and 'msg_urgency' is +the urgency of the notification, it is named so to not conflict with trying to modify the urgency. To define a matching rule simply assign the specified value to the value that @@ -630,9 +630,9 @@ Shell-like globing is supported. =item B The following attributes can be overridden: timeout, urgency, foreground, -background, frame_color, new_icon, set_transient, format, fullscreen where, -as with the filtering attributes, each one corresponds to the respective -notification attribute to be modified. +background, frame_color, new_icon, set_transient, format, fullscreen, +set_stack_tag where, as with the filtering attributes, each one corresponds to +the respective notification attribute to be modified. As with filtering, to make a rule modify an attribute simply assign it in the rule definition. @@ -640,6 +640,11 @@ rule definition. If the format is set to an empty string, the notification will not be suppressed. +Notifications with the same non-empty stack_tag value will be stacked +together. The default stack_stag value is set from the string hints +"synchronous", "private-synchronous", "x-canonical-private-synchronous", and +"x-dunst-stack-tag". + =back =head2 SCRIPTING diff --git a/dunstrc b/dunstrc index b3f99fb..bf9fc99 100644 --- a/dunstrc +++ b/dunstrc @@ -147,10 +147,10 @@ # Ignore newlines '\n' in notifications. ignore_newline = no - # Merge multiple notifications with the same content + # Stack together notifications with the same content stack_duplicates = true - # Hide the count of merged notifications with the same content + # Hide the count of stacked notifications with the same content hide_duplicate_count = false # Display indicators for URLs (U) and actions (A). @@ -299,7 +299,8 @@ # override settings for certain messages. # Messages can be matched by "appname", "summary", "body", "icon", "category", # "msg_urgency" and you can override the "timeout", "urgency", "foreground", -# "background", "frame_color", "new_icon" and "format", "fullscreen". +# "background", "frame_color", "new_icon" and "format", "fullscreen", +# "stack_tag". # Shell-like globbing will get expanded. # # SCRIPTING @@ -365,4 +366,8 @@ # summary = *twitter.com* # urgency = normal # +#[stack-volumes] +# appname = "some_volume_notifiers" +# set_stack_tag = "volume" +# # vim: ft=cfg diff --git a/src/dbus.c b/src/dbus.c index 1e4710d..2330efc 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -65,6 +65,13 @@ static const char *introspection_xml = " " ""; +static const char *stack_tag_hints[] = { + "synchronous", + "private-synchronous", + "x-canonical-private-synchronous", + "x-dunst-stack-tag" +}; + static void on_get_capabilities(GDBusConnection *connection, const gchar *sender, const GVariant *parameters, @@ -120,6 +127,9 @@ static void on_get_capabilities(GDBusConnection *connection, g_variant_builder_add(builder, "s", "body"); g_variant_builder_add(builder, "s", "body-hyperlinks"); + for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) + g_variant_builder_add(builder, "s", stack_tag_hints[i]); + if (settings.markup != MARKUP_NO) g_variant_builder_add(builder, "s", "body-markup"); @@ -245,6 +255,20 @@ static struct notification *dbus_message_to_notification(const gchar *sender, GV n->progress = g_variant_get_uint32(dict_value); g_variant_unref(dict_value); } + + /* Check for hints that define the stack_tag + * + * Only accept to first one we find. + */ + for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) { + dict_value = g_variant_lookup_value(content, stack_tag_hints[i], G_VARIANT_TYPE_STRING); + if (dict_value) { + n->stack_tag = g_variant_dup_string(dict_value, NULL); + g_variant_unref(dict_value); + break; + } + } + } break; case 7: diff --git a/src/notification.c b/src/notification.c index e8699c8..0155093 100644 --- a/src/notification.c +++ b/src/notification.c @@ -66,6 +66,7 @@ void notification_print(const struct notification *n) printf("\tframe: %s\n", n->colors[ColFrame]); printf("\tfullscreen: %s\n", enum_to_string_fullscreen(n->fullscreen)); printf("\tprogress: %d\n", n->progress); + printf("\tstack_tag: %s\n", (n->stack_tag ? n->stack_tag : "")); printf("\tid: %d\n", n->id); if (n->urls) { char *urls = string_replace_all("\n", "\t\t\n", g_strdup(n->urls)); @@ -251,6 +252,7 @@ void notification_unref(struct notification *n) g_free(n->colors[ColFG]); g_free(n->colors[ColBG]); g_free(n->colors[ColFrame]); + g_free(n->stack_tag); actions_free(n->actions); rawimage_free(n->raw_icon); diff --git a/src/notification.h b/src/notification.h index ffc887b..185232d 100644 --- a/src/notification.h +++ b/src/notification.h @@ -70,6 +70,8 @@ struct notification { const char *script; char *colors[3]; + char *stack_tag; /**< stack notifications by tag */ + /* Hints */ bool transient; /**< timeout albeit user is idle */ int progress; /**< percentage (-1: undefined) */ diff --git a/src/queues.c b/src/queues.c index efa28af..c90faf2 100644 --- a/src/queues.c +++ b/src/queues.c @@ -34,6 +34,7 @@ int next_notification_id = 1; bool pause_displayed = false; static bool queues_stack_duplicate(struct notification *n); +static bool queues_stack_by_tag(struct notification *n); /* see queues.h */ void queues_init(void) @@ -123,6 +124,7 @@ static bool queues_notification_is_ready(const struct notification *n, bool full /* see queues.h */ int queues_notification_insert(struct notification *n) { + bool inserted = false; /* do not display the message, if the message is empty */ if (STR_EMPTY(n->msg)) { @@ -146,14 +148,17 @@ int queues_notification_insert(struct notification *n) return 0; } - if (n->id == 0) { + if (!inserted && n->id != 0 && queues_notification_replace_id(n)) + inserted = true; + else n->id = ++next_notification_id; - if (!settings.stack_duplicates || !queues_stack_duplicate(n)) - g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); - } else { - if (!queues_notification_replace_id(n)) - g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); - } + + if (!inserted && STR_FULL(n->stack_tag) && queues_stack_by_tag(n)) + inserted = true; + if (!inserted && settings.stack_duplicates && queues_stack_duplicate(n)) + inserted = true; + if (!inserted) + g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); if (settings.print_notifications) notification_print(n); @@ -170,7 +175,7 @@ int queues_notification_insert(struct notification *n) static bool queues_stack_duplicate(struct notification *n) { GQueue *allqueues[] = { displayed, waiting }; - for (int i = 0; i < sizeof(allqueues)/sizeof(GList*); i++) { + for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) { struct notification *orig = iter->data; @@ -188,7 +193,7 @@ static bool queues_stack_duplicate(struct notification *n) n->dup_count = orig->dup_count; signal_notification_closed(orig, 1); - if ( allqueues[i] == displayed ) + if (allqueues[i] == displayed) n->start = time_monotonic_now(); notification_unref(orig); @@ -200,11 +205,43 @@ static bool queues_stack_duplicate(struct notification *n) return false; } +/** + * Replaces the first notification of the same stack_tag + * + * @return true, if notification got stacked + * @return false, if notification did not get stacked + */ +static bool queues_stack_by_tag(struct notification *new) +{ + GQueue *allqueues[] = { displayed, waiting }; + for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { + for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; + iter = iter->next) { + struct notification *old = iter->data; + if (STR_FULL(old->stack_tag) && STR_EQ(old->stack_tag, new->stack_tag)) { + iter->data = new; + new->dup_count = old->dup_count; + + signal_notification_closed(old, 1); + + if (allqueues[i] == displayed) { + new->start = time_monotonic_now(); + notification_run_script(new); + } + + notification_unref(old); + return true; + } + } + } + return false; +} + /* see queues.h */ bool queues_notification_replace_id(struct notification *new) { GQueue *allqueues[] = { displayed, waiting }; - for (int i = 0; i < sizeof(allqueues)/sizeof(GList*); i++) { + for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) { @@ -213,7 +250,7 @@ bool queues_notification_replace_id(struct notification *new) iter->data = new; new->dup_count = old->dup_count; - if ( allqueues[i] == displayed ) { + if (allqueues[i] == displayed) { new->start = time_monotonic_now(); notification_run_script(new); } @@ -232,7 +269,7 @@ void queues_notification_close_id(int id, enum reason reason) struct notification *target = NULL; GQueue *allqueues[] = { displayed, waiting }; - for (int i = 0; i < sizeof(allqueues)/sizeof(GList*); i++) { + for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; iter = iter->next) { struct notification *n = iter->data; diff --git a/src/rules.c b/src/rules.c index 4f13977..104b4f2 100644 --- a/src/rules.c +++ b/src/rules.c @@ -47,6 +47,10 @@ void rule_apply(struct rule *r, struct notification *n) n->format = r->format; if (r->script) n->script = r->script; + if (r->set_stack_tag) { + g_free(n->stack_tag); + n->stack_tag = g_strdup(r->set_stack_tag); + } } /* @@ -73,6 +77,7 @@ void rule_init(struct rule *r) r->body = NULL; r->icon = NULL; r->category = NULL; + r->stack_tag = NULL; r->msg_urgency = URG_NONE; r->timeout = -1; r->urgency = URG_NONE; @@ -86,6 +91,7 @@ void rule_init(struct rule *r) r->bg = NULL; r->fc = NULL; r->format = NULL; + r->set_stack_tag = NULL; } /* @@ -93,11 +99,12 @@ void rule_init(struct rule *r) */ bool rule_matches_notification(struct rule *r, struct notification *n) { - return ( (!r->appname || (n->appname && !fnmatch(r->appname, n->appname, 0))) - && (!r->summary || (n->summary && !fnmatch(r->summary, n->summary, 0))) - && (!r->body || (n->body && !fnmatch(r->body, n->body, 0))) - && (!r->icon || (n->icon && !fnmatch(r->icon, n->icon, 0))) - && (!r->category || (n->category && !fnmatch(r->category, n->category, 0))) + return ( (!r->appname || (n->appname && !fnmatch(r->appname, n->appname, 0))) + && (!r->summary || (n->summary && !fnmatch(r->summary, n->summary, 0))) + && (!r->body || (n->body && !fnmatch(r->body, n->body, 0))) + && (!r->icon || (n->icon && !fnmatch(r->icon, n->icon, 0))) + && (!r->category || (n->category && !fnmatch(r->category, n->category, 0))) + && (!r->stack_tag || (n->stack_tag && !fnmatch(r->stack_tag, n->stack_tag, 0))) && (r->match_transient == -1 || (r->match_transient == n->transient)) && (r->msg_urgency == URG_NONE || r->msg_urgency == n->urgency)); } diff --git a/src/rules.h b/src/rules.h index 3ab82be..df45f5d 100644 --- a/src/rules.h +++ b/src/rules.h @@ -16,6 +16,7 @@ struct rule { char *body; char *icon; char *category; + char *stack_tag; int msg_urgency; /* actions */ @@ -32,6 +33,7 @@ struct rule { const char *format; const char *script; enum behavior_fullscreen fullscreen; + char *set_stack_tag; }; extern GSList *rules; diff --git a/src/settings.c b/src/settings.c index a71978f..c27cc4b 100644 --- a/src/settings.c +++ b/src/settings.c @@ -368,7 +368,7 @@ void load_settings(char *cmdline_config_path) settings.hide_duplicate_count = option_get_bool( "global", "hide_duplicate_count", "-hide_duplicate_count", false, - "Hide the count of merged notifications with the same content" + "Hide the count of stacked notifications with the same content" ); settings.sticky_history = option_get_bool( @@ -444,7 +444,7 @@ void load_settings(char *cmdline_config_path) settings.stack_duplicates = option_get_bool( "global", "stack_duplicates", "-stack_duplicates", true, - "Merge multiple notifications with the same content" + "Stack together notifications with the same content" ); settings.startup_notification = option_get_bool( @@ -785,6 +785,7 @@ 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->category = ini_get_string(cur_section, "category", r->category); + r->stack_tag = ini_get_string(cur_section, "stack_tag", r->stack_tag); r->timeout = ini_get_time(cur_section, "timeout", r->timeout); { @@ -819,6 +820,7 @@ void load_settings(char *cmdline_config_path) g_free(c); } r->script = ini_get_path(cur_section, "script", NULL); + r->set_stack_tag = ini_get_string(cur_section, "set_stack_tag", r->set_stack_tag); } #ifndef STATIC_CONFIG