Merge pull request #496 from dunst-project/refactor-drawing

Drawing refactor
This commit is contained in:
Benedikt Heine 2018-05-14 11:22:52 +02:00 committed by GitHub
commit de89328359
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1303 additions and 876 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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 */
@ -37,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,

586
src/draw.c Normal file
View File

@ -0,0 +1,586 @@
#include "draw.h"
#include <assert.h>
#include <cairo.h>
#include <math.h>
#include <pango/pango-attributes.h>
#include <pango/pango-font.h>
#include <pango/pango-layout.h>
#include <pango/pango-types.h>
#include <pango/pangocairo.h>
#include <stdlib.h>
#include "dunst.h"
#include "icon.h"
#include "markup.h"
#include "notification.h"
#include "log.h"
#include "queues.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;
window_x11 *win;
PangoFontDescription *pango_fdesc;
void draw_setup(void)
{
x_setup();
win = x_win_create();
pango_fdesc = pango_font_description_from_string(settings.font);
}
static color_t 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 string_to_color(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 color_hex_to_double(val);
}
static double color_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 = 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 layout_get_sepcolor(colored_layout *cl, colored_layout *cl_next)
{
switch (settings.sep_color) {
case SEP_FRAME:
if (cl_next->n->urgency > cl->n->urgency)
return cl_next->frame;
else
return cl->frame;
case SEP_CUSTOM:
return string_to_color(settings.sep_custom_color_str);
case SEP_FOREGROUND:
return cl->fg;
case SEP_AUTO:
return calculate_foreground_color(cl->bg);
default:
LOG_E("Invalid %s enum value in %s:%d", "sep_color", __FILE__, __LINE__);
break;
}
}
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);
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 (settings.geometry.width_set && settings.geometry.w == 0);
}
static struct dimensions calculate_dimensions(GSList *layouts)
{
struct dimensions dim = { 0 };
screen_info *scr = get_active_screen();
if (have_dynamic_width()) {
/* dynamic width */
dim.w = 0;
} else if (settings.geometry.width_set) {
/* fixed width */
if (settings.geometry.negative_width) {
dim.w = scr->w - settings.geometry.w;
} else {
dim.w = settings.geometry.w;
}
} else {
/* across the screen */
dim.w = scr->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->w) {
/* set width to screen width */
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;
}
/* 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;
layout_setup_pango(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 *layout_create(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 *layout_init_shared(cairo_t *c, notification *n)
{
colored_layout *cl = g_malloc(sizeof(colored_layout));
cl->l = layout_create(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:
LOG_E("Invalid %s enum value in %s:%d", "ellipsize", __FILE__, __LINE__);
break;
}
pango_layout_set_ellipsize(cl->l, ellipsize);
}
if (settings.icon_position != icons_off) {
cl->icon = icon_get_for_notification(n);
} 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 = 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;
struct dimensions dim = calculate_dimensions(NULL);
int width = dim.w;
if (have_dynamic_width()) {
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;
layout_setup_pango(cl->l, width);
}
return cl;
}
static colored_layout *layout_derive_xmore(cairo_t *c, notification *n, int qlen)
{
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 *layout_from_notification(cairo_t *c, notification *n)
{
colored_layout *cl = layout_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 *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 && 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;
}
layouts = g_slist_append(layouts,
layout_from_notification(c, n));
}
if (xmore_is_needed && settings.geometry.h != 1) {
/* append xmore message as new message */
layouts = g_slist_append(layouts,
layout_derive_xmore(c, last, qlen));
}
return layouts;
}
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 *ret_width)
{
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);
if ( settings.sep_color != SEP_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);
if (ret_width)
*ret_width = width;
return cairo_surface_create_for_rectangle(srf, x, y, width, height);
}
static void render_content(cairo_t *c, colored_layout *cl, int width)
{
const int h = layout_get_height(cl);
int h_text;
pango_layout_get_pixel_size(cl->l, NULL, &h_text);
if (cl->icon && settings.icon_position == icons_left) {
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.h_padding, settings.padding + h/2 - h_text/2);
} else {
cairo_move_to(c, settings.h_padding, 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 (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.h_padding;
} 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);
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 cl_h = layout_get_height(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, &bg_width);
cairo_t *c = cairo_create(content);
render_content(c, cl, bg_width);
/* adding frame */
if (first)
dim.y += settings.frame_width;
if (!last)
dim.y += settings.separator_height;
if (settings.notification_height <= (2 * settings.padding) + cl_h)
dim.y += cl_h + 2 * settings.padding;
else
dim.y += settings.notification_height;
cairo_destroy(c);
cairo_surface_destroy(content);
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)
{
GSList *layouts = create_layouts(x_win_get_context(win));
struct dimensions dim = calculate_dimensions(layouts);
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) {
if (iter->next)
dim = layout_render(image_surface, iter->data, iter->next->data, dim, first, iter->next == NULL);
else
dim = layout_render(image_surface, iter->data, NULL, dim, first, iter->next == NULL);
first = false;
}
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);
}
void draw_deinit(void)
{
x_win_destroy(win);
x_free();
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

14
src/draw.h Normal file
View File

@ -0,0 +1,14 @@
#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);
void draw_deinit(void);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -1,7 +1,5 @@
/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#define XLIB_ILLEGAL_ACCESS
#include "dunst.h"
#include <X11/Xlib.h>
@ -13,6 +11,7 @@
#include <stdlib.h>
#include "dbus.h"
#include "draw.h"
#include "log.h"
#include "menu.h"
#include "notification.h"
@ -27,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;
@ -58,19 +51,19 @@ static gboolean run(void *data)
static gint64 next_timeout = 0;
if (!xctx.visible && queues_length_displayed() > 0) {
x_win_show();
if (!x_win_visible(win) && queues_length_displayed() > 0) {
x_win_show(win);
}
if (xctx.visible && queues_length_displayed() == 0) {
x_win_hide();
if (x_win_visible(win) && queues_length_displayed() == 0) {
x_win_hide(win);
}
if (xctx.visible) {
x_win_draw();
if (x_win_visible(win)) {
draw();
}
if (xctx.visible) {
if (x_win_visible(win)) {
gint64 now = time_monotonic_now();
gint64 sleep = queues_get_next_datachange(now);
gint64 timeout_at = now + sleep;
@ -120,7 +113,7 @@ static void teardown(void)
teardown_queues();
x_free();
draw_deinit();
}
int dunst_main(int argc, char *argv[])
@ -154,7 +147,17 @@ int dunst_main(int argc, char *argv[])
int owner_id = initdbus();
x_setup();
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();
@ -171,37 +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);
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 = xctx.win;
g_source_add_poll(x11_source, &dpy_pollfd);
g_source_attach(x11_source, NULL);
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);
@ -212,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();

183
src/icon.c Normal file
View File

@ -0,0 +1,183 @@
#include "icon.h"
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <stdbool.h>
#include <string.h>
#include "log.h"
#include "notification.h"
#include "settings.h"
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 *filename)
{
GdkPixbuf *pixbuf = NULL;
if (is_readable_file(filename)) {
GError *error = NULL;
pixbuf = gdk_pixbuf_new_from_file(filename, &error);
if (!pixbuf) {
LOG_W("%s", error->message);
g_error_free(error);
}
}
return pixbuf;
}
GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
{
if (!iconname || iconname[0] == '\0')
return NULL;
const char *suffixes[] = { ".svg", ".png", ".xpm", NULL };
GdkPixbuf *pixbuf = NULL;
gchar *uri_path = NULL;
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)
break;
}
g_free(current_folder);
if (pixbuf)
break;
start = end + 1;
} while (*(end) != '\0');
}
if (!pixbuf)
LOG_W("No icon found for: '%s'", iconname);
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;
}
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,
(settings.max_icon_size * h) / w,
GDK_INTERP_BILINEAR);
} else {
scaled = gdk_pixbuf_scale_simple(pixbuf,
(settings.max_icon_size * w) / h,
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: */

34
src/icon.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef DUNST_ICON_H
#define DUNST_ICON_H
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "notification.h"
cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf);
/** 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);
/**
* 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: */

View File

@ -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;
}
}

