diff --git a/completions/_dunstctl.zshcomp b/completions/_dunstctl.zshcomp index a278ddd3f..fb8fd2c33 100644 --- a/completions/_dunstctl.zshcomp +++ b/completions/_dunstctl.zshcomp @@ -29,6 +29,7 @@ case $state in 'set-pause-level:Set the pause level' 'rule:Enable or disable a rule by its name' 'rules:Displays configured rules' + 'reload:Reload the settings of the running instance, optionally with specific configuration files' 'debug:Print debugging information' 'help:Show help' ) diff --git a/completions/dunstctl.bashcomp b/completions/dunstctl.bashcomp index 49973eef4..d59449fdb 100644 --- a/completions/dunstctl.bashcomp +++ b/completions/dunstctl.bashcomp @@ -2,7 +2,7 @@ _dunstctl() { local opts cur prev _get_comp_words_by_ref cur prev COMPREPLY=() - opts='action close close-all context count debug help history history-clear history-pop history-rm is-paused rule rules set-paused get-pause-level set-pause-level' + opts='action close close-all context count debug help history history-clear history-pop history-rm is-paused rule rules set-paused get-pause-level set-pause-level reload' case "$prev" in count) COMPREPLY=( $( compgen -W 'displayed history waiting' -- "$cur" ) ) diff --git a/completions/dunstctl.fishcomp b/completions/dunstctl.fishcomp index 4e65364ac..d20f7428a 100644 --- a/completions/dunstctl.fishcomp +++ b/completions/dunstctl.fishcomp @@ -28,6 +28,7 @@ complete -c dunstctl -f -n __fish_use_subcommand -a get-pause-level -d 'Get the complete -c dunstctl -f -n __fish_use_subcommand -a set-pause-level -d 'Set the pause level' complete -c dunstctl -f -n __fish_use_subcommand -a rules -d 'Displays configured rules (optionally in JSON)' complete -c dunstctl -f -n __fish_use_subcommand -a rule -d 'Enable or disable a rule by its name' +complete -c dunstctl -f -n __fish_use_subcommand -a reload -d 'Reload the settings of the running instance, optionally with specific configuration files' complete -c dunstctl -f -n __fish_use_subcommand -a debug -d 'Print debugging information' complete -c dunstctl -f -n __fish_use_subcommand -a help -d 'Show help' @@ -38,5 +39,6 @@ complete -c dunstctl -x -n '__fish_seen_subcommand_from history-pop history-rm' complete -c dunstctl -x -n '__fish_seen_subcommand_from set-paused' -a 'true false toggle' complete -c dunstctl -x -n '__fish_seen_subcommand_from rule' -a '(__fish_dunstctl_rule_complete (commandline -c))' complete -c dunstctl -x -n '__fish_seen_subcommand_from rules' -a --json +complete -c dunstctl -n '__fish_seen_subcommand_from reload' # ex: filetype=fish diff --git a/docs/dunstctl.pod b/docs/dunstctl.pod index c98db8314..f6c93cef4 100644 --- a/docs/dunstctl.pod +++ b/docs/dunstctl.pod @@ -63,7 +63,7 @@ will be kept but not shown until it is unpaused. =item B true/false/toggle Set the paused status of dunst. If false, dunst is running normally, if true, -dunst is paused (with maximum pause level of 100). +dunst is paused (with maximum pause level of 100). See the is-paused command and the dunst man page for more information. =item B @@ -90,6 +90,14 @@ to temporarily activate or deactivate specific rules. Exports all currently configured rules (optionally JSON formatted). +=item B [dunstrc ...] + +Reload the settings of the running dunst instance. You can optionally specify +which configuration files to use. Otherwise, the config specified by the first invocation +of dunst will be reloaded. +When dunst is reloaded all the rules are reapplied to the original notification, +so modifications made by previous rules are not taken into account. + =item B Tries to contact dunst and checks for common faults between dunstctl and dunst. diff --git a/dunstctl b/dunstctl index 59097c4e5..b78cd1b4f 100755 --- a/dunstctl +++ b/dunstctl @@ -19,7 +19,7 @@ show_help() { context menu of the notification at the given position close [ID] Close the last notification or the - notification with given ID. + notification with given ID close-all Close all the notifications context Open context menu count [displayed|history|waiting] Show the number of notifications @@ -37,6 +37,9 @@ show_help() { rule name enable|disable|toggle Enable or disable a rule by its name rules [--json] Displays configured rules (optionally in JSON) + reload [dunstrc ...] Reload the settings of the running + instance, optionally with specific + config files (space/comma-separated) debug Print debugging information help Show help EOH @@ -206,8 +209,12 @@ case "${1:-}" in busctl --user --json=pretty --no-pager call org.freedesktop.Notifications /org/freedesktop/Notifications org.dunstproject.cmd0 NotificationListHistory 2>/dev/null \ || die "Dunst is not running." ;; + "reload") + shift + method_call "${DBUS_IFAC_DUNST}.ConfigReload" "array:string:$(IFS=','; echo "$*")" >/dev/null + ;; "") - die "dunstctl: No command specified. Please consult the usage." + die "dunstctl: No command specified. Use dunstctl help" ;; *) die "dunstctl: unrecognized command '${1:-}'. Please consult the usage." diff --git a/src/dbus.c b/src/dbus.c index e989d5caa..989698144 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -100,6 +100,9 @@ static const char *introspection_xml = " " " " " " + " " + " " + " " " " " " @@ -193,8 +196,12 @@ DBUS_METHOD(dunst_NotificationRemoveFromHistory); DBUS_METHOD(dunst_NotificationShow); DBUS_METHOD(dunst_RuleEnable); DBUS_METHOD(dunst_RuleList); +DBUS_METHOD(dunst_ConfigReload); DBUS_METHOD(dunst_Ping); + +// NOTE: Keep the names sorted alphabetically static struct dbus_method methods_dunst[] = { + {"ConfigReload", dbus_cb_dunst_ConfigReload}, {"ContextMenuCall", dbus_cb_dunst_ContextMenuCall}, {"NotificationAction", dbus_cb_dunst_NotificationAction}, {"NotificationClearHistory", dbus_cb_dunst_NotificationClearHistory}, @@ -603,6 +610,19 @@ static void dbus_cb_dunst_RuleEnable(GDBusConnection *connection, g_dbus_connection_flush(connection, NULL, NULL, NULL); } +static void dbus_cb_dunst_ConfigReload(GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + gchar **configs = NULL; + g_variant_get(parameters, "(^as)", &configs); + reload(configs); + + g_dbus_method_invocation_return_value(invocation, NULL); + g_dbus_connection_flush(connection, NULL, NULL, NULL); +} + /* Just a simple Ping command to give the ability to dunstctl to test for the existence of this interface * Any other way requires parsing the XML of the Introspection or other foo. Just calling the Ping on an old dunst version will fail. */ static void dbus_cb_dunst_Ping(GDBusConnection *connection, @@ -790,29 +810,41 @@ static struct notification *dbus_message_to_notification(const gchar *sender, GV // Modify these values after the notification is initialized and all rules are applied. if ((dict_value = g_variant_lookup_value(hints, "fgcolor", G_VARIANT_TYPE_STRING))) { struct color c; - if (string_parse_color(g_variant_get_string(dict_value, NULL), &c)) + if (string_parse_color(g_variant_get_string(dict_value, NULL), &c)) { + notification_keep_original(n); + if (!COLOR_VALID(n->original->fg)) n->original->fg = n->colors.fg; n->colors.fg = c; + } g_variant_unref(dict_value); } if ((dict_value = g_variant_lookup_value(hints, "bgcolor", G_VARIANT_TYPE_STRING))) { struct color c; - if (string_parse_color(g_variant_get_string(dict_value, NULL), &c)) + if (string_parse_color(g_variant_get_string(dict_value, NULL), &c)) { + notification_keep_original(n); + if (!COLOR_VALID(n->original->bg)) n->original->bg = n->colors.bg; n->colors.bg = c; + } g_variant_unref(dict_value); } if ((dict_value = g_variant_lookup_value(hints, "frcolor", G_VARIANT_TYPE_STRING))) { struct color c; - if (string_parse_color(g_variant_get_string(dict_value, NULL), &c)) + if (string_parse_color(g_variant_get_string(dict_value, NULL), &c)) { + notification_keep_original(n); + if (!COLOR_VALID(n->original->fc)) n->original->fc = n->colors.frame; n->colors.frame = c; + } g_variant_unref(dict_value); } if ((dict_value = g_variant_lookup_value(hints, "hlcolor", G_VARIANT_TYPE_STRING))) { struct color c; - if (string_parse_color(g_variant_get_string(dict_value, NULL), &c)) + if (string_parse_color(g_variant_get_string(dict_value, NULL), &c)) { + notification_keep_original(n); + if (!COLOR_VALID(n->original->highlight)) n->original->highlight = n->colors.highlight; n->colors.highlight = c; + } g_variant_unref(dict_value); } @@ -1083,7 +1115,7 @@ gboolean dbus_cb_dunst_Properties_Set(GDBusConnection *connection, int targetPauseLevel = -1; if (STR_EQ(property_name, "paused")) { if (g_variant_get_boolean(value)) { - targetPauseLevel = MAX_PAUSE_LEVEL; + targetPauseLevel = MAX_PAUSE_LEVEL; } else { targetPauseLevel = 0; } diff --git a/src/draw.c b/src/draw.c index 87437cc3e..5d7583fa6 100644 --- a/src/draw.c +++ b/src/draw.c @@ -980,6 +980,7 @@ void draw(void) void draw_deinit(void) { + pango_font_description_free(pango_fdesc); output->win_destroy(win); output->deinit(); if (settings.enable_recursive_icon_lookup) diff --git a/src/dunst.c b/src/dunst.c index ae875ed3d..8d8bdd4e5 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -25,6 +25,7 @@ GMainLoop *mainloop = NULL; static struct dunst_status status; static bool setup_done = false; +static char **config_paths = NULL; /* see dunst.h */ void dunst_status(const enum dunst_status_field field, @@ -205,11 +206,31 @@ static void teardown(void) queues_teardown(); draw_deinit(); + + g_strfreev(config_paths); } -int dunst_main(int argc, char *argv[]) +void reload(char **const configs) { + guint length = g_strv_length(configs); + LOG_M("Reloading settings (with the %s files)", length != 0 ? "new" : "old"); + + pause_signal(NULL); + + setup_done = false; + draw_deinit(); + + load_settings(configs); + draw_setup(); + setup_done = true; + queues_reapply_all_rules(); + + unpause_signal(NULL); +} + +int dunst_main(int argc, char *argv[]) +{ dunst_status_int(S_PAUSE_LEVEL, 0); dunst_status(S_IDLE, false); @@ -229,10 +250,21 @@ int dunst_main(int argc, char *argv[]) log_set_level_from_string(verbosity); g_free(verbosity); - char *cmdline_config_path; - cmdline_config_path = - cmdline_get_string("-conf/-config", NULL, - "Path to configuration file"); + cmdline_usage_append("-conf/-config", "string", "Path to configuration file"); + + int start = 1, count = 1; + while (cmdline_get_string_offset("-conf/-config", NULL, start, &start)) + count++; + + // Leaves an extra space for the NULL + config_paths = g_malloc0(sizeof(char *) * count); + start = 1, count = 0; + char *path = NULL; + + do { + path = cmdline_get_string_offset("-conf/-config", NULL, start, &start); + config_paths[count++] = path; + } while (path != NULL); settings.print_notifications = cmdline_get_bool("-print/--print", false, "Print notifications to stdout"); @@ -244,7 +276,7 @@ int dunst_main(int argc, char *argv[]) usage(EXIT_SUCCESS); } - load_settings(cmdline_config_path); + load_settings(config_paths); int dbus_owner_id = dbus_init(); mainloop = g_main_loop_new(NULL, FALSE); diff --git a/src/dunst.h b/src/dunst.h index dc5267e0f..f2a4e4e3a 100644 --- a/src/dunst.h +++ b/src/dunst.h @@ -38,6 +38,7 @@ void dunst_status_int(const enum dunst_status_field field, struct dunst_status dunst_status_get(void); void wake_up(void); +void reload(char **const configs); int dunst_main(int argc, char *argv[]); diff --git a/src/log.c b/src/log.c index afaeecec9..76eca65ad 100644 --- a/src/log.c +++ b/src/log.c @@ -11,7 +11,8 @@ #include "utils.h" -static GLogLevelFlags log_level = G_LOG_LEVEL_WARNING; +// NOTE: Keep updated with the dunst manual +static GLogLevelFlags log_level = G_LOG_LEVEL_MESSAGE; /* see log.h */ static const char *log_level_to_string(GLogLevelFlags level) diff --git a/src/notification.c b/src/notification.c index 94411c232..1d2689680 100644 --- a/src/notification.c +++ b/src/notification.c @@ -25,6 +25,7 @@ #include "utils.h" #include "draw.h" #include "icon-lookup.h" +#include "settings_data.h" static void notification_extract_urls(struct notification *n); static void notification_format_message(struct notification *n); @@ -293,6 +294,15 @@ void notification_unref(struct notification *n) if (!g_atomic_int_dec_and_test(&n->priv->refcount)) return; + if (n->original) { + g_free(n->original->action_name); + g_free(n->original->set_category); + g_free(n->original->default_icon); + g_free(n->original->set_stack_tag); + g_free(n->original->new_icon); + g_free(n->original); + } + g_free(n->appname); g_free(n->summary); g_free(n->body); @@ -316,9 +326,7 @@ void notification_unref(struct notification *n) notification_private_free(n->priv); - if (n->script_count > 0) { - g_free(n->scripts); - } + g_strfreev(n->scripts); g_free(n); } @@ -770,4 +778,11 @@ void notification_invalidate_actions(struct notification *n) { g_hash_table_remove_all(n->actions); } +void notification_keep_original(struct notification *n) +{ + if (n->original) return; + n->original = g_malloc0(sizeof(struct rule)); + *n->original = empty_rule; +} + /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/notification.h b/src/notification.h index 3ef3ff6be..ab432efaa 100644 --- a/src/notification.h +++ b/src/notification.h @@ -51,6 +51,9 @@ struct notification { char *dbus_client; bool dbus_valid; + // We keep the original notification properties here when it is modified + struct rule *original; + char *appname; char *summary; char *body; @@ -83,7 +86,7 @@ struct notification { enum markup_mode markup; const char *format; - const char **scripts; + char **scripts; int script_count; struct notification_colors colors; @@ -242,7 +245,7 @@ void notification_open_url(struct notification *n); /** * Open the context menu for the notification. - * + * * Convenience function that creates the GList and passes it to context_menu_for(). */ void notification_open_context_menu(struct notification *n); @@ -266,5 +269,7 @@ const char *notification_urgency_to_string(const enum urgency urgency); */ const char *enum_to_string_fullscreen(enum behavior_fullscreen in); +void notification_keep_original(struct notification *n); + #endif /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/option_parser.c b/src/option_parser.c index 589b4570a..49d57d5c9 100644 --- a/src/option_parser.c +++ b/src/option_parser.c @@ -18,11 +18,7 @@ static int cmdline_argc; static char **cmdline_argv; - static char *usage_str = NULL; -static void cmdline_usage_append(const char *key, const char *type, const char *description); - -static int cmdline_find_option(const char *key); #define STRING_PARSE_RET(string, value) if (STR_EQ(s, string)) { *ret = value; return true; } @@ -517,14 +513,14 @@ void cmdline_load(int argc, char *argv[]) cmdline_argv = argv; } -int cmdline_find_option(const char *key) +static int cmdline_find_option(const char *key, int start) { ASSERT_OR_RET(key, -1); gchar **keys = g_strsplit(key, "/", -1); for (int i = 0; keys[i] != NULL; i++) { - for (int j = 0; j < cmdline_argc; j++) { + for (int j = start; j < cmdline_argc; j++) { if (STR_EQ(keys[i], cmdline_argv[j])) { g_strfreev(keys); return j; @@ -536,13 +532,16 @@ int cmdline_find_option(const char *key) return -1; } -static const char *cmdline_get_value(const char *key) +static const char *cmdline_get_value(const char *key, int start, int *found) { - int idx = cmdline_find_option(key); + int idx = cmdline_find_option(key, start); if (idx < 0) { return NULL; } + if (found) + *found = idx + 1; + if (idx + 1 >= cmdline_argc) { /* the argument is missing */ LOG_W("%s: Missing argument. Ignoring.", key); @@ -551,10 +550,9 @@ static const char *cmdline_get_value(const char *key) return cmdline_argv[idx + 1]; } -char *cmdline_get_string(const char *key, const char *def, const char *description) +char *cmdline_get_string_offset(const char *key, const char *def, int start, int *found) { - cmdline_usage_append(key, "string", description); - const char *str = cmdline_get_value(key); + const char *str = cmdline_get_value(key, start, found); if (str) return g_strdup(str); @@ -564,10 +562,16 @@ char *cmdline_get_string(const char *key, const char *def, const char *descripti return NULL; } +char *cmdline_get_string(const char *key, const char *def, const char *description) +{ + cmdline_usage_append(key, "string", description); + return cmdline_get_string_offset(key, def, 1, NULL); +} + char *cmdline_get_path(const char *key, const char *def, const char *description) { cmdline_usage_append(key, "string", description); - const char *str = cmdline_get_value(key); + const char *str = cmdline_get_value(key, 1, NULL); if (str) return string_to_path(g_strdup(str)); @@ -578,7 +582,7 @@ char *cmdline_get_path(const char *key, const char *def, const char *description char **cmdline_get_list(const char *key, const char *def, const char *description) { cmdline_usage_append(key, "list", description); - const char *str = cmdline_get_value(key); + const char *str = cmdline_get_value(key, 1, NULL); if (str) return string_to_array(str, ","); @@ -589,7 +593,7 @@ char **cmdline_get_list(const char *key, const char *def, const char *descriptio gint64 cmdline_get_time(const char *key, gint64 def, const char *description) { cmdline_usage_append(key, "time", description); - const char *timestring = cmdline_get_value(key); + const char *timestring = cmdline_get_value(key, 1, NULL); gint64 val = def; if (timestring) { @@ -602,7 +606,7 @@ gint64 cmdline_get_time(const char *key, gint64 def, const char *description) int cmdline_get_int(const char *key, int def, const char *description) { cmdline_usage_append(key, "int", description); - const char *str = cmdline_get_value(key); + const char *str = cmdline_get_value(key, 1, NULL); if (str) return atoi(str); @@ -613,7 +617,7 @@ int cmdline_get_int(const char *key, int def, const char *description) double cmdline_get_double(const char *key, double def, const char *description) { cmdline_usage_append(key, "double", description); - const char *str = cmdline_get_value(key); + const char *str = cmdline_get_value(key, 1, NULL); if (str) return atof(str); @@ -624,7 +628,7 @@ double cmdline_get_double(const char *key, double def, const char *description) int cmdline_get_bool(const char *key, int def, const char *description) { cmdline_usage_append(key, "", description); - int idx = cmdline_find_option(key); + int idx = cmdline_find_option(key, 1); if (idx > 0) return true; @@ -634,7 +638,7 @@ int cmdline_get_bool(const char *key, int def, const char *description) bool cmdline_is_set(const char *key) { - return cmdline_get_value(key) != NULL; + return cmdline_get_value(key, 1, NULL) != NULL; } void cmdline_usage_append(const char *key, const char *type, const char *description) diff --git a/src/option_parser.h b/src/option_parser.h index 14ceb6bce..fe3f10c61 100644 --- a/src/option_parser.h +++ b/src/option_parser.h @@ -22,6 +22,7 @@ void save_settings(struct ini *ini); void cmdline_load(int argc, char *argv[]); /* for all cmdline_get_* key can be either "-key" or "-key/-longkey" */ +char *cmdline_get_string_offset(const char *key, const char *def, int start, int *found); char *cmdline_get_string(const char *key, const char *def, const char *description); char *cmdline_get_path(const char *key, const char *def, const char *description); char **cmdline_get_list(const char *key, const char *def, const char *description); @@ -29,6 +30,7 @@ int cmdline_get_int(const char *key, int def, const char *description); double cmdline_get_double(const char *key, double def, const char *description); int cmdline_get_bool(const char *key, int def, const char *description); bool cmdline_is_set(const char *key); +void cmdline_usage_append(const char *key, const char *type, const char *description); const char *cmdline_create_usage(void); #endif diff --git a/src/queues.c b/src/queues.c index 2849bd755..64a218022 100644 --- a/src/queues.c +++ b/src/queues.c @@ -25,7 +25,7 @@ #include "notification.h" #include "settings.h" #include "utils.h" -#include "output.h" // For checking if wayland is active. +#include "rules.h" /* notification lists */ static GQueue *waiting = NULL; /**< all new notifications get into here */ @@ -617,6 +617,21 @@ struct notification* queues_get_by_id(int id) return NULL; } +void queues_reapply_all_rules(void) +{ + GQueue *recqueues[] = { displayed, waiting, history }; + for (int i = 0; i < sizeof(recqueues)/sizeof(GQueue*); i++) { + for (GList *iter = g_queue_peek_head_link(recqueues[i]); iter; + iter = iter->next) { + struct notification *cur = iter->data; + if (cur->original) { + rule_apply(cur->original, cur, false); + } + rule_apply_all(cur); + } + } +} + /** * Helper function for queues_teardown() to free a single notification * diff --git a/src/queues.h b/src/queues.h index 387ef91ee..55e85baf0 100644 --- a/src/queues.h +++ b/src/queues.h @@ -179,6 +179,10 @@ gint64 queues_get_next_datachange(gint64 time); */ struct notification* queues_get_by_id(int id); +/** + * Reapply all rules to the queue (used when reloading configs) + */ +void queues_reapply_all_rules(void); /** * Remove all notifications from all list and free the notifications diff --git a/src/rules.c b/src/rules.c index b3dcf0ff7..e369cf622 100644 --- a/src/rules.c +++ b/src/rules.c @@ -14,66 +14,98 @@ GSList *rules = NULL; +// NOTE: Internal, only for rule_apply(...) + +#define RULE_APPLY2(nprop, rprop, defval) \ + if (r->rprop != (defval)) { \ + if (save && n->original->rprop == (defval)) \ + n->original->rprop = n->nprop; \ + n->nprop = r->rprop; \ + } + +#define RULE_APPLY(prop, defval) RULE_APPLY2(prop, prop, defval) + /* * Apply rule to notification. + * If save is true the original value will be saved in the notification. */ -void rule_apply(struct rule *r, struct notification *n) +void rule_apply(struct rule *r, struct notification *n, bool save) { - if (r->timeout != -1) - n->timeout = r->timeout; - if (r->override_dbus_timeout != -1) - n->dbus_timeout = r->override_dbus_timeout; - if (r->urgency != URG_NONE) - n->urgency = r->urgency; - if (r->fullscreen != FS_NULL) - n->fullscreen = r->fullscreen; - if (r->history_ignore != -1) - n->history_ignore = r->history_ignore; - if (r->set_transient != -1) - n->transient = r->set_transient; - if (r->skip_display != -1) - n->skip_display = r->skip_display; - if (r->word_wrap != -1) - n->word_wrap = r->word_wrap; - if (r->ellipsize != -1) - n->ellipsize = r->ellipsize; - if (r->alignment != -1) - n->alignment = r->alignment; - if (r->hide_text != -1) - n->hide_text = r->hide_text; - if (r->progress_bar_alignment != -1) - n->progress_bar_alignment = r->progress_bar_alignment; - if (r->min_icon_size != -1) - n->min_icon_size = r->min_icon_size; - if (r->max_icon_size != -1) - n->max_icon_size = r->max_icon_size; + if (save) notification_keep_original(n); + + RULE_APPLY2(dbus_timeout, override_dbus_timeout, -1); + RULE_APPLY2(transient, set_transient, -1); + + RULE_APPLY(timeout, -1); + RULE_APPLY(urgency, URG_NONE); + RULE_APPLY(fullscreen, FS_NULL); + RULE_APPLY(history_ignore, -1); + RULE_APPLY(skip_display, -1); + RULE_APPLY(word_wrap, -1); + RULE_APPLY(ellipsize, -1); + RULE_APPLY(alignment, -1); + RULE_APPLY(hide_text, -1); + RULE_APPLY(progress_bar_alignment, -1); + RULE_APPLY(min_icon_size, -1); + RULE_APPLY(max_icon_size, -1); + RULE_APPLY(markup, MARKUP_NULL); + RULE_APPLY(icon_position, -1); + RULE_APPLY(override_pause_level, -1); + + if (COLOR_VALID(r->fg)) { + if (save && !COLOR_VALID(n->original->fg)) n->original->fg = n->colors.fg; + n->colors.fg = r->fg; + } + if (COLOR_VALID(r->bg)) { + if (save && !COLOR_VALID(n->original->bg)) n->original->bg = n->colors.bg; + n->colors.bg = r->bg; + } + if (COLOR_VALID(r->highlight)) { + if (save && !COLOR_VALID(n->original->highlight)) n->original->highlight = n->colors.highlight; + n->colors.highlight = r->highlight; + } + if (COLOR_VALID(r->fc)) { + if (save && !COLOR_VALID(n->original->fc)) n->original->fc = n->colors.frame; + n->colors.frame = r->fc; + } + if (r->format) + RULE_APPLY(format, NULL); if (r->action_name) { - g_free(n->default_action_name); + if (save && n->original->action_name) + n->original->action_name = n->default_action_name; + else + g_free(n->default_action_name); + n->default_action_name = g_strdup(r->action_name); } if (r->set_category) { - g_free(n->category); + if (save && n->original->set_category) + n->original->set_category = n->category; + else + g_free(n->category); + n->category = g_strdup(r->set_category); } - if (r->markup != MARKUP_NULL) - n->markup = r->markup; - if (r->icon_position != -1) - n->icon_position = r->icon_position; - if (COLOR_VALID(r->fg)) - n->colors.fg = r->fg; - if (COLOR_VALID(r->bg)) - n->colors.bg = r->bg; - if (COLOR_VALID(r->highlight)) - n->colors.highlight = r->highlight; - if (COLOR_VALID(r->fc)) - n->colors.frame = r->fc; - if (r->format) - n->format = r->format; if (r->default_icon) { - g_free(n->default_icon_name); + if (save && n->original->default_icon) + n->original->default_icon = n->default_icon_name; + else + g_free(n->default_icon_name); + n->default_icon_name = g_strdup(r->default_icon); } + if (r->set_stack_tag) { + if (save && !n->original->set_stack_tag) + n->original->set_stack_tag = n->stack_tag; + else + g_free(n->stack_tag); + + n->stack_tag = g_strdup(r->set_stack_tag); + } if (r->new_icon) { + if (save && !n->original->new_icon) + n->original->new_icon = g_strdup(n->iconname); + // FIXME This is not efficient when the icon is replaced // multiple times for the same notification. To fix this, a // separate variable is needed to track if the icon is @@ -82,18 +114,66 @@ void rule_apply(struct rule *r, struct notification *n) n->receiving_raw_icon = false; } if (r->script) { - n->scripts = g_renew(const char*,n->scripts,n->script_count + 1); - n->scripts[n->script_count] = r->script; + if (save && !n->original->script && n->script_count > 0) + n->original->script = n->scripts[0]; + n->scripts = g_renew(char *, n->scripts, n->script_count + 2); + n->scripts[n->script_count] = g_strdup(r->script); + n->scripts[n->script_count + 1] = NULL; n->script_count++; } - if (r->set_stack_tag) { - g_free(n->stack_tag); - n->stack_tag = g_strdup(r->set_stack_tag); - } - if (r->override_pause_level != -1) { - n->override_pause_level = r->override_pause_level; - } +} + +void rule_print(const struct rule *r) +{ + printf("{\n"); + printf("\tname: '%s'\n", STR_NN(r->name)); + printf("\tenabled: %d\n", r->enabled); + + // filters + if (r->appname != NULL) printf("\tappname: '%s'\n", r->appname); + if (r->summary != NULL) printf("\tsummary: '%s'\n", r->summary); + if (r->body != NULL) printf("\tbody: '%s'\n", r->body); + if (r->icon != NULL) printf("\ticon: '%s'\n", r->icon); + if (r->category != NULL) printf("\tcategory: '%s'\n", r->category); + if (r->msg_urgency != URG_NONE) printf("\tmsg_urgency: '%s'\n", notification_urgency_to_string(r->msg_urgency)); + if (r->stack_tag != NULL) printf("\tstack_tag: '%s'\n", r->stack_tag); + if (r->desktop_entry != NULL) printf("\tdesktop_entry: '%s'\n", r->desktop_entry); + if (r->match_dbus_timeout != -1) printf("\tmatch_dbus_timeout: %ld\n", r->match_dbus_timeout); + if (r->match_transient != -1) printf("\tmatch_transient: %d\n", r->match_transient); + + // modifiers + if (r->timeout != -1) printf("\ttimeout: %ld\n", r->timeout); + if (r->override_dbus_timeout != -1) printf("\toverride_dbus_timeout: %ld\n", r->override_dbus_timeout); + if (r->markup != -1) printf("\tmarkup: %d\n", r->markup); + if (r->action_name != NULL) printf("\taction_name: '%s'\n", r->action_name); + if (r->urgency != URG_NONE) printf("\turgency: '%s'\n", notification_urgency_to_string(r->urgency)); + if (r->history_ignore != -1) printf("\thistory_ignore: %d\n", r->history_ignore); + if (r->set_transient != -1) printf("\tset_transient: %d\n", r->set_transient); + if (r->skip_display != -1) printf("\tskip_display: %d\n", r->skip_display); + if (r->word_wrap != -1) printf("\tword_wrap: %d\n", r->word_wrap); + if (r->ellipsize != -1) printf("\tellipsize: %d\n", r->ellipsize); + if (r->alignment != -1) printf("\talignment: %d\n", r->alignment); + if (r->hide_text != -1) printf("\thide_text: %d\n", r->hide_text); + if (r->icon_position != -1) printf("\ticon_position: %d\n", r->icon_position); + if (r->min_icon_size != -1) printf("\tmin_icon_size: %d\n", r->min_icon_size); + if (r->max_icon_size != -1) printf("\tmax_icon_size: %d\n", r->max_icon_size); + if (r->override_pause_level != -1) printf("\toverride_pause_level: %d\n", r->override_pause_level); + if (r->new_icon != NULL) printf("\tnew_icon: '%s'\n", r->new_icon); + if (r->default_icon != NULL) printf("\tdefault_icon: '%s'\n", r->default_icon); + + char buf[10]; + if (COLOR_VALID(r->fg)) printf("\tfg: %s\n", color_to_string(r->fg, buf)); + if (COLOR_VALID(r->bg)) printf("\tbg: %s\n", color_to_string(r->bg, buf)); + if (COLOR_VALID(r->highlight)) printf("\thighlight: %s\n", color_to_string(r->highlight, buf)); + if (COLOR_VALID(r->fc)) printf("\tframe: %s\n", color_to_string(r->fc, buf)); + if (r->set_category != NULL) printf("\tset_category: '%s'\n", r->set_category); + if (r->format != NULL) printf("\tformat: '%s'\n", r->format); + if (r->script != NULL) printf("\tscript: '%s'\n", r->script); + if (r->fullscreen != FS_NULL) printf("\tfullscreen: %s\n", enum_to_string_fullscreen(r->fullscreen)); + if (r->progress_bar_alignment != -1) printf("\tprogress_bar_alignment: %d\n", r->progress_bar_alignment); + if (r->set_stack_tag != NULL) printf("\tset_stack_tag: %s\n", r->set_stack_tag); + printf("}\n"); } /* @@ -104,7 +184,7 @@ void rule_apply_all(struct notification *n) for (GSList *iter = rules; iter; iter = iter->next) { struct rule *r = iter->data; if (rule_matches_notification(r, n)) { - rule_apply(r, n); + rule_apply(r, n, true); } } } diff --git a/src/rules.h b/src/rules.h index b8e1339b0..e676f3a5b 100644 --- a/src/rules.h +++ b/src/rules.h @@ -75,7 +75,8 @@ extern GSList *rules; */ struct rule *rule_new(const char *name); -void rule_apply(struct rule *r, struct notification *n); +void rule_print(const struct rule *r); +void rule_apply(struct rule *r, struct notification *n, bool save); void rule_apply_all(struct notification *n); bool rule_matches_notification(struct rule *r, struct notification *n); diff --git a/src/settings.c b/src/settings.c index 0220b6e42..f056ff4ad 100644 --- a/src/settings.c +++ b/src/settings.c @@ -106,17 +106,8 @@ static void config_files_add_drop_ins(GPtrArray *config_files, const char *path) * drop-ins and puts their locations in a GPtrArray, @e most important last. * * The returned GPtrArray and it's elements are owned by the caller. - * - * @param path The config path that overrides the default config path. No - * drop-in files or other configs are searched. */ -static GPtrArray* get_conf_files(const char *path) { - if (path) { - GPtrArray *result = g_ptr_array_new_full(1, g_free); - g_ptr_array_add(result, g_strdup(path)); - return result; - } - +static GPtrArray* get_conf_files(void) { GPtrArray *config_locations = get_xdg_conf_basedirs(); GPtrArray *config_files = g_ptr_array_new_full(3, g_free); char *dunstrc_location = NULL; @@ -160,18 +151,6 @@ void settings_init(void) { } } -// NOTE: This function is probably outdated and no longer relevant -// Since it does not even fully display rule information we -// may want to remove it or change it -void print_rule(struct rule* r) { - LOG_D("Rule %s", STR_NN(r->name)); - LOG_D("summary %s", STR_NN(r->summary)); - LOG_D("appname %s", STR_NN(r->appname)); - LOG_D("script %s", STR_NN(r->script)); - char buf[10]; - LOG_D("frame %s", STR_NN(color_to_string(r->fc, buf))); -} - void check_and_correct_settings(struct settings *s) { #ifndef ENABLE_WAYLAND @@ -289,27 +268,36 @@ static void process_conf_file(const gpointer conf_fname, gpointer n_success) { ++(*(int *) n_success); } -void load_settings(const char *const path) { - // NOTE: settings_init should be called before if some settings are changed - // But having it here is useful for tests - // Potentially, we could update the settings code and move this somewhere else +void load_settings(char **const config_paths) +{ + // NOTE: settings_init should be called at the start of dunst_main, otherwise + // the cmdline settings would be reset settings_init(); + LOG_D("Setting defaults"); set_defaults(); - GPtrArray *conf_files = get_conf_files(path); + guint length = g_strv_length(config_paths); + + GPtrArray *conf_files; + + if (length != 0) { + conf_files = g_ptr_array_new_full(length, g_free); + for (int i = 0; config_paths[i]; i++) + g_ptr_array_add(conf_files, g_strdup(config_paths[i])); + } else { + // Use default locations (and search drop-ins) + conf_files = get_conf_files(); + } /* Load all conf files and drop-ins, least important first. */ int n_loaded_confs = 0; g_ptr_array_foreach(conf_files, process_conf_file, &n_loaded_confs); if (0 == n_loaded_confs) - LOG_I("No configuration file found, using defaults"); + LOG_M("No configuration file found, using defaults"); - for (GSList *iter = rules; iter; iter = iter->next) { - struct rule *r = iter->data; - print_rule(r); - } g_ptr_array_unref(conf_files); } + /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/settings.h b/src/settings.h index fcefa5b59..c59c3e89f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -183,7 +183,7 @@ extern struct settings settings; void settings_init(void); -void load_settings(const char *const path); +void load_settings(char **const config_paths); #endif /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/settings_data.h b/src/settings_data.h index 43d6d55dc..979d26d6d 100644 --- a/src/settings_data.h +++ b/src/settings_data.h @@ -168,6 +168,7 @@ static const struct rule empty_rule = { .progress_bar_alignment = -1, .min_icon_size = -1, .max_icon_size = -1, + .fullscreen = FS_NULL, .override_pause_level = -1 }; diff --git a/src/wayland/wl.c b/src/wayland/wl.c index e97e35662..ec6133e4b 100644 --- a/src/wayland/wl.c +++ b/src/wayland/wl.c @@ -332,10 +332,10 @@ void wl_deinit(void) { // could have been aborted half way through, or the compositor doesn't // support some of these features. if (ctx.layer_surface != NULL) { - zwlr_layer_surface_v1_destroy(ctx.layer_surface); + g_clear_pointer(&ctx.layer_surface, zwlr_layer_surface_v1_destroy); } if (ctx.surface != NULL) { - wl_surface_destroy(ctx.surface); + g_clear_pointer(&ctx.surface, wl_surface_destroy); } finish_buffer(&ctx.buffers[0]); finish_buffer(&ctx.buffers[1]); @@ -352,38 +352,41 @@ void wl_deinit(void) { destroy_seat(seat); } + ctx.outputs = (struct wl_list) {0}; + ctx.seats = (struct wl_list) {0}; + #ifdef HAVE_WL_CURSOR_SHAPE if (ctx.cursor_shape_manager) - wp_cursor_shape_manager_v1_destroy(ctx.cursor_shape_manager); + g_clear_pointer(&ctx.cursor_shape_manager, wp_cursor_shape_manager_v1_destroy); #endif #ifdef HAVE_WL_EXT_IDLE_NOTIFY if (ctx.ext_idle_notifier) - ext_idle_notifier_v1_destroy(ctx.ext_idle_notifier); + g_clear_pointer(&ctx.ext_idle_notifier, ext_idle_notifier_v1_destroy); #endif if (ctx.idle_handler) - org_kde_kwin_idle_destroy(ctx.idle_handler); + g_clear_pointer(&ctx.idle_handler, org_kde_kwin_idle_destroy); if (ctx.layer_shell) - zwlr_layer_shell_v1_destroy(ctx.layer_shell); + g_clear_pointer(&ctx.layer_shell, zwlr_layer_shell_v1_destroy); if (ctx.compositor) - wl_compositor_destroy(ctx.compositor); + g_clear_pointer(&ctx.compositor, wl_compositor_destroy); if (ctx.shm) - wl_shm_destroy(ctx.shm); + g_clear_pointer(&ctx.shm, wl_shm_destroy); if (ctx.registry) - wl_registry_destroy(ctx.registry); + g_clear_pointer(&ctx.registry, wl_registry_destroy); if (ctx.cursor_theme != NULL) { - wl_cursor_theme_destroy(ctx.cursor_theme); - wl_surface_destroy(ctx.cursor_surface); + g_clear_pointer(&ctx.cursor_theme, wl_cursor_theme_destroy); + g_clear_pointer(&ctx.cursor_surface, wl_surface_destroy); } // this also disconnects the wl_display - g_water_wayland_source_free(ctx.esrc); + g_clear_pointer(&ctx.esrc, g_water_wayland_source_free); } static void schedule_frame_and_commit(void); diff --git a/test/functional-tests/script_test.sh b/test/functional-tests/script_test.sh index 8e21d4023..705c3802d 100755 --- a/test/functional-tests/script_test.sh +++ b/test/functional-tests/script_test.sh @@ -1,4 +1,4 @@ #!/bin/bash echo "triggered script!" -$DUNSTIFY "Success" "ooooh yeah" +dunstify "Success" "ooooh yeah" diff --git a/test/functional-tests/test.sh b/test/functional-tests/test.sh index a66009a71..a401eac61 100755 --- a/test/functional-tests/test.sh +++ b/test/functional-tests/test.sh @@ -3,11 +3,12 @@ # prefix should be the root of the repository PREFIX="${PREFIX:-../..}" TESTDIR="$PREFIX/test/functional-tests" +DUNST="${DUNST:-$PREFIX/dunst}" +DUNSTIFY="${DUNSTIFY:-$PREFIX/dunstify}" +DUNSTCTL="${DUSNTCTL:-$PREFIX/dunstctl}" # for run_script export PATH="$TESTDIR:$PATH" -export DUNST="${DUNST:-$PREFIX/dunst}" -export DUNSTIFY="${DUNSTIFY:-$PREFIX/dunstify}" function keypress { echo "press enter to continue..." @@ -20,7 +21,7 @@ function tmp_dunstrc { } function tmp_clean { - rm "$TESTDIR/dunstrc.tmp" + rm "$TESTDIR/dunstrc.tmp" } function start_dunst { @@ -371,6 +372,38 @@ function vertical_align { done } +function hot_reload { + echo "###################################" + echo "hot_reload" + echo "###################################" + + tmp_dunstrc dunstrc.default "background = \"#313131\"" + start_dunst dunstrc.tmp + + $DUNSTIFY -a "dunst tester" "Nice notification" "This will change once" + $DUNSTIFY -a "dunst tester" --hints string:category:change "Change" "And this too" + keypress + + tmp_dunstrc dunstrc.default " + [change] + category = change + background = \"#525\" + set_category = notchange + urgency = critical + " + $DUNSTCTL reload + keypress + + $DUNSTCTL reload + keypress + + $DUNSTCTL reload "$TESTDIR/dunstrc.hot_reload" + $DUNSTIFY -a "dunst tester" "New notification" "Now we are using another config file" + keypress + + tmp_clean +} + if [ -n "$1" ]; then while [ -n "$1" ]; do $1 @@ -393,6 +426,7 @@ else separator_click dynamic_height vertical_align + hot_reload fi killall dunst diff --git a/test/icon-lookup.c b/test/icon-lookup.c index 012e8a9f9..819a9d711 100644 --- a/test/icon-lookup.c +++ b/test/icon-lookup.c @@ -57,7 +57,7 @@ TEST test_new_icon_overrides_raw_icon(void) { ASSERT(n->icon); int old_width = cairo_image_surface_get_width(n->icon); - rule_apply(rule, n); + rule_apply(rule, n, true); ASSERT(n->icon); ASSERT(old_width != cairo_image_surface_get_width(n->icon)); diff --git a/test/setting.c b/test/setting.c index 5b73c7094..5083cadb1 100644 --- a/test/setting.c +++ b/test/setting.c @@ -9,11 +9,11 @@ extern const char *base; // In this suite a few dunstrc's are tested to see if the settings code works // This file is called setting.c, since the name settings.c caused issues. -char *config_path; +char *config_paths[2] = {0}; TEST test_dunstrc_markup(void) { - config_path = g_strconcat(base, "/data/dunstrc.markup", NULL); - load_settings(config_path); + config_paths[0] = g_strconcat(base, "/data/dunstrc.markup", NULL); + load_settings(config_paths); ASSERT_STR_EQ(settings.font, "Monospace 8"); @@ -24,13 +24,13 @@ TEST test_dunstrc_markup(void) { ASSERT_STR_EQ(e_format, got_format); ASSERT(settings.indicate_hidden); - g_free(config_path); + g_clear_pointer(&config_paths[0], g_free); PASS(); } TEST test_dunstrc_nomarkup(void) { - config_path = g_strconcat(base, "/data/dunstrc.nomarkup", NULL); - load_settings(config_path); + config_paths[0] = g_strconcat(base, "/data/dunstrc.nomarkup", NULL); + load_settings(config_paths); ASSERT_STR_EQ(settings.font, "Monospace 8"); @@ -41,7 +41,7 @@ TEST test_dunstrc_nomarkup(void) { ASSERT_STR_EQ(e_format, got_format); ASSERT(settings.indicate_hidden); - g_free(config_path); + g_clear_pointer(&config_paths[0], g_free); PASS(); } @@ -50,11 +50,11 @@ TEST test_dunstrc_defaults(void) { struct settings s_default; struct settings s_dunstrc; - config_path = g_strconcat(base, "/data/dunstrc.default", NULL); + config_paths[0] = g_strconcat(base, "/data/dunstrc.default", NULL); set_defaults(); s_default = settings; - load_settings(config_path); + load_settings(config_paths); s_dunstrc = settings; ASSERT_EQ(s_default.corner_radius, s_dunstrc.corner_radius); @@ -105,7 +105,7 @@ TEST test_dunstrc_defaults(void) { /* printf("%zu\n", offset); */ } - g_free(config_path); + g_clear_pointer(&config_paths[0], g_free); PASS(); } diff --git a/test/test.c b/test/test.c index 0e6312548..5d4e42907 100644 --- a/test/test.c +++ b/test/test.c @@ -48,8 +48,9 @@ int main(int argc, char *argv[]) { // initialize settings - char *config_path = g_strconcat(base, "/data/dunstrc.default", NULL); - load_settings(config_path); + char **configs = g_malloc0(2 * sizeof(char *)); + configs[0] = g_strconcat(base, "/data/dunstrc.default", NULL); + load_settings(configs); GREATEST_MAIN_BEGIN(); RUN_SUITE(suite_utils); @@ -71,7 +72,7 @@ int main(int argc, char *argv[]) { RUN_SUITE(suite_input); base = NULL; - g_free(config_path); + g_strfreev(configs); free(prog); // this returns the error code