Merge pull request #496 from dunst-project/refactor-drawing
Drawing refactor
This commit is contained in:
commit
de89328359
@ -8,3 +8,32 @@
|
|||||||
...
|
...
|
||||||
fun:main
|
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
|
||||||
|
}
|
||||||
|
1
Makefile
1
Makefile
@ -80,6 +80,7 @@ test-valgrind: test/test
|
|||||||
--leak-check=full \
|
--leak-check=full \
|
||||||
--show-leak-kinds=definite \
|
--show-leak-kinds=definite \
|
||||||
--errors-for-leak-kinds=definite \
|
--errors-for-leak-kinds=definite \
|
||||||
|
--num-callers=40 \
|
||||||
--error-exitcode=123 \
|
--error-exitcode=123 \
|
||||||
./test
|
./test
|
||||||
|
|
||||||
|
12
config.h
12
config.h
@ -16,7 +16,15 @@ settings_t defaults = {
|
|||||||
.icons = { "dialog-information", "dialog-information", "dialog-warning" }, /* low, normal, critical */
|
.icons = { "dialog-information", "dialog-information", "dialog-warning" }, /* low, normal, critical */
|
||||||
|
|
||||||
.transparency = 0, /* transparency */
|
.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 */
|
.title = "Dunst", /* the title of dunst notification windows */
|
||||||
.class = "Dunst", /* the class of dunst notification windows */
|
.class = "Dunst", /* the class of dunst notification windows */
|
||||||
.shrink = false, /* shrinking */
|
.shrink = false, /* shrinking */
|
||||||
@ -37,7 +45,7 @@ settings_t defaults = {
|
|||||||
.separator_height = 2, /* height of the separator line between two notifications */
|
.separator_height = 2, /* height of the separator line between two notifications */
|
||||||
.padding = 0,
|
.padding = 0,
|
||||||
.h_padding = 0, /* horizontal padding */
|
.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 */
|
.sep_custom_color_str = NULL,/* custom color if sep_color is set to CUSTOM */
|
||||||
|
|
||||||
.frame_width = 0,
|
.frame_width = 0,
|
||||||
|
586
src/draw.c
Normal file
586
src/draw.c
Normal 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
14
src/draw.h
Normal 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: */
|
70
src/dunst.c
70
src/dunst.c
@ -1,7 +1,5 @@
|
|||||||
/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
|
/* copyright 2012 - 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
|
||||||
|
|
||||||
#define XLIB_ILLEGAL_ACCESS
|
|
||||||
|
|
||||||
#include "dunst.h"
|
#include "dunst.h"
|
||||||
|
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
@ -13,6 +11,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "dbus.h"
|
#include "dbus.h"
|
||||||
|
#include "draw.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "notification.h"
|
#include "notification.h"
|
||||||
@ -27,12 +26,6 @@
|
|||||||
#define VERSION "version info needed"
|
#define VERSION "version info needed"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct _x11_source {
|
|
||||||
GSource source;
|
|
||||||
Display *dpy;
|
|
||||||
Window w;
|
|
||||||
} x11_source_t;
|
|
||||||
|
|
||||||
/* index of colors fit to urgency level */
|
/* index of colors fit to urgency level */
|
||||||
|
|
||||||
GMainLoop *mainloop = NULL;
|
GMainLoop *mainloop = NULL;
|
||||||
@ -58,19 +51,19 @@ static gboolean run(void *data)
|
|||||||
|
|
||||||
static gint64 next_timeout = 0;
|
static gint64 next_timeout = 0;
|
||||||
|
|
||||||
if (!xctx.visible && queues_length_displayed() > 0) {
|
if (!x_win_visible(win) && queues_length_displayed() > 0) {
|
||||||
x_win_show();
|
x_win_show(win);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xctx.visible && queues_length_displayed() == 0) {
|
if (x_win_visible(win) && queues_length_displayed() == 0) {
|
||||||
x_win_hide();
|
x_win_hide(win);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xctx.visible) {
|
if (x_win_visible(win)) {
|
||||||
x_win_draw();
|
draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xctx.visible) {
|
if (x_win_visible(win)) {
|
||||||
gint64 now = time_monotonic_now();
|
gint64 now = time_monotonic_now();
|
||||||
gint64 sleep = queues_get_next_datachange(now);
|
gint64 sleep = queues_get_next_datachange(now);
|
||||||
gint64 timeout_at = now + sleep;
|
gint64 timeout_at = now + sleep;
|
||||||
@ -120,7 +113,7 @@ static void teardown(void)
|
|||||||
|
|
||||||
teardown_queues();
|
teardown_queues();
|
||||||
|
|
||||||
x_free();
|
draw_deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
int dunst_main(int argc, char *argv[])
|
int dunst_main(int argc, char *argv[])
|
||||||
@ -154,7 +147,17 @@ int dunst_main(int argc, char *argv[])
|
|||||||
|
|
||||||
int owner_id = initdbus();
|
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) {
|
if (settings.startup_notification) {
|
||||||
notification *n = notification_create();
|
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
|
// 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);
|
run(NULL);
|
||||||
g_main_loop_run(mainloop);
|
g_main_loop_run(mainloop);
|
||||||
g_main_loop_unref(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(term_src);
|
||||||
g_source_remove(int_src);
|
g_source_remove(int_src);
|
||||||
|
|
||||||
g_source_destroy(x11_source);
|
|
||||||
|
|
||||||
dbus_tear_down(owner_id);
|
dbus_tear_down(owner_id);
|
||||||
|
|
||||||
teardown();
|
teardown();
|
||||||
|
183
src/icon.c
Normal file
183
src/icon.c
Normal 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
34
src/icon.h
Normal 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: */
|
@ -37,7 +37,8 @@ const char *enum_to_string_fullscreen(enum behavior_fullscreen in)
|
|||||||
case FS_PUSHBACK: return "pushback";
|
case FS_PUSHBACK: return "pushback";
|
||||||
case FS_NULL: return "(null)";
|
case FS_NULL: return "(null)";
|
||||||
default:
|
default:
|
||||||
LOG_E("Enum behavior_fullscreen has wrong value.");
|
LOG_E("Invalid %s enum value in %s:%d", "fullscreen", __FILE__, __LINE__);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include "notification.h"
|
#include "notification.h"
|
||||||
#include "option_parser.h"
|
#include "option_parser.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "x11/x.h"
|
||||||
|
|
||||||
settings_t settings;
|
settings_t settings;
|
||||||
|
|
||||||
@ -256,12 +257,25 @@ void load_settings(char *cmdline_config_path)
|
|||||||
"Define the class of windows spawned by dunst."
|
"Define the class of windows spawned by dunst."
|
||||||
);
|
);
|
||||||
|
|
||||||
settings.geom = option_get_string(
|
{
|
||||||
|
|
||||||
|
char *c = option_get_string(
|
||||||
"global",
|
"global",
|
||||||
"geometry", "-geom/-geometry", defaults.geom,
|
"geometry", "-geom/-geometry", NULL,
|
||||||
"Geometry for the window"
|
"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(
|
settings.shrink = option_get_bool(
|
||||||
"global",
|
"global",
|
||||||
"shrink", "-shrink", defaults.shrink,
|
"shrink", "-shrink", defaults.shrink,
|
||||||
@ -363,13 +377,13 @@ void load_settings(char *cmdline_config_path)
|
|||||||
|
|
||||||
if (strlen(c) > 0) {
|
if (strlen(c) > 0) {
|
||||||
if (strcmp(c, "auto") == 0)
|
if (strcmp(c, "auto") == 0)
|
||||||
settings.sep_color = AUTO;
|
settings.sep_color = SEP_AUTO;
|
||||||
else if (strcmp(c, "foreground") == 0)
|
else if (strcmp(c, "foreground") == 0)
|
||||||
settings.sep_color = FOREGROUND;
|
settings.sep_color = SEP_FOREGROUND;
|
||||||
else if (strcmp(c, "frame") == 0)
|
else if (strcmp(c, "frame") == 0)
|
||||||
settings.sep_color = FRAME;
|
settings.sep_color = SEP_FRAME;
|
||||||
else {
|
else {
|
||||||
settings.sep_color = CUSTOM;
|
settings.sep_color = SEP_CUSTOM;
|
||||||
settings.sep_custom_color_str = g_strdup(c);
|
settings.sep_custom_color_str = g_strdup(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,22 @@
|
|||||||
enum alignment { left, center, right };
|
enum alignment { left, center, right };
|
||||||
enum ellipsize { start, middle, end };
|
enum ellipsize { start, middle, end };
|
||||||
enum icon_position_t { icons_left, icons_right, icons_off };
|
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 follow_mode { FOLLOW_NONE, FOLLOW_MOUSE, FOLLOW_KEYBOARD };
|
||||||
enum markup_mode { MARKUP_NULL, MARKUP_NO, MARKUP_STRIP, MARKUP_FULL };
|
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 {
|
typedef struct _settings {
|
||||||
bool print_notifications;
|
bool print_notifications;
|
||||||
bool per_monitor_dpi;
|
bool per_monitor_dpi;
|
||||||
@ -33,7 +45,7 @@ typedef struct _settings {
|
|||||||
gint64 timeouts[3];
|
gint64 timeouts[3];
|
||||||
char *icons[3];
|
char *icons[3];
|
||||||
unsigned int transparency;
|
unsigned int transparency;
|
||||||
char *geom;
|
struct geometry geometry;
|
||||||
char *title;
|
char *title;
|
||||||
char *class;
|
char *class;
|
||||||
int shrink;
|
int shrink;
|
||||||
|
@ -126,12 +126,12 @@ void randr_update(void)
|
|||||||
alloc_screen_ar(n);
|
alloc_screen_ar(n);
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
screens[i].scr = i;
|
screens[i].id = i;
|
||||||
screens[i].dim.x = m[i].x;
|
screens[i].x = m[i].x;
|
||||||
screens[i].dim.y = m[i].y;
|
screens[i].y = m[i].y;
|
||||||
screens[i].dim.w = m[i].width;
|
screens[i].w = m[i].width;
|
||||||
screens[i].dim.h = m[i].height;
|
screens[i].h = m[i].height;
|
||||||
screens[i].dim.mmh = m[i].mheight;
|
screens[i].mmh = m[i].mheight;
|
||||||
}
|
}
|
||||||
|
|
||||||
XRRFreeMonitors(m);
|
XRRFreeMonitors(m);
|
||||||
@ -139,7 +139,7 @@ void randr_update(void)
|
|||||||
|
|
||||||
static int autodetect_dpi(screen_info *scr)
|
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)
|
void screen_check_event(XEvent event)
|
||||||
@ -165,11 +165,11 @@ void xinerama_update(void)
|
|||||||
alloc_screen_ar(n);
|
alloc_screen_ar(n);
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
screens[i].scr = i;
|
screens[i].id = i;
|
||||||
screens[i].dim.x = info[i].x_org;
|
screens[i].x = info[i].x_org;
|
||||||
screens[i].dim.y = info[i].y_org;
|
screens[i].y = info[i].y_org;
|
||||||
screens[i].dim.h = info[i].height;
|
screens[i].h = info[i].height;
|
||||||
screens[i].dim.w = info[i].width;
|
screens[i].w = info[i].width;
|
||||||
}
|
}
|
||||||
XFree(info);
|
XFree(info);
|
||||||
}
|
}
|
||||||
@ -184,8 +184,8 @@ void screen_update_fallback(void)
|
|||||||
else
|
else
|
||||||
screen = DefaultScreen(xctx.dpy);
|
screen = DefaultScreen(xctx.dpy);
|
||||||
|
|
||||||
screens[0].dim.w = DisplayWidth(xctx.dpy, screen);
|
screens[0].w = DisplayWidth(xctx.dpy, screen);
|
||||||
screens[0].dim.h = DisplayHeight(xctx.dpy, screen);
|
screens[0].h = DisplayHeight(xctx.dpy, screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* see screen.h */
|
/* see screen.h */
|
||||||
@ -328,8 +328,8 @@ screen_info *get_active_screen(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < screens_len; i++) {
|
for (int i = 0; i < screens_len; i++) {
|
||||||
if (INRECT(x, y, screens[i].dim.x, screens[i].dim.y,
|
if (INRECT(x, y, screens[i].x, screens[i].y,
|
||||||
screens[i].dim.w, screens[i].dim.h)) {
|
screens[i].w, screens[i].h)) {
|
||||||
ret = i;
|
ret = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,19 +7,13 @@
|
|||||||
|
|
||||||
#define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh))
|
#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 x;
|
||||||
int y;
|
int y;
|
||||||
unsigned int h;
|
unsigned int h;
|
||||||
unsigned int mmh;
|
unsigned int mmh;
|
||||||
unsigned int w;
|
unsigned int w;
|
||||||
int mask;
|
|
||||||
int negative_width;
|
|
||||||
} dimension_t;
|
|
||||||
|
|
||||||
typedef struct _screen_info {
|
|
||||||
int scr;
|
|
||||||
dimension_t dim;
|
|
||||||
} screen_info;
|
} screen_info;
|
||||||
|
|
||||||
void init_screens(void);
|
void init_screens(void);
|
||||||
|
924
src/x11/x.c
924
src/x11/x.c
File diff suppressed because it is too large
Load Diff
48
src/x11/x.h
48
src/x11/x.h
@ -2,6 +2,9 @@
|
|||||||
#ifndef DUNST_X_H
|
#ifndef DUNST_X_H
|
||||||
#define DUNST_X_H
|
#define DUNST_X_H
|
||||||
|
|
||||||
|
#define XLIB_ILLEGAL_ACCESS
|
||||||
|
|
||||||
|
#include <cairo.h>
|
||||||
#include <X11/X.h>
|
#include <X11/X.h>
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#include <X11/extensions/scrnsaver.h>
|
#include <X11/extensions/scrnsaver.h>
|
||||||
@ -10,10 +13,6 @@
|
|||||||
|
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
|
||||||
#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask)
|
|
||||||
#define FONT_HEIGHT_BORDER 2
|
|
||||||
#define DEFFONT "Monospace-11"
|
|
||||||
|
|
||||||
typedef struct _keyboard_shortcut {
|
typedef struct _keyboard_shortcut {
|
||||||
const char *str;
|
const char *str;
|
||||||
KeyCode code;
|
KeyCode code;
|
||||||
@ -22,17 +21,22 @@ typedef struct _keyboard_shortcut {
|
|||||||
bool is_valid;
|
bool is_valid;
|
||||||
} keyboard_shortcut;
|
} 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 {
|
typedef struct _xctx {
|
||||||
Atom utf8;
|
|
||||||
Display *dpy;
|
Display *dpy;
|
||||||
int cur_screen;
|
|
||||||
Window win;
|
|
||||||
bool visible;
|
|
||||||
dimension_t geometry;
|
|
||||||
const char *colors[3][3];
|
const char *colors[3][3];
|
||||||
XScreenSaverInfo *screensaver_info;
|
XScreenSaverInfo *screensaver_info;
|
||||||
dimension_t window_dim;
|
|
||||||
unsigned long sep_custom_col;
|
|
||||||
} xctx_t;
|
} xctx_t;
|
||||||
|
|
||||||
typedef struct _color_t {
|
typedef struct _color_t {
|
||||||
@ -44,25 +48,23 @@ typedef struct _color_t {
|
|||||||
extern xctx_t xctx;
|
extern xctx_t xctx;
|
||||||
|
|
||||||
/* window */
|
/* window */
|
||||||
void x_win_draw(void);
|
window_x11 *x_win_create(void);
|
||||||
void x_win_hide(void);
|
void x_win_destroy(window_x11 *win);
|
||||||
void x_win_show(void);
|
|
||||||
|
|
||||||
/* shortcut */
|
void x_win_show(window_x11 *win);
|
||||||
void x_shortcut_init(keyboard_shortcut *shortcut);
|
void x_win_hide(window_x11 *win);
|
||||||
void x_shortcut_ungrab(keyboard_shortcut *ks);
|
|
||||||
int x_shortcut_grab(keyboard_shortcut *ks);
|
void x_display_surface(cairo_surface_t *srf, window_x11 *win, const struct dimensions *dim);
|
||||||
KeySym x_shortcut_string_to_mask(const char *str);
|
|
||||||
|
bool x_win_visible(window_x11 *win);
|
||||||
|
cairo_t* x_win_get_context(window_x11 *win);
|
||||||
|
|
||||||
/* X misc */
|
/* X misc */
|
||||||
bool x_is_idle(void);
|
bool x_is_idle(void);
|
||||||
void x_setup(void);
|
void x_setup(void);
|
||||||
void x_free(void);
|
void x_free(void);
|
||||||
|
|
||||||
gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback,
|
struct geometry x_parse_geometry(const char *geom_str);
|
||||||
gpointer user_data);
|
|
||||||
gboolean x_mainloop_fd_check(GSource *source);
|
|
||||||
gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|
||||||
|
1
test/data/icons/invalid.png
Normal file
1
test/data/icons/invalid.png
Normal file
@ -0,0 +1 @@
|
|||||||
|
Got'cha! This has to be invalid!
|
1
test/data/icons/invalid.svg
Normal file
1
test/data/icons/invalid.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
Got'cha! This has to be invalid!
|
1
test/data/icons/path/invalid/icon1.png
Symbolic link
1
test/data/icons/path/invalid/icon1.png
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../invalid.png
|
1
test/data/icons/path/invalid/icon1.svg
Symbolic link
1
test/data/icons/path/invalid/icon1.svg
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../invalid.svg
|
1
test/data/icons/path/valid/icon1.png
Symbolic link
1
test/data/icons/path/valid/icon1.png
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../valid.png
|
1
test/data/icons/path/valid/icon1.svg
Symbolic link
1
test/data/icons/path/valid/icon1.svg
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../valid.svg
|
1
test/data/icons/path/valid/onlypng.png
Symbolic link
1
test/data/icons/path/valid/onlypng.png
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../valid.png
|
1
test/data/icons/path/valid/onlysvg.svg
Symbolic link
1
test/data/icons/path/valid/onlysvg.svg
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../valid.svg
|
BIN
test/data/icons/valid.png
Normal file
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
65
test/data/icons/valid.svg
Normal 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
98
test/icon.c
Normal 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: */
|
@ -120,6 +120,8 @@ SUITE(suite_notification)
|
|||||||
g_free(b);
|
g_free(b);
|
||||||
|
|
||||||
RUN_TEST(test_notification_replace_single_field);
|
RUN_TEST(test_notification_replace_single_field);
|
||||||
|
|
||||||
|
g_clear_pointer(&settings.icon_path, g_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|
||||||
|
@ -8,6 +8,7 @@ SUITE_EXTERN(suite_utils);
|
|||||||
SUITE_EXTERN(suite_option_parser);
|
SUITE_EXTERN(suite_option_parser);
|
||||||
SUITE_EXTERN(suite_notification);
|
SUITE_EXTERN(suite_notification);
|
||||||
SUITE_EXTERN(suite_markup);
|
SUITE_EXTERN(suite_markup);
|
||||||
|
SUITE_EXTERN(suite_icon);
|
||||||
|
|
||||||
GREATEST_MAIN_DEFS();
|
GREATEST_MAIN_DEFS();
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ int main(int argc, char *argv[]) {
|
|||||||
RUN_SUITE(suite_option_parser);
|
RUN_SUITE(suite_option_parser);
|
||||||
RUN_SUITE(suite_notification);
|
RUN_SUITE(suite_notification);
|
||||||
RUN_SUITE(suite_markup);
|
RUN_SUITE(suite_markup);
|
||||||
|
RUN_SUITE(suite_icon);
|
||||||
GREATEST_MAIN_END();
|
GREATEST_MAIN_END();
|
||||||
}
|
}
|
||||||
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user