wayland: Add HiDPI support

This commit implements support for HiDPI rendering for wayland. X11
should be unaffected by this.
It is implemented by scaling everything that's rendered by a scale
factor that's obtained from the wayland protocol. All sizes before
rendering remain the same, so the same settings should provide the same
output for different scaling factors, only scaled by that factor.
This commit is contained in:
fwsmit 2021-04-21 13:27:45 +02:00
parent aad4dbaf3d
commit db5e6ce8f4
10 changed files with 170 additions and 74 deletions

View File

@ -141,10 +141,11 @@ static struct color layout_get_sepcolor(struct colored_layout *cl,
static void layout_setup_pango(PangoLayout *layout, int width) static void layout_setup_pango(PangoLayout *layout, int width)
{ {
int scale = output->get_scale();
pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
pango_layout_set_width(layout, width * PANGO_SCALE); pango_layout_set_width(layout, width * scale * PANGO_SCALE);
pango_layout_set_font_description(layout, pango_fdesc); pango_layout_set_font_description(layout, pango_fdesc);
pango_layout_set_spacing(layout, settings.line_height * PANGO_SCALE); pango_layout_set_spacing(layout, settings.line_height * scale * PANGO_SCALE);
PangoAlignment align; PangoAlignment align;
switch (settings.align) { switch (settings.align) {
@ -192,9 +193,20 @@ static bool have_progress_bar(const struct notification *n)
return (n->progress >= 0 && settings.progress_bar == true); return (n->progress >= 0 && settings.progress_bar == true);
} }
static void get_text_size(PangoLayout *l, int *w, int *h, int scale) {
pango_layout_get_pixel_size(l, w, h);
// scale the size down, because it may be rendered at higher DPI
if (w)
*w /= scale;
if (h)
*h /= scale;
}
static struct dimensions calculate_dimensions(GSList *layouts) static struct dimensions calculate_dimensions(GSList *layouts)
{ {
struct dimensions dim = { 0 }; struct dimensions dim = { 0 };
int scale = output->get_scale();
const struct screen_info *scr = output->get_active_screen(); const struct screen_info *scr = output->get_active_screen();
if (have_dynamic_width()) { if (have_dynamic_width()) {
@ -221,10 +233,10 @@ static struct dimensions calculate_dimensions(GSList *layouts)
for (GSList *iter = layouts; iter; iter = iter->next) { for (GSList *iter = layouts; iter; iter = iter->next) {
struct colored_layout *cl = iter->data; struct colored_layout *cl = iter->data;
int w=0,h=0; int w=0,h=0;
pango_layout_get_pixel_size(cl->l, &w, &h); get_text_size(cl->l, &w, &h, scale);
if (cl->icon) { if (cl->icon) {
h = MAX(cairo_image_surface_get_height(cl->icon), h); h = MAX(get_icon_height(cl->icon, scale), h);
w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; w += get_icon_width(cl->icon, scale) + settings.h_padding;
} }
h = MAX(settings.notification_height, h + settings.padding * 2); h = MAX(settings.notification_height, h + settings.padding * 2);
dim.h += h; dim.h += h;
@ -250,15 +262,15 @@ static struct dimensions calculate_dimensions(GSList *layouts)
w -= 2 * settings.h_padding; w -= 2 * settings.h_padding;
w -= 2 * settings.frame_width; w -= 2 * settings.frame_width;
if (cl->icon) { if (cl->icon) {
w -= cairo_image_surface_get_width(cl->icon) + get_text_icon_padding(); w -= get_icon_width(cl->icon, scale) + get_text_icon_padding();
} }
layout_setup_pango(cl->l, w); layout_setup_pango(cl->l, w);
/* re-read information */ /* re-read information */
pango_layout_get_pixel_size(cl->l, &w, &h); get_text_size(cl->l, &w, &h, scale);
if (cl->icon) { if (cl->icon) {
h = MAX(cairo_image_surface_get_height(cl->icon), h); h = MAX(get_icon_height(cl->icon, scale), h);
w += cairo_image_surface_get_width(cl->icon) + settings.h_padding; w += get_icon_width(cl->icon, scale) + settings.h_padding;
} }
h = MAX(settings.notification_height, h + settings.padding * 2); h = MAX(settings.notification_height, h + settings.padding * 2);
dim.h += h; dim.h += h;
@ -299,6 +311,7 @@ static struct colored_layout *layout_init_shared(cairo_t *c, const struct notifi
{ {
struct colored_layout *cl = g_malloc(sizeof(struct colored_layout)); struct colored_layout *cl = g_malloc(sizeof(struct colored_layout));
cl->l = layout_create(c); cl->l = layout_create(c);
int scale = output->get_scale();
if (!settings.word_wrap) { if (!settings.word_wrap) {
PangoEllipsizeMode ellipsize; PangoEllipsizeMode ellipsize;
@ -346,7 +359,7 @@ static struct colored_layout *layout_init_shared(cairo_t *c, const struct notifi
width -= 2 * settings.h_padding; width -= 2 * settings.h_padding;
width -= 2 * settings.frame_width; width -= 2 * settings.frame_width;
if (cl->icon) { if (cl->icon) {
width -= cairo_image_surface_get_width(cl->icon) + get_text_icon_padding(); width -= get_icon_width(cl->icon, scale) + get_text_icon_padding();
} }
layout_setup_pango(cl->l, width); layout_setup_pango(cl->l, width);
} }
@ -367,6 +380,7 @@ static struct colored_layout *layout_from_notification(cairo_t *c, struct notifi
{ {
struct colored_layout *cl = layout_init_shared(c, n); struct colored_layout *cl = layout_init_shared(c, n);
int scale = output->get_scale();
/* markup */ /* markup */
GError *err = NULL; GError *err = NULL;
@ -388,8 +402,8 @@ static struct colored_layout *layout_from_notification(cairo_t *c, struct notifi
} }
pango_layout_get_pixel_size(cl->l, NULL, &(n->displayed_height)); get_text_size(cl->l, NULL, &(n->displayed_height), scale);
if (cl->icon) n->displayed_height = MAX(cairo_image_surface_get_height(cl->icon), n->displayed_height); if (cl->icon) n->displayed_height = MAX(get_icon_height(cl->icon, scale), n->displayed_height);
n->displayed_height = n->displayed_height + settings.padding * 2; n->displayed_height = n->displayed_height + settings.padding * 2;
@ -435,14 +449,14 @@ static GSList *create_layouts(cairo_t *c)
} }
static int layout_get_height(struct colored_layout *cl) static int layout_get_height(struct colored_layout *cl, int scale)
{ {
int h; int h;
int h_icon = 0; int h_icon = 0;
int h_progress_bar = 0; int h_progress_bar = 0;
pango_layout_get_pixel_size(cl->l, NULL, &h); get_text_size(cl->l, NULL, &h, scale);
if (cl->icon) if (cl->icon)
h_icon = cairo_image_surface_get_height(cl->icon); h_icon = get_icon_height(cl->icon, scale);
if (have_progress_bar(cl->n)){ if (have_progress_bar(cl->n)){
h_progress_bar = settings.progress_bar_height + settings.padding; h_progress_bar = settings.progress_bar_height + settings.padding;
} }
@ -482,8 +496,14 @@ static int frame_internal_radius (int r, int w, int h)
* The top corners will get rounded by `corner_radius`, if `first` is set. * The top corners will get rounded by `corner_radius`, if `first` is set.
* Respectably the same for `last` with the bottom corners. * Respectably the same for `last` with the bottom corners.
*/ */
void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, bool first, bool last) void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, int scale, bool first, bool last)
{ {
width *= scale;
height *= scale;
x *= scale;
y *= scale;
corner_radius *= scale;
const float degrees = M_PI / 180.0; const float degrees = M_PI / 180.0;
cairo_new_sub_path(c); cairo_new_sub_path(c);
@ -531,6 +551,13 @@ void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corn
cairo_close_path(c); cairo_close_path(c);
} }
/**
* A small wrapper around cairo_rectange for drawing a scaled rectangle.
*/
void draw_rect(cairo_t *c, int x, int y, int width, int height, int scale) {
cairo_rectangle(c, x * scale, y * scale, width * scale, height * scale);
}
static cairo_surface_t *render_background(cairo_surface_t *srf, static cairo_surface_t *render_background(cairo_surface_t *srf,
struct colored_layout *cl, struct colored_layout *cl,
struct colored_layout *cl_next, struct colored_layout *cl_next,
@ -540,7 +567,8 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
int corner_radius, int corner_radius,
bool first, bool first,
bool last, bool last,
int *ret_width) int *ret_width,
int scale)
{ {
int x = 0; int x = 0;
int radius_int = corner_radius; int radius_int = corner_radius;
@ -560,7 +588,7 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
else else
height += settings.separator_height; height += settings.separator_height;
draw_rounded_rect(c, x, y, width, height, corner_radius, first, last); draw_rounded_rect(c, x, y, width, height, corner_radius, scale, first, last);
/* adding frame */ /* adding frame */
x += settings.frame_width; x += settings.frame_width;
@ -578,11 +606,11 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
radius_int = frame_internal_radius(corner_radius, settings.frame_width, height); radius_int = frame_internal_radius(corner_radius, settings.frame_width, height);
draw_rounded_rect(c, x, y, width, height, radius_int, first, last); draw_rounded_rect(c, x, y, width, height, radius_int, scale, first, last);
cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a); cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a);
cairo_fill(c); cairo_fill(c);
draw_rounded_rect(c, x, y, width, height, radius_int, first, last); draw_rounded_rect(c, x, y, width, height, radius_int, scale, first, last);
cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a); cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a);
cairo_fill(c); cairo_fill(c);
@ -594,7 +622,7 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
struct color sep_color = layout_get_sepcolor(cl, cl_next); struct color sep_color = layout_get_sepcolor(cl, cl_next);
cairo_set_source_rgba(c, sep_color.r, sep_color.g, sep_color.b, sep_color.a); cairo_set_source_rgba(c, sep_color.r, sep_color.g, sep_color.b, sep_color.a);
cairo_rectangle(c, settings.frame_width, y + height, width, settings.separator_height); draw_rect(c, settings.frame_width, y + height, width, settings.separator_height, scale);
cairo_fill(c); cairo_fill(c);
} }
@ -604,18 +632,18 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
if (ret_width) if (ret_width)
*ret_width = width; *ret_width = width;
return cairo_surface_create_for_rectangle(srf, x, y, width, height); return cairo_surface_create_for_rectangle(srf, x * scale, y * scale, width * scale, height * scale);
} }
static void render_content(cairo_t *c, struct colored_layout *cl, int width) static void render_content(cairo_t *c, struct colored_layout *cl, int width, int scale)
{ {
const int h = layout_get_height(cl); const int h = layout_get_height(cl, scale);
int h_without_progress_bar = h; int h_without_progress_bar = h;
if (have_progress_bar(cl->n)){ if (have_progress_bar(cl->n)){
h_without_progress_bar -= settings.progress_bar_height + settings.padding; h_without_progress_bar -= settings.progress_bar_height + settings.padding;
} }
int h_text; int h_text;
pango_layout_get_pixel_size(cl->l, NULL, &h_text); get_text_size(cl->l, NULL, &h_text, scale);
int text_x = settings.h_padding, int text_x = settings.h_padding,
text_y = settings.padding + h_without_progress_bar / 2 - h_text / 2; text_y = settings.padding + h_without_progress_bar / 2 - h_text / 2;
@ -633,10 +661,10 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width)
// icon position // icon position
if (settings.icon_position == ICON_LEFT) { if (settings.icon_position == ICON_LEFT) {
text_x = cairo_image_surface_get_width(cl->icon) + settings.h_padding + get_text_icon_padding(); text_x = get_icon_width(cl->icon, scale) + settings.h_padding + get_text_icon_padding();
} // else ICON_RIGHT } // else ICON_RIGHT
} }
cairo_move_to(c, text_x, text_y); cairo_move_to(c, text_x * scale, text_y * scale);
cairo_set_source_rgba(c, cl->fg.r, cl->fg.g, cl->fg.b, cl->fg.a); cairo_set_source_rgba(c, cl->fg.r, cl->fg.g, cl->fg.b, cl->fg.a);
pango_cairo_update_layout(c, cl->l); pango_cairo_update_layout(c, cl->l);
@ -645,8 +673,8 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width)
// icon positioning // icon positioning
if (cl->icon) { if (cl->icon) {
unsigned int image_width = cairo_image_surface_get_width(cl->icon), unsigned int image_width = get_icon_width(cl->icon, scale),
image_height = cairo_image_surface_get_height(cl->icon), image_height = get_icon_height(cl->icon, scale),
image_x = width - settings.h_padding - image_width, image_x = width - settings.h_padding - image_width,
image_y = settings.padding + h_without_progress_bar/2 - image_height/2; image_y = settings.padding + h_without_progress_bar/2 - image_height/2;
@ -664,8 +692,8 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width)
image_x = settings.h_padding; image_x = settings.h_padding;
} // else ICON_RIGHT } // else ICON_RIGHT
cairo_set_source_surface(c, cl->icon, image_x, image_y); cairo_set_source_surface(c, cl->icon, image_x * scale, image_y * scale);
cairo_rectangle(c, image_x, image_y, image_width, image_height); draw_rect(c, image_x, image_y, image_width, image_height, scale);
cairo_fill(c); cairo_fill(c);
} }
@ -689,16 +717,17 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width)
// Note: the bar could be drawn a bit smaller, because the frame is drawn on top // Note: the bar could be drawn a bit smaller, because the frame is drawn on top
// left side // left side
cairo_set_source_rgba(c, cl->highlight.r, cl->highlight.g, cl->highlight.b, cl->highlight.a); cairo_set_source_rgba(c, cl->highlight.r, cl->highlight.g, cl->highlight.b, cl->highlight.a);
cairo_rectangle(c, x_bar_1, frame_y, progress_width_1, progress_height); draw_rect(c, x_bar_1, frame_y, progress_width_1, progress_height, scale);
cairo_fill(c); cairo_fill(c);
// right side // right side
cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a); cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a);
cairo_rectangle(c, x_bar_2, frame_y, progress_width_2, progress_height); draw_rect(c, x_bar_2, frame_y, progress_width_2, progress_height, scale);
cairo_fill(c); cairo_fill(c);
// border // border
cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a); cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a);
cairo_rectangle(c, frame_x + half_frame_width, frame_y + half_frame_width, progress_width - frame_width, progress_height); // TODO draw_rect instead of cairo_rectangle resulted in blurry lines. Why?
cairo_set_line_width(c, frame_width); cairo_rectangle(c, (frame_x + half_frame_width) * scale, (frame_y + half_frame_width) * scale, (progress_width - frame_width) * scale, progress_height * scale);
cairo_set_line_width(c, frame_width * scale);
cairo_stroke(c); cairo_stroke(c);
} }
} }
@ -710,18 +739,19 @@ static struct dimensions layout_render(cairo_surface_t *srf,
bool first, bool first,
bool last) bool last)
{ {
const int cl_h = layout_get_height(cl); int scale = output->get_scale();
const int cl_h = layout_get_height(cl, scale);
int h_text = 0; int h_text = 0;
pango_layout_get_pixel_size(cl->l, NULL, &h_text); get_text_size(cl->l, NULL, &h_text, scale);
int bg_width = 0; int bg_width = 0;
int bg_height = MAX(settings.notification_height, (2 * settings.padding) + cl_h); int bg_height = MAX(settings.notification_height, (2 * settings.padding) + cl_h);
cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, dim.corner_radius, first, last, &bg_width); cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, dim.corner_radius, first, last, &bg_width, scale);
cairo_t *c = cairo_create(content); cairo_t *c = cairo_create(content);
render_content(c, cl, bg_width); render_content(c, cl, bg_width, scale);
/* adding frame */ /* adding frame */
if (first) if (first)
@ -773,8 +803,9 @@ void draw(void)
GSList *layouts = create_layouts(output->win_get_context(win)); GSList *layouts = create_layouts(output->win_get_context(win));
struct dimensions dim = calculate_dimensions(layouts); struct dimensions dim = calculate_dimensions(layouts);
int scale = output->get_scale();
cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dim.w, dim.h); cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dim.w * scale, dim.h * scale);
bool first = true; bool first = true;
for (GSList *iter = layouts; iter; iter = iter->next) { for (GSList *iter = layouts; iter; iter = iter->next) {
@ -799,4 +830,14 @@ void draw_deinit(void)
output->win_destroy(win); output->win_destroy(win);
output->deinit(); output->deinit();
} }
int draw_get_scale(void)
{
if (output) {
return output->get_scale();
} else {
LOG_W("Called draw_get_scale before output init");
return 1;
}
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -12,9 +12,13 @@ void draw_setup(void);
void draw(void); void draw(void);
void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, bool first, bool last); void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, int scale, bool first, bool last);
// TODO get rid of this function by passing scale to everything that needs it.
int draw_get_scale(void);
void draw_deinit(void); void draw_deinit(void);
#endif #endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -24,6 +24,7 @@
GMainLoop *mainloop = NULL; GMainLoop *mainloop = NULL;
static struct dunst_status status; static struct dunst_status status;
static bool setup_done = false;
/* see dunst.h */ /* see dunst.h */
void dunst_status(const enum dunst_status_field field, void dunst_status(const enum dunst_status_field field,
@ -56,6 +57,14 @@ static gboolean run(void *data);
void wake_up(void) void wake_up(void)
{ {
// If wake_up is being called before the output has been setup we should
// return.
if (!setup_done)
{
LOG_D("Ignoring wake up");
return;
}
LOG_D("Waking up"); LOG_D("Waking up");
run(NULL); run(NULL);
} }
@ -197,6 +206,7 @@ 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
} }
setup_done = true;
run(NULL); run(NULL);
g_main_loop_run(mainloop); g_main_loop_run(mainloop);
g_clear_pointer(&mainloop, g_main_loop_unref); g_clear_pointer(&mainloop, g_main_loop_unref);

View File

@ -86,6 +86,14 @@ static void pixbuf_data_to_cairo_data(
} }
} }
int get_icon_width(cairo_surface_t *icon, int scale) {
return cairo_image_surface_get_width(icon) / scale;
}
int get_icon_height(cairo_surface_t *icon, int scale) {
return cairo_image_surface_get_height(icon) / scale;
}
cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf) cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf)
{ {
assert(pixbuf); assert(pixbuf);
@ -144,22 +152,26 @@ static bool icon_size_clamp(int *w, int *h) {
* *
* @param pixbuf (nullable) The pixbuf, which may be too big. * @param pixbuf (nullable) The pixbuf, which may be too big.
* Takes ownership of the reference. * Takes ownership of the reference.
* @param dpi_scale An integer for the dpi scaling. That doesn't mean the icon
* is always scaled by dpi_scale.
* @return the scaled version of the pixbuf. If scaling wasn't * @return the scaled version of the pixbuf. If scaling wasn't
* necessary, it returns the same pixbuf. Transfers full * necessary, it returns the same pixbuf. Transfers full
* ownership of the reference. * ownership of the reference.
*/ */
static GdkPixbuf *icon_pixbuf_scale(GdkPixbuf *pixbuf) static GdkPixbuf *icon_pixbuf_scale(GdkPixbuf *pixbuf, int dpi_scale)
{ {
ASSERT_OR_RET(pixbuf, NULL); ASSERT_OR_RET(pixbuf, NULL);
int w = gdk_pixbuf_get_width(pixbuf); int w = gdk_pixbuf_get_width(pixbuf);
int h = gdk_pixbuf_get_height(pixbuf); int h = gdk_pixbuf_get_height(pixbuf);
// TODO immediately rescale icon upon scale changes
if (icon_size_clamp(&w, &h)) { if (icon_size_clamp(&w, &h)) {
GdkPixbuf *scaled = gdk_pixbuf_scale_simple( GdkPixbuf *scaled = gdk_pixbuf_scale_simple(
pixbuf, pixbuf,
w, w * dpi_scale,
h, h * dpi_scale,
GDK_INTERP_BILINEAR); GDK_INTERP_BILINEAR);
g_object_unref(pixbuf); g_object_unref(pixbuf);
pixbuf = scaled; pixbuf = scaled;
@ -168,7 +180,7 @@ static GdkPixbuf *icon_pixbuf_scale(GdkPixbuf *pixbuf)
return pixbuf; return pixbuf;
} }
GdkPixbuf *get_pixbuf_from_file(const char *filename) GdkPixbuf *get_pixbuf_from_file(const char *filename, int scale)
{ {
char *path = string_to_path(g_strdup(filename)); char *path = string_to_path(g_strdup(filename));
GError *error = NULL; GError *error = NULL;
@ -179,10 +191,11 @@ GdkPixbuf *get_pixbuf_from_file(const char *filename)
g_free(path); g_free(path);
return NULL; return NULL;
} }
// TODO immediately rescale icon upon scale changes
icon_size_clamp(&w, &h); icon_size_clamp(&w, &h);
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_scale(path, GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_scale(path,
w, w * scale,
h, h * scale,
TRUE, TRUE,
&error); &error);
@ -250,7 +263,7 @@ char *get_path_from_icon_name(const char *iconname)
return new_name; return new_name;
} }
GdkPixbuf *get_pixbuf_from_icon(const char *iconname) GdkPixbuf *get_pixbuf_from_icon(const char *iconname, int scale)
{ {
char *path = get_path_from_icon_name(iconname); char *path = get_path_from_icon_name(iconname);
if (!path) { if (!path) {
@ -259,7 +272,7 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
GdkPixbuf *pixbuf = NULL; GdkPixbuf *pixbuf = NULL;
pixbuf = get_pixbuf_from_file(path); pixbuf = get_pixbuf_from_file(path, scale);
g_free(path); g_free(path);
if (!pixbuf) if (!pixbuf)
@ -268,18 +281,18 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
return pixbuf; return pixbuf;
} }
GdkPixbuf *icon_get_for_name(const char *name, char **id) GdkPixbuf *icon_get_for_name(const char *name, char **id, int scale)
{ {
ASSERT_OR_RET(name, NULL); ASSERT_OR_RET(name, NULL);
ASSERT_OR_RET(id, NULL); ASSERT_OR_RET(id, NULL);
GdkPixbuf *pb = get_pixbuf_from_icon(name); GdkPixbuf *pb = get_pixbuf_from_icon(name, scale);
if (pb) if (pb)
*id = g_strdup(name); *id = g_strdup(name);
return pb; return pb;
} }
GdkPixbuf *icon_get_for_data(GVariant *data, char **id) GdkPixbuf *icon_get_for_data(GVariant *data, char **id, int dpi_scale)
{ {
ASSERT_OR_RET(data, NULL); ASSERT_OR_RET(data, NULL);
ASSERT_OR_RET(id, NULL); ASSERT_OR_RET(id, NULL);
@ -383,7 +396,7 @@ GdkPixbuf *icon_get_for_data(GVariant *data, char **id)
g_free(data_chk); g_free(data_chk);
g_variant_unref(data_variant); g_variant_unref(data_variant);
pixbuf = icon_pixbuf_scale(pixbuf); pixbuf = icon_pixbuf_scale(pixbuf, dpi_scale);
return pixbuf; return pixbuf;
} }

View File

@ -11,11 +11,26 @@ cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf);
/** Retrieve an icon by its full filepath, scaled according to settings. /** Retrieve an icon by its full filepath, scaled according to settings.
* *
* @param filename A string representing a readable file path * @param filename A string representing a readable file path
* @param scale An integer representing the output dpi scaling.
* *
* @return an instance of `GdkPixbuf` * @return an instance of `GdkPixbuf`
* @retval NULL: file does not exist, not readable, etc.. * @retval NULL: file does not exist, not readable, etc..
*/ */
GdkPixbuf *get_pixbuf_from_file(const char *filename); GdkPixbuf *get_pixbuf_from_file(const char *filename, int scale);
/**
* Get the unscaled icon width.
*
* If scale is 2 for example, the icon will render in twice the size, but
* get_icon_width still returns the same size as when scale is 1.
*/
int get_icon_width(cairo_surface_t *icon, int scale);
/**
* Get the unscaled icon height, see get_icon_width.
*/
int get_icon_height(cairo_surface_t *icon, int scale);
/** Retrieve a path from an icon name. /** Retrieve a path from an icon name.
* *
@ -33,11 +48,12 @@ char *get_path_from_icon_name(const char *iconname);
* @param iconname A string describing a `file://` URL, an arbitary filename * @param iconname A string describing a `file://` URL, an arbitary filename
* or an icon name, which then gets searched for in the * or an icon name, which then gets searched for in the
* settings.icon_path * settings.icon_path
* @param scale An integer representing the output dpi scaling.
* *
* @return an instance of `GdkPixbuf` * @return an instance of `GdkPixbuf`
* @retval NULL: file does not exist, not readable, etc.. * @retval NULL: file does not exist, not readable, etc..
*/ */
GdkPixbuf *get_pixbuf_from_icon(const char *iconname); GdkPixbuf *get_pixbuf_from_icon(const char *iconname, int scale);
/** Read an icon from disk and convert it to a GdkPixbuf, scaled according to settings /** Read an icon from disk and convert it to a GdkPixbuf, scaled according to settings
* *
@ -49,10 +65,11 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname);
* get searched in the folders of the icon_path setting. * get searched in the folders of the icon_path setting.
* @param id (necessary) A unique identifier of the returned pixbuf. Only filled, * @param id (necessary) A unique identifier of the returned pixbuf. Only filled,
* if the return value is non-NULL. * if the return value is non-NULL.
* @param scale An integer representing the output dpi scaling.
* @return an instance of `GdkPixbuf`, representing the name's image * @return an instance of `GdkPixbuf`, representing the name's image
* @retval NULL: Invalid path given * @retval NULL: Invalid path given
*/ */
GdkPixbuf *icon_get_for_name(const char *name, char **id); GdkPixbuf *icon_get_for_name(const char *name, char **id, int dpi_scale);
/** Convert a GVariant like described in GdkPixbuf, scaled according to settings /** Convert a GVariant like described in GdkPixbuf, scaled according to settings
* *
@ -63,10 +80,11 @@ GdkPixbuf *icon_get_for_name(const char *name, char **id);
* like described in the notification spec. * like described in the notification spec.
* @param id (necessary) A unique identifier of the returned pixbuf. * @param id (necessary) A unique identifier of the returned pixbuf.
* Only filled, if the return value is non-NULL. * Only filled, if the return value is non-NULL.
* @param scale An integer representing the output dpi scaling.
* @return an instance of `GdkPixbuf` derived from the GVariant * @return an instance of `GdkPixbuf` derived from the GVariant
* @retval NULL: GVariant parameter nulled, invalid or in wrong format * @retval NULL: GVariant parameter nulled, invalid or in wrong format
*/ */
GdkPixbuf *icon_get_for_data(GVariant *data, char **id); GdkPixbuf *icon_get_for_data(GVariant *data, char **id, int scale);
#endif #endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -23,6 +23,7 @@
#include "rules.h" #include "rules.h"
#include "settings.h" #include "settings.h"
#include "utils.h" #include "utils.h"
#include "draw.h"
static void notification_extract_urls(struct notification *n); static void notification_extract_urls(struct notification *n);
static void notification_format_message(struct notification *n); static void notification_format_message(struct notification *n);
@ -314,7 +315,7 @@ void notification_icon_replace_path(struct notification *n, const char *new_icon
g_clear_object(&n->icon); g_clear_object(&n->icon);
g_clear_pointer(&n->icon_id, g_free); g_clear_pointer(&n->icon_id, g_free);
n->icon = icon_get_for_name(new_icon, &n->icon_id); n->icon = icon_get_for_name(new_icon, &n->icon_id, draw_get_scale());
} }
void notification_icon_replace_data(struct notification *n, GVariant *new_icon) void notification_icon_replace_data(struct notification *n, GVariant *new_icon)
@ -325,7 +326,7 @@ void notification_icon_replace_data(struct notification *n, GVariant *new_icon)
g_clear_object(&n->icon); g_clear_object(&n->icon);
g_clear_pointer(&n->icon_id, g_free); g_clear_pointer(&n->icon_id, g_free);
n->icon = icon_get_for_data(new_icon, &n->icon_id); n->icon = icon_get_for_data(new_icon, &n->icon_id, draw_get_scale());
} }
/* see notification.h */ /* see notification.h */

View File

@ -116,6 +116,8 @@ static void output_handle_scale(void *data, struct wl_output *wl_output,
int32_t factor) { int32_t factor) {
struct dunst_output *output = data; struct dunst_output *output = data;
output->scale = factor; output->scale = factor;
wake_up();
} }
static const struct wl_output_listener output_listener = { static const struct wl_output_listener output_listener = {
@ -622,7 +624,7 @@ static void schedule_frame_and_commit();
// Draw and commit a new frame. // Draw and commit a new frame.
static void send_frame() { static void send_frame() {
int scale = 1; int scale = wl_get_scale();
struct dunst_output *output = get_configured_output(); struct dunst_output *output = get_configured_output();
int height = ctx.cur_dim.h; int height = ctx.cur_dim.h;
@ -689,8 +691,6 @@ static void send_frame() {
if (ctx.height != height || ctx.width != width) { if (ctx.height != height || ctx.width != width) {
struct dimensions dim = ctx.cur_dim; struct dimensions dim = ctx.cur_dim;
// Set window size // Set window size
LOG_D("Window dimensions %ix%i", dim.w, dim.h);
LOG_D("Window position %ix%i", dim.x, dim.y);
zwlr_layer_surface_v1_set_size(ctx.layer_surface, zwlr_layer_surface_v1_set_size(ctx.layer_surface,
dim.w, dim.h); dim.w, dim.h);
@ -815,12 +815,15 @@ void wl_win_hide(window win) {
void wl_display_surface(cairo_surface_t *srf, window winptr, const struct dimensions* dim) { void wl_display_surface(cairo_surface_t *srf, window winptr, const struct dimensions* dim) {
/* struct window_wl *win = (struct window_wl*)winptr; */ /* struct window_wl *win = (struct window_wl*)winptr; */
ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers, dim->w, dim->h); int scale = wl_get_scale();
LOG_D("Buffer size (scaled) %ix%i", dim->w * scale, dim->h * scale);
ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers,
dim->w * scale, dim->h * scale);
cairo_t *c = ctx.current_buffer->cairo; cairo_t *c = ctx.current_buffer->cairo;
cairo_save(c); cairo_save(c);
cairo_set_source_surface(c, srf, 0, 0); cairo_set_source_surface(c, srf, 0, 0);
cairo_rectangle(c, 0, 0, dim->w, dim->h); cairo_rectangle(c, 0, 0, dim->w * scale, dim->h * scale);
cairo_fill(c); cairo_fill(c);
cairo_restore(c); cairo_restore(c);
@ -848,6 +851,7 @@ const struct screen_info* wl_get_active_screen(void) {
.id = 0, .id = 0,
.mmh = 500 .mmh = 500
}; };
scr.dpi = wl_get_scale() * 96;
return &scr; return &scr;
} }

View File

@ -24,6 +24,9 @@ const struct screen_info* wl_get_active_screen(void);
bool wl_is_idle(void); bool wl_is_idle(void);
bool wl_have_fullscreen_window(void); bool wl_have_fullscreen_window(void);
// Return the dpi scaling of the current output. Everything that's rendered
// should be multiplied by this value, but don't use it to multiply other
// values. All sizes should be in unscaled units.
int wl_get_scale(void); int wl_get_scale(void);
#endif #endif
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */ /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -113,7 +113,7 @@ static void x_win_corners_shape(struct window_x11 *win, const int rad)
draw_rounded_rect(cr, 0, 0, draw_rounded_rect(cr, 0, 0,
width, height, width, height,
rad, rad, 1,
true, true); true, true);
cairo_fill(cr); cairo_fill(cr);

View File

@ -12,6 +12,8 @@
extern const char *base; extern const char *base;
int scale = 1;
TEST test_get_path_from_icon_null(void){ TEST test_get_path_from_icon_null(void){
char *result = get_path_from_icon_name(NULL); char *result = get_path_from_icon_name(NULL);
ASSERT_EQ(result, NULL); ASSERT_EQ(result, NULL);
@ -86,7 +88,7 @@ TEST test_get_pixbuf_from_file_tilde(void)
gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL); gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL);
path = string_replace_at(path, 0, strlen(home), "~"); path = string_replace_at(path, 0, strlen(home), "~");
GdkPixbuf *pixbuf = get_pixbuf_from_file(path); GdkPixbuf *pixbuf = get_pixbuf_from_file(path, scale);
g_clear_pointer(&path, g_free); g_clear_pointer(&path, g_free);
ASSERT(pixbuf); ASSERT(pixbuf);
@ -101,7 +103,7 @@ TEST test_get_pixbuf_from_file_absolute(void)
gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL); gchar *path = g_build_filename(base, iconpath, "valid", "icon1.svg", NULL);
GdkPixbuf *pixbuf = get_pixbuf_from_file(path); GdkPixbuf *pixbuf = get_pixbuf_from_file(path, scale);
g_clear_pointer(&path, g_free); g_clear_pointer(&path, g_free);
ASSERT(pixbuf); ASSERT(pixbuf);
@ -113,7 +115,7 @@ TEST test_get_pixbuf_from_file_absolute(void)
TEST test_get_pixbuf_from_icon_invalid(void) TEST test_get_pixbuf_from_icon_invalid(void)
{ {
GdkPixbuf *pixbuf = get_pixbuf_from_icon("invalid"); GdkPixbuf *pixbuf = get_pixbuf_from_icon("invalid", scale);
ASSERT(pixbuf == NULL); ASSERT(pixbuf == NULL);
g_clear_pointer(&pixbuf, g_object_unref); g_clear_pointer(&pixbuf, g_object_unref);
@ -122,7 +124,7 @@ TEST test_get_pixbuf_from_icon_invalid(void)
TEST test_get_pixbuf_from_icon_both(void) TEST test_get_pixbuf_from_icon_both(void)
{ {
GdkPixbuf *pixbuf = get_pixbuf_from_icon("icon1"); GdkPixbuf *pixbuf = get_pixbuf_from_icon("icon1", scale);
// the first icon found is invalid, so the pixbuf is empty // the first icon found is invalid, so the pixbuf is empty
ASSERT(!pixbuf); ASSERT(!pixbuf);
g_clear_pointer(&pixbuf, g_object_unref); g_clear_pointer(&pixbuf, g_object_unref);
@ -132,7 +134,7 @@ TEST test_get_pixbuf_from_icon_both(void)
TEST test_get_pixbuf_from_icon_onlysvg(void) TEST test_get_pixbuf_from_icon_onlysvg(void)
{ {
GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlysvg"); GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlysvg", scale);
ASSERT(pixbuf); ASSERT(pixbuf);
ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf)); ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf));
g_clear_pointer(&pixbuf, g_object_unref); g_clear_pointer(&pixbuf, g_object_unref);
@ -142,7 +144,7 @@ TEST test_get_pixbuf_from_icon_onlysvg(void)
TEST test_get_pixbuf_from_icon_onlypng(void) TEST test_get_pixbuf_from_icon_onlypng(void)
{ {
GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng"); GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng", scale);
ASSERT(pixbuf); ASSERT(pixbuf);
ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf)); ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf));
g_clear_pointer(&pixbuf, g_object_unref); g_clear_pointer(&pixbuf, g_object_unref);
@ -153,7 +155,7 @@ TEST test_get_pixbuf_from_icon_onlypng(void)
TEST test_get_pixbuf_from_icon_filename(void) TEST test_get_pixbuf_from_icon_filename(void)
{ {
char *icon = g_strconcat(base, "/data/icons/valid.png", NULL); char *icon = g_strconcat(base, "/data/icons/valid.png", NULL);
GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon); GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon, scale);
ASSERT(pixbuf); ASSERT(pixbuf);
ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf)); ASSERTm("PNG pixbuf isn't loaded", IS_ICON_PNG(pixbuf));
g_clear_pointer(&pixbuf, g_object_unref); g_clear_pointer(&pixbuf, g_object_unref);
@ -165,7 +167,7 @@ TEST test_get_pixbuf_from_icon_filename(void)
TEST test_get_pixbuf_from_icon_fileuri(void) TEST test_get_pixbuf_from_icon_fileuri(void)
{ {
char *icon = g_strconcat("file://", base, "/data/icons/valid.svg", NULL); char *icon = g_strconcat("file://", base, "/data/icons/valid.svg", NULL);
GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon); GdkPixbuf *pixbuf = get_pixbuf_from_icon(icon, scale);
ASSERT(pixbuf); ASSERT(pixbuf);
ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf)); ASSERTm("SVG pixbuf isn't loaded", IS_ICON_SVG(pixbuf));
g_clear_pointer(&pixbuf, g_object_unref); g_clear_pointer(&pixbuf, g_object_unref);
@ -220,7 +222,7 @@ TEST test_icon_size_clamp_too_small_then_too_big(void)
TEST test_get_pixbuf_from_icon_both_is_scaled(void) TEST test_get_pixbuf_from_icon_both_is_scaled(void)
{ {
GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng"); GdkPixbuf *pixbuf = get_pixbuf_from_icon("onlypng", scale);
ASSERT(pixbuf); ASSERT(pixbuf);
ASSERT_EQ(gdk_pixbuf_get_width(pixbuf), 16); ASSERT_EQ(gdk_pixbuf_get_width(pixbuf), 16);
ASSERT_EQ(gdk_pixbuf_get_height(pixbuf), 16); ASSERT_EQ(gdk_pixbuf_get_height(pixbuf), 16);