diff --git a/include/swaybar/tray/item.h b/include/swaybar/tray/item.h new file mode 100644 index 00000000..57affb78 --- /dev/null +++ b/include/swaybar/tray/item.h @@ -0,0 +1,38 @@ +#ifndef _SWAYBAR_TRAY_ITEM_H +#define _SWAYBAR_TRAY_ITEM_H + +#include +#include "swaybar/tray/tray.h" +#include "list.h" + +struct swaybar_pixmap { + int size; + unsigned char pixels[]; +}; + +struct swaybar_sni { + // icon properties + struct swaybar_tray *tray; + cairo_surface_t *icon; + int min_size; + int max_size; + + // dbus properties + char *watcher_id; + char *service; + char *path; + char *interface; + + char *status; + char *icon_name; + list_t *icon_pixmap; // struct swaybar_pixmap * + char *attention_icon_name; + list_t *attention_icon_pixmap; // struct swaybar_pixmap * + bool item_is_menu; + char *menu; +}; + +struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray); +void destroy_sni(struct swaybar_sni *sni); + +#endif diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h index 1d976b4a..8958b69a 100644 --- a/include/swaybar/tray/tray.h +++ b/include/swaybar/tray/tray.h @@ -24,7 +24,7 @@ struct swaybar_tray { struct swaybar_host host_xdg; struct swaybar_host host_kde; - list_t *items; // char * + list_t *items; // struct swaybar_sni * struct swaybar_watcher *watcher_xdg; struct swaybar_watcher *watcher_kde; diff --git a/swaybar/meson.build b/swaybar/meson.build index 85783a0f..312ca97b 100644 --- a/swaybar/meson.build +++ b/swaybar/meson.build @@ -1,6 +1,7 @@ tray_files = get_option('enable-tray') ? [ 'tray/host.c', 'tray/icon.c', + 'tray/item.c', 'tray/tray.c', 'tray/watcher.c' ] : [] diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c index 3cc90254..8ab896d4 100644 --- a/swaybar/tray/host.c +++ b/swaybar/tray/host.c @@ -5,6 +5,7 @@ #include #include #include "swaybar/tray/host.h" +#include "swaybar/tray/item.h" #include "swaybar/tray/tray.h" #include "list.h" #include "log.h" @@ -12,15 +13,15 @@ static const char *watcher_path = "/StatusNotifierWatcher"; static int cmp_sni_id(const void *item, const void *cmp_to) { - const char *sni = item; - return strcmp(sni, cmp_to); + const struct swaybar_sni *sni = item; + return strcmp(sni->watcher_id, cmp_to); } static void add_sni(struct swaybar_tray *tray, char *id) { int idx = list_seq_find(tray->items, cmp_sni_id, id); if (idx == -1) { wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id); - char *sni = strdup(id); + struct swaybar_sni *sni = create_sni(id, tray); if (sni) { list_add(tray->items, sni); } @@ -53,7 +54,7 @@ static int handle_sni_unregistered(sd_bus_message *msg, void *data, int idx = list_seq_find(tray->items, cmp_sni_id, id); if (idx != -1) { wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id); - free(tray->items->items[idx]); + destroy_sni(tray->items->items[idx]); list_del(tray->items, idx); } return ret; diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c new file mode 100644 index 00000000..561a3425 --- /dev/null +++ b/swaybar/tray/item.c @@ -0,0 +1,236 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include "swaybar/tray/host.h" +#include "swaybar/tray/item.h" +#include "swaybar/tray/tray.h" +#include "list.h" +#include "log.h" + +// TODO menu + +static int read_pixmap(sd_bus_message *msg, struct swaybar_sni *sni, + const char *prop, list_t **dest) { + int ret = sd_bus_message_enter_container(msg, 'a', "(iiay)"); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret)); + return ret; + } + + if (sd_bus_message_at_end(msg, 0)) { + return ret; + } + + list_t *pixmaps = create_list(); + if (!pixmaps) { + return -12; // -ENOMEM + } + + while (!sd_bus_message_at_end(msg, 0)) { + ret = sd_bus_message_enter_container(msg, 'r', "iiay"); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret)); + goto error; + } + + int size; + ret = sd_bus_message_read(msg, "ii", NULL, &size); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret)); + goto error; + } + + const void *pixels; + size_t npixels; + ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret)); + goto error; + } + + struct swaybar_pixmap *pixmap = + malloc(sizeof(struct swaybar_pixmap) + npixels); + pixmap->size = size; + memcpy(pixmap->pixels, pixels, npixels); + list_add(pixmaps, pixmap); + + sd_bus_message_exit_container(msg); + } + *dest = pixmaps; + + return ret; +error: + list_free_items_and_destroy(pixmaps); + return ret; +} + +struct get_property_data { + struct swaybar_sni *sni; + const char *prop; + const char *type; + void *dest; +}; + +static int get_property_callback(sd_bus_message *msg, void *data, + sd_bus_error *error) { + struct get_property_data *d = data; + struct swaybar_sni *sni = d->sni; + const char *prop = d->prop; + const char *type = d->type; + void *dest = d->dest; + + int ret; + if (sd_bus_message_is_method_error(msg, NULL)) { + sd_bus_error err = *sd_bus_message_get_error(msg); + wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, err.message); + ret = -sd_bus_error_get_errno(&err); + goto cleanup; + } + + ret = sd_bus_message_enter_container(msg, 'v', type); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret)); + goto cleanup; + } + + if (!type) { + ret = read_pixmap(msg, sni, prop, dest); + if (ret < 0) { + goto cleanup; + } + } else { + ret = sd_bus_message_read(msg, type, dest); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, + strerror(-ret)); + goto cleanup; + } else if (*type == 's' || *type == 'o') { + char **str = dest; + *str = strdup(*str); + } + } +cleanup: + free(data); + return ret; +} + +static void sni_get_property_async(struct swaybar_sni *sni, const char *prop, + const char *type, void *dest) { + struct get_property_data *data = malloc(sizeof(struct get_property_data)); + data->sni = sni; + data->prop = prop; + data->type = type; + data->dest = dest; + int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, + sni->path, "org.freedesktop.DBus.Properties", "Get", + get_property_callback, data, "ss", sni->interface, prop); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, strerror(-ret)); + } +} + +static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) { + struct swaybar_sni *sni = data; + wlr_log(WLR_DEBUG, "%s has new IconName", sni->watcher_id); + + free(sni->icon_name); + sni->icon_name = NULL; + sni_get_property_async(sni, "IconName", "s", &sni->icon_name); + + list_free_items_and_destroy(sni->icon_pixmap); + sni->icon_pixmap = NULL; + sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap); + + return 0; +} + +static int handle_new_attention_icon(sd_bus_message *msg, void *data, + sd_bus_error *error) { + struct swaybar_sni *sni = data; + wlr_log(WLR_DEBUG, "%s has new AttentionIconName", sni->watcher_id); + + free(sni->attention_icon_name); + sni->attention_icon_name = NULL; + sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name); + + list_free_items_and_destroy(sni->attention_icon_pixmap); + sni->attention_icon_pixmap = NULL; + sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap); + + return 0; +} + +static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) { + char *status; + int ret = sd_bus_message_read(msg, "s", &status); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read new status message: %s", strerror(-ret)); + } else { + struct swaybar_sni *sni = data; + free(sni->status); + sni->status = strdup(status); + wlr_log(WLR_DEBUG, "%s has new Status '%s'", sni->watcher_id, status); + } + return ret; +} + +static void sni_match_signal(struct swaybar_sni *sni, char *signal, + sd_bus_message_handler_t callback) { + int ret = sd_bus_match_signal(sni->tray->bus, NULL, sni->service, sni->path, + sni->interface, signal, callback, sni); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to subscribe to signal %s: %s", signal, + strerror(-ret)); + } +} + +struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray) { + struct swaybar_sni *sni = calloc(1, sizeof(struct swaybar_sni)); + if (!sni) { + return NULL; + } + sni->tray = tray; + sni->watcher_id = strdup(id); + char *path_ptr = strchr(id, '/'); + if (!path_ptr) { + sni->service = strdup(id); + sni->path = strdup("/StatusNotifierItem"); + sni->interface = "org.freedesktop.StatusNotifierItem"; + } else { + sni->service = strndup(id, path_ptr - id); + sni->path = strdup(path_ptr); + sni->interface = "org.kde.StatusNotifierItem"; + } + + // Ignored: Category, Id, Title, WindowId, OverlayIconName, + // OverlayIconPixmap, AttentionMovieName, ToolTip + sni_get_property_async(sni, "Status", "s", &sni->status); + sni_get_property_async(sni, "IconName", "s", &sni->icon_name); + sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap); + sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name); + sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap); + sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu); + sni_get_property_async(sni, "Menu", "o", &sni->menu); + + sni_match_signal(sni, "NewIcon", handle_new_icon); + sni_match_signal(sni, "NewAttentionIcon", handle_new_attention_icon); + sni_match_signal(sni, "NewStatus", handle_new_status); + + return sni; +} + +void destroy_sni(struct swaybar_sni *sni) { + if (!sni) { + return; + } + + free(sni->watcher_id); + free(sni->service); + free(sni->path); + free(sni->status); + free(sni->icon_name); + free(sni->icon_pixmap); + free(sni->attention_icon_name); + free(sni->menu); + free(sni); +} diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c index e760812c..4ef28a78 100644 --- a/swaybar/tray/tray.c +++ b/swaybar/tray/tray.c @@ -5,6 +5,7 @@ #include "swaybar/bar.h" #include "swaybar/tray/icon.h" #include "swaybar/tray/host.h" +#include "swaybar/tray/item.h" #include "swaybar/tray/tray.h" #include "swaybar/tray/watcher.h" #include "list.h" @@ -48,7 +49,7 @@ void destroy_tray(struct swaybar_tray *tray) { finish_host(&tray->host_xdg); finish_host(&tray->host_kde); for (int i = 0; i < tray->items->length; ++i) { - free(tray->items->items[0]); + destroy_sni(tray->items->items[0]); } list_free(tray->items); destroy_watcher(tray->watcher_xdg);