aboutsummaryrefslogtreecommitdiff
path: root/swaybar/tray/item.c
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2018-12-31 15:43:23 -0500
committerGitHub <noreply@github.com>2018-12-31 15:43:23 -0500
commit3d7c20f062bebe39199d3872e5b64d5e8d61d100 (patch)
tree93b8ddb51873cae14db8d5f07db74ca96236f184 /swaybar/tray/item.c
parent4a3ada303b948ce2bdf21c7e6254005c85b000ea (diff)
parent9e31f5d79ce9de8f1c73013b792aa94f355a1983 (diff)
Merge pull request #3249 from ianyfan/tray
Swaybar tray
Diffstat (limited to 'swaybar/tray/item.c')
-rw-r--r--swaybar/tray/item.c443
1 files changed, 443 insertions, 0 deletions
diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
new file mode 100644
index 00000000..41cacd16
--- /dev/null
+++ b/swaybar/tray/item.c
@@ -0,0 +1,443 @@
+#define _POSIX_C_SOURCE 200809L
+#include <cairo.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include "swaybar/bar.h"
+#include "swaybar/config.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 "background-image.h"
+#include "cairo.h"
+#include "list.h"
+#include "log.h"
+#include "wlr-layer-shell-unstable-v1-client-protocol.h"
+
+// TODO menu
+
+static bool sni_ready(struct swaybar_sni *sni) {
+ return sni->status && (sni->status[0] == 'N' ?
+ sni->attention_icon_name || sni->attention_icon_pixmap :
+ sni->icon_name || sni->icon_pixmap);
+}
+
+static void set_sni_dirty(struct swaybar_sni *sni) {
+ if (sni_ready(sni)) {
+ sni->min_size = sni->max_size = 0; // invalidate previous icon
+ set_bar_dirty(sni->tray->bar);
+ }
+}
+
+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);
+ }
+ }
+
+ if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ?
+ prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) {
+ set_sni_dirty(sni);
+ }
+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);
+ set_sni_dirty(sni);
+ }
+ 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";
+ sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path);
+ }
+
+ // 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);
+}
+
+static void handle_click(struct swaybar_sni *sni, int x, int y,
+ enum x11_button button, int delta) {
+ const char *method = sni->tray->bar->config->tray_bindings[button];
+ if (!method) {
+ static const char *default_bindings[10] = {
+ "nop",
+ "Activate",
+ "SecondaryActivate",
+ "ContextMenu",
+ "ScrollUp",
+ "ScrollDown",
+ "ScrollLeft",
+ "ScrollRight",
+ "nop",
+ "nop"
+ };
+ method = default_bindings[button];
+ }
+ if (strcmp(method, "nop") == 0) {
+ return;
+ }
+ if (sni->item_is_menu && strcmp(method, "Activate") == 0) {
+ method = "ContextMenu";
+ }
+
+ if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
+ char dir = method[strlen("Scroll")];
+ char *orientation = (dir = 'U' || dir == 'D') ? "vertical" : "horizontal";
+ int sign = (dir == 'U' || dir == 'L') ? -1 : 1;
+
+ int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
+ sni->path, sni->interface, "Scroll", NULL, NULL, "is",
+ delta*sign, orientation);
+ if (ret < 0) {
+ wlr_log(WLR_DEBUG, "Failed to scroll on SNI: %s", strerror(-ret));
+ }
+ } else {
+ int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
+ sni->path, sni->interface, method, NULL, NULL, "ii", x, y);
+ if (ret < 0) {
+ wlr_log(WLR_DEBUG, "Failed to click on SNI: %s", strerror(-ret));
+ }
+ }
+}
+
+static int cmp_sni_id(const void *item, const void *cmp_to) {
+ const struct swaybar_sni *sni = item;
+ return strcmp(sni->watcher_id, cmp_to);
+}
+
+static enum hotspot_event_handling icon_hotspot_callback(
+ struct swaybar_output *output, struct swaybar_hotspot *hotspot,
+ int x, int y, enum x11_button button, void *data) {
+ wlr_log(WLR_DEBUG, "Clicked on Status Notifier Item '%s'", (char *)data);
+
+ struct swaybar_tray *tray = output->bar->tray;
+ int idx = list_seq_find(tray->items, cmp_sni_id, data);
+
+ if (idx != -1) {
+ struct swaybar_sni *sni = tray->items->items[idx];
+ // guess global position since wayland doesn't expose it
+ struct swaybar_config *config = tray->bar->config;
+ int global_x = output->output_x + config->gaps.left + x;
+ bool top_bar = config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
+ int global_y = output->output_y + (top_bar ? config->gaps.top + y:
+ (int) output->output_height - config->gaps.bottom - y);
+
+ wlr_log(WLR_DEBUG, "Guessing click at (%d, %d)", global_x, global_y);
+ handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event
+ return HOTSPOT_IGNORE;
+ } else {
+ wlr_log(WLR_DEBUG, "but it doesn't exist");
+ }
+
+ return HOTSPOT_PROCESS;
+}
+
+uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
+ struct swaybar_sni *sni) {
+ uint32_t height = output->height * output->scale;
+ int padding = output->bar->config->tray_padding;
+ int ideal_size = height - 2*padding;
+ if ((ideal_size < sni->min_size || ideal_size > sni->max_size) && sni_ready(sni)) {
+ bool icon_found = false;
+ char *icon_name = sni->status[0] == 'N' ?
+ sni->attention_icon_name : sni->icon_name;
+ if (icon_name) {
+ char *icon_path = find_icon(sni->tray->themes, sni->tray->basedirs,
+ icon_name, ideal_size, output->bar->config->icon_theme,
+ &sni->min_size, &sni->max_size);
+ if (!icon_path && sni->icon_theme_path) {
+ icon_path = find_icon_in_dir(icon_name, sni->icon_theme_path,
+ &sni->min_size, &sni->max_size);
+ }
+ if (icon_path) {
+ cairo_surface_destroy(sni->icon);
+ sni->icon = load_background_image(icon_path);
+ free(icon_path);
+ icon_found = true;
+ }
+ }
+ if (!icon_found) {
+ list_t *pixmaps = sni->status[0] == 'N' ?
+ sni->attention_icon_pixmap : sni->icon_pixmap;
+ if (pixmaps) {
+ int idx = -1;
+ unsigned smallest_error = -1; // UINT_MAX
+ for (int i = 0; i < pixmaps->length; ++i) {
+ struct swaybar_pixmap *pixmap = pixmaps->items[i];
+ unsigned error = (ideal_size - pixmap->size) *
+ (ideal_size < pixmap->size ? -1 : 1);
+ if (error < smallest_error) {
+ smallest_error = error;
+ idx = i;
+ }
+ }
+ struct swaybar_pixmap *pixmap = pixmaps->items[idx];
+ cairo_surface_destroy(sni->icon);
+ sni->icon = cairo_image_surface_create_for_data(pixmap->pixels,
+ CAIRO_FORMAT_ARGB32, pixmap->size, pixmap->size,
+ cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size));
+ }
+ }
+ }
+
+ int icon_size;
+ cairo_surface_t *icon;
+ if (sni->icon) {
+ int actual_size = cairo_image_surface_get_height(sni->icon);
+ icon_size = actual_size < ideal_size ?
+ actual_size*(ideal_size/actual_size) : ideal_size;
+ icon = cairo_image_surface_scale(sni->icon, icon_size, icon_size);
+ } else { // draw a sad face
+ icon_size = ideal_size*0.8;
+ icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, icon_size, icon_size);
+ cairo_t *cairo_icon = cairo_create(icon);
+ cairo_set_source_u32(cairo_icon, 0xFF0000FF);
+ cairo_translate(cairo_icon, icon_size/2, icon_size/2);
+ cairo_scale(cairo_icon, icon_size/2, icon_size/2);
+ cairo_arc(cairo_icon, 0, 0, 1, 0, 7);
+ cairo_fill(cairo_icon);
+ cairo_set_operator(cairo_icon, CAIRO_OPERATOR_CLEAR);
+ cairo_arc(cairo_icon, 0.35, -0.3, 0.1, 0, 7);
+ cairo_fill(cairo_icon);
+ cairo_arc(cairo_icon, -0.35, -0.3, 0.1, 0, 7);
+ cairo_fill(cairo_icon);
+ cairo_arc(cairo_icon, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469);
+ cairo_set_line_width(cairo_icon, 0.1);
+ cairo_stroke(cairo_icon);
+ cairo_destroy(cairo_icon);
+ }
+
+ int padded_size = icon_size + 2*padding;
+ *x -= padded_size;
+ int y = floor((height - padded_size) / 2.0);
+
+ cairo_operator_t op = cairo_get_operator(cairo);
+ cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
+ cairo_set_source_surface(cairo, icon, *x + padding, y + padding);
+ cairo_rectangle(cairo, *x, y, padded_size, padded_size);
+ cairo_fill(cairo);
+ cairo_set_operator(cairo, op);
+
+ cairo_surface_destroy(icon);
+
+ struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
+ hotspot->x = *x;
+ hotspot->y = 0;
+ hotspot->width = height;
+ hotspot->height = height;
+ hotspot->callback = icon_hotspot_callback;
+ hotspot->destroy = free;
+ hotspot->data = strdup(sni->watcher_id);
+ wl_list_insert(&output->hotspots, &hotspot->link);
+
+ return output->height;
+}