From b28f999e4813c85f95f3549bac1c95d2b980288d Mon Sep 17 00:00:00 2001 From: arelive Date: Thu, 29 May 2025 10:49:30 +0300 Subject: [PATCH 1/2] feat: use libsfdo for icon lookup --- config.c | 23 ++++++++++++++++ icon.c | 70 +++++++++++++++++++++++++++++++++++++++++------- include/config.h | 3 ++- meson.build | 4 +++ 4 files changed, 90 insertions(+), 10 deletions(-) diff --git a/config.c b/config.c index 817f7e97..72b6c51b 100644 --- a/config.c +++ b/config.c @@ -100,6 +100,7 @@ void init_default_style(struct mako_style *style) { #endif style->max_icon_size = 64; style->icon_path = strdup(""); // hicolor and pixmaps are implicit. + style->icon_theme = strdup(""); style->icon_border_radius = 0; style->font = strdup("monospace 10"); @@ -155,6 +156,7 @@ void finish_style(struct mako_style *style) { finish_binding(&style->touch_binding); finish_binding(&style->notify_binding); free(style->icon_path); + free(style->icon_theme); free(style->font); free(style->format); free(style->output); @@ -181,6 +183,7 @@ bool apply_style(struct mako_style *target, const struct mako_style *style) { char *new_font = NULL; char *new_format = NULL; char *new_icon_path = NULL; + char *new_icon_theme = NULL; char *new_output = NULL; if (style->spec.font) { @@ -210,12 +213,23 @@ bool apply_style(struct mako_style *target, const struct mako_style *style) { } } + if (style->spec.icon_theme) { + new_icon_theme = strdup(style->icon_theme); + if (new_icon_theme == NULL) { + free(new_format); + free(new_font); + fprintf(stderr, "allocation failed\n"); + return false; + } + } + if (style->spec.output) { new_output = strdup(style->output); if (new_output == NULL) { free(new_format); free(new_font); free(new_icon_path); + free(new_icon_theme); fprintf(stderr, "allocation failed\n"); return false; } @@ -269,6 +283,12 @@ bool apply_style(struct mako_style *target, const struct mako_style *style) { target->spec.icon_path = true; } + if (style->spec.icon_theme) { + free(target->icon_theme); + target->icon_theme = new_icon_theme; + target->spec.icon_theme = true; + } + if (style->spec.icon_border_radius) { target->icon_border_radius = style->icon_border_radius; target->spec.icon_border_radius = true; @@ -614,6 +634,9 @@ static bool apply_style_option(struct mako_style *style, const char *name, } else if (strcmp(name, "icon-path") == 0) { free(style->icon_path); return spec->icon_path = !!(style->icon_path = strdup(value)); + } else if (strcmp(name, "icon-theme") == 0) { + free(style->icon_theme); + return spec->icon_theme = !!(style->icon_theme == strdup(value)); } else if (strcmp(name, "icon-border-radius") == 0) { spec->icon_border_radius = parse_int_ge(value, &style->icon_border_radius, 0); return spec->icon_border_radius; diff --git a/icon.c b/icon.c index 3a0d49b7..faac955f 100644 --- a/icon.c +++ b/icon.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "mako.h" #include "icon.h" @@ -122,6 +124,10 @@ static char *resolve_icon(struct mako_notification *notif) { return icon_path; } + if (!validate_icon_name(icon_name)) { + return NULL; + } + // Determine the largest scale factor of any attached output. int32_t max_scale = 1; struct mako_output *output = NULL; @@ -131,10 +137,62 @@ static char *resolve_icon(struct mako_notification *notif) { } } + static const char fallback_name[] = "%s:hicolor"; + char *search = mako_asprintf(fallback_name, notif->style.icon_theme); + + static char *saveptr = NULL; + char *theme_name = strtok_r(search, ":", &saveptr); + + struct sfdo_basedir_ctx *basedir_ctx = sfdo_basedir_ctx_create(); + struct sfdo_icon_ctx *icon_ctx = sfdo_icon_ctx_create(basedir_ctx); + if (icon_ctx == NULL) { + sfdo_basedir_ctx_destroy(basedir_ctx); + return NULL; + } + + const int THEME_OPTIONS = SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT + | SFDO_ICON_THEME_LOAD_OPTION_RELAXED + | SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING; + + const int ICON_OPTIONS = SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT; + + char *icon_path = NULL; + for (;theme_name; theme_name = strtok_r(NULL, ":", &saveptr)) { + if (strlen(theme_name) == 0) { + continue; + } + + struct sfdo_icon_theme *icon_theme = sfdo_icon_theme_load(icon_ctx, theme_name, THEME_OPTIONS); + if (icon_theme == NULL) { + continue; + } + + struct sfdo_icon_file *icon_file = sfdo_icon_theme_lookup( + icon_theme, icon_name, SFDO_NT, notif->style.max_icon_size, max_scale, ICON_OPTIONS); + sfdo_icon_theme_destroy(icon_theme); + if (icon_file == NULL || icon_file == SFDO_ICON_FILE_INVALID) { + continue; + } + + icon_path = strdup(sfdo_icon_file_get_path(icon_file, NULL)); + break; + } + + sfdo_icon_ctx_destroy(icon_ctx); + sfdo_basedir_ctx_destroy(basedir_ctx); + + free(search); + + if (icon_path != NULL) { + return icon_path; + } + + // Now by path directly + static const char fallback[] = "%s:/usr/share/icons/hicolor"; - char *search = mako_asprintf(fallback, notif->style.icon_path); + search = mako_asprintf(fallback, notif->style.icon_path); - char *saveptr = NULL; + saveptr = NULL; char *theme_path = strtok_r(search, ":", &saveptr); // Match all icon files underneath of the theme_path followed by any icon @@ -142,14 +200,9 @@ static char *resolve_icon(struct mako_notification *notif) { // files in the icon path are valid icon types. static const char pattern_fmt[] = "%s/*/*/%s.*"; - char *icon_path = NULL; int32_t last_icon_size = 0; - if (!validate_icon_name(icon_name)) { - return NULL; - } - - while (theme_path) { + for (;theme_path; theme_path = strtok_r(NULL, ":", &saveptr)) { if (strlen(theme_path) == 0) { continue; } @@ -225,7 +278,6 @@ static char *resolve_icon(struct mako_notification *notif) { // themes even if one is a better size. break; } - theme_path = strtok_r(NULL, ":", &saveptr); } if (icon_path == NULL) { diff --git a/include/config.h b/include/config.h index 48769898..68d080bc 100644 --- a/include/config.h +++ b/include/config.h @@ -41,7 +41,7 @@ enum mako_icon_location { struct mako_style_spec { bool width, height, outer_margin, margin, padding, border_size, border_radius, font, markup, format, text_alignment, actions, default_timeout, ignore_timeout, - icons, max_icon_size, icon_path, icon_border_radius, group_criteria_spec, invisible, history, + icons, max_icon_size, icon_path, icon_theme, icon_border_radius, group_criteria_spec, invisible, history, icon_location, max_visible, layer, output, anchor; struct { bool background, text, border, progress; @@ -67,6 +67,7 @@ struct mako_style { bool icons; int32_t max_icon_size; char *icon_path; + char *icon_theme; int32_t icon_border_radius; char *font; diff --git a/meson.build b/meson.build index c3284e4c..03bbcef4 100644 --- a/meson.build +++ b/meson.build @@ -33,6 +33,8 @@ realtime = cc.find_library('rt') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols', version: '>=1.32') wayland_cursor = dependency('wayland-cursor') +sfdo_basedir = dependency('libsfdo-basedir') +sfdo_icon = dependency('libsfdo-icon') epoll = dependency('', required: false) if (not cc.has_function('timerfd_create', prefix: '#include ') or @@ -94,6 +96,8 @@ executable( realtime, wayland_client, wayland_cursor, + sfdo_basedir, + sfdo_icon, ], include_directories: [mako_inc], install: true, From 982610db2f5d7ad016089b1f3ab2ce62ed3640f1 Mon Sep 17 00:00:00 2001 From: arelive Date: Fri, 30 May 2025 02:10:30 +0300 Subject: [PATCH 2/2] feat: min-icon-size --- config.c | 16 ++++++++++ contrib/completions/bash/mako | 1 + contrib/completions/fish/mako.fish | 1 + doc/mako.5.scd | 5 +++ icon.c | 49 ++++++++++++++++++++++++------ include/config.h | 3 +- main.c | 1 + 7 files changed, 65 insertions(+), 11 deletions(-) diff --git a/config.c b/config.c index 817f7e97..63fb1c5c 100644 --- a/config.c +++ b/config.c @@ -15,6 +15,10 @@ static int32_t max(int32_t a, int32_t b) { return (a > b) ? a : b; } +static int32_t min(int32_t a, int32_t b) { + return (a < b) ? a : b; +} + void init_default_config(struct mako_config *config) { wl_list_init(&config->criteria); struct mako_criteria *new_criteria = create_criteria(config); @@ -99,6 +103,7 @@ void init_default_style(struct mako_style *style) { style->icons = false; #endif style->max_icon_size = 64; + style->min_icon_size = 8; style->icon_path = strdup(""); // hicolor and pixmaps are implicit. style->icon_border_radius = 0; @@ -263,6 +268,11 @@ bool apply_style(struct mako_style *target, const struct mako_style *style) { target->spec.max_icon_size = true; } + if (style->spec.min_icon_size) { + target->min_icon_size = style->min_icon_size; + target->spec.min_icon_size = true; + } + if (style->spec.icon_path) { free(target->icon_path); target->icon_path = new_icon_path; @@ -420,6 +430,7 @@ bool apply_superset_style( target->spec.border_radius = true; target->spec.icons = true; target->spec.max_icon_size = true; + target->spec.min_icon_size = true; target->spec.default_timeout = true; target->spec.markup = true; target->spec.actions = true; @@ -466,6 +477,7 @@ bool apply_superset_style( target->border_size = max(style->border_size, target->border_size); target->icons = style->icons || target->icons; target->max_icon_size = max(style->max_icon_size, target->max_icon_size); + target->min_icon_size = min(style->min_icon_size, target->min_icon_size); target->default_timeout = max(style->default_timeout, target->default_timeout); @@ -611,6 +623,9 @@ static bool apply_style_option(struct mako_style *style, const char *name, } else if (strcmp(name, "max-icon-size") == 0) { return spec->max_icon_size = parse_int_ge(value, &style->max_icon_size, 1); + } else if (strcmp(name, "min-icon-size") == 0) { + return spec->min_icon_size = + parse_int_ge(value, &style->min_icon_size, 1); } else if (strcmp(name, "icon-path") == 0) { free(style->icon_path); return spec->icon_path = !!(style->icon_path = strdup(value)); @@ -912,6 +927,7 @@ int parse_config_arguments(struct mako_config *config, int argc, char **argv) { {"icon-location", required_argument, 0, 0}, {"icon-path", required_argument, 0, 0}, {"max-icon-size", required_argument, 0, 0}, + {"min-icon-size", required_argument, 0, 0}, {"icon-border-radius", required_argument, 0, 0}, {"markup", required_argument, 0, 0}, {"actions", required_argument, 0, 0}, diff --git a/contrib/completions/bash/mako b/contrib/completions/bash/mako index bdb5bbb6..0a9455c4 100644 --- a/contrib/completions/bash/mako +++ b/contrib/completions/bash/mako @@ -23,6 +23,7 @@ _mako() '--icons' '--icon-path' '--max-icon-size' + '--min-icon-size' '--icon-border-radius' '--markup' '--actions' diff --git a/contrib/completions/fish/mako.fish b/contrib/completions/fish/mako.fish index c64d203d..b0cb5050 100644 --- a/contrib/completions/fish/mako.fish +++ b/contrib/completions/fish/mako.fish @@ -22,6 +22,7 @@ complete -c mako -l progress-color -d 'Progress color indicator' -x complete -c mako -l icons -d 'Show icons or not' -xa "1 0" complete -c mako -l icon-path -d 'Icon search path, colon delimited' -r complete -c mako -l max-icon-size -d 'Max icon size in px' -x +complete -c mako -l min-icon-size -d 'Min icon size in px' -x complete -c mako -l icon-border-radius -d 'Icon border radius value in px' -x complete -c mako -l markup -d 'Enable markup or not' -xa "1 0" complete -c mako -l actions -d 'Enable actions or not' -xa "1 0" diff --git a/doc/mako.5.scd b/doc/mako.5.scd index 6c1683a2..cd888855 100644 --- a/doc/mako.5.scd +++ b/doc/mako.5.scd @@ -204,6 +204,11 @@ Supported actions: Default: 64 +*min-icon-size*=_px_ + Set minimum icon size to _px_ pixels. + + Default: 8 + *icon-path*=_path_\[:_path_...\] Paths to search for icons when a notification specifies a name instead of a full path. Colon-delimited. This approximates the search algorithm diff --git a/icon.c b/icon.c index 3a0d49b7..7e77c139 100644 --- a/icon.c +++ b/icon.c @@ -54,6 +54,31 @@ static GdkPixbuf *load_image(const char *path) { return pixbuf; } +static GdkPixbuf *load_scaled_to_minimum(GdkPixbuf *image, char *path, int min_size) { + int image_width = gdk_pixbuf_get_width(image); + int image_height = gdk_pixbuf_get_height(image); + + double longest = image_width > image_height ? image_width : image_height; + + if (longest < min_size) { + const double scale = min_size / longest; + image_width *= scale; + image_height *= scale; + + GError *err = NULL; + GdkPixbuf *scaled_pixbuf = gdk_pixbuf_new_from_file_at_scale( + path, image_width, image_height, true, &err); + if (!scaled_pixbuf) { + fprintf(stderr, "Failed to load icon (%s)\n", err->message); + g_error_free(err); + return NULL; + } + return scaled_pixbuf; + } + + return image; +} + static GdkPixbuf *load_image_data(struct mako_image_data *image_data) { GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(image_data->data, GDK_COLORSPACE_RGB, image_data->has_alpha, image_data->bits_per_sample, image_data->width, @@ -65,9 +90,15 @@ static GdkPixbuf *load_image_data(struct mako_image_data *image_data) { return pixbuf; } -static double fit_to_square(int width, int height, int square_size) { - double longest = width > height ? width : height; - return longest > square_size ? square_size/longest : 1.0; +static void fit_to_square(GdkPixbuf *image, struct mako_icon *icon, int max_size) { + int image_width = gdk_pixbuf_get_width(image); + int image_height = gdk_pixbuf_get_height(image); + + double longest = image_width > image_height ? image_width : image_height; + + icon->scale = longest > max_size ? max_size / longest : 1.0; + icon->width = image_width * icon->scale; + icon->height = image_height * icon->scale; } static char hex_val(char digit) { @@ -263,20 +294,18 @@ struct mako_icon *create_icon(struct mako_notification *notif) { } image = load_image(path); + GdkPixbuf *scaled = load_scaled_to_minimum(image, path, notif->style.min_icon_size); + free(image); + image = scaled; + free(path); if (image == NULL) { return NULL; } } - int image_width = gdk_pixbuf_get_width(image); - int image_height = gdk_pixbuf_get_height(image); - struct mako_icon *icon = calloc(1, sizeof(struct mako_icon)); - icon->scale = fit_to_square( - image_width, image_height, notif->style.max_icon_size); - icon->width = image_width * icon->scale; - icon->height = image_height * icon->scale; + fit_to_square(image, icon, notif->style.max_icon_size); icon->image = create_cairo_surface_from_gdk_pixbuf(image); g_object_unref(image); diff --git a/include/config.h b/include/config.h index 48769898..d5d161e6 100644 --- a/include/config.h +++ b/include/config.h @@ -41,7 +41,7 @@ enum mako_icon_location { struct mako_style_spec { bool width, height, outer_margin, margin, padding, border_size, border_radius, font, markup, format, text_alignment, actions, default_timeout, ignore_timeout, - icons, max_icon_size, icon_path, icon_border_radius, group_criteria_spec, invisible, history, + icons, max_icon_size, min_icon_size, icon_path, icon_border_radius, group_criteria_spec, invisible, history, icon_location, max_visible, layer, output, anchor; struct { bool background, text, border, progress; @@ -66,6 +66,7 @@ struct mako_style { bool icons; int32_t max_icon_size; + int32_t min_icon_size; char *icon_path; int32_t icon_border_radius; diff --git a/main.c b/main.c index c273a4b8..dcf65917 100644 --- a/main.c +++ b/main.c @@ -39,6 +39,7 @@ static const char usage[] = " --icons <0|1> Show icons in notifications.\n" " --icon-path [:...] Icon search path, colon delimited.\n" " --max-icon-size Set max size of icons.\n" + " --min-icon-size Set min size of icons.\n" " --icon-border-radius Icon's corner radius.\n" " --markup <0|1> Enable/disable markup.\n" " --actions <0|1> Enable/disable application action\n"