View File

@ -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",
@ -363,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);
}
}

View File

@ -9,10 +9,22 @@
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 };
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;

View File

@ -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;
}
}

View File

@ -7,19 +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;
int mask;
int negative_width;
} dimension_t;
typedef struct _screen_info {
int scr;
dimension_t dim;
} screen_info;
void init_screens(void);

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,9 @@
#ifndef DUNST_X_H
#define DUNST_X_H
#define XLIB_ILLEGAL_ACCESS
#include <cairo.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/scrnsaver.h>
@ -10,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;
@ -22,17 +21,22 @@ typedef struct _keyboard_shortcut {
bool is_valid;
} keyboard_shortcut;
// Cyclical dependency
#include "src/settings.h"
typedef struct window_x11 window_x11;
struct dimensions {
int x;
int y;
int w;
int 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;
unsigned long sep_custom_col;
} xctx_t;
typedef struct _color_t {
@ -44,25 +48,23 @@ typedef struct _color_t {
extern xctx_t xctx;
/* window */
void x_win_draw(void);
void x_win_hide(void);
void x_win_show(void);
window_x11 *x_win_create(void);
void x_win_destroy(window_x11 *win);
/* 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);
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);
void x_free(void);
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);
struct geometry x_parse_geometry(const char *geom_str);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -0,0 +1 @@
Got'cha! This has to be invalid!

View File

@ -0,0 +1 @@
Got'cha! This has to be invalid!

View File

@ -0,0 +1 @@
../../invalid.png

View File

@ -0,0 +1 @@
../../invalid.svg

View File

@ -0,0 +1 @@
../../valid.png

View File

@ -0,0 +1 @@
../../valid.svg

View File

@ -0,0 +1 @@
../../valid.png

View File

@ -0,0 +1 @@
../../valid.svg

BIN
test/data/icons/valid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

65
test/data/icons/valid.svg Normal file
View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
id="SVGRoot"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="valid.svg">
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="16"
inkscape:cx="4.6127988"
inkscape:cy="8"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="958"
inkscape:window-height="1034"
inkscape:window-x="960"
inkscape:window-y="46"
inkscape:window-maximized="0"
inkscape:grid-bbox="true" />
<defs
id="defs20" />
<metadata
id="metadata23">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<rect
style="opacity:0.98999999;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:1;stroke-miterlimit:4.19999981;stroke-dasharray:none;stroke-opacity:0.15686275"
id="rect36"
width="8"
height="15.4375"
x="3.75"
y="0.25"
ry="4.875" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

98
test/icon.c Normal file
View File

@ -0,0 +1,98 @@
#include "greatest.h"
#include "../src/icon.h"
#include "../src/utils.h"
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <glib.h>
#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_icon_invalid(void)
{
GdkPixbuf *pixbuf = get_pixbuf_from_icon("invalid");
ASSERT(pixbuf == NULL);
g_clear_pointer(&pixbuf, g_object_unref);
PASS();
}
TEST test_get_pixbuf_from_icon_both(void)
{
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);
PASS();
}
TEST test_get_pixbuf_from_icon_onlysvg(void)
{
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);
PASS();
}
TEST test_get_pixbuf_from_icon_onlypng(void)
{
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);
PASS();
}
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_icon(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_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_icon(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_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;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -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: */

View File

@ -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: */