swaybar/tray: replace icon lookup with libsfdo

Drops the in-tree icon theme parser and lookup logic in favor of
libsfdo-icon, which implements the icon theme spec.

Absolute icon paths are handled in swaybar itself with an access()
probe, since libsfdo intentionally doesn't deal with them. Pixmap
fallback still kicks in if load_image() fails on the absolute path.

KDE's IconThemePath is honored via sfdo_icon_theme_load_from() with
a per-SNI override theme that gets dropped when the property changes.

The min/max size cache per SNI is replaced by re-lookup when the
rendered size changes; libsfdo caches the parsed theme internally
and reads gtk's icon-theme.cache files, so this is cheap.

Theme switching at runtime is preserved via a new
tray_reload_icon_theme() called from parse_bar_config(). The old
code worked here implicitly because all themes were preloaded.

Closes: https://github.com/swaywm/sway/issues/8607
This commit is contained in:
Tim Jochen Kicker 2026-05-05 23:55:55 +02:00
parent c857ca3a97
commit 81c31b0c4d
11 changed files with 175 additions and 597 deletions

View file

@ -524,6 +524,8 @@ static bool handle_barconfig_update(struct swaybar *bar, const char *payload,
loop_remove_fd(bar->eventloop, bar->tray->fd);
destroy_tray(bar->tray);
bar->tray = NULL;
} else if (bar->tray) {
tray_reload_icon_theme(bar->tray, newcfg->icon_theme);
}
#endif

View file

