Control: tags 1019755 + patch Control: tags 1019755 + pending Dear maintainer,
I've prepared an NMU for dunst (versioned as 1.9.0-0.1) and uploaded it to DELAYED/15. Please feel free to tell me if I should delay it longer. Regards, Boyuan Yang
diff -Nru dunst-1.8.1/CHANGELOG.md dunst-1.9.0/CHANGELOG.md --- dunst-1.8.1/CHANGELOG.md 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/CHANGELOG.md 2022-06-27 08:43:39.000000000 -0400 @@ -1,5 +1,39 @@ # Dunst changelog +## 1.9.0 -- 2022-06-27 + +### Added +- `override_dbus_timeout` setting to override the notification timeout set via + dbus. (#1035) +- Support notification gaps via the `gap_size` setting. Note that since the + notifications are not separate windows, you cannot click in between the + notifications. (#1053) +- Make `min_icon_size` and `max_icon_size` a rule for even more flexibility + (#1069) + +### Changed +- The window offset is now scaled according to `scale` as well. This way + notification stay visually in the same place on higher DPI screens. (#1039) +- For the recursive icon lookup, revert to using `min_icon_size` and + `max_icon_size` instead of `icon_size`. `min_icon_size` is used as the size to + look for in icon themes. This way of defining icon size is more flexible and + compatible with the old icon lookup. The new icon lookup should now be + superior for all use cases. (#1069) +- Recursive icon lookup is no longer experimental. +- Recursive icon lookup is enabled in the default dunstrc. This does not change + your settings when you have a custom dunstrc. + +### Fixed +- Added back the `action_name` setting that was accidentally dropped. (#1051) +- Broken `dunstctl history`. (#1060) +- Merged a few wayland fixes from mako (https://github.com/emersion/mako) + (#1067) +- `follow=keyboard`: Fix regression where we don't fall back to mouse (#1062) +- Raw icons not being scaled according to icon size (#1043) +- Notifications not disappearing. For some people notifications would sometimes + stay on screen until a new notification appeared. This should not happen + anymore (#1073). + ## 1.8.1 -- 2022-03-02 ### Fixed diff -Nru dunst-1.8.1/debian/changelog dunst-1.9.0/debian/changelog --- dunst-1.8.1/debian/changelog 2022-06-11 04:42:31.000000000 -0400 +++ dunst-1.9.0/debian/changelog 2022-11-25 14:04:01.000000000 -0500 @@ -1,3 +1,10 @@ +dunst (1.9.0-0.1) unstable; urgency=medium + + * Non-maintainer upload. + * New upstream release. (Closes: #1019755) + + -- Boyuan Yang <by...@debian.org> Fri, 25 Nov 2022 14:04:01 -0500 + dunst (1.8.1-1) unstable; urgency=medium [ Debian Janitor ] diff -Nru dunst-1.8.1/docs/dunst.5.pod dunst-1.9.0/docs/dunst.5.pod --- dunst-1.8.1/docs/dunst.5.pod 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/docs/dunst.5.pod 2022-06-27 08:43:39.000000000 -0400 @@ -206,7 +206,8 @@ =item B<separator_height> (default: 2) The height in pixels of the separator between notifications, if set to 0 there -will be no separating line between notifications. +will be no separating line between notifications. This setting will be ignored +if B<gap_size> is greater than 0. =item B<padding> (default: 8) @@ -240,6 +241,16 @@ Defines width in pixels of frame around the notification window. Set to 0 to disable. +=item B<gap_size> (default: 0) + +Size of gap to display between notifications. + +If value is greater than 0, B<separator_height> will be ignored and a border +of size B<frame_width> will be drawn around each notification instead. + +This setting requires a compositor and any applications displayed between the +gaps are not currently clickable. + =item B<separator_color> (values: [auto/foreground/frame/#RRGGBB] default: frame) Sets the color of the separator line between two notifications. @@ -328,6 +339,9 @@ If any of the following strings are present, they will be replaced with the equivalent notification attribute. +For a complete markup reference, see +<https://docs.gtk.org/Pango/pango_markup.html>. + =over 4 =item B<%a> appname @@ -385,31 +399,6 @@ Show an indicator if a notification contains actions and/or open-able URLs. See ACTIONS below for further details. -=item B<min_icon_size> (default: 0) - -Defines the minimum size in pixels for the icons. -If the icon is larger than or equal to the specified value it won't be affected. -If it's smaller then it will be scaled up so that the smaller axis is equivalent -to the specified size. - -Set to 0 to disable icon upscaling. (default) - -If B<icon_position> is set to off, this setting is ignored. - -=item B<max_icon_size> (default: 32) - -Defines the maximum size in pixels for the icons. -If the icon is smaller than or equal to the specified value it won't be affected. -If it's larger then it will be scaled down so that the larger axis is equivalent -to the specified size. - -Set to 0 to disable icon downscaling. - -If both B<min_icon_size> and B<max_icon_size> are enabled, the latter -gets the last say. - -If B<icon_position> is set to off, this setting is ignored. - =item B<icon_path> (default: "/usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/") Can be set to a colon-separated list of paths to search for icons to use with @@ -419,7 +408,7 @@ system, see B<enable_recursive_icon_lookup>. This new system will eventually replace this and will need new settings. -=item B<icon_theme> (default: "Adwaita", example: "Adwaita, breeze") I<Experimental> +=item B<icon_theme> (default: "Adwaita", example: "Adwaita, breeze") Comma-separated of names of the the themes to use for looking up icons. This has to be the name of the directory in which the theme is located, not the @@ -439,19 +428,17 @@ This setting is experimental and not enabled by default. See B<enable_recursive_icon_lookup> for how to enable it. -=item B<enable_recursive_icon_lookup> (default: false) I<Experimental> +=item B<enable_recursive_icon_lookup> (default: false) This setting enables the new icon lookup method. This new system will eventually be the old icon lookup. -Currently icons are looked up in the B<icon_path> and scaled according to -B<min_icon_size> and B<max_icon_size>. Since the B<icon_path> wasn't recursive, -one had to add a ton of paths to this list. +Currently icons are looked up in the B<icon_path>. Since the B<icon_path> wasn't +recursive, one had to add a ton of paths to this list. This has been drastically simplified by the new lookup method. Now you only have -to set B<icon_theme> to the name of the theme and B<icon_size> to the icon size -you want. To enable this new behaviour, set B<enable_recursive_icon_lookup> to -true in the I<[experimental]> section. See the respective settings for more -details. +to set B<icon_theme> to the name of the theme you want. To enable this new +behaviour, set B<enable_recursive_icon_lookup> to true in the I<[experimental]> +section. See the respective settings for more details. =item B<sticky_history> (values: [true/false], default: true) @@ -768,6 +755,13 @@ See C<set_transient> for more details about this attribute. +=item C<match_dbus_timeout> + +Matches the dbus timeout of the notification as set by the client or by some +other rule. + +See C<override_dbus_timeout> for more details about this attribute. + =item C<msg_urgency> Matches the urgency of the notification as set by the client or by some other @@ -855,6 +849,33 @@ Default: show +=item B<min_icon_size> (default: 32) + +Defines the minimum size in pixels for the icons. +If the icon is larger than or equal to the specified value it won't be affected. +If it's smaller then it will be scaled up so that the smaller axis is equivalent +to the specified size. + +When using recursive icon lookup (see B<enable_recursive_icon_lookup>), all +icons from a theme will be this size. + +If B<icon_position> is set to off, this setting is ignored. + +=item B<max_icon_size> (default: 128) + +Defines the maximum size in pixels for the icons. +If the icon is smaller than or equal to the specified value it won't be affected. +If it's larger then it will be scaled down so that the larger axis is equivalent +to the specified size. + +Set to 0 to disable icon downscaling. + +If both B<min_icon_size> and B<max_icon_size> are enabled, the latter +gets the last say. + +If B<icon_position> is set to off, this setting is ignored. + + =item C<new_icon> Updates the icon of the notification, it should be a path or a name for a valid @@ -901,6 +922,11 @@ Equivalent to the C<timeout> setting in the urgency sections. +=item C<override_dbus_timeout> + +Overrides the timeout specified in dbus. +This takes precedence over C<timeout>. + =item C<urgency> This sets the notification urgency. @@ -991,17 +1017,6 @@ =back -=item B<icon_size> (default: 32) I<Experimental> - -The size of the icon in pixels. This is commonly a multiple of 2, for example: -16, 32 or 64. This size is used for searching the right icon in B<icon_theme>. -If no icon of the right size can be found, no icon is displayed. When passing a -full icon path to dunst the icon will be used even when it's not the right -size. The icon is then scaled to be of size B<icon_size>. - -This setting is experimental and not enabled by default. See -B<enable_recursive_icon_lookup> for how to enable it. - =back As with the filtering attributes, each one corresponds to diff -Nru dunst-1.8.1/dunstctl dunst-1.9.0/dunstctl --- dunst-1.8.1/dunstctl 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/dunstctl 2022-06-27 08:43:39.000000000 -0400 @@ -138,7 +138,7 @@ || die "Dunst controlling interface not available. Is the version too old?" ;; "history") - busctl --user --json=pretty -l --no-pager call org.freedesktop.Notifications /org/freedesktop/Notifications org.dunstproject.cmd0 NotificationListHistory 2>/dev/null \ + 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." ;; "") diff -Nru dunst-1.8.1/dunstrc dunst-1.9.0/dunstrc --- dunst-1.8.1/dunstrc 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/dunstrc 2022-06-27 08:43:39.000000000 -0400 @@ -73,6 +73,7 @@ # Draw a line of "separator_height" pixel height between two # notifications. # Set to 0 to disable. + # If gap_size is greater than 0, this setting will be ignored. separator_height = 2 # Padding between text and separator. @@ -91,6 +92,12 @@ # Defines color of the frame around the notification window. frame_color = "#aaaaaa" + # Size of gap to display between notifications - requires a compositor. + # If value is greater than 0, separator_height will be ignored and a border + # of size frame_width will be drawn around each notification instead. + # Click events on gaps do not currently propagate to applications below. + gap_size = 0 + # Define a color for the separator. # possible values are: # * auto: dunst tries to find a color fitting to the background; @@ -184,18 +191,27 @@ ### Icons ### + # Recursive icon lookup. You can set a single theme, instead of having to + # define all lookup paths. + enable_recursive_icon_lookup = true + + # Set icon theme (only used for recursive icon lookup) + icon_theme = Adwaita + # You can also set multiple icon themes, with the leftmost one being used first. + # icon_theme = "Adwaita, breeze" + # Align icons left/right/top/off icon_position = left # Scale small icons up to this size, set to 0 to disable. Helpful # for e.g. small files or high-dpi screens. In case of conflict, # max_icon_size takes precedence over this. - min_icon_size = 0 + min_icon_size = 32 # Scale larger icons down to this size, set to 0 to disable - max_icon_size = 32 + max_icon_size = 128 - # Paths to default icons. + # Paths to default icons (only neccesary when not using recursive icon lookup) icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/ ### History ### diff -Nru dunst-1.8.1/HACKING.md dunst-1.9.0/HACKING.md --- dunst-1.8.1/HACKING.md 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/HACKING.md 2022-06-27 08:43:39.000000000 -0400 @@ -2,6 +2,78 @@ **You can generate an internal overview with doxygen. For this, use `make doc-doxygen` and you'll find an internal overview of all functions and symbols in `docs/internal/html`. You will also need `graphviz` for this.** + +For people wanting to develop new features or fix bugs for dunst, here are the +steps you should take. + +# Running dunst + +For building dunst, you should take a look at the README. After dunst is build, +you can run it with: + + ./dunst + +This might not work, however, since dunst will abort when another instance of +dunst or another notification daemon is running. You will see a message like: + + CRITICAL: [dbus_cb_name_lost:1044] Cannot acquire 'org.freedesktop.Notifications': Name is acquired by 'dunst' with PID '20937'. + +So it's best to kill any running instance of dunst before trying to run the +version you just built. You can do that by making a shell function as follows +and put it in your bashrc/zshrc/config.fish: + +```sh +run_dunst() { + if make -j dunst; then + pkill dunst + else + return 1 + fi + ./dunst $@ +} +``` + +If you run this function is the root directory of the repository, it will build +dunst, kill any running instances and run your freshly built version of dunst. + +# Testing dunst + +To test dunst, it's good to know the following commands. This way you can test +dunst on your local system and you don't have to wait for CI to finish. + +## Run test suite + +This will build dunst if there were any changes and run the test suite. You will +need `awk` for this to work (to color the output of the tests). + + make test + +## Run memory leak tests + +This will build dunst if there were any changes and run the test suite with +valgrind to make sure there aren't any memory leaks. You will have to build your +tests so that they free all allocated memory after you are done, otherwise this +test will fail. You will need to have `valgrind` installed for this. + + make test-valgrind + + +## Build the doxygen documentation + +The internal documentation can be built with (`doxygen` and `graphviz` required): + + make doc-doxygen + +To open them in your browser you can run something like: + + firefox docs/internal/html/index.html + + +# Running the tests with docker + +Dunst has a few docker images for running tests on different distributions. The +documentation for this can be found at https://github.com/dunst-project/docker-images + # Comments - Comment system is held similar to JavaDoc diff -Nru dunst-1.8.1/Makefile dunst-1.9.0/Makefile --- dunst-1.8.1/Makefile 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/Makefile 2022-06-27 08:43:39.000000000 -0400 @@ -3,7 +3,7 @@ include config.mk -VERSION := "1.8.1 (2022-03-02)" +VERSION := "1.9.0 (2022-06-27)" ifneq ($(wildcard ./.git/),) VERSION := $(shell ${GIT} describe --tags) endif diff -Nru dunst-1.8.1/RELEASE_NOTES dunst-1.9.0/RELEASE_NOTES --- dunst-1.8.1/RELEASE_NOTES 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/RELEASE_NOTES 2022-06-27 08:43:39.000000000 -0400 @@ -1,4 +1,17 @@ =================================================================================== +Release Notes For v1.9.0 +=================================================================================== + +This release marks the point for a few big features to be useable. The +recursive icon lookup is marked stable and is used by default for new users. It +is now also possible to add gaps between notifications, although it is not done +with separate windows, so clicks in between notification will not register to +the below window. You'll also need a compositor for the transparancy to take +effect. + +Take a look at the changelog for a more detailed change description. + +=================================================================================== Release Notes For v1.8.0 =================================================================================== diff -Nru dunst-1.8.1/src/dbus.c dunst-1.9.0/src/dbus.c --- dunst-1.8.1/src/dbus.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/dbus.c 2022-06-27 08:43:39.000000000 -0400 @@ -528,6 +528,7 @@ } GVariant *dict_value; + GVariant *icon_value = NULL; // First process the items that can be filtered on if ((dict_value = g_variant_lookup_value(hints, "urgency", G_VARIANT_TYPE_BYTE))) { @@ -600,15 +601,30 @@ if (!dict_value) dict_value = g_variant_lookup_value(hints, "icon_data", G_VARIANT_TYPE("(iiibiiay)")); if (dict_value) { - notification_icon_replace_data(n, dict_value); - g_variant_unref(dict_value); + // Signal that the notification is still waiting for a raw + // icon. It cannot be set now, because min_icon_size and + // max_icon_size aren't known yet. It cannot be set later, + // because it has to be overwritten by the new_icon rule. + n->receiving_raw_icon = true; + icon_value = dict_value; + dict_value = NULL; } + // Set the dbus timeout + if (timeout >= 0) + n->dbus_timeout = ((gint64)timeout) * 1000; + // All attributes that have to be set before initializations are set, // so we can initialize the notification. This applies all rules that // are defined and applies the formatting to the message. notification_init(n); + if (icon_value) { + if (n->receiving_raw_icon) + notification_icon_replace_data(n, icon_value); + g_variant_unref(icon_value); + } + // 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))) { g_free(n->colors.fg); @@ -633,9 +649,6 @@ g_variant_unref(dict_value); } - if (timeout >= 0) - n->timeout = ((gint64)timeout) * 1000; - g_variant_unref(hints); g_variant_type_free(required_type); g_free(actions); // the strv is only a shallow copy diff -Nru dunst-1.8.1/src/draw.c dunst-1.9.0/src/draw.c --- dunst-1.8.1/src/draw.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/draw.c 2022-06-27 08:43:39.000000000 -0400 @@ -293,12 +293,10 @@ static struct dimensions calculate_dimensions(GSList *layouts) { + int layout_count = g_slist_length(layouts); struct dimensions dim = { 0 }; double scale = output->get_scale(); - dim.h += 2 * settings.frame_width; - dim.h += (g_slist_length(layouts) - 1) * settings.separator_height; - dim.corner_radius = settings.corner_radius; for (GSList *iter = layouts; iter; iter = iter->next) { @@ -319,6 +317,16 @@ dim.w = max_width; } + if (settings.gap_size) { + int extra_frame_height = layout_count * (2 * settings.frame_width); + int extra_gap_height = (layout_count * settings.gap_size) - settings.gap_size; + int total_extra_height = extra_frame_height + extra_gap_height; + dim.h += total_extra_height; + } else { + dim.h += 2 * settings.frame_width; + dim.h += (layout_count - 1) * settings.separator_height; + } + return dim; } @@ -782,15 +790,19 @@ if (first) dim.y += settings.frame_width; - if (!last) - dim.y += settings.separator_height; - + if (last) + dim.y += settings.frame_width; if ((2 * settings.padding + cl_h) < settings.height) dim.y += cl_h + 2 * settings.padding; else dim.y += settings.height; + if (settings.gap_size) + dim.y += settings.gap_size; + else + dim.y += settings.separator_height; + cairo_destroy(c); cairo_surface_destroy(content); return dim; @@ -810,12 +822,12 @@ case ORIGIN_TOP_LEFT: case ORIGIN_LEFT_CENTER: case ORIGIN_BOTTOM_LEFT: - *ret_x = scr->x + settings.offset.x; + *ret_x = scr->x + round(settings.offset.x * draw_get_scale()); break; case ORIGIN_TOP_RIGHT: case ORIGIN_RIGHT_CENTER: case ORIGIN_BOTTOM_RIGHT: - *ret_x = scr->x + (scr->w - width) - settings.offset.x; + *ret_x = scr->x + (scr->w - width) - round(settings.offset.x * draw_get_scale()); break; case ORIGIN_TOP_CENTER: case ORIGIN_CENTER: @@ -830,12 +842,12 @@ case ORIGIN_TOP_LEFT: case ORIGIN_TOP_CENTER: case ORIGIN_TOP_RIGHT: - *ret_y = scr->y + settings.offset.y; + *ret_y = scr->y + round(settings.offset.y * draw_get_scale()); break; case ORIGIN_BOTTOM_LEFT: case ORIGIN_BOTTOM_CENTER: case ORIGIN_BOTTOM_RIGHT: - *ret_y = scr->y + (scr->h - height) - settings.offset.y; + *ret_y = scr->y + (scr->h - height) - round(settings.offset.y * draw_get_scale()); break; case ORIGIN_LEFT_CENTER: case ORIGIN_CENTER: @@ -861,12 +873,19 @@ round(dim.h * scale)); bool first = true; + bool last; for (GSList *iter = layouts; iter; iter = iter->next) { struct colored_layout *cl_this = iter->data; struct colored_layout *cl_next = iter->next ? iter->next->data : NULL; + last = !cl_next; + + if (settings.gap_size) { + first = true; + last = true; + } - dim = layout_render(image_surface, cl_this, cl_next, dim, first, !cl_next); + dim = layout_render(image_surface, cl_this, cl_next, dim, first, last); first = false; } diff -Nru dunst-1.8.1/src/dunst.c dunst-1.9.0/src/dunst.c --- dunst-1.8.1/src/dunst.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/dunst.c 2022-06-27 08:43:39.000000000 -0400 @@ -66,19 +66,21 @@ } LOG_D("Waking up"); - run(NULL); + run(GINT_TO_POINTER(1)); } static gboolean run(void *data) { static gint64 next_timeout = 0; + int reason = GPOINTER_TO_INT(data); - LOG_D("RUN"); + LOG_D("RUN, reason %i", reason); + gint64 now = time_monotonic_now(); dunst_status(S_FULLSCREEN, output->have_fullscreen_window()); dunst_status(S_IDLE, output->is_idle()); - queues_update(status); + queues_update(status, now); bool active = queues_length_displayed() > 0; @@ -91,14 +93,13 @@ } if (active) { - gint64 now = time_monotonic_now(); gint64 sleep = queues_get_next_datachange(now); gint64 timeout_at = now + sleep; LOG_D("Sleeping for %li ms", sleep/1000); if (sleep >= 0) { - if (next_timeout < now || timeout_at < next_timeout) { + if (reason == 0 || next_timeout < now || timeout_at < next_timeout) { g_timeout_add(sleep/1000, run, NULL); next_timeout = timeout_at; } diff -Nru dunst-1.8.1/src/icon.c dunst-1.9.0/src/icon.c --- dunst-1.8.1/src/icon.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/icon.c 2022-06-27 08:43:39.000000000 -0400 @@ -122,21 +122,23 @@ * * @param w a pointer to the image width, to be modified in-place * @param h a pointer to the image height, to be modified in-place + * @param min_size the minimum icon size setting for this notification + * @param max_size the maximum icon size setting for this notification * @return TRUE if the dimensions were updated, FALSE if they were left unchanged */ -static bool icon_size_clamp(int *w, int *h) { +static bool icon_size_clamp(int *w, int *h, int min_size, int max_size) { int _w = *w, _h = *h; int landscape = _w > _h; int orig_larger = landscape ? _w : _h; double larger = orig_larger; double smaller = landscape ? _h : _w; - if (settings.min_icon_size && smaller < settings.min_icon_size) { - larger = larger / smaller * settings.min_icon_size; - smaller = settings.min_icon_size; - } - if (settings.max_icon_size && larger > settings.max_icon_size) { - smaller = smaller / larger * settings.max_icon_size; - larger = settings.max_icon_size; + if (min_size && smaller < min_size) { + larger = larger / smaller * min_size; + smaller = min_size; + } + if (max_size && larger > max_size) { + smaller = smaller / larger * max_size; + larger = max_size; } if ((int) larger != orig_larger) { *w = (int) (landscape ? larger : smaller); @@ -146,67 +148,42 @@ return FALSE; } -static bool icon_size_clamp2(int *w, int *h, int desired_size, double scale) { - int largest_size = MAX(*w, *h); - int new_size = desired_size * scale; - if (largest_size != new_size) { - double scale = (double) new_size / (double) largest_size; - *w = round(*w * scale); - *h = round(*h * scale); - return true; - } - return false; -} /** * Scales the given GdkPixbuf to a given size.. If the image is not square, the * largest size will be scaled up to the given size. * - * The icon is scaled to a size of icon_size * dpi_scale. - * * @param pixbuf (nullable) The pixbuf, which may be too big. * Takes ownership of the reference. - * @param icon_size An integer the unscaled icon size. * @param dpi_scale A double for the dpi scaling. + * @param min_size The minimum allowed icon size. + * @param max_size The maximum allowed icon size. * @return the scaled version of the pixbuf. If scaling wasn't * necessary, it returns the same pixbuf. Transfers full * ownership of the reference. */ -static GdkPixbuf *icon_pixbuf_scale_to_size(GdkPixbuf *pixbuf, int icon_size, double dpi_scale) +static GdkPixbuf *icon_pixbuf_scale_to_size(GdkPixbuf *pixbuf, double dpi_scale, int min_size, int max_size) { ASSERT_OR_RET(pixbuf, NULL); int w = gdk_pixbuf_get_width(pixbuf); int h = gdk_pixbuf_get_height(pixbuf); - bool needs_change = false; - if (settings.enable_recursive_icon_lookup) - { - needs_change = icon_size_clamp2(&w, &h, icon_size, dpi_scale); - if (needs_change) { - LOG_D("Scaling to a size of %ix%i", w, h); - LOG_D("While the icon size and scale are %ix%f", icon_size, dpi_scale); - } - } else { - // TODO immediately rescale icon upon scale changes - if(icon_size_clamp(&w, &h)) { - w = round(w * dpi_scale); - h = round(h * dpi_scale); - needs_change = true; - } - } - if (needs_change) { - GdkPixbuf *scaled = gdk_pixbuf_scale_simple( - pixbuf, - w, - h, - GDK_INTERP_BILINEAR); - g_object_unref(pixbuf); - pixbuf = scaled; - } + // TODO immediately rescale icon upon scale changes + if(icon_size_clamp(&w, &h, min_size, max_size)) { + w = round(w * dpi_scale); + h = round(h * dpi_scale); + } + GdkPixbuf *scaled = gdk_pixbuf_scale_simple( + pixbuf, + w, + h, + GDK_INTERP_BILINEAR); + g_object_unref(pixbuf); + pixbuf = scaled; return pixbuf; } -GdkPixbuf *get_pixbuf_from_file(const char *filename, int icon_size, double scale) +GdkPixbuf *get_pixbuf_from_file(const char *filename, int min_size, int max_size, double scale) { char *path = string_to_path(g_strdup(filename)); GError *error = NULL; @@ -218,23 +195,13 @@ return NULL; } GdkPixbuf *pixbuf = NULL; - if (settings.enable_recursive_icon_lookup) - { - icon_size_clamp2(&w, &h, icon_size, scale); - pixbuf = gdk_pixbuf_new_from_file_at_scale(path, - w, - h, - TRUE, - &error); - } else { - // TODO immediately rescale icon upon scale changes - icon_size_clamp(&w, &h); - pixbuf = gdk_pixbuf_new_from_file_at_scale(path, - round(w * scale), - round(h * scale), - TRUE, - &error); - } + // TODO immediately rescale icon upon scale changes + icon_size_clamp(&w, &h, min_size, max_size); + pixbuf = gdk_pixbuf_new_from_file_at_scale(path, + round(w * scale), + round(h * scale), + TRUE, + &error); if (error) { LOG_W("%s", error->message); @@ -305,7 +272,7 @@ return new_name; } -GdkPixbuf *icon_get_for_data(GVariant *data, char **id, double dpi_scale, int icon_size) +GdkPixbuf *icon_get_for_data(GVariant *data, char **id, double dpi_scale, int min_size, int max_size) { ASSERT_OR_RET(data, NULL); ASSERT_OR_RET(id, NULL); @@ -415,7 +382,7 @@ g_free(data_chk); g_variant_unref(data_variant); - pixbuf = icon_pixbuf_scale_to_size(pixbuf, icon_size, dpi_scale); + pixbuf = icon_pixbuf_scale_to_size(pixbuf, dpi_scale, min_size, max_size); return pixbuf; } diff -Nru dunst-1.8.1/src/icon.h dunst-1.9.0/src/icon.h --- dunst-1.8.1/src/icon.h 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/icon.h 2022-06-27 08:43:39.000000000 -0400 @@ -11,13 +11,14 @@ /** Retrieve an icon by its full filepath, scaled according to settings. * * @param filename A string representing a readable file path - * @param icon_size An iteger representing the desired unscaled icon size. + * @param min_size An iteger representing the desired minimum unscaled icon size. + * @param max_size An iteger representing the desired maximum unscaled icon size. * @param scale An integer representing the output dpi scaling. * * @return an instance of `GdkPixbuf` * @retval NULL: file does not exist, not readable, etc.. */ -GdkPixbuf *get_pixbuf_from_file(const char *filename, int icon_size, double scale); +GdkPixbuf *get_pixbuf_from_file(const char *filename, int min_size, int max_size, double scale); /** @@ -55,12 +56,13 @@ * like described in the notification spec. * @param id (necessary) A unique identifier of the returned pixbuf. * Only filled, if the return value is non-NULL. - * @param scale An integer representing the output dpi scaling. - * @param icon_size An integer representing the desired unscaled icon size. + * @param dpi_scale An integer representing the output dpi scaling. + * @param min_size An integer representing the desired minimum unscaled icon size. + * @param max_size An integer representing the desired maximum unscaled icon size. * @return an instance of `GdkPixbuf` derived from the GVariant * @retval NULL: GVariant parameter nulled, invalid or in wrong format */ -GdkPixbuf *icon_get_for_data(GVariant *data, char **id, double scale, int icon_size); +GdkPixbuf *icon_get_for_data(GVariant *data, char **id, double dpi_scale, int min_size, int max_size); #endif /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.8.1/src/input.c dunst-1.9.0/src/input.c --- dunst-1.8.1/src/input.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/input.c 2022-06-27 08:43:39.000000000 -0400 @@ -6,16 +6,43 @@ #include <stddef.h> #include <linux/input-event-codes.h> +int get_notification_clickable_height(struct notification *n, bool first, bool last) +{ + int notification_size = n->displayed_height; + if (settings.gap_size) { + notification_size += settings.frame_width * 2; + } else { + double half_separator = settings.separator_height / 2.0; + notification_size += settings.separator_height; + if(first) + notification_size += (settings.frame_width - half_separator); + if(last) + notification_size += (settings.frame_width - half_separator); + } + return notification_size; +} + struct notification *get_notification_at(const int y) { - int curr_y = settings.frame_width; + int curr_y = 0; + bool first = true; + bool last; for (const GList *iter = queues_get_displayed(); iter; iter = iter->next) { struct notification *current = iter->data; - if (y > curr_y && y < curr_y + current->displayed_height) { + struct notification *next = iter->next ? iter->next->data : NULL; + + last = !next; + int notification_size = get_notification_clickable_height(current, first, last); + + if (y >= curr_y && y < curr_y + notification_size) { return current; } - curr_y += current->displayed_height + settings.separator_height; + curr_y += notification_size; + if (settings.gap_size) + curr_y += settings.gap_size; + + first = false; } // no matching notification was found return NULL; diff -Nru dunst-1.8.1/src/notification.c dunst-1.9.0/src/notification.c --- dunst-1.8.1/src/notification.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/notification.c 2022-06-27 08:43:39.000000000 -0400 @@ -337,10 +337,11 @@ g_clear_pointer(&n->icon_id, g_free); g_free(n->icon_path); - n->icon_path = get_path_from_icon_name(new_icon, n->icon_size); + n->icon_path = get_path_from_icon_name(new_icon, n->min_icon_size); if (n->icon_path) { GdkPixbuf *pixbuf = get_pixbuf_from_file(n->icon_path, - n->icon_size, draw_get_scale()); + n->min_icon_size, n->max_icon_size, + draw_get_scale()); if (pixbuf) { n->icon = gdk_pixbuf_to_cairo_surface(pixbuf); g_object_unref(pixbuf); @@ -360,7 +361,7 @@ g_clear_pointer(&n->icon_id, g_free); GdkPixbuf *icon = icon_get_for_data(new_icon, &n->icon_id, - draw_get_scale(), n->icon_size); + draw_get_scale(), n->min_icon_size, n->max_icon_size); n->icon = gdk_pixbuf_to_cairo_surface(icon); if (icon) g_object_unref(icon); @@ -414,6 +415,7 @@ n->urgency = URG_NORM; n->timeout = -1; + n->dbus_timeout = -1; n->transient = false; n->progress = -1; @@ -423,7 +425,9 @@ n->progress_bar_alignment = PANGO_ALIGN_CENTER; n->hide_text = false; n->icon_position = ICON_LEFT; - n->icon_size = 32; + n->min_icon_size = 32; + n->max_icon_size = 32; + n->receiving_raw_icon = false; n->script_run = false; n->dbus_valid = false; @@ -506,6 +510,11 @@ /* UPDATE derived fields */ notification_extract_urls(n); notification_format_message(n); + + /* Update timeout: dbus_timeout has priority over timeout */ + if (n->dbus_timeout >= 0) + n->timeout = n->dbus_timeout; + } static void notification_format_message(struct notification *n) diff -Nru dunst-1.8.1/src/notification.h dunst-1.9.0/src/notification.h --- dunst-1.8.1/src/notification.h 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/notification.h 2022-06-27 08:43:39.000000000 -0400 @@ -64,12 +64,15 @@ Use this to compare the icon name with rules. May also be modified by rules.*/ char *icon_path; /**< Full path to the notification's icon. */ char *default_icon_name; /**< The icon that is used when no other icon is available. */ - int icon_size; /**< Size of the icon used for searching the right icon. */ + int min_icon_size; /**< Minimum icon size. Also used for looking up icon names. */ + int max_icon_size; /**< Maximum icon size. */ enum icon_position icon_position; /**< Icon position (enum left,right,top,off). */ + bool receiving_raw_icon; /**< Still waiting for raw icon to be received */ gint64 start; /**< begin of current display (in milliseconds) */ gint64 timestamp; /**< arrival time (in milliseconds) */ gint64 timeout; /**< time to display (in milliseconds) */ + gint64 dbus_timeout; /**< time to display (in milliseconds) (set by dbus) */ int locked; /**< If non-zero the notification is locked **/ PangoAlignment progress_bar_alignment; /**< Horizontal alignment of the progress bar **/ diff -Nru dunst-1.8.1/src/queues.c dunst-1.9.0/src/queues.c --- dunst-1.8.1/src/queues.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/queues.c 2022-06-27 08:43:39.000000000 -0400 @@ -134,10 +134,11 @@ * * @param n the notification to check * @param status the current status of dunst + * @param time the current time * @retval true: the notification is timed out * @retval false: otherwise */ -static bool queues_notification_is_finished(struct notification *n, struct dunst_status status) +static bool queues_notification_is_finished(struct notification *n, struct dunst_status status, gint64 time) { assert(n); @@ -156,7 +157,7 @@ } /* remove old message */ - if (time_monotonic_now() - n->start > n->timeout) { + if (time - n->start > n->timeout) { return true; } @@ -408,7 +409,7 @@ } /* see queues.h */ -void queues_update(struct dunst_status status) +void queues_update(struct dunst_status status, gint64 time) { GList *iter, *nextiter; @@ -433,7 +434,7 @@ } - if (queues_notification_is_finished(n, status)){ + if (queues_notification_is_finished(n, status, time)){ queues_notification_close(n, REASON_TIME); iter = nextiter; continue; diff -Nru dunst-1.8.1/src/queues.h dunst-1.9.0/src/queues.h --- dunst-1.8.1/src/queues.h 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/queues.h 2022-06-27 08:43:39.000000000 -0400 @@ -141,8 +141,9 @@ * (which closes old and shows new notifications on screen) * * @param status the current status of dunst + * @param time the current time */ -void queues_update(struct dunst_status status); +void queues_update(struct dunst_status status, gint64 time); /** * Calculate the distance to the next event, when an element in the diff -Nru dunst-1.8.1/src/rules.c dunst-1.9.0/src/rules.c --- dunst-1.8.1/src/rules.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/rules.c 2022-06-27 08:43:39.000000000 -0400 @@ -21,6 +21,8 @@ { 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) @@ -41,6 +43,10 @@ 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 (r->action_name) { g_free(n->default_action_name); n->default_action_name = g_strdup(r->action_name); @@ -53,8 +59,6 @@ n->markup = r->markup; if (r->icon_position != -1) n->icon_position = r->icon_position; - if (r->set_icon_size > 0) - n->icon_size = r->set_icon_size; if (r->fg) { g_free(n->colors.fg); n->colors.fg = g_strdup(r->fg); @@ -83,6 +87,7 @@ // separate variable is needed to track if the icon is // replaced, like in 86cbc1d34bb0f551461dbd466cd9e4860ae01817. notification_icon_replace_path(n, r->new_icon); + n->receiving_raw_icon = false; } if (r->script){ n->scripts = g_renew(const char*,n->scripts,n->script_count + 1); @@ -190,6 +195,7 @@ { return r->enabled && (r->msg_urgency == URG_NONE || r->msg_urgency == n->urgency) + && (r->match_dbus_timeout < 0 || (r->match_dbus_timeout == n->dbus_timeout)) && (r->match_transient == -1 || (r->match_transient == n->transient)) && rule_field_matches_string(n->appname, r->appname) && rule_field_matches_string(n->desktop_entry, r->desktop_entry) diff -Nru dunst-1.8.1/src/rules.h dunst-1.9.0/src/rules.h --- dunst-1.8.1/src/rules.h 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/rules.h 2022-06-27 08:43:39.000000000 -0400 @@ -26,9 +26,11 @@ char *stack_tag; char *desktop_entry; int msg_urgency; + gint64 match_dbus_timeout; /* modifying */ gint64 timeout; // this has to be the first modifying rule + gint64 override_dbus_timeout; enum urgency urgency; char *action_name; enum markup_mode markup; @@ -41,7 +43,8 @@ int alignment; int hide_text; int icon_position; - int set_icon_size; + int min_icon_size; + int max_icon_size; char *new_icon; char *fg; char *bg; diff -Nru dunst-1.8.1/src/settings.c dunst-1.9.0/src/settings.c --- dunst-1.8.1/src/settings.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/settings.c 2022-06-27 08:43:39.000000000 -0400 @@ -203,29 +203,31 @@ } } + // TODO Implement this with icon sizes as rules + // restrict the icon size to a reasonable limit if we have a fixed width. // Otherwise the layout will be broken by too large icons. // See https://github.com/dunst-project/dunst/issues/540 - if (s->width.max > 0) { - const int icon_size_limit = s->width.max / 2; - if ( s->max_icon_size == 0 - || s->max_icon_size > icon_size_limit) { - if (s->max_icon_size != 0) { - LOG_W("Max width was set to %d but got a max_icon_size of %d, too large to use. Setting max_icon_size=%d", - s->width.max, s->max_icon_size, icon_size_limit); - } else { - LOG_I("Max width was set but max_icon_size is unlimited. Limiting icons to %d pixels", icon_size_limit); - } + // if (s->width.max > 0) { + // const int icon_size_limit = s->width.max / 2; + // if ( s->max_icon_size == 0 + // || s->max_icon_size > icon_size_limit) { + // if (s->max_icon_size != 0) { + // LOG_W("Max width was set to %d but got a max_icon_size of %d, too large to use. Setting max_icon_size=%d", + // s->width.max, s->max_icon_size, icon_size_limit); + // } else { + // LOG_I("Max width was set but max_icon_size is unlimited. Limiting icons to %d pixels", icon_size_limit); + // } - s->max_icon_size = icon_size_limit; - } - } + // s->max_icon_size = icon_size_limit; + // } + // } - int text_icon_padding = settings.text_icon_padding != 0 ? settings.text_icon_padding : settings.h_padding; - int max_text_width = settings.width.max - settings.max_icon_size - text_icon_padding - 2 * settings.h_padding; - if (max_text_width < 10) { - DIE("max_icon_size and horizontal padding are too large for the given width"); - } + // int text_icon_padding = settings.text_icon_padding != 0 ? settings.text_icon_padding : settings.h_padding; + // int max_text_width = settings.width.max - settings.max_icon_size - text_icon_padding - 2 * settings.h_padding; + // if (max_text_width < 10) { + // DIE("max_icon_size and horizontal padding are too large for the given width"); + // } } diff -Nru dunst-1.8.1/src/settings_data.h dunst-1.9.0/src/settings_data.h --- dunst-1.8.1/src/settings_data.h 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/settings_data.h 2022-06-27 08:43:39.000000000 -0400 @@ -96,6 +96,16 @@ * .rule_offset = offsetof(struct rule, *member*); */ size_t rule_offset; + + /** + * True if a setting has a different default in the default dunstrc. + * This is useful to transition a default value without breaking exisitng + * configs. This value is needed for the test suite to skip testing this + * setting against the default dunstrc. + * + * False by default. + */ + bool different_default; }; @@ -125,19 +135,21 @@ static const struct rule empty_rule = { .name = "empty", .appname = NULL, + .action_name = NULL, .summary = NULL, .body = NULL, .icon = NULL, .category = NULL, .msg_urgency = URG_NONE, + .match_dbus_timeout = -1, .timeout = -1, + .override_dbus_timeout = -1, .urgency = URG_NONE, .markup = MARKUP_NULL, .history_ignore = -1, .match_transient = -1, .set_transient = -1, .icon_position = -1, - .set_icon_size = -1, .skip_display = -1, .word_wrap = -1, .ellipsize = -1, @@ -151,6 +163,8 @@ .script = NULL, .enabled = true, .progress_bar_alignment = -1, + .min_icon_size = -1, + .max_icon_size = -1, }; @@ -415,6 +429,17 @@ .rule_offset = offsetof(struct rule, msg_urgency), }, { + .name = "match_dbus_timeout", + .section = "*", + .description = "Matches the dbus_timeout of the notification", + .type = TYPE_TIME, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, match_dbus_timeout), + }, + { .name = "stack_tag", .section = "*", .description = "Matches the stack tag of the notification as set by the client or by some other rule.", @@ -461,6 +486,17 @@ .rule_offset = offsetof(struct rule, bg), }, { + .name = "action_name", + .section = "*", + .description = "Sets the name of the action to be invoked on do_action.", + .type = TYPE_STRING, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, action_name), + }, + { .name = "foreground", .section = "*", .description = "The foreground color of the notification.", @@ -571,6 +607,17 @@ .rule_offset = offsetof(struct rule, timeout), }, { + .name = "override_dbus_timeout", + .section = "*", + .description = "Replace the dbus timeout with this value.", + .type = TYPE_TIME, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, override_dbus_timeout), + }, + { .name = "urgency", .section = "*", .description = "This sets the notification urgency.", @@ -670,17 +717,6 @@ .rule_offset = offsetof(struct rule, icon_position), }, { - .name = "icon_size", - .section = "*", - .description = "Set the size of the icon", - .type = TYPE_INT, - .default_value = "*", - .value = NULL, - .parser = NULL, - .parser_data = NULL, - .rule_offset = offsetof(struct rule, set_icon_size), - }, - { .name = "enabled", .section = "*", .description = "Enable or disable a rule", @@ -702,6 +738,28 @@ .parser_data = horizontal_alignment_enum_data, .rule_offset = offsetof(struct rule, progress_bar_alignment), }, + { + .name = "min_icon_size", + .section = "global", + .description = "Scale smaller icons up to this size, set to 0 to disable. If max_icon_size also specified, that has the final say.", + .type = TYPE_INT, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, min_icon_size), + }, + { + .name = "max_icon_size", + .section = "global", + .description = "Scale larger icons down to this size, set to 0 to disable", + .type = TYPE_INT, + .default_value = "*", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, max_icon_size), + }, // end of modifying rules // other settings below @@ -1047,26 +1105,6 @@ .parser_data = &settings.browser_cmd, }, { - .name = "min_icon_size", - .section = "global", - .description = "Scale smaller icons up to this size, set to 0 to disable. If max_icon_size also specified, that has the final say.", - .type = TYPE_INT, - .default_value = "0", - .value = &settings.min_icon_size, - .parser = NULL, - .parser_data = NULL, - }, - { - .name = "max_icon_size", - .section = "global", - .description = "Scale larger icons down to this size, set to 0 to disable", - .type = TYPE_INT, - .default_value = "32", - .value = &settings.max_icon_size, - .parser = NULL, - .parser_data = NULL, - }, - { .name = "always_run_script", .section = "global", .description = "Always run rule-defined scripts, even if the notification is suppressed with format = \"\".", @@ -1186,6 +1224,7 @@ .value = &settings.enable_recursive_icon_lookup, .parser = string_parse_bool, .parser_data = boolean_enum_data, + .different_default = true, }, { .name = "enable_posix_regex", @@ -1453,6 +1492,16 @@ .parser = NULL, .parser_data = NULL, }, + { + .name = "gap_size", + .section = "global", + .description = "Size of gap between notifications", + .type = TYPE_INT, + .default_value = "0", + .value = &settings.gap_size, + .parser = NULL, + .parser_data = NULL, + }, }; #endif /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.8.1/src/settings.h dunst-1.9.0/src/settings.h --- dunst-1.8.1/src/settings.h 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/settings.h 2022-06-27 08:43:39.000000000 -0400 @@ -127,8 +127,6 @@ char *browser; char **browser_cmd; enum vertical_alignment vertical_alignment; - int min_icon_size; - int max_icon_size; char **icon_theme; // experimental bool enable_recursive_icon_lookup; // experimental bool enable_regex; // experimental @@ -156,6 +154,7 @@ int height; struct position offset; int notification_limit; + int gap_size; }; extern struct settings settings; diff -Nru dunst-1.8.1/src/wayland/wl.c dunst-1.9.0/src/wayland/wl.c --- dunst-1.8.1/src/wayland/wl.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/wayland/wl.c 2022-06-27 08:43:39.000000000 -0400 @@ -135,17 +135,27 @@ LOG_E("allocation failed"); return; } + + bool recreate_surface = false; static int number = 0; LOG_I("New output found - id %i", number); output->global_name = global_name; output->wl_output = wl_output; output->scale = 1; output->fullscreen = false; + + recreate_surface = wl_list_empty(&ctx.outputs); + wl_list_insert(&ctx.outputs, &output->link); wl_output_set_user_data(wl_output, output); wl_output_add_listener(wl_output, &output_listener, output); number++; + + if (recreate_surface) { + // We had no outputs, force our surface to redraw + set_dirty(ctx.surface); + } } static void destroy_output(struct dunst_output *output) { @@ -280,13 +290,19 @@ static void layer_surface_handle_configure(void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) { + zwlr_layer_surface_v1_ack_configure(surface, serial); + + if (ctx.configured && + ctx.width == (int32_t) width && + ctx.height == (int32_t) height) { + wl_surface_commit(ctx.surface); + return; + } + ctx.configured = true; ctx.width = width; ctx.height = height; - // not needed as it is set somewhere else - /* zwlr_layer_surface_v1_set_size(surface, width, height); */ - zwlr_layer_surface_v1_ack_configure(surface, serial); send_frame(); } @@ -589,6 +605,11 @@ static void send_frame() { int scale = wl_get_scale(); + if (wl_list_empty(&ctx.outputs)) { + ctx.dirty = false; + return; + } + struct dunst_output *output = get_configured_output(); int height = ctx.cur_dim.h; int width = ctx.cur_dim.w; diff -Nru dunst-1.8.1/src/x11/screen.c dunst-1.9.0/src/x11/screen.c --- dunst-1.8.1/src/x11/screen.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/src/x11/screen.c 2022-06-27 08:43:39.000000000 -0400 @@ -389,12 +389,12 @@ */ static Window get_focused_window(void) { - Window focused; + Window focused, root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); int ignored; XGetInputFocus(xctx.dpy, &focused, &ignored); - if (focused == None || focused == PointerRoot) + if (focused == None || focused == PointerRoot || focused == root) focused = 0; return focused; } diff -Nru dunst-1.8.1/test/data/dunstrc.default dunst-1.9.0/test/data/dunstrc.default --- dunst-1.8.1/test/data/dunstrc.default 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/data/dunstrc.default 2022-06-27 08:43:39.000000000 -0400 @@ -73,6 +73,7 @@ # Draw a line of "separator_height" pixel height between two # notifications. # Set to 0 to disable. + # If gap_size is greater than 0, this setting will be ignored. separator_height = 2 # Padding between text and separator. @@ -91,6 +92,12 @@ # Defines color of the frame around the notification window. frame_color = "#aaaaaa" + # Size of gap to display between notifications - requires a compositor. + # If value is greater than 0, separator_height will be ignored and a border + # of size frame_width will be drawn around each notification instead. + # Click events on gaps do not currently propagate to applications below. + gap_size = 0 + # Define a color for the separator. # possible values are: # * auto: dunst tries to find a color fitting to the background; @@ -184,18 +191,27 @@ ### Icons ### + # Recursive icon lookup. You can set a single theme, instead of having to + # define all lookup paths. + enable_recursive_icon_lookup = true + + # Set icon theme (only used for recursive icon lookup) + icon_theme = Adwaita + # You can also set multiple icon themes, with the leftmost one being used first. + # icon_theme = "Adwaita, breeze" + # Align icons left/right/top/off icon_position = left # Scale small icons up to this size, set to 0 to disable. Helpful # for e.g. small files or high-dpi screens. In case of conflict, # max_icon_size takes precedence over this. - min_icon_size = 0 + min_icon_size = 32 # Scale larger icons down to this size, set to 0 to disable - max_icon_size = 32 + max_icon_size = 128 - # Paths to default icons. + # Paths to default icons (only neccesary when not using recursive icon lookup) icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/ ### History ### diff -Nru dunst-1.8.1/test/dbus.c dunst-1.9.0/test/dbus.c --- dunst-1.8.1/test/dbus.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/dbus.c 2022-06-27 08:43:39.000000000 -0400 @@ -815,6 +815,91 @@ PASS(); } +TEST test_override_dbus_timeout(void) +{ + struct notification *n; + struct dbus_notification *n_dbus; + struct rule *rule; + + n_dbus = dbus_notification_new(); + n_dbus->app_name = "dunstteststack"; + n_dbus->expire_timeout = 2147484; + + rule = rule_new("test_override_dbus_timeout"); + rule->appname = "dunstteststack"; + rule->override_dbus_timeout = 100000; + + gint64 expected_timeout = rule->override_dbus_timeout; + + guint id; + ASSERT(dbus_notification_fire(n_dbus, &id)); + ASSERT(id != 0); + + n = queues_debug_find_notification_by_id(id); + ASSERT_EQ_FMT(expected_timeout, n->timeout, "%" G_GINT64_FORMAT); + + dbus_notification_free(n_dbus); + rule->enabled = false; + + PASS(); +} + +TEST test_match_dbus_timeout(void) +{ + struct notification *n; + struct dbus_notification *n_dbus; + struct rule *rule; + + n_dbus = dbus_notification_new(); + n_dbus->app_name = "dunstteststack"; + n_dbus->expire_timeout = 2147484; + + rule = rule_new("test_match_dbus_timeout"); + rule->match_dbus_timeout = 2147484000; + rule->override_dbus_timeout = 100000; + + gint64 expected_timeout = rule->override_dbus_timeout; + + guint id; + ASSERT(dbus_notification_fire(n_dbus, &id)); + ASSERT(id != 0); + + n = queues_debug_find_notification_by_id(id); + ASSERT_EQ_FMT(expected_timeout, n->timeout, "%" G_GINT64_FORMAT); + + dbus_notification_free(n_dbus); + rule->enabled = false; + + PASS(); +} + +TEST test_timeout(void) +{ + struct notification *n; + struct dbus_notification *n_dbus; + struct rule *rule; + + n_dbus = dbus_notification_new(); + n_dbus->app_name = "dunstteststack"; + + rule = rule_new("test_timeout"); + rule->appname = "dunstteststack"; + rule->timeout = 100001; + + gint64 expected_timeout = rule->timeout; + + guint id; + ASSERT(dbus_notification_fire(n_dbus, &id)); + ASSERT(id != 0); + + n = queues_debug_find_notification_by_id(id); + ASSERT_EQ_FMT(expected_timeout, n->timeout, "%" G_GINT64_FORMAT); + + dbus_notification_free(n_dbus); + rule->enabled = false; + + PASS(); +} // TESTS END @@ -844,6 +929,9 @@ RUN_TEST(test_close_and_signal); RUN_TEST(test_signal_actioninvoked); RUN_TEST(test_timeout_overflow); + RUN_TEST(test_override_dbus_timeout); + RUN_TEST(test_match_dbus_timeout); + RUN_TEST(test_timeout); RUN_TEST(assert_methodlists_sorted); diff -Nru dunst-1.8.1/test/draw.c dunst-1.9.0/test/draw.c --- dunst-1.8.1/test/draw.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/draw.c 2022-06-27 08:43:39.000000000 -0400 @@ -5,6 +5,8 @@ cairo_t *c; +double get_dummy_scale() { return 1; } + const struct screen_info* noop_screen(void) { static struct screen_info i; return &i; @@ -28,9 +30,64 @@ x_is_idle, have_fullscreen_window, - x_get_scale, + get_dummy_scale, }; +GSList *get_dummy_layouts(GSList *notifications) +{ + GSList *layouts = NULL; + + for (GSList *iter = notifications; iter; iter = iter->next) { + struct colored_layout *cl = layout_from_notification(c, iter->data); + layouts = g_slist_append(layouts, cl); + + } + return layouts; +} + +int get_small_max_height() +{ + // to keep test calculations simpler, set max height small to + // only test cases where height is not dynamically determined + // by notification content + // future tests targeting dynamic sizing logic could be added + // to address this limitation + int small_max_height = 10; + return small_max_height; +} + +int get_expected_dimension_height(int layout_count) +{ + // assumes settings.height == notification height, see get_small_max_height + int separator_height = (layout_count - 1) * settings.separator_height; + int total_gap_size = (layout_count - 1) * settings.gap_size; + int height = settings.height * layout_count; + int frame_width_total_height; + int expected_height; + if(settings.gap_size) { + frame_width_total_height = layout_count * (2 * settings.frame_width); + expected_height = height + frame_width_total_height + total_gap_size; + } else { + frame_width_total_height = 2 * settings.frame_width; + expected_height = separator_height + height + frame_width_total_height; + } + return expected_height; +} + +int get_expected_dimension_y_offset(int layout_count) +{ + // assumes settings.height == notification height, see get_small_max_height + int expected_y = layout_count * settings.height; + if(settings.gap_size) { + expected_y += (layout_count * (2 * settings.frame_width)); + expected_y += (layout_count * settings.gap_size); + } else { + expected_y += (2 * settings.frame_width); + expected_y += (layout_count * settings.separator_height); + } + return expected_y; +} + TEST test_layout_from_notification(void) { struct notification *n = test_notification_with_icon("test", 10); @@ -71,6 +128,179 @@ PASS(); } +TEST test_calculate_dimensions_height_no_gaps(void) +{ + int original_height = settings.height; + bool orginal_gap_size = settings.gap_size; + settings.height = get_small_max_height(); + settings.gap_size = 10; + + int layout_count; + GSList *notifications; + GSList *layouts; + struct dimensions dim; + int expected_height; + + layout_count = 1; + notifications = get_dummy_notifications(layout_count); + layouts = get_dummy_layouts(notifications); + dim = calculate_dimensions(layouts); + expected_height = get_expected_dimension_height(layout_count); + ASSERT(dim.h == expected_height); + g_slist_free_full(layouts, free_colored_layout); + g_slist_free_full(notifications, free_dummy_notification); + + layout_count = 2; + notifications = get_dummy_notifications(layout_count); + layouts = get_dummy_layouts(notifications); + dim = calculate_dimensions(layouts); + expected_height = get_expected_dimension_height(layout_count); + ASSERT(dim.h == expected_height); + g_slist_free_full(layouts, free_colored_layout); + g_slist_free_full(notifications, free_dummy_notification); + + layout_count = 3; + notifications = get_dummy_notifications(layout_count); + layouts = get_dummy_layouts(notifications); + dim = calculate_dimensions(layouts); + expected_height = get_expected_dimension_height(layout_count); + ASSERT(dim.h == expected_height); + g_slist_free_full(layouts, free_colored_layout); + g_slist_free_full(notifications, free_dummy_notification); + + settings.gap_size = orginal_gap_size; + settings.height = original_height; + + PASS(); +} + +TEST test_calculate_dimensions_height_gaps(void) +{ + int original_height = settings.height; + bool orginal_gap_size = settings.gap_size; + settings.height = get_small_max_height(); + settings.gap_size = 10; + + int layout_count; + GSList *notifications; + GSList *layouts; + struct dimensions dim; + int expected_height; + + layout_count = 1; + notifications = get_dummy_notifications(layout_count); + layouts = get_dummy_layouts(notifications); + dim = calculate_dimensions(layouts); + expected_height = get_expected_dimension_height(layout_count); + ASSERT(dim.h == expected_height); + g_slist_free_full(layouts, free_colored_layout); + g_slist_free_full(notifications, free_dummy_notification); + + layout_count = 2; + notifications = get_dummy_notifications(layout_count); + layouts = get_dummy_layouts(notifications); + dim = calculate_dimensions(layouts); + expected_height = get_expected_dimension_height(layout_count); + ASSERT(dim.h == expected_height); + g_slist_free_full(layouts, free_colored_layout); + g_slist_free_full(notifications, free_dummy_notification); + + layout_count = 3; + notifications = get_dummy_notifications(layout_count); + layouts = get_dummy_layouts(notifications); + dim = calculate_dimensions(layouts); + expected_height = get_expected_dimension_height(layout_count); + ASSERT(dim.h == expected_height); + + g_slist_free_full(layouts, free_colored_layout); + g_slist_free_full(notifications, free_dummy_notification); + settings.gap_size = orginal_gap_size; + settings.height = original_height; + + PASS(); +} + +TEST test_layout_render_no_gaps(void) +{ + int original_height = settings.height; + bool orginal_gap_size = settings.gap_size; + settings.height = get_small_max_height(); + settings.gap_size = 0; + + int layout_count; + GSList *notifications; + GSList *layouts; + struct dimensions dim; + cairo_surface_t *image_surface; + int expected_y; + + layout_count = 3; + notifications = get_dummy_notifications(layout_count); + layouts = get_dummy_layouts(notifications); + dim = calculate_dimensions(layouts); + image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + + bool first = true; + for (GSList *iter = layouts; iter; iter = iter->next) { + struct colored_layout *cl_this = iter->data; + struct colored_layout *cl_next = iter->next ? iter->next->data : NULL; + + dim = layout_render(image_surface, cl_this, cl_next, dim, first, !cl_next); + + first = false; + } + + expected_y = get_expected_dimension_y_offset(layout_count); + ASSERT(dim.y == expected_y); + + g_slist_free_full(layouts, free_colored_layout); + g_slist_free_full(notifications, free_dummy_notification); + cairo_surface_destroy(image_surface); + settings.gap_size = orginal_gap_size; + settings.height = original_height; + + PASS(); +} + +TEST test_layout_render_gaps(void) +{ + int original_height = settings.height; + bool orginal_gap_size = settings.gap_size; + settings.height = get_small_max_height(); + settings.gap_size = 10; + + int layout_count; + GSList *notifications; + GSList *layouts; + struct dimensions dim; + cairo_surface_t *image_surface; + int expected_y; + + layout_count = 3; + notifications = get_dummy_notifications(layout_count); + layouts = get_dummy_layouts(notifications); + dim = calculate_dimensions(layouts); + image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + + for (GSList *iter = layouts; iter; iter = iter->next) { + struct colored_layout *cl_this = iter->data; + struct colored_layout *cl_next = iter->next ? iter->next->data : NULL; + + dim = layout_render(image_surface, cl_this, cl_next, dim, true, true); + } + + expected_y = get_expected_dimension_y_offset(layout_count); + ASSERT(dim.y == expected_y); + + g_slist_free_full(layouts, free_colored_layout); + g_slist_free_full(notifications, free_dummy_notification); + cairo_surface_destroy(image_surface); + settings.gap_size = orginal_gap_size; + settings.height = original_height; + + PASS(); +} + SUITE(suite_draw) { output = &dummy_output; @@ -81,5 +311,9 @@ RUN_TEST(test_layout_from_notification); RUN_TEST(test_layout_from_notification_icon_off); RUN_TEST(test_layout_from_notification_no_icon); + RUN_TEST(test_calculate_dimensions_height_no_gaps); + RUN_TEST(test_calculate_dimensions_height_gaps); + RUN_TEST(test_layout_render_no_gaps); + RUN_TEST(test_layout_render_gaps); }); } diff -Nru dunst-1.8.1/test/functional-tests/dunstrc.gaps dunst-1.9.0/test/functional-tests/dunstrc.gaps --- dunst-1.8.1/test/functional-tests/dunstrc.gaps 1969-12-31 19:00:00.000000000 -0500 +++ dunst-1.9.0/test/functional-tests/dunstrc.gaps 2022-06-27 08:43:39.000000000 -0400 @@ -0,0 +1,22 @@ +[urgency_low] + background = "#222222" + foreground = "#888888" + timeout = 10 + +[urgency_normal] + background = "#285577" + foreground = "#ffffff" + timeout = 10 + +[urgency_critical] + background = "#900000" + foreground = "#ffffff" + timeout = 0 + +[global] + gap_size = 35 + frame_width = 15 + + mouse_left_click = do_action + mouse_middle_click = close_current + mouse_right_click = context diff -Nru dunst-1.8.1/test/functional-tests/dunstrc.separator_click dunst-1.9.0/test/functional-tests/dunstrc.separator_click --- dunst-1.8.1/test/functional-tests/dunstrc.separator_click 1969-12-31 19:00:00.000000000 -0500 +++ dunst-1.9.0/test/functional-tests/dunstrc.separator_click 2022-06-27 08:43:39.000000000 -0400 @@ -0,0 +1,23 @@ +[urgency_low] + background = "#222222" + foreground = "#888888" + timeout = 10 + +[urgency_normal] + background = "#285577" + foreground = "#ffffff" + timeout = 10 + +[urgency_critical] + background = "#900000" + foreground = "#ffffff" + timeout = 0 + +[global] + frame_width = 5 + separator_height = 50 + separator_color = "#000000" + + mouse_left_click = do_action + mouse_middle_click = close_current + mouse_right_click = context diff -Nru dunst-1.8.1/test/functional-tests/test.sh dunst-1.9.0/test/functional-tests/test.sh --- dunst-1.8.1/test/functional-tests/test.sh 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/functional-tests/test.sh 2022-06-27 08:43:39.000000000 -0400 @@ -243,6 +243,40 @@ keypress } +function gaps { + echo "###################################" + echo "gaps" + echo "###################################" + start_dunst dunstrc.gaps + CHOICE=$(../../dunstify -a "dunst tester" -A "default,Default" -A "optional,Optional" "Click #1" -u l) \ + && echo Clicked $CHOICE for \#1 & + CHOICE=$(../../dunstify -a "dunst tester" -A "default,Default" -A "optional,Optional" "Click #2" -u n) \ + && echo Clicked $CHOICE for \#2 & + CHOICE=$(../../dunstify -a "dunst tester" -A "default,Default" -A "optional,Optional" "Click #3" -u c) \ + && echo Clicked $CHOICE for \#3 & + CHOICE=$(../../dunstify -a "dunst tester" -A "default,Default" -A "optional,Optional" "Click #4" -u l) \ + && echo Clicked $CHOICE for \#4 & + CHOICE=$(../../dunstify -a "dunst tester" -A "default,Default" -A "optional,Optional" "Click #5" -u n) \ + && echo Clicked $CHOICE for \#5 & + CHOICE=$(../../dunstify -a "dunst tester" -A "default,Default" -A "optional,Optional" "Click #6" -u c) \ + && echo Clicked $CHOICE for \#6 & + keypress +} + +function separator_click { + echo "###################################" + echo "separator_click" + echo "###################################" + start_dunst dunstrc.separator_click + CHOICE=$(../../dunstify -a "dunst tester" -A "default,Default" -A "optional,Optional" "Click #1" -u l) \ + && echo Clicked $CHOICE for \#1 & + CHOICE=$(../../dunstify -a "dunst tester" -A "default,Default" -A "optional,Optional" "Click #2" -u c) \ + && echo Clicked $CHOICE for \#2 & + CHOICE=$(../../dunstify -a "dunst tester" -A "default,Default" -A "optional,Optional" "Click #3" -u n) \ + && echo Clicked $CHOICE for \#3 & + keypress +} + if [ -n "$1" ]; then while [ -n "$1" ]; do $1 @@ -261,6 +295,8 @@ progress_bar icon_position hide_text + gaps + separator_click fi killall dunst diff -Nru dunst-1.8.1/test/helpers.c dunst-1.9.0/test/helpers.c --- dunst-1.8.1/test/helpers.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/helpers.c 2022-06-27 08:43:39.000000000 -0400 @@ -67,4 +67,26 @@ return n; } +GSList *get_dummy_notifications(int count) +{ + GSList *notifications = NULL; + + int message_size = 24; + for (int i = 0; i < count; i++) { + char msg[message_size]; + snprintf(msg, message_size, "test %d", i); + struct notification *n = test_notification(msg, 10); + n->icon_position = ICON_LEFT; + n->text_to_render = g_strdup("dummy layout"); + notifications = g_slist_append(notifications, n); + } + return notifications; +} + +void free_dummy_notification(void *notification) +{ + // wrapper function to work with g_slist_free_full + notification_unref((struct notification *) notification); +} + /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.8.1/test/helpers.h dunst-1.9.0/test/helpers.h --- dunst-1.8.1/test/helpers.h 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/helpers.h 2022-06-27 08:43:39.000000000 -0400 @@ -7,6 +7,8 @@ struct notification *test_notification_uninitialized(const char *name); struct notification *test_notification(const char *name, gint64 timeout); struct notification *test_notification_with_icon(const char *name, gint64 timeout); +GSList *get_dummy_notifications(int count); +void free_dummy_notification(void *notification); #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.8.1/test/icon.c dunst-1.9.0/test/icon.c --- dunst-1.8.1/test/icon.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/icon.c 2022-06-27 08:43:39.000000000 -0400 @@ -27,7 +27,7 @@ { const char *iconpath = ICONPATH; - gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL); + gchar *path = g_build_filename(base, iconpath, "16x16", "actions", "edit.png", NULL); char *result = get_path_from_icon_name(path, size); ASSERT(result); @@ -38,10 +38,10 @@ PASS(); } -TEST test_icon_size_clamp_too_small(void) +TEST test_icon_size_clamp_too_small(int min_icon_size, int max_icon_size) { int w = 12, h = 24; - bool resized = icon_size_clamp(&w, &h); + bool resized = icon_size_clamp(&w, &h, min_icon_size, max_icon_size); ASSERT(resized); ASSERT_EQ(w, 16); ASSERT_EQ(h, 32); @@ -49,10 +49,10 @@ PASS(); } -TEST test_icon_size_clamp_not_necessary(void) +TEST test_icon_size_clamp_not_necessary(int min_icon_size, int max_icon_size) { int w = 20, h = 30; - bool resized = icon_size_clamp(&w, &h); + bool resized = icon_size_clamp(&w, &h, min_icon_size, max_icon_size); ASSERT(!resized); ASSERT_EQ(w, 20); ASSERT_EQ(h, 30); @@ -60,10 +60,10 @@ PASS(); } -TEST test_icon_size_clamp_too_big(void) +TEST test_icon_size_clamp_too_big(int min_icon_size, int max_icon_size) { int w = 75, h = 150; - bool resized = icon_size_clamp(&w, &h); + bool resized = icon_size_clamp(&w, &h, min_icon_size, max_icon_size); ASSERT(resized); ASSERT_EQ(w, 50); ASSERT_EQ(h, 100); @@ -71,10 +71,10 @@ PASS(); } -TEST test_icon_size_clamp_too_small_then_too_big(void) +TEST test_icon_size_clamp_too_small_then_too_big(int min_icon_size, int max_icon_size) { int w = 8, h = 80; - bool resized = icon_size_clamp(&w, &h); + bool resized = icon_size_clamp(&w, &h, min_icon_size, max_icon_size); ASSERT(resized); ASSERT_EQ(w, 10); ASSERT_EQ(h, 100); @@ -90,30 +90,19 @@ printf("Icon path: %s\n", icon_path); RUN_TEST(test_get_path_from_icon_null); RUN_TEST(test_get_path_from_icon_name_full); - RUN_TEST(test_icon_size_clamp_not_necessary); + RUN_TESTp(test_icon_size_clamp_not_necessary, 0, 100); - settings.min_icon_size = 16; - settings.max_icon_size = 100; + RUN_TESTp(test_icon_size_clamp_too_small, 16, 100); + RUN_TESTp(test_icon_size_clamp_not_necessary, 16, 100); + RUN_TESTp(test_icon_size_clamp_too_big, 16, 100); + RUN_TESTp(test_icon_size_clamp_too_small_then_too_big, 16, 100); - RUN_TEST(test_icon_size_clamp_too_small); - RUN_TEST(test_icon_size_clamp_not_necessary); - RUN_TEST(test_icon_size_clamp_too_big); - RUN_TEST(test_icon_size_clamp_too_small_then_too_big); + RUN_TESTp(test_icon_size_clamp_too_small, 16, 0); + RUN_TESTp(test_icon_size_clamp_not_necessary, 16, 0); - settings.min_icon_size = 16; - settings.max_icon_size = 0; + RUN_TESTp(test_icon_size_clamp_not_necessary, 0, 100); + RUN_TESTp(test_icon_size_clamp_too_big, 0, 100); - RUN_TEST(test_icon_size_clamp_too_small); - RUN_TEST(test_icon_size_clamp_not_necessary); - - settings.min_icon_size = 0; - settings.max_icon_size = 100; - - RUN_TEST(test_icon_size_clamp_not_necessary); - RUN_TEST(test_icon_size_clamp_too_big); - - settings.min_icon_size = 0; - settings.max_icon_size = 0; g_clear_pointer(&icon_path, g_free); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff -Nru dunst-1.8.1/test/icon-lookup.c dunst-1.9.0/test/icon-lookup.c --- dunst-1.8.1/test/icon-lookup.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/icon-lookup.c 2022-06-27 08:43:39.000000000 -0400 @@ -80,18 +80,11 @@ ASSERT(n->icon); - void *old_icon = (void*) n->icon; - ASSERT(old_icon == n->icon); + int old_width = cairo_image_surface_get_width(n->icon); rule_apply(rule, n); - ASSERT(old_icon != n->icon); - /* n->icon = malloc(1); // allocate some data to emulate a raw icon */ + ASSERT(old_width != cairo_image_surface_get_width(n->icon)); - /* printf("%lu\n", sizeof(n->icon)); */ - - - /* printf("%lu\n", sizeof(n->icon)); */ - - free(n->icon); + cairo_surface_destroy(n->icon); n->icon = NULL; notification_unref(n); diff -Nru dunst-1.8.1/test/input.c dunst-1.9.0/test/input.c --- dunst-1.8.1/test/input.c 1969-12-31 19:00:00.000000000 -0500 +++ dunst-1.9.0/test/input.c 2022-06-27 08:43:39.000000000 -0400 @@ -0,0 +1,161 @@ +#include "../src/input.c" +#include "queues.h" +#include "greatest.h" +#include "helpers.h" +#include "../src/utils.h" + +TEST test_get_notification_clickable_height_first(void) +{ + bool orginal_gap_size = settings.gap_size; + settings.gap_size = 0; + + struct notification *n = test_notification("test", 10); + n->displayed_height = 12; + + int expected_size = n->displayed_height + settings.frame_width; + expected_size += (settings.separator_height / 2.0); + int result = get_notification_clickable_height(n, true, false); + + ASSERT(result == expected_size); + + settings.gap_size = orginal_gap_size; + notification_unref(n); + PASS(); +} + +TEST test_get_notification_clickable_height_middle(void) +{ + bool orginal_gap_size = settings.gap_size; + settings.gap_size = 0; + + struct notification *n = test_notification("test", 10); + n->displayed_height = 12; + + int expected_size = n->displayed_height + settings.separator_height; + int result = get_notification_clickable_height(n, false, false); + + ASSERT(result == expected_size); + + settings.gap_size = orginal_gap_size; + notification_unref(n); + PASS(); +} + +TEST test_get_notification_clickable_height_last(void) +{ + bool orginal_gap_size = settings.gap_size; + settings.gap_size = 0; + + struct notification *n = test_notification("test", 10); + n->displayed_height = 12; + + int expected_size = n->displayed_height + settings.frame_width; + expected_size += (settings.separator_height / 2.0); + int result = get_notification_clickable_height(n, false, true); + + ASSERT(result == expected_size); + + settings.gap_size = orginal_gap_size; + notification_unref(n); + PASS(); +} + +TEST test_get_notification_clickable_height_gaps(void) +{ + bool orginal_gap_size = settings.gap_size; + settings.gap_size = 7; + + struct notification *n = test_notification("test", 10); + n->displayed_height = 12; + + int expected_size = n->displayed_height + (settings.frame_width * 2); + + int result_first = get_notification_clickable_height(n, true, false); + ASSERT(result_first == expected_size); + + int result_middle = get_notification_clickable_height(n, false, false); + ASSERT(result_middle == expected_size); + + int result_last = get_notification_clickable_height(n, false, true); + ASSERT(result_last == expected_size); + + settings.gap_size = orginal_gap_size; + notification_unref(n); + PASS(); +} + +TEST test_notification_at(void) +{ + int total_notifications = 3; + GSList *notifications = get_dummy_notifications(total_notifications); + + queues_init(); + + int display_height = 12; + struct notification *n; + for (GSList *iter = notifications; iter; iter = iter->next) { + n = iter->data; + n->displayed_height = display_height; + queues_notification_insert(n); + } + + queues_update(STATUS_NORMAL, time_monotonic_now()); + + struct notification *top_notification = g_slist_nth_data(notifications, 0); + int top_notification_height = get_notification_clickable_height(top_notification, true, false); + + struct notification *middle_notification = g_slist_nth_data(notifications, 1); + int middle_notification_height = get_notification_clickable_height(middle_notification, false, false); + + struct notification *bottom_notification = g_slist_nth_data(notifications, 2); + int bottom_notification_height = get_notification_clickable_height(bottom_notification, false, true); + + struct notification *result; + + int top_y_coord; + top_y_coord = 0; + result = get_notification_at(top_y_coord); + ASSERT(result != NULL); + ASSERT(result == top_notification); + + top_y_coord = top_notification_height - 1; + result = get_notification_at(top_y_coord); + ASSERT(result != NULL); + ASSERT(result == top_notification); + + int middle_y_coord; + middle_y_coord = top_notification_height; + result = get_notification_at(middle_y_coord); + ASSERT(result != NULL); + ASSERT(result == middle_notification); + + middle_y_coord = top_notification_height; + result = get_notification_at(middle_y_coord); + ASSERT(result != NULL); + ASSERT(result == middle_notification); + + int bottom_y_coord; + bottom_y_coord = top_notification_height + middle_notification_height; + result = get_notification_at(bottom_y_coord); + ASSERT(result != NULL); + ASSERT(result == bottom_notification); + + bottom_y_coord = top_notification_height + middle_notification_height + bottom_notification_height - 1; + result = get_notification_at(bottom_y_coord); + ASSERT(result != NULL); + ASSERT(result == bottom_notification); + + g_slist_free_full(notifications, free_dummy_notification); + PASS(); +} + +SUITE(suite_input) +{ + SHUFFLE_TESTS(time(NULL), { + RUN_TEST(test_get_notification_clickable_height_first); + RUN_TEST(test_get_notification_clickable_height_middle); + RUN_TEST(test_get_notification_clickable_height_last); + RUN_TEST(test_get_notification_clickable_height_gaps); + RUN_TEST(test_notification_at); + }); +} diff -Nru dunst-1.8.1/test/notification.c dunst-1.9.0/test/notification.c --- dunst-1.8.1/test/notification.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/notification.c 2022-06-27 08:43:39.000000000 -0400 @@ -137,11 +137,9 @@ GVariant *rawIcon = notification_setup_raw_image(path); - settings.min_icon_size = min_icon_size; - settings.max_icon_size = max_icon_size; + n->min_icon_size = min_icon_size; + n->max_icon_size = max_icon_size; notification_icon_replace_data(n, rawIcon); - settings.min_icon_size = 0; - settings.max_icon_size = 0; g_variant_unref(rawIcon); g_free(path); diff -Nru dunst-1.8.1/test/queues.c dunst-1.9.0/test/queues.c --- dunst-1.8.1/test/queues.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/queues.c 2022-06-27 08:43:39.000000000 -0400 @@ -36,7 +36,7 @@ n = test_notification("n2", 0); queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); n = test_notification("n3", 0); queues_notification_insert(n); @@ -113,7 +113,7 @@ ASSERT_EQ(a->id, b->id); NOT_LAST(a); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); c = test_notification("c", -1); c->id = b->id; @@ -138,7 +138,7 @@ queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); queues_notification_close(n, REASON_UNDEF); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 0, 1); queues_teardown(); @@ -148,10 +148,10 @@ queues_init(); queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 1, 0); queues_notification_close(n, REASON_UNDEF); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 0, 1); queues_teardown(); @@ -170,7 +170,7 @@ queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); queues_notification_close(n, REASON_UNDEF); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 0, 0); queues_teardown(); @@ -181,10 +181,10 @@ queues_init(); queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 1, 0); queues_notification_close(n, REASON_UNDEF); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 0, 0); queues_teardown(); @@ -202,7 +202,7 @@ queues_init(); queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 0, 1); queues_teardown(); @@ -220,12 +220,12 @@ queues_init(); queues_notification_insert(n); QUEUE_LEN_ALL(1, 0, 0); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 0, 1); queues_history_pop(); QUEUE_LEN_ALL(1, 0, 0); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_CONTAINSm("A skip display notification should stay in displayed " "queue when it got pulled out of history queue", DISP, n); @@ -264,7 +264,7 @@ QUEUE_LEN_ALL(notification_buffer_size, 0, 0); // update notification event - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 0, notification_buffer_size); @@ -274,7 +274,7 @@ if(is_n_popped[i]) { queues_history_pop_by_id(i+1); popped_notification_count++; - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); } } @@ -282,7 +282,7 @@ QUEUE_LEN_ALL(0, popped_notification_count, notification_buffer_size-popped_notification_count); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); // check if any of the notifications got moved for(size_t i=0; i<notification_buffer_size; i++) { if(is_n_popped[i]) { @@ -312,7 +312,7 @@ char name[] = { 'n', '0'+i, '\0' }; // n<i> n = test_notification(name, -1); queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); queues_notification_close(n, REASON_UNDEF); } @@ -344,7 +344,7 @@ n = test_notification(name, -1); queues_notification_insert(n); } - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); for (int i = 0; i < 10; i++) { char name[] = { '2', 'n', '0'+i, '\0' }; // 2n<i> @@ -410,7 +410,7 @@ struct notification *n = test_notification("n", 0); queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); ASSERTm("Age threshold is deactivated and the notification is infinite, there is no wakeup necessary.", queues_get_next_datachange(time_monotonic_now()) < 0); @@ -428,7 +428,7 @@ struct notification *n = test_notification("n", 0); queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); ASSERT_IN_RANGEm("Age threshold is activated and the next wakeup should be less than a second away", S2US(1)/2, queues_get_next_datachange(time_monotonic_now() + S2US(4)), S2US(1)/2); @@ -454,7 +454,7 @@ ASSERTm("The inserted notification is inside the waiting queue, so it should get ignored.", queues_get_next_datachange(time_monotonic_now()) < S2US(0)); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); ASSERT_IN_RANGEm("The notification has to get closed in less than its timeout", S2US(10)/2, queues_get_next_datachange(time_monotonic_now()), S2US(10)/2); @@ -474,14 +474,14 @@ n = test_notification("n1", 15); queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); ASSERT_IN_RANGEm("The notification has to get closed in less than its timeout.", n->timeout/2, queues_get_next_datachange(time_monotonic_now()), n->timeout/2); n = test_notification("n2", 10); queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); ASSERT_IN_RANGEm("The timeout of the second notification has to get used as sleep time now.", n->timeout/2, queues_get_next_datachange(time_monotonic_now()), n->timeout/2); @@ -511,7 +511,7 @@ NOT_LAST(n1); notification_ref(n2); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); queues_notification_insert(n3); QUEUE_LEN_ALL(0, 1, 0); NOT_LAST(n2); @@ -542,7 +542,7 @@ NOT_LAST(n1); notification_ref(n2); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); queues_notification_insert(n3); QUEUE_LEN_ALL(0, 1, 0); NOT_LAST(n2); @@ -575,7 +575,7 @@ NOT_LAST(n1); queues_notification_insert(n3); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); printf("queue %i\n",g_queue_get_length(QUEUE(HIST))); QUEUE_LEN_ALL(0, 2, 0); @@ -603,7 +603,7 @@ queues_notification_insert(n2); notification_ref(n2); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); queues_notification_insert(n3); QUEUE_LEN_ALL(0, 2, 0); NOT_LAST(n2); @@ -628,13 +628,13 @@ queues_notification_insert(n2); queues_notification_insert(n3); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); // hacky way to shift time n1->start -= S2US(11); n2->start -= S2US(11); n3->start -= S2US(11); - queues_update(STATUS_IDLE); + queues_update(STATUS_IDLE, time_monotonic_now()); QUEUE_LEN_ALL(0,2,1); QUEUE_CONTAINS(HIST, n3); @@ -642,7 +642,7 @@ // hacky way to shift time n1->start -= S2US(11); n2->start -= S2US(11); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0,1,2); QUEUE_CONTAINS(DISP, n1); @@ -671,17 +671,17 @@ queues_notification_insert(n_dela); queues_notification_insert(n_push); - queues_update(STATUS_FS); + queues_update(STATUS_FS, time_monotonic_now()); QUEUE_CONTAINS(DISP, n_show); QUEUE_CONTAINS(WAIT, n_dela); QUEUE_CONTAINS(WAIT, n_push); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_CONTAINS(DISP, n_show); QUEUE_CONTAINS(DISP, n_dela); QUEUE_CONTAINS(DISP, n_push); - queues_update(STATUS_FS); + queues_update(STATUS_FS, time_monotonic_now()); QUEUE_CONTAINS(DISP, n_show); QUEUE_CONTAINS(DISP, n_dela); QUEUE_CONTAINS(WAIT, n_push); @@ -706,13 +706,13 @@ QUEUE_LEN_ALL(3,0,0); - queues_update(STATUS_PAUSE); + queues_update(STATUS_PAUSE, time_monotonic_now()); QUEUE_LEN_ALL(3,0,0); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0,3,0); - queues_update(STATUS_PAUSE); + queues_update(STATUS_PAUSE, time_monotonic_now()); QUEUE_LEN_ALL(3,0,0); queues_teardown(); @@ -752,7 +752,7 @@ queues_notification_insert(nl5); QUEUE_LEN_ALL(5,0,0); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0,5,0); queues_notification_insert(nc1); @@ -762,7 +762,7 @@ queues_notification_insert(nc5); QUEUE_LEN_ALL(5,5,0); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(5,5,0); QUEUE_CONTAINS(DISP, nc1); @@ -798,15 +798,15 @@ queues_notification_insert(n2); queues_notification_insert(n3); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0,3,0); queues_notification_insert(n4); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0,4,0); queues_notification_insert(n5); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(2,3,0); queues_teardown(); @@ -833,12 +833,12 @@ queues_notification_insert(n1); queues_notification_insert(n2); - queues_update(STATUS_FS); + queues_update(STATUS_FS, time_monotonic_now()); QUEUE_LEN_ALL(2,0,0); queues_notification_insert(n3); - queues_update(STATUS_FS); + queues_update(STATUS_FS, time_monotonic_now()); QUEUE_LEN_ALL(2,1,0); QUEUE_CONTAINS(WAIT, n1); @@ -857,10 +857,10 @@ n = test_notification("n", 10); queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); n->start -= S2US(11); - queues_update(STATUS_PAUSE); + queues_update(STATUS_PAUSE, time_monotonic_now()); QUEUE_LEN_ALL(0,0,1); @@ -906,7 +906,7 @@ n->skip_display = true; queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 0, 3); @@ -940,24 +940,24 @@ n = test_notification("n0", 0); queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); n = test_notification("n1", 0); queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); n = test_notification("n2", 0); queues_notification_insert(n); - queues_update(STATUS_PAUSE); + queues_update(STATUS_PAUSE, time_monotonic_now()); n = test_notification("n3", 0); queues_notification_insert(n); - queues_update(STATUS_PAUSE); - /* queues_update(STATUS_NORMAL); */ + queues_update(STATUS_PAUSE, time_monotonic_now()); + /* queues_update(STATUS_NORMAL, time_monotonic_now()); */ n = test_notification("n4", 0); queues_notification_insert(n); - queues_update(STATUS_NORMAL); + queues_update(STATUS_NORMAL, time_monotonic_now()); QUEUE_LEN_ALL(0, 5, 0); diff -Nru dunst-1.8.1/test/setting.c dunst-1.9.0/test/setting.c --- dunst-1.8.1/test/setting.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/setting.c 2022-06-27 08:43:39.000000000 -0400 @@ -64,6 +64,9 @@ if (!allowed_settings[i].value) { continue; // it's a rule, that's harder to test } + if (allowed_settings[i].different_default) { + continue; // Skip testing, since it's an intended difference. + } size_t offset = (char*)allowed_settings[i].value - (char*)&settings; enum setting_type type = allowed_settings[i].type; snprintf(message, 500, "The default of setting %s does not match. Different defaults are set in code and dunstrc" diff -Nru dunst-1.8.1/test/test.c dunst-1.9.0/test/test.c --- dunst-1.8.1/test/test.c 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/test/test.c 2022-06-27 08:43:39.000000000 -0400 @@ -7,6 +7,7 @@ #include "../src/log.h" #include "../src/settings.h" +#include "helpers.h" const char *base; @@ -27,6 +28,7 @@ SUITE_EXTERN(suite_icon_lookup); SUITE_EXTERN(suite_draw); SUITE_EXTERN(suite_rules); +SUITE_EXTERN(suite_input); GREATEST_MAIN_DEFS(); @@ -66,6 +68,7 @@ RUN_SUITE(suite_icon_lookup); RUN_SUITE(suite_draw); RUN_SUITE(suite_rules); + RUN_SUITE(suite_input); base = NULL; g_free(config_path); diff -Nru dunst-1.8.1/.valgrind.suppressions dunst-1.9.0/.valgrind.suppressions --- dunst-1.8.1/.valgrind.suppressions 2022-03-02 05:55:25.000000000 -0500 +++ dunst-1.9.0/.valgrind.suppressions 2022-06-27 08:43:39.000000000 -0400 @@ -99,3 +99,16 @@ fun:gdk_pixbuf_new_from_file ... } + +# fontconfig memory leaks that occur when using certain pango library +# functions, such as pango_layout_get_pixel_size +{ + fontconfig_pango_leaks + Memcheck:Leak + fun:*alloc + ... + obj:*fontconfig* + ... + obj:*pango* + ... +}
signature.asc
Description: This is a digitally signed message part