Merge pull request #496 from dunst-project/refactor-drawing
Drawing refactor
This commit is contained in:
commit
de89328359
@ -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
|
||||
}
|
||||
|
1
Makefile
1
Makefile
@ -80,6 +80,7 @@ test-valgrind: test/test
|
||||
--leak-check=full \
|
||||
--show-leak-kinds=definite \
|
||||
--errors-for-leak-kinds=definite \
|
||||
--num-callers=40 \
|
||||
--error-exitcode=123 \
|
||||
./test
|
||||
|
||||
|
12
config.h
12
config.h
@ -16,7 +16,15 @@ settings_t defaults = {
|
||||
.icons = { "dialog-information", "dialog-information", "dialog-warning" }, /* low, normal, critical */
|
||||
|
||||
.transparency = 0, /* transparency */
|
||||
.geom = "0x0", /* geometry */
|
||||
.geometry = { .x = 0, /* geometry */
|
||||
.y = 0,
|
||||
.w = 0,
|
||||
.h = 0,
|
||||
.negative_x = 0,
|
||||
.negative_y = 0,
|
||||
.negative_width = 0,
|
||||
.width_set = 0
|
||||
},
|
||||
.title = "Dunst", /* the title of dunst notification windows */
|
||||
.class = "Dunst", /* the class of dunst notification windows */
|
||||
.shrink = false, /* shrinking */
|
||||
@ -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
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) */
|
||||
|
||||
#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
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: */
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
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
|
||||
#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: */
|
||||
|
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);
|
||||
|
||||
RUN_TEST(test_notification_replace_single_field);
|
||||
|
||||
g_clear_pointer(&settings.icon_path, g_free);
|
||||
}
|
||||
|
||||
/* 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_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: */
|
||||
|
Loading…
x
Reference in New Issue
Block a user