mirror of
https://github.com/swaywm/sway.git
synced 2026-06-13 14:33:19 -04:00
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:
parent
c857ca3a97
commit
81c31b0c4d
11 changed files with 175 additions and 597 deletions
|
|
@ -7,6 +7,7 @@ packages:
|
|||
- libdisplay-info
|
||||
- libegl
|
||||
- libinput
|
||||
- libsfdo
|
||||
- libxcb
|
||||
- libxkbcommon
|
||||
- meson
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ tasks:
|
|||
cd subprojects
|
||||
ln -s ../../wlroots wlroots
|
||||
cd ..
|
||||
meson setup build --fatal-meson-warnings -Dtray=enabled -Dsd-bus-provider=basu
|
||||
# libsfdo is not packaged for FreeBSD yet; tray is disabled here.
|
||||
meson setup build --fatal-meson-warnings -Dtray=disabled
|
||||
- build: |
|
||||
cd sway
|
||||
ninja -C build
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
#ifndef _SWAYBAR_TRAY_ICON_H
|
||||
#define _SWAYBAR_TRAY_ICON_H
|
||||
|
||||
#include "list.h"
|
||||
|
||||
struct icon_theme_subdir {
|
||||
char *name;
|
||||
int size;
|
||||
|
||||
enum {
|
||||
THRESHOLD,
|
||||
SCALABLE,
|
||||
FIXED
|
||||
} type;
|
||||
|
||||
int max_size;
|
||||
int min_size;
|
||||
int threshold;
|
||||
};
|
||||
|
||||
struct icon_theme {
|
||||
char *name;
|
||||
char *comment;
|
||||
list_t *inherits; // char *
|
||||
list_t *directories; // char *
|
||||
|
||||
char *dir;
|
||||
list_t *subdirs; // struct icon_theme_subdir *
|
||||
};
|
||||
|
||||
void init_themes(list_t **themes, list_t **basedirs);
|
||||
void finish_themes(list_t *themes, list_t *basedirs);
|
||||
|
||||
/*
|
||||
* Finds an icon of a specified size given a list of themes and base directories.
|
||||
* If the icon is found, the pointers min_size & max_size are set to minimum &
|
||||
* maximum size that the icon can be scaled to, respectively.
|
||||
* Returns: path of icon (which should be freed), or NULL if the icon is not found.
|
||||
*/
|
||||
char *find_icon(list_t *themes, list_t *basedirs, char *name, int size,
|
||||
char *theme, int *min_size, int *max_size);
|
||||
|
||||
#endif
|
||||
|
|
@ -28,9 +28,9 @@ struct swaybar_sni {
|
|||
// icon properties
|
||||
struct swaybar_tray *tray;
|
||||
cairo_surface_t *icon;
|
||||
int min_size;
|
||||
int max_size;
|
||||
int icon_size;
|
||||
int target_size;
|
||||
struct sfdo_icon_theme *icon_theme_override; // non-NULL if IconThemePath set
|
||||
|
||||
// dbus properties
|
||||
char *watcher_id;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <basu/sd-bus.h>
|
||||
#endif
|
||||
#include <cairo.h>
|
||||
#include <sfdo-icon.h>
|
||||
#include <stdint.h>
|
||||
#include "swaybar/tray/host.h"
|
||||
#include "list.h"
|
||||
|
|
@ -30,13 +31,15 @@ struct swaybar_tray {
|
|||
struct swaybar_watcher *watcher_xdg;
|
||||
struct swaybar_watcher *watcher_kde;
|
||||
|
||||
list_t *basedirs; // char *
|
||||
list_t *themes; // struct swaybar_theme *
|
||||
struct sfdo_icon_ctx *icon_ctx;
|
||||
struct sfdo_icon_theme *icon_theme;
|
||||
char *icon_theme_name;
|
||||
};
|
||||
|
||||
struct swaybar_tray *create_tray(struct swaybar *bar);
|
||||
void destroy_tray(struct swaybar_tray *tray);
|
||||
void tray_in(int fd, short mask, void *data);
|
||||
void tray_reload_icon_theme(struct swaybar_tray *tray, const char *name);
|
||||
uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -97,9 +97,12 @@ else
|
|||
sdbus = dependency(get_option('sd-bus-provider'), required: get_option('tray'))
|
||||
endif
|
||||
|
||||
tray_deps_found = sdbus.found()
|
||||
sfdo_icon = dependency('libsfdo-icon', version: '>=0.1.3', required: get_option('tray'))
|
||||
sfdo_basedir = dependency('libsfdo-basedir', version: '>=0.1.3', required: get_option('tray'))
|
||||
|
||||
tray_deps_found = sdbus.found() and sfdo_icon.found() and sfdo_basedir.found()
|
||||
if get_option('tray').enabled() and not tray_deps_found
|
||||
error('Building with -Dtray=enabled, but sd-bus has not been not found')
|
||||
error('Building with -Dtray=enabled, but sd-bus or libsfdo has not been found')
|
||||
endif
|
||||
have_tray = (not get_option('tray').disabled()) and tray_deps_found
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,26 +425,89 @@ 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);
|
||||
if (sni->icon) {
|
||||
return;
|
||||
}
|
||||
// load_image() failed (unsupported format, file vanished,
|
||||
// etc); fall through to pixmap fallback if available.
|
||||
}
|
||||
}
|
||||
|
||||
list_t *pixmaps = sni->status[0] == 'N' ?
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue