Skip to content

Commit c89afc7

Browse files
vilhalmeremersion
authored andcommitted
Add resolve_icon to support icon names
1 parent 74938f1 commit c89afc7

File tree

10 files changed

+245
-59
lines changed

10 files changed

+245
-59
lines changed

config.c

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ void init_default_style(struct mako_style *style) {
100100
style->icons = false;
101101
#endif
102102
style->max_icon_size = 64;
103-
103+
style->icon_path = strdup(""); // hicolor and pixmaps are implicit.
104104

105105
style->font = strdup("monospace 10");
106106
style->markup = true;
@@ -139,6 +139,7 @@ bool apply_style(struct mako_style *target, const struct mako_style *style) {
139139
// to bail without changing `target`.
140140
char *new_font = NULL;
141141
char *new_format = NULL;
142+
char *new_icon_path = NULL;
142143

143144
if (style->spec.font) {
144145
new_font = strdup(style->font);
@@ -157,6 +158,16 @@ bool apply_style(struct mako_style *target, const struct mako_style *style) {
157158
}
158159
}
159160

161+
if (style->spec.icon_path) {
162+
new_icon_path = strdup(style->icon_path);
163+
if (new_icon_path == NULL) {
164+
free(new_format);
165+
free(new_font);
166+
fprintf(stderr, "allocation failed\n");
167+
return false;
168+
}
169+
}
170+
160171
// Now on to actually setting things!
161172

162173
if (style->spec.width) {
@@ -194,6 +205,12 @@ bool apply_style(struct mako_style *target, const struct mako_style *style) {
194205
target->spec.max_icon_size = true;
195206
}
196207

208+
if (style->spec.icon_path) {
209+
free(target->icon_path);
210+
target->icon_path = new_icon_path;
211+
target->spec.icon_path = true;
212+
}
213+
197214
if (style->spec.font) {
198215
free(target->font);
199216
target->font = new_font;
@@ -457,6 +474,9 @@ static bool apply_style_option(struct mako_style *style, const char *name,
457474
} else if (strcmp(name, "max-icon-size") == 0) {
458475
return spec->max_icon_size =
459476
parse_int(value, &style->max_icon_size);
477+
} else if (strcmp(name, "icon-path") == 0) {
478+
free(style->icon_path);
479+
return spec->icon_path = !!(style->icon_path = strdup(value));
460480
} else if (strcmp(name, "markup") == 0) {
461481
return spec->markup = parse_boolean(value, &style->markup);
462482
} else if (strcmp(name, "actions") == 0) {

dbus/xdg.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,6 @@ static int handle_notify(sd_bus_message *msg, void *data,
118118
notif->app_icon = strdup(app_icon);
119119
notif->summary = strdup(summary);
120120
notif->body = strdup(body);
121-
if (state->config.superstyle.icons) {
122-
notif->icon = create_icon(notif->app_icon, state->config.superstyle.max_icon_size);
123-
}
124121

125122
// These fields may not be filled, so make sure they're valid strings.
126123
notif->category = strdup("");
@@ -283,6 +280,10 @@ static int handle_notify(sd_bus_message *msg, void *data,
283280
handle_notification_timer, notif);
284281
}
285282

283+
if (notif->style.icons) {
284+
notif->icon = create_icon(notif);
285+
}
286+
286287
// Now we need to perform the grouping based on the new notification's
287288
// group criteria specification (list of critera which must match). We
288289
// don't necessarily want to start with the new notification, as depending

icon.c

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1+
#define _POSIX_C_SOURCE 200809L
2+
#include <assert.h>
3+
#include <errno.h>
4+
#include <fnmatch.h>
5+
#include <glob.h>
6+
#include <libgen.h>
17
#include <stdio.h>
28
#include <stdlib.h>
39
#include <assert.h>
410
#include <cairo/cairo.h>
511

12+
#include "mako.h"
613
#include "icon.h"
14+
#include "string-util.h"
15+
#include "wayland.h"
716

817
#ifdef HAVE_ICONS
918
#include <gdk/gdk.h>
@@ -28,7 +37,148 @@ static double fit_to_square(int width, int height, int square_size) {
2837
return longest > square_size ? square_size/longest : 1.0;
2938
}
3039

31-
struct mako_icon *create_icon(const char *path, double max_size) {
40+
// Attempt to find a full path for a notification's icon_name, which may be:
41+
// - An absolute path, which will simply be returned (as a new string)
42+
// - A file:// URI, which will be converted to an absolute path
43+
// - A Freedesktop icon name, which will be resolved within the configured
44+
// `icon-path` using something that looks vaguely like the algorithm defined
45+
// in the icon theme spec (https://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html)
46+
//
47+
// Returns the resolved path, or NULL if it was unable to find an icon. The
48+
// return value must be freed by the caller.
49+
static char *resolve_icon(struct mako_notification *notif) {
50+
char *icon_name = notif->app_icon;
51+
if (icon_name[0] == '\0') {
52+
return NULL;
53+
}
54+
if (icon_name[0] == '/') {
55+
return strdup(icon_name);
56+
}
57+
if (strstr(icon_name, "file://") == icon_name) {
58+
// Just chop off the scheme.
59+
return strdup(icon_name + strlen("file://"));
60+
}
61+
62+
// Determine the largest scale factor of any attached output.
63+
int32_t max_scale = 1;
64+
struct mako_output *output = NULL;
65+
wl_list_for_each(output, &notif->state->outputs, link) {
66+
if (output->scale > max_scale) {
67+
max_scale = output->scale;
68+
}
69+
}
70+
71+
static const char fallback[] = "%s:/usr/share/icons/hicolor";
72+
char *search = mako_asprintf(fallback, notif->style.icon_path);
73+
74+
char *saveptr = NULL;
75+
char *theme_path = strtok_r(search, ":", &saveptr);
76+
77+
// Match all icon files underneath of the theme_path followed by any icon
78+
// size and category subdirectories. This pattern assumes that all the
79+
// files in the icon path are valid icon types.
80+
static const char pattern_fmt[] = "%s/*/*/%s.*";
81+
82+
char *icon_path = NULL;
83+
int32_t last_icon_size = 0;
84+
while (theme_path) {
85+
if (strlen(theme_path) == 0) {
86+
continue;
87+
}
88+
89+
glob_t icon_glob = {0};
90+
char *pattern = mako_asprintf(pattern_fmt, theme_path, icon_name);
91+
92+
// Disable sorting because we're going to do our own anyway.
93+
int found = glob(pattern, GLOB_NOSORT, NULL, &icon_glob);
94+
size_t found_count = 0;
95+
if (found == 0) {
96+
// The value of gl_pathc isn't guaranteed to be usable if glob
97+
// returns non-zero.
98+
found_count = icon_glob.gl_pathc;
99+
}
100+
101+
for (size_t i = 0; i < found_count; ++i) {
102+
char *relative_path = icon_glob.gl_pathv[i];
103+
104+
// Find the end of the current search path and walk to the next
105+
// path component. Hopefully this will be the icon resolution
106+
// subdirectory.
107+
relative_path += strlen(theme_path);
108+
while (relative_path[0] == '/') {
109+
++relative_path;
110+
}
111+
112+
errno = 0;
113+
int32_t icon_size = strtol(relative_path, NULL, 10);
114+
if (errno || icon_size == 0) {
115+
continue;
116+
}
117+
118+
int32_t icon_scale = 1;
119+
char *scale_str = strchr(relative_path, '@');
120+
if (scale_str != NULL) {
121+
icon_scale = strtol(scale_str + 1, NULL, 10);
122+
}
123+
124+
if (icon_size == notif->style.max_icon_size &&
125+
icon_scale == max_scale) {
126+
// If we find an exact match, we're done.
127+
free(icon_path);
128+
icon_path = strdup(icon_glob.gl_pathv[i]);
129+
break;
130+
} else if (icon_size < notif->style.max_icon_size * max_scale &&
131+
icon_size > last_icon_size) {
132+
// Otherwise, if this icon is small enough to fit but bigger
133+
// than the last best match, choose it on a provisional basis.
134+
// We multiply by max_scale to increase the odds of finding an
135+
// icon which looks sharp on the highest-scale output.
136+
free(icon_path);
137+
icon_path = strdup(icon_glob.gl_pathv[i]);
138+
last_icon_size = icon_size;
139+
}
140+
}
141+
142+
free(pattern);
143+
globfree(&icon_glob);
144+
145+
if (icon_path) {
146+
// The spec says that if we find any match whatsoever in a theme,
147+
// we should stop there to avoid mixing icons from different
148+
// themes even if one is a better size.
149+
break;
150+
}
151+
theme_path = strtok_r(NULL, ":", &saveptr);
152+
}
153+
154+
if (icon_path == NULL) {
155+
// Finally, fall back to looking in /usr/share/pixmaps. These are
156+
// unsized icons, which may lead to downscaling, but some apps are
157+
// still using it.
158+
static const char pixmaps_fmt[] = "/usr/share/pixmaps/%s.*";
159+
160+
char *pattern = mako_asprintf(pixmaps_fmt, icon_name);
161+
162+
glob_t icon_glob = {0};
163+
int found = glob(pattern, GLOB_NOSORT, NULL, &icon_glob);
164+
165+
if (found == 0 && icon_glob.gl_pathc > 0) {
166+
icon_path = strdup(icon_glob.gl_pathv[0]);
167+
}
168+
free(pattern);
169+
globfree(&icon_glob);
170+
}
171+
172+
free(search);
173+
return icon_path;
174+
}
175+
176+
struct mako_icon *create_icon(struct mako_notification *notif) {
177+
char *path = resolve_icon(notif);
178+
if (path == NULL) {
179+
return NULL;
180+
}
181+
32182
GdkPixbuf *image = load_image(path);
33183
if (image == NULL) {
34184
return NULL;
@@ -37,7 +187,8 @@ struct mako_icon *create_icon(const char *path, double max_size) {
37187
int image_height = gdk_pixbuf_get_height(image);
38188

39189
struct mako_icon *icon = calloc(1, sizeof(struct mako_icon));
40-
icon->scale = fit_to_square(image_width, image_height, max_size);
190+
icon->scale = fit_to_square(
191+
image_width, image_height, notif->style.max_icon_size);
41192
icon->width = image_width * icon->scale;
42193
icon->height = image_height * icon->scale;
43194

include/config.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ enum mako_sort_criteria {
2525
struct mako_style_spec {
2626
bool width, height, margin, padding, border_size, border_radius, font,
2727
markup, format, actions, default_timeout, ignore_timeout, icons,
28-
max_icon_size, group_criteria_spec, invisible;
28+
max_icon_size, icon_path, group_criteria_spec, invisible;
2929

3030
struct {
3131
bool background, text, border, progress;
@@ -45,6 +45,7 @@ struct mako_style {
4545

4646
bool icons;
4747
int32_t max_icon_size;
48+
char *icon_path;
4849

4950
char *font;
5051
bool markup;

include/icon.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define MAKO_ICON_H
33

44
#include <cairo/cairo.h>
5+
#include "notification.h"
56

67
struct mako_icon {
78
double width;
@@ -10,7 +11,7 @@ struct mako_icon {
1011
cairo_surface_t *image;
1112
};
1213

13-
struct mako_icon *create_icon(const char *path, double max_size);
14+
struct mako_icon *create_icon(struct mako_notification *notif);
1415
void destroy_icon(struct mako_icon *icon);
1516
void draw_icon(cairo_t *cairo, struct mako_icon *icon,
1617
double xpos, double ypos, double scale);

include/string-util.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#ifndef MAKO_STRING_H
2+
#define MAKO_STRING_H
3+
4+
char *mako_asprintf(const char *fmt, ...);
5+
6+
#endif

main.c

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,33 @@
1414
static const char usage[] =
1515
"Usage: mako [options...]\n"
1616
"\n"
17-
" -h, --help Show help message and quit.\n"
18-
" --font <font> Font family and size.\n"
19-
" --background-color <color> Background color.\n"
20-
" --text-color <color> Text color.\n"
21-
" --width <px> Notification width.\n"
22-
" --height <px> Max notification height.\n"
23-
" --margin <px>[,<px>...] Margin values, comma separated.\n"
24-
" Up to four values, with the same\n"
25-
" meaning as in CSS.\n"
26-
" --padding <px>[,<px>...] Padding values, comma separated.\n"
27-
" Up to four values, with the same\n"
28-
" meaning as in CSS.\n"
29-
" --border-size <px> Border size.\n"
30-
" --border-color <color> Border color.\n"
31-
" --border-radius <px> Corner radius\n"
32-
" --progress-color <color> Progress indicator color.\n"
33-
" --icons <0|1> Show icons in notifications.\n"
34-
" --max-icon-size <px> Set max size of icons.\n"
35-
" --markup <0|1> Enable/disable markup.\n"
36-
" --format <format> Format string.\n"
37-
" --hidden-format <format> Format string.\n"
38-
" --max-visible <n> Max number of visible notifications.\n"
39-
" --default-timeout <timeout> Default timeout in milliseconds.\n"
40-
" --output <name> Show notifications on this output.\n"
41-
" --layer <layer> Arrange notifications at this layer.\n"
42-
" --anchor <position> Position on output to put notifications.\n"
17+
" -h, --help Show help message and quit.\n"
18+
" --font <font> Font family and size.\n"
19+
" --background-color <color> Background color.\n"
20+
" --text-color <color> Text color.\n"
21+
" --width <px> Notification width.\n"
22+
" --height <px> Max notification height.\n"
23+
" --margin <px>[,<px>...] Margin values, comma separated.\n"
24+
" Up to four values, with the same\n"
25+
" meaning as in CSS.\n"
26+
" --padding <px>[,<px>...] Padding values, comma separated.\n"
27+
" Up to four values, with the same\n"
28+
" meaning as in CSS.\n"
29+
" --border-size <px> Border size.\n"
30+
" --border-color <color> Border color.\n"
31+
" --border-radius <px> Corner radius\n"
32+
" --progress-color <color> Progress indicator color.\n"
33+
" --icons <0|1> Show icons in notifications.\n"
34+
" --icon-path <path>[:<path>...] Icon search path, colon delimited.\n"
35+
" --max-icon-size <px> Set max size of icons.\n"
36+
" --markup <0|1> Enable/disable markup.\n"
37+
" --format <format> Format string.\n"
38+
" --hidden-format <format> Format string.\n"
39+
" --max-visible <n> Max number of visible notifications.\n"
40+
" --default-timeout <timeout> Default timeout in milliseconds.\n"
41+
" --output <name> Show notifications on this output.\n"
42+
" --layer <layer> Arrange notifications at this layer.\n"
43+
" --anchor <position> Position on output to put notifications.\n"
4344
"\n"
4445
"Colors can be specified with the format #RRGGBB or #RRGGBBAA.\n";
4546

meson.build

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ src_files = [
5454
'wayland.c',
5555
'criteria.c',
5656
'types.c',
57-
'icon.c'
57+
'icon.c',
58+
'string-util.c',
5859
]
5960

6061
executable(

0 commit comments

Comments
 (0)