From 52055f12c74f20f1eae386a2ddbd57a7dd3b31ac Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Mon, 5 Mar 2018 19:30:48 +0200 Subject: [PATCH 01/38] Move icon related methods to icon.c The first step to cleaning out x.c. --- src/icon.c | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/icon.h | 14 +++++ src/x11/x.c | 136 +----------------------------------------------- 3 files changed, 160 insertions(+), 135 deletions(-) create mode 100644 src/icon.c create mode 100644 src/icon.h diff --git a/src/icon.c b/src/icon.c new file mode 100644 index 0000000..2fa6eae --- /dev/null +++ b/src/icon.c @@ -0,0 +1,145 @@ +#include "icon.h" + +#include +#include +#include +#include + +#include "log.h" +#include "notification.h" +#include "settings.h" + +static bool does_file_exist(const char *filename) +{ + return (access(filename, F_OK) != -1); +} + +static bool is_readable_file(const char *filename) +{ + return (access(filename, R_OK) != -1); +} + +const char *get_filename_ext(const char *filename) +{ + const char *dot = strrchr(filename, '.'); + if (!dot || dot == filename) return ""; + return dot + 1; +} + +static cairo_status_t read_from_buf(void *closure, unsigned char *data, unsigned int size) +{ + GByteArray *buf = (GByteArray *)closure; + + unsigned int cpy = MIN(size, buf->len); + memcpy(data, buf->data, cpy); + g_byte_array_remove_range(buf, 0, cpy); + + return CAIRO_STATUS_SUCCESS; +} + + +cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf) +{ + /* + * Export the gdk pixbuf into buffer as a png and import the png buffer + * via cairo again as a cairo_surface_t. + * It looks counterintuitive, as there is gdk_cairo_set_source_pixbuf, + * which does the job faster. But this would require gtk3 as a dependency + * for a single function call. See discussion in #334 and #376. + */ + cairo_surface_t *icon_surface = NULL; + GByteArray *buffer; + char *bufstr; + gsize buflen; + + gdk_pixbuf_save_to_buffer(pixbuf, &bufstr, &buflen, "png", NULL, NULL); + + buffer = g_byte_array_new_take((guint8*)bufstr, buflen); + icon_surface = cairo_image_surface_create_from_png_stream(read_from_buf, buffer); + + g_byte_array_free(buffer, TRUE); + + return icon_surface; +} + +static GdkPixbuf *get_pixbuf_from_file(const char *icon_path) +{ + GdkPixbuf *pixbuf = NULL; + if (is_readable_file(icon_path)) { + GError *error = NULL; + pixbuf = gdk_pixbuf_new_from_file(icon_path, &error); + if (pixbuf == NULL) + g_error_free(error); + } + return pixbuf; +} + +GdkPixbuf *get_pixbuf_from_path(char *icon_path) +{ + GdkPixbuf *pixbuf = NULL; + gchar *uri_path = NULL; + if (strlen(icon_path) > 0) { + if (g_str_has_prefix(icon_path, "file://")) { + uri_path = g_filename_from_uri(icon_path, NULL, NULL); + if (uri_path != NULL) { + icon_path = uri_path; + } + } + /* absolute path? */ + if (icon_path[0] == '/' || icon_path[0] == '~') { + pixbuf = get_pixbuf_from_file(icon_path); + } + /* search in icon_path */ + if (pixbuf == NULL) { + char *start = settings.icon_path, + *end, *current_folder, *maybe_icon_path; + do { + end = strchr(start, ':'); + if (end == NULL) end = strchr(settings.icon_path, '\0'); /* end = end of string */ + + current_folder = g_strndup(start, end - start); + /* try svg */ + maybe_icon_path = g_strconcat(current_folder, "/", icon_path, ".svg", NULL); + if (!does_file_exist(maybe_icon_path)) { + g_free(maybe_icon_path); + /* fallback to png */ + maybe_icon_path = g_strconcat(current_folder, "/", icon_path, ".png", NULL); + } + g_free(current_folder); + + pixbuf = get_pixbuf_from_file(maybe_icon_path); + g_free(maybe_icon_path); + if (pixbuf != NULL) { + return pixbuf; + } + + start = end + 1; + } while (*(end) != '\0'); + } + if (pixbuf == NULL) { + LOG_W("Could not load icon: '%s'", icon_path); + } + if (uri_path != NULL) { + g_free(uri_path); + } + } + return pixbuf; +} + +GdkPixbuf *get_pixbuf_from_raw_image(const RawImage *raw_image) +{ + GdkPixbuf *pixbuf = NULL; + + pixbuf = gdk_pixbuf_new_from_data(raw_image->data, + GDK_COLORSPACE_RGB, + raw_image->has_alpha, + raw_image->bits_per_sample, + raw_image->width, + raw_image->height, + raw_image->rowstride, + NULL, + NULL); + + return pixbuf; +} +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/icon.h b/src/icon.h new file mode 100644 index 0000000..838a0dc --- /dev/null +++ b/src/icon.h @@ -0,0 +1,14 @@ +#ifndef DUNST_ICON_H +#define DUNST_ICON_H + +#include +#include + +#include "notification.h" + +cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf); +GdkPixbuf *get_pixbuf_from_path(char *icon_path); +GdkPixbuf *get_pixbuf_from_raw_image(const RawImage *raw_image); + +#endif +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/x11/x.c b/src/x11/x.c index a21840d..b0130ab 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -26,6 +25,7 @@ #include "src/dbus.h" #include "src/dunst.h" +#include "src/icon.h" #include "src/log.h" #include "src/markup.h" #include "src/notification.h" @@ -187,23 +187,6 @@ static bool have_dynamic_width(void) return (xctx.geometry.mask & WidthValue && xctx.geometry.w == 0); } -static bool does_file_exist(const char *filename) -{ - return (access(filename, F_OK) != -1); -} - -static bool is_readable_file(const char *filename) -{ - return (access(filename, R_OK) != -1); -} - -const char *get_filename_ext(const char *filename) -{ - const char *dot = strrchr(filename, '.'); - if (!dot || dot == filename) return ""; - return dot + 1; -} - static dimension_t calculate_dimensions(GSList *layouts) { dimension_t dim; @@ -287,123 +270,6 @@ static dimension_t calculate_dimensions(GSList *layouts) return dim; } -static cairo_status_t read_from_buf(void *closure, unsigned char *data, unsigned int size) -{ - GByteArray *buf = (GByteArray *)closure; - - unsigned int cpy = MIN(size, buf->len); - memcpy(data, buf->data, cpy); - g_byte_array_remove_range(buf, 0, cpy); - - return CAIRO_STATUS_SUCCESS; -} - - -static cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf) -{ - /* - * Export the gdk pixbuf into buffer as a png and import the png buffer - * via cairo again as a cairo_surface_t. - * It looks counterintuitive, as there is gdk_cairo_set_source_pixbuf, - * which does the job faster. But this would require gtk3 as a dependency - * for a single function call. See discussion in #334 and #376. - */ - cairo_surface_t *icon_surface = NULL; - GByteArray *buffer; - char *bufstr; - gsize buflen; - - gdk_pixbuf_save_to_buffer(pixbuf, &bufstr, &buflen, "png", NULL, NULL); - - buffer = g_byte_array_new_take((guint8*)bufstr, buflen); - icon_surface = cairo_image_surface_create_from_png_stream(read_from_buf, buffer); - - g_byte_array_free(buffer, TRUE); - - return icon_surface; -} - -static GdkPixbuf *get_pixbuf_from_file(const char *icon_path) -{ - GdkPixbuf *pixbuf = NULL; - if (is_readable_file(icon_path)) { - GError *error = NULL; - pixbuf = gdk_pixbuf_new_from_file(icon_path, &error); - if (pixbuf == NULL) - g_error_free(error); - } - return pixbuf; -} - -static GdkPixbuf *get_pixbuf_from_path(char *icon_path) -{ - GdkPixbuf *pixbuf = NULL; - gchar *uri_path = NULL; - if (strlen(icon_path) > 0) { - if (g_str_has_prefix(icon_path, "file://")) { - uri_path = g_filename_from_uri(icon_path, NULL, NULL); - if (uri_path != NULL) { - icon_path = uri_path; - } - } - /* absolute path? */ - if (icon_path[0] == '/' || icon_path[0] == '~') { - pixbuf = get_pixbuf_from_file(icon_path); - } - /* search in icon_path */ - if (pixbuf == NULL) { - char *start = settings.icon_path, - *end, *current_folder, *maybe_icon_path; - do { - end = strchr(start, ':'); - if (end == NULL) end = strchr(settings.icon_path, '\0'); /* end = end of string */ - - current_folder = g_strndup(start, end - start); - /* try svg */ - maybe_icon_path = g_strconcat(current_folder, "/", icon_path, ".svg", NULL); - if (!does_file_exist(maybe_icon_path)) { - g_free(maybe_icon_path); - /* fallback to png */ - maybe_icon_path = g_strconcat(current_folder, "/", icon_path, ".png", NULL); - } - g_free(current_folder); - - pixbuf = get_pixbuf_from_file(maybe_icon_path); - g_free(maybe_icon_path); - if (pixbuf != NULL) { - return pixbuf; - } - - start = end + 1; - } while (*(end) != '\0'); - } - if (pixbuf == NULL) { - LOG_W("Could not load icon: '%s'", icon_path); - } - if (uri_path != NULL) { - g_free(uri_path); - } - } - return pixbuf; -} - -static GdkPixbuf *get_pixbuf_from_raw_image(const RawImage *raw_image) -{ - GdkPixbuf *pixbuf = NULL; - - pixbuf = gdk_pixbuf_new_from_data(raw_image->data, - GDK_COLORSPACE_RGB, - raw_image->has_alpha, - raw_image->bits_per_sample, - raw_image->width, - raw_image->height, - raw_image->rowstride, - NULL, - NULL); - - return pixbuf; -} - static PangoLayout *create_layout(cairo_t *c) { screen_info *screen = get_active_screen(); From 045ec98c0e5e517f8ed41bb5da6ba63da3fa140d Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Mon, 5 Mar 2018 19:52:25 +0200 Subject: [PATCH 02/38] Setup initial file for draw.c Setup the initial template for draw.c to be between X11 calls and the rest of the codebase. This prepares the file to move all the drawing related function here in the next commit. --- src/draw.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/draw.h | 11 +++++++++++ src/dunst.c | 7 ++++--- src/x11/x.c | 11 +++++++++-- src/x11/x.h | 3 +++ 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/draw.c create mode 100644 src/draw.h diff --git a/src/draw.c b/src/draw.c new file mode 100644 index 0000000..25287e8 --- /dev/null +++ b/src/draw.c @@ -0,0 +1,50 @@ +#include "draw.h" + +#include +#include +#include +#include +#include +#include + +#include "notification.h" +#include "x11/x.h" + +typedef struct { + PangoLayout *l; + color_t fg; + color_t bg; + color_t frame; + char *text; + PangoAttrList *attr; + cairo_surface_t *icon; + notification *n; +} colored_layout; + +cairo_surface_t *root_surface; +cairo_t *c_context; + +PangoFontDescription *pango_fdesc; + +void draw_setup(void) +{ + x_setup(); + + root_surface = x_create_cairo_surface(); + c_context = cairo_create(root_surface); + pango_fdesc = pango_font_description_from_string(settings.font); +} + +void draw(void) +{ + x_win_draw(); +} + +void draw_deinit(void) +{ + cairo_destroy(c_context); + cairo_surface_destroy(root_surface); + + x_free(); +} +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/draw.h b/src/draw.h new file mode 100644 index 0000000..de9fb28 --- /dev/null +++ b/src/draw.h @@ -0,0 +1,11 @@ +#ifndef DUNST_DRAW_H +#define DUNST_DRAW_H + +void draw_setup(void); + +void draw(void); + +void draw_deinit(void); + +#endif +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/dunst.c b/src/dunst.c index 2bf6899..f6f3a24 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -13,6 +13,7 @@ #include #include "dbus.h" +#include "draw.h" #include "log.h" #include "menu.h" #include "notification.h" @@ -67,7 +68,7 @@ static gboolean run(void *data) } if (xctx.visible) { - x_win_draw(); + draw(); } if (xctx.visible) { @@ -120,7 +121,7 @@ static void teardown(void) teardown_queues(); - x_free(); + draw_deinit(); } int dunst_main(int argc, char *argv[]) @@ -154,7 +155,7 @@ int dunst_main(int argc, char *argv[]) int owner_id = initdbus(); - x_setup(); + draw_setup(); if (settings.startup_notification) { notification *n = notification_create(); diff --git a/src/x11/x.c b/src/x11/x.c index b0130ab..1c0310f 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -23,6 +23,7 @@ #include #include +#include "src/draw.h" #include "src/dbus.h" #include "src/dunst.h" #include "src/icon.h" @@ -148,6 +149,12 @@ static void x_cairo_setup(void) cairo_ctx.desc = pango_font_description_from_string(settings.font); } +cairo_surface_t *x_create_cairo_surface(void) +{ + return cairo_xlib_surface_create(xctx.dpy, + xctx.win, DefaultVisual(xctx.dpy, 0), WIDTH, HEIGHT); +} + static void r_setup_pango_layout(PangoLayout *layout, int width) { pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); @@ -725,7 +732,7 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer switch (ev.type) { case Expose: if (ev.xexpose.count == 0 && xctx.visible) { - x_win_draw(); + draw(); } break; case SelectionNotify: @@ -790,7 +797,7 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer */ && xctx.visible && get_active_screen()->scr != xctx.cur_screen) { - x_win_draw(); + draw(); } break; default: diff --git a/src/x11/x.h b/src/x11/x.h index 8ee757a..b6ac16e 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -2,6 +2,7 @@ #ifndef DUNST_X_H #define DUNST_X_H +#include #include #include #include @@ -59,6 +60,8 @@ bool x_is_idle(void); void x_setup(void); void x_free(void); +cairo_surface_t *x_create_cairo_surface(void); + gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data); gboolean x_mainloop_fd_check(GSource *source); From 466f95eb91996022bfdd0ca16c5f9940a46ef115 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Mon, 5 Mar 2018 20:11:10 +0200 Subject: [PATCH 03/38] Move drawing functions to draw.c Copy-paste all the drawing functions from x.c to draw.c. In the spirit of making changes easily traceable the minimal amount of changes required to make the functions work was made. Further improvement (like removing the cairo-xlib dependency) will be done in later commits. --- src/draw.c | 517 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/x11/x.c | 514 +-------------------------------------------------- src/x11/x.h | 2 +- 3 files changed, 518 insertions(+), 515 deletions(-) diff --git a/src/draw.c b/src/draw.c index 25287e8..5306a2a 100644 --- a/src/draw.c +++ b/src/draw.c @@ -1,13 +1,23 @@ #include "draw.h" +#include // Temporary +#include #include +#include // Temporary +#include #include #include #include #include #include +#include +#include "dunst.h" +#include "icon.h" +#include "markup.h" #include "notification.h" +#include "log.h" +#include "queues.h" #include "x11/x.h" typedef struct { @@ -35,9 +45,514 @@ void draw_setup(void) pango_fdesc = pango_font_description_from_string(settings.font); } +static color_t x_color_hex_to_double(int hexValue) +{ + color_t color; + color.r = ((hexValue >> 16) & 0xFF) / 255.0; + color.g = ((hexValue >> 8) & 0xFF) / 255.0; + color.b = ((hexValue) & 0xFF) / 255.0; + + return color; +} + +static color_t x_string_to_color_t(const char *str) +{ + char *end; + long int val = strtol(str+1, &end, 16); + if (*end != '\0' && *(end+1) != '\0') { + LOG_W("Invalid color string: '%s'", str); + } + + return x_color_hex_to_double(val); +} + +static double _apply_delta(double base, double delta) +{ + base += delta; + if (base > 1) + base = 1; + if (base < 0) + base = 0; + + return base; +} + +static color_t calculate_foreground_color(color_t bg) +{ + double c_delta = 0.1; + color_t color = bg; + + /* do we need to darken or brighten the colors? */ + bool darken = (bg.r + bg.g + bg.b) / 3 > 0.5; + + int signedness = darken ? -1 : 1; + + color.r = _apply_delta(color.r, c_delta * signedness); + color.g = _apply_delta(color.g, c_delta * signedness); + color.b = _apply_delta(color.b, c_delta * signedness); + + return color; +} + +static color_t x_get_separator_color(colored_layout *cl, colored_layout *cl_next) +{ + switch (settings.sep_color) { + case FRAME: + if (cl_next->n->urgency > cl->n->urgency) + return cl_next->frame; + else + return cl->frame; + case CUSTOM: + return x_string_to_color_t(settings.sep_custom_color_str); + case FOREGROUND: + return cl->fg; + case AUTO: + return calculate_foreground_color(cl->bg); + default: + LOG_E("Unknown separator color type."); + } +} + +static void r_setup_pango_layout(PangoLayout *layout, int width) +{ + pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); + pango_layout_set_width(layout, width * PANGO_SCALE); + pango_layout_set_font_description(layout, pango_fdesc); + pango_layout_set_spacing(layout, settings.line_height * PANGO_SCALE); + + PangoAlignment align; + switch (settings.align) { + case left: + default: + align = PANGO_ALIGN_LEFT; + break; + case center: + align = PANGO_ALIGN_CENTER; + break; + case right: + align = PANGO_ALIGN_RIGHT; + break; + } + pango_layout_set_alignment(layout, align); + +} + +static void free_colored_layout(void *data) +{ + colored_layout *cl = data; + g_object_unref(cl->l); + pango_attr_list_unref(cl->attr); + g_free(cl->text); + if (cl->icon) cairo_surface_destroy(cl->icon); + g_free(cl); +} + +static bool have_dynamic_width(void) +{ + return (xctx.geometry.mask & WidthValue && xctx.geometry.w == 0); +} + +static dimension_t calculate_dimensions(GSList *layouts) +{ + dimension_t dim; + dim.w = 0; + dim.h = 0; + dim.x = 0; + dim.y = 0; + dim.mask = xctx.geometry.mask; + + screen_info *scr = get_active_screen(); + if (have_dynamic_width()) { + /* dynamic width */ + dim.w = 0; + } else if (xctx.geometry.mask & WidthValue) { + /* fixed width */ + if (xctx.geometry.negative_width) { + dim.w = scr->dim.w - xctx.geometry.w; + } else { + dim.w = xctx.geometry.w; + } + } else { + /* across the screen */ + dim.w = scr->dim.w; + } + + dim.h += 2 * settings.frame_width; + dim.h += (g_slist_length(layouts) - 1) * settings.separator_height; + + int text_width = 0, total_width = 0; + for (GSList *iter = layouts; iter; iter = iter->next) { + colored_layout *cl = iter->data; + int w=0,h=0; + pango_layout_get_pixel_size(cl->l, &w, &h); + if (cl->icon) { + h = MAX(cairo_image_surface_get_height(cl->icon), h); + w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; + } + h = MAX(settings.notification_height, h + settings.padding * 2); + dim.h += h; + text_width = MAX(w, text_width); + + if (have_dynamic_width() || settings.shrink) { + /* dynamic width */ + total_width = MAX(text_width + 2 * settings.h_padding, total_width); + + /* subtract height from the unwrapped text */ + dim.h -= h; + + if (total_width > scr->dim.w) { + /* set width to screen width */ + dim.w = scr->dim.w - xctx.geometry.x * 2; + } else if (have_dynamic_width() || (total_width < xctx.geometry.w && settings.shrink)) { + /* set width to text width */ + dim.w = total_width + 2 * settings.frame_width; + } + + /* re-setup the layout */ + w = dim.w; + w -= 2 * settings.h_padding; + w -= 2 * settings.frame_width; + if (cl->icon) w -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; + r_setup_pango_layout(cl->l, w); + + /* re-read information */ + pango_layout_get_pixel_size(cl->l, &w, &h); + if (cl->icon) { + h = MAX(cairo_image_surface_get_height(cl->icon), h); + w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; + } + h = MAX(settings.notification_height, h + settings.padding * 2); + dim.h += h; + text_width = MAX(w, text_width); + } + } + + if (dim.w <= 0) { + dim.w = text_width + 2 * settings.h_padding; + dim.w += 2 * settings.frame_width; + } + + return dim; +} + +static PangoLayout *create_layout(cairo_t *c) +{ + screen_info *screen = get_active_screen(); + + PangoContext *context = pango_cairo_create_context(c); + pango_cairo_context_set_resolution(context, get_dpi_for_screen(screen)); + + PangoLayout *layout = pango_layout_new(context); + + g_object_unref(context); + + return layout; +} + +static colored_layout *r_init_shared(cairo_t *c, notification *n) +{ + colored_layout *cl = g_malloc(sizeof(colored_layout)); + cl->l = create_layout(c); + + if (!settings.word_wrap) { + PangoEllipsizeMode ellipsize; + switch (settings.ellipsize) { + case start: + ellipsize = PANGO_ELLIPSIZE_START; + break; + case middle: + ellipsize = PANGO_ELLIPSIZE_MIDDLE; + break; + case end: + ellipsize = PANGO_ELLIPSIZE_END; + break; + default: + assert(false); + } + pango_layout_set_ellipsize(cl->l, ellipsize); + } + + GdkPixbuf *pixbuf = NULL; + + if (n->raw_icon && + settings.icon_position != icons_off) { + + pixbuf = get_pixbuf_from_raw_image(n->raw_icon); + + } else if (n->icon && settings.icon_position != icons_off) { + pixbuf = get_pixbuf_from_path(n->icon); + } + + if (pixbuf != NULL) { + int w = gdk_pixbuf_get_width(pixbuf); + int h = gdk_pixbuf_get_height(pixbuf); + int larger = w > h ? w : h; + if (settings.max_icon_size && larger > settings.max_icon_size) { + GdkPixbuf *scaled; + if (w >= h) { + scaled = gdk_pixbuf_scale_simple(pixbuf, + settings.max_icon_size, + (int) ((double) settings.max_icon_size / w * h), + GDK_INTERP_BILINEAR); + } else { + scaled = gdk_pixbuf_scale_simple(pixbuf, + (int) ((double) settings.max_icon_size / h * w), + settings.max_icon_size, + GDK_INTERP_BILINEAR); + } + g_object_unref(pixbuf); + pixbuf = scaled; + } + + cl->icon = gdk_pixbuf_to_cairo_surface(pixbuf); + g_object_unref(pixbuf); + } else { + cl->icon = NULL; + } + + if (cl->icon && cairo_surface_status(cl->icon) != CAIRO_STATUS_SUCCESS) { + cairo_surface_destroy(cl->icon); + cl->icon = NULL; + } + + cl->fg = x_string_to_color_t(n->colors[ColFG]); + cl->bg = x_string_to_color_t(n->colors[ColBG]); + cl->frame = x_string_to_color_t(n->colors[ColFrame]); + + cl->n = n; + + dimension_t dim = calculate_dimensions(NULL); + int width = dim.w; + + if (have_dynamic_width()) { + r_setup_pango_layout(cl->l, -1); + } else { + width -= 2 * settings.h_padding; + width -= 2 * settings.frame_width; + if (cl->icon) width -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; + r_setup_pango_layout(cl->l, width); + } + + return cl; +} + +static colored_layout *r_create_layout_for_xmore(cairo_t *c, notification *n, int qlen) +{ + colored_layout *cl = r_init_shared(c, n); + cl->text = g_strdup_printf("(%d more)", qlen); + cl->attr = NULL; + pango_layout_set_text(cl->l, cl->text, -1); + return cl; +} + +static colored_layout *r_create_layout_from_notification(cairo_t *c, notification *n) +{ + + colored_layout *cl = r_init_shared(c, n); + + /* markup */ + GError *err = NULL; + pango_parse_markup(n->text_to_render, -1, 0, &(cl->attr), &(cl->text), NULL, &err); + + if (!err) { + pango_layout_set_text(cl->l, cl->text, -1); + pango_layout_set_attributes(cl->l, cl->attr); + } else { + /* remove markup and display plain message instead */ + n->text_to_render = markup_strip(n->text_to_render); + cl->text = NULL; + cl->attr = NULL; + pango_layout_set_text(cl->l, n->text_to_render, -1); + if (n->first_render) { + LOG_W("Unable to parse markup: %s", err->message); + } + g_error_free(err); + } + + + 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->first_render = false; + return cl; +} + +static GSList *r_create_layouts(cairo_t *c) +{ + GSList *layouts = NULL; + + int qlen = queues_length_waiting(); + bool xmore_is_needed = qlen > 0 && settings.indicate_hidden; + + notification *last = NULL; + for (const GList *iter = queues_get_displayed(); + iter; iter = iter->next) + { + notification *n = iter->data; + last = n; + + notification_update_text_to_render(n); + + if (!iter->next && xmore_is_needed && xctx.geometry.h == 1) { + char *new_ttr = g_strdup_printf("%s (%d more)", n->text_to_render, qlen); + g_free(n->text_to_render); + n->text_to_render = new_ttr; + } + layouts = g_slist_append(layouts, + r_create_layout_from_notification(c, n)); + } + + if (xmore_is_needed && xctx.geometry.h != 1) { + /* append xmore message as new message */ + layouts = g_slist_append(layouts, + r_create_layout_for_xmore(c, last, qlen)); + } + + return layouts; +} + +static void r_free_layouts(GSList *layouts) +{ + g_slist_free_full(layouts, free_colored_layout); +} + +static dimension_t x_render_layout(cairo_t *c, colored_layout *cl, colored_layout *cl_next, dimension_t dim, bool first, bool last) +{ + int h; + int h_text = 0; + pango_layout_get_pixel_size(cl->l, NULL, &h); + if (cl->icon) { + h_text = h; + h = MAX(cairo_image_surface_get_height(cl->icon), h); + } + + int bg_x = 0; + int bg_y = dim.y; + int bg_width = dim.w; + int bg_height = MAX(settings.notification_height, (2 * settings.padding) + h); + double bg_half_height = settings.notification_height/2.0; + int pango_offset = (int) floor(h/2.0); + + if (first) bg_height += settings.frame_width; + if (last) bg_height += settings.frame_width; + else bg_height += settings.separator_height; + + cairo_set_source_rgb(c, cl->frame.r, cl->frame.g, cl->frame.b); + cairo_rectangle(c, bg_x, bg_y, bg_width, bg_height); + cairo_fill(c); + + /* adding frame */ + bg_x += settings.frame_width; + if (first) { + dim.y += settings.frame_width; + bg_y += settings.frame_width; + bg_height -= settings.frame_width; + if (!last) bg_height -= settings.separator_height; + } + bg_width -= 2 * settings.frame_width; + if (last) + bg_height -= settings.frame_width; + + cairo_set_source_rgb(c, cl->bg.r, cl->bg.g, cl->bg.b); + cairo_rectangle(c, bg_x, bg_y, bg_width, bg_height); + cairo_fill(c); + + bool use_padding = settings.notification_height <= (2 * settings.padding) + h; + if (use_padding) + dim.y += settings.padding; + else + dim.y += (int) (ceil(bg_half_height) - pango_offset); + + if (cl->icon && settings.icon_position == icons_left) { + cairo_move_to(c, settings.frame_width + cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding, bg_y + settings.padding + h/2 - h_text/2); + } else if (cl->icon && settings.icon_position == icons_right) { + cairo_move_to(c, settings.frame_width + settings.h_padding, bg_y + settings.padding + h/2 - h_text/2); + } else { + cairo_move_to(c, settings.frame_width + settings.h_padding, bg_y + settings.padding); + } + + cairo_set_source_rgb(c, cl->fg.r, cl->fg.g, cl->fg.b); + pango_cairo_update_layout(c, cl->l); + pango_cairo_show_layout(c, cl->l); + if (use_padding) + dim.y += h + settings.padding; + else + dim.y += (int)(floor(bg_half_height) + pango_offset); + + if (settings.separator_height > 0 && !last) { + color_t sep_color = x_get_separator_color(cl, cl_next); + cairo_set_source_rgb(c, sep_color.r, sep_color.g, sep_color.b); + + if (settings.sep_color == FRAME) + // Draw over the borders on both sides to avoid + // the wrong color in the corners. + cairo_rectangle(c, 0, dim.y, dim.w, settings.separator_height); + else + cairo_rectangle(c, settings.frame_width, dim.y + settings.frame_width, dim.w - 2 * settings.frame_width, settings.separator_height); + + cairo_fill(c); + dim.y += settings.separator_height; + } + cairo_move_to(c, settings.h_padding, dim.y); + + if (cl->icon) { + unsigned int image_width = cairo_image_surface_get_width(cl->icon), + image_height = cairo_image_surface_get_height(cl->icon), + image_x, + image_y = bg_y + settings.padding + h/2 - image_height/2; + + if (settings.icon_position == icons_left) { + image_x = settings.frame_width + settings.h_padding; + } else { + image_x = bg_width - settings.h_padding - image_width + settings.frame_width; + } + + cairo_set_source_surface(c, cl->icon, image_x, image_y); + cairo_rectangle(c, image_x, image_y, image_width, image_height); + cairo_fill(c); + } + + return dim; +} + void draw(void) { - x_win_draw(); + + GSList *layouts = r_create_layouts(c_context); + + dimension_t dim = calculate_dimensions(layouts); + int width = dim.w; + int height = dim.h; + + cairo_t *c; + cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + c = cairo_create(image_surface); + + x_win_move(width, height); + cairo_xlib_surface_set_size(root_surface, width, height); + + cairo_move_to(c, 0, 0); + + bool first = true; + for (GSList *iter = layouts; iter; iter = iter->next) { + if (iter->next) + dim = x_render_layout(c, iter->data, iter->next->data, dim, first, iter->next == NULL); + else + dim = x_render_layout(c, iter->data, NULL, dim, first, iter->next == NULL); + + first = false; + } + + cairo_set_source_surface(c_context, image_surface, 0, 0); + cairo_paint(c_context); + cairo_show_page(c_context); + + XFlush(xctx.dpy); + + cairo_destroy(c); + cairo_surface_destroy(image_surface); + r_free_layouts(layouts); } void draw_deinit(void) diff --git a/src/x11/x.c b/src/x11/x.c index 1c0310f..c5a5353 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -26,7 +26,6 @@ #include "src/draw.h" #include "src/dbus.h" #include "src/dunst.h" -#include "src/icon.h" #include "src/log.h" #include "src/markup.h" #include "src/notification.h" @@ -66,79 +65,10 @@ static bool fullscreen_last = false; /* FIXME refactor setup teardown handlers into one setup and one teardown */ static void x_shortcut_setup_error_handler(void); static int x_shortcut_tear_down_error_handler(void); -static void x_win_move(int width, int height); static void setopacity(Window win, unsigned long opacity); static void x_handle_click(XEvent ev); static void x_win_setup(void); -static color_t x_color_hex_to_double(int hexValue) -{ - color_t color; - color.r = ((hexValue >> 16) & 0xFF) / 255.0; - color.g = ((hexValue >> 8) & 0xFF) / 255.0; - color.b = ((hexValue) & 0xFF) / 255.0; - - return color; -} - -static color_t x_string_to_color_t(const char *str) -{ - char *end; - long int val = strtol(str+1, &end, 16); - if (*end != '\0' && *(end+1) != '\0') { - LOG_W("Invalid color string: '%s'", str); - } - - return x_color_hex_to_double(val); -} - -static double _apply_delta(double base, double delta) -{ - base += delta; - if (base > 1) - base = 1; - if (base < 0) - base = 0; - - return base; -} - -static color_t calculate_foreground_color(color_t bg) -{ - double c_delta = 0.1; - color_t color = bg; - - /* do we need to darken or brighten the colors? */ - bool darken = (bg.r + bg.g + bg.b) / 3 > 0.5; - - int signedness = darken ? -1 : 1; - - color.r = _apply_delta(color.r, c_delta * signedness); - color.g = _apply_delta(color.g, c_delta * signedness); - color.b = _apply_delta(color.b, c_delta * signedness); - - return color; -} - -static color_t x_get_separator_color(colored_layout *cl, colored_layout *cl_next) -{ - switch (settings.sep_color) { - case FRAME: - if (cl_next->n->urgency > cl->n->urgency) - return cl_next->frame; - else - return cl->frame; - case CUSTOM: - return x_string_to_color_t(settings.sep_custom_color_str); - case FOREGROUND: - return cl->fg; - case AUTO: - return calculate_foreground_color(cl->bg); - default: - LOG_E("Unknown separator color type."); - } -} - static void x_cairo_setup(void) { cairo_ctx.surface = cairo_xlib_surface_create(xctx.dpy, @@ -155,449 +85,7 @@ cairo_surface_t *x_create_cairo_surface(void) xctx.win, DefaultVisual(xctx.dpy, 0), WIDTH, HEIGHT); } -static void r_setup_pango_layout(PangoLayout *layout, int width) -{ - pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); - pango_layout_set_width(layout, width * PANGO_SCALE); - pango_layout_set_font_description(layout, cairo_ctx.desc); - pango_layout_set_spacing(layout, settings.line_height * PANGO_SCALE); - - PangoAlignment align; - switch (settings.align) { - case left: - default: - align = PANGO_ALIGN_LEFT; - break; - case center: - align = PANGO_ALIGN_CENTER; - break; - case right: - align = PANGO_ALIGN_RIGHT; - break; - } - pango_layout_set_alignment(layout, align); - -} - -static void free_colored_layout(void *data) -{ - colored_layout *cl = data; - g_object_unref(cl->l); - pango_attr_list_unref(cl->attr); - g_free(cl->text); - if (cl->icon) cairo_surface_destroy(cl->icon); - g_free(cl); -} - -static bool have_dynamic_width(void) -{ - return (xctx.geometry.mask & WidthValue && xctx.geometry.w == 0); -} - -static dimension_t calculate_dimensions(GSList *layouts) -{ - dimension_t dim; - dim.w = 0; - dim.h = 0; - dim.x = 0; - dim.y = 0; - dim.mask = xctx.geometry.mask; - - screen_info *scr = get_active_screen(); - if (have_dynamic_width()) { - /* dynamic width */ - dim.w = 0; - } else if (xctx.geometry.mask & WidthValue) { - /* fixed width */ - if (xctx.geometry.negative_width) { - dim.w = scr->dim.w - xctx.geometry.w; - } else { - dim.w = xctx.geometry.w; - } - } else { - /* across the screen */ - dim.w = scr->dim.w; - } - - dim.h += 2 * settings.frame_width; - dim.h += (g_slist_length(layouts) - 1) * settings.separator_height; - - int text_width = 0, total_width = 0; - for (GSList *iter = layouts; iter; iter = iter->next) { - colored_layout *cl = iter->data; - int w=0,h=0; - pango_layout_get_pixel_size(cl->l, &w, &h); - if (cl->icon) { - h = MAX(cairo_image_surface_get_height(cl->icon), h); - w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; - } - h = MAX(settings.notification_height, h + settings.padding * 2); - dim.h += h; - text_width = MAX(w, text_width); - - if (have_dynamic_width() || settings.shrink) { - /* dynamic width */ - total_width = MAX(text_width + 2 * settings.h_padding, total_width); - - /* subtract height from the unwrapped text */ - dim.h -= h; - - if (total_width > scr->dim.w) { - /* set width to screen width */ - dim.w = scr->dim.w - xctx.geometry.x * 2; - } else if (have_dynamic_width() || (total_width < xctx.geometry.w && settings.shrink)) { - /* set width to text width */ - dim.w = total_width + 2 * settings.frame_width; - } - - /* re-setup the layout */ - w = dim.w; - w -= 2 * settings.h_padding; - w -= 2 * settings.frame_width; - if (cl->icon) w -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; - r_setup_pango_layout(cl->l, w); - - /* re-read information */ - pango_layout_get_pixel_size(cl->l, &w, &h); - if (cl->icon) { - h = MAX(cairo_image_surface_get_height(cl->icon), h); - w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; - } - h = MAX(settings.notification_height, h + settings.padding * 2); - dim.h += h; - text_width = MAX(w, text_width); - } - } - - if (dim.w <= 0) { - dim.w = text_width + 2 * settings.h_padding; - dim.w += 2 * settings.frame_width; - } - - return dim; -} - -static PangoLayout *create_layout(cairo_t *c) -{ - screen_info *screen = get_active_screen(); - - PangoContext *context = pango_cairo_create_context(c); - pango_cairo_context_set_resolution(context, get_dpi_for_screen(screen)); - - PangoLayout *layout = pango_layout_new(context); - - g_object_unref(context); - - return layout; -} - -static colored_layout *r_init_shared(cairo_t *c, notification *n) -{ - colored_layout *cl = g_malloc(sizeof(colored_layout)); - cl->l = create_layout(c); - - if (!settings.word_wrap) { - PangoEllipsizeMode ellipsize; - switch (settings.ellipsize) { - case start: - ellipsize = PANGO_ELLIPSIZE_START; - break; - case middle: - ellipsize = PANGO_ELLIPSIZE_MIDDLE; - break; - case end: - ellipsize = PANGO_ELLIPSIZE_END; - break; - default: - assert(false); - } - pango_layout_set_ellipsize(cl->l, ellipsize); - } - - GdkPixbuf *pixbuf = NULL; - - if (n->raw_icon && - settings.icon_position != icons_off) { - - pixbuf = get_pixbuf_from_raw_image(n->raw_icon); - - } else if (n->icon && settings.icon_position != icons_off) { - pixbuf = get_pixbuf_from_path(n->icon); - } - - if (pixbuf != NULL) { - int w = gdk_pixbuf_get_width(pixbuf); - int h = gdk_pixbuf_get_height(pixbuf); - int larger = w > h ? w : h; - if (settings.max_icon_size && larger > settings.max_icon_size) { - GdkPixbuf *scaled; - if (w >= h) { - scaled = gdk_pixbuf_scale_simple(pixbuf, - settings.max_icon_size, - (int) ((double) settings.max_icon_size / w * h), - GDK_INTERP_BILINEAR); - } else { - scaled = gdk_pixbuf_scale_simple(pixbuf, - (int) ((double) settings.max_icon_size / h * w), - settings.max_icon_size, - GDK_INTERP_BILINEAR); - } - g_object_unref(pixbuf); - pixbuf = scaled; - } - - cl->icon = gdk_pixbuf_to_cairo_surface(pixbuf); - g_object_unref(pixbuf); - } else { - cl->icon = NULL; - } - - if (cl->icon && cairo_surface_status(cl->icon) != CAIRO_STATUS_SUCCESS) { - cairo_surface_destroy(cl->icon); - cl->icon = NULL; - } - - cl->fg = x_string_to_color_t(n->colors[ColFG]); - cl->bg = x_string_to_color_t(n->colors[ColBG]); - cl->frame = x_string_to_color_t(n->colors[ColFrame]); - - cl->n = n; - - dimension_t dim = calculate_dimensions(NULL); - int width = dim.w; - - if (have_dynamic_width()) { - r_setup_pango_layout(cl->l, -1); - } else { - width -= 2 * settings.h_padding; - width -= 2 * settings.frame_width; - if (cl->icon) width -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; - r_setup_pango_layout(cl->l, width); - } - - return cl; -} - -static colored_layout *r_create_layout_for_xmore(cairo_t *c, notification *n, int qlen) -{ - colored_layout *cl = r_init_shared(c, n); - cl->text = g_strdup_printf("(%d more)", qlen); - cl->attr = NULL; - pango_layout_set_text(cl->l, cl->text, -1); - return cl; -} - -static colored_layout *r_create_layout_from_notification(cairo_t *c, notification *n) -{ - - colored_layout *cl = r_init_shared(c, n); - - /* markup */ - GError *err = NULL; - pango_parse_markup(n->text_to_render, -1, 0, &(cl->attr), &(cl->text), NULL, &err); - - if (!err) { - pango_layout_set_text(cl->l, cl->text, -1); - pango_layout_set_attributes(cl->l, cl->attr); - } else { - /* remove markup and display plain message instead */ - n->text_to_render = markup_strip(n->text_to_render); - cl->text = NULL; - cl->attr = NULL; - pango_layout_set_text(cl->l, n->text_to_render, -1); - if (n->first_render) { - LOG_W("Unable to parse markup: %s", err->message); - } - g_error_free(err); - } - - - 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->first_render = false; - return cl; -} - -static GSList *r_create_layouts(cairo_t *c) -{ - GSList *layouts = NULL; - - int qlen = queues_length_waiting(); - bool xmore_is_needed = qlen > 0 && settings.indicate_hidden; - - notification *last = NULL; - for (const GList *iter = queues_get_displayed(); - iter; iter = iter->next) - { - notification *n = iter->data; - last = n; - - notification_update_text_to_render(n); - - if (!iter->next && xmore_is_needed && xctx.geometry.h == 1) { - char *new_ttr = g_strdup_printf("%s (%d more)", n->text_to_render, qlen); - g_free(n->text_to_render); - n->text_to_render = new_ttr; - } - layouts = g_slist_append(layouts, - r_create_layout_from_notification(c, n)); - } - - if (xmore_is_needed && xctx.geometry.h != 1) { - /* append xmore message as new message */ - layouts = g_slist_append(layouts, - r_create_layout_for_xmore(c, last, qlen)); - } - - return layouts; -} - -static void r_free_layouts(GSList *layouts) -{ - g_slist_free_full(layouts, free_colored_layout); -} - -static dimension_t x_render_layout(cairo_t *c, colored_layout *cl, colored_layout *cl_next, dimension_t dim, bool first, bool last) -{ - int h; - int h_text = 0; - pango_layout_get_pixel_size(cl->l, NULL, &h); - if (cl->icon) { - h_text = h; - h = MAX(cairo_image_surface_get_height(cl->icon), h); - } - - int bg_x = 0; - int bg_y = dim.y; - int bg_width = dim.w; - int bg_height = MAX(settings.notification_height, (2 * settings.padding) + h); - double bg_half_height = settings.notification_height/2.0; - int pango_offset = (int) floor(h/2.0); - - if (first) bg_height += settings.frame_width; - if (last) bg_height += settings.frame_width; - else bg_height += settings.separator_height; - - cairo_set_source_rgb(c, cl->frame.r, cl->frame.g, cl->frame.b); - cairo_rectangle(c, bg_x, bg_y, bg_width, bg_height); - cairo_fill(c); - - /* adding frame */ - bg_x += settings.frame_width; - if (first) { - dim.y += settings.frame_width; - bg_y += settings.frame_width; - bg_height -= settings.frame_width; - if (!last) bg_height -= settings.separator_height; - } - bg_width -= 2 * settings.frame_width; - if (last) - bg_height -= settings.frame_width; - - cairo_set_source_rgb(c, cl->bg.r, cl->bg.g, cl->bg.b); - cairo_rectangle(c, bg_x, bg_y, bg_width, bg_height); - cairo_fill(c); - - bool use_padding = settings.notification_height <= (2 * settings.padding) + h; - if (use_padding) - dim.y += settings.padding; - else - dim.y += (int) (ceil(bg_half_height) - pango_offset); - - if (cl->icon && settings.icon_position == icons_left) { - cairo_move_to(c, settings.frame_width + cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding, bg_y + settings.padding + h/2 - h_text/2); - } else if (cl->icon && settings.icon_position == icons_right) { - cairo_move_to(c, settings.frame_width + settings.h_padding, bg_y + settings.padding + h/2 - h_text/2); - } else { - cairo_move_to(c, settings.frame_width + settings.h_padding, bg_y + settings.padding); - } - - cairo_set_source_rgb(c, cl->fg.r, cl->fg.g, cl->fg.b); - pango_cairo_update_layout(c, cl->l); - pango_cairo_show_layout(c, cl->l); - if (use_padding) - dim.y += h + settings.padding; - else - dim.y += (int)(floor(bg_half_height) + pango_offset); - - if (settings.separator_height > 0 && !last) { - color_t sep_color = x_get_separator_color(cl, cl_next); - cairo_set_source_rgb(c, sep_color.r, sep_color.g, sep_color.b); - - if (settings.sep_color == FRAME) - // Draw over the borders on both sides to avoid - // the wrong color in the corners. - cairo_rectangle(c, 0, dim.y, dim.w, settings.separator_height); - else - cairo_rectangle(c, settings.frame_width, dim.y + settings.frame_width, dim.w - 2 * settings.frame_width, settings.separator_height); - - cairo_fill(c); - dim.y += settings.separator_height; - } - cairo_move_to(c, settings.h_padding, dim.y); - - if (cl->icon) { - unsigned int image_width = cairo_image_surface_get_width(cl->icon), - image_height = cairo_image_surface_get_height(cl->icon), - image_x, - image_y = bg_y + settings.padding + h/2 - image_height/2; - - if (settings.icon_position == icons_left) { - image_x = settings.frame_width + settings.h_padding; - } else { - image_x = bg_width - settings.h_padding - image_width + settings.frame_width; - } - - cairo_set_source_surface(c, cl->icon, image_x, image_y); - cairo_rectangle(c, image_x, image_y, image_width, image_height); - cairo_fill(c); - } - - return dim; -} - -void x_win_draw(void) -{ - - GSList *layouts = r_create_layouts(cairo_ctx.context); - - dimension_t dim = calculate_dimensions(layouts); - int width = dim.w; - int height = dim.h; - - cairo_t *c; - cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); - c = cairo_create(image_surface); - - x_win_move(width, height); - cairo_xlib_surface_set_size(cairo_ctx.surface, width, height); - - cairo_move_to(c, 0, 0); - - bool first = true; - for (GSList *iter = layouts; iter; iter = iter->next) { - if (iter->next) - dim = x_render_layout(c, iter->data, iter->next->data, dim, first, iter->next == NULL); - else - dim = x_render_layout(c, iter->data, NULL, dim, first, iter->next == NULL); - - first = false; - } - - cairo_set_source_surface(cairo_ctx.context, image_surface, 0, 0); - cairo_paint(cairo_ctx.context); - cairo_show_page(cairo_ctx.context); - - XFlush(xctx.dpy); - - cairo_destroy(c); - cairo_surface_destroy(image_surface); - r_free_layouts(layouts); -} - -static void x_win_move(int width, int height) +void x_win_move(int width, int height) { int x, y; diff --git a/src/x11/x.h b/src/x11/x.h index b6ac16e..da46389 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -45,7 +45,7 @@ typedef struct _color_t { extern xctx_t xctx; /* window */ -void x_win_draw(void); +void x_win_move(int width, int height); void x_win_hide(void); void x_win_show(void); From 5895593d0bac6861854bbe5e155b0b37590f5067 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Tue, 6 Mar 2018 19:47:48 +0200 Subject: [PATCH 04/38] Remove leftover cairo variables in x.c Clean-up x.c from cairo items that were left over after the move to draw.c. --- src/x11/x.c | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/src/x11/x.c b/src/x11/x.c index c5a5353..1c5fb5a 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -12,11 +12,6 @@ #include #include #include -#include -#include -#include -#include -#include #include #include #include @@ -41,25 +36,6 @@ xctx_t xctx; bool dunst_grab_errored = false; -typedef struct _cairo_ctx { - cairo_status_t status; - cairo_surface_t *surface; - cairo_t *context; - PangoFontDescription *desc; -} cairo_ctx_t; - -typedef struct _colored_layout { - PangoLayout *l; - color_t fg; - color_t bg; - color_t frame; - char *text; - PangoAttrList *attr; - cairo_surface_t *icon; - notification *n; -} colored_layout; - -cairo_ctx_t cairo_ctx; static bool fullscreen_last = false; /* FIXME refactor setup teardown handlers into one setup and one teardown */ @@ -69,16 +45,6 @@ static void setopacity(Window win, unsigned long opacity); static void x_handle_click(XEvent ev); static void x_win_setup(void); -static void x_cairo_setup(void) -{ - cairo_ctx.surface = cairo_xlib_surface_create(xctx.dpy, - xctx.win, DefaultVisual(xctx.dpy, 0), WIDTH, HEIGHT); - - cairo_ctx.context = cairo_create(cairo_ctx.surface); - - cairo_ctx.desc = pango_font_description_from_string(settings.font); -} - cairo_surface_t *x_create_cairo_surface(void) { return cairo_xlib_surface_create(xctx.dpy, @@ -347,9 +313,6 @@ static void x_handle_click(XEvent ev) void x_free(void) { - cairo_surface_destroy(cairo_ctx.surface); - cairo_destroy(cairo_ctx.context); - if (xctx.dpy) XCloseDisplay(xctx.dpy); } @@ -429,7 +392,6 @@ void x_setup(void) init_screens(); x_win_setup(); - x_cairo_setup(); x_shortcut_grab(&settings.history_ks); } From a7fd3cb0ec9255ff5bc13cc614b714e946fda122 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Tue, 6 Mar 2018 19:50:12 +0200 Subject: [PATCH 05/38] Move geometry handling to settings Change the value of geometry in settings from an unparsed string to a struct containing the properly parsed geometry info. Since we depend on X11 for geometry parsing this (unfortunately) introduces an X11 dependency on the settings module, this can only be resolved if we implement our own parsing. --- config.h | 10 ++++++- src/draw.c | 19 +++++++------ src/settings.c | 24 +++++++++++++---- src/settings.h | 14 +++++++++- src/x11/screen.h | 2 -- src/x11/x.c | 69 ++++++++++++++++++++++++++++-------------------- src/x11/x.h | 6 ++++- 7 files changed, 95 insertions(+), 49 deletions(-) diff --git a/config.h b/config.h index fc6a42b..e809ef9 100644 --- a/config.h +++ b/config.h @@ -16,7 +16,15 @@ settings_t defaults = { .icons = { "dialog-information", "dialog-information", "dialog-warning" }, /* low, normal, critical */ .transparency = 0, /* transparency */ -.geom = "0x0", /* geometry */ +.geometry = { .x = 0, /* geometry */ + .y = 0, + .w = 0, + .h = 0, + .negative_x = 0, + .negative_y = 0, + .negative_width = 0, + .width_set = 0 + }, .title = "Dunst", /* the title of dunst notification windows */ .class = "Dunst", /* the class of dunst notification windows */ .shrink = false, /* shrinking */ diff --git a/src/draw.c b/src/draw.c index 5306a2a..1e0899d 100644 --- a/src/draw.c +++ b/src/draw.c @@ -149,7 +149,7 @@ static void free_colored_layout(void *data) static bool have_dynamic_width(void) { - return (xctx.geometry.mask & WidthValue && xctx.geometry.w == 0); + return (settings.geometry.width_set && settings.geometry.w == 0); } static dimension_t calculate_dimensions(GSList *layouts) @@ -159,18 +159,17 @@ static dimension_t calculate_dimensions(GSList *layouts) dim.h = 0; dim.x = 0; dim.y = 0; - dim.mask = xctx.geometry.mask; screen_info *scr = get_active_screen(); if (have_dynamic_width()) { /* dynamic width */ dim.w = 0; - } else if (xctx.geometry.mask & WidthValue) { + } else if (settings.geometry.width_set) { /* fixed width */ - if (xctx.geometry.negative_width) { - dim.w = scr->dim.w - xctx.geometry.w; + if (settings.geometry.negative_width) { + dim.w = scr->dim.w - settings.geometry.w; } else { - dim.w = xctx.geometry.w; + dim.w = settings.geometry.w; } } else { /* across the screen */ @@ -202,8 +201,8 @@ static dimension_t calculate_dimensions(GSList *layouts) if (total_width > scr->dim.w) { /* set width to screen width */ - dim.w = scr->dim.w - xctx.geometry.x * 2; - } else if (have_dynamic_width() || (total_width < xctx.geometry.w && settings.shrink)) { + dim.w = scr->dim.w - settings.geometry.x * 2; + } else if (have_dynamic_width() || (total_width < settings.geometry.w && settings.shrink)) { /* set width to text width */ dim.w = total_width + 2 * settings.frame_width; } @@ -394,7 +393,7 @@ static GSList *r_create_layouts(cairo_t *c) notification_update_text_to_render(n); - if (!iter->next && xmore_is_needed && xctx.geometry.h == 1) { + if (!iter->next && xmore_is_needed && settings.geometry.h == 1) { char *new_ttr = g_strdup_printf("%s (%d more)", n->text_to_render, qlen); g_free(n->text_to_render); n->text_to_render = new_ttr; @@ -403,7 +402,7 @@ static GSList *r_create_layouts(cairo_t *c) r_create_layout_from_notification(c, n)); } - if (xmore_is_needed && xctx.geometry.h != 1) { + if (xmore_is_needed && settings.geometry.h != 1) { /* append xmore message as new message */ layouts = g_slist_append(layouts, r_create_layout_for_xmore(c, last, qlen)); diff --git a/src/settings.c b/src/settings.c index aa0109d..6e31d28 100644 --- a/src/settings.c +++ b/src/settings.c @@ -17,6 +17,7 @@ #include "notification.h" #include "option_parser.h" #include "utils.h" +#include "x11/x.h" settings_t settings; @@ -256,11 +257,24 @@ void load_settings(char *cmdline_config_path) "Define the class of windows spawned by dunst." ); - settings.geom = option_get_string( - "global", - "geometry", "-geom/-geometry", defaults.geom, - "Geometry for the window" - ); + { + + char *c = option_get_string( + "global", + "geometry", "-geom/-geometry", NULL, + "Geometry for the window" + ); + + if (c) { + // TODO: Implement own geometry parsing to get rid of + // the include dependency on X11 + settings.geometry = x_parse_geometry(c); + g_free(c); + } else { + settings.geometry = defaults.geometry; + } + + } settings.shrink = option_get_bool( "global", diff --git a/src/settings.h b/src/settings.h index 0577f42..50eb1c6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -13,6 +13,18 @@ enum separator_color { FOREGROUND, AUTO, FRAME, CUSTOM }; enum follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD }; enum markup_mode { MARKUP_NULL, MARKUP_NO, MARKUP_STRIP, MARKUP_FULL }; +struct geometry { + int x; + int y; + unsigned int w; + unsigned int h; + bool negative_x; + bool negative_y; + bool negative_width; + bool width_set; + +}; + typedef struct _settings { bool print_notifications; bool per_monitor_dpi; @@ -33,7 +45,7 @@ typedef struct _settings { gint64 timeouts[3]; char *icons[3]; unsigned int transparency; - char *geom; + struct geometry geometry; char *title; char *class; int shrink; diff --git a/src/x11/screen.h b/src/x11/screen.h index fcd020d..33bd227 100644 --- a/src/x11/screen.h +++ b/src/x11/screen.h @@ -13,8 +13,6 @@ typedef struct _dimension_t { unsigned int h; unsigned int mmh; unsigned int w; - int mask; - int negative_width; } dimension_t; typedef struct _screen_info { diff --git a/src/x11/x.c b/src/x11/x.c index 1c5fb5a..34375b5 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -58,16 +58,16 @@ void x_win_move(int width, int height) screen_info *scr = get_active_screen(); xctx.cur_screen = scr->scr; /* calculate window position */ - if (xctx.geometry.mask & XNegative) { - x = (scr->dim.x + (scr->dim.w - width)) + xctx.geometry.x; + if (settings.geometry.negative_x) { + x = (scr->dim.x + (scr->dim.w - width)) + settings.geometry.x; } else { - x = scr->dim.x + xctx.geometry.x; + x = scr->dim.x + settings.geometry.x; } - if (xctx.geometry.mask & YNegative) { - y = scr->dim.y + (scr->dim.h + xctx.geometry.y) - height; + if (settings.geometry.negative_y) { + y = scr->dim.y + (scr->dim.h + settings.geometry.y) - height; } else { - y = scr->dim.y + xctx.geometry.y; + y = scr->dim.y + settings.geometry.y; } /* move and resize */ @@ -365,29 +365,6 @@ void x_setup(void) else xctx.colors[ColFrame][URG_CRIT] = settings.frame_color; - /* parse and set xctx.geometry and monitor position */ - if (settings.geom[0] == '-') { - xctx.geometry.negative_width = true; - settings.geom++; - } else { - xctx.geometry.negative_width = false; - } - - xctx.geometry.mask = XParseGeometry(settings.geom, - &xctx.geometry.x, &xctx.geometry.y, - &xctx.geometry.w, &xctx.geometry.h); - - /* calculate maximum notification count and push information to queue */ - if (xctx.geometry.h == 0) { - queues_displayed_limit(0); - } else if (xctx.geometry.h == 1) { - queues_displayed_limit(1); - } else if (settings.indicate_hidden) { - queues_displayed_limit(xctx.geometry.h - 1); - } else { - queues_displayed_limit(xctx.geometry.h); - } - xctx.screensaver_info = XScreenSaverAllocInfo(); init_screens(); @@ -395,6 +372,40 @@ void x_setup(void) x_shortcut_grab(&settings.history_ks); } +struct geometry x_parse_geometry(const char *geom_str) +{ + assert(geom_str); + + if (geom_str[0] == '-') { + settings.geometry.negative_width = true; + geom_str++; + } else { + settings.geometry.negative_width = false; + } + + struct geometry geometry = { 0 }; + + int mask = XParseGeometry(geom_str, + &geometry.x, &geometry.y, + &geometry.w, &geometry.h); + geometry.width_set = mask & WidthValue; + geometry.negative_x = mask & XNegative; + geometry.negative_y = mask & YNegative; + + /* calculate maximum notification count and push information to queue */ + if (geometry.h == 0) { + queues_displayed_limit(0); + } else if (geometry.h == 1) { + queues_displayed_limit(1); + } else if (settings.indicate_hidden) { + queues_displayed_limit(geometry.h - 1); + } else { + queues_displayed_limit(geometry.h); + } + + return geometry; +} + static void x_set_wm(Window win) { diff --git a/src/x11/x.h b/src/x11/x.h index da46389..96dd07c 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -23,13 +23,15 @@ typedef struct _keyboard_shortcut { bool is_valid; } keyboard_shortcut; +// Cyclical dependency +#include "src/settings.h" + typedef struct _xctx { Atom utf8; Display *dpy; int cur_screen; Window win; bool visible; - dimension_t geometry; const char *colors[3][3]; XScreenSaverInfo *screensaver_info; dimension_t window_dim; @@ -60,6 +62,8 @@ bool x_is_idle(void); void x_setup(void); void x_free(void); +struct geometry x_parse_geometry(const char *geom_str); + cairo_surface_t *x_create_cairo_surface(void); gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, From 651be8eee9fecf55029ecc8375b9bb9488eea872 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Thu, 8 Mar 2018 18:25:56 +0200 Subject: [PATCH 06/38] Avoid using dimensions struct for unrelated uses The `dimension_t` struct was used in many different places and even held on the geometry info at some point which is counter-intuitive as it's best to have each struct serve a single purpose. --- src/draw.c | 22 +++++++++------------- src/x11/screen.c | 32 ++++++++++++++++---------------- src/x11/screen.h | 8 ++------ src/x11/x.c | 39 +++++++++++++++++++-------------------- src/x11/x.h | 8 +++++++- 5 files changed, 53 insertions(+), 56 deletions(-) diff --git a/src/draw.c b/src/draw.c index 1e0899d..7b8bd72 100644 --- a/src/draw.c +++ b/src/draw.c @@ -152,13 +152,9 @@ static bool have_dynamic_width(void) return (settings.geometry.width_set && settings.geometry.w == 0); } -static dimension_t calculate_dimensions(GSList *layouts) +static struct dimensions calculate_dimensions(GSList *layouts) { - dimension_t dim; - dim.w = 0; - dim.h = 0; - dim.x = 0; - dim.y = 0; + struct dimensions dim = { 0 }; screen_info *scr = get_active_screen(); if (have_dynamic_width()) { @@ -167,13 +163,13 @@ static dimension_t calculate_dimensions(GSList *layouts) } else if (settings.geometry.width_set) { /* fixed width */ if (settings.geometry.negative_width) { - dim.w = scr->dim.w - settings.geometry.w; + dim.w = scr->w - settings.geometry.w; } else { dim.w = settings.geometry.w; } } else { /* across the screen */ - dim.w = scr->dim.w; + dim.w = scr->w; } dim.h += 2 * settings.frame_width; @@ -199,9 +195,9 @@ static dimension_t calculate_dimensions(GSList *layouts) /* subtract height from the unwrapped text */ dim.h -= h; - if (total_width > scr->dim.w) { + if (total_width > scr->w) { /* set width to screen width */ - dim.w = scr->dim.w - settings.geometry.x * 2; + dim.w = scr->w - settings.geometry.x * 2; } else if (have_dynamic_width() || (total_width < settings.geometry.w && settings.shrink)) { /* set width to text width */ dim.w = total_width + 2 * settings.frame_width; @@ -320,7 +316,7 @@ static colored_layout *r_init_shared(cairo_t *c, notification *n) cl->n = n; - dimension_t dim = calculate_dimensions(NULL); + struct dimensions dim = calculate_dimensions(NULL); int width = dim.w; if (have_dynamic_width()) { @@ -416,7 +412,7 @@ static void r_free_layouts(GSList *layouts) g_slist_free_full(layouts, free_colored_layout); } -static dimension_t x_render_layout(cairo_t *c, colored_layout *cl, colored_layout *cl_next, dimension_t dim, bool first, bool last) +static struct dimensions x_render_layout(cairo_t *c, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) { int h; int h_text = 0; @@ -520,7 +516,7 @@ void draw(void) GSList *layouts = r_create_layouts(c_context); - dimension_t dim = calculate_dimensions(layouts); + struct dimensions dim = calculate_dimensions(layouts); int width = dim.w; int height = dim.h; diff --git a/src/x11/screen.c b/src/x11/screen.c index 2bd37b8..e18fd51 100644 --- a/src/x11/screen.c +++ b/src/x11/screen.c @@ -126,12 +126,12 @@ void randr_update(void) alloc_screen_ar(n); for (int i = 0; i < n; i++) { - screens[i].scr = i; - screens[i].dim.x = m[i].x; - screens[i].dim.y = m[i].y; - screens[i].dim.w = m[i].width; - screens[i].dim.h = m[i].height; - screens[i].dim.mmh = m[i].mheight; + screens[i].id = i; + screens[i].x = m[i].x; + screens[i].y = m[i].y; + screens[i].w = m[i].width; + screens[i].h = m[i].height; + screens[i].mmh = m[i].mheight; } XRRFreeMonitors(m); @@ -139,7 +139,7 @@ void randr_update(void) static int autodetect_dpi(screen_info *scr) { - return (double)scr->dim.h * 25.4 / (double)scr->dim.mmh; + return (double)scr->h * 25.4 / (double)scr->mmh; } void screen_check_event(XEvent event) @@ -165,11 +165,11 @@ void xinerama_update(void) alloc_screen_ar(n); for (int i = 0; i < n; i++) { - screens[i].scr = i; - screens[i].dim.x = info[i].x_org; - screens[i].dim.y = info[i].y_org; - screens[i].dim.h = info[i].height; - screens[i].dim.w = info[i].width; + screens[i].id = i; + screens[i].x = info[i].x_org; + screens[i].y = info[i].y_org; + screens[i].h = info[i].height; + screens[i].w = info[i].width; } XFree(info); } @@ -184,8 +184,8 @@ void screen_update_fallback(void) else screen = DefaultScreen(xctx.dpy); - screens[0].dim.w = DisplayWidth(xctx.dpy, screen); - screens[0].dim.h = DisplayHeight(xctx.dpy, screen); + screens[0].w = DisplayWidth(xctx.dpy, screen); + screens[0].h = DisplayHeight(xctx.dpy, screen); } /* see screen.h */ @@ -328,8 +328,8 @@ screen_info *get_active_screen(void) } for (int i = 0; i < screens_len; i++) { - if (INRECT(x, y, screens[i].dim.x, screens[i].dim.y, - screens[i].dim.w, screens[i].dim.h)) { + if (INRECT(x, y, screens[i].x, screens[i].y, + screens[i].w, screens[i].h)) { ret = i; } } diff --git a/src/x11/screen.h b/src/x11/screen.h index 33bd227..1188299 100644 --- a/src/x11/screen.h +++ b/src/x11/screen.h @@ -7,17 +7,13 @@ #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) -typedef struct _dimension_t { +typedef struct { + int id; int x; int y; unsigned int h; unsigned int mmh; unsigned int w; -} dimension_t; - -typedef struct _screen_info { - int scr; - dimension_t dim; } screen_info; void init_screens(void); diff --git a/src/x11/x.c b/src/x11/x.c index 34375b5..6b48a16 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -53,35 +53,39 @@ cairo_surface_t *x_create_cairo_surface(void) void x_win_move(int width, int height) { + // Previous dimensions of the window to avoid calling X11 if no change + // is needed + static struct dimensions window_dim = { 0 }; int x, y; screen_info *scr = get_active_screen(); - xctx.cur_screen = scr->scr; + xctx.cur_screen = scr->id; + /* calculate window position */ if (settings.geometry.negative_x) { - x = (scr->dim.x + (scr->dim.w - width)) + settings.geometry.x; + x = (scr->x + (scr->w - width)) + settings.geometry.x; } else { - x = scr->dim.x + settings.geometry.x; + x = scr->x + settings.geometry.x; } if (settings.geometry.negative_y) { - y = scr->dim.y + (scr->dim.h + settings.geometry.y) - height; + y = scr->y + (scr->h + settings.geometry.y) - height; } else { - y = scr->dim.y + settings.geometry.y; + y = scr->y + settings.geometry.y; } /* move and resize */ - if (x != xctx.window_dim.x || y != xctx.window_dim.y) { + if (x != window_dim.x || y != window_dim.y) { XMoveWindow(xctx.dpy, xctx.win, x, y); } - if (width != xctx.window_dim.w || height != xctx.window_dim.h) { + if (width != window_dim.w || height != window_dim.h) { XResizeWindow(xctx.dpy, xctx.win, width, height); } - xctx.window_dim.x = x; - xctx.window_dim.y = y; - xctx.window_dim.h = height; - xctx.window_dim.w = width; + window_dim.x = x; + window_dim.y = y; + window_dim.h = height; + window_dim.w = width; } static void setopacity(Window win, unsigned long opacity) @@ -250,7 +254,7 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer * to detect a focus change to another screen */ && xctx.visible - && get_active_screen()->scr != xctx.cur_screen) { + && get_active_screen()->id != xctx.cur_screen) { draw(); } break; @@ -467,11 +471,6 @@ static void x_win_setup(void) Window root; XSetWindowAttributes wa; - xctx.window_dim.x = 0; - xctx.window_dim.y = 0; - xctx.window_dim.w = 0; - xctx.window_dim.h = 0; - root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); xctx.utf8 = XInternAtom(xctx.dpy, "UTF8_STRING", false); @@ -484,9 +483,9 @@ static void x_win_setup(void) screen_info *scr = get_active_screen(); xctx.win = XCreateWindow(xctx.dpy, root, - scr->dim.x, - scr->dim.y, - scr->dim.w, + scr->x, + scr->y, + scr->w, 1, 0, DefaultDepth(xctx.dpy, DefaultScreen(xctx.dpy)), diff --git a/src/x11/x.h b/src/x11/x.h index 96dd07c..a2afde7 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -26,6 +26,13 @@ typedef struct _keyboard_shortcut { // Cyclical dependency #include "src/settings.h" +struct dimensions { + int x; + int y; + int w; + int h; +}; + typedef struct _xctx { Atom utf8; Display *dpy; @@ -34,7 +41,6 @@ typedef struct _xctx { bool visible; const char *colors[3][3]; XScreenSaverInfo *screensaver_info; - dimension_t window_dim; unsigned long sep_custom_col; } xctx_t; From 3f7de41732b42fbedb6e5a0643f9966be4dc1298 Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Sat, 10 Mar 2018 14:38:38 +0100 Subject: [PATCH 07/38] Add tests for icon.c --- .valgrind.suppressions | 29 ++++++++ test/data/icons/invalid.png | 1 + test/data/icons/invalid.svg | 1 + test/data/icons/path/invalid/icon1.png | 1 + test/data/icons/path/invalid/icon1.svg | 1 + test/data/icons/path/valid/icon1.png | 1 + test/data/icons/path/valid/icon1.svg | 1 + test/data/icons/path/valid/onlypng.png | 1 + test/data/icons/path/valid/onlysvg.svg | 1 + test/data/icons/valid.png | Bin 0 -> 193 bytes test/data/icons/valid.svg | 65 ++++++++++++++++ test/icon.c | 98 +++++++++++++++++++++++++ test/notification.c | 2 + test/test.c | 2 + 14 files changed, 204 insertions(+) create mode 100644 test/data/icons/invalid.png create mode 100644 test/data/icons/invalid.svg create mode 120000 test/data/icons/path/invalid/icon1.png create mode 120000 test/data/icons/path/invalid/icon1.svg create mode 120000 test/data/icons/path/valid/icon1.png create mode 120000 test/data/icons/path/valid/icon1.svg create mode 120000 test/data/icons/path/valid/onlypng.png create mode 120000 test/data/icons/path/valid/onlysvg.svg create mode 100644 test/data/icons/valid.png create mode 100644 test/data/icons/valid.svg create mode 100644 test/icon.c diff --git a/.valgrind.suppressions b/.valgrind.suppressions index de42697..2b694ff 100644 --- a/.valgrind.suppressions +++ b/.valgrind.suppressions @@ -8,3 +8,32 @@ ... fun:main } + +# librsvg leaks some memory, when an invalid svg file is read +# TODO: find the memory leak and fix it upstream +{ + invalid_svgs1 + Memcheck:Leak + ... + fun:gdk_pixbuf__svg_image_load_increment + ... + fun:get_pixbuf_from_file +} + +{ + invalid_svgs2 + Memcheck:Leak + ... + fun:gdk_pixbuf__svg_image_begin_load + ... + fun:get_pixbuf_from_file +} + +{ + invalid_svgs3 + Memcheck:Leak + ... + fun:rsvg_handle_write + ... + fun:get_pixbuf_from_file +} diff --git a/test/data/icons/invalid.png b/test/data/icons/invalid.png new file mode 100644 index 0000000..07623af --- /dev/null +++ b/test/data/icons/invalid.png @@ -0,0 +1 @@ +Got'cha! This has to be invalid! diff --git a/test/data/icons/invalid.svg b/test/data/icons/invalid.svg new file mode 100644 index 0000000..07623af --- /dev/null +++ b/test/data/icons/invalid.svg @@ -0,0 +1 @@ +Got'cha! This has to be invalid! diff --git a/test/data/icons/path/invalid/icon1.png b/test/data/icons/path/invalid/icon1.png new file mode 120000 index 0000000..d6b006b --- /dev/null +++ b/test/data/icons/path/invalid/icon1.png @@ -0,0 +1 @@ +../../invalid.png \ No newline at end of file diff --git a/test/data/icons/path/invalid/icon1.svg b/test/data/icons/path/invalid/icon1.svg new file mode 120000 index 0000000..b5f5825 --- /dev/null +++ b/test/data/icons/path/invalid/icon1.svg @@ -0,0 +1 @@ +../../invalid.svg \ No newline at end of file diff --git a/test/data/icons/path/valid/icon1.png b/test/data/icons/path/valid/icon1.png new file mode 120000 index 0000000..28a474c --- /dev/null +++ b/test/data/icons/path/valid/icon1.png @@ -0,0 +1 @@ +../../valid.png \ No newline at end of file diff --git a/test/data/icons/path/valid/icon1.svg b/test/data/icons/path/valid/icon1.svg new file mode 120000 index 0000000..95267e3 --- /dev/null +++ b/test/data/icons/path/valid/icon1.svg @@ -0,0 +1 @@ +../../valid.svg \ No newline at end of file diff --git a/test/data/icons/path/valid/onlypng.png b/test/data/icons/path/valid/onlypng.png new file mode 120000 index 0000000..28a474c --- /dev/null +++ b/test/data/icons/path/valid/onlypng.png @@ -0,0 +1 @@ +../../valid.png \ No newline at end of file diff --git a/test/data/icons/path/valid/onlysvg.svg b/test/data/icons/path/valid/onlysvg.svg new file mode 120000 index 0000000..95267e3 --- /dev/null +++ b/test/data/icons/path/valid/onlysvg.svg @@ -0,0 +1 @@ +../../valid.svg \ No newline at end of file diff --git a/test/data/icons/valid.png b/test/data/icons/valid.png new file mode 100644 index 0000000000000000000000000000000000000000..014e6a7d318b3c524d84741683be66f10881c346 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=*pj^6T^Rm@;DWu&Cj&(|3p^r= z85p>QL70(Y)*K0-AbW|YuPgf_W-cyy?iW!vVu3=knIRD+&iT2ysd*(pE(3#eQEFmI zYKlU6W=V#EyQgnJie4%^P+Zm1#W95Ay6LHnybKBqhYhCxuh(Nv?Ov)8y42NwAL}O> couquT*e!nqerF%P8UQlM)78&qol`;+0G19l%K!iX literal 0 HcmV?d00001 diff --git a/test/data/icons/valid.svg b/test/data/icons/valid.svg new file mode 100644 index 0000000..7a1cb7f --- /dev/null +++ b/test/data/icons/valid.svg @@ -0,0 +1,65 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/test/icon.c b/test/icon.c new file mode 100644 index 0000000..56abb07 --- /dev/null +++ b/test/icon.c @@ -0,0 +1,98 @@ +#include "greatest.h" +#include "../src/icon.h" +#include "../src/utils.h" + +#include +#include + +#define ICONPREFIX "./data/icons/path" + +/* As there are no hints to test if the loaded GdkPixbuf is + * read from a PNG or an SVG file, the sample icons in the + * test structure have different sizes + */ +#define IS_ICON_PNG(pb) 4 == gdk_pixbuf_get_width(pb) +#define IS_ICON_SVG(pb) 16 == gdk_pixbuf_get_width(pb) + +TEST test_get_pixbuf_from_path_invalid(void) +{ + GdkPixbuf *pixbuf = get_pixbuf_from_path("invalid"); + ASSERT(pixbuf == NULL); + g_clear_pointer(&pixbuf, g_object_unref); + + PASS(); +} + +TEST test_get_pixbuf_from_path_both(void) +{ + GdkPixbuf *pixbuf = get_pixbuf_from_path("icon1"); + ASSERT(pixbuf); + ASSERTm("SVG pixbuf hasn't precedence", IS_ICON_SVG(pixbuf)); + g_clear_pointer(&pixbuf, g_object_unref); + + PASS(); +} + +TEST test_get_pixbuf_from_path_onlysvg(void) +{ + GdkPixbuf *pixbuf = get_pixbuf_from_path("onlysvg"); + ASSERT(pixbuf); + ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf)); + g_clear_pointer(&pixbuf, g_object_unref); + + PASS(); +} + +TEST test_get_pixbuf_from_path_onlypng(void) +{ + GdkPixbuf *pixbuf = get_pixbuf_from_path("onlypng"); + ASSERT(pixbuf); + ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf)); + g_clear_pointer(&pixbuf, g_object_unref); + + PASS(); +} + +TEST test_get_pixbuf_from_path_filename(void) +{ + char *icon = string_append(g_get_current_dir(), "/data/icons/valid.png", NULL); + GdkPixbuf *pixbuf = get_pixbuf_from_path(icon); + ASSERT(pixbuf); + ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf)); + g_clear_pointer(&pixbuf, g_object_unref); + + g_free(icon); + PASS(); +} + +TEST test_get_pixbuf_from_path_fileuri(void) +{ + char *curdir = g_get_current_dir(); + char *icon = g_strconcat("file://", curdir,"/data/icons/valid.svg", NULL); + GdkPixbuf *pixbuf = get_pixbuf_from_path(icon); + ASSERT(pixbuf); + ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf)); + g_clear_pointer(&pixbuf, g_object_unref); + + g_free(icon); + g_free(curdir); + PASS(); +} + +SUITE(suite_icon) +{ + settings.icon_path = + ICONPREFIX "/invalid" + ":" ICONPREFIX "/valid" + ":" ICONPREFIX "/both"; + + RUN_TEST(test_get_pixbuf_from_path_invalid); + RUN_TEST(test_get_pixbuf_from_path_both); + RUN_TEST(test_get_pixbuf_from_path_onlysvg); + RUN_TEST(test_get_pixbuf_from_path_onlypng); + RUN_TEST(test_get_pixbuf_from_path_filename); + RUN_TEST(test_get_pixbuf_from_path_fileuri); + + settings.icon_path = NULL; +} +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/test/notification.c b/test/notification.c index c4e3494..a9c02d6 100644 --- a/test/notification.c +++ b/test/notification.c @@ -120,6 +120,8 @@ SUITE(suite_notification) g_free(b); RUN_TEST(test_notification_replace_single_field); + + g_clear_pointer(&settings.icon_path, g_free); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/test/test.c b/test/test.c index 935bda9..09835fd 100644 --- a/test/test.c +++ b/test/test.c @@ -8,6 +8,7 @@ SUITE_EXTERN(suite_utils); SUITE_EXTERN(suite_option_parser); SUITE_EXTERN(suite_notification); SUITE_EXTERN(suite_markup); +SUITE_EXTERN(suite_icon); GREATEST_MAIN_DEFS(); @@ -20,6 +21,7 @@ int main(int argc, char *argv[]) { RUN_SUITE(suite_option_parser); RUN_SUITE(suite_notification); RUN_SUITE(suite_markup); + RUN_SUITE(suite_icon); GREATEST_MAIN_END(); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ From 3bfc77864f79ad38cadd276c6b3a690753e34605 Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Sat, 10 Mar 2018 14:59:05 +0100 Subject: [PATCH 08/38] Refactor GdkPixbuf loading Rename both functions to make clear, what the difference between the previous functions _from_file and _from_path is. Also remove the superfluous `== NULL` checks as these don't match dunst's current style. --- src/draw.c | 12 +++---- src/icon.c | 94 +++++++++++++++++++++++++---------------------------- src/icon.h | 14 +++++++- test/icon.c | 36 ++++++++++---------- 4 files changed, 81 insertions(+), 75 deletions(-) diff --git a/src/draw.c b/src/draw.c index 7b8bd72..7059830 100644 --- a/src/draw.c +++ b/src/draw.c @@ -269,13 +269,11 @@ static colored_layout *r_init_shared(cairo_t *c, notification *n) GdkPixbuf *pixbuf = NULL; - if (n->raw_icon && - settings.icon_position != icons_off) { - - pixbuf = get_pixbuf_from_raw_image(n->raw_icon); - - } else if (n->icon && settings.icon_position != icons_off) { - pixbuf = get_pixbuf_from_path(n->icon); + if (settings.icon_position != icons_off) { + if (n->raw_icon) + pixbuf = get_pixbuf_from_raw_image(n->raw_icon); + else if (n->icon) + pixbuf = get_pixbuf_from_icon(n->icon); } if (pixbuf != NULL) { diff --git a/src/icon.c b/src/icon.c index 2fa6eae..2d2bd44 100644 --- a/src/icon.c +++ b/src/icon.c @@ -9,11 +9,6 @@ #include "notification.h" #include "settings.h" -static bool does_file_exist(const char *filename) -{ - return (access(filename, F_OK) != -1); -} - static bool is_readable_file(const char *filename) { return (access(filename, R_OK) != -1); @@ -62,67 +57,68 @@ cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf) return icon_surface; } -static GdkPixbuf *get_pixbuf_from_file(const char *icon_path) +static GdkPixbuf *get_pixbuf_from_file(const char *filename) { GdkPixbuf *pixbuf = NULL; - if (is_readable_file(icon_path)) { + if (is_readable_file(filename)) { GError *error = NULL; - pixbuf = gdk_pixbuf_new_from_file(icon_path, &error); - if (pixbuf == NULL) + pixbuf = gdk_pixbuf_new_from_file(filename, &error); + if (!pixbuf) g_error_free(error); } return pixbuf; } -GdkPixbuf *get_pixbuf_from_path(char *icon_path) +GdkPixbuf *get_pixbuf_from_icon(const char *iconname) { + if (!iconname || iconname[0] == '\0') + return NULL; + + const char *suffixes[] = { ".svg", ".png", NULL }; GdkPixbuf *pixbuf = NULL; gchar *uri_path = NULL; - if (strlen(icon_path) > 0) { - if (g_str_has_prefix(icon_path, "file://")) { - uri_path = g_filename_from_uri(icon_path, NULL, NULL); - if (uri_path != NULL) { - icon_path = uri_path; - } - } - /* absolute path? */ - if (icon_path[0] == '/' || icon_path[0] == '~') { - pixbuf = get_pixbuf_from_file(icon_path); - } - /* search in icon_path */ - if (pixbuf == NULL) { - char *start = settings.icon_path, - *end, *current_folder, *maybe_icon_path; - do { - end = strchr(start, ':'); - if (end == NULL) end = strchr(settings.icon_path, '\0'); /* end = end of string */ - current_folder = g_strndup(start, end - start); - /* try svg */ - maybe_icon_path = g_strconcat(current_folder, "/", icon_path, ".svg", NULL); - if (!does_file_exist(maybe_icon_path)) { - g_free(maybe_icon_path); - /* fallback to png */ - maybe_icon_path = g_strconcat(current_folder, "/", icon_path, ".png", NULL); - } - g_free(current_folder); + if (g_str_has_prefix(iconname, "file://")) { + uri_path = g_filename_from_uri(iconname, NULL, NULL); + if (uri_path) + iconname = uri_path; + } + /* absolute path? */ + if (iconname[0] == '/' || iconname[0] == '~') { + pixbuf = get_pixbuf_from_file(iconname); + } + + /* search in icon_path */ + if (!pixbuf) { + char *start = settings.icon_path, + *end, *current_folder, *maybe_icon_path; + do { + end = strchr(start, ':'); + if (!end) end = strchr(settings.icon_path, '\0'); /* end = end of string */ + + current_folder = g_strndup(start, end - start); + + for (const char **suf = suffixes; *suf; suf++) { + maybe_icon_path = g_strconcat(current_folder, "/", iconname, *suf, NULL); pixbuf = get_pixbuf_from_file(maybe_icon_path); g_free(maybe_icon_path); - if (pixbuf != NULL) { - return pixbuf; - } - start = end + 1; - } while (*(end) != '\0'); - } - if (pixbuf == NULL) { - LOG_W("Could not load icon: '%s'", icon_path); - } - if (uri_path != NULL) { - g_free(uri_path); - } + if (pixbuf) + break; + } + + g_free(current_folder); + if (pixbuf) + break; + + start = end + 1; + } while (*(end) != '\0'); } + if (!pixbuf) + LOG_W("Could not load icon: '%s'", iconname); + + g_free(uri_path); return pixbuf; } diff --git a/src/icon.h b/src/icon.h index 838a0dc..dcd6f3d 100644 --- a/src/icon.h +++ b/src/icon.h @@ -7,7 +7,19 @@ #include "notification.h" cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf); -GdkPixbuf *get_pixbuf_from_path(char *icon_path); + +/** Retrieve an icon by its name sent via the notification bus + * + * @param iconname A string describing a `file://` URL, an arbitary filename + * or an icon name, which then gets searched for in the + * settings.icon_path + * + * @return an instance of `GdkPixbuf` or `NULL` if not found + */ +GdkPixbuf *get_pixbuf_from_icon(const char *iconname); + +/** Convert a RawImage to a `GdkPixbuf` + */ GdkPixbuf *get_pixbuf_from_raw_image(const RawImage *raw_image); #endif diff --git a/test/icon.c b/test/icon.c index 56abb07..5695a82 100644 --- a/test/icon.c +++ b/test/icon.c @@ -14,18 +14,18 @@ #define IS_ICON_PNG(pb) 4 == gdk_pixbuf_get_width(pb) #define IS_ICON_SVG(pb) 16 == gdk_pixbuf_get_width(pb) -TEST test_get_pixbuf_from_path_invalid(void) +TEST test_get_pixbuf_from_icon_invalid(void) { - GdkPixbuf *pixbuf = get_pixbuf_from_path("invalid"); + GdkPixbuf *pixbuf = get_pixbuf_from_icon("invalid"); ASSERT(pixbuf == NULL); g_clear_pointer(&pixbuf, g_object_unref); PASS(); } -TEST test_get_pixbuf_from_path_both(void) +TEST test_get_pixbuf_from_icon_both(void) { - GdkPixbuf *pixbuf = get_pixbuf_from_path("icon1"); + GdkPixbuf *pixbuf = get_pixbuf_from_icon("icon1"); ASSERT(pixbuf); ASSERTm("SVG pixbuf hasn't precedence", IS_ICON_SVG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); @@ -33,9 +33,9 @@ TEST test_get_pixbuf_from_path_both(void) PASS(); } -TEST test_get_pixbuf_from_path_onlysvg(void) +TEST test_get_pixbuf_from_icon_onlysvg(void) { - GdkPixbuf *pixbuf = get_pixbuf_from_path("onlysvg"); + GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlysvg"); ASSERT(pixbuf); ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); @@ -43,9 +43,9 @@ TEST test_get_pixbuf_from_path_onlysvg(void) PASS(); } -TEST test_get_pixbuf_from_path_onlypng(void) +TEST test_get_pixbuf_from_icon_onlypng(void) { - GdkPixbuf *pixbuf = get_pixbuf_from_path("onlypng"); + GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng"); ASSERT(pixbuf); ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); @@ -53,10 +53,10 @@ TEST test_get_pixbuf_from_path_onlypng(void) PASS(); } -TEST test_get_pixbuf_from_path_filename(void) +TEST test_get_pixbuf_from_icon_filename(void) { char *icon = string_append(g_get_current_dir(), "/data/icons/valid.png", NULL); - GdkPixbuf *pixbuf = get_pixbuf_from_path(icon); + GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon); ASSERT(pixbuf); ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); @@ -65,11 +65,11 @@ TEST test_get_pixbuf_from_path_filename(void) PASS(); } -TEST test_get_pixbuf_from_path_fileuri(void) +TEST test_get_pixbuf_from_icon_fileuri(void) { char *curdir = g_get_current_dir(); char *icon = g_strconcat("file://", curdir,"/data/icons/valid.svg", NULL); - GdkPixbuf *pixbuf = get_pixbuf_from_path(icon); + GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon); ASSERT(pixbuf); ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf)); g_clear_pointer(&pixbuf, g_object_unref); @@ -86,12 +86,12 @@ SUITE(suite_icon) ":" ICONPREFIX "/valid" ":" ICONPREFIX "/both"; - RUN_TEST(test_get_pixbuf_from_path_invalid); - RUN_TEST(test_get_pixbuf_from_path_both); - RUN_TEST(test_get_pixbuf_from_path_onlysvg); - RUN_TEST(test_get_pixbuf_from_path_onlypng); - RUN_TEST(test_get_pixbuf_from_path_filename); - RUN_TEST(test_get_pixbuf_from_path_fileuri); + RUN_TEST(test_get_pixbuf_from_icon_invalid); + RUN_TEST(test_get_pixbuf_from_icon_both); + RUN_TEST(test_get_pixbuf_from_icon_onlysvg); + RUN_TEST(test_get_pixbuf_from_icon_onlypng); + RUN_TEST(test_get_pixbuf_from_icon_filename); + RUN_TEST(test_get_pixbuf_from_icon_fileuri); settings.icon_path = NULL; } From f02626e1a7f8820b0b290f500f4ff606d93fe4ec Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Mon, 19 Mar 2018 08:36:22 +0200 Subject: [PATCH 09/38] Increase the number of entries in the stack trace in test-valgrind The default seems to hide a lot of detail when the malloc call is very deep inside a library and not in dunst code itself. --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 034cf31..3edd1e7 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,7 @@ test-valgrind: test/test --leak-check=full \ --show-leak-kinds=definite \ --errors-for-leak-kinds=definite \ + --num-callers=40 \ --error-exitcode=123 \ ./test From e52b1ae495eb495dc971faf75c39c8629efc873a Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Mon, 19 Mar 2018 09:01:32 +0200 Subject: [PATCH 10/38] Rename draw functions Remove prefixes used to distinguish between X and draw functions in x.c, this is obviously no longer necessary. Additionally, add appropriate layout_ and color_ prefixes to the relevant functions. --- src/draw.c | 66 +++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/draw.c b/src/draw.c index 7059830..1c4cee2 100644 --- a/src/draw.c +++ b/src/draw.c @@ -45,7 +45,7 @@ void draw_setup(void) pango_fdesc = pango_font_description_from_string(settings.font); } -static color_t x_color_hex_to_double(int hexValue) +static color_t color_hex_to_double(int hexValue) { color_t color; color.r = ((hexValue >> 16) & 0xFF) / 255.0; @@ -55,7 +55,7 @@ static color_t x_color_hex_to_double(int hexValue) return color; } -static color_t x_string_to_color_t(const char *str) +static color_t string_to_color(const char *str) { char *end; long int val = strtol(str+1, &end, 16); @@ -63,10 +63,10 @@ static color_t x_string_to_color_t(const char *str) LOG_W("Invalid color string: '%s'", str); } - return x_color_hex_to_double(val); + return color_hex_to_double(val); } -static double _apply_delta(double base, double delta) +static double color_apply_delta(double base, double delta) { base += delta; if (base > 1) @@ -87,14 +87,14 @@ static color_t calculate_foreground_color(color_t bg) int signedness = darken ? -1 : 1; - color.r = _apply_delta(color.r, c_delta * signedness); - color.g = _apply_delta(color.g, c_delta * signedness); - color.b = _apply_delta(color.b, c_delta * signedness); + color.r = color_apply_delta(color.r, c_delta * signedness); + color.g = color_apply_delta(color.g, c_delta * signedness); + color.b = color_apply_delta(color.b, c_delta * signedness); return color; } -static color_t x_get_separator_color(colored_layout *cl, colored_layout *cl_next) +static color_t layout_get_sepcolor(colored_layout *cl, colored_layout *cl_next) { switch (settings.sep_color) { case FRAME: @@ -103,7 +103,7 @@ static color_t x_get_separator_color(colored_layout *cl, colored_layout *cl_next else return cl->frame; case CUSTOM: - return x_string_to_color_t(settings.sep_custom_color_str); + return string_to_color(settings.sep_custom_color_str); case FOREGROUND: return cl->fg; case AUTO: @@ -113,7 +113,7 @@ static color_t x_get_separator_color(colored_layout *cl, colored_layout *cl_next } } -static void r_setup_pango_layout(PangoLayout *layout, int width) +static void layout_setup_pango(PangoLayout *layout, int width) { pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); pango_layout_set_width(layout, width * PANGO_SCALE); @@ -208,7 +208,7 @@ static struct dimensions calculate_dimensions(GSList *layouts) w -= 2 * settings.h_padding; w -= 2 * settings.frame_width; if (cl->icon) w -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; - r_setup_pango_layout(cl->l, w); + layout_setup_pango(cl->l, w); /* re-read information */ pango_layout_get_pixel_size(cl->l, &w, &h); @@ -230,7 +230,7 @@ static struct dimensions calculate_dimensions(GSList *layouts) return dim; } -static PangoLayout *create_layout(cairo_t *c) +static PangoLayout *layout_create(cairo_t *c) { screen_info *screen = get_active_screen(); @@ -244,10 +244,10 @@ static PangoLayout *create_layout(cairo_t *c) return layout; } -static colored_layout *r_init_shared(cairo_t *c, notification *n) +static colored_layout *layout_init_shared(cairo_t *c, notification *n) { colored_layout *cl = g_malloc(sizeof(colored_layout)); - cl->l = create_layout(c); + cl->l = layout_create(c); if (!settings.word_wrap) { PangoEllipsizeMode ellipsize; @@ -308,9 +308,9 @@ static colored_layout *r_init_shared(cairo_t *c, notification *n) cl->icon = NULL; } - cl->fg = x_string_to_color_t(n->colors[ColFG]); - cl->bg = x_string_to_color_t(n->colors[ColBG]); - cl->frame = x_string_to_color_t(n->colors[ColFrame]); + cl->fg = string_to_color(n->colors[ColFG]); + cl->bg = string_to_color(n->colors[ColBG]); + cl->frame = string_to_color(n->colors[ColFrame]); cl->n = n; @@ -318,30 +318,30 @@ static colored_layout *r_init_shared(cairo_t *c, notification *n) int width = dim.w; if (have_dynamic_width()) { - r_setup_pango_layout(cl->l, -1); + layout_setup_pango(cl->l, -1); } else { width -= 2 * settings.h_padding; width -= 2 * settings.frame_width; if (cl->icon) width -= cairo_image_surface_get_width(cl->icon) + settings.h_padding; - r_setup_pango_layout(cl->l, width); + layout_setup_pango(cl->l, width); } return cl; } -static colored_layout *r_create_layout_for_xmore(cairo_t *c, notification *n, int qlen) +static colored_layout *layout_derive_xmore(cairo_t *c, notification *n, int qlen) { - colored_layout *cl = r_init_shared(c, n); + colored_layout *cl = layout_init_shared(c, n); cl->text = g_strdup_printf("(%d more)", qlen); cl->attr = NULL; pango_layout_set_text(cl->l, cl->text, -1); return cl; } -static colored_layout *r_create_layout_from_notification(cairo_t *c, notification *n) +static colored_layout *layout_from_notification(cairo_t *c, notification *n) { - colored_layout *cl = r_init_shared(c, n); + colored_layout *cl = layout_init_shared(c, n); /* markup */ GError *err = NULL; @@ -371,7 +371,7 @@ static colored_layout *r_create_layout_from_notification(cairo_t *c, notificatio return cl; } -static GSList *r_create_layouts(cairo_t *c) +static GSList *create_layouts(cairo_t *c) { GSList *layouts = NULL; @@ -393,24 +393,24 @@ static GSList *r_create_layouts(cairo_t *c) n->text_to_render = new_ttr; } layouts = g_slist_append(layouts, - r_create_layout_from_notification(c, n)); + layout_from_notification(c, n)); } if (xmore_is_needed && settings.geometry.h != 1) { /* append xmore message as new message */ layouts = g_slist_append(layouts, - r_create_layout_for_xmore(c, last, qlen)); + layout_derive_xmore(c, last, qlen)); } return layouts; } -static void r_free_layouts(GSList *layouts) +static void free_layouts(GSList *layouts) { g_slist_free_full(layouts, free_colored_layout); } -static struct dimensions x_render_layout(cairo_t *c, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) +static struct dimensions layout_render(cairo_t *c, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) { int h; int h_text = 0; @@ -474,7 +474,7 @@ static struct dimensions x_render_layout(cairo_t *c, colored_layout *cl, colored dim.y += (int)(floor(bg_half_height) + pango_offset); if (settings.separator_height > 0 && !last) { - color_t sep_color = x_get_separator_color(cl, cl_next); + color_t sep_color = layout_get_sepcolor(cl, cl_next); cairo_set_source_rgb(c, sep_color.r, sep_color.g, sep_color.b); if (settings.sep_color == FRAME) @@ -512,7 +512,7 @@ static struct dimensions x_render_layout(cairo_t *c, colored_layout *cl, colored void draw(void) { - GSList *layouts = r_create_layouts(c_context); + GSList *layouts = create_layouts(c_context); struct dimensions dim = calculate_dimensions(layouts); int width = dim.w; @@ -530,9 +530,9 @@ void draw(void) bool first = true; for (GSList *iter = layouts; iter; iter = iter->next) { if (iter->next) - dim = x_render_layout(c, iter->data, iter->next->data, dim, first, iter->next == NULL); + dim = layout_render(c, iter->data, iter->next->data, dim, first, iter->next == NULL); else - dim = x_render_layout(c, iter->data, NULL, dim, first, iter->next == NULL); + dim = layout_render(c, iter->data, NULL, dim, first, iter->next == NULL); first = false; } @@ -545,7 +545,7 @@ void draw(void) cairo_destroy(c); cairo_surface_destroy(image_surface); - r_free_layouts(layouts); + free_layouts(layouts); } void draw_deinit(void) From ce5c49f0e8b482105f1b7e46df4810854ae0578f Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Mon, 19 Mar 2018 11:37:01 +0200 Subject: [PATCH 11/38] Remove dead code from XEvent handler According to the X11 docs XSelectionNotify event is generated from a response to the ConvertSelection protocol which we don't use. --- src/x11/x.c | 4 ---- src/x11/x.h | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/x11/x.c b/src/x11/x.c index 6b48a16..1fdf58b 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -193,9 +193,6 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer draw(); } break; - case SelectionNotify: - if (ev.xselection.property == xctx.utf8) - break; case ButtonRelease: if (ev.xbutton.window == xctx.win) { x_handle_click(ev); @@ -472,7 +469,6 @@ static void x_win_setup(void) XSetWindowAttributes wa; root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); - xctx.utf8 = XInternAtom(xctx.dpy, "UTF8_STRING", false); wa.override_redirect = true; wa.background_pixmap = ParentRelative; diff --git a/src/x11/x.h b/src/x11/x.h index a2afde7..2eee3b5 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -34,14 +34,12 @@ struct dimensions { }; typedef struct _xctx { - Atom utf8; Display *dpy; int cur_screen; Window win; bool visible; const char *colors[3][3]; XScreenSaverInfo *screensaver_info; - unsigned long sep_custom_col; } xctx_t; typedef struct _color_t { From a2863d53126565269f3f3ac538e1d5fd031b631e Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Mon, 19 Mar 2018 12:57:45 +0200 Subject: [PATCH 12/38] Move window-related fields to dedicated struct The first step in allowing dunst to have multiple windows. --- src/dunst.c | 10 +++++----- src/x11/x.c | 36 ++++++++++++++++++------------------ src/x11/x.h | 12 +++++++++--- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/dunst.c b/src/dunst.c index f6f3a24..79e648c 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -59,19 +59,19 @@ static gboolean run(void *data) static gint64 next_timeout = 0; - if (!xctx.visible && queues_length_displayed() > 0) { + if (!xctx.win.visible && queues_length_displayed() > 0) { x_win_show(); } - if (xctx.visible && queues_length_displayed() == 0) { + if (xctx.win.visible && queues_length_displayed() == 0) { x_win_hide(); } - if (xctx.visible) { + if (xctx.win.visible) { draw(); } - if (xctx.visible) { + if (xctx.win.visible) { gint64 now = time_monotonic_now(); gint64 sleep = queues_get_next_datachange(now); gint64 timeout_at = now + sleep; @@ -190,7 +190,7 @@ int dunst_main(int argc, char *argv[]) GSource *x11_source = g_source_new(&x11_source_funcs, sizeof(x11_source_t)); ((x11_source_t *) x11_source)->dpy = xctx.dpy; - ((x11_source_t *) x11_source)->w = xctx.win; + ((x11_source_t *) x11_source)->w = xctx.win.xwin; g_source_add_poll(x11_source, &dpy_pollfd); g_source_attach(x11_source, NULL); diff --git a/src/x11/x.c b/src/x11/x.c index 1fdf58b..8e71d11 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -48,7 +48,7 @@ static void x_win_setup(void); cairo_surface_t *x_create_cairo_surface(void) { return cairo_xlib_surface_create(xctx.dpy, - xctx.win, DefaultVisual(xctx.dpy, 0), WIDTH, HEIGHT); + xctx.win.xwin, DefaultVisual(xctx.dpy, 0), WIDTH, HEIGHT); } void x_win_move(int width, int height) @@ -59,7 +59,7 @@ void x_win_move(int width, int height) int x, y; screen_info *scr = get_active_screen(); - xctx.cur_screen = scr->id; + xctx.win.cur_screen = scr->id; /* calculate window position */ if (settings.geometry.negative_x) { @@ -76,10 +76,10 @@ void x_win_move(int width, int height) /* move and resize */ if (x != window_dim.x || y != window_dim.y) { - XMoveWindow(xctx.dpy, xctx.win, x, y); + XMoveWindow(xctx.dpy, xctx.win.xwin, x, y); } if (width != window_dim.w || height != window_dim.h) { - XResizeWindow(xctx.dpy, xctx.win, width, height); + XResizeWindow(xctx.dpy, xctx.win.xwin, width, height); } window_dim.x = x; @@ -189,12 +189,12 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer switch (ev.type) { case Expose: - if (ev.xexpose.count == 0 && xctx.visible) { + if (ev.xexpose.count == 0 && xctx.win.visible) { draw(); } break; case ButtonRelease: - if (ev.xbutton.window == xctx.win) { + if (ev.xbutton.window == xctx.win.xwin) { x_handle_click(ev); wake_up(); } @@ -250,8 +250,8 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer * same screen. PropertyNotify is only neccessary * to detect a focus change to another screen */ - && xctx.visible - && get_active_screen()->id != xctx.cur_screen) { + && xctx.win.visible + && get_active_screen()->id != xctx.win.cur_screen) { draw(); } break; @@ -477,7 +477,7 @@ static void x_win_setup(void) ButtonReleaseMask | FocusChangeMask| StructureNotifyMask; screen_info *scr = get_active_screen(); - xctx.win = XCreateWindow(xctx.dpy, + xctx.win.xwin = XCreateWindow(xctx.dpy, root, scr->x, scr->y, @@ -490,10 +490,10 @@ static void x_win_setup(void) CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); - x_set_wm(xctx.win); + x_set_wm(xctx.win.xwin); settings.transparency = settings.transparency > 100 ? 100 : settings.transparency; - setopacity(xctx.win, + setopacity(xctx.win.xwin, (unsigned long)((100 - settings.transparency) * (0xffffffff / 100))); @@ -509,7 +509,7 @@ static void x_win_setup(void) void x_win_show(void) { /* window is already mapped or there's nothing to show */ - if (xctx.visible || queues_length_displayed() == 0) { + if (xctx.win.visible || queues_length_displayed() == 0) { return; } @@ -521,7 +521,7 @@ void x_win_show(void) XGrabButton(xctx.dpy, AnyButton, AnyModifier, - xctx.win, + xctx.win.xwin, false, BUTTONMASK, GrabModeAsync, @@ -532,8 +532,8 @@ void x_win_show(void) LOG_W("Unable to grab mouse button(s)."); } - XMapRaised(xctx.dpy, xctx.win); - xctx.visible = true; + XMapRaised(xctx.dpy, xctx.win.xwin); + xctx.win.visible = true; } /* @@ -545,10 +545,10 @@ void x_win_hide(void) x_shortcut_ungrab(&settings.close_all_ks); x_shortcut_ungrab(&settings.context_ks); - XUngrabButton(xctx.dpy, AnyButton, AnyModifier, xctx.win); - XUnmapWindow(xctx.dpy, xctx.win); + XUngrabButton(xctx.dpy, AnyButton, AnyModifier, xctx.win.xwin); + XUnmapWindow(xctx.dpy, xctx.win.xwin); XFlush(xctx.dpy); - xctx.visible = false; + xctx.win.visible = false; } /* diff --git a/src/x11/x.h b/src/x11/x.h index 2eee3b5..45e1d99 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -33,11 +33,17 @@ struct dimensions { int h; }; +typedef struct { + Window xwin; + cairo_surface_t *root_surface; + int cur_screen; + bool visible; + struct dimensions dim; +} window_x11; + typedef struct _xctx { Display *dpy; - int cur_screen; - Window win; - bool visible; + window_x11 win; const char *colors[3][3]; XScreenSaverInfo *screensaver_info; } xctx_t; From 4faa9cbaaa640279dc67243a84f149ea926fe406 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 25 Mar 2018 15:53:42 +0300 Subject: [PATCH 13/38] Isolate GdkPixbuf usage to icon.c Cleans up the clutter in draw and since only a single icon function is called externally from icon.c it'll make things easier if we ever decide to switch icon libraries. --- src/draw.c | 32 +------------------------------- src/icon.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/icon.h | 8 ++++++++ 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/draw.c b/src/draw.c index 1c4cee2..47af2f9 100644 --- a/src/draw.c +++ b/src/draw.c @@ -267,38 +267,8 @@ static colored_layout *layout_init_shared(cairo_t *c, notification *n) pango_layout_set_ellipsize(cl->l, ellipsize); } - GdkPixbuf *pixbuf = NULL; - if (settings.icon_position != icons_off) { - if (n->raw_icon) - pixbuf = get_pixbuf_from_raw_image(n->raw_icon); - else if (n->icon) - pixbuf = get_pixbuf_from_icon(n->icon); - } - - if (pixbuf != NULL) { - int w = gdk_pixbuf_get_width(pixbuf); - int h = gdk_pixbuf_get_height(pixbuf); - int larger = w > h ? w : h; - if (settings.max_icon_size && larger > settings.max_icon_size) { - GdkPixbuf *scaled; - if (w >= h) { - scaled = gdk_pixbuf_scale_simple(pixbuf, - settings.max_icon_size, - (int) ((double) settings.max_icon_size / w * h), - GDK_INTERP_BILINEAR); - } else { - scaled = gdk_pixbuf_scale_simple(pixbuf, - (int) ((double) settings.max_icon_size / h * w), - settings.max_icon_size, - GDK_INTERP_BILINEAR); - } - g_object_unref(pixbuf); - pixbuf = scaled; - } - - cl->icon = gdk_pixbuf_to_cairo_surface(pixbuf); - g_object_unref(pixbuf); + cl->icon = icon_get_for_notification(n); } else { cl->icon = NULL; } diff --git a/src/icon.c b/src/icon.c index 2d2bd44..9685940 100644 --- a/src/icon.c +++ b/src/icon.c @@ -138,4 +138,44 @@ GdkPixbuf *get_pixbuf_from_raw_image(const RawImage *raw_image) return pixbuf; } + +cairo_surface_t *icon_get_for_notification(const notification *n) +{ + GdkPixbuf *pixbuf; + + if (n->raw_icon) + pixbuf = get_pixbuf_from_raw_image(n->raw_icon); + else if (n->icon) + pixbuf = get_pixbuf_from_icon(n->icon); + else + return NULL; + + if (!pixbuf) + return NULL; + + int w = gdk_pixbuf_get_width(pixbuf); + int h = gdk_pixbuf_get_height(pixbuf); + int larger = w > h ? w : h; + if (settings.max_icon_size && larger > settings.max_icon_size) { + GdkPixbuf *scaled; + if (w >= h) { + scaled = gdk_pixbuf_scale_simple(pixbuf, + settings.max_icon_size, + (int) ((double) settings.max_icon_size / w * h), + GDK_INTERP_BILINEAR); + } else { + scaled = gdk_pixbuf_scale_simple(pixbuf, + (int) ((double) settings.max_icon_size / h * w), + settings.max_icon_size, + GDK_INTERP_BILINEAR); + } + g_object_unref(pixbuf); + pixbuf = scaled; + } + + cairo_surface_t *ret = gdk_pixbuf_to_cairo_surface(pixbuf); + g_object_unref(pixbuf); + return ret; +} + /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/icon.h b/src/icon.h index dcd6f3d..c314465 100644 --- a/src/icon.h +++ b/src/icon.h @@ -22,5 +22,13 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname); */ GdkPixbuf *get_pixbuf_from_raw_image(const RawImage *raw_image); +/** + * Get a cairo surface with the appropriate icon for the notification, scaled + * according to the current settings + * + * @return a cairo_surface_t pointer or NULL if no icon could be retrieved. + */ +cairo_surface_t *icon_get_for_notification(const notification *n); + #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ From f513d8417f422a561e6d99c6e76532a55f6d53bf Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 25 Mar 2018 20:08:37 +0300 Subject: [PATCH 14/38] Move positioning logic out of x_win_move --- src/draw.c | 29 ++++++++++++++++++++++++++++- src/x11/x.c | 40 ++++++++++++++-------------------------- src/x11/x.h | 2 +- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/draw.c b/src/draw.c index 47af2f9..21a2d86 100644 --- a/src/draw.c +++ b/src/draw.c @@ -479,6 +479,31 @@ static struct dimensions layout_render(cairo_t *c, colored_layout *cl, colored_l return dim; } +/** + * Calculates the position the window should be placed at given its width and + * height and stores them in \p ret_x and \p ret_y. + */ +static void calc_window_pos(int width, int height, int *ret_x, int *ret_y) +{ + screen_info *scr = get_active_screen(); + + if (ret_x) { + if (settings.geometry.negative_x) { + *ret_x = (scr->x + (scr->w - width)) + settings.geometry.x; + } else { + *ret_x = scr->x + settings.geometry.x; + } + } + + if (ret_y) { + if (settings.geometry.negative_y) { + *ret_y = scr->y + (scr->h + settings.geometry.y) - height; + } else { + *ret_y = scr->y + settings.geometry.y; + } + } +} + void draw(void) { @@ -487,12 +512,14 @@ void draw(void) struct dimensions dim = calculate_dimensions(layouts); int width = dim.w; int height = dim.h; + int win_x, win_y; cairo_t *c; cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); c = cairo_create(image_surface); - x_win_move(width, height); + calc_window_pos(dim.w, dim.h, &win_x, &win_y); + x_win_move(win_x, win_y, width, height); cairo_xlib_surface_set_size(root_surface, width, height); cairo_move_to(c, 0, 0); diff --git a/src/x11/x.c b/src/x11/x.c index 8e71d11..de6dc38 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -51,41 +51,26 @@ cairo_surface_t *x_create_cairo_surface(void) xctx.win.xwin, DefaultVisual(xctx.dpy, 0), WIDTH, HEIGHT); } -void x_win_move(int width, int height) +void x_win_move(int x, int y, int width, int height) { // Previous dimensions of the window to avoid calling X11 if no change // is needed static struct dimensions window_dim = { 0 }; - int x, y; - screen_info *scr = get_active_screen(); - xctx.win.cur_screen = scr->id; - - /* calculate window position */ - if (settings.geometry.negative_x) { - x = (scr->x + (scr->w - width)) + settings.geometry.x; - } else { - x = scr->x + settings.geometry.x; - } - - if (settings.geometry.negative_y) { - y = scr->y + (scr->h + settings.geometry.y) - height; - } else { - y = scr->y + settings.geometry.y; - } - /* move and resize */ if (x != window_dim.x || y != window_dim.y) { XMoveWindow(xctx.dpy, xctx.win.xwin, x, y); - } - if (width != window_dim.w || height != window_dim.h) { - XResizeWindow(xctx.dpy, xctx.win.xwin, width, height); + + window_dim.x = x; + window_dim.y = y; } - window_dim.x = x; - window_dim.y = y; - window_dim.h = height; - window_dim.w = width; + if (width != window_dim.w || height != window_dim.h) { + XResizeWindow(xctx.dpy, xctx.win.xwin, width, height); + + window_dim.h = height; + window_dim.w = width; + } } static void setopacity(Window win, unsigned long opacity) @@ -181,6 +166,7 @@ gboolean x_mainloop_fd_check(GSource *source) gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { bool fullscreen_now; + screen_info *scr; XEvent ev; unsigned int state; while (XPending(xctx.dpy) > 0) { @@ -241,6 +227,7 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer break; case PropertyNotify: fullscreen_now = have_fullscreen_window(); + scr = get_active_screen(); if (fullscreen_now != fullscreen_last) { fullscreen_last = fullscreen_now; @@ -251,8 +238,9 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer * to detect a focus change to another screen */ && xctx.win.visible - && get_active_screen()->id != xctx.win.cur_screen) { + && scr->id != xctx.win.cur_screen) { draw(); + xctx.win.cur_screen = scr->id; } break; default: diff --git a/src/x11/x.h b/src/x11/x.h index 45e1d99..851be0a 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -57,7 +57,7 @@ typedef struct _color_t { extern xctx_t xctx; /* window */ -void x_win_move(int width, int height); +void x_win_move(int x, int y, int width, int height); void x_win_hide(void); void x_win_show(void); From 562dbe391853e17772adc66241edff05c5d48fb8 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Tue, 3 Apr 2018 20:07:41 +0300 Subject: [PATCH 15/38] Pass cairo surface to render_layout --- src/draw.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/draw.c b/src/draw.c index 21a2d86..ea55ce9 100644 --- a/src/draw.c +++ b/src/draw.c @@ -380,7 +380,7 @@ static void free_layouts(GSList *layouts) g_slist_free_full(layouts, free_colored_layout); } -static struct dimensions layout_render(cairo_t *c, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) +static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) { int h; int h_text = 0; @@ -397,6 +397,8 @@ static struct dimensions layout_render(cairo_t *c, colored_layout *cl, colored_l double bg_half_height = settings.notification_height/2.0; int pango_offset = (int) floor(h/2.0); + cairo_t *c = cairo_create(srf); + if (first) bg_height += settings.frame_width; if (last) bg_height += settings.frame_width; else bg_height += settings.separator_height; @@ -476,6 +478,7 @@ static struct dimensions layout_render(cairo_t *c, colored_layout *cl, colored_l cairo_fill(c); } + cairo_destroy(c); return dim; } @@ -514,22 +517,18 @@ void draw(void) int height = dim.h; int win_x, win_y; - cairo_t *c; cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); - c = cairo_create(image_surface); calc_window_pos(dim.w, dim.h, &win_x, &win_y); x_win_move(win_x, win_y, width, height); cairo_xlib_surface_set_size(root_surface, width, height); - cairo_move_to(c, 0, 0); - bool first = true; for (GSList *iter = layouts; iter; iter = iter->next) { if (iter->next) - dim = layout_render(c, iter->data, iter->next->data, dim, first, iter->next == NULL); + dim = layout_render(image_surface, iter->data, iter->next->data, dim, first, iter->next == NULL); else - dim = layout_render(c, iter->data, NULL, dim, first, iter->next == NULL); + dim = layout_render(image_surface, iter->data, NULL, dim, first, iter->next == NULL); first = false; } @@ -540,7 +539,6 @@ void draw(void) XFlush(xctx.dpy); - cairo_destroy(c); cairo_surface_destroy(image_surface); free_layouts(layouts); } From 781267f30da5c73b79e8245b7f5f77611a71072c Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Tue, 3 Apr 2018 20:59:51 +0300 Subject: [PATCH 16/38] Split background drawing from render_layout --- src/draw.c | 71 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/src/draw.c b/src/draw.c index ea55ce9..c02f047 100644 --- a/src/draw.c +++ b/src/draw.c @@ -380,6 +380,40 @@ static void free_layouts(GSList *layouts) g_slist_free_full(layouts, free_colored_layout); } +static cairo_surface_t *render_background(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, int y, int width, int height, bool first, bool last) +{ + int x = 0; + + cairo_t *c = cairo_create(srf); + + if (first) height += settings.frame_width; + if (last) height += settings.frame_width; + else height += settings.separator_height; + + cairo_set_source_rgb(c, cl->frame.r, cl->frame.g, cl->frame.b); + cairo_rectangle(c, x, y, width, height); + cairo_fill(c); + + /* adding frame */ + x += settings.frame_width; + if (first) { + y += settings.frame_width; + height -= settings.frame_width; + } + width -= 2 * settings.frame_width; + if (last) + height -= settings.frame_width; + else + height -= settings.separator_height; + + cairo_set_source_rgb(c, cl->bg.r, cl->bg.g, cl->bg.b); + cairo_rectangle(c, x, y, width, height); + cairo_fill(c); + + cairo_destroy(c); + return cairo_surface_create_for_rectangle(srf, x, y, width, height); +} + static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) { int h; @@ -390,38 +424,17 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, h = MAX(cairo_image_surface_get_height(cl->icon), h); } - int bg_x = 0; - int bg_y = dim.y; - int bg_width = dim.w; int bg_height = MAX(settings.notification_height, (2 * settings.padding) + h); double bg_half_height = settings.notification_height/2.0; int pango_offset = (int) floor(h/2.0); - cairo_t *c = cairo_create(srf); - - if (first) bg_height += settings.frame_width; - if (last) bg_height += settings.frame_width; - else bg_height += settings.separator_height; - - cairo_set_source_rgb(c, cl->frame.r, cl->frame.g, cl->frame.b); - cairo_rectangle(c, bg_x, bg_y, bg_width, bg_height); - cairo_fill(c); + cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, first, last); + cairo_t *c = cairo_create(content); /* adding frame */ - bg_x += settings.frame_width; if (first) { dim.y += settings.frame_width; - bg_y += settings.frame_width; - bg_height -= settings.frame_width; - if (!last) bg_height -= settings.separator_height; } - bg_width -= 2 * settings.frame_width; - if (last) - bg_height -= settings.frame_width; - - cairo_set_source_rgb(c, cl->bg.r, cl->bg.g, cl->bg.b); - cairo_rectangle(c, bg_x, bg_y, bg_width, bg_height); - cairo_fill(c); bool use_padding = settings.notification_height <= (2 * settings.padding) + h; if (use_padding) @@ -430,11 +443,11 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, dim.y += (int) (ceil(bg_half_height) - pango_offset); if (cl->icon && settings.icon_position == icons_left) { - cairo_move_to(c, settings.frame_width + cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding, bg_y + settings.padding + h/2 - h_text/2); + cairo_move_to(c, settings.frame_width + cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding, settings.padding + h/2 - h_text/2); } else if (cl->icon && settings.icon_position == icons_right) { - cairo_move_to(c, settings.frame_width + settings.h_padding, bg_y + settings.padding + h/2 - h_text/2); + cairo_move_to(c, settings.frame_width + settings.h_padding, settings.padding + h/2 - h_text/2); } else { - cairo_move_to(c, settings.frame_width + settings.h_padding, bg_y + settings.padding); + cairo_move_to(c, settings.frame_width + settings.h_padding, settings.padding); } cairo_set_source_rgb(c, cl->fg.r, cl->fg.g, cl->fg.b); @@ -465,12 +478,13 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, unsigned int image_width = cairo_image_surface_get_width(cl->icon), image_height = cairo_image_surface_get_height(cl->icon), image_x, - image_y = bg_y + settings.padding + h/2 - image_height/2; + image_y = settings.padding + h/2 - image_height/2; if (settings.icon_position == icons_left) { image_x = settings.frame_width + settings.h_padding; } else { - image_x = bg_width - settings.h_padding - image_width + settings.frame_width; + int bg_width = dim.w - 2 * settings.frame_width; + image_x = bg_width - settings.h_padding - image_width; } cairo_set_source_surface(c, cl->icon, image_x, image_y); @@ -479,6 +493,7 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, } cairo_destroy(c); + cairo_surface_destroy(content); return dim; } From d35807deaa4304b209f3f5e3bb9fba38f992bf26 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Wed, 4 Apr 2018 13:28:06 +0300 Subject: [PATCH 17/38] Move height calculation out of render_layout --- src/draw.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/draw.c b/src/draw.c index c02f047..abb8fb5 100644 --- a/src/draw.c +++ b/src/draw.c @@ -380,6 +380,17 @@ static void free_layouts(GSList *layouts) g_slist_free_full(layouts, free_colored_layout); } +static int layout_get_height(colored_layout *cl) +{ + int h; + int h_icon = 0; + pango_layout_get_pixel_size(cl->l, NULL, &h); + if (cl->icon) + h_icon = cairo_image_surface_get_height(cl->icon); + + return MAX(h, h_icon); +} + static cairo_surface_t *render_background(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, int y, int width, int height, bool first, bool last) { int x = 0; @@ -416,13 +427,10 @@ static cairo_surface_t *render_background(cairo_surface_t *srf, colored_layout * static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) { - int h; + int h = layout_get_height(cl); + int h_text = 0; - pango_layout_get_pixel_size(cl->l, NULL, &h); - if (cl->icon) { - h_text = h; - h = MAX(cairo_image_surface_get_height(cl->icon), h); - } + pango_layout_get_pixel_size(cl->l, NULL, &h_text); int bg_height = MAX(settings.notification_height, (2 * settings.padding) + h); double bg_half_height = settings.notification_height/2.0; From c26445aa5de4af97aa6e6af7b3af2923f9cc1fd2 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Wed, 4 Apr 2018 14:25:34 +0300 Subject: [PATCH 18/38] Split content drawing from render_layout --- src/draw.c | 92 +++++++++++++++++++++++++++++------------------------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/src/draw.c b/src/draw.c index abb8fb5..07dc49c 100644 --- a/src/draw.c +++ b/src/draw.c @@ -425,31 +425,12 @@ static cairo_surface_t *render_background(cairo_surface_t *srf, colored_layout * return cairo_surface_create_for_rectangle(srf, x, y, width, height); } -static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) +static void render_content(cairo_t *c, colored_layout *cl, int width) { - int h = layout_get_height(cl); - - int h_text = 0; + const int h = layout_get_height(cl); + int h_text; pango_layout_get_pixel_size(cl->l, NULL, &h_text); - int bg_height = MAX(settings.notification_height, (2 * settings.padding) + h); - double bg_half_height = settings.notification_height/2.0; - int pango_offset = (int) floor(h/2.0); - - cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, first, last); - cairo_t *c = cairo_create(content); - - /* adding frame */ - if (first) { - dim.y += settings.frame_width; - } - - bool use_padding = settings.notification_height <= (2 * settings.padding) + h; - if (use_padding) - dim.y += settings.padding; - else - dim.y += (int) (ceil(bg_half_height) - pango_offset); - if (cl->icon && settings.icon_position == icons_left) { cairo_move_to(c, settings.frame_width + cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding, settings.padding + h/2 - h_text/2); } else if (cl->icon && settings.icon_position == icons_right) { @@ -461,10 +442,53 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, cairo_set_source_rgb(c, cl->fg.r, cl->fg.g, cl->fg.b); pango_cairo_update_layout(c, cl->l); pango_cairo_show_layout(c, cl->l); - if (use_padding) - dim.y += h + settings.padding; + + + if (cl->icon) { + unsigned int image_width = cairo_image_surface_get_width(cl->icon), + image_height = cairo_image_surface_get_height(cl->icon), + image_x, + image_y = settings.padding + h/2 - image_height/2; + + if (settings.icon_position == icons_left) { + image_x = settings.frame_width + settings.h_padding; + } else { + int bg_width = width - 2 * settings.frame_width; + image_x = bg_width - settings.h_padding - image_width; + } + + cairo_set_source_surface(c, cl->icon, image_x, image_y); + cairo_rectangle(c, image_x, image_y, image_width, image_height); + cairo_fill(c); + } + +} + +static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) +{ + const int h = layout_get_height(cl); + + int h_text = 0; + pango_layout_get_pixel_size(cl->l, NULL, &h_text); + + int bg_height = MAX(settings.notification_height, (2 * settings.padding) + h); + double bg_half_height = settings.notification_height/2.0; + int pango_offset = (int) floor(h/2.0); + + cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, first, last); + cairo_t *c = cairo_create(content); + + render_content(c, cl, dim.w); + + /* adding frame */ + if (first) { + dim.y += settings.frame_width; + } + + if (settings.notification_height <= (2 * settings.padding) + h) + dim.y += h + 2 * settings.padding; else - dim.y += (int)(floor(bg_half_height) + pango_offset); + dim.y += 2 *( (int) (ceil(bg_half_height) - pango_offset)); if (settings.separator_height > 0 && !last) { color_t sep_color = layout_get_sepcolor(cl, cl_next); @@ -482,24 +506,6 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, } cairo_move_to(c, settings.h_padding, dim.y); - if (cl->icon) { - unsigned int image_width = cairo_image_surface_get_width(cl->icon), - image_height = cairo_image_surface_get_height(cl->icon), - image_x, - image_y = settings.padding + h/2 - image_height/2; - - if (settings.icon_position == icons_left) { - image_x = settings.frame_width + settings.h_padding; - } else { - int bg_width = dim.w - 2 * settings.frame_width; - image_x = bg_width - settings.h_padding - image_width; - } - - cairo_set_source_surface(c, cl->icon, image_x, image_y); - cairo_rectangle(c, image_x, image_y, image_width, image_height); - cairo_fill(c); - } - cairo_destroy(c); cairo_surface_destroy(content); return dim; From 4e57e1db1cf4d89f15e99b5d4b3734be21121c26 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Wed, 4 Apr 2018 14:56:20 +0300 Subject: [PATCH 19/38] Move separator drawing to render_background --- src/draw.c | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/draw.c b/src/draw.c index 07dc49c..2fe3cee 100644 --- a/src/draw.c +++ b/src/draw.c @@ -421,6 +421,17 @@ static cairo_surface_t *render_background(cairo_surface_t *srf, colored_layout * cairo_rectangle(c, x, y, width, height); cairo_fill(c); + if ( settings.sep_color != FRAME + && settings.separator_height > 0 + && !last) { + color_t sep_color = layout_get_sepcolor(cl, cl_next); + cairo_set_source_rgb(c, sep_color.r, sep_color.g, sep_color.b); + + cairo_rectangle(c, settings.frame_width, y + height, width, settings.separator_height); + + cairo_fill(c); + } + cairo_destroy(c); return cairo_surface_create_for_rectangle(srf, x, y, width, height); } @@ -481,31 +492,18 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, render_content(c, cl, dim.w); /* adding frame */ - if (first) { + if (first) dim.y += settings.frame_width; - } + + if (!last) + dim.y += settings.separator_height; + if (settings.notification_height <= (2 * settings.padding) + h) dim.y += h + 2 * settings.padding; else dim.y += 2 *( (int) (ceil(bg_half_height) - pango_offset)); - if (settings.separator_height > 0 && !last) { - color_t sep_color = layout_get_sepcolor(cl, cl_next); - cairo_set_source_rgb(c, sep_color.r, sep_color.g, sep_color.b); - - if (settings.sep_color == FRAME) - // Draw over the borders on both sides to avoid - // the wrong color in the corners. - cairo_rectangle(c, 0, dim.y, dim.w, settings.separator_height); - else - cairo_rectangle(c, settings.frame_width, dim.y + settings.frame_width, dim.w - 2 * settings.frame_width, settings.separator_height); - - cairo_fill(c); - dim.y += settings.separator_height; - } - cairo_move_to(c, settings.h_padding, dim.y); - cairo_destroy(c); cairo_surface_destroy(content); return dim; From 5e7f58203e2fb44dceef9221dc4448a10f8dd739 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 22 Apr 2018 11:44:30 +0300 Subject: [PATCH 20/38] Refactor hacky floating point casts Avoid casting an int to a double for a calculation only to cast it back, instead prefer to use integer operations where appropriate. --- src/draw.c | 4 +--- src/icon.c | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/draw.c b/src/draw.c index 2fe3cee..d3edd0a 100644 --- a/src/draw.c +++ b/src/draw.c @@ -483,8 +483,6 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, pango_layout_get_pixel_size(cl->l, NULL, &h_text); int bg_height = MAX(settings.notification_height, (2 * settings.padding) + h); - double bg_half_height = settings.notification_height/2.0; - int pango_offset = (int) floor(h/2.0); cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, first, last); cairo_t *c = cairo_create(content); @@ -502,7 +500,7 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, if (settings.notification_height <= (2 * settings.padding) + h) dim.y += h + 2 * settings.padding; else - dim.y += 2 *( (int) (ceil(bg_half_height) - pango_offset)); + dim.y += settings.notification_height; cairo_destroy(c); cairo_surface_destroy(content); diff --git a/src/icon.c b/src/icon.c index 9685940..73322f1 100644 --- a/src/icon.c +++ b/src/icon.c @@ -161,11 +161,11 @@ cairo_surface_t *icon_get_for_notification(const notification *n) if (w >= h) { scaled = gdk_pixbuf_scale_simple(pixbuf, settings.max_icon_size, - (int) ((double) settings.max_icon_size / w * h), + (settings.max_icon_size * h) / w, GDK_INTERP_BILINEAR); } else { scaled = gdk_pixbuf_scale_simple(pixbuf, - (int) ((double) settings.max_icon_size / h * w), + (settings.max_icon_size * w) / h, settings.max_icon_size, GDK_INTERP_BILINEAR); } From 7043af2e64b15a7f85fa6088bd01a2f01c8f360e Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 22 Apr 2018 11:17:21 +0300 Subject: [PATCH 21/38] Rename h variable to more descriptive name Rename h to cl_h. --- src/draw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/draw.c b/src/draw.c index d3edd0a..1ccf563 100644 --- a/src/draw.c +++ b/src/draw.c @@ -477,12 +477,12 @@ static void render_content(cairo_t *c, colored_layout *cl, int width) static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) { - const int h = layout_get_height(cl); + const int cl_h = layout_get_height(cl); int h_text = 0; pango_layout_get_pixel_size(cl->l, NULL, &h_text); - int bg_height = MAX(settings.notification_height, (2 * settings.padding) + h); + int bg_height = MAX(settings.notification_height, (2 * settings.padding) + cl_h); cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, first, last); cairo_t *c = cairo_create(content); @@ -497,8 +497,8 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, dim.y += settings.separator_height; - if (settings.notification_height <= (2 * settings.padding) + h) - dim.y += h + 2 * settings.padding; + if (settings.notification_height <= (2 * settings.padding) + cl_h) + dim.y += cl_h + 2 * settings.padding; else dim.y += settings.notification_height; From 21948122e06d821120f5414856e25138e0b16784 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 22 Apr 2018 11:22:03 +0300 Subject: [PATCH 22/38] Refactor render_background to return the layout width Rather than calculating the width of the subsurface we are drawing on everywhere simply add a return pointer to render_background. This commit also fixes a bug where the text and icon where drawn more to the right than usual (according to frame_width) which was caused because we added the frame_width to the draw position while we are drawing in a subsurface that already accounts for it. --- src/draw.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/draw.c b/src/draw.c index 1ccf563..10bc051 100644 --- a/src/draw.c +++ b/src/draw.c @@ -391,7 +391,7 @@ static int layout_get_height(colored_layout *cl) return MAX(h, h_icon); } -static cairo_surface_t *render_background(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, int y, int width, int height, bool first, bool last) +static cairo_surface_t *render_background(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, int y, int width, int height, bool first, bool last, int *ret_width) { int x = 0; @@ -411,7 +411,9 @@ static cairo_surface_t *render_background(cairo_surface_t *srf, colored_layout * y += settings.frame_width; height -= settings.frame_width; } + width -= 2 * settings.frame_width; + if (last) height -= settings.frame_width; else @@ -433,6 +435,10 @@ static cairo_surface_t *render_background(cairo_surface_t *srf, colored_layout * } cairo_destroy(c); + + if (ret_width) + *ret_width = width; + return cairo_surface_create_for_rectangle(srf, x, y, width, height); } @@ -443,11 +449,12 @@ static void render_content(cairo_t *c, colored_layout *cl, int width) pango_layout_get_pixel_size(cl->l, NULL, &h_text); if (cl->icon && settings.icon_position == icons_left) { - cairo_move_to(c, settings.frame_width + cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding, settings.padding + h/2 - h_text/2); + cairo_move_to(c, cairo_image_surface_get_width(cl->icon) + 2 * settings.h_padding, + settings.padding + h/2 - h_text/2); } else if (cl->icon && settings.icon_position == icons_right) { - cairo_move_to(c, settings.frame_width + settings.h_padding, settings.padding + h/2 - h_text/2); + cairo_move_to(c, settings.h_padding, settings.padding + h/2 - h_text/2); } else { - cairo_move_to(c, settings.frame_width + settings.h_padding, settings.padding); + cairo_move_to(c, settings.h_padding, settings.padding); } cairo_set_source_rgb(c, cl->fg.r, cl->fg.g, cl->fg.b); @@ -462,10 +469,9 @@ static void render_content(cairo_t *c, colored_layout *cl, int width) image_y = settings.padding + h/2 - image_height/2; if (settings.icon_position == icons_left) { - image_x = settings.frame_width + settings.h_padding; + image_x = settings.h_padding; } else { - int bg_width = width - 2 * settings.frame_width; - image_x = bg_width - settings.h_padding - image_width; + image_x = width - settings.h_padding - image_width; } cairo_set_source_surface(c, cl->icon, image_x, image_y); @@ -482,12 +488,13 @@ static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, int h_text = 0; pango_layout_get_pixel_size(cl->l, NULL, &h_text); + int bg_width = 0; int bg_height = MAX(settings.notification_height, (2 * settings.padding) + cl_h); - cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, first, last); + cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, first, last, &bg_width); cairo_t *c = cairo_create(content); - render_content(c, cl, dim.w); + render_content(c, cl, bg_width); /* adding frame */ if (first) From 43c7078ccc5d45ee8030d91f7157cc9e8d674ffc Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 22 Apr 2018 11:48:38 +0300 Subject: [PATCH 23/38] Break down long function prototypes --- src/draw.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/draw.c b/src/draw.c index 10bc051..ad389b6 100644 --- a/src/draw.c +++ b/src/draw.c @@ -391,7 +391,15 @@ static int layout_get_height(colored_layout *cl) return MAX(h, h_icon); } -static cairo_surface_t *render_background(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, int y, int width, int height, bool first, bool last, int *ret_width) +static cairo_surface_t *render_background(cairo_surface_t *srf, + colored_layout *cl, + colored_layout *cl_next, + int y, + int width, + int height, + bool first, + bool last, + int *ret_width) { int x = 0; @@ -481,7 +489,12 @@ static void render_content(cairo_t *c, colored_layout *cl, int width) } -static struct dimensions layout_render(cairo_surface_t *srf, colored_layout *cl, colored_layout *cl_next, struct dimensions dim, bool first, bool last) +static struct dimensions layout_render(cairo_surface_t *srf, + colored_layout *cl, + colored_layout *cl_next, + struct dimensions dim, + bool first, + bool last) { const int cl_h = layout_get_height(cl); From dffe1e1a5ab39682ce4b279144ad1d1e774f00d7 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Mon, 30 Apr 2018 11:57:25 +0300 Subject: [PATCH 24/38] Fix dunst losing on-top status on WM restart Subscribe to receive window creation events on the root window and raise the window when that happens to ensure dunst is on top. We do not raise the window when another override redirect window is created to avoid being on top of things like screen lockers. Fixes #160 --- src/x11/x.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/x11/x.c b/src/x11/x.c index de6dc38..010a779 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -225,6 +225,11 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer case FocusOut: wake_up(); break; + case CreateNotify: + if (xctx.win.visible && + ev.xcreatewindow.override_redirect == 0) + XRaiseWindow(xctx.dpy, xctx.win.xwin); + break; case PropertyNotify: fullscreen_now = have_fullscreen_window(); scr = get_active_screen(); @@ -485,10 +490,12 @@ static void x_win_setup(void) (unsigned long)((100 - settings.transparency) * (0xffffffff / 100))); + + long root_event_mask = SubstructureNotifyMask; if (settings.f_mode != FOLLOW_NONE) { - long root_event_mask = FocusChangeMask | PropertyChangeMask; - XSelectInput(xctx.dpy, root, root_event_mask); + root_event_mask |= FocusChangeMask | PropertyChangeMask; } + XSelectInput(xctx.dpy, root, root_event_mask); } /* From 806ba9b9783d5d5074b20575349d62254f87e8b6 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Wed, 2 May 2018 20:30:55 +0300 Subject: [PATCH 25/38] Add xpm to supported icon extensions The freedesktop icon spec specifies that xpm image files should be supported for backwards compatibility. --- src/icon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/icon.c b/src/icon.c index 73322f1..c7ecf8d 100644 --- a/src/icon.c +++ b/src/icon.c @@ -74,7 +74,7 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname) if (!iconname || iconname[0] == '\0') return NULL; - const char *suffixes[] = { ".svg", ".png", NULL }; + const char *suffixes[] = { ".svg", ".png", ".xpm", NULL }; GdkPixbuf *pixbuf = NULL; gchar *uri_path = NULL; From 2a1c4946b5730a85db62f41533a260db26775b01 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Wed, 2 May 2018 20:51:42 +0300 Subject: [PATCH 26/38] Add log message when failing to load an icon --- src/icon.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/icon.c b/src/icon.c index c7ecf8d..164a618 100644 --- a/src/icon.c +++ b/src/icon.c @@ -63,8 +63,10 @@ static GdkPixbuf *get_pixbuf_from_file(const char *filename) if (is_readable_file(filename)) { GError *error = NULL; pixbuf = gdk_pixbuf_new_from_file(filename, &error); - if (!pixbuf) + if (!pixbuf) { + LOG_W("%s", error->message); g_error_free(error); + } } return pixbuf; } @@ -116,7 +118,7 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname) } while (*(end) != '\0'); } if (!pixbuf) - LOG_W("Could not load icon: '%s'", iconname); + LOG_W("No icon found for: '%s'", iconname); g_free(uri_path); return pixbuf; From bec02ef6a0cc27527f67a0987dd2e3f401da0543 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Wed, 2 May 2018 21:25:08 +0300 Subject: [PATCH 27/38] Fix wrong return value in x11 event dispatch According to glib docs the dispatch function should be G_SOURCE_REMOVE or G_SOURCE_CONTINUE. Since this was worked properly until now I assume we were lucky enough that true == G_SOURCE_CONTINUE but it may not always be the case. --- src/x11/x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/x11/x.c b/src/x11/x.c index 010a779..731986c 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -253,7 +253,7 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer break; } } - return true; + return G_SOURCE_CONTINUE; } /* From 72aab3daf4dbca48387876b9e79e76106531b6cb Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 6 May 2018 19:47:16 +0300 Subject: [PATCH 28/38] Refactor window creation and destruction Move the main window reference from xctx to draw.c and at the same time make each X11 function that modifies the window require the pointer as a parameter. This makes it easier to implement features that require multiple windows. --- src/draw.c | 20 +++++++---------- src/draw.h | 3 +++ src/dunst.c | 10 ++++----- src/x11/x.c | 64 ++++++++++++++++++++++++++++++----------------------- src/x11/x.h | 6 ++--- 5 files changed, 55 insertions(+), 48 deletions(-) diff --git a/src/draw.c b/src/draw.c index ad389b6..410e918 100644 --- a/src/draw.c +++ b/src/draw.c @@ -31,8 +31,7 @@ typedef struct { notification *n; } colored_layout; -cairo_surface_t *root_surface; -cairo_t *c_context; +window_x11 *win; PangoFontDescription *pango_fdesc; @@ -40,8 +39,7 @@ void draw_setup(void) { x_setup(); - root_surface = x_create_cairo_surface(); - c_context = cairo_create(root_surface); + win = x_win_create(); pango_fdesc = pango_font_description_from_string(settings.font); } @@ -555,7 +553,7 @@ static void calc_window_pos(int width, int height, int *ret_x, int *ret_y) void draw(void) { - GSList *layouts = create_layouts(c_context); + GSList *layouts = create_layouts(win->c_ctx); struct dimensions dim = calculate_dimensions(layouts); int width = dim.w; @@ -566,7 +564,7 @@ void draw(void) calc_window_pos(dim.w, dim.h, &win_x, &win_y); x_win_move(win_x, win_y, width, height); - cairo_xlib_surface_set_size(root_surface, width, height); + cairo_xlib_surface_set_size(win->root_surface, width, height); bool first = true; for (GSList *iter = layouts; iter; iter = iter->next) { @@ -578,9 +576,9 @@ void draw(void) first = false; } - cairo_set_source_surface(c_context, image_surface, 0, 0); - cairo_paint(c_context); - cairo_show_page(c_context); + cairo_set_source_surface(win->c_ctx, image_surface, 0, 0); + cairo_paint(win->c_ctx); + cairo_show_page(win->c_ctx); XFlush(xctx.dpy); @@ -590,9 +588,7 @@ void draw(void) void draw_deinit(void) { - cairo_destroy(c_context); - cairo_surface_destroy(root_surface); - + x_win_destroy(win); x_free(); } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/draw.h b/src/draw.h index de9fb28..3a3ea88 100644 --- a/src/draw.h +++ b/src/draw.h @@ -1,6 +1,9 @@ #ifndef DUNST_DRAW_H #define DUNST_DRAW_H +#include "src/x11/x.h" +extern window_x11 *win; // Temporary + void draw_setup(void); void draw(void); diff --git a/src/dunst.c b/src/dunst.c index 79e648c..cea339b 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -59,19 +59,19 @@ static gboolean run(void *data) static gint64 next_timeout = 0; - if (!xctx.win.visible && queues_length_displayed() > 0) { + if (!win->visible && queues_length_displayed() > 0) { x_win_show(); } - if (xctx.win.visible && queues_length_displayed() == 0) { + if (win->visible && queues_length_displayed() == 0) { x_win_hide(); } - if (xctx.win.visible) { + if (win->visible) { draw(); } - if (xctx.win.visible) { + if (win->visible) { gint64 now = time_monotonic_now(); gint64 sleep = queues_get_next_datachange(now); gint64 timeout_at = now + sleep; @@ -190,7 +190,7 @@ int dunst_main(int argc, char *argv[]) GSource *x11_source = g_source_new(&x11_source_funcs, sizeof(x11_source_t)); ((x11_source_t *) x11_source)->dpy = xctx.dpy; - ((x11_source_t *) x11_source)->w = xctx.win.xwin; + ((x11_source_t *) x11_source)->w = win->xwin; g_source_add_poll(x11_source, &dpy_pollfd); g_source_attach(x11_source, NULL); diff --git a/src/x11/x.c b/src/x11/x.c index 731986c..d722b4b 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -43,13 +43,6 @@ static void x_shortcut_setup_error_handler(void); static int x_shortcut_tear_down_error_handler(void); static void setopacity(Window win, unsigned long opacity); static void x_handle_click(XEvent ev); -static void x_win_setup(void); - -cairo_surface_t *x_create_cairo_surface(void) -{ - return cairo_xlib_surface_create(xctx.dpy, - xctx.win.xwin, DefaultVisual(xctx.dpy, 0), WIDTH, HEIGHT); -} void x_win_move(int x, int y, int width, int height) { @@ -59,14 +52,14 @@ void x_win_move(int x, int y, int width, int height) /* move and resize */ if (x != window_dim.x || y != window_dim.y) { - XMoveWindow(xctx.dpy, xctx.win.xwin, x, y); + XMoveWindow(xctx.dpy, win->xwin, x, y); window_dim.x = x; window_dim.y = y; } if (width != window_dim.w || height != window_dim.h) { - XResizeWindow(xctx.dpy, xctx.win.xwin, width, height); + XResizeWindow(xctx.dpy, win->xwin, width, height); window_dim.h = height; window_dim.w = width; @@ -175,12 +168,12 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer switch (ev.type) { case Expose: - if (ev.xexpose.count == 0 && xctx.win.visible) { + if (ev.xexpose.count == 0 && win->visible) { draw(); } break; case ButtonRelease: - if (ev.xbutton.window == xctx.win.xwin) { + if (ev.xbutton.window == win->xwin) { x_handle_click(ev); wake_up(); } @@ -226,9 +219,9 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer wake_up(); break; case CreateNotify: - if (xctx.win.visible && + if (win->visible && ev.xcreatewindow.override_redirect == 0) - XRaiseWindow(xctx.dpy, xctx.win.xwin); + XRaiseWindow(xctx.dpy, win->xwin); break; case PropertyNotify: fullscreen_now = have_fullscreen_window(); @@ -242,10 +235,10 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer * same screen. PropertyNotify is only neccessary * to detect a focus change to another screen */ - && xctx.win.visible - && scr->id != xctx.win.cur_screen) { + && win->visible + && scr->id != win->cur_screen) { draw(); - xctx.win.cur_screen = scr->id; + win->cur_screen = scr->id; } break; default: @@ -362,7 +355,6 @@ void x_setup(void) xctx.screensaver_info = XScreenSaverAllocInfo(); init_screens(); - x_win_setup(); x_shortcut_grab(&settings.history_ks); } @@ -455,8 +447,9 @@ static void x_set_wm(Window win) /* * Setup the window */ -static void x_win_setup(void) +window_x11 *x_win_create(void) { + window_x11 *win = g_malloc0(sizeof(window_x11)); Window root; XSetWindowAttributes wa; @@ -470,7 +463,7 @@ static void x_win_setup(void) ButtonReleaseMask | FocusChangeMask| StructureNotifyMask; screen_info *scr = get_active_screen(); - xctx.win.xwin = XCreateWindow(xctx.dpy, + win->xwin = XCreateWindow(xctx.dpy, root, scr->x, scr->y, @@ -483,19 +476,34 @@ static void x_win_setup(void) CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); - x_set_wm(xctx.win.xwin); + x_set_wm(win->xwin); settings.transparency = settings.transparency > 100 ? 100 : settings.transparency; - setopacity(xctx.win.xwin, + setopacity(win->xwin, (unsigned long)((100 - settings.transparency) * (0xffffffff / 100))); + win->root_surface = cairo_xlib_surface_create(xctx.dpy, win->xwin, + DefaultVisual(xctx.dpy, 0), + WIDTH, HEIGHT); + win->c_ctx = cairo_create(win->root_surface); long root_event_mask = SubstructureNotifyMask; if (settings.f_mode != FOLLOW_NONE) { root_event_mask |= FocusChangeMask | PropertyChangeMask; } XSelectInput(xctx.dpy, root, root_event_mask); + + return win; +} + +void x_win_destroy(window_x11 *win) +{ + cairo_destroy(win->c_ctx); + cairo_surface_destroy(win->root_surface); + XDestroyWindow(xctx.dpy, win->xwin); + + g_free(win); } /* @@ -504,7 +512,7 @@ static void x_win_setup(void) void x_win_show(void) { /* window is already mapped or there's nothing to show */ - if (xctx.win.visible || queues_length_displayed() == 0) { + if (win->visible || queues_length_displayed() == 0) { return; } @@ -516,7 +524,7 @@ void x_win_show(void) XGrabButton(xctx.dpy, AnyButton, AnyModifier, - xctx.win.xwin, + win->xwin, false, BUTTONMASK, GrabModeAsync, @@ -527,8 +535,8 @@ void x_win_show(void) LOG_W("Unable to grab mouse button(s)."); } - XMapRaised(xctx.dpy, xctx.win.xwin); - xctx.win.visible = true; + XMapRaised(xctx.dpy, win->xwin); + win->visible = true; } /* @@ -540,10 +548,10 @@ void x_win_hide(void) x_shortcut_ungrab(&settings.close_all_ks); x_shortcut_ungrab(&settings.context_ks); - XUngrabButton(xctx.dpy, AnyButton, AnyModifier, xctx.win.xwin); - XUnmapWindow(xctx.dpy, xctx.win.xwin); + XUngrabButton(xctx.dpy, AnyButton, AnyModifier, win->xwin); + XUnmapWindow(xctx.dpy, win->xwin); XFlush(xctx.dpy); - xctx.win.visible = false; + win->visible = false; } /* diff --git a/src/x11/x.h b/src/x11/x.h index 851be0a..606c092 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -36,6 +36,7 @@ struct dimensions { typedef struct { Window xwin; cairo_surface_t *root_surface; + cairo_t *c_ctx; int cur_screen; bool visible; struct dimensions dim; @@ -43,7 +44,6 @@ typedef struct { typedef struct _xctx { Display *dpy; - window_x11 win; const char *colors[3][3]; XScreenSaverInfo *screensaver_info; } xctx_t; @@ -57,9 +57,11 @@ typedef struct _color_t { extern xctx_t xctx; /* window */ +window_x11 *x_win_create(void); void x_win_move(int x, int y, int width, int height); void x_win_hide(void); void x_win_show(void); +void x_win_destroy(window_x11 *win); /* shortcut */ void x_shortcut_init(keyboard_shortcut *shortcut); @@ -74,8 +76,6 @@ void x_free(void); struct geometry x_parse_geometry(const char *geom_str); -cairo_surface_t *x_create_cairo_surface(void); - gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data); gboolean x_mainloop_fd_check(GSource *source); From c29b1a39fbfb339c27df5beac0c0dfc6228d56dd Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 6 May 2018 19:55:13 +0300 Subject: [PATCH 29/38] Refactor window positioning and surface rendering into x.c Move the double-buffering implementation and the x_win_call move into x.c so we only need to call x_display_surface for each draw. This isolates all uses of the cairo context and root surface outside of x.c which will help to add support for other display servers. --- src/draw.c | 18 +++--------------- src/x11/x.c | 31 ++++++++++++++++++++----------- src/x11/x.h | 2 +- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/draw.c b/src/draw.c index 410e918..8469ab9 100644 --- a/src/draw.c +++ b/src/draw.c @@ -1,9 +1,7 @@ #include "draw.h" -#include // Temporary #include #include -#include // Temporary #include #include #include @@ -556,15 +554,8 @@ void draw(void) GSList *layouts = create_layouts(win->c_ctx); struct dimensions dim = calculate_dimensions(layouts); - int width = dim.w; - int height = dim.h; - int win_x, win_y; - cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); - - calc_window_pos(dim.w, dim.h, &win_x, &win_y); - x_win_move(win_x, win_y, width, height); - cairo_xlib_surface_set_size(win->root_surface, width, height); + cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dim.w, dim.h); bool first = true; for (GSList *iter = layouts; iter; iter = iter->next) { @@ -576,11 +567,8 @@ void draw(void) first = false; } - cairo_set_source_surface(win->c_ctx, image_surface, 0, 0); - cairo_paint(win->c_ctx); - cairo_show_page(win->c_ctx); - - XFlush(xctx.dpy); + calc_window_pos(dim.w, dim.h, &dim.x, &dim.y); + x_display_surface(image_surface, win, &dim); cairo_surface_destroy(image_surface); free_layouts(layouts); diff --git a/src/x11/x.c b/src/x11/x.c index d722b4b..5856008 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -44,28 +44,37 @@ static int x_shortcut_tear_down_error_handler(void); static void setopacity(Window win, unsigned long opacity); static void x_handle_click(XEvent ev); -void x_win_move(int x, int y, int width, int height) +static void x_win_move(window_x11 *win, int x, int y, int width, int height) { - // Previous dimensions of the window to avoid calling X11 if no change - // is needed - static struct dimensions window_dim = { 0 }; - /* move and resize */ - if (x != window_dim.x || y != window_dim.y) { + if (x != win->dim.x || y != win->dim.y) { XMoveWindow(xctx.dpy, win->xwin, x, y); - window_dim.x = x; - window_dim.y = y; + win->dim.x = x; + win->dim.y = y; } - if (width != window_dim.w || height != window_dim.h) { + if (width != win->dim.w || height != win->dim.h) { XResizeWindow(xctx.dpy, win->xwin, width, height); - window_dim.h = height; - window_dim.w = width; + win->dim.h = height; + win->dim.w = width; } } +void x_display_surface(cairo_surface_t *srf, window_x11 *win, const struct dimensions *dim) +{ + x_win_move(win, dim->x, dim->y, dim->w, dim->h); + cairo_xlib_surface_set_size(win->root_surface, dim->w, dim->h); + + cairo_set_source_surface(win->c_ctx, srf, 0, 0); + cairo_paint(win->c_ctx); + cairo_show_page(win->c_ctx); + + XFlush(xctx.dpy); + +} + static void setopacity(Window win, unsigned long opacity) { Atom _NET_WM_WINDOW_OPACITY = diff --git a/src/x11/x.h b/src/x11/x.h index 606c092..0062359 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -58,10 +58,10 @@ extern xctx_t xctx; /* window */ window_x11 *x_win_create(void); -void x_win_move(int x, int y, int width, int height); void x_win_hide(void); void x_win_show(void); void x_win_destroy(window_x11 *win); +void x_display_surface(cairo_surface_t *srf, window_x11 *win, const struct dimensions *dim); /* shortcut */ void x_shortcut_init(keyboard_shortcut *shortcut); From 90dd111970e802e123aa93778ee7781e7494f1af Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 6 May 2018 20:32:00 +0300 Subject: [PATCH 30/38] Refactor x11 event source to handle multiple windows Move the code to register the x11 event source under x_win_create so multiple windows can be created and be properly registered as the event source. One more missing piece is that the callback functions are still using the global win variable and not getting the window from the source. --- src/dunst.c | 33 +-------------------------------- src/x11/x.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ src/x11/x.h | 3 +++ 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/dunst.c b/src/dunst.c index cea339b..5b8d155 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -1,7 +1,5 @@ /* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */ -#define XLIB_ILLEGAL_ACCESS - #include "dunst.h" #include @@ -28,12 +26,6 @@ #define VERSION "version info needed" #endif -typedef struct _x11_source { - GSource source; - Display *dpy; - Window w; -} x11_source_t; - /* index of colors fit to urgency level */ GMainLoop *mainloop = NULL; @@ -155,8 +147,6 @@ int dunst_main(int argc, char *argv[]) int owner_id = initdbus(); - draw_setup(); - if (settings.startup_notification) { notification *n = notification_create(); n->id = 0; @@ -174,26 +164,7 @@ int dunst_main(int argc, char *argv[]) mainloop = g_main_loop_new(NULL, FALSE); - GPollFD dpy_pollfd = { xctx.dpy->fd, - G_IO_IN | G_IO_HUP | G_IO_ERR, 0 - }; - - GSourceFuncs x11_source_funcs = { - x_mainloop_fd_prepare, - x_mainloop_fd_check, - x_mainloop_fd_dispatch, - NULL, - NULL, - NULL - }; - - GSource *x11_source = - g_source_new(&x11_source_funcs, sizeof(x11_source_t)); - ((x11_source_t *) x11_source)->dpy = xctx.dpy; - ((x11_source_t *) x11_source)->w = win->xwin; - g_source_add_poll(x11_source, &dpy_pollfd); - - g_source_attach(x11_source, NULL); + draw_setup(); guint pause_src = g_unix_signal_add(SIGUSR1, pause_signal, NULL); guint unpause_src = g_unix_signal_add(SIGUSR2, unpause_signal, NULL); @@ -213,8 +184,6 @@ int dunst_main(int argc, char *argv[]) g_source_remove(term_src); g_source_remove(int_src); - g_source_destroy(x11_source); - dbus_tear_down(owner_id); teardown(); diff --git a/src/x11/x.c b/src/x11/x.c index 5856008..fdfe33c 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -33,6 +33,12 @@ #define WIDTH 400 #define HEIGHT 400 +struct x11_source { + GSource source; + Display *dpy; + Window w; +}; + xctx_t xctx; bool dunst_grab_errored = false; @@ -309,6 +315,9 @@ static void x_handle_click(XEvent ev) void x_free(void) { + if (xctx.screensaver_info) + XFree(xctx.screensaver_info); + if (xctx.dpy) XCloseDisplay(xctx.dpy); } @@ -453,6 +462,39 @@ static void x_set_wm(Window win) PropModeReplace, (unsigned char *) data, 1L); } +GSource* x_win_reg_source(window_x11 *win) +{ + GPollFD *dpy_pollfd = g_malloc0(sizeof(dpy_pollfd)); + + *dpy_pollfd = (GPollFD) { + xctx.dpy->fd, + G_IO_IN | G_IO_HUP | G_IO_ERR, 0 + }; + + // Static is necessary here because glib keeps the pointer and we need + // to keep the reference alive. + static GSourceFuncs xsrc_fn = { + x_mainloop_fd_prepare, + x_mainloop_fd_check, + x_mainloop_fd_dispatch, + NULL, + NULL, + NULL + }; + + struct x11_source *xsrc = (struct x11_source*) g_source_new(&xsrc_fn, + sizeof(struct x11_source)); + + xsrc->dpy = xctx.dpy; + xsrc->w = win->xwin; + + g_source_add_poll((GSource*) xsrc_fn, dpy_pollfd); + + g_source_attach((GSource*) xsrc, NULL); + + return (GSource*)xsrc; +} + /* * Setup the window */ @@ -497,6 +539,8 @@ window_x11 *x_win_create(void) WIDTH, HEIGHT); win->c_ctx = cairo_create(win->root_surface); + win->esrc = x_win_reg_source(win); + long root_event_mask = SubstructureNotifyMask; if (settings.f_mode != FOLLOW_NONE) { root_event_mask |= FocusChangeMask | PropertyChangeMask; @@ -508,6 +552,9 @@ window_x11 *x_win_create(void) void x_win_destroy(window_x11 *win) { + g_source_destroy(win->esrc); + g_source_unref(win->esrc); + cairo_destroy(win->c_ctx); cairo_surface_destroy(win->root_surface); XDestroyWindow(xctx.dpy, win->xwin); diff --git a/src/x11/x.h b/src/x11/x.h index 0062359..ef1c412 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -2,6 +2,8 @@ #ifndef DUNST_X_H #define DUNST_X_H +#define XLIB_ILLEGAL_ACCESS + #include #include #include @@ -37,6 +39,7 @@ typedef struct { Window xwin; cairo_surface_t *root_surface; cairo_t *c_ctx; + GSource *esrc; int cur_screen; bool visible; struct dimensions dim; From adc075bf2c31d5e3d494fd50a6b4e752771d23a8 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 6 May 2018 21:43:34 +0300 Subject: [PATCH 31/38] Cleanup x.h prototypes --- src/x11/x.c | 9 ++++++--- src/x11/x.h | 11 ----------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/x11/x.c b/src/x11/x.c index fdfe33c..c10574e 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -44,6 +44,9 @@ bool dunst_grab_errored = false; static bool fullscreen_last = false; +static void x_shortcut_init(keyboard_shortcut *ks); +static int x_shortcut_grab(keyboard_shortcut *ks); +static void x_shortcut_ungrab(keyboard_shortcut *ks); /* FIXME refactor setup teardown handlers into one setup and one teardown */ static void x_shortcut_setup_error_handler(void); static int x_shortcut_tear_down_error_handler(void); @@ -676,7 +679,7 @@ static int x_shortcut_tear_down_error_handler(void) /* * Grab the given keyboard shortcut. */ -int x_shortcut_grab(keyboard_shortcut *ks) +static int x_shortcut_grab(keyboard_shortcut *ks) { if (!ks->is_valid) return 1; @@ -713,7 +716,7 @@ int x_shortcut_grab(keyboard_shortcut *ks) /* * Ungrab the given keyboard shortcut. */ -void x_shortcut_ungrab(keyboard_shortcut *ks) +static void x_shortcut_ungrab(keyboard_shortcut *ks) { Window root; root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); @@ -726,7 +729,7 @@ void x_shortcut_ungrab(keyboard_shortcut *ks) /* * Initialize the keyboard shortcut. */ -void x_shortcut_init(keyboard_shortcut *ks) +static void x_shortcut_init(keyboard_shortcut *ks) { if (ks == NULL || ks->str == NULL) return; diff --git a/src/x11/x.h b/src/x11/x.h index ef1c412..dc1f29a 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -66,12 +66,6 @@ void x_win_show(void); void x_win_destroy(window_x11 *win); void x_display_surface(cairo_surface_t *srf, window_x11 *win, const struct dimensions *dim); -/* shortcut */ -void x_shortcut_init(keyboard_shortcut *shortcut); -void x_shortcut_ungrab(keyboard_shortcut *ks); -int x_shortcut_grab(keyboard_shortcut *ks); -KeySym x_shortcut_string_to_mask(const char *str); - /* X misc */ bool x_is_idle(void); void x_setup(void); @@ -79,10 +73,5 @@ void x_free(void); struct geometry x_parse_geometry(const char *geom_str); -gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, - gpointer user_data); -gboolean x_mainloop_fd_check(GSource *source); -gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout); - #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ From 1fe80a5db8c2608faf1710ef555a54b1c97bd067 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Fri, 11 May 2018 16:32:38 +0300 Subject: [PATCH 32/38] Refactor x event to use g_source_add_unix_fd According to the glib docs we should use g_source_add_unix_fd rather than g_source_add_poll. Additionally, this helps us with memory management since we don't have to handle the allocation of dpy_pollfd. --- src/x11/x.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/x11/x.c b/src/x11/x.c index c10574e..e6bd542 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -467,13 +467,6 @@ static void x_set_wm(Window win) GSource* x_win_reg_source(window_x11 *win) { - GPollFD *dpy_pollfd = g_malloc0(sizeof(dpy_pollfd)); - - *dpy_pollfd = (GPollFD) { - xctx.dpy->fd, - G_IO_IN | G_IO_HUP | G_IO_ERR, 0 - }; - // Static is necessary here because glib keeps the pointer and we need // to keep the reference alive. static GSourceFuncs xsrc_fn = { @@ -491,7 +484,7 @@ GSource* x_win_reg_source(window_x11 *win) xsrc->dpy = xctx.dpy; xsrc->w = win->xwin; - g_source_add_poll((GSource*) xsrc_fn, dpy_pollfd); + g_source_add_unix_fd((GSource*) xsrc, xctx.dpy->fd, G_IO_IN | G_IO_HUP | G_IO_ERR); g_source_attach((GSource*) xsrc, NULL); From 5761ef1c0952fb46eb3017e23162fb6bf25d7ea2 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sat, 12 May 2018 14:27:13 +0300 Subject: [PATCH 33/38] Fix segfault with startup_notification As it currently stands notification_init uses xctx.color to initialize the notification colors which means that it depends on x11 having been previously initialized and crashes otherwise. This bug was introduced in 04b327f where draw_setup() was after the startup notification was created. As a temporary fix the notification creation was moved under draw_setup but for a long term solution we should look into removing these xctx dependencies. --- src/dunst.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/dunst.c b/src/dunst.c index 5b8d155..081e2f3 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -147,6 +147,18 @@ int dunst_main(int argc, char *argv[]) int owner_id = initdbus(); + mainloop = g_main_loop_new(NULL, FALSE); + + draw_setup(); + + guint pause_src = g_unix_signal_add(SIGUSR1, pause_signal, NULL); + guint unpause_src = g_unix_signal_add(SIGUSR2, unpause_signal, NULL); + + /* register SIGINT/SIGTERM handler for + * graceful termination */ + guint term_src = g_unix_signal_add(SIGTERM, quit_signal, NULL); + guint int_src = g_unix_signal_add(SIGINT, quit_signal, NULL); + if (settings.startup_notification) { notification *n = notification_create(); n->id = 0; @@ -162,18 +174,6 @@ int dunst_main(int argc, char *argv[]) // we do not call wakeup now, wake_up does not work here yet } - mainloop = g_main_loop_new(NULL, FALSE); - - draw_setup(); - - guint pause_src = g_unix_signal_add(SIGUSR1, pause_signal, NULL); - guint unpause_src = g_unix_signal_add(SIGUSR2, unpause_signal, NULL); - - /* register SIGINT/SIGTERM handler for - * graceful termination */ - guint term_src = g_unix_signal_add(SIGTERM, quit_signal, NULL); - guint int_src = g_unix_signal_add(SIGINT, quit_signal, NULL); - run(NULL); g_main_loop_run(mainloop); g_main_loop_unref(mainloop); From 1d58d2ec875075898e8e85e960c448b863b0bd1c Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Sat, 12 May 2018 12:46:47 +0200 Subject: [PATCH 34/38] Canonify invalid enum values handling --- src/draw.c | 6 ++++-- src/notification.c | 13 +++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/draw.c b/src/draw.c index 8469ab9..3a5b95b 100644 --- a/src/draw.c +++ b/src/draw.c @@ -105,7 +105,8 @@ static color_t layout_get_sepcolor(colored_layout *cl, colored_layout *cl_next) case AUTO: return calculate_foreground_color(cl->bg); default: - LOG_E("Unknown separator color type."); + LOG_E("Invalid %s enum value in %s:%d", "sep_color", __FILE__, __LINE__); + break; } } @@ -258,7 +259,8 @@ static colored_layout *layout_init_shared(cairo_t *c, notification *n) ellipsize = PANGO_ELLIPSIZE_END; break; default: - assert(false); + LOG_E("Invalid %s enum value in %s:%d", "ellipsize", __FILE__, __LINE__); + break; } pango_layout_set_ellipsize(cl->l, ellipsize); } diff --git a/src/notification.c b/src/notification.c index 183fc63..1cea286 100644 --- a/src/notification.c +++ b/src/notification.c @@ -32,12 +32,13 @@ static void notification_dmenu_string(notification *n); const char *enum_to_string_fullscreen(enum behavior_fullscreen in) { switch (in) { - case FS_SHOW: return "show"; - case FS_DELAY: return "delay"; - case FS_PUSHBACK: return "pushback"; - case FS_NULL: return "(null)"; - default: - LOG_E("Enum behavior_fullscreen has wrong value."); + case FS_SHOW: return "show"; + case FS_DELAY: return "delay"; + case FS_PUSHBACK: return "pushback"; + case FS_NULL: return "(null)"; + default: + LOG_E("Invalid %s enum value in %s:%d", "fullscreen", __FILE__, __LINE__); + break; } } From b6283724b9a9defdb572d4a8fbb63d0a3a9e80f0 Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Sun, 13 May 2018 00:11:47 +0200 Subject: [PATCH 35/38] Prefix the separator enum --- config.h | 2 +- src/draw.c | 10 +++++----- src/settings.c | 8 ++++---- src/settings.h | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/config.h b/config.h index e809ef9..8ffa7c9 100644 --- a/config.h +++ b/config.h @@ -45,7 +45,7 @@ settings_t defaults = { .separator_height = 2, /* height of the separator line between two notifications */ .padding = 0, .h_padding = 0, /* horizontal padding */ -.sep_color = AUTO, /* AUTO, FOREGROUND, FRAME, CUSTOM */ +.sep_color = SEP_AUTO, /* SEP_AUTO, SEP_FOREGROUND, SEP_FRAME, SEP_CUSTOM */ .sep_custom_color_str = NULL,/* custom color if sep_color is set to CUSTOM */ .frame_width = 0, diff --git a/src/draw.c b/src/draw.c index 3a5b95b..cdf4f94 100644 --- a/src/draw.c +++ b/src/draw.c @@ -93,16 +93,16 @@ static color_t calculate_foreground_color(color_t bg) static color_t layout_get_sepcolor(colored_layout *cl, colored_layout *cl_next) { switch (settings.sep_color) { - case FRAME: + case SEP_FRAME: if (cl_next->n->urgency > cl->n->urgency) return cl_next->frame; else return cl->frame; - case CUSTOM: + case SEP_CUSTOM: return string_to_color(settings.sep_custom_color_str); - case FOREGROUND: + case SEP_FOREGROUND: return cl->fg; - case AUTO: + case SEP_AUTO: return calculate_foreground_color(cl->bg); default: LOG_E("Invalid %s enum value in %s:%d", "sep_color", __FILE__, __LINE__); @@ -429,7 +429,7 @@ static cairo_surface_t *render_background(cairo_surface_t *srf, cairo_rectangle(c, x, y, width, height); cairo_fill(c); - if ( settings.sep_color != FRAME + if ( settings.sep_color != SEP_FRAME && settings.separator_height > 0 && !last) { color_t sep_color = layout_get_sepcolor(cl, cl_next); diff --git a/src/settings.c b/src/settings.c index 6e31d28..397d45b 100644 --- a/src/settings.c +++ b/src/settings.c @@ -377,13 +377,13 @@ void load_settings(char *cmdline_config_path) if (strlen(c) > 0) { if (strcmp(c, "auto") == 0) - settings.sep_color = AUTO; + settings.sep_color = SEP_AUTO; else if (strcmp(c, "foreground") == 0) - settings.sep_color = FOREGROUND; + settings.sep_color = SEP_FOREGROUND; else if (strcmp(c, "frame") == 0) - settings.sep_color = FRAME; + settings.sep_color = SEP_FRAME; else { - settings.sep_color = CUSTOM; + settings.sep_color = SEP_CUSTOM; settings.sep_custom_color_str = g_strdup(c); } } diff --git a/src/settings.h b/src/settings.h index 50eb1c6..ee7e4a1 100644 --- a/src/settings.h +++ b/src/settings.h @@ -9,7 +9,7 @@ enum alignment { left, center, right }; enum ellipsize { start, middle, end }; enum icon_position_t { icons_left, icons_right, icons_off }; -enum separator_color { FOREGROUND, AUTO, FRAME, CUSTOM }; +enum separator_color { SEP_FOREGROUND, SEP_AUTO, SEP_FRAME, SEP_CUSTOM }; enum follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD }; enum markup_mode { MARKUP_NULL, MARKUP_NO, MARKUP_STRIP, MARKUP_FULL }; From 119340c07d5d8383a20402e4170404c0e4e56977 Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Sun, 13 May 2018 00:12:40 +0200 Subject: [PATCH 36/38] Remove unused and superfluous macro definitions --- src/x11/x.c | 2 +- src/x11/x.h | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/x11/x.c b/src/x11/x.c index e6bd542..e298548 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -578,7 +578,7 @@ void x_win_show(void) AnyModifier, win->xwin, false, - BUTTONMASK, + (ButtonPressMask|ButtonReleaseMask), GrabModeAsync, GrabModeSync, None, diff --git a/src/x11/x.h b/src/x11/x.h index dc1f29a..df1e100 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -13,10 +13,6 @@ #include "screen.h" -#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) -#define FONT_HEIGHT_BORDER 2 -#define DEFFONT "Monospace-11" - typedef struct _keyboard_shortcut { const char *str; KeyCode code; From 54face5956ceea2649cbfe87598581efb77e7c8d Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Sun, 13 May 2018 14:06:13 +0300 Subject: [PATCH 37/38] Fix unchecked corner case in icon_position check In the case that cl->icon is not NULL we assume that icon_position is either left or right but this might not always be the case in the future. --- src/draw.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/draw.c b/src/draw.c index cdf4f94..a670fe2 100644 --- a/src/draw.c +++ b/src/draw.c @@ -476,8 +476,10 @@ static void render_content(cairo_t *c, colored_layout *cl, int width) if (settings.icon_position == icons_left) { image_x = settings.h_padding; - } else { + } else if (settings.icon_position == icons_right){ image_x = width - settings.h_padding - image_width; + } else { + LOG_E("Tried to draw icon but icon position is not valid. %s:%d", __FILE__, __LINE__); } cairo_set_source_surface(c, cl->icon, image_x, image_y); From 4891710af36df4b9f26f3af69c63e37c68825ed4 Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Mon, 14 May 2018 10:06:43 +0300 Subject: [PATCH 38/38] Refactor window_x11 struct to be opaque This removes window accesses from outside the x.c file which will allow us to abstract the window struct to implement multiple outputs in the future. --- src/draw.c | 2 +- src/dunst.c | 12 ++++++------ src/x11/x.c | 32 ++++++++++++++++++++++++++------ src/x11/x.h | 21 +++++++++------------ 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/draw.c b/src/draw.c index a670fe2..f6487ae 100644 --- a/src/draw.c +++ b/src/draw.c @@ -555,7 +555,7 @@ static void calc_window_pos(int width, int height, int *ret_x, int *ret_y) void draw(void) { - GSList *layouts = create_layouts(win->c_ctx); + GSList *layouts = create_layouts(x_win_get_context(win)); struct dimensions dim = calculate_dimensions(layouts); diff --git a/src/dunst.c b/src/dunst.c index 081e2f3..8b6ae46 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -51,19 +51,19 @@ static gboolean run(void *data) static gint64 next_timeout = 0; - if (!win->visible && queues_length_displayed() > 0) { - x_win_show(); + if (!x_win_visible(win) && queues_length_displayed() > 0) { + x_win_show(win); } - if (win->visible && queues_length_displayed() == 0) { - x_win_hide(); + if (x_win_visible(win) && queues_length_displayed() == 0) { + x_win_hide(win); } - if (win->visible) { + if (x_win_visible(win)) { draw(); } - if (win->visible) { + if (x_win_visible(win)) { gint64 now = time_monotonic_now(); gint64 sleep = queues_get_next_datachange(now); gint64 timeout_at = now + sleep; diff --git a/src/x11/x.c b/src/x11/x.c index e298548..a559fbf 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -33,10 +33,19 @@ #define WIDTH 400 #define HEIGHT 400 +struct window_x11 { + Window xwin; + cairo_surface_t *root_surface; + cairo_t *c_ctx; + GSource *esrc; + int cur_screen; + bool visible; + struct dimensions dim; +}; + struct x11_source { GSource source; - Display *dpy; - Window w; + window_x11 *win; }; xctx_t xctx; @@ -84,6 +93,16 @@ void x_display_surface(cairo_surface_t *srf, window_x11 *win, const struct dimen } +bool x_win_visible(window_x11 *win) +{ + return win->visible; +} + +cairo_t* x_win_get_context(window_x11 *win) +{ + return win->c_ctx; +} + static void setopacity(Window win, unsigned long opacity) { Atom _NET_WM_WINDOW_OPACITY = @@ -176,6 +195,8 @@ gboolean x_mainloop_fd_check(GSource *source) */ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { + window_x11 *win = ((struct x11_source*) source)->win; + bool fullscreen_now; screen_info *scr; XEvent ev; @@ -481,8 +502,7 @@ GSource* x_win_reg_source(window_x11 *win) struct x11_source *xsrc = (struct x11_source*) g_source_new(&xsrc_fn, sizeof(struct x11_source)); - xsrc->dpy = xctx.dpy; - xsrc->w = win->xwin; + xsrc->win = win; g_source_add_unix_fd((GSource*) xsrc, xctx.dpy->fd, G_IO_IN | G_IO_HUP | G_IO_ERR); @@ -561,7 +581,7 @@ void x_win_destroy(window_x11 *win) /* * Show the window and grab shortcuts. */ -void x_win_show(void) +void x_win_show(window_x11 *win) { /* window is already mapped or there's nothing to show */ if (win->visible || queues_length_displayed() == 0) { @@ -594,7 +614,7 @@ void x_win_show(void) /* * Hide the window and ungrab unused keyboard_shortcuts */ -void x_win_hide(void) +void x_win_hide(window_x11 *win) { x_shortcut_ungrab(&settings.close_ks); x_shortcut_ungrab(&settings.close_all_ks); diff --git a/src/x11/x.h b/src/x11/x.h index df1e100..f32fefa 100644 --- a/src/x11/x.h +++ b/src/x11/x.h @@ -24,6 +24,8 @@ typedef struct _keyboard_shortcut { // Cyclical dependency #include "src/settings.h" +typedef struct window_x11 window_x11; + struct dimensions { int x; int y; @@ -31,16 +33,6 @@ struct dimensions { int h; }; -typedef struct { - Window xwin; - cairo_surface_t *root_surface; - cairo_t *c_ctx; - GSource *esrc; - int cur_screen; - bool visible; - struct dimensions dim; -} window_x11; - typedef struct _xctx { Display *dpy; const char *colors[3][3]; @@ -57,11 +49,16 @@ extern xctx_t xctx; /* window */ window_x11 *x_win_create(void); -void x_win_hide(void); -void x_win_show(void); void x_win_destroy(window_x11 *win); + +void x_win_show(window_x11 *win); +void x_win_hide(window_x11 *win); + void x_display_surface(cairo_surface_t *srf, window_x11 *win, const struct dimensions *dim); +bool x_win_visible(window_x11 *win); +cairo_t* x_win_get_context(window_x11 *win); + /* X misc */ bool x_is_idle(void); void x_setup(void);