@ -1,6 +1,5 @@
tray_files = have_tray ? [
'tray/host.c',
'tray/icon.c',
'tray/item.c',
'tray/tray.c',
'tray/watcher.c'
@ -18,7 +17,7 @@ swaybar_deps = [
wayland_cursor
]
if have_tray
swaybar_deps += sdbus
swaybar_deps += [sdbus, sfdo_icon, sfdo_basedir]
endif
executable(

View file

@ -1,525 +0,0 @@
#include <ctype.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wordexp.h>
#include "swaybar/tray/icon.h"
#include "config.h"
#include "list.h"
#include "log.h"
#include "stringop.h"
static int cmp_id(const void *item, const void *cmp_to) {
return strcmp(item, cmp_to);
}
static bool dir_exists(char *path) {
struct stat sb;
return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode);
}
static list_t *get_basedirs(void) {
list_t *basedirs = create_list();
list_add(basedirs, strdup("$HOME/.icons")); // deprecated
char *data_home = getenv("XDG_DATA_HOME");
list_add(basedirs, strdup(data_home && *data_home ?
"$XDG_DATA_HOME/icons" : "$HOME/.local/share/icons"));
list_add(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);
list_add(basedirs, path);
} while ((dir = strtok(NULL, ":")));
free(data_dirs);
list_t *basedirs_expanded = create_list();
for (int i = 0; i < basedirs->length; ++i) {
wordexp_t p;
if (wordexp(basedirs->items[i], &p, WRDE_UNDEF) == 0) {
if (dir_exists(p.we_wordv[0])) {
list_add(basedirs_expanded, strdup(p.we_wordv[0]));
}
wordfree(&p);
}
}
list_free_items_and_destroy(basedirs);
return basedirs_expanded;
}
static void destroy_theme(struct icon_theme *theme) {
if (!theme) {
return;
}
free(theme->name);
free(theme->comment);
list_free_items_and_destroy(theme->inherits);
list_free_items_and_destroy(theme->directories);
free(theme->dir);
for (int i = 0; i < theme->subdirs->length; ++i) {
struct icon_theme_subdir *subdir = theme->subdirs->items[i];
free(subdir->name);
free(subdir);
}
list_free(theme->subdirs);
free(theme);
}
static const char *group_handler(char *old_group, char *new_group,
struct icon_theme *theme) {
if (!old_group) {
return new_group && strcmp(new_group, "Icon Theme") == 0 ? NULL :
"first group must be 'Icon Theme'";
}
if (strcmp(old_group, "Icon Theme") == 0) {
if (!theme->name) {
return "missing required key 'Name'";
} else if (!theme->comment) {
return "missing required key 'Comment'";
} else if (!theme->directories) {
return "missing required key 'Directories'";
} else {
for (char *c = theme->name; *c; ++c) {
if (*c == ',' || *c == ' ') {
return "malformed theme name";
}
}
}
} else {
if (theme->subdirs->length == 0) { // skip
return NULL;
}
struct icon_theme_subdir *subdir =
theme->subdirs->items[theme->subdirs->length - 1];
if (!subdir->size) {
return "missing required key 'Size'";
}
switch (subdir->type) {
case FIXED: subdir->max_size = subdir->min_size = subdir->size;
break;
case SCALABLE: {
if (!subdir->max_size) subdir->max_size = subdir->size;
if (!subdir->min_size) subdir->min_size = subdir->size;
break;
}
case THRESHOLD:
subdir->max_size = subdir->size + subdir->threshold;
subdir->min_size = subdir->size - subdir->threshold;
}
}
if (new_group && list_seq_find(theme->directories, cmp_id, new_group) != -1) {
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;
list_add(theme->subdirs, subdir);
}
return NULL;
}
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);
} else if (strcmp(key, "Comment") == 0) {
theme->comment = strdup(value);
} 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 (theme->subdirs->length == 0) { // skip
return NULL;
}
struct icon_theme_subdir *subdir =
theme->subdirs->items[theme->subdirs->length - 1];
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 = FIXED;
} else if (strcmp(value, "Scalable") == 0) {
subdir->type = SCALABLE;
} else if (strcmp(value, "Threshold") == 0) {
subdir->type = 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;
} // Ignored: Scale
}
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
*/
static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
// look for index.theme file
char *path = format_str("%s/%s/index.theme", basedir, theme_name);
FILE *theme_file = fopen(path, "r");
free(path);
if (!theme_file) {
return NULL;
}
struct icon_theme *theme = calloc(1, sizeof(struct icon_theme));
if (!theme) {
fclose(theme_file);
return NULL;
}
theme->subdirs = create_list();
list_t *groups = create_list();
const char *error = NULL;
int line_no = 0;
char *full_line = NULL;
size_t full_len = 0;
ssize_t nread;
while ((nread = getline(&full_line, &full_len, theme_file)) != -1) {
++line_no;
char *line = full_line - 1;
while (isspace(*++line)) {} // remove leading whitespace
if (!*line || line[0] == '#') continue; // ignore blank lines & comments
int len = nread - (line - full_line);
while (isspace(line[--len])) {}
line[++len] = '\0'; // remove trailing whitespace
if (line[0] == '[') { // group header
// check well-formed
int i = 1;
for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {}
if (i != --len || line[i] != ']') {
error = "malformed group header";
break;
}
line[len] = '\0';
// check group is not duplicate
if (list_seq_find(groups, cmp_id, &line[1]) != -1) {
error = "duplicate group";
break;
}
// call handler
char *last_group = groups->length > 0 ? groups->items[groups->length - 1] : NULL;
error = group_handler(last_group, &line[1], theme);
if (error) {
break;
}
list_add(groups, strdup(&line[1]));
} else { // key-value pair
if (groups->length == 0) {
error = "unexpected content before first header";
break;
}
// check well-formed
int eok = 0;
for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale?
int i = eok - 1;
while (isspace(line[++i])) {}
if (line[i] != '=') {
error = "malformed key-value pair";
break;
}
line[eok] = '\0'; // split into key-value pair
char *value = &line[i];
while (isspace(*++value)) {}
// TODO unescape value
error = entry_handler(groups->items[groups->length - 1], line,
value, theme);
if (error) {
break;
}
}
}
if (!error) {
if (groups->length > 0) {
error = group_handler(groups->items[groups->length - 1], NULL, theme);
} else {
error = "empty file";
}
}
if (!error) {
theme->dir = strdup(theme_name);
} else {
char *last_group = groups->length > 0 ? groups->items[groups->length-1] : "n/a";
sway_log(SWAY_DEBUG, "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, last_group, error);
destroy_theme(theme);
theme = NULL;
}
free(full_line);
list_free_items_and_destroy(groups);
fclose(theme_file);
return theme;
}
static list_t *load_themes_in_dir(char *basedir) {
DIR *dir;
if (!(dir = opendir(basedir))) {
return NULL;
}
list_t *themes = create_list();
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) {
list_add(themes, theme);
}
}
closedir(dir);
return themes;
}
static void log_loaded_themes(list_t *themes) {
if (themes->length == 0) {
sway_log(SWAY_INFO, "Warning: no icon themes loaded");
return;
}
const char sep[] = ", ";
size_t sep_len = strlen(sep);
size_t len = 0;
for (int i = 0; i < themes->length; ++i) {
struct icon_theme *theme = themes->items[i];
len += strlen(theme->name) + sep_len;
}
char *str = malloc(len + 1);
if (!str) {
return;
}
char *p = str;
for (int i = 0; i < themes->length; ++i) {
if (i > 0) {
memcpy(p, sep, sep_len);
p += sep_len;
}
struct icon_theme *theme = themes->items[i];
size_t name_len = strlen(theme->name);
memcpy(p, theme->name, name_len);
p += name_len;
}
*p = '\0';
sway_log(SWAY_DEBUG, "Loaded icon themes: %s", str);
free(str);
}
void init_themes(list_t **themes, list_t **basedirs) {
*basedirs = get_basedirs();
*themes = create_list();
for (int i = 0; i < (*basedirs)->length; ++i) {
list_t *dir_themes = load_themes_in_dir((*basedirs)->items[i]);
if (dir_themes == NULL) {
continue;
}
list_cat(*themes, dir_themes);
list_free(dir_themes);
}
log_loaded_themes(*themes);
}
void finish_themes(list_t *themes, list_t *basedirs) {
for (int i = 0; i < themes->length; ++i) {
destroy_theme(themes->items[i]);
}
list_free(themes);
list_free_items_and_destroy(basedirs);
}
static char *find_icon_in_subdir(char *name, char *basedir, char *theme,
char *subdir) {
static const char *extensions[] = {
#if HAVE_GDK_PIXBUF
"svg",
#endif
"png",
#if HAVE_GDK_PIXBUF
"xpm" // deprecated
#endif
};
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 (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(list_t *basedirs, list_t *themes, char *name,
int size, char *theme_name, int *min_size, int *max_size) {
struct icon_theme *theme = NULL;
for (int i = 0; i < themes->length; ++i) {
theme = themes->items[i];
if (strcmp(theme->name, theme_name) == 0) {
break;
}
theme = NULL;
}
if (!theme) return NULL;
char *icon = NULL;
for (int i = 0; i < basedirs->length; ++i) {
if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
continue;
}
// search backwards to hopefully hit scalable/larger icons first
for (int j = theme->subdirs->length - 1; j >= 0; --j) {
struct icon_theme_subdir *subdir = theme->subdirs->items[j];
if (size >= subdir->min_size && size <= subdir->max_size) {
if ((icon = find_icon_in_subdir(name, basedirs->items[i],
theme->dir, subdir->name))) {
*min_size = subdir->min_size;
*max_size = subdir->max_size;
return icon;
}
}
}
}
// inexact match
unsigned smallest_error = -1; // UINT_MAX
for (int i = 0; i < basedirs->length; ++i) {
if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
continue;
}
for (int j = theme->subdirs->length - 1; j >= 0; --j) {
struct icon_theme_subdir *subdir = theme->subdirs->items[j];
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, basedirs->items[i],
theme->dir, subdir->name);
if (test_icon) {
icon = test_icon;
smallest_error = error;
*min_size = subdir->min_size;
*max_size = subdir->max_size;
}
}
}
}
if (!icon && theme->inherits) {
for (int i = 0; i < theme->inherits->length; ++i) {
icon = find_icon_with_theme(basedirs, themes, name, size,
theme->inherits->items[i], min_size, max_size);
if (icon) {
break;
}
}
}
return icon;
}
static char *find_fallback_icon(list_t *basedirs, char *name, int *min_size,
int *max_size) {
for (int i = 0; i < basedirs->length; ++i) {
char *icon = find_icon_in_subdir(name, basedirs->items[i], "", "");
if (icon) {
*min_size = 1;
*max_size = 512;
return icon;
}
}
return NULL;
}
char *find_icon(list_t *themes, list_t *basedirs, char *name, int size,
char *theme, int *min_size, int *max_size) {
// TODO https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#implementation_notes
char *icon = NULL;
if (theme) {
icon = find_icon_with_theme(basedirs, themes, name, size, theme,
min_size, max_size);
}
if (!icon && !(theme && strcmp(theme, "Hicolor") == 0)) {
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;
}

View file

@ -1,15 +1,17 @@
#include <arpa/inet.h>
#include <cairo.h>
#include <limits.h>
#include <sfdo-icon.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "config.h"
#include "swaybar/bar.h"
#include "swaybar/config.h"
#include "swaybar/image.h"
#include "swaybar/input.h"
#include "swaybar/tray/host.h"
#include "swaybar/tray/icon.h"
#include "swaybar/tray/item.h"
#include "swaybar/tray/tray.h"
#include "cairo_util.h"
@ -28,7 +30,7 @@ static bool sni_ready(struct swaybar_sni *sni) {
static void set_sni_dirty(struct swaybar_sni *sni) {
if (sni_ready(sni)) {
sni->target_size = sni->min_size = sni->max_size = 0; // invalidate previous icon
sni->target_size = sni->icon_size = 0; // invalidate previous icon
set_bar_dirty(sni->tray->bar);
}
}
@ -161,6 +163,13 @@ static int get_property_callback(sd_bus_message *msg, void *data,
}
}
// IconThemePath may change at runtime; the cached override theme is
// keyed on the old path, so drop it.
if (strcmp(prop, "IconThemePath") == 0) {
sfdo_icon_theme_destroy(sni->icon_theme_override);
sni->icon_theme_override = NULL;
}
if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ?
prop[0] == 'A' : has_prefix(prop, "Icon")))) {
set_sni_dirty(sni);
@ -313,6 +322,7 @@ void destroy_sni(struct swaybar_sni *sni) {
}
cairo_surface_destroy(sni->icon);
sfdo_icon_theme_destroy(sni->icon_theme_override);
free(sni->watcher_id);
free(sni->service);
free(sni->path);
@ -415,25 +425,88 @@ static enum hotspot_event_handling icon_hotspot_callback(
return HOTSPOT_PROCESS;
}
static void reload_sni(struct swaybar_sni *sni, char *icon_theme,
// Some apps send "foo.png" as an icon name. The icon theme spec doesn't
// allow extensions in lookups; strip a known one before passing to libsfdo.
static size_t name_len_without_extension(const char *name) {
size_t len = strlen(name);
if (len >= 4 && name[len - 4] == '.') {
const char *ext = &name[len - 3];
if (strcmp(ext, "png") == 0 || strcmp(ext, "svg") == 0 ||
strcmp(ext, "xpm") == 0) {
return len - 4;
}
}
return len;
}
static struct sfdo_icon_theme *get_sni_icon_theme(struct swaybar_sni *sni,
const char *theme_name) {
if (!sni->icon_theme_path) {
return sni->tray->icon_theme;
}
if (sni->icon_theme_override) {
return sni->icon_theme_override;
}
struct sfdo_string basedir = {
.data = sni->icon_theme_path,
.len = strlen(sni->icon_theme_path),
};
int options = SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING |
SFDO_ICON_THEME_LOAD_OPTION_RELAXED;
sni->icon_theme_override = sfdo_icon_theme_load_from(sni->tray->icon_ctx,
theme_name, &basedir, 1, options);
if (!sni->icon_theme_override) {
sway_log(SWAY_DEBUG, "%s: failed to load theme '%s' from '%s', "
"falling back to global theme", sni->watcher_id,
theme_name ? theme_name : "(default)", sni->icon_theme_path);
return sni->tray->icon_theme;
}
return sni->icon_theme_override;
}
static char *lookup_icon_path(struct swaybar_sni *sni, const char *icon_name,
const char *theme_name, int target_size) {
// Absolute paths are outside the XDG icon theme spec; handle directly
if (icon_name[0] == '/') {
return access(icon_name, R_OK) == 0 ? strdup(icon_name) : NULL;
}
struct sfdo_icon_theme *theme = get_sni_icon_theme(sni, theme_name);
if (!theme) {
return NULL;
}
int options = SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT;
#if !HAVE_GDK_PIXBUF
options |= SFDO_ICON_THEME_LOOKUP_OPTION_NO_SVG;
#endif
size_t name_len = name_len_without_extension(icon_name);
struct sfdo_icon_file *file = sfdo_icon_theme_lookup(theme, icon_name,
name_len, target_size, 1, options);
if (!file || file == SFDO_ICON_FILE_INVALID) {
return NULL;
}
char *path = strdup(sfdo_icon_file_get_path(file, NULL));
sfdo_icon_file_destroy(file);
return path;
}
static void reload_sni(struct swaybar_sni *sni, const char *icon_theme,
int target_size) {
char *icon_name = sni->status[0] == 'N' ?
sni->attention_icon_name : sni->icon_name;
if (icon_name) {
list_t *icon_search_paths = create_list();
list_cat(icon_search_paths, sni->tray->basedirs);
if (sni->icon_theme_path) {
list_add(icon_search_paths, sni->icon_theme_path);
}
char *icon_path = find_icon(sni->tray->themes, icon_search_paths,
icon_name, target_size, icon_theme,
&sni->min_size, &sni->max_size);
list_free(icon_search_paths);
char *icon_path = lookup_icon_path(sni, icon_name, icon_theme,
target_size);
if (icon_path) {
cairo_surface_destroy(sni->icon);
sni->icon = load_image(icon_path);
free(icon_path);
return;
if (sni->icon) {
return;
}
// load_image() failed (unsupported format, file vanished,
// etc); fall through to pixmap fallback if available.
}
}
@ -463,11 +536,10 @@ uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
int padding = output->bar->config->tray_padding;
int target_size = height - 2*padding;
if (target_size != sni->target_size && sni_ready(sni)) {
// check if another icon should be loaded
if (target_size < sni->min_size || target_size > sni->max_size) {
if (target_size != sni->icon_size) {
reload_sni(sni, output->bar->config->icon_theme, target_size);
sni->icon_size = target_size;
}
sni->target_size = target_size;
}

View file

@ -1,11 +1,12 @@
#include <cairo.h>
#include <poll.h>
#include <sfdo-basedir.h>
#include <sfdo-icon.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "swaybar/config.h"
#include "swaybar/bar.h"
#include "swaybar/tray/icon.h"
#include "swaybar/tray/host.h"
#include "swaybar/tray/item.h"
#include "swaybar/tray/tray.h"
@ -34,6 +35,22 @@ static int handle_lost_watcher(sd_bus_message *msg,
return 0;
}
static struct sfdo_icon_theme *load_icon_theme(struct sfdo_icon_ctx *ctx,
const char *name) {
int options = SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING |
SFDO_ICON_THEME_LOAD_OPTION_RELAXED;
struct sfdo_icon_theme *theme = sfdo_icon_theme_load(ctx, name, options);
if (!theme) {
// _ALLOW_MISSING falls back to hicolor when the named theme is
// missing, but returns NULL when the theme is found but invalid.
// Manually retry with hicolor in that case.
sway_log(SWAY_DEBUG, "Failed to load icon theme '%s', "
"falling back to hicolor", name ? name : "(default)");
theme = sfdo_icon_theme_load(ctx, "hicolor", options);
}
return theme;
}
struct swaybar_tray *create_tray(struct swaybar *bar) {
sway_log(SWAY_DEBUG, "Initializing tray");
@ -46,12 +63,29 @@ struct swaybar_tray *create_tray(struct swaybar *bar) {
struct swaybar_tray *tray = calloc(1, sizeof(struct swaybar_tray));
if (!tray) {
sd_bus_flush_close_unref(bus);
return NULL;
}
tray->bar = bar;
tray->bus = bus;
tray->fd = sd_bus_get_fd(tray->bus);
struct sfdo_basedir_ctx *basedir_ctx = sfdo_basedir_ctx_create();
if (!basedir_ctx) {
sway_log(SWAY_ERROR, "Failed to create sfdo basedir context");
goto error;
}
tray->icon_ctx = sfdo_icon_ctx_create(basedir_ctx);
sfdo_basedir_ctx_destroy(basedir_ctx);
if (!tray->icon_ctx) {
sway_log(SWAY_ERROR, "Failed to create sfdo icon context");
goto error;
}
const char *theme_name = bar->config->icon_theme;
tray->icon_theme = load_icon_theme(tray->icon_ctx, theme_name);
tray->icon_theme_name = theme_name ? strdup(theme_name) : NULL;
tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
tray->watcher_kde = create_watcher("kde", tray->bus);
@ -68,9 +102,13 @@ struct swaybar_tray *create_tray(struct swaybar *bar) {
init_host(&tray->host_xdg, "freedesktop", tray);
init_host(&tray->host_kde, "kde", tray);
init_themes(&tray->themes, &tray->basedirs);
return tray;
error:
sfdo_icon_ctx_destroy(tray->icon_ctx);
sd_bus_flush_close_unref(tray->bus);
free(tray);
return NULL;
}
void destroy_tray(struct swaybar_tray *tray) {
@ -86,10 +124,37 @@ void destroy_tray(struct swaybar_tray *tray) {
destroy_watcher(tray->watcher_xdg);
destroy_watcher(tray->watcher_kde);
sd_bus_flush_close_unref(tray->bus);
finish_themes(tray->themes, tray->basedirs);
sfdo_icon_theme_destroy(tray->icon_theme);
sfdo_icon_ctx_destroy(tray->icon_ctx);
free(tray->icon_theme_name);
free(tray);
}
void tray_reload_icon_theme(struct swaybar_tray *tray, const char *name) {
if ((!tray->icon_theme_name && !name) ||
(tray->icon_theme_name && name &&
strcmp(tray->icon_theme_name, name) == 0)) {
return;
}
sway_log(SWAY_DEBUG, "Reloading tray icon theme: '%s' -> '%s'",
tray->icon_theme_name ? tray->icon_theme_name : "(default)",
name ? name : "(default)");
sfdo_icon_theme_destroy(tray->icon_theme);
tray->icon_theme = load_icon_theme(tray->icon_ctx, name);
free(tray->icon_theme_name);
tray->icon_theme_name = name ? strdup(name) : NULL;
// invalidate per-SNI cached state so icons reload on next render
for (int i = 0; i < tray->items->length; ++i) {
struct swaybar_sni *sni = tray->items->items[i];
sfdo_icon_theme_destroy(sni->icon_theme_override);
sni->icon_theme_override = NULL;
sni->icon_size = 0;
sni->target_size = 0;
}
set_bar_dirty(tray->bar);
}
void tray_in(int fd, short mask, void *data) {
struct swaybar *bar = data;
int ret;