yambar/icon.c
Jordan Isaacs 6113f9b94e
particles/icon: init
Introduce a new icon particle. It follows the icon
spec (https://specifications.freedesktop.org/icon-theme-spec/latest/index.html).
Rendering logic is taken from fuzzel (using nanosvg + libpng), while loading
logic is taken from sway. Standard usage is with `use-tag = false` which expands
the provided string template and then loads the string as the icon name. There
are settings to manually override the base paths, themes, etc. The second usage
which is required for tray support is a special icon tag that transfers raw
pixmaps. With `use-tag = true` it first expands the string, and then uses that
output to find an icon pixmap tag. To reduce memory usage, themes are reference
counted so they can be passed down the configuration stack without having to
load them in multiple times.

For programmability, a fallback particle can be specified if no icon/tag is
found `fallback: ...`. And the new icon pixmap tag can be existence checked in
map conditions using `+{tag_name}`.

Future work to be done in follow up diffs:
1. Icon caching. Currently performs an icon lookup on each instantiation & a
   render on each refresh.
2. Theme caching. Changing theme directories results in a new "theme collection"
   being created resulting in the possibility of duplicated theme loading.
2024-07-23 22:58:23 -07:00

968 lines
26 KiB
C

#include "tllist.h"
#include <ctype.h>
#include <dirent.h>
#include <limits.h>
#include <nanosvg/nanosvg.h>
#include <nanosvg/nanosvgrast.h>
#include <pixman.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wordexp.h>
#define LOG_MODULE "icon"
#define LOG_ENABLE_DBG 0
#include "icon.h"
#include "log.h"
#include "png-yambar.h"
#include "stride.h"
#include "stringop.h"
void
icon_pixmaps_free(const struct ref *ref)
{
struct icon_pixmaps *p = (struct icon_pixmaps *)ref;
tll_free_and_free(p->list, free);
free(p);
}
void
icon_pixmaps_dec(struct icon_pixmaps *p)
{
ref_dec(&p->refcount);
}
struct icon_pixmaps *
icon_pixmaps_inc(struct icon_pixmaps *p)
{
ref_inc(&p->refcount);
return p;
}
struct icon_pixmaps *
new_icon_pixmaps()
{
struct icon_pixmaps *p = malloc(sizeof(*p));
p->refcount = (struct ref){icon_pixmaps_free, 1};
icon_pixmaps_t tmp = tll_init();
p->list = tmp;
return p;
}
void
icon_from_pixmaps(struct icon *icon, struct icon_pixmaps *p)
{
icon->pixmaps = icon_pixmaps_inc(p);
icon->type = ICON_PIXMAP;
}
bool
icon_from_png(struct icon *icon, const char *file_name)
{
pixman_image_t *png = png_load(file_name);
if (png == NULL)
return false;
icon->type = ICON_PNG;
icon->png = png;
return true;
}
bool
icon_from_svg(struct icon *icon, const char *file_name)
{
/* TODO: DPI */
NSVGimage *svg = nsvgParseFromFile(file_name, "px", 96);
if (svg == NULL)
return false;
if (svg->width == 0 || svg->height == 0) {
nsvgDelete(svg);
return false;
}
icon->type = ICON_SVG;
icon->svg = svg;
return true;
}
static void
scale_if_necessary(pixman_image_t *img, int x, int y, int size, pixman_image_t *dest)
{
pixman_format_code_t fmt = pixman_image_get_format(img);
int height = pixman_image_get_height(img);
int width = pixman_image_get_width(img);
bool scale_img = height > size;
// Exposable is const so have to recalculate every-time...
//
if (scale_img) {
double scale = (double)size / height;
pixman_f_transform_t _scale_transform;
pixman_f_transform_init_scale(&_scale_transform, 1. / scale, 1. / scale);
pixman_transform_t scale_transform;
pixman_transform_from_pixman_f_transform(&scale_transform, &_scale_transform);
pixman_image_set_transform(img, &scale_transform);
int param_count = 0;
pixman_kernel_t kernel = PIXMAN_KERNEL_LANCZOS3;
pixman_fixed_t *params = pixman_filter_create_separable_convolution(
&param_count, pixman_double_to_fixed(1. / scale), pixman_double_to_fixed(1. / scale), kernel, kernel,
kernel, kernel, pixman_int_to_fixed(1), pixman_int_to_fixed(1));
if (params != NULL || param_count == 0) {
pixman_image_set_filter(img, PIXMAN_FILTER_SEPARABLE_CONVOLUTION, params, param_count);
}
free(params);
width *= scale;
height *= scale;
int stride = stride_for_format_and_width(fmt, width);
uint8_t *data = malloc(height * stride);
pixman_image_t *scaled_img = pixman_image_create_bits_no_clear(fmt, width, height, (uint32_t *)data, stride);
pixman_image_composite32(PIXMAN_OP_SRC, img, NULL, scaled_img, 0, 0, 0, 0, 0, 0, width, height);
img = scaled_img;
}
pixman_image_composite32(PIXMAN_OP_OVER, img, NULL, dest, 0, 0, 0, 0, x, y, width, height);
if (scale_img) {
free(pixman_image_get_data(img));
pixman_image_unref(img);
}
}
static void
render_png(const struct icon *icon, int x, int y, int size, pixman_image_t *dest)
{
assert(icon->type == ICON_PNG);
assert(icon->png != NULL);
pixman_image_t *png = icon->png;
scale_if_necessary(png, x, y, size, dest);
}
static void
render_svg(const struct icon *icon, int x, int y, int size, pixman_image_t *dest)
{
assert(icon->type == ICON_SVG);
assert(icon->svg != NULL);
NSVGimage *svg = icon->svg;
struct NSVGrasterizer *rast = nsvgCreateRasterizer();
if (rast == NULL)
return;
float scale = svg->width > svg->height ? size / svg->width : size / svg->height;
uint8_t *data = malloc(size * size * 4);
nsvgRasterize(rast, svg, 0, 0, scale, data, size, size, size * 4);
pixman_image_t *img = pixman_image_create_bits_no_clear(PIXMAN_a8b8g8r8, size, size, (uint32_t *)data, size * 4);
/* Nanosvg produces non-premultiplied ABGR, while pixman expects
* premultiplied */
for (uint32_t *abgr = (uint32_t *)data; abgr < (uint32_t *)(data + size * size * 4); abgr++) {
uint8_t alpha = (*abgr >> 24) & 0xff;
uint8_t blue = (*abgr >> 16) & 0xff;
uint8_t green = (*abgr >> 8) & 0xff;
uint8_t red = (*abgr >> 0) & 0xff;
if (alpha == 0xff)
continue;
if (alpha == 0x00)
blue = green = red = 0x00;
else {
blue = blue * alpha / 0xff;
green = green * alpha / 0xff;
red = red * alpha / 0xff;
}
*abgr = (uint32_t)alpha << 24 | blue << 16 | green << 8 | red;
}
nsvgDeleteRasterizer(rast);
pixman_image_composite32(PIXMAN_OP_OVER, img, NULL, dest, 0, 0, 0, 0, x, y, size, size);
free(pixman_image_get_data(img));
pixman_image_unref(img);
}
static void
render_pixmap(const struct icon *icon, int x, int y, int size, pixman_image_t *dest)
{
assert(icon->type == ICON_PIXMAP);
assert(icon->pixmaps != NULL);
struct icon_pixmap *icon_pixmap = NULL;
int min_error = INT_MAX;
tll_foreach(icon->pixmaps->list, it)
{
int e = abs(size - it->item->size);
if (e < min_error) {
icon_pixmap = it->item;
min_error = e;
}
}
assert(icon_pixmap != NULL);
pixman_image_t *img = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, icon_pixmap->size, icon_pixmap->size, (uint32_t *)icon_pixmap->pixels,
stride_for_format_and_width(PIXMAN_a8r8g8b8, icon_pixmap->size));
scale_if_necessary(img, x, y, size, dest);
pixman_image_unref(img);
}
void
render_icon(const struct icon *icon, int x, int y, int size, pixman_image_t *dest)
{
switch (icon->type) {
case ICON_NONE:
break;
case ICON_PNG:
render_png(icon, x, y, size, dest);
break;
case ICON_SVG:
render_svg(icon, x, y, size, dest);
break;
case ICON_PIXMAP:
render_pixmap(icon, x, y, size, dest);
break;
}
}
void
reset_icon(struct icon *icon)
{
switch (icon->type) {
case ICON_NONE:
break;
case ICON_PNG:
free(pixman_image_get_data(icon->png));
pixman_image_unref(icon->png);
icon->png = NULL;
break;
case ICON_SVG:
nsvgDelete(icon->svg);
icon->svg = NULL;
break;
case ICON_PIXMAP:
icon_pixmaps_dec(icon->pixmaps);
icon->pixmaps = NULL;
break;
}
icon->type = ICON_NONE;
}
void
string_list_free(const struct ref *ref)
{
struct string_list *b = (struct string_list *)ref;
tll_free_and_free(b->strings, free);
free(b);
}
void
string_list_dec(struct string_list *b)
{
ref_dec(&b->refcount);
}
struct string_list *
string_list_inc(struct string_list *p)
{
ref_inc(&p->refcount);
return p;
}
struct string_list *
string_list_new()
{
string_list_t strings = tll_init();
struct string_list *out = malloc(sizeof(*out));
out->strings = strings;
out->refcount = (struct ref){string_list_free, 1};
return out;
}
bool
dir_exists(char *path)
{
struct stat sb;
return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode);
}
static void
destroy_theme(struct icon_theme *theme)
{
if (!theme) {
return;
}
free(theme->name);
free(theme->comment);
tll_free_and_free(theme->inherits, free);
tll_free_and_free(theme->directories, free);
free(theme->dir);
tll_foreach(theme->subdirs, it)
{
free(it->item->name);
free(it->item);
tll_remove(theme->subdirs, it);
}
free(theme);
}
void
basedirs_free(const struct ref *ref)
{
struct basedirs *b = (struct basedirs *)ref;
tll_free_and_free(b->basedirs, free);
free(b);
}
void
basedirs_dec(struct basedirs *b)
{
ref_dec(&b->refcount);
}
struct basedirs *
basedirs_inc(struct basedirs *p)
{
ref_inc(&p->refcount);
return p;
}
struct basedirs *
basedirs_new(void)
{
string_list_t basedirs = tll_init();
struct basedirs *out = malloc(sizeof(*out));
out->basedirs = basedirs;
out->refcount = (struct ref){basedirs_free, 1};
return out;
}
struct basedirs *
get_basedirs(void)
{
string_list_t basedirs = tll_init();
// Follows freedesktop specs:
//
tll_push_back(basedirs, strdup("$HOME/.icons")); // deprecated
char *data_home = getenv("XDG_DATA_HOME");
tll_push_back(basedirs, strdup(data_home && *data_home ? "$XDG_DATA_HOME/icons" : "$HOME/.local/share/icons"));
tll_push_back(basedirs, strdup("/usr/share/pixmaps"));
char *data_dirs = getenv("XDG_DATA_DIRS");
if (!(data_dirs && *data_dirs)) {
data_dirs = "/usr/local/share:/usr/share";
}
data_dirs = strdup(data_dirs);
char *dir = strtok(data_dirs, ":");
do {
char *path = format_str("%s/icons", dir);
tll_push_back(basedirs, path);
} while ((dir = strtok(NULL, ":")));
free(data_dirs);
string_list_t basedirs_expanded = tll_init();
tll_foreach(basedirs, it)
{
wordexp_t p;
if (wordexp(it->item, &p, WRDE_UNDEF) == 0) {
if (dir_exists(p.we_wordv[0])) {
tll_push_back(basedirs_expanded, strdup(p.we_wordv[0]));
}
wordfree(&p);
}
};
tll_free_and_free(basedirs, free);
struct basedirs *out = malloc(sizeof(*out));
out->basedirs = basedirs_expanded;
out->refcount = (struct ref){basedirs_free, 1};
return out;
}
static const char *
group_handler(char *old_group, char *new_group, struct icon_theme *theme)
{
if (!old_group) {
return new_group && strcasecmp(new_group, "icon theme") == 0 ? NULL : "first group must be 'Icon Theme'";
}
if (strcasecmp(old_group, "icon theme") == 0) {
if (!theme->name) {
return "missing required key 'Name'";
} else if (!theme->comment) {
return "missing required key 'Comment'";
} else if (tll_length(theme->directories) == 0) {
return "missing required key 'Directories'";
}
} else {
if (tll_length(theme->subdirs) == 0) { // skip
return NULL;
}
struct icon_theme_subdir *subdir = tll_back(theme->subdirs);
if (!subdir->size) {
return "missing required key 'Size'";
}
switch (subdir->type) {
case ICON_DIR_FIXED:
subdir->max_size = subdir->min_size = subdir->size;
break;
case ICON_DIR_SCALABLE: {
if (!subdir->max_size)
subdir->max_size = subdir->size;
if (!subdir->min_size)
subdir->min_size = subdir->size;
break;
}
case ICON_DIR_THRESHOLD:
subdir->max_size = subdir->size + subdir->threshold;
subdir->min_size = subdir->size - subdir->threshold;
}
}
if (new_group) {
bool found = false;
tll_foreach(theme->directories, it)
{
if (strcmp(it->item, new_group)) {
found = true;
break;
}
}
if (found) {
struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir));
if (!subdir) {
return "out of memory";
}
subdir->name = strdup(new_group);
subdir->threshold = 2;
tll_push_back(theme->subdirs, subdir);
}
}
return NULL;
}
string_list_t
split_string(const char *str, const char *delims)
{
string_list_t res = tll_init();
char *copy = strdup(str);
char *token = strtok(copy, delims);
while (token) {
tll_push_back(res, strdup(token));
token = strtok(NULL, delims);
}
free(copy);
return res;
}
#define OOM_ERROR(val) \
if (!val) \
return "out of memory";
static const char *
entry_handler(char *group, char *key, char *value, struct icon_theme *theme)
{
if (strcmp(group, "Icon Theme") == 0) {
if (strcmp(key, "Name") == 0) {
theme->name = strdup(value);
OOM_ERROR(theme->name);
} else if (strcmp(key, "Comment") == 0) {
theme->comment = strdup(value);
OOM_ERROR(theme->comment);
} else if (strcmp(key, "Inherits") == 0) {
theme->inherits = split_string(value, ",");
} else if (strcmp(key, "Directories") == 0) {
theme->directories = split_string(value, ",");
} // Ignored: ScaledDirectories, Hidden, Example
} else {
if (tll_length(theme->subdirs) == 0) { // skip
return NULL;
}
struct icon_theme_subdir *subdir = tll_back(theme->subdirs);
if (strcmp(subdir->name, group) != 0) { // skip
return NULL;
}
if (strcmp(key, "Context") == 0) {
return NULL; // ignored, but explicitly handled to not fail parsing
} else if (strcmp(key, "Type") == 0) {
if (strcmp(value, "Fixed") == 0) {
subdir->type = ICON_DIR_FIXED;
} else if (strcmp(value, "Scalable") == 0) {
subdir->type = ICON_DIR_SCALABLE;
} else if (strcmp(value, "Threshold") == 0) {
subdir->type = ICON_DIR_THRESHOLD;
} else {
return "invalid value - expected 'Fixed', 'Scalable', or 'Threshold";
}
return NULL;
}
char *end;
int n = strtol(value, &end, 10);
if (*end != '\0') {
return "invalid value - expected a number";
}
if (strcmp(key, "Size") == 0) {
subdir->size = n;
} else if (strcmp(key, "MaxSize") == 0) {
subdir->max_size = n;
} else if (strcmp(key, "MinSize") == 0) {
subdir->min_size = n;
} else if (strcmp(key, "Threshold") == 0) {
subdir->threshold = n;
} else if (strcmp(key, "Scale") == 0) {
subdir->scale = n;
}
}
return NULL;
}
/*
* This is a Freedesktop Desktop Entry parser (essentially INI)
* It calls entry_handler for every entry
* and group_handler between every group (as well as at both ends)
* Handlers return whether an error occurred, which stops parsing
*/
struct icon_theme *
read_theme_file(char *basedir, char *theme_name)
{
struct icon_theme *theme = calloc(1, sizeof(struct icon_theme));
if (!theme) {
return NULL;
}
string_list_t tmp1 = tll_init();
subdirs_t tmp3 = tll_init();
theme->directories = tmp1;
theme->inherits = tmp1;
theme->subdirs = tmp3;
// look for index.theme file
char *path = format_str("%s/%s/index.theme", basedir, theme_name);
if (!path) {
return NULL;
}
FILE *theme_file = fopen(path, "r");
free(path);
if (!theme_file) {
free(theme);
return NULL;
}
string_list_t groups = tll_init();
char *full_line = NULL;
const char *error = NULL;
int line_no = 0;
size_t sz = 0;
while (true) {
const char *warning = NULL;
ssize_t nread = getline(&full_line, &sz, theme_file);
if (nread == -1) {
break;
}
++line_no;
char *line = full_line - 1;
while (isspace(*++line)) {
} // remove leading whitespace
if (!*line || line[0] == '#')
goto next; // ignore blank lines & comments
int len = nread - (line - full_line);
while (isspace(line[--len])) {
}
line[++len] = '\0'; // Remove trailing whitespace
if (line[0] == '[') { // Group handler
// check well-formed
int i = 1;
for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {
}
if (i != --len || line[i] != ']') {
warning = "malformed group header";
goto warn;
}
line[len] = '\0';
line = &line[1];
tll_foreach(groups, it)
{
// If duplicate move to back and continue
//
if (strcmp(it->item, line) == 0) {
tll_push_back(groups, it->item);
tll_remove(groups, it);
goto next;
}
}
char *last_group = tll_length(groups) ? tll_back(groups) : NULL;
error = group_handler(last_group, line, theme);
if (error) {
break;
}
tll_push_back(groups, strdup(line));
} else {
if (tll_length(groups) == 0) {
error = "unexpected content before first header";
break;
}
// check well-formed
int eok = 0;
for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {
}
int i = eok - 1;
while (isspace(line[++i])) {
}
if (line[i] == '[') {
// not handling localized values:
// https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s05.html
//
goto next;
}
if (line[i] != '=') {
warning = "malformed key-value pair";
goto warn;
}
line[eok] = '\0'; // split into key-value pair
char *value = &line[i];
while (isspace(*++value)) {
}
error = entry_handler(tll_back(groups), line, value, theme);
if (error) {
break;
}
}
next:
continue;
warn:;
assert(warning != NULL);
char *group = tll_length(groups) > 0 ? tll_back(groups) : "n/a";
LOG_INFO("Error during load of theme '%s' - parsing of file "
"'%s/%s/index.theme' encountered '%s' on line %d (group '%s') - continuing",
theme_name, basedir, theme_name, warning, line_no, group);
}
if (!error) {
if (tll_length(groups) > 0) {
error = group_handler(tll_back(groups), NULL, theme);
} else {
error = "empty file";
}
}
if (error) {
char *group = tll_length(groups) > 0 ? tll_back(groups) : "n/a";
LOG_WARN("Failed to load theme '%s' - parsing of file "
"'%s/%s/index.theme' failed on line %d (group '%s'): %s",
theme_name, basedir, theme_name, line_no, group, error);
destroy_theme(theme);
theme = NULL;
} else {
theme->dir = strdup(theme_name);
}
free(full_line);
fclose(theme_file);
tll_free_and_free(groups, free);
return theme;
}
themes_t
load_themes_in_dir(char *basedir)
{
themes_t themes = tll_init();
DIR *dir;
if (!(dir = opendir(basedir))) {
return themes;
}
struct dirent *entry;
while ((entry = readdir(dir))) {
if (entry->d_name[0] == '.')
continue;
struct icon_theme *theme = read_theme_file(basedir, entry->d_name);
if (theme) {
LOG_DBG("Found theme [%s]: dir: %s", theme->name, theme->dir);
tll_push_back(themes, theme);
}
}
closedir(dir);
return themes;
}
void
log_loaded_themes(themes_t themes)
{
LOG_INFO("Logging themes");
if (tll_length(themes) == 0) {
LOG_INFO("Warning: no icon themes loaded");
return;
}
const char sep[] = ", ";
size_t sep_len = strlen(sep);
size_t len = 0;
tll_foreach(themes, it) { len += strlen(it->item->name) + sep_len; }
char *str = malloc(len + 1);
if (!str) {
return;
}
char *p = str;
bool start = true;
tll_foreach(themes, it)
{
if (!start) {
memcpy(p, sep, sep_len);
p += sep_len;
}
start = false;
struct icon_theme *theme = it->item;
size_t name_len = strlen(theme->name);
memcpy(p, theme->name, name_len);
p += name_len;
}
*p = '\0';
LOG_INFO("Loaded icon themes: %s", str);
free(str);
}
void
themes_free(const struct ref *ref)
{
LOG_DBG("themes free");
struct themes *p = (struct themes *)ref;
tll_free_and_free(p->themes, destroy_theme);
free(p);
}
void
themes_dec(struct themes *t)
{
ref_dec(&t->refcount);
}
struct themes *
themes_inc(struct themes *t)
{
ref_inc(&t->refcount);
return t;
}
struct themes *
init_themes(struct basedirs *basedirs)
{
themes_t themes = tll_init();
tll_foreach(basedirs->basedirs, it)
{
themes_t dir_themes = load_themes_in_dir(it->item);
tll_foreach(dir_themes, it)
{
struct icon_theme *theme = it->item;
tll_remove(dir_themes, it);
tll_push_back(themes, theme);
}
}
log_loaded_themes(themes);
struct themes *out = malloc(sizeof(*out));
out->themes = themes;
out->refcount = (struct ref){themes_free, 1};
return out;
}
static char *
find_icon_in_subdir(char *name, char *basedir, char *theme, char *subdir)
{
static const char *extensions[] = {
"svg",
"png",
};
for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) {
char *path = format_str("%s/%s/%s/%s.%s", basedir, theme, subdir, name, extensions[i]);
if (path && access(path, R_OK) == 0) {
return path;
}
free(path);
}
return NULL;
}
static bool
theme_exists_in_basedir(char *theme, char *basedir)
{
char *path = format_str("%s/%s", basedir, theme);
bool ret = dir_exists(path);
free(path);
return ret;
}
static char *
find_icon_with_theme(string_list_t basedirs, themes_t themes, char *name, int size, char *theme_name, int *min_size,
int *max_size)
{
LOG_DBG("Looking for icon [%s] in theme [%s]", name, theme_name);
struct icon_theme *theme = NULL;
tll_foreach(themes, it)
{
theme = it->item;
if (strcmp(theme->name, theme_name) == 0) {
break;
}
theme = NULL;
}
if (!theme)
return NULL;
char *icon = NULL;
tll_foreach(basedirs, bd_it)
{
if (!theme_exists_in_basedir(theme->dir, bd_it->item)) {
continue;
}
tll_rforeach(theme->subdirs, sd_it)
{
struct icon_theme_subdir *subdir = sd_it->item;
if (size >= subdir->min_size && size <= subdir->max_size) {
if ((icon = find_icon_in_subdir(name, bd_it->item, theme->dir, subdir->name))) {
*min_size = subdir->min_size;
*max_size = subdir->max_size;
LOG_DBG("Found icon [%s] in theme [%s]", name, theme_name);
return icon;
}
}
}
}
// inexact match
unsigned smallest_error = -1; // UINT_MAX
tll_foreach(basedirs, bd_it)
{
if (!theme_exists_in_basedir(theme->dir, bd_it->item)) {
continue;
}
tll_rforeach(theme->subdirs, sd_it)
{
struct icon_theme_subdir *subdir = sd_it->item;
unsigned error = (size > subdir->max_size ? size - subdir->max_size : 0)
+ (size < subdir->min_size ? subdir->min_size - size : 0);
if (error < smallest_error) {
char *test_icon = find_icon_in_subdir(name, bd_it->item, theme->dir, subdir->name);
if (test_icon) {
if (icon)
free(icon);
icon = test_icon;
smallest_error = error;
*min_size = subdir->min_size;
*max_size = subdir->max_size;
}
}
}
}
if (!icon) {
tll_foreach(theme->inherits, it)
{
icon = find_icon_with_theme(basedirs, themes, name, size, it->item, min_size, max_size);
if (icon) {
break;
}
}
}
return icon;
}
static char *
find_fallback_icon(string_list_t basedirs, char *name, int *min_size, int *max_size)
{
tll_foreach(basedirs, it)
{
char *icon = find_icon_in_subdir(name, it->item, "", "");
if (icon) {
*min_size = 1;
*max_size = 512;
LOG_DBG("Found icon [%s] in fallback theme [%s]", name, it->item);
return icon;
}
}
return NULL;
}
char *
find_icon(themes_t themes, string_list_t basedirs, char *name, int size, string_list_t icon_themes, int *min_size,
int *max_size)
{
// TODO https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#implementation_notes
//
char *icon = NULL;
bool seenHicolor = false;
tll_foreach(icon_themes, it)
{
icon = find_icon_with_theme(basedirs, themes, name, size, it->item, min_size, max_size);
if (icon) {
return icon;
}
seenHicolor |= strcmp(it->item, "Hicolor") == 0;
}
if (!seenHicolor) {
icon = find_icon_with_theme(basedirs, themes, name, size, "Hicolor", min_size, max_size);
}
if (!icon) {
icon = find_fallback_icon(basedirs, name, min_size, max_size);
}
return icon;
}