diff --git a/.gitignore b/.gitignore index ab32db2..87f02de 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ dwl-bar compile_flags.txt bar.log config.h +src/lib.h *-protocol.* diff --git a/Makefile b/Makefile index da7ab44..a705e84 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ # @version 0.0 VERSION = 0.0 PKG_CONFIG = pkg-config +PKG_EXISTS = $(PKG_CONFIG) --exists +SD_BUS = $(shell { $(PKG_EXISTS) libsystemd && echo "libsystemd"; } || { $(PKG_EXISTS) libelogind && echo "libelogind"; } || { $(PKG_EXISTS) basu && echo "basu"; } || exit 1; ) # paths PREFIX = /usr/local @@ -12,11 +14,15 @@ MANDIR = $(PREFIX)/share/man SRCDIR = src PKGS = wayland-client wayland-cursor pangocairo +PKGS += $(SD_BUS) FILES = $(SRCDIR)/main.c $(SRCDIR)/main.h $(SRCDIR)/log.c $(SRCDIR)/log.h \ $(SRCDIR)/render.c $(SRCDIR)/render.h $(SRCDIR)/event.c $(SRCDIR)/event.h \ $(SRCDIR)/util.c $(SRCDIR)/util.h $(SRCDIR)/shm.c $(SRCDIR)/shm.h \ $(SRCDIR)/input.c $(SRCDIR)/input.h $(SRCDIR)/user.c $(SRCDIR)/user.h \ $(SRCDIR)/bar.c $(SRCDIR)/bar.h $(SRCDIR)/config.h +FILES += $(SRCDIR)/lib.h $(SRCDIR)/icon.c $(SRCDIR)/icon.h $(SRCDIR)/item.c $(SRCDIR)/item.h \ + $(SRCDIR)/host.c $(SRCDIR)/host.h $(SRCDIR)/watcher.c $(SRCDIR)/watcher.h \ + $(SRCDIR)/tray.c $(SRCDIR)/tray.h OBJS = $(SRCDIR)/xdg-output-unstable-v1-protocol.o $(SRCDIR)/xdg-shell-protocol.o \ $(SRCDIR)/wlr-layer-shell-unstable-v1-protocol.o OBJS := $(filter-out $(SRCDIR)/xdg-output-unstable-v1-protocol.o,$(OBJS)) @@ -58,13 +64,21 @@ $(SRCDIR)/dwl-ipc-unstable-v2-protocol.c: $(WAYLAND_SCANNER) private-code \ protocols/dwl-ipc-unstable-v2.xml $@ +$(SRCDIR)/lib.h: + touch $(SRCDIR)/lib.h + echo -e "#ifndef LIB_H_\n#define LIB_H_\n" > $(SRCDIR)/lib.h + { $(PKG_EXISTS) libsystemd && echo -e "#define SYSTEMD 1\n" | tee -a $(SRCDIR)/lib.h; } || echo -e "#define SYSTEMD 0\n" | tee -a $(SRCDIR)/lib.h; + { $(PKG_EXISTS) libelogind && echo -e "#define ELOGIND 1\n" | tee -a $(SRCDIR)/lib.h; } || echo -e "#define ELOGIND 0\n" | tee -a $(SRCDIR)/lib.h; + { $(PKG_EXISTS) basu && echo -e "#define BASU 1\n" | tee -a $(SRCDIR)/lib.h; } || echo -e "#define BASU 0\n" | tee -a $(SRCDIR)/lib.h; + echo "#endif // LIB_H_" | tee -a $(SRCDIR)/lib.h + $(SRCDIR)/config.h: cp src/config.def.h $@ -dev: clean $(SRCDIR)/config.h $(OBJS) +dev: clean $(SRCDIR)/lib.h $(SRCDIR)/config.h $(OBJS) clean: - rm -f dwl-bar src/config.h src/*.o src/*-protocol.* + rm -f dwl-bar src/config.h src/lib.h src/*.o src/*-protocol.* dist: clean mkdir -p dwl-bar-$(VERSION) diff --git a/src/config.def.h b/src/config.def.h index f87bcec..6fc0c0c 100644 --- a/src/config.def.h +++ b/src/config.def.h @@ -10,6 +10,7 @@ static const int bar_top = 1; /* Boolean value, non-zero is true. If no static const int status_on_active = 1; /* Display the status on active monitor only. If not then on all. */ static const char *font = "Monospace 10"; static const char *terminal[] = { "alacritty", NULL }; +static const char *icon_theme = "Hicolor"; /* * Colors: @@ -53,6 +54,13 @@ static const Binding bindings[] = { { Click_Tag, BTN_MIDDLE, tag, 0, {0} }, { Click_Tag, BTN_RIGHT, toggle_view, 0, {0} }, { Click_Tag, BTN_LEFT, view, 0, {0} }, + { Click_Systray, BTN_LEFT, systray, 0, {0} }, + { Click_Systray, BTN_MIDDLE, systray, 0, {0} }, + { Click_Systray, BTN_RIGHT, systray, 0, {0} }, + { Click_Systray, Scroll_Up, systray, 0, {0} }, + { Click_Systray, Scroll_Down, systray, 0, {0} }, + { Click_Systray, Scroll_Left, systray, 0, {0} }, + { Click_Systray, Scroll_Right, systray, 0, {0} }, }; #endif // CONFIG_H_ diff --git a/src/host.c b/src/host.c new file mode 100644 index 0000000..791b3ef --- /dev/null +++ b/src/host.c @@ -0,0 +1,173 @@ +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "tray.h" +#include "host.h" +#include "item.h" +#include "util.h" +#include "main.h" + +static int get_registered_items_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int handle_new_watcher(sd_bus_message *m, void *userdata, sd_bus_error *error); +static int handle_registered_item(sd_bus_message *m, void *userdata, sd_bus_error *error); +static int handle_unregistered_item(sd_bus_message *m, void *userdata, sd_bus_error *error); +static int register_to_watcher(struct Host *host); + +int get_registered_items_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + if (sd_bus_message_is_method_error(m, NULL)) + panic("get_registered_items_callback sd_bus_message_is_method_error %s", sd_bus_message_get_error(m)->message); + + if (sd_bus_message_enter_container(m, 'v', NULL) < 0) + panic("get_registered_items_callback sd_bus_message_enter_container"); + + char **ids; + if (sd_bus_message_read_strv(m, &ids) < 0) + panic("get_registered_items_callback sd_bus_message_read_strv"); + + if (ids) { + struct Tray *tray = userdata; + for (char **id = ids; *id; id++) { + struct Item *item = item_create(tray, *id); + wl_list_insert(&tray->items, &item->link); + free(*id); + } + } + free(ids); + return 0; +} + +int handle_new_watcher(sd_bus_message *m, void *userdata, sd_bus_error *error) { + char *service, *old_owner, *new_owner; + if (sd_bus_message_read(m, "sss", &service, &old_owner, &new_owner) < 0) + panic("handle_new_watcher sd_bus_message_read"); + if (*old_owner) + return 0; + + struct Host *host = userdata; + if (STRING_EQUAL(service, host->interface)) + register_to_watcher(host); + + return 0; +} + +int handle_registered_item(sd_bus_message *m, void *userdata, sd_bus_error *error) { + char *id; + if (sd_bus_message_read(m, "s", &id) < 0) + panic("handle_registered_item sd_bus_message_read"); + + struct Tray *tray = userdata; + + struct Item *item; + wl_list_for_each(item, &tray->items, link) { + if (STRING_EQUAL(item->watcher_id, id)) // If we already have this item. + return 0; + } + + item = item_create(tray, id); + wl_list_insert(&tray->items, &item->link); + + return 0; +} + +int handle_unregistered_item(sd_bus_message *m, void *userdata, sd_bus_error *error) { + char *id; + if (sd_bus_message_read(m, "s", &id) < 0) + panic("sd_bus_message_read"); + + struct Tray *tray = userdata; + + struct Item *item, *tmp; + wl_list_for_each_safe(item, tmp, &tray->items, link) { + if (!(STRING_EQUAL(item->watcher_id, id))) + continue; + + wl_list_remove(&item->link); + item_destroy(item); + monitors_update(); + break; + } + + return 0; +} + +struct Host *host_create(char *protocol, struct Tray *tray) { + if (!protocol || !tray) + return NULL; + + struct Host *host = ecalloc(1, sizeof(*host)); + sd_bus_slot *register_slot = NULL, *unregister_slot = NULL, *watcher_slot = NULL; + const char *error; + + host->interface = string_create("org.%s.StatusNotifierWatcher", protocol); + host->tray = tray; + + if (sd_bus_match_signal(tray->bus, ®ister_slot, host->interface, watcher_path, host->interface, + "StatusNotifierItemRegistered", handle_registered_item, tray) < 0) { + error = "sd_bus_match_signal StatusNotifierItemRegistered protocol: %s"; + goto error; + } + + if (sd_bus_match_signal(tray->bus, &unregister_slot, host->interface, watcher_path, host->interface, + "StatusNotifierItemUnregistered", handle_unregistered_item, tray) < 0) { + error = "sd_bus_match_signal StatusNotifierItemUnregistered protocol: %s"; + goto error; + } + + if (sd_bus_match_signal(tray->bus, &watcher_slot, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", + handle_new_watcher, host) < 0) { + error = "sd_bus_match_signal NameOwnerChanged protocol: %s"; + goto error; + } + + pid_t pid = getpid(); + host->name = string_create("org.%s.StatusNotifierHost-%d", protocol, pid); + + if (!register_to_watcher(host)) { + error = "register_to_watcher protocol: %s"; + goto error; + } + + sd_bus_slot_set_floating(register_slot, 0); + sd_bus_slot_set_floating(unregister_slot, 0); + sd_bus_slot_set_floating(watcher_slot, 0); + + return host; + +error: + sd_bus_slot_unref(register_slot); + sd_bus_slot_unref(unregister_slot); + sd_bus_slot_unref(watcher_slot); + host_destroy(host); + panic(error, protocol); + return NULL; +} + +void host_destroy(struct Host *host) { + if (!host) return; + + sd_bus_release_name(host->tray->bus, host->name); + free(host->name); + free(host->interface); +} + +int register_to_watcher(struct Host *host) { + if (sd_bus_call_method_async(host->tray->bus, NULL, + host->interface, watcher_path, host->interface, + "RegisterStatusNotifierHost", NULL, NULL, "s", host->name) < 0) + return 0; + + if (sd_bus_call_method_async(host->tray->bus, NULL, + host->interface, watcher_path, + "org.freedesktop.DBus.Properties", "Get", + get_registered_items_callback, host, "ss", + host->interface, "RegisteredStatusNotifierItems") < 0) + return 0; + + return 1; +} diff --git a/src/host.h b/src/host.h new file mode 100644 index 0000000..50dc029 --- /dev/null +++ b/src/host.h @@ -0,0 +1,15 @@ +#ifndef HOST_H_ +#define HOST_H_ + +#include "tray.h" + +struct Host { + struct Tray *tray; + char *name; + char *interface; +}; + +struct Host *host_create(char *protocol, struct Tray *tray); +void host_destroy(struct Host *host); + +#endif // HOST_H_ diff --git a/src/icon.c b/src/icon.c new file mode 100644 index 0000000..da2f685 --- /dev/null +++ b/src/icon.c @@ -0,0 +1,523 @@ +#include "icon.h" +#include "util.h" +#include "log.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void add_dir(struct List *list, char *dir); +static int basedir_has_theme(char *theme, char *dir); +static int dir_exists(char *path); +static char *entry_handler(char *group, char *key, char *value, struct Theme *theme); +static void get_basedirs(struct List *basedirs); +static char *get_fallback_icon(struct List *themes, struct List *basedirs, char *name); +static char *group_handler(char *last_group, char *new_group, struct Theme *theme); +static void load_themes(struct List *themes, char *basedir); +static struct Theme *read_theme_file(char *dir, char *name); +static struct List *split_string(const char *str, const char *delim); +static char *subdir_get_icon(char *name, char *basedir, char *theme, char *subdir); +static void theme_destroy(struct Theme *theme); +static void theme_element_destroy(void *ptr); +static char *theme_get_icon(struct List *themes, struct List *basedirs, char *name, int size, const char *theme_name); +static int theme_name_compare(const void *left, const void *right); + +void add_dir(struct List *list, char *dir) { + // If the directory doesn't exist then free it and don't think about it. + if (!dir_exists(dir)) { + free(dir); + return; + } + + list_add(list, dir); +} + +int basedir_has_theme(char *theme, char *dir) { + char *path = string_create("%s/%s", dir, theme); + int exist = dir_exists(path); + free(path); + return exist; +} + +int dir_exists(char *path) { + struct stat sb; + return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); +} + +// Return error as string. +char *entry_handler(char *group, char *key, char *value, struct Theme *theme) { + struct List *temp = NULL; + + if (STRING_EQUAL(group, "Icon Theme")) { + if (STRING_EQUAL(key, "Name")) { + theme->name = strdup(value); + } else if (STRING_EQUAL(key, "Comment")) { + theme->comment = strdup(value); + } else if (STRING_EQUAL(key, "Inherits")) { + temp = split_string(value, ","); // This only happens once + list_copy(theme->inherits, temp); + } else if (STRING_EQUAL(key, "Directories")) { + temp = split_string(value, ","); // This only happens once + list_copy(theme->directories, temp); + } + + if (temp) + list_destroy(temp); + } else { + if (theme->subdirectories->length == 0) + return NULL; + + struct Subdir *subdir = theme->subdirectories->data[theme->subdirectories->length-1]; + if (strcmp(subdir->name, group) != 0) // Skip + return NULL; + + if (STRING_EQUAL(key, "Context")) { + return NULL; // Ignore + } else if (STRING_EQUAL(key, "Type")) { + if (STRING_EQUAL(value, "Fixed")) { + subdir->type = FIXED; + } else if (STRING_EQUAL(value, "Scalable")) { + subdir->type = SCALABLE; + } else if (STRING_EQUAL(value, "Threshold")) { + subdir->type = THRESHOLD; + } else { + return "invalid value - expected 'Fixed', 'Scalable' or 'Threshold'."; + } + return NULL; + } + + char *end; + long n = strtol(value, &end, 10); + if (*end != '\0') + return "invalid value - expected a number"; + + if (STRING_EQUAL(key, "Size")) { + subdir->size = n; + } else if (STRING_EQUAL(key, "MaxSize")) { + subdir->max_size = n; + } else if (STRING_EQUAL(key, "MinSize")) { + subdir->min_size = n; + } else if (STRING_EQUAL(key, "Threshold")) { + subdir->threshold = n; + } // Ignore scale. + } + + return NULL; +} + +void get_basedirs(struct List *basedirs) { + size_t len; + char *home_icons, *data_home_icons, + *dir, *path, *data_dirs; + const char *data_home, *home; + + list_add(basedirs, strdup("/usr/share/pixmaps")); + + // Create and add home data icons dir to array. + home = getenv("HOME"); + data_home = getenv("XDG_DATA_HOME"); + char *format = "%s/icons"; + if (!(data_home && *data_home)) { + data_home = home; + format = "%s/.local/share/icons"; + } + data_home_icons = string_create(format, data_home); + + add_dir(basedirs, data_home_icons); + + // Create and add data dirs to array. + data_dirs = getenv("XDG_DATA_DIRS"); + if (!(data_dirs && *data_dirs)) + data_dirs = "/usr/local/share:/usr/share"; + data_dirs = strdup(data_dirs); + + dir = strtok(data_dirs, ":"); + do { + path = string_create("%s/icons", dir); + add_dir(basedirs, path); + } while ((dir = strtok(NULL, ":"))); + free(data_dirs); +} + +char *get_fallback_icon(struct List *themes, struct List *basedirs, char *name) { + char *basedir, *icon = NULL; + struct Theme *theme; + struct Subdir *subdir; + int i; + // Do it backwards because we only really need to do this for the icon_theme_path of an item. + for (i = basedirs->length-1; i >= 0; i--) { + basedir = basedirs->data[i]; + icon = subdir_get_icon(name, basedir, "", ""); + if (icon) + return icon; + } + + for (i = 0; i < basedirs->length; i++) { + basedir = basedirs->data[i]; + for (int n = 0; n < themes->length; n++) { + theme = themes->data[n]; + for (int x = theme->subdirectories->length-1; x >= 0; x--) { + subdir = theme->subdirectories->data[x]; + icon = subdir_get_icon(name, basedir, theme->dir, subdir->name); + if (icon) + return icon; + } + } + } + + return NULL; +} + +char *get_icon(struct List *themes, struct List *basedirs, char *name, int size, const char *theme) { + if (!themes || !basedirs || !name || !theme) + return NULL; + + char *icon = NULL; + if (theme) { + icon = theme_get_icon(themes, basedirs, name, size, theme); + } + if (!icon && !(theme && STRING_EQUAL(theme, "Hicolor"))) { + icon = theme_get_icon(themes, basedirs, name, size, "Hicolor"); + } + if (!icon) { + icon = get_fallback_icon(themes, basedirs, name); + } + + return icon; +} + +// Return error as string. +char *group_handler(char *last_group, char *new_group, struct Theme *theme) { + if (!last_group) + return new_group && STRING_EQUAL(new_group, "Icon Theme") ? NULL + : "first group must be 'Icon Theme'"; + + if (STRING_EQUAL(last_group, "Icon Theme")) { + if (!theme->name) { + return "missing required key 'Name'"; + } else if (!theme->comment) { + return "missing required key 'Comment'"; + } else if (theme->directories->length == 0) { + return "missing required key 'Directories'"; + } else { + for (char *c = theme->name; *c; c++) + if (*c == ',' || *c == ' ') + return "malformed theme name"; + } + } else { + if (theme->subdirectories->length == 0) + return NULL; + + struct Subdir *subdir = theme->subdirectories->data[theme->subdirectories->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) + return NULL; + + if (list_cmp_find(theme->directories, new_group, cmp_id) != -1) { + struct Subdir *subdir = list_add(theme->subdirectories, ecalloc(1, sizeof(*subdir))); + subdir->name = strdup(new_group); + subdir->threshold = 2; + } + + return NULL; +} + +void load_themes(struct List *themes, char *basedir) { + DIR *dir; + if (!(dir = opendir(basedir))) + return; + + struct dirent *entry; + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') continue; + struct Theme *theme = read_theme_file(basedir, entry->d_name); + if (theme) + list_add(themes, theme); + } + + struct Theme *theme; + struct Subdir *subdir; + for (int i = 0; i < themes->length; i++) { + theme = themes->data[i]; + if (!theme) continue; + for (int n = 0; n < theme->subdirectories->length; n++) { + subdir = theme->subdirectories->data[n]; + if (!subdir) continue; + } + } + + closedir(dir); +} + +struct Theme *read_theme_file(char *dir, char *name) { + char *path = string_create("%s/%s/index.theme", dir, name); + FILE *index_theme = fopen(path, "r"); + free(path); + if (!index_theme) + return NULL; + + struct Theme *theme = ecalloc(1, sizeof(*theme)); + struct List *groups = list_create(0); // char* + theme->inherits = list_create(0); + theme->directories = list_create(0); + theme->subdirectories = list_create(0); + + const char *error = NULL; + char *full_line = NULL; + int line_no = 0; + size_t full_len = 0; + ssize_t nread; + while ((nread = getline(&full_line, &full_len, index_theme)) > 0) { + line_no++; + + char *line = full_line - 1; + while (isspace(*++line)); // Remove whitespace + if (!*line || line[0] == '#') continue; // Ignore blank or comments + + int len = nread - (line - full_line); + while (isspace(line[--len])); // Remove whitespace + line[++len] = '\0'; + + if (line[0] == '[') { // group header + int i = 1; + for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; i++); + if (i != --len || line[i] != ']') { + error = "malformed group header"; + goto error; + } + + line[len] = '\0'; + + // check for duplicate groups + if (list_cmp_find(groups, &line[1], cmp_id) != -1) { + error = "duplicate group"; + goto error; + } + + // Handler + char *last_group = groups->length != 0 ? groups->data[groups->length-1] : NULL; + error = group_handler(last_group, &line[1], theme); + if (error) + goto error; + + list_add(groups, strdup(&line[1])); + + } else { + if (!groups->length) { // If empty + error = "unexpected content before first header"; + goto error; + } + + int eok = 0; + for (; isalnum(line[eok]) || line[eok] == '-'; eok++); + + + int i = eok - 1; + while (isspace(line[++i])); + if (line[i] != '=') { + error = "malformed key-value pair"; + goto error; + } + + line[eok] = '\0'; + char *value = &line[i]; + while(isspace(*++value)); + + error = entry_handler(groups->data[groups->length-1], line, value, theme); + if (error) + goto error; + } + } + + if (groups->length) { // Not empty + error = group_handler(groups->data[groups->length-1], NULL, theme); + } else { + error = "empty file"; + } + + if (!error) + theme->dir = strdup(name); +error: + + if (error) { + theme_destroy(theme); + theme = NULL; + } + + list_elements_destroy(groups, free); + free(full_line); + fclose(index_theme); + return theme; +} + +struct List *split_string(const char *str, const char *delim) { + struct List *list = list_create(0); + char *copy = strdup(str), + *token = strtok(copy, delim); + + while (token) { + list_add(list, strdup(token)); + token = strtok(NULL, delim); + } + free(copy); + return list; +} + +// Only support png cause I don't want to add another dependency lol. +char *subdir_get_icon(char *name, char *basedir, char *theme, char *subdir) { + // So that we don't include a / if the theme or subdir doesn't contain anything. + // So the icon path is usable. + theme = string_create(strlen(theme) ? "%s/" : "%s", theme); + subdir = string_create(strlen(subdir) ? "%s/" : "%s", subdir); + + char *path = string_create("%s/%s%s%s.png", basedir, theme, subdir, name); + + free(theme); + free(subdir); + + if (access(path, R_OK) == 0) + return path; + + free(path); + return NULL; +} + +void themes_create(struct List *themes, struct List *basedirs) { + if (!themes || !basedirs) + return; + + get_basedirs(basedirs); // char** + + char *basedir; + for (int i = 0; i < basedirs->length; i++) { + basedir = basedirs->data[i]; + load_themes(themes, basedir); // struct Theme** + } +} + +void theme_destroy(struct Theme *theme) { + if (!theme) return; + + free(theme->name); + free(theme->comment); + free(theme->dir); + + list_elements_destroy(theme->inherits, free); + list_elements_destroy(theme->directories, free); + struct Subdir *subdir; + for (int i = 0; i < theme->subdirectories->length; i++) { + subdir = theme->subdirectories->data[i]; + free(subdir->name); + free(subdir); + } + list_destroy(theme->subdirectories); + + free(theme); +} + +void theme_element_destroy(void *ptr) { + theme_destroy((struct Theme *)ptr); +} + +void themes_destroy(struct List *themes, struct List *basedirs) { + if (!themes || !basedirs) + return; + + list_elements_destroy(basedirs, free); + list_elements_destroy(themes, theme_element_destroy); +} + +char *theme_get_icon(struct List *themes, struct List *basedirs, char *name, int size, const char *theme_name) { + // Find theme + int pos = 0; + struct Theme *theme; + if ((pos = list_cmp_find(themes, theme_name, theme_name_compare)) == -1) { // The desired theme isn't available. + return NULL; + } else { + theme = themes->data[pos]; + } + + char *icon = NULL, *basedir; + for (int i = 0; i < basedirs->length; i++) { + basedir = basedirs->data[i]; + struct Subdir *subdir; + for (int n = theme->subdirectories->length-1; n >= 0; n--) { + subdir = theme->subdirectories->data[n]; + if (size < subdir->min_size || size > subdir->max_size || + !(icon = subdir_get_icon(name, basedir, theme->dir, subdir->name))) + continue; + + return icon; + } + } + // find an inexact but close match. + uint smallest_error = -1; // The max num uint can store + for (int i = 0; i < basedirs->length; i++) { + basedir = basedirs->data[i]; + if (!basedir_has_theme(theme->dir, basedir)) + continue; + + struct Subdir *subdir; + for (int n = theme->subdirectories->length-1; n >= 0; n--) { + subdir = theme->subdirectories->data[n]; + uint 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 = subdir_get_icon(name, basedir, theme->dir, subdir->name); + if (!test) + continue; + icon = test; + smallest_error = error; + } + } + } + + if (icon || theme->inherits->length == 0) + return icon; + + // If we get here then we couldn't find an icon, so check the inherited themes. If there are any. + char *inherit; + for (int i = 0; i < theme->inherits->length; i++) { + inherit = theme->inherits->data[i]; + icon = theme_get_icon(themes, basedirs, name, size, inherit); + if (icon) + break; + } + + return icon; +} + +int theme_name_compare(const void *left, const void *right) { + const char *theme_name = left; + const struct Theme *theme = right; + + return strcmp(theme_name, theme->name); +} diff --git a/src/icon.h b/src/icon.h new file mode 100644 index 0000000..2ffcd18 --- /dev/null +++ b/src/icon.h @@ -0,0 +1,29 @@ +#ifndef ICON_H_ +#define ICON_H_ + +#include "tray.h" + +struct Subdir { + char *name; + int size, max_size, min_size, threshold; + + enum { + THRESHOLD, + SCALABLE, + FIXED + } type; +}; + +struct Theme { + char *name, *comment, *dir; + struct List *inherits /* char* */, + *directories /* char* */, + *subdirectories /* struct Subdir* */; +}; + +char *get_icon(struct List *themes, struct List *basedirs, + char *name, int size, const char *theme); +void themes_create(struct List *themes, struct List *basedirs); +void themes_destroy(struct List *themes, struct List *basedirs); + +#endif // ICON_H_ diff --git a/src/item.c b/src/item.c new file mode 100644 index 0000000..cc9b0f8 --- /dev/null +++ b/src/item.c @@ -0,0 +1,442 @@ +#include "item.h" +#include "config.def.h" +#include "icon.h" +#include "cairo.h" +#include "config.h" +#include "icon.h" +#include "main.h" +#include "render.h" +#include "util.h" +#include "log.h" +#include +#include +#include +#include +#include +#include +#include + +static int check_message_sender(struct Item *item, sd_bus_message *m, const char *signal); +static void get_item_property(struct Item *item, const char *property, const char *type, void *dest); +static int get_property_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int handle_new_attention_icon(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int handle_new_icon(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int handle_new_status(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static void item_invalidate(struct Item *item); +static void item_match_signal(struct Item *item, char *signal, sd_bus_message_handler_t callback); +static cairo_surface_t *load_image(const char *path); +static cairo_surface_t *load_icon(struct Item *item, const char *theme, int size); +static int read_pixmap(sd_bus_message *m, struct Item *item, const char *property, struct wl_list *destination); +static cairo_surface_t *scale_icon(cairo_surface_t *icon, int width, int height); + +const char *button_to_method(int button) { + switch (button) { + case BTN_LEFT: + return "Activate"; + case BTN_MIDDLE: + return "SecondaryActivate"; + case BTN_RIGHT: + return "ContextMenu"; + case Scroll_Up: + return "ScrollUp"; + case Scroll_Down: + return "ScrollDown"; + case Scroll_Left: + return "ScrollLeft"; + case Scroll_Right: + return "ScrollRight"; + default: + return "nop"; + } +} + +int check_message_sender(struct Item *item, sd_bus_message *m, const char *signal) { + int has_well_known_names = sd_bus_creds_get_mask(sd_bus_message_get_creds(m)) & SD_BUS_CREDS_WELL_KNOWN_NAMES; + if (item->service[0] == ':' || has_well_known_names) + return 1; + + return 0; +} + +void get_item_property(struct Item *item, const char *property, const char *type, void *destination) { + struct ItemData *data = ecalloc(1, sizeof(*data)); + data->item = item; + data->property = property; + data->type = type; + data->destination = destination; + + if (sd_bus_call_method_async(item->tray->bus, &data->slot, item->service, + item->path, "org.freedesktop.DBus.Properties", "Get", + get_property_callback, data, "ss", item->interface, property) < 0) { + free(data); + return; + } + + wl_list_insert(&item->item_data, &data->link); +} + +int get_property_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + struct ItemData *data = userdata; + struct Item *item = data->item; + const char *property = data->property, + *type = data->type; + if (sd_bus_message_is_method_error(m, NULL)) + goto cleanup; + + if (sd_bus_message_enter_container(m, 'v', type) < 0) + panic("get_property_callback sd_bus_message_enter_container"); + + if (!type) { + if (!read_pixmap(m, item, property, (struct wl_list*)data->destination)) + goto cleanup; + } else { + if (*type == 's' || *type == 'o') + free(*(char**)data->destination); + + if (sd_bus_message_read(m, type, data->destination) < 0) + panic("get_property_callback sd_bus_message_read"); + + if (*type == 's' || *type == 'o') { + char **str = data->destination; + *str = strdup(*str); + } + } + + item_invalidate(item); + +cleanup: + wl_list_remove(&data->link); + free(data); + return 1; +} + +int handle_new_attention_icon(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + struct ItemData *data = userdata; + struct Item *item = data->item; + struct Pixmap *pixmap, *tmp; + wl_list_for_each_safe(pixmap, tmp, &item->attention_pixmap, link) { + wl_list_remove(&pixmap->link); + free(pixmap->pixels); + free(pixmap); + } + + get_item_property(item, "AttentionIconName", "s", &item->attention_icon_name); + get_item_property(item, "AttentionIconPixmap", NULL, &item->attention_pixmap); + + return check_message_sender(item, m, "attention icon"); +} + +int handle_new_icon(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + struct ItemData *data = userdata; + struct Item *item = data->item; + struct Pixmap *pixmap, *tmp; + wl_list_for_each_safe(pixmap, tmp, &item->icon_pixmap, link) { + if (!pixmap) continue; + wl_list_remove(&pixmap->link); + free(pixmap->pixels); + free(pixmap); + } + + get_item_property(item, "IconName", "s", &item->icon_name); + get_item_property(item, "IconPixmap", NULL, &item->icon_pixmap); + if (STRING_EQUAL(item->interface, "org.kde.StatusNotifierItem")) + get_item_property(item, "IconThemePath", "s", &item->icon_theme_path); + + return check_message_sender(item, m, "icon"); +} + +int handle_new_status(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + struct ItemData *data = userdata; + struct Item *item = data->item; + int ret = check_message_sender(item, m, "status"); + if (ret) { + char *status; + if (sd_bus_message_read(m, "s", &status) < 0) { + return 0; + } else { + if (item->status) + free(item->status); + item->status = strdup(status); + item_invalidate(item); + } + } else { + get_item_property(item, "Status", "s", &item->status); + } + + return ret; +} + +int is_passive(struct Item *item) { + if (!item) return 0; + + return item->status && item->status[0] == 'P'; +} + +int is_ready(struct Item *item) { + if (!item) return 0; + + return item->status && (item->status[0] == 'N' ? + item->attention_icon_name || !(wl_list_empty(&item->attention_pixmap)) : + item->icon_name || !(wl_list_empty(&item->icon_pixmap))); +} + +struct Item *item_create(struct Tray* tray, char *id) { + if (!tray || !id) return NULL; + + struct Item *item = ecalloc(1, sizeof(*item)); + char *path; + + item->tray = tray; + item->watcher_id = strdup(id); + item->status = item->icon_name = item->attention_icon_name = + item->menu_path = item->menu_path = NULL; + wl_list_init(&item->icon_pixmap); + wl_list_init(&item->attention_pixmap); + wl_list_init(&item->item_data); + + path = strchr(id, '/'); + if (!path) { + item->service = strdup(id); + item->path = strdup("/StatusNotifierItem"); + item->interface = "org.freedesktop.StatusNotifierItem"; + } else { + item->service = strndup(id, path - id); + item->path = strdup(path); + item->interface = "org.kde.StatusNotifierItem"; + get_item_property(item, "IconThemePath", "s", (void**)&item->icon_theme_path); + } + + get_item_property(item, "Status", "s", &item->status); + get_item_property(item, "IconName", "s", &item->icon_name); + get_item_property(item, "IconPixmap", NULL, &item->icon_pixmap); + get_item_property(item, "AttentionIconName", "s", &item->attention_icon_name); + get_item_property(item, "AttentionIconPixmap", NULL, &item->attention_pixmap); + get_item_property(item, "ItemIsMenu", "b", &item->is_menu); + get_item_property(item, "Menu", "o", &item->menu_path); + + item_match_signal(item, "NewIcon", handle_new_icon); + item_match_signal(item, "NewAttentionIcon", handle_new_attention_icon); + item_match_signal(item, "NewStatus", handle_new_status); + + return item; +} + +void item_destroy(struct Item* item) { + if (!item) return; + + free(item->watcher_id); + free(item->service); + free(item->path); + free(item->status); + free(item->icon_name); + free(item->attention_icon_name); + free(item->menu_path); + free(item->icon_theme_path); + cairo_surface_destroy(item->icon); + + struct Pixmap *pixmap, *tmp; + wl_list_for_each_safe(pixmap, tmp, &item->icon_pixmap, link) { + wl_list_remove(&pixmap->link); + free(pixmap->pixels); + free(pixmap); + } + wl_list_for_each_safe(pixmap, tmp, &item->attention_pixmap, link) { + wl_list_remove(&pixmap->link); + free(pixmap->pixels); + free(pixmap); + } + + struct ItemData *data, *temp; + wl_list_for_each_safe(data, temp, &item->item_data, link) { + wl_list_remove(&data->link); + sd_bus_slot_unref(data->slot); + free(data); + } + + free(item); +} + +void item_invalidate(struct Item *item) { + item->invalid = 1; + monitors_update(); +} + +int item_is_clicked(struct Item *item, double x, double y) { + if (!item) return 0; + + return (x > item->x && y > item->y && + x < (item->x + item->width) && y < (item->y + item->height)); +} + +void item_match_signal(struct Item *item, char *signal, sd_bus_message_handler_t callback) { + struct ItemData *data = ecalloc(1, sizeof(*data)); + data->item = item; + if (sd_bus_match_signal_async(item->tray->bus, &data->slot, + item->service, item->path, item->interface, signal, callback, NULL, data) < 0) { + free(data); + return; + } + + wl_list_insert(&item->item_data, &data->link); +} + +void item_render(struct Pipeline *pipeline, cairo_t *painter, struct Item *item, int *x, int *y) { + if (is_passive(item) || !is_ready(item) || !pipeline || !painter || !item || !x || !y) + return; + + int icon_size = pipeline->font->height; + item->x = *x; + item->y = *y; + item->width = icon_size+2; + item->height = pipeline->shm->height; + + pipeline_set_colorscheme(pipeline, schemes[InActive_Scheme]); + pipeline_color_background(pipeline, painter); + cairo_rectangle(painter, *x, 0, item->width, item->height); + cairo_fill(painter); + + if (item->invalid) { + cairo_surface_destroy(item->icon); + item->icon = load_icon(item, icon_theme, icon_size); + item->invalid = 0; + } + + if (!item->icon) { + int sad_face_size = icon_size*0.8; + cairo_surface_t *item_icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + icon_size, icon_size); + cairo_t *sad_face = cairo_create(item_icon); + cairo_set_source_rgba(painter, + (double)schemes[Active_Scheme][0][0]/255, + (double)schemes[Active_Scheme][0][1]/255, + (double)schemes[Active_Scheme][0][2]/255, + (double)schemes[Active_Scheme][0][3]/255); + cairo_translate(sad_face, (double)icon_size/2, (double)icon_size/2); + cairo_scale(sad_face, (double)icon_size/2, (double)icon_size/2); + cairo_arc(sad_face, 0, 0, 1, 0, 7); + cairo_fill(sad_face); + cairo_set_operator(sad_face, CAIRO_OPERATOR_CLEAR); + cairo_arc(sad_face, 0.35, -0.3, 0.1, 0, 7); + cairo_fill(sad_face); + cairo_arc(sad_face, -0.35, -0.3, 0.1, 0, 7); + cairo_fill(sad_face); + cairo_arc(sad_face, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469); + cairo_set_line_width(sad_face, 0.1); + cairo_stroke(sad_face); + cairo_destroy(sad_face); + item->icon = item_icon; + } + + cairo_operator_t op = cairo_get_operator(painter); + cairo_set_operator(painter, CAIRO_OPERATOR_OVER); + + int actual_size = cairo_image_surface_get_height(item->icon); + icon_size = actual_size < icon_size ? + actual_size * (icon_size / actual_size) : icon_size; + cairo_surface_t *image = scale_icon(item->icon, icon_size, icon_size); + cairo_set_source_surface(painter, image, *x+1, item->height - icon_size - 1); + cairo_paint(painter); + + cairo_set_operator(painter, op); + *x += item->width; +} + +cairo_surface_t *load_image(const char *path) { + cairo_surface_t *image = cairo_image_surface_create_from_png(path); + return (!image || cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) ? NULL : image; +} + +cairo_surface_t *load_icon(struct Item *item, const char *theme, int size) { + cairo_surface_t *icon = NULL; + char *name = item->status[0] == 'N' ? item->attention_icon_name : item->icon_name; + if (name) { + struct List *search_paths = list_create(item->tray->basedirs->length+1); + list_copy(search_paths, item->tray->basedirs); + if (item->icon_theme_path) + list_add(search_paths, item->icon_theme_path); + + char *path = get_icon(item->tray->themes, search_paths, name, size, theme); + + list_destroy(search_paths); + + if (path) { + icon = load_image(path); + free(path); + return icon; + } + } + + struct wl_list *pixmaps = item->status[0] == 'N' ? &item->attention_pixmap : &item->icon_pixmap; + if (!wl_list_empty(pixmaps)) { + struct Pixmap *pixmap = NULL, *pos; + int min_error = INT_MAX; + wl_list_for_each(pos, pixmaps, link) { + int e = abs(size - pos->size); + if (e < min_error) { + pixmap = pos; + min_error = e; + } + } + icon = cairo_image_surface_create_for_data((unsigned char*)pixmap->pixels, CAIRO_FORMAT_ARGB32, + pixmap->size, pixmap->size, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size)); + } + + return icon; +} + +// We assume that when we get the list that it is valid. +int read_pixmap(sd_bus_message *m, struct Item *item, const char *property, struct wl_list *destination) { + if (sd_bus_message_enter_container(m, 'a', "(iiay)") < 0) + panic("read_pixmap sd_bus_message_enter_container 'a'"); + + if (sd_bus_message_at_end(m, 0)) // No Icon + return 0; + + while(!sd_bus_message_at_end(m, 0)) { + if (sd_bus_message_enter_container(m, 'r', "iiay") < 0) + panic("read_pixmap sd_bus_message_enter_container 'r'"); + + int width, height; + if (sd_bus_message_read(m, "ii", &width, &height) < 0) + panic("read_pixmap sd_bus_message_read width height"); + + const void *pixels; + size_t npixels; + if (sd_bus_message_read_array(m, 'y', &pixels, &npixels) < 0) + panic("read_pixmap sd_bus_message_read_array pixels npixels"); + + if (height > 0 && width == height) { // If is valid icon + struct Pixmap *pixmap = ecalloc(1, sizeof(struct Pixmap)); + pixmap->pixels = ecalloc(npixels, sizeof(uint32_t*)); + pixmap->size = height; + + // Covert to host byte order from network order. + for (int i = 0; i < height * width; i++) + pixmap->pixels[i] = ntohl(((uint32_t*)pixels)[i]); + + wl_list_insert(destination, &pixmap->link); + } + + sd_bus_message_exit_container(m); + } + + if (wl_list_empty(destination)) + return 0; + + return 1; +} + +cairo_surface_t *scale_icon(cairo_surface_t *icon, int width, int height) { + int image_height = cairo_image_surface_get_height(icon); + int image_width = cairo_image_surface_get_width(icon); + + cairo_surface_t *new = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_t *painter = cairo_create(new); + cairo_scale(painter, (double)width / image_width, + (double)height / image_height); + cairo_set_source_surface(painter, icon, 0, 0); + + cairo_paint(painter); + cairo_destroy(painter); + return new; +} diff --git a/src/item.h b/src/item.h new file mode 100644 index 0000000..42373a9 --- /dev/null +++ b/src/item.h @@ -0,0 +1,52 @@ +#ifndef ITEM_H_ +#define ITEM_H_ + +#include +#include +#include +#include "cairo.h" +#include "tray.h" +#include "main.h" +#include "user.h" + +struct Pixmap { + int size; + uint32_t *pixels; + struct wl_list link; +}; + +struct ItemData { + struct Item *item; + const char *property, *type; + void *destination; + sd_bus_slot *slot; + + struct wl_list link; +}; + +struct ItemClick { + const struct Item *item; + const int x, y, button; +}; + +struct Item { + struct Tray *tray; + cairo_surface_t *icon; + int is_menu, invalid, x, y, width, height; + + char *watcher_id, *service, *path, *interface, + *status, *icon_name, *attention_icon_name, + *menu_path, *icon_theme_path /* Non-standard kde property */; + + struct wl_list icon_pixmap, attention_pixmap, item_data, link; +}; + +const char *button_to_method(int button); +int is_passive(struct Item *item); +int is_ready(struct Item *item); +struct Item *item_create(struct Tray* tray, char *id); +void item_destroy(struct Item* item); +int item_is_clicked(struct Item *item, double x, double y); +void item_render(struct Pipeline *pipeline, cairo_t *painter, struct Item *item, int *x, int *y); + +#endif // ITEM_H_ diff --git a/src/main.c b/src/main.c index 1acdd71..4a4cb1b 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include "event.h" #include "log.h" #include "render.h" +#include "tray.h" #include "util.h" #include "main.h" #include "input.h" @@ -29,6 +30,7 @@ static void check_global(void *global, const char *name); static void check_globals(void); static void cleanup(void); +static void dbus_in(int fd, short mask, void *data); static void display_in(int fd, short mask, void *data); static void fifo_handle(const char *line); static void fifo_in(int fd, short mask, void *data); @@ -80,6 +82,7 @@ static struct wl_list seats; // struct Seat* static int self_pipe[2]; struct zwlr_layer_shell_v1 *shell; struct wl_shm *shm; +static struct Tray *tray; static int tagcount; static const struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = xdg_wm_base_ping, @@ -138,6 +141,18 @@ void cleanup(void) { wl_display_disconnect(display); } +void dbus_in(int fd, short mask, void *data) { + if (mask & (POLLHUP | POLLERR)) { + running = 0; + return; + } + + int return_value; + while((return_value = sd_bus_process(tray->bus, NULL)) > 0); + if (return_value < 0) + panic("dbus_in: sd_bus_process failed to process bus."); +} + void display_in(int fd, short mask, void *data) { if (mask & (POLLHUP | POLLERR) || wl_display_dispatch(display) == -1) { @@ -249,6 +264,7 @@ void monitor_initialize(struct Monitor *monitor) { monitor->hotspots = list_create(1); monitor->pipeline = pipeline_create(); monitor->bar = bar_create(monitor->hotspots, monitor->pipeline); + tray_register_to_monitor(tray, monitor->hotspots, monitor->pipeline); if (!monitor->pipeline || !monitor->bar) panic("Failed to create a pipline or bar for a monitor"); monitor_update(monitor); @@ -392,6 +408,8 @@ void setup(void) { wl_list_init(&seats); wl_list_init(&monitors); + tray = tray_create(); + struct wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, NULL); wl_display_roundtrip(display); @@ -417,6 +435,7 @@ void setup(void) { events_add(events, display_fd, POLLIN, NULL, display_in); events_add(events, self_pipe[0], POLLIN, NULL, pipe_in); events_add(events, fifo_fd, POLLIN, NULL, fifo_in); + events_add(events, tray->bus_fd, POLLIN, NULL, dbus_in); } void sigaction_handler(int _) { diff --git a/src/tray.c b/src/tray.c new file mode 100644 index 0000000..cac2867 --- /dev/null +++ b/src/tray.c @@ -0,0 +1,169 @@ +#include "log.h" +#include "main.h" +#include "icon.h" +#include "item.h" +#include "render.h" +#include "tray.h" +#include "user.h" +#include "watcher.h" +#include "host.h" +#include "util.h" +#include "input.h" +#include "config.h" +#include +#include +#include +#include +#include +#include + +// Thanks to the ianyfan from swaybar for the tray code, +// I don't have a clue of how to use sd-bus, so that code was useful. + +static int handle_lost_watcher(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static void tray_click(struct Monitor *monitor, void *data, uint32_t button, double x, double y); +static void tray_render(struct Pipeline *pipeline, void *data, cairo_t *painter, int *x, int *y); +static int tray_width(struct Pipeline *pipeline, void *data, unsigned int future_widths); +static void tray_bounds(void *data, double *x, double *y, double *width, double *height); + +static const struct PipelineListener tray_pipeline_listener = { .render = tray_render, .width = tray_width, }; +static const struct HotspotListener tray_hotspot_listener = { .click = tray_click, .bounds = tray_bounds }; + +int cmp_id(const void *left, const void *right) { + if (!left || !right) + return -1; + + return strcmp(left, right); +} + +int handle_lost_watcher(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + char *service, *old_owner, *new_owner; + if (sd_bus_message_read(m, "sss", &service, &old_owner, &new_owner) < 0) + panic("handle_lost_watcher sd_bus_message_read"); + + if (!*new_owner) { + struct Tray *tray = userdata; + + if (STRING_EQUAL(service, "org.freedesktop.StatusNotifierWatcher")) { + tray->xdg_watcher = watcher_create("freedesktop", tray->bus); + } else if (STRING_EQUAL(service, "org.kde.StatusNotifierWatcher")) { + tray->kde_watcher = watcher_create("kde", tray->bus); + } + } + + return 0; +} + +struct Tray *tray_create(void) { + struct Tray *tray; + sd_bus *bus; + + if (sd_bus_open_user(&bus) < 0) + panic("sd_bus_open_user"); + + tray = ecalloc(1, sizeof(*tray)); + tray->bus = bus; + tray->bus_fd = sd_bus_get_fd(tray->bus); + tray->themes = list_create(0); + tray->basedirs = list_create(0); + wl_list_init(&tray->items); + + tray->xdg_watcher = watcher_create("freedesktop", bus); + tray->kde_watcher = watcher_create("kde", bus); + + if (sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", + "NameOwnerChanged", handle_lost_watcher, tray) < 0) + panic("Failed to subscribe to NameOwnerChange"); + + tray->xdg_host = host_create("freedesktop", tray); + tray->kde_host = host_create("kde", tray); + + themes_create(tray->themes, tray->basedirs); + + return tray; +} + +void tray_destroy(struct Tray *tray) { + if (!tray) return; + + host_destroy(tray->xdg_host); + host_destroy(tray->kde_host); + + watcher_destroy(tray->kde_watcher); + watcher_destroy(tray->xdg_watcher); + + struct Item *item, *tmp; + wl_list_for_each_safe(item, tmp, &tray->items, link) { + wl_list_remove(&item->link); + item_destroy(item); + } + + themes_destroy(tray->themes, tray->basedirs); + free(tray); +} + +void tray_click(struct Monitor *monitor, void *data, uint32_t button, double x, double y) { + struct Tray *tray = data; + struct Item *item = NULL; + wl_list_for_each(item, &tray->items, link) { + if (item_is_clicked(item, x, y)) + break; + } + + if (!item) + return; + + union Arg arg; + struct ItemClick click = { item, x, y, button }; + arg.v = &click; + + const struct Binding *binding; + for (int i = 0; i < LENGTH(bindings); i++) { + binding = &bindings[i]; + if (binding->button != button || binding->clicked != Click_Systray) + continue; + + binding->callback(monitor, binding->bypass ? &binding->arg : &arg); + } +} + +void tray_register_to_monitor(struct Tray *tray, struct List *hotspots, struct Pipeline *pipeline) { + if (!tray || !hotspots || !pipeline) return; + + tray->pipeline = pipeline; + pipeline_add(pipeline, &tray_pipeline_listener, tray); + struct Hotspot *hotspot = list_add(hotspots, ecalloc(1, sizeof(*hotspot))); + hotspot->listener = &tray_hotspot_listener; + hotspot->data = tray; +} + +void tray_render(struct Pipeline *pipeline, void *data, cairo_t *painter, int *x, int *y) { + struct Tray *tray = data; + tray->x = *x; + tray->y = *y; + struct Item *item; + wl_list_for_each(item, &tray->items, link) + item_render(pipeline, painter, item, x, y); +} + +int tray_width(struct Pipeline *pipeline, void *data, unsigned int future_widths) { + struct Tray *tray = data; + + int amount = 0; + struct Item *item; + wl_list_for_each(item, &tray->items, link) { + if (!is_passive(item) && is_ready(item)) + amount++; + } + + return (pipeline->font->height + 2) * amount; +} + +static void tray_bounds(void *data, double *x, double *y, double *width, double *height) { + struct Tray *tray = data; + *x = tray->x; + *y = tray->y; + *width = tray_width(tray->pipeline, tray, 0); + *height = tray->pipeline->shm->height; +} diff --git a/src/tray.h b/src/tray.h new file mode 100644 index 0000000..9f52bea --- /dev/null +++ b/src/tray.h @@ -0,0 +1,41 @@ +#ifndef TRAY_H_ +#define TRAY_H_ + +#include +#include + +#include "render.h" +#include "lib.h" +#if SYSTEMD +#include +#elif ELOGIND +#include +#elif BASU +#include +#else +#error "No dbus library to use" +#endif + +static const char *watcher_path = "/StatusNotifierWatcher"; + +struct Tray { + sd_bus *bus; + int bus_fd, x, y; + + struct Pipeline *pipeline; + + struct Watcher *xdg_watcher, *kde_watcher; + struct Host *xdg_host, *kde_host; + + struct wl_list items; // struct Item* + + struct List *themes /* struct Theme* */, + *basedirs /* char* */; +}; + +int cmp_id(const void *left, const void *right); +struct Tray *tray_create(void); +void tray_destroy(struct Tray *tray); +void tray_register_to_monitor(struct Tray *tray, struct List *hotspots, struct Pipeline *pipeline); + +#endif // TRAY_H_ diff --git a/src/user.c b/src/user.c index 14bdc73..562527c 100644 --- a/src/user.c +++ b/src/user.c @@ -1,5 +1,7 @@ #include "user.h" #include "util.h" +#include "item.h" +#include "log.h" #include #include #include @@ -39,3 +41,23 @@ void toggle_view(struct Monitor *monitor, const union Arg *arg) { void view(struct Monitor *monitor, const union Arg *arg) { zdwl_ipc_output_v2_set_tags(monitor->dwl_output, 1<ui, 1); } + +void systray(struct Monitor *monitor, const union Arg *arg) { + const struct ItemClick *click = arg->v; + const struct Item *item = click->item; + const char *method = button_to_method(click->button); + + if (STRING_EQUAL(method, "nop")) + return; + if (STRINGN_EQUAL(method, "Scroll", strlen("Scroll"))) { + char direction = method[strlen("Scroll")]; + char *orientation = (direction == 'U' || direction == 'D') ? "vertical" : "horizontal"; + int sign = (direction == 'U' || direction == 'L') ? -1 : 1; + + sd_bus_call_method_async(item->tray->bus, NULL, item->service, item->path, + item->interface, "Scroll", NULL, NULL, "is", sign, orientation); + } else { + sd_bus_call_method_async(item->tray->bus, NULL, item->service, item->path, + item->interface, method, NULL, NULL, "ii", click->x, click->y); + } +} diff --git a/src/user.h b/src/user.h index cc42751..1bb3fea 100644 --- a/src/user.h +++ b/src/user.h @@ -11,6 +11,7 @@ enum Clicked { Click_Layout, Click_Title, Click_Status, + Click_Systray, }; enum ColorScheme { @@ -52,5 +53,6 @@ void spawn(struct Monitor *monitor, const union Arg *arg); void tag(struct Monitor *monitor, const union Arg *arg); void toggle_view(struct Monitor *monitor, const union Arg *arg); void view(struct Monitor *monitor, const union Arg *arg); +void systray(struct Monitor *monitor, const union Arg *arg); #endif // USER_H_ diff --git a/src/watcher.c b/src/watcher.c new file mode 100644 index 0000000..0c3bfa6 --- /dev/null +++ b/src/watcher.c @@ -0,0 +1,181 @@ +#include +#include +#include +#include + +#include "log.h" +#include "tray.h" +#include "watcher.h" +#include "util.h" +#include "main.h" + +static int get_registered_items(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); +static int handle_lost_service(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int is_registered_host(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); +static int register_host(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int register_item(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +static int watcher_using_freedesktop(struct Watcher *watcher); + +static const sd_bus_vtable watcher_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_item, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_items, + 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_registered_host, + 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("ProtocolVersion", "i", NULL, offsetof(struct Watcher, version), + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0), + SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0), + SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0), + SD_BUS_VTABLE_END, +}; + +int get_registered_items(sd_bus *bus, const char *path, const char *interface, + const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { + struct Watcher *watcher = userdata; + list_add(watcher->items, NULL); // Must be NULL terminated array. + int ret = sd_bus_message_append_strv(reply, (char**)watcher->items->data); + list_remove(watcher->items, watcher->items->length - 1); + return ret; +} + +int handle_lost_service(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + char *service, *old_owner, *new_owner; + if (sd_bus_message_read(m, "sss", &service, &new_owner, &new_owner) < 0) + panic("handle_lost_service sd_bus_message_read"); + + struct Watcher *watcher = userdata; + + if (*new_owner) + return 0; + + int i; + char *id; + for (i = 0; i < watcher->items->length; i++) { // Check to see if item. + id = watcher->items->data[i]; + + int same_service = (watcher_using_freedesktop(watcher) ? strcmp(id, service) : + strncmp(id, service, strlen(service))) == 0; + if (!same_service) + continue; + + list_remove(watcher->items, i--); + sd_bus_emit_signal(watcher->bus, watcher_path, watcher->interface, + "StatusNotifierItemUnregistered", "s", id); + free(id); + if (watcher_using_freedesktop(watcher)) + break; + } + + if ((i = list_cmp_find(watcher->hosts, service, cmp_id)) != -1) // If not item then host. + free(list_remove(watcher->hosts, i)); + + return 0; +} + +int is_registered_host(sd_bus *bus, const char *path, const char *interface, + const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { + struct Watcher *watcher = userdata; + int has = watcher->hosts->length > 0; + return sd_bus_message_append_basic(reply, 'b', &has); +} + +int register_host(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + char *service; + if (sd_bus_message_read(m, "s", &service) < 0) + panic("register_host sd_bus_message_read"); + + struct Watcher *watcher = userdata; + if (list_cmp_find(watcher->hosts, service, cmp_id) == -1) { + list_add(watcher->hosts, strdup(service)); + sd_bus_emit_signal(watcher->bus, watcher_path, watcher->interface, + "StatusNotifierHostRegistered", NULL); + } + + return sd_bus_reply_method_return(m, ""); +} + + +int register_item(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + char *service_path, *id; + if (sd_bus_message_read(m, "s", &service_path) < 0) + panic("register_item sd_bus_message_read"); + + struct Watcher *watcher = userdata; + if (watcher_using_freedesktop(watcher)) { + id = strdup(service_path); + } else { + const char *service, *path; + if (service_path[0] == '/') { + service = sd_bus_message_get_sender(m); + path = service_path; + } else { + service = service_path; + path = "/StatusNotifierItem"; + } + id = string_create("%s%s", service, path); + } + + if (list_cmp_find(watcher->items, id, cmp_id) != -1) { + free(id); + } else { + list_add(watcher->items, id); + sd_bus_emit_signal(watcher->bus, watcher_path, watcher->interface, + "StatusNotifierItemRegistered", "s", id); + } + + return sd_bus_reply_method_return(m, ""); +} + +struct Watcher *watcher_create(char *protocol, sd_bus *bus) { + if (!protocol || !bus) + return NULL; + + struct Watcher *watcher = ecalloc(1, sizeof(*watcher)); + sd_bus_slot *signal = NULL, *vtable = NULL; + watcher->interface = string_create("org.%s.StatusNotifierWatcher", protocol); + + if (sd_bus_add_object_vtable(bus, &vtable, watcher_path, + watcher->interface, watcher_vtable, watcher) < 0) + goto error; + + if (sd_bus_match_signal(bus, &signal, "org.freedesktop.DBus", "/org/freedesktop/DBus", + "org.freedesktop.DBus", "NameOwnerChanged", handle_lost_service, watcher) < 0) + goto error; + + if (sd_bus_request_name(bus, watcher->interface, 0) < 0) + goto error; + + sd_bus_slot_set_floating(signal, 0); + sd_bus_slot_set_floating(vtable, 0); + + watcher->bus = bus; + watcher->hosts = list_create(0); + watcher->items = list_create(0); + + return watcher; + +error: + sd_bus_slot_unref(vtable); + sd_bus_slot_unref(signal); + watcher_destroy(watcher); + panic("Creating %s watcher failed dying.", protocol); + return NULL; +} + +void watcher_destroy(struct Watcher *watcher) { + if (!watcher) return; + + list_elements_destroy(watcher->hosts, free); + list_elements_destroy(watcher->items, free); + free(watcher->interface); + free(watcher); +} + +int watcher_using_freedesktop(struct Watcher *watcher) { + return watcher->interface[strlen("org.")] == 'f'; +} diff --git a/src/watcher.h b/src/watcher.h new file mode 100644 index 0000000..41df40c --- /dev/null +++ b/src/watcher.h @@ -0,0 +1,18 @@ +#ifndef WATCHER_H_ +#define WATCHER_H_ + +#include "tray.h" + +// In case there isn't already a watcher available. +struct Watcher { + char *interface; + sd_bus *bus; + int version; + + struct List *hosts, *items; /* char* */ +}; + +struct Watcher *watcher_create(char *protocol, sd_bus *bus); +void watcher_destroy(struct Watcher *watcher); + +#endif // WATCHER_H_