Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
draw.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
7
8#include <assert.h>
9#include <math.h>
10#include <pango/pango-attributes.h>
11#include <pango/pangocairo.h>
12#include <pango/pango-font.h>
13#include <pango/pango-layout.h>
14#include <pango/pango-types.h>
15#include <stdlib.h>
16#include <inttypes.h>
17#include <glib.h>
18
19#include "draw.h"
20#include "dunst.h"
21#include "icon.h"
22#include "log.h"
23#include "markup.h"
24#include "notification.h"
25#include "queues.h"
26#include "output.h"
27#include "settings.h"
28#include "utils.h"
29#include "icon-lookup.h"
30
32 PangoLayout *l;
33 char *text;
34 PangoAttrList *attr;
35 cairo_surface_t *icon;
36 struct notification *n;
37 bool is_xmore;
38};
39
40const struct output *output;
41window win;
42
43PangoFontDescription *pango_fdesc;
44
45// NOTE: Saves some characters
46#define COLOR(cl, field) (cl)->n->colors.field
47
48void load_icon_themes(void)
49{
50 bool loaded_theme = false;
51
52 for (int i = 0; settings.icon_theme[i] != NULL; i++) {
53 char *theme = settings.icon_theme[i];
54 int theme_index = load_icon_theme(theme);
55 if (theme_index >= 0) {
56 LOG_I("Adding icon theme %s", theme);
57 add_default_theme(theme_index);
58 loaded_theme = true;
59 }
60 }
61 if (!loaded_theme) {
62 int theme_index = load_icon_theme("hicolor");
63 add_default_theme(theme_index);
64 }
65
66}
67
68char *color_to_string(struct color c, char buf[10])
69{
70 if (!COLOR_VALID(c)) return NULL;
71
72 g_snprintf(buf, 10, "#%02x%02x%02x%02x",
73 (int)(c.r * 255),
74 (int)(c.g * 255),
75 (int)(c.b * 255),
76 (int)(c.a * 255));
77 return buf;
78}
79
80struct gradient *gradient_alloc(size_t length)
81{
82 if (length == 0)
83 return NULL;
84
85 struct gradient *grad = g_rc_box_alloc(sizeof(struct gradient) + length * sizeof(struct color));
86
87 grad->length = length;
88 grad->pattern = NULL;
89
90 return grad;
91}
92
93struct gradient *gradient_acquire(struct gradient *grad)
94{
95 return grad != NULL ? g_rc_box_acquire(grad) : NULL;
96}
97
98static void gradient_free(struct gradient *grad)
99{
100 if (grad->pattern)
101 cairo_pattern_destroy(grad->pattern);
102}
103
104void gradient_release(struct gradient *grad)
105{
106 if (grad != NULL)
107 g_rc_box_release_full(grad, (GDestroyNotify)gradient_free);
108}
109
110void gradient_pattern(struct gradient *grad)
111{
112 if (grad->length == 1) {
113 grad->pattern = cairo_pattern_create_rgba(grad->colors[0].r,
114 grad->colors[0].g,
115 grad->colors[0].b,
116 grad->colors[0].a);
117 } else {
118 grad->pattern = cairo_pattern_create_linear(0, 0, 1, 0);
119 for (size_t i = 0; i < grad->length; i++) {
120 double offset = i / (double)(grad->length - 1);
121 cairo_pattern_add_color_stop_rgba(grad->pattern,
122 offset,
123 grad->colors[i].r,
124 grad->colors[i].g,
125 grad->colors[i].b,
126 grad->colors[i].a);
127 }
128 }
129}
130
131char *gradient_to_string(const struct gradient *grad)
132{
133 if (!GRADIENT_VALID(grad)) return NULL;
134
135 int max = grad->length * 11 + 1;
136 char *buf = g_malloc(max);
137
138 for (size_t i = 0, j = 0; i < grad->length; i++) {
139 j += g_snprintf(buf + j, max - j, "#%02x%02x%02x%02x",
140 (int)(grad->colors[i].r * 255),
141 (int)(grad->colors[i].g * 255),
142 (int)(grad->colors[i].b * 255),
143 (int)(grad->colors[i].a * 255));
144
145 if (i != grad->length - 1) {
146 j += g_snprintf(buf + j, max - j, ", ");
147 }
148 }
149
150 return buf;
151}
152
153void draw_setup(void)
154{
155 const struct output *out = output_create(settings.force_xwayland);
156 output = out;
157
158 win = out->win_create();
159
160 LOG_D("Trying to load font: '%s'", settings.font);
161 pango_fdesc = pango_font_description_from_string(settings.font);
162 LOG_D("Loaded closest matching font: '%s'", pango_font_description_get_family(pango_fdesc));
163
164 if (settings.enable_recursive_icon_lookup)
165 load_icon_themes();
166}
167
168static inline double color_apply_delta(double base, double delta)
169{
170 base += delta;
171 if (base > 1)
172 base = 1;
173 if (base < 0)
174 base = 0;
175
176 return base;
177}
178
179static struct color calculate_foreground_color(struct color bg)
180{
181 double c_delta = 0.1;
182 struct color fg = bg;
183
184 /* do we need to darken or brighten the colors? */
185 bool darken = (bg.r + bg.g + bg.b) / 3 > 0.5;
186
187 int signedness = darken ? -1 : 1;
188
189 fg.r = color_apply_delta(fg.r, c_delta * signedness);
190 fg.g = color_apply_delta(fg.g, c_delta * signedness);
191 fg.b = color_apply_delta(fg.b, c_delta * signedness);
192
193 return fg;
194}
195
196static struct color layout_get_sepcolor(struct colored_layout *cl,
197 struct colored_layout *cl_next)
198{
199 switch (settings.sep_color.type) {
200 case SEP_FRAME:
201 return COLOR(cl_next->n->urgency > cl->n->urgency ? cl_next : cl, frame);
202 case SEP_CUSTOM:
203 return settings.sep_color.color;
204 case SEP_FOREGROUND:
205 return COLOR(cl, fg);
206 case SEP_AUTO:
207 return calculate_foreground_color(COLOR(cl, bg));
208 default:
209 LOG_E("Invalid %s enum value in %s:%d", "sep_color", __FILE__, __LINE__);
210 break;
211 }
212}
213
214static int get_horizontal_text_icon_padding(struct notification *n)
215{
216 bool horizontal_icon = (
217 n->icon && (n->icon_position == ICON_LEFT || n->icon_position == ICON_RIGHT)
218 );
219 if (settings.text_icon_padding && horizontal_icon) {
220 return settings.text_icon_padding;
221 } else {
222 return settings.h_padding;
223 }
224}
225
226static int get_vertical_text_icon_padding(struct notification *n)
227{
228 bool vertical_icon = n->icon && (n->icon_position == ICON_TOP);
229 if (settings.text_icon_padding && vertical_icon) {
230 return settings.text_icon_padding;
231 } else {
232 return settings.padding;
233 }
234}
235
236static bool have_progress_bar(const struct colored_layout *cl)
237{
238 return (cl->n->progress >= 0 && settings.progress_bar == true &&
239 !cl->is_xmore);
240}
241
242static void get_text_size(PangoLayout *l, int *w, int *h, double scale) {
243 pango_layout_get_pixel_size(l, w, h);
244 // scale the size down, because it may be rendered at higher DPI
245
246 if (w)
247 *w = ceil(*w / scale);
248 if (h)
249 *h = ceil(*h / scale);
250}
251
252// Set up pango for a given layout.
253// @param width The avaiable text width in pixels, used for caluclating alignment and wrapping
254// @param height The maximum text height in pixels.
255static void layout_setup_pango(PangoLayout *layout, int width, int height,
256 bool word_wrap, PangoEllipsizeMode ellipsize_mode,
257 PangoAlignment alignment)
258{
259 double scale = output->get_scale();
260 pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
261 pango_layout_set_width(layout, round(width * scale * PANGO_SCALE));
262
263 // When no height is set, word wrap is turned off
264 if (word_wrap)
265 pango_layout_set_height(layout, round(height * scale * PANGO_SCALE));
266
267 pango_layout_set_font_description(layout, pango_fdesc);
268 pango_layout_set_spacing(layout, round(settings.line_height * scale * PANGO_SCALE));
269
270 pango_layout_set_ellipsize(layout, ellipsize_mode);
271
272 pango_layout_set_alignment(layout, alignment);
273}
274
275// Set up the layout of a single notification
276// @param width Width of the layout
277// @param height Height of the layout
278static void layout_setup(struct colored_layout *cl, int width, int height, double scale)
279{
280 int horizontal_padding = get_horizontal_text_icon_padding(cl->n);
281 int icon_width = cl->icon ? get_icon_width(cl->icon, scale) + horizontal_padding : 0;
282 int text_width = width - 2 * settings.h_padding - (cl->n->icon_position == ICON_TOP ? 0 : icon_width);
283 int progress_bar_height = have_progress_bar(cl) ? settings.progress_bar_height + settings.padding : 0;
284 int max_text_height = MAX(0, settings.height.max - progress_bar_height - 2 * settings.padding);
285 layout_setup_pango(cl->l, text_width, max_text_height, cl->n->word_wrap, cl->n->ellipsize, cl->n->alignment);
286}
287
288static void free_colored_layout(void *data)
289{
290 struct colored_layout *cl = data;
291 g_object_unref(cl->l);
292 pango_attr_list_unref(cl->attr);
293 g_free(cl->text);
294 g_free(cl);
295}
296
297// calculates the minimum dimensions of the notification excluding the frame
298static struct dimensions calculate_notification_dimensions(struct colored_layout *cl, double scale)
299{
300 struct dimensions dim = { 0 };
301 layout_setup(cl, settings.width.max, settings.height.max, scale);
302
303 int horizontal_padding = get_horizontal_text_icon_padding(cl->n);
304 int icon_width = cl->icon? get_icon_width(cl->icon, scale) + horizontal_padding : 0;
305 int icon_height = cl->icon? get_icon_height(cl->icon, scale) : 0;
306 int progress_bar_height = have_progress_bar(cl) ? settings.progress_bar_height + settings.padding : 0;
307
308 int vertical_padding;
309 if (cl->n->hide_text) {
310 vertical_padding = 0;
311 dim.text_width = 0;
312 dim.text_height = 0;
313 } else {
314 get_text_size(cl->l, &dim.text_width, &dim.text_height, scale);
315 vertical_padding = get_vertical_text_icon_padding(cl->n);
316 }
317
318 if (cl->n->icon_position == ICON_TOP && cl->n->icon) {
319 dim.h = icon_height + dim.text_height + vertical_padding;
320 } else {
321 dim.h = MAX(icon_height, dim.text_height);
322 }
323
324 dim.h += progress_bar_height + settings.padding * 2;
325 dim.w = dim.text_width + icon_width + 2 * settings.h_padding;
326
327 if (have_progress_bar(cl))
328 dim.w = MAX(settings.progress_bar_min_width, dim.w);
329
330 dim.h = MAX(settings.height.min, dim.h);
331 dim.h = MIN(settings.height.max, dim.h);
332
333 dim.w = MAX(settings.width.min, dim.w);
334 dim.w = MIN(settings.width.max, dim.w);
335
336 cl->n->displayed_height = dim.h;
337 return dim;
338}
339
340static struct dimensions calculate_dimensions(GSList *layouts)
341{
342 int layout_count = g_slist_length(layouts);
343 struct dimensions dim = { 0 };
344 double scale = output->get_scale();
345
346 dim.corner_radius = settings.corner_radius;
347
348 for (GSList *iter = layouts; iter; iter = iter->next) {
349 struct colored_layout *cl = iter->data;
350 struct dimensions n_dim = calculate_notification_dimensions(cl, scale);
351 dim.h += n_dim.h;
352 LOG_D("Notification dimensions %ix%i", n_dim.w, n_dim.h);
353 dim.w = MAX(dim.w, n_dim.w + settings.frame_width);
354 }
355
356 dim.w += 2 * settings.frame_width;
357 dim.corner_radius = MIN(dim.corner_radius, dim.h/2);
358
359 /* clamp max width to screen width */
360 const struct screen_info *scr = output->get_active_screen();
361 int max_width = scr->w - settings.offset.x;
362 if (dim.w > max_width) {
363 dim.w = max_width;
364 }
365
366 if (settings.gap_size) {
367 int extra_frame_height = layout_count * (2 * settings.frame_width);
368 int extra_gap_height = (layout_count * settings.gap_size) - settings.gap_size;
369 int total_extra_height = extra_frame_height + extra_gap_height;
370 dim.h += total_extra_height;
371 } else {
372 dim.h += 2 * settings.frame_width;
373 dim.h += (layout_count - 1) * settings.separator_height;
374 }
375
376 return dim;
377}
378
379static PangoLayout *layout_create(cairo_t *c)
380{
381 const struct screen_info *screen = output->get_active_screen();
382
383 PangoContext *context = pango_cairo_create_context(c);
384 pango_cairo_context_set_resolution(context, screen->dpi);
385
386 PangoLayout *layout = pango_layout_new(context);
387
388 g_object_unref(context);
389
390 return layout;
391}
392
393static struct colored_layout *layout_init_shared(cairo_t *c, struct notification *n)
394{
395 struct colored_layout *cl = g_malloc(sizeof(struct colored_layout));
396 cl->l = layout_create(c);
397 cl->is_xmore = false;
398 cl->n = n;
399
400 // Invalid colors should never reach this point!
401 assert(settings.frame_width == 0 || COLOR_VALID(COLOR(cl, frame)));
402 assert(!have_progress_bar(cl) || COLOR(cl, highlight) != NULL);
403 assert(COLOR_VALID(COLOR(cl, fg)));
404 assert(COLOR_VALID(COLOR(cl, bg)));
405 return cl;
406}
407
408static struct colored_layout *layout_derive_xmore(cairo_t *c, struct notification *n, int qlen)
409{
410 struct colored_layout *cl = layout_init_shared(c, n);
411 cl->text = g_strdup_printf("(%d more)", qlen);
412 cl->attr = NULL;
413 cl->is_xmore = true;
414 cl->icon = NULL;
415 pango_layout_set_text(cl->l, cl->text, -1);
416 return cl;
417}
418
419static struct colored_layout *layout_from_notification(cairo_t *c, struct notification *n)
420{
421
422 struct colored_layout *cl = layout_init_shared(c, n);
423
424 if (n->icon_position != ICON_OFF && n->icon) {
425 cl->icon = n->icon;
426 } else {
427 cl->icon = NULL;
428 }
429
430 /* markup */
431 GError *err = NULL;
432 pango_parse_markup(n->text_to_render, -1, 0, &(cl->attr), &(cl->text), NULL, &err);
433
434 if (!err) {
435 pango_layout_set_text(cl->l, cl->text, -1);
436 pango_attr_list_insert(cl->attr, pango_attr_fallback_new(true));
437 pango_layout_set_attributes(cl->l, cl->attr);
438 } else {
439 /* remove markup and display plain message instead */
441 cl->text = NULL;
442 cl->attr = pango_attr_list_new();
443 pango_attr_list_insert(cl->attr, pango_attr_fallback_new(true));
444 pango_layout_set_text(cl->l, n->text_to_render, -1);
445 if (n->first_render) {
446 LOG_W("Unable to parse markup: %s", err->message);
447 }
448 g_error_free(err);
449 }
450
451 n->first_render = false;
452 return cl;
453}
454
455static GSList *create_layouts(cairo_t *c)
456{
457 GSList *layouts = NULL;
458
459 int qlen = queues_length_waiting();
460 bool xmore_is_needed = qlen > 0 && settings.indicate_hidden;
461
462 for (const GList *iter = queues_get_displayed();
463 iter; iter = iter->next)
464 {
465 struct notification *n = iter->data;
466
467 notification_update_text_to_render(n);
468
469 if (!iter->next && xmore_is_needed && settings.notification_limit == 1) {
470 char *new_ttr = g_strdup_printf("%s (%d more)", n->text_to_render, qlen);
471 g_free(n->text_to_render);
472 n->text_to_render = new_ttr;
473 }
474 layouts = g_slist_append(layouts,
475 layout_from_notification(c, n));
476 }
477
478 if (xmore_is_needed && settings.notification_limit != 1) {
479 /* append xmore message as new message */
480 layouts = g_slist_append(layouts,
481 layout_derive_xmore(c, queues_get_head_waiting(), qlen));
482 }
483
484 return layouts;
485}
486
487
488static int layout_get_height(struct colored_layout *cl, double scale)
489{
490 int h_text = 0;
491 int h_icon = 0;
492 int h_progress_bar = 0;
493
494 int vertical_padding;
495 if (cl->n->hide_text) {
496 vertical_padding = 0;
497 } else {
498 get_text_size(cl->l, NULL, &h_text, scale);
499 vertical_padding = get_vertical_text_icon_padding(cl->n);
500 }
501
502 if (cl->icon)
503 h_icon = get_icon_height(cl->icon, scale);
504
505 if (have_progress_bar(cl)) {
506 h_progress_bar = settings.progress_bar_height + settings.padding;
507 }
508
509 return (cl->n->icon_position == ICON_TOP && cl->n->icon)
510 ? h_icon + h_text + h_progress_bar + vertical_padding
511 : MAX(h_text, h_icon) + h_progress_bar;
512}
513
514/* Attempt to make internal radius more organic.
515 * Simple r-w is not enough for too small r/w ratio.
516 * simplifications: r/2 == r - w + w*w / (r * 2) with (w == r)
517 * r, w - corner radius & frame width,
518 * h - box height
519 */
520static int frame_internal_radius (int r, int w, int h)
521{
522 if (r == 0 || h + (w - r) * 2 == 0)
523 return 0;
524
525 // Integer precision scaler, using 1/4 of int size
526 const int s = 2 << (8 * sizeof(int) / 4);
527
528 int r1, r2, ret;
529 h *= s;
530 r *= s;
531 w *= s;
532 r1 = r - w + w * w / (r * 2); // w < r
533 r2 = r * h / (h + (w - r) * 2); // w >= r
534
535 ret = (r > w) ? r1 : (r / 2 < r2) ? r / 2 : r2;
536
537 return ret / s;
538}
539
543static inline void draw_rect(cairo_t *c, double x, double y, double width, double height, double scale)
544{
545 cairo_rectangle(c, round(x * scale), round(y * scale), round(width * scale), round(height * scale));
546}
547
556void draw_rounded_rect(cairo_t *c, float x, float y, int width, int height, int corner_radius, double scale, enum corner_pos corners)
557{
558 // Fast path for simple rects
559 if (corners == C_NONE || corner_radius <= 0) {
560 draw_rect(c, x, y, width, height, scale);
561 return;
562 }
563
564 width = round(width * scale);
565 height = round(height * scale);
566 x *= scale;
567 y *= scale;
568 corner_radius = round(corner_radius * scale);
569
570 // Nothing valid to draw
571 if (width <= 0 || height <= 0 || scale <= 0)
572 return;
573
574 const double degrees = M_PI / 180.0;
575
576 // This seems to be needed for a problem that occurs mostly when using cairo_stroke
577 // and not cairo_fill. Since the only case where we have partial angles is the progress
578 // bar and there we use fill, maybe this can be removed completely?
579 enum corner_pos skip = C_NONE;
580
581 float top_y_off = 0, bot_y_off = 0, top_x_off, bot_x_off;
582 top_x_off = bot_x_off = MAX(width - corner_radius, corner_radius);
583
584 double bot_left_angle1 = degrees * 90;
585 double bot_left_angle2 = degrees * 180;
586
587 double top_left_angle1 = degrees * 180;
588 double top_left_angle2 = degrees * 270;
589
590 double top_right_angle1 = degrees * 270;
591 double top_right_angle2 = degrees * 360;
592
593 double bot_right_angle1 = degrees * 0;
594 double bot_right_angle2 = degrees * 90;
595
596 // The trickiest cases to handle are when the width is less than corner_radius and corner_radius * 2,
597 // because we have to split the angle for the arc in the rounded corner
598 if (width <= corner_radius) {
599 double angle1 = 0, angle2 = 0;
600
601 // If there are two corners on the top/bottom they occupy half of the width
602 if ((corners & C_TOP) == C_TOP)
603 angle1 = acos(1.0 - ((double)width / 2.0) / (double)corner_radius);
604 else
605 angle1 = acos(1.0 - (double)width / (double)corner_radius);
606
607 if ((corners & C_BOT) == C_BOT)
608 angle2 = acos(1.0 - ((double)width / 2.0) / (double)corner_radius);
609 else
610 angle2 = acos(1.0 - (double)width / (double)corner_radius);
611
612 if ((corners & (C_TOP_RIGHT | C_BOT_LEFT)) == (C_TOP_RIGHT | C_BOT_LEFT) && !(corners & C_TOP_LEFT)) {
613 top_y_off -= corner_radius * (1.0 - sin(angle1));
614 }
615
616 if ((corners & (C_TOP_LEFT | C_BOT_RIGHT)) == (C_TOP_LEFT | C_BOT_RIGHT) && !(corners & C_BOT_LEFT)) {
617 bot_y_off = corner_radius * (1.0 - sin(angle2));
618 }
619
620 top_left_angle2 = degrees * 180 + angle1;
621 top_right_angle1 = degrees * 360 - angle1;
622 bot_left_angle1 = degrees * 180 - angle2;
623 bot_right_angle2 = angle2;
624
625 top_x_off = -(corner_radius - width);
626 bot_x_off = -(corner_radius - width);
627
628 if (corners != C_TOP && corners != C_BOT)
629 skip = ~corners;
630
631 } else if (width <= corner_radius * 2 && (corners & C_LEFT && corners & C_RIGHT)) {
632 double angle1 = 0, angle2 = 0;
633 if (!(corners & C_TOP_LEFT) && corners & C_TOP_RIGHT)
634 top_x_off = width - corner_radius;
635 else
636 angle1 = acos((double)width / (double)corner_radius - 1.0);
637
638 if (!(corners & C_BOT_LEFT) && corners & C_BOT_RIGHT)
639 bot_x_off = width - corner_radius;
640 else
641 angle2 = acos((double)width / (double)corner_radius - 1.0);
642
643 top_right_angle2 = degrees * 360 - angle1;
644 bot_right_angle1 = angle2;
645 }
646
647 cairo_new_sub_path(c);
648
649 // bottom left
650 if (!(skip & C_BOT_LEFT)) {
651 if (corners & C_BOT_LEFT) {
652 cairo_arc(c,
653 x + corner_radius,
654 y + height - corner_radius,
655 corner_radius,
656 bot_left_angle1,
657 bot_left_angle2);
658 } else {
659 cairo_line_to(c, x, y + height);
660 }
661 }
662
663 // top left
664 if (!(skip & C_TOP_LEFT)) {
665 if (corners & C_TOP_LEFT) {
666 cairo_arc(c,
667 x + corner_radius,
668 y + corner_radius,
669 corner_radius,
670 top_left_angle1,
671 top_left_angle2);
672 } else {
673 cairo_line_to(c, x, y);
674 }
675 }
676
677 // top right
678 if (!(skip & C_TOP_RIGHT)) {
679 if (corners & C_TOP_RIGHT) {
680 cairo_arc(c,
681 x + top_x_off,
682 y + corner_radius + top_y_off,
683 corner_radius,
684 top_right_angle1,
685 top_right_angle2);
686 } else {
687 cairo_line_to(c, x + width, y);
688 }
689 }
690
691 // bottom right
692 if (!(skip & C_BOT_RIGHT)) {
693 if (corners & C_BOT_RIGHT) {
694 cairo_arc(c,
695 x + bot_x_off,
696 y + height - corner_radius + bot_y_off,
697 corner_radius,
698 bot_right_angle1,
699 bot_right_angle2);
700 } else {
701 cairo_line_to(c, x + width, y + height);
702 }
703 }
704
705 cairo_close_path(c);
706}
707
708static cairo_surface_t *render_background(cairo_surface_t *srf,
709 struct colored_layout *cl,
710 struct colored_layout *cl_next,
711 int y,
712 int width,
713 int height,
714 int corner_radius,
715 enum corner_pos corners,
716 int *ret_width,
717 double scale)
718{
719 int x = 0;
720 int radius_int = corner_radius;
721
722 cairo_t *c = cairo_create(srf);
723
724 /* stroke area doesn't intersect with main area */
725 cairo_set_fill_rule(c, CAIRO_FILL_RULE_EVEN_ODD);
726
727 /* for correct combination of adjacent areas */
728 cairo_set_operator(c, CAIRO_OPERATOR_ADD);
729
730 if (corners & (C_TOP | _C_FIRST))
731 height += settings.frame_width;
732 if (corners & (C_BOT | _C_LAST))
733 height += settings.frame_width;
734 else
735 height += settings.separator_height;
736
737 draw_rounded_rect(c, x, y, width, height, corner_radius, scale, corners);
738
739 /* adding frame */
740 x += settings.frame_width;
741 if (corners & (C_TOP | _C_FIRST)) {
742 y += settings.frame_width;
743 height -= settings.frame_width;
744 }
745
746 width -= 2 * settings.frame_width;
747
748 if (corners & (C_BOT | _C_LAST))
749 height -= settings.frame_width;
750 else
751 height -= settings.separator_height;
752
753 radius_int = frame_internal_radius(corner_radius, settings.frame_width, height);
754
755 draw_rounded_rect(c, x, y, width, height, radius_int, scale, corners);
756 cairo_set_source_rgba(c, COLOR(cl, frame.r), COLOR(cl, frame.g), COLOR(cl, frame.b), COLOR(cl, frame.a));
757 cairo_fill(c);
758
759 draw_rounded_rect(c, x, y, width, height, radius_int, scale, corners);
760 cairo_set_source_rgba(c, COLOR(cl, bg.r), COLOR(cl, bg.g), COLOR(cl, bg.b), COLOR(cl, bg.a));
761 cairo_fill(c);
762
763 cairo_set_operator(c, CAIRO_OPERATOR_SOURCE);
764
765 if ( settings.sep_color.type != SEP_FRAME
766 && settings.separator_height > 0
767 && (corners & (C_BOT | _C_LAST)) == 0) {
768 struct color sep_color = layout_get_sepcolor(cl, cl_next);
769 cairo_set_source_rgba(c, sep_color.r, sep_color.g, sep_color.b, sep_color.a);
770
771 draw_rect(c, settings.frame_width, y + height, width, settings.separator_height, scale);
772
773 cairo_fill(c);
774 }
775
776 cairo_destroy(c);
777
778 if (ret_width)
779 *ret_width = width;
780
781 return cairo_surface_create_for_rectangle(srf,
782 round(x * scale), round(y * scale),
783 round(width * scale), round(height * scale));
784}
785
786static void render_content(cairo_t *c, struct colored_layout *cl, int width, int height, double scale)
787{
788 // Redo layout setup, while knowing the width. This is to make
789 // alignment work correctly
790 layout_setup(cl, width, height, scale);
791
792 // NOTE: Includes paddings!
793 int h_without_progress_bar = height;
794 if (have_progress_bar(cl)) {
795 h_without_progress_bar -= settings.progress_bar_height + settings.padding;
796 }
797
798 int text_h = 0;
799 if (!cl->n->hide_text) {
800 get_text_size(cl->l, NULL, &text_h, scale);
801 }
802
803 // text vertical alignment
804 int text_x = settings.h_padding,
805 text_y = settings.padding;
806
807 if (settings.vertical_alignment == VERTICAL_CENTER) {
808 text_y = h_without_progress_bar / 2 - text_h / 2;
809 } else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
810 text_y = h_without_progress_bar - settings.padding - text_h;
811 if (text_y < 0) text_y = settings.padding;
812 } // else VERTICAL_TOP
813
814 // icon positioning
815 if (cl->icon && cl->n->icon_position != ICON_OFF) {
816 int image_width = get_icon_width(cl->icon, scale),
817 image_height = get_icon_height(cl->icon, scale),
818 image_x = width - settings.h_padding - image_width,
819 image_y = text_y,
820 v_padding = get_vertical_text_icon_padding(cl->n);
821
822 // vertical alignment
823 switch (settings.vertical_alignment) {
824 case VERTICAL_TOP:
825 if (cl->n->icon_position == ICON_TOP) {
826 // Shift text downward
827 text_y += image_height + v_padding;
828 }
829 break;
830 case VERTICAL_CENTER:
831 if (cl->n->icon_position == ICON_TOP) {
832 // Adjust text and image by half
833 image_y -= (image_height + v_padding) / 2;
834 text_y += (image_height + v_padding) / 2;
835 } else {
836 image_y += text_h / 2 - image_height / 2;
837 }
838 break;
839 case VERTICAL_BOTTOM:
840 if (cl->n->icon_position == ICON_TOP) {
841 image_y -= image_height + v_padding;
842 } else {
843 image_y -= image_height - text_h;
844 }
845 break;
846 }
847
848 // icon position
849 if (cl->n->icon_position == ICON_TOP) {
850 image_x = (width - image_width) / 2;
851 } else if (cl->n->icon_position == ICON_LEFT) {
852 image_x = settings.h_padding;
853 text_x += image_width + get_horizontal_text_icon_padding(cl->n);
854 } // else ICON_RIGHT
855
856 cairo_set_source_surface(c, cl->icon, round(image_x * scale), round(image_y * scale));
857 draw_rounded_rect(c, image_x, image_y, image_width, image_height, settings.icon_corner_radius, scale, settings.icon_corners);
858 cairo_fill(c);
859 }
860
861 // text positioning
862 if (!cl->n->hide_text) {
863 cairo_move_to(c, round(text_x * scale), round(text_y * scale));
864 cairo_set_source_rgba(c, COLOR(cl, fg.r), COLOR(cl, fg.g), COLOR(cl, fg.b), COLOR(cl, fg.a));
865 pango_cairo_update_layout(c, cl->l);
866 pango_cairo_show_layout(c, cl->l);
867 }
868
869 // progress bar positioning
870 if (have_progress_bar(cl)) {
871 int progress = MIN(cl->n->progress, 100);
872 unsigned int frame_x = 0;
873 unsigned int frame_width = settings.progress_bar_frame_width,
874 progress_width = MIN(width - 2 * settings.h_padding, settings.progress_bar_max_width),
875 progress_height = settings.progress_bar_height - frame_width,
876 frame_y = h_without_progress_bar,
877 progress_width_without_frame = progress_width - 2 * frame_width,
878 progress_width_1 = progress_width_without_frame * progress / 100,
879 progress_width_2 = progress_width_without_frame - 1,
880 progress_width_scaled = (progress_width + 1) * scale;
881
882 switch (cl->n->progress_bar_alignment) {
883 case PANGO_ALIGN_LEFT:
884 frame_x = settings.h_padding;
885 break;
886 case PANGO_ALIGN_CENTER:
887 frame_x = width/2 - progress_width/2;
888 break;
889 case PANGO_ALIGN_RIGHT:
890 frame_x = width - progress_width - settings.h_padding;
891 break;
892 }
893 unsigned int x_bar_1 = frame_x + frame_width,
894 x_bar_2 = x_bar_1 + 0.5;
895
896 double half_frame_width = (double)frame_width / 2.0;
897
898 /* Draw progress bar
899 * TODO: Modify draw_rounded_rect to fix blurry lines due to fractional scaling
900 * Note: for now the bar background is drawn a little bit smaller than the fill, however
901 * this solution is not particularly solid (basically subracting a pixel or two)
902 */
903
904 // back layer (background)
905 cairo_set_source_rgba(c, COLOR(cl, bg.r), COLOR(cl, bg.g), COLOR(cl, bg.b), COLOR(cl, bg.a));
906 draw_rounded_rect(c, x_bar_2, frame_y, progress_width_2, progress_height,
907 settings.progress_bar_corner_radius, scale, settings.progress_bar_corners);
908 cairo_fill(c);
909
910 // top layer (fill)
911 cairo_matrix_t matrix;
912 cairo_matrix_init_scale(&matrix, 1.0 / progress_width_scaled, 1.0);
913 cairo_pattern_set_matrix(COLOR(cl, highlight->pattern), &matrix);
914 cairo_set_source(c, COLOR(cl, highlight->pattern));
915
916 draw_rounded_rect(c, x_bar_1, frame_y, progress_width_1, progress_height,
917 settings.progress_bar_corner_radius, scale, settings.progress_bar_corners);
918 cairo_fill(c);
919
920 // border
921 cairo_set_source_rgba(c, COLOR(cl, frame.r), COLOR(cl, frame.g), COLOR(cl, frame.b), COLOR(cl, frame.a));
922 cairo_set_line_width(c, frame_width * scale);
924 frame_x + half_frame_width + 1,
925 frame_y,
926 progress_width - frame_width - 2,
927 progress_height,
928 settings.progress_bar_corner_radius,
929 scale, settings.progress_bar_corners);
930 cairo_stroke(c);
931 }
932}
933
934static struct dimensions layout_render(cairo_surface_t *srf,
935 struct colored_layout *cl,
936 struct colored_layout *cl_next,
937 struct dimensions dim,
938 enum corner_pos corners)
939{
940 double scale = output->get_scale();
941 const int cl_h = layout_get_height(cl, scale);
942
943 int bg_width = 0;
944 int bg_height = MAX(settings.height.min, 2 * settings.padding + cl_h);
945 bg_height = MIN(settings.height.max, bg_height);
946
947 cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, dim.corner_radius, corners, &bg_width, scale);
948 cairo_t *c = cairo_create(content);
949
950 render_content(c, cl, bg_width, bg_height, scale);
951
952 /* adding frame */
953 if (corners & (C_TOP | _C_FIRST))
954 dim.y += settings.frame_width;
955
956 if (corners & (C_BOT | _C_LAST))
957 dim.y += settings.frame_width;
958
959 dim.y += bg_height;
960
961 if (settings.gap_size)
962 dim.y += settings.gap_size;
963 else
964 dim.y += settings.separator_height;
965
966 cairo_destroy(c);
967 cairo_surface_destroy(content);
968 return dim;
969}
970
975void calc_window_pos(const struct screen_info *scr, int width, int height, int *ret_x, int *ret_y)
976{
977 if(!ret_x || !ret_y)
978 return;
979
980 // horizontal
981 switch (settings.origin) {
982 case ORIGIN_TOP_LEFT:
983 case ORIGIN_LEFT_CENTER:
984 case ORIGIN_BOTTOM_LEFT:
985 *ret_x = scr->x + round(settings.offset.x * draw_get_scale());
986 break;
987 case ORIGIN_TOP_RIGHT:
988 case ORIGIN_RIGHT_CENTER:
989 case ORIGIN_BOTTOM_RIGHT:
990 *ret_x = scr->x + (scr->w - width) - round(settings.offset.x * draw_get_scale());
991 break;
992 case ORIGIN_TOP_CENTER:
993 case ORIGIN_CENTER:
994 case ORIGIN_BOTTOM_CENTER:
995 default:
996 *ret_x = scr->x + (scr->w - width) / 2;
997 break;
998 }
999
1000 // vertical
1001 switch (settings.origin) {
1002 case ORIGIN_TOP_LEFT:
1003 case ORIGIN_TOP_CENTER:
1004 case ORIGIN_TOP_RIGHT:
1005 *ret_y = scr->y + round(settings.offset.y * draw_get_scale());
1006 break;
1007 case ORIGIN_BOTTOM_LEFT:
1008 case ORIGIN_BOTTOM_CENTER:
1009 case ORIGIN_BOTTOM_RIGHT:
1010 *ret_y = scr->y + (scr->h - height) - round(settings.offset.y * draw_get_scale());
1011 break;
1012 case ORIGIN_LEFT_CENTER:
1013 case ORIGIN_CENTER:
1014 case ORIGIN_RIGHT_CENTER:
1015 default:
1016 *ret_y = scr->y + (scr->h - height) / 2;
1017 break;
1018 }
1019}
1020
1021void draw(void)
1022{
1023 assert(queues_length_displayed() > 0);
1024
1025 cairo_t *c = output->win_get_context(win);
1026
1027 if (c == NULL) {
1028 return;
1029 }
1030
1031 GSList *layouts = create_layouts(c);
1032
1033 struct dimensions dim = calculate_dimensions(layouts);
1034 LOG_D("Window dimensions %ix%i", dim.w, dim.h);
1035 double scale = output->get_scale();
1036
1037 cairo_surface_t *image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1038 round(dim.w * scale),
1039 round(dim.h * scale));
1040
1041 enum corner_pos corners = (settings.corners & C_TOP) | _C_FIRST;
1042 for (GSList *iter = layouts; iter; iter = iter->next) {
1043
1044 struct colored_layout *cl_this = iter->data;
1045 struct colored_layout *cl_next = iter->next ? iter->next->data : NULL;
1046
1047 if (settings.gap_size)
1048 corners = settings.corners;
1049 else if (!cl_next)
1050 corners |= (settings.corners & C_BOT) | _C_LAST;
1051
1052 dim = layout_render(image_surface, cl_this, cl_next, dim, corners);
1053 corners &= ~(C_TOP | _C_FIRST);
1054 }
1055
1056 output->display_surface(image_surface, win, &dim);
1057
1058 cairo_surface_destroy(image_surface);
1059 g_slist_free_full(layouts, free_colored_layout);
1060}
1061
1062void draw_deinit(void)
1063{
1064 pango_font_description_free(pango_fdesc);
1065 output->win_destroy(win);
1066 output->deinit();
1067 if (settings.enable_recursive_icon_lookup)
1069}
1070
1071double draw_get_scale(void)
1072{
1073 if (output) {
1074 return output->get_scale();
1075 } else {
1076 LOG_W("Called draw_get_scale before output init");
1077 return 1;
1078 }
1079}
char * color_to_string(struct color c, char buf[10])
Stringify a color struct to a RRGGBBAA string.
Definition draw.c:68
void calc_window_pos(const struct screen_info *scr, int width, int height, int *ret_x, int *ret_y)
Calculates the position the window should be placed at given its width and height and stores them in ...
Definition draw.c:975
void draw_rounded_rect(cairo_t *c, float x, float y, int width, int height, int corner_radius, double scale, enum corner_pos corners)
Create a path on the given cairo context to draw the background of a notification.
Definition draw.c:556
static void draw_rect(cairo_t *c, double x, double y, double width, double height, double scale)
A small wrapper around cairo_rectange for drawing a scaled rectangle.
Definition draw.c:543
Layout and render notifications.
corner_pos
Specify which corner to draw in draw_rouned_rect.
Definition draw.h:28
Main event loop logic.
int load_icon_theme(char *name)
Load a theme with given name from a standard icon directory.
void free_all_themes(void)
Free all icon themes.
void add_default_theme(int theme_index)
Add theme to the list of default themes.
Recursive icon lookup in theme directories.
int get_icon_width(cairo_surface_t *icon, double scale)
Get the unscaled icon width.
Definition icon.c:91
int get_icon_height(cairo_surface_t *icon, double scale)
Get the unscaled icon height, see get_icon_width.
Definition icon.c:95
Notification images loading.
Logging subsystem and helpers.
#define LOG_E
Prefix message with "[<source path>:<function name>:<line number>] ".
Definition log.h:42
char * markup_strip(char *str)
Strip any markup from text; turn it in to plain text.
Definition markup.c:230
Markup handling for notifications body.
Notification type definitions.
const struct output * output_create(bool force_xwayland)
return an initialized output, selecting the correct output type from either wayland or X11 according ...
Definition output.c:99
Generic graphics backend wrapper.
unsigned int queues_length_waiting(void)
Returns the current amount of notifications, which are waiting to get displayed.
Definition queues.c:55
GList * queues_get_displayed(void)
Receive the current list of displayed notifications.
Definition queues.c:41
unsigned int queues_length_displayed(void)
Returns the current amount of notifications, which are shown in the UI.
Definition queues.c:61
struct notification * queues_get_head_waiting(void)
Get the highest notification in line.
Definition queues.c:47
Queues for history, waiting and displayed notifications.
Type definitions for settings.
Definition draw.h:48
PangoAlignment progress_bar_alignment
Horizontal alignment of the progress bar.
enum icon_position icon_position
Icon position (enum left,right,top,off).
int progress
percentage (-1: undefined)
cairo_surface_t * icon
The raw cached icon data used to draw.
char * text_to_render
formatted message (with age and action indicators)
bool first_render
markup has been rendered before?
struct position offset
Definition settings.h:189
String, time and other various helpers.