You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
6.5 KiB

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#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);
}