diff --git a/config.h b/config.h index c5f2577c4..c98cbad0e 100644 --- a/config.h +++ b/config.h @@ -6,10 +6,13 @@ struct settings defaults = { .markup = MARKUP_NO, .colors_norm.bg = "#1793D1", .colors_norm.fg = "#DDDDDD", +.colors_norm.highlight = "#1745d1", .colors_crit.bg = "#ffaaaa", .colors_crit.fg = "#000000", +.colors_crit.highlight = "#ff6666", .colors_low.bg = "#aaaaff", .colors_low.fg = "#000000", +.colors_low.highlight = "#7f7fff", .format = "%s %b", /* default format */ .timeouts = { S2US(10), S2US(10), S2US(0) }, /* low, normal, critical */ @@ -110,6 +113,15 @@ struct settings defaults = { .mouse_right_click = (enum mouse_action []){MOUSE_CLOSE_ALL, -1}, +.progress_bar_height = 10, + +.progress_bar_min_width = 150, + +.progress_bar_max_width = 300, + +.progress_bar_frame_width = 1, + +.progress_bar = true, }; struct rule default_rules[] = { diff --git a/docs/dunst.pod b/docs/dunst.pod index 90973ac15..45864f930 100644 --- a/docs/dunst.pod +++ b/docs/dunst.pod @@ -142,6 +142,32 @@ the notification on the left border of the screen while a horizontal offset of =back +=item B (values: [true/false], default: true) + +When an integer value is passed to dunst as a hint (see B), a +progress bar will be drawn at the bottom of the notification. This +behavior can be turned off by setting this setting to false. + +=item B (default: 10) + +The height of the progress bar in pixel. This includes the frame. Make sure +this value is bigger than twice the frame width. + +=item B (default: 150) + +The minimum width of the progress bar in pixels. The notification is rescaled +to fit the bar. + +=item B (default: 300) + +The maximum width of the progress bar in pixels. The notification is resized +to fit the progress bar. + +=item B (default: 1) + +The frame width of the progress bar in pixels. This value should be smaller +than half of the progress bar height. + =item B (values: [true/false], default: true) If this is set to true, a notification indicating how many notifications are @@ -561,8 +587,8 @@ Specifies the keyboard shortcut that opens the context menu. The urgency sections work in a similar way to rules and can be used to specify attributes for the different urgency levels of notifications (low, normal, -critical). Currently only the background, foreground, timeout, frame_color and -icon attributes can be modified. +critical). Currently only the background, foreground, hightlight, timeout, +frame_color and icon attributes can be modified. The urgency sections are urgency_low, urgency_normal, urgency_critical for low, normal and critical urgency respectively. @@ -595,6 +621,12 @@ Defines the background color for low, normal and critical notifications respecti See COLORS for the value format. +=item B<-lh/nh/ch color> + +Defines the highlight color for low, normal and critical notifications respectively. + +See COLORS for the value format. + =item B<-lfr/nfr/cfr color> Defines the frame color for low, normal and critical notifications respectively. @@ -725,7 +757,12 @@ The background color of the notification. See COLORS for possible values. =item C -The background color of the notification. See COLORS for possible values. +The foreground color of the notification. See COLORS for possible values. + +=item C + +The highlight color of the notification. This color is used for coloring the +progress bar. See COLORS for possible values. =item C diff --git a/dunstrc b/dunstrc index 331347ee3..511eb4e34 100644 --- a/dunstrc +++ b/dunstrc @@ -30,7 +30,24 @@ # The width can be negative. In this case the actual width is the # screen width minus the width defined in within the geometry option. geometry = "300x5-30+20" - + + # Turn on the progess bar + progress_bar = true + + # Set the progress bar height. This includes the frame, so make sure + # it's at least twice as big as the frame width. + progress_bar_height = 10 + + # Set the frame width of the progress bar + progress_bar_frame_width = 1 + + # Set the minimum width for the progress bar + progress_bar_min_width = 150 + + # Set the maximum width for the progress bar + progress_bar_max_width = 300 + + # Show how many messages are currently hidden (because of geometry). indicate_hidden = yes diff --git a/src/draw.c b/src/draw.c index 6a23f4335..8a839c476 100644 --- a/src/draw.c +++ b/src/draw.c @@ -23,6 +23,7 @@ struct colored_layout { PangoLayout *l; struct color fg; struct color bg; + struct color highlight; struct color frame; char *text; PangoAttrList *attr; @@ -166,6 +167,11 @@ static bool have_dynamic_width(void) return (settings.geometry.width_set && settings.geometry.w == 0); } +static bool have_progress_bar(const struct notification *n) +{ + return (n->progress >= 0 && settings.progress_bar == true); +} + static struct dimensions calculate_dimensions(GSList *layouts) { struct dimensions dim = { 0 }; @@ -237,6 +243,11 @@ static struct dimensions calculate_dimensions(GSList *layouts) text_width = MAX(w, text_width); } + if (have_progress_bar(cl->n)){ + dim.h += settings.progress_bar_height + settings.padding; + dim.w = MAX(dim.w, settings.progress_bar_min_width); + } + dim.corner_radius = MIN(dim.corner_radius, h/2); } @@ -299,6 +310,7 @@ static struct colored_layout *layout_init_shared(cairo_t *c, const struct notifi cl->fg = string_to_color(n->colors.fg); cl->bg = string_to_color(n->colors.bg); + cl->highlight = string_to_color(n->colors.highlight); cl->frame = string_to_color(n->colors.frame); cl->n = n; @@ -354,7 +366,13 @@ static struct colored_layout *layout_from_notification(cairo_t *c, struct notifi pango_layout_get_pixel_size(cl->l, NULL, &(n->displayed_height)); if (cl->icon) n->displayed_height = MAX(cairo_image_surface_get_height(cl->icon), n->displayed_height); - n->displayed_height = MAX(settings.notification_height, n->displayed_height + settings.padding * 2); + + n->displayed_height = n->displayed_height + settings.padding * 2; + + // progress bar + if (have_progress_bar(n)) n->displayed_height += settings.progress_bar_height + settings.padding; + + n->displayed_height = MAX(settings.notification_height, n->displayed_height); n->first_render = false; return cl; @@ -397,11 +415,16 @@ static int layout_get_height(struct colored_layout *cl) { int h; int h_icon = 0; + int h_progress_bar = 0; pango_layout_get_pixel_size(cl->l, NULL, &h); if (cl->icon) h_icon = cairo_image_surface_get_height(cl->icon); + if (have_progress_bar(cl->n)){ + h_progress_bar = settings.progress_bar_height + settings.padding; + } - return MAX(h, h_icon); + int res = MAX(h, h_icon) + h_progress_bar; + return res; } /* Attempt to make internal radius more organic. @@ -563,11 +586,15 @@ static cairo_surface_t *render_background(cairo_surface_t *srf, static void render_content(cairo_t *c, struct colored_layout *cl, int width) { const int h = layout_get_height(cl); + int h_without_progress_bar = h; + if (have_progress_bar(cl->n)){ + h_without_progress_bar -= settings.progress_bar_height + settings.padding; + } int h_text; pango_layout_get_pixel_size(cl->l, NULL, &h_text); int text_x = settings.h_padding, - text_y = settings.padding + h / 2 - h_text / 2; + text_y = settings.padding + h_without_progress_bar / 2 - h_text / 2; // text positioning if (cl->icon) { @@ -575,7 +602,7 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width) if (settings.vertical_alignment == VERTICAL_TOP) { text_y = settings.padding; } else if (settings.vertical_alignment == VERTICAL_BOTTOM) { - text_y = h + settings.padding - h_text; + text_y = h_without_progress_bar + settings.padding - h_text; if (text_y < 0) text_y = settings.padding; } // else VERTICAL_CENTER @@ -597,14 +624,14 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width) unsigned int image_width = cairo_image_surface_get_width(cl->icon), image_height = cairo_image_surface_get_height(cl->icon), image_x = width - settings.h_padding - image_width, - image_y = settings.padding + h/2 - image_height/2; + image_y = settings.padding + h_without_progress_bar/2 - image_height/2; // vertical alignment if (settings.vertical_alignment == VERTICAL_TOP) { image_y = settings.padding; } else if (settings.vertical_alignment == VERTICAL_BOTTOM) { - image_y = h + settings.padding - image_height; - if (image_y < settings.padding || image_y > h) + image_y = h_without_progress_bar + settings.padding - image_height; + if (image_y < settings.padding || image_y > h_without_progress_bar) image_y = settings.padding; } // else VERTICAL_CENTER @@ -618,6 +645,38 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width) cairo_fill(c); } + // progress bar positioning + if (have_progress_bar(cl->n)){ + int progress = MIN(cl->n->progress, 100); + unsigned int frame_width = settings.progress_bar_frame_width, + progress_width = MIN(width - 2 * settings.h_padding, settings.progress_bar_max_width), + progress_height = settings.progress_bar_height - frame_width, + frame_x = settings.h_padding, + frame_y = settings.padding + h - settings.progress_bar_height, + progress_width_without_frame = progress_width - 2 * frame_width, + progress_width_1 = progress_width_without_frame * progress / 100, + progress_width_2 = progress_width_without_frame - progress_width_1, + x_bar_1 = frame_x + frame_width, + x_bar_2 = x_bar_1 + progress_width_1; + + double half_frame_width = frame_width / 2.0; + + // draw progress bar + // Note: the bar could be drawn a bit smaller, because the frame is drawn on top + // left side + cairo_set_source_rgba(c, cl->highlight.r, cl->highlight.g, cl->highlight.b, cl->highlight.a); + cairo_rectangle(c, x_bar_1, frame_y, progress_width_1, progress_height); + cairo_fill(c); + // right side + cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a); + cairo_rectangle(c, x_bar_2, frame_y, progress_width_2, progress_height); + cairo_fill(c); + // border + cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a); + cairo_rectangle(c, frame_x + half_frame_width, frame_y + half_frame_width, progress_width - frame_width, progress_height); + cairo_set_line_width(c, frame_width); + cairo_stroke(c); + } } static struct dimensions layout_render(cairo_surface_t *srf, diff --git a/src/notification.c b/src/notification.c index 1c18db418..c693231c5 100644 --- a/src/notification.c +++ b/src/notification.c @@ -64,6 +64,7 @@ void notification_print(const struct notification *n) printf("\tformatted: '%s'\n", n->msg); printf("\tfg: %s\n", n->colors.fg); printf("\tbg: %s\n", n->colors.bg); + printf("\thighlight: %s\n", n->colors.highlight); printf("\tframe: %s\n", n->colors.frame); printf("\tfullscreen: %s\n", enum_to_string_fullscreen(n->fullscreen)); printf("\tprogress: %d\n", n->progress); @@ -236,6 +237,7 @@ void notification_unref(struct notification *n) g_free(n->urls); g_free(n->colors.fg); g_free(n->colors.bg); + g_free(n->colors.highlight); g_free(n->colors.frame); g_free(n->stack_tag); g_free(n->desktop_entry); @@ -393,6 +395,8 @@ void notification_init(struct notification *n) n->colors.fg = g_strdup(defcolors.fg); if (!n->colors.bg) n->colors.bg = g_strdup(defcolors.bg); + if (!n->colors.highlight) + n->colors.highlight = g_strdup(defcolors.highlight); if (!n->colors.frame) n->colors.frame = g_strdup(defcolors.frame); diff --git a/src/notification.h b/src/notification.h index e387c4a90..5795de67d 100644 --- a/src/notification.h +++ b/src/notification.h @@ -33,6 +33,7 @@ struct notification_colors { char *frame; char *bg; char *fg; + char *highlight; }; struct notification { diff --git a/src/rules.c b/src/rules.c index b7f185c98..35934730c 100644 --- a/src/rules.c +++ b/src/rules.c @@ -38,6 +38,10 @@ void rule_apply(struct rule *r, struct notification *n) g_free(n->colors.bg); n->colors.bg = g_strdup(r->bg); } + if (r->highlight) { + g_free(n->colors.highlight); + n->colors.highlight = g_strdup(r->highlight); + } if (r->fc) { g_free(n->colors.frame); n->colors.frame = g_strdup(r->fc); diff --git a/src/rules.h b/src/rules.h index 6b2d7b290..5589446d6 100644 --- a/src/rules.h +++ b/src/rules.h @@ -31,6 +31,7 @@ struct rule { char *new_icon; char *fg; char *bg; + char *highlight; char *fc; const char *format; const char *script; diff --git a/src/settings.c b/src/settings.c index 44774039b..419bf731f 100644 --- a/src/settings.c +++ b/src/settings.c @@ -355,6 +355,49 @@ void load_settings(char *cmdline_config_path) "Window corner radius" ); + settings.progress_bar_height = option_get_int( + "global", + "progress_bar_height", "-progress_bar_height", defaults.progress_bar_height, + "Height of the progress bar" + ); + + settings.progress_bar_min_width = option_get_int( + "global", + "progress_bar_min_width", "-progress_bar_min_width", defaults.progress_bar_min_width, + "Minimum width of the progress bar" + ); + + settings.progress_bar_max_width = option_get_int( + "global", + "progress_bar_max_width", "-progress_bar_max_width", defaults.progress_bar_max_width, + "Maximum width of the progress bar" + ); + + settings.progress_bar_frame_width = option_get_int( + "global", + "progress_bar_frame_width", "-progress_bar_frame_width", defaults.progress_bar_frame_width, + "Frame width of the progress bar" + ); + + settings.progress_bar = option_get_bool( + "global", + "progress_bar", "-progress_bar", true, + "Show the progress bar" + ); + + // check sanity of the progress bar options + { + if (settings.progress_bar_height < (2 * settings.progress_bar_frame_width)){ + LOG_E("setting progress_bar_frame_width is bigger than half of progress_bar_height"); + } + if (settings.progress_bar_max_width < (2 * settings.progress_bar_frame_width)){ + LOG_E("setting progress_bar_frame_width is bigger than half of progress_bar_max_width"); + } + if (settings.progress_bar_max_width < settings.progress_bar_min_width){ + LOG_E("setting progress_bar_max_width is smaller than progress_bar_min_width"); + } + } + { char *c = option_get_string( "global", @@ -584,6 +627,12 @@ void load_settings(char *cmdline_config_path) "Foreground color for notifications with low urgency" ); + settings.colors_low.highlight = option_get_string( + "urgency_low", + "highlight", "-lh", defaults.colors_low.highlight, + "Highlight color for notifications with low urgency" + ); + settings.colors_low.frame = option_get_string( "urgency_low", "frame_color", "-lfr", settings.frame_color ? settings.frame_color : defaults.colors_low.frame, @@ -614,6 +663,12 @@ void load_settings(char *cmdline_config_path) "Foreground color for notifications with normal urgency" ); + settings.colors_norm.highlight = option_get_string( + "urgency_normal", + "highlight", "-nh", defaults.colors_norm.highlight, + "Highlight color for notifications with normal urgency" + ); + settings.colors_norm.frame = option_get_string( "urgency_normal", "frame_color", "-nfr", settings.frame_color ? settings.frame_color : defaults.colors_norm.frame, @@ -644,6 +699,12 @@ void load_settings(char *cmdline_config_path) "Foreground color for notifications with ciritical urgency" ); + settings.colors_crit.highlight = option_get_string( + "urgency_critical", + "highlight", "-ch", defaults.colors_crit.highlight, + "Highlight color for notifications with ciritical urgency" + ); + settings.colors_crit.frame = option_get_string( "urgency_critical", "frame_color", "-cfr", settings.frame_color ? settings.frame_color : defaults.colors_crit.frame, @@ -756,6 +817,7 @@ void load_settings(char *cmdline_config_path) r->msg_urgency = ini_get_urgency(cur_section, "msg_urgency", r->msg_urgency); r->fg = ini_get_string(cur_section, "foreground", r->fg); r->bg = ini_get_string(cur_section, "background", r->bg); + r->bg = ini_get_string(cur_section, "highlight", r->highlight); r->fc = ini_get_string(cur_section, "frame_color", r->fc); r->format = ini_get_string(cur_section, "format", r->format); r->new_icon = ini_get_string(cur_section, "new_icon", r->new_icon); diff --git a/src/settings.h b/src/settings.h index 9f1e042f9..d1787b6b7 100644 --- a/src/settings.h +++ b/src/settings.h @@ -92,6 +92,11 @@ struct settings { enum mouse_action *mouse_left_click; enum mouse_action *mouse_middle_click; enum mouse_action *mouse_right_click; + int progress_bar_height; + int progress_bar_min_width; + int progress_bar_max_width; + int progress_bar_frame_width; + bool progress_bar; }; extern struct settings settings; diff --git a/test/functional-tests/dunstrc.progress_bar b/test/functional-tests/dunstrc.progress_bar new file mode 100644 index 000000000..4d80ae35c --- /dev/null +++ b/test/functional-tests/dunstrc.progress_bar @@ -0,0 +1,10 @@ +[global] + font = Monospace 8 + allow_markup = yes + format = "%s\n%b" + geometry = "0x5-30+20" + icon_position = left + progress_bar_min_width = 100 + progress_bar_max_width = 200 + progress_bar_frame_width = 5 + progress_bar_height = 30 diff --git a/test/functional-tests/test.sh b/test/functional-tests/test.sh index ae3ca172f..a8766f138 100755 --- a/test/functional-tests/test.sh +++ b/test/functional-tests/test.sh @@ -177,12 +177,35 @@ function geometry { keypress } +function progress_bar { + killall dunst + ../../dunst -config dunstrc.default & + ../../dunstify -h int:value:0 -a "dunst tester" -u c "Progress bar 0%: " + ../../dunstify -h int:value:33 -a "dunst tester" -u c "Progress bar 33%: " + ../../dunstify -h int:value:66 -a "dunst tester" -u c "Progress bar 66%: " + ../../dunstify -h int:value:100 -a "dunst tester" -u c "Progress bar 100%: " + keypress + killall dunst + ../../dunst -config dunstrc.default & + ../../dunstify -h int:value:33 -a "dunst tester" -u l "Low priority: " + ../../dunstify -h int:value:33 -a "dunst tester" -u n "Normal priority: " + ../../dunstify -h int:value:33 -a "dunst tester" -u c "Critical priority: " + keypress + killall dunst + ../../dunst -config dunstrc.progress_bar & + ../../dunstify -h int:value:33 -a "dunst tester" -u n "The progress bar should not be the entire width" + ../../dunstify -h int:value:33 -a "dunst tester" -u n "You might also notice height and frame size are changed" + ../../dunstify -h int:value:33 -a "dunst tester" -u c "Short" + keypress +} + if [ -n "$1" ]; then while [ -n "$1" ]; do $1 shift done else + progress_bar geometry corners show_age