mirror of
https://github.com/swaywm/sway.git
synced 2026-06-13 14:33:19 -04:00
Merge 81c31b0c4d into 97c342f9e1
This commit is contained in:
commit
a4bf086249
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
|
||||
|
||||
|
|
|
|||
|
|
@ -546,6 +546,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,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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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