From 843ad38b3c427adb0bf319e9613d9813c8d9246c Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Wed, 7 Jun 2017 16:45:28 -0700 Subject: Implement Tray Icons This commit implements the StatusNotifierItem protocol, and enables swaybar to show tray icons. It also uses `xembedsniproxy` in order to communicate with xembed applications. The tray is completely optional, and can be disabled on compile time with the `enable-tray` option. Or on runtime with the bar config option `tray_output none`. Overview of changes: In swaybar very little is changed outside the tray subfolder except that all events are now polled in `event_loop.c`, this creates no functional difference. Six bar configuration options were added, these are detailed in sway-bar(5) The tray subfolder is where all protocol implementation takes place and is organised as follows: tray/sni_watcher.c: This file contains the StatusNotifierWatcher. It keeps track of items and hosts and reports when they come or go. tray/tray.c This file contains the StatusNotifierHost. It keeps track of sway's version of the items and represents the tray itself. tray/sni.c This file contains the StatusNotifierItem struct and all communication with individual items. tray/icon.c This file implements the icon theme protocol. It allows for finding icons by name, rather than by pixmap. tray/dbus.c This file allows for asynchronous DBus communication. See #986 #343 --- swaybar/tray/dbus.c | 189 ++++++++++++++++++ swaybar/tray/icon.c | 404 +++++++++++++++++++++++++++++++++++++ swaybar/tray/sni.c | 463 ++++++++++++++++++++++++++++++++++++++++++ swaybar/tray/sni_watcher.c | 487 +++++++++++++++++++++++++++++++++++++++++++++ swaybar/tray/tray.c | 279 ++++++++++++++++++++++++++ 5 files changed, 1822 insertions(+) create mode 100644 swaybar/tray/dbus.c create mode 100644 swaybar/tray/icon.c create mode 100644 swaybar/tray/sni.c create mode 100644 swaybar/tray/sni_watcher.c create mode 100644 swaybar/tray/tray.c (limited to 'swaybar/tray') diff --git a/swaybar/tray/dbus.c b/swaybar/tray/dbus.c new file mode 100644 index 00000000..333d398e --- /dev/null +++ b/swaybar/tray/dbus.c @@ -0,0 +1,189 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include +#include +#include +#include "swaybar/tray/dbus.h" +#include "swaybar/event_loop.h" +#include "log.h" + +DBusConnection *conn = NULL; + +static void dispatch_watch(int fd, short mask, void *data) { + sway_log(L_DEBUG, "Dispatching watch"); + DBusWatch *watch = data; + + if (!dbus_watch_get_enabled(watch)) { + return; + } + + uint32_t flags = 0; + + if (mask & POLLIN) { + flags |= DBUS_WATCH_READABLE; + } if (mask & POLLOUT) { + flags |= DBUS_WATCH_WRITABLE; + } if (mask & POLLHUP) { + flags |= DBUS_WATCH_HANGUP; + } if (mask & POLLERR) { + flags |= DBUS_WATCH_ERROR; + } + + dbus_watch_handle(watch, flags); +} + +static dbus_bool_t add_watch(DBusWatch *watch, void *_data) { + if (!dbus_watch_get_enabled(watch)) { + // Watch should not be polled + return TRUE; + } + + short mask = 0; + uint32_t flags = dbus_watch_get_flags(watch); + + if (flags & DBUS_WATCH_READABLE) { + mask |= POLLIN; + } if (flags & DBUS_WATCH_WRITABLE) { + mask |= POLLOUT; + } + + int fd = dbus_watch_get_unix_fd(watch); + + sway_log(L_DEBUG, "Adding DBus watch fd: %d", fd); + add_event(fd, mask, dispatch_watch, watch); + + return TRUE; +} + +static void remove_watch(DBusWatch *watch, void *_data) { + int fd = dbus_watch_get_unix_fd(watch); + + remove_event(fd); +} + +static void dispatch_timeout(timer_t timer, void *data) { + sway_log(L_DEBUG, "Dispatching DBus timeout"); + DBusTimeout *timeout = data; + + if (dbus_timeout_get_enabled(timeout)) { + dbus_timeout_handle(timeout); + } +} + +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *_data) { + if (!dbus_timeout_get_enabled(timeout)) { + return TRUE; + } + + timer_t *timer = malloc(sizeof(timer_t)); + if (!timer) { + sway_log(L_ERROR, "Cannot allocate memory"); + return FALSE; + } + struct sigevent ev = { + .sigev_notify = SIGEV_NONE, + }; + + if (timer_create(CLOCK_MONOTONIC, &ev, timer)) { + sway_log(L_ERROR, "Could not create DBus timer"); + return FALSE; + } + + int interval = dbus_timeout_get_interval(timeout); + int interval_sec = interval / 1000; + int interval_msec = (interval_sec * 1000) - interval; + + struct timespec period = { + (time_t) interval_sec, + ((long) interval_msec) * 1000 * 1000, + }; + struct itimerspec time = { + period, + period, + }; + + timer_settime(*timer, 0, &time, NULL); + + dbus_timeout_set_data(timeout, timer, free); + + sway_log(L_DEBUG, "Adding DBus timeout. Interval: %ds %dms", interval_sec, interval_msec); + add_timer(*timer, dispatch_timeout, timeout); + + return TRUE; +} +static void remove_timeout(DBusTimeout *timeout, void *_data) { + timer_t *timer = (timer_t *) dbus_timeout_get_data(timeout); + sway_log(L_DEBUG, "Removing DBus timeout."); + + if (timer) { + remove_timer(*timer); + } +} + +static bool should_dispatch = true; + +static void dispatch_status(DBusConnection *connection, DBusDispatchStatus new_status, + void *_data) { + if (new_status == DBUS_DISPATCH_DATA_REMAINS) { + should_dispatch = true; + } +} + +/* Public functions below */ + +void dispatch_dbus() { + if (!should_dispatch) { + return; + } + + DBusDispatchStatus status; + + do { + status = dbus_connection_dispatch(conn); + } while (status == DBUS_DISPATCH_DATA_REMAINS); + + if (status != DBUS_DISPATCH_COMPLETE) { + sway_log(L_ERROR, "Cannot dispatch dbus events: %d", status); + } + + should_dispatch = false; +} + +int dbus_init() { + DBusError error; + dbus_error_init(&error); + + conn = dbus_bus_get(DBUS_BUS_SESSION, &error); + dbus_connection_set_exit_on_disconnect(conn, FALSE); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "Cannot get bus connection: %s\n", error.message); + conn = NULL; + return -1; + } + + sway_log(L_INFO, "Unique name: %s\n", dbus_bus_get_unique_name(conn)); + + // Will be called if dispatch status changes + dbus_connection_set_dispatch_status_function(conn, dispatch_status, NULL, NULL); + + if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch, + NULL, NULL, NULL)) { + dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); + sway_log(L_ERROR, "Failed to activate DBUS watch functions"); + return -1; + } + + if (!dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, + NULL, NULL, NULL)) { + dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); + dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, NULL); + sway_log(L_ERROR, "Failed to activate DBUS timeout functions"); + return -1; + } + + return 0; +} diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c new file mode 100644 index 00000000..29151a74 --- /dev/null +++ b/swaybar/tray/icon.c @@ -0,0 +1,404 @@ +#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "swaybar/tray/icon.h" +#include "swaybar/bar.h" +#include "swaybar/config.h" +#include "stringop.h" +#include "log.h" + +/** + * REVIEW: + * This file repeats lots of "costly" operations that are the same for every + * icon. It's possible to create a dictionary or some other structure to cache + * these, though it may complicate things somewhat. + * + * Also parsing (index.theme) is currently pretty messy, so that could be made + * much better as well. Over all, things work, but are not optimal. + */ + +/* Finds all themes that the given theme inherits */ +static list_t *find_inherits(const char *theme_dir) { + const char inherits[] = "Inherits"; + const char index_name[] = "index.theme"; + list_t *themes = create_list(); + FILE *index = NULL; + char *path = malloc(strlen(theme_dir) + sizeof(index_name)); + if (!path) { + goto fail; + } + if (!themes) { + goto fail; + } + + strcpy(path, theme_dir); + strcat(path, index_name); + + index = fopen(path, "r"); + if (!index) { + goto fail; + } + + char *buf = NULL; + size_t n = 0; + while (!feof(index)) { + getline(&buf, &n, index); + if (n <= sizeof(inherits) + 1) { + continue; + } + if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) { + char *themestr = buf + sizeof(inherits); + themes = split_string(themestr, ","); + break; + } + } + free(buf); + +fail: + free(path); + if (index) { + fclose(index); + } + return themes; +} + +static bool isdir(const char *path) { + struct stat statbuf; + if (stat(path, &statbuf) != -1) { + if (S_ISDIR(statbuf.st_mode)) { + return true; + } + } + return false; + +} + +/** + * Returns the directory of a given theme if it exists. + * The returned pointer must be freed. + */ +static char *find_theme_dir(const char *theme) { + char *basedir; + char *icon_dir; + + if (!theme) { + return NULL; + } + + if (!(icon_dir = malloc(1024))) { + sway_log(L_ERROR, "Out of memory!"); + goto fail; + } + + if ((basedir = getenv("HOME"))) { + if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + // XXX perhaps just goto trying in /usr/share? This + // shouldn't happen anyway, but might with a long global + goto fail; + } + + if (isdir(icon_dir)) { + return icon_dir; + } + } + + if ((basedir = getenv("XDG_DATA_DIRS"))) { + if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + // ditto + goto fail; + } + + if (isdir(icon_dir)) { + return icon_dir; + } + } + + // Spec says use "/usr/share/pixmaps/", but I see everything in + // "/usr/share/icons/" look it both, I suppose. + if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + goto fail; + } + if (isdir(icon_dir)) { + return icon_dir; + } + + if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + goto fail; + } + if (isdir(icon_dir)) { + return icon_dir; + } + +fail: + free(icon_dir); + sway_log(L_ERROR, "Could not find dir for theme: %s", theme); + return NULL; +} + +/** + * Returns all theme dirs needed to be looked in for an icon. + * Does not check for duplicates + */ +static list_t *find_all_theme_dirs(const char *theme) { + list_t *dirs = create_list(); + if (!dirs) { + return NULL; + } + char *dir = find_theme_dir(theme); + if (dir) { + list_add(dirs, dir); + list_t *inherits = find_inherits(dir); + list_cat(dirs, inherits); + list_free(inherits); + } + dir = find_theme_dir("hicolor"); + if (dir) { + list_add(dirs, dir); + } + + return dirs; +} + +struct subdir { + int size; + char name[]; +}; + +static int subdir_str_cmp(const void *_subdir, const void *_str) { + const struct subdir *subdir = _subdir; + const char *str = _str; + return strcmp(subdir->name, str); +} +/** + * Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but + * generates a list of struct subdirs + */ +static list_t *split_subdirs(char *subdir_str) { + list_t *subdir_list = create_list(); + char *copy = strdup(subdir_str); + if (!subdir_list || !copy) { + list_free(subdir_list); + free(copy); + return NULL; + } + + char *token; + token = strtok(copy, ","); + while(token) { + int len = strlen(token) + 1; + struct subdir *subdir = + malloc(sizeof(struct subdir) + sizeof(char [len])); + if (!subdir) { + // Return what we have + return subdir_list; + } + subdir->size = 0; + strcpy(subdir->name, token); + + list_add(subdir_list, subdir); + + token = strtok(NULL, ","); + } + free(copy); + + return subdir_list; +} +/** + * Returns a list of all subdirectories of a theme. + * Take note: the subdir names are all relative to `theme_dir` and must be + * combined with it to form a valid directory. + * + * Each member of the list is of type (struct subdir *) this struct contains + * the name of the subdir, along with size information. These must be freed + * bye the caller. + * + * This currently ignores min and max sizes of icons. + */ +static list_t* find_theme_subdirs(const char *theme_dir) { + const char index_name[] = "/index.theme"; + list_t *dirs = NULL; + char *path = malloc(strlen(theme_dir) + sizeof(index_name)); + FILE *index = NULL; + if (!path) { + sway_log(L_ERROR, "Failed to allocate memory"); + goto fail; + } + + strcpy(path, theme_dir); + strcat(path, index_name); + + index = fopen(path, "r"); + if (!index) { + sway_log(L_ERROR, "Could not open file: %s", path); + goto fail; + } + + char *buf = NULL; + size_t n = 0; + while (!feof(index)) { + const char directories[] = "Directories"; + getline(&buf, &n, index); + if (n <= sizeof(directories) + 1) { + continue; + } + if (strncmp(directories, buf, sizeof(directories) - 1) == 0) { + char *dirstr = buf + sizeof(directories); + dirs = split_subdirs(dirstr); + break; + } + } + // Now, find the size of each dir + struct subdir *current_subdir = NULL; + while (!feof(index)) { + const char size[] = "Size"; + getline(&buf, &n, index); + + if (buf[0] == '[') { + int len = strlen(buf); + if (buf[len-1] == '\n') { + len--; + } + // replace ']' + buf[len-1] = '\0'; + + int index; + if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) { + current_subdir = (dirs->items[index]); + } + } + + if (strncmp(size, buf, sizeof(size) - 1) == 0) { + if (current_subdir) { + current_subdir->size = atoi(buf + sizeof(size)); + } + } + } + free(buf); +fail: + free(path); + if (index) { + fclose(index); + } + return dirs; +} + +/* Returns the file of an icon given its name and size */ +static char *find_icon_file(const char *name, int size) { + int namelen = strlen(name); + list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme); + if (!dirs) { + return NULL; + } + int min_size_diff = INT_MAX; + char *current_file = NULL; + + for (int i = 0; i < dirs->length; ++i) { + char *dir = dirs->items[i]; + list_t *subdirs = find_theme_subdirs(dir); + + if (!subdirs) { + continue; + } + + for (int i = 0; i < subdirs->length; ++i) { + struct subdir *subdir = subdirs->items[i]; + + // Only use an unsized if we don't already have a + // canidate this should probably change to allow svgs + if (!subdir->size && current_file) { + continue; + } + + int size_diff = abs(size - subdir->size); + + if (size_diff >= min_size_diff) { + continue; + } + + char *path = malloc(strlen(subdir->name) + strlen(dir) + 2); + + strcpy(path, dir); + path[strlen(dir)] = '/'; + strcpy(path + strlen(dir) + 1, subdir->name); + + DIR *icons = opendir(path); + if (!icons) { + free(path); + continue; + } + + struct dirent *direntry; + while ((direntry = readdir(icons)) != NULL) { + int len = strlen(direntry->d_name); + if (len <= namelen + 2) { //must have some ext + continue; + } + if (strncmp(direntry->d_name, name, namelen) == 0) { + char *ext = direntry->d_name + namelen + 1; +#ifdef WITH_GDK_PIXBUF + if (strcmp(ext, "png") == 0 || + strcmp(ext, "xpm") == 0 || + strcmp(ext, "svg") == 0) { +#else + if (strcmp(ext, "png") == 0) { +#endif + free(current_file); + char *icon_path = malloc(strlen(path) + len + 2); + + strcpy(icon_path, path); + icon_path[strlen(path)] = '/'; + strcpy(icon_path + strlen(path) + 1, direntry->d_name); + current_file = icon_path; + min_size_diff = size_diff; + } + } + } + free(path); + closedir(icons); + } + free_flat_list(subdirs); + } + free_flat_list(dirs); + + return current_file; +} + +cairo_surface_t *find_icon(const char *name, int size) { + char *image_path = find_icon_file(name, size); + if (image_path == NULL) { + return NULL; + } + + cairo_surface_t *image = NULL; +#ifdef WITH_GDK_PIXBUF + GError *err = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err); + if (!pixbuf) { + sway_log(L_ERROR, "Failed to load icon image: %s", err->message); + } + image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); + g_object_unref(pixbuf); +#else + // TODO make svg work? cairo supports it. maybe remove gdk alltogether + image = cairo_image_surface_create_from_png(image_path); +#endif //WITH_GDK_PIXBUF + if (!image) { + sway_log(L_ERROR, "Could not read icon image"); + return NULL; + } + + free(image_path); + return image; +} diff --git a/swaybar/tray/sni.c b/swaybar/tray/sni.c new file mode 100644 index 00000000..f0638dca --- /dev/null +++ b/swaybar/tray/sni.c @@ -0,0 +1,463 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include +#include +#include +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni.h" +#include "swaybar/tray/icon.h" +#include "swaybar/bar.h" +#include "client/cairo.h" +#include "log.h" + +// Not sure what this is but cairo needs it. +static const cairo_user_data_key_t cairo_user_data_key; + +struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item, + int height) { + struct sni_icon_ref *sni_ref = malloc(sizeof(struct sni_icon_ref)); + if (!sni_ref) { + return NULL; + } + sni_ref->icon = cairo_image_surface_scale(item->image, height, height); + sni_ref->ref = item; + + return sni_ref; +} + +void sni_icon_ref_free(struct sni_icon_ref *sni_ref) { + if (!sni_ref) { + return; + } + cairo_surface_destroy(sni_ref->icon); + free(sni_ref); +} + +/* Gets the pixmap of an icon */ +static void reply_icon(DBusPendingCall *pending, void *_data) { + struct StatusNotifierItem *item = _data; + + DBusMessage *reply = dbus_pending_call_steal_reply(pending); + + if (!reply) { + sway_log(L_ERROR, "Did not get reply"); + goto bail; + } + + int message_type = dbus_message_get_type(reply); + + if (message_type == DBUS_MESSAGE_TYPE_ERROR) { + char *msg; + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &msg, + DBUS_TYPE_INVALID); + + sway_log(L_ERROR, "Message is error: %s", msg); + goto bail; + } + + DBusMessageIter iter; + DBusMessageIter variant; /* v[a(iiay)] */ + DBusMessageIter array; /* a(iiay) */ + DBusMessageIter d_struct; /* (iiay) */ + DBusMessageIter icon; /* ay */ + + dbus_message_iter_init(reply, &iter); + + // Each if here checks the types above before recursing + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"v\", is \"%s\"", + dbus_message_iter_get_signature(&iter)); + goto bail; + } + dbus_message_iter_recurse(&iter, &variant); + + if (strcmp("a(iiay)", dbus_message_iter_get_signature(&variant)) != 0) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"a(iiay)\", is \"%s\"", + dbus_message_iter_get_signature(&variant)); + goto bail; + } + + if (dbus_message_iter_get_element_count(&variant) == 0) { + // Can't recurse if there are no items + sway_log(L_INFO, "Item has no icon"); + goto bail; + } + dbus_message_iter_recurse(&variant, &array); + + dbus_message_iter_recurse(&array, &d_struct); + + int width; + dbus_message_iter_get_basic(&d_struct, &width); + dbus_message_iter_next(&d_struct); + + int height; + dbus_message_iter_get_basic(&d_struct, &height); + dbus_message_iter_next(&d_struct); + + int len = dbus_message_iter_get_element_count(&d_struct); + + if (!len) { + sway_log(L_ERROR, "No icon data"); + goto bail; + } + + // Also implies len % 4 == 0, useful below + if (len != width * height * 4) { + sway_log(L_ERROR, "Incorrect array size passed"); + goto bail; + } + + dbus_message_iter_recurse(&d_struct, &icon); + + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + // FIXME support a variable stride + // (works on my machine though for all tested widths) + if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) { + goto bail; + } + + // Data is by reference, no need to free + uint8_t *message_data; + dbus_message_iter_get_fixed_array(&icon, &message_data, &len); + + uint8_t *image_data = malloc(stride * height); + if (!image_data) { + sway_log(L_ERROR, "Could not allocate memory for icon"); + goto bail; + } + + // Transform from network byte order to host byte order + // Assumptions are safe because the equality above + uint32_t *network = (uint32_t *) message_data; + uint32_t *host = (uint32_t *)image_data; + for (int i = 0; i < width * height; ++i) { + host[i] = ntohl(network[i]); + } + + cairo_surface_t *image = cairo_image_surface_create_for_data( + image_data, CAIRO_FORMAT_ARGB32, + width, height, stride); + + if (image) { + if (item->image) { + cairo_surface_destroy(item->image); + } + item->image = image; + // Free the image data on surface destruction + cairo_surface_set_user_data(image, + &cairo_user_data_key, + image_data, + free); + item->dirty = true; + dirty = true; + + dbus_message_unref(reply); + return; + } else { + sway_log(L_ERROR, "Could not create image surface"); + free(image_data); + } + +bail: + if (reply) { + dbus_message_unref(reply); + } + sway_log(L_ERROR, "Could not get icon from item"); + return; +} +static void send_icon_msg(struct StatusNotifierItem *item) { + DBusPendingCall *pending; + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + "org.freedesktop.DBus.Properties", + "Get"); + const char *iface; + if (item->kde_special_snowflake) { + iface = "org.kde.StatusNotifierItem"; + } else { + iface = "org.freedesktop.StatusNotifierItem"; + } + const char *prop = "IconPixmap"; + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + bool status = + dbus_connection_send_with_reply(conn, message, &pending, -1); + + dbus_message_unref(message); + + if (!(pending || status)) { + sway_log(L_ERROR, "Could not get item icon"); + return; + } + + dbus_pending_call_set_notify(pending, reply_icon, item, NULL); +} + +/* Get an icon by its name */ +static void reply_icon_name(DBusPendingCall *pending, void *_data) { + struct StatusNotifierItem *item = _data; + + DBusMessage *reply = dbus_pending_call_steal_reply(pending); + + if (!reply) { + sway_log(L_INFO, "Got no icon name reply from item"); + goto bail; + } + + int message_type = dbus_message_get_type(reply); + + if (message_type == DBUS_MESSAGE_TYPE_ERROR) { + char *msg; + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &msg, + DBUS_TYPE_INVALID); + + sway_log(L_INFO, "Could not get icon name: %s", msg); + goto bail; + } + + DBusMessageIter iter; /* v[s] */ + DBusMessageIter variant; /* s */ + + dbus_message_iter_init(reply, &iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"v\", is \"%s\"", + dbus_message_iter_get_signature(&iter)); + goto bail; + } + dbus_message_iter_recurse(&iter, &variant); + + + if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"s\", is \"%s\"", + dbus_message_iter_get_signature(&iter)); + goto bail; + } + + char *icon_name; + dbus_message_iter_get_basic(&variant, &icon_name); + + cairo_surface_t *image = find_icon(icon_name, 256); + + if (image) { + sway_log(L_DEBUG, "Icon for %s found with size %d", icon_name, + cairo_image_surface_get_width(image)); + if (item->image) { + cairo_surface_destroy(item->image); + } + item->image = image; + item->dirty = true; + dirty = true; + + dbus_message_unref(reply); + return; + } + +bail: + if (reply) { + dbus_message_unref(reply); + } + // Now try the pixmap + send_icon_msg(item); + return; +} +static void send_icon_name_msg(struct StatusNotifierItem *item) { + DBusPendingCall *pending; + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + "org.freedesktop.DBus.Properties", + "Get"); + const char *iface; + if (item->kde_special_snowflake) { + iface = "org.kde.StatusNotifierItem"; + } else { + iface = "org.freedesktop.StatusNotifierItem"; + } + const char *prop = "IconName"; + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + bool status = + dbus_connection_send_with_reply(conn, message, &pending, -1); + + dbus_message_unref(message); + + if (!(pending || status)) { + sway_log(L_ERROR, "Could not get item icon name"); + return; + } + + dbus_pending_call_set_notify(pending, reply_icon_name, item, NULL); +} + +void get_icon(struct StatusNotifierItem *item) { + send_icon_name_msg(item); +} + +void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { + const char *iface = + (item->kde_special_snowflake ? "org.kde.StatusNotifierItem" + : "org.freedesktop.StatusNotifierItem"); + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + iface, + "Activate"); + + dbus_message_append_args(message, + DBUS_TYPE_INT32, &x, + DBUS_TYPE_INT32, &y, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} + +void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { + const char *iface = + (item->kde_special_snowflake ? "org.kde.StatusNotifierItem" + : "org.freedesktop.StatusNotifierItem"); + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + iface, + "ContextMenu"); + + dbus_message_append_args(message, + DBUS_TYPE_INT32, &x, + DBUS_TYPE_INT32, &y, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} +void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { + const char *iface = + (item->kde_special_snowflake ? "org.kde.StatusNotifierItem" + : "org.freedesktop.StatusNotifierItem"); + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + iface, + "SecondaryActivate"); + + dbus_message_append_args(message, + DBUS_TYPE_INT32, &x, + DBUS_TYPE_INT32, &y, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} + +static void get_unique_name(struct StatusNotifierItem *item) { + // I think that we're fine being sync here becaues the message is + // directly to the message bus. Could be async though. + DBusMessage *message = dbus_message_new_method_call( + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetNameOwner"); + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &item->name, + DBUS_TYPE_INVALID); + + DBusMessage *reply = dbus_connection_send_with_reply_and_block( + conn, message, -1, NULL); + + dbus_message_unref(message); + + if (!reply) { + sway_log(L_ERROR, "Could not get unique name for item: %s", + item->name); + return; + } + + if (!dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &item->unique_name, + DBUS_TYPE_INVALID)) { + item->unique_name = NULL; + sway_log(L_ERROR, "Error parsing method args"); + } + + dbus_message_unref(reply); +} + +struct StatusNotifierItem *sni_create(const char *name) { + struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem)); + item->name = strdup(name); + item->unique_name = NULL; + item->image = NULL; + item->dirty = false; + + // If it doesn't use this name then assume that it uses the KDE spec + // This is because xembed-sni-proxy uses neither "org.freedesktop" nor + // "org.kde" and just gives us the items "unique name" + // + // We could use this to our advantage and fill out the "unique name" + // field with the given name if it is neither freedesktop or kde, but + // that's makes us rely on KDE hackyness which is bad practice + const char freedesktop_name[] = "org.freedesktop"; + if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) { + item->kde_special_snowflake = true; + } else { + item->kde_special_snowflake = false; + } + + get_icon(item); + + get_unique_name(item); + + return item; +} +/* Return true if `item` has a name of `str` */ +int sni_str_cmp(const void *_item, const void *_str) { + const struct StatusNotifierItem *item = _item; + const char *str = _str; + + return strcmp(item->name, str); +} +/* Returns true if `item` has a unique name of `str` */ +int sni_uniq_cmp(const void *_item, const void *_str) { + const struct StatusNotifierItem *item = _item; + const char *str = _str; + + if (!item->unique_name) { + return false; + } + return strcmp(item->unique_name, str); +} +void sni_free(struct StatusNotifierItem *item) { + if (!item) { + return; + } + free(item->name); + if (item->image) { + cairo_surface_destroy(item->image); + } + free(item); +} diff --git a/swaybar/tray/sni_watcher.c b/swaybar/tray/sni_watcher.c new file mode 100644 index 00000000..388e181d --- /dev/null +++ b/swaybar/tray/sni_watcher.c @@ -0,0 +1,487 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include +#include "swaybar/tray/dbus.h" +#include "list.h" +#include "log.h" + +static list_t *items = NULL; +static list_t *hosts = NULL; + +/** + * Describes the function of the StatusNotifierWatcher + * See https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/ + * + * We also implement KDE's special snowflake protocol, it's like this but with + * all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect. + */ +static const char *interface_xml = + "" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +static void host_registered_signal(DBusConnection *connection) { + // Send one signal for each protocol + DBusMessage *signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierHostRegistered"); + + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); + + + signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierHostRegistered"); + + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); +} +static void item_registered_signal(DBusConnection *connection, const char *name) { + DBusMessage *signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemRegistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); + + signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierItemRegistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); +} +static void item_unregistered_signal(DBusConnection *connection, const char *name) { + DBusMessage *signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemUnregistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); + + signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierItemUnregistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); +} + +static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) { + DBusMessage *reply; + + reply = dbus_message_new_method_return(request); + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &interface_xml, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static void register_item(DBusConnection *connection, DBusMessage *message) { + DBusError error; + char *name; + + dbus_error_init(&error); + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error parsing method args: %s\n", error.message); + } + + name = strdup(name); + sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name); + + // Don't add duplicate or not real item + if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) { + return; + } + if (!dbus_bus_name_has_owner(connection, name, &error)) { + return; + } + + list_add(items, name); + item_registered_signal(connection, name); + + // It's silly, but xembedsniproxy wants a reply for this function + DBusMessage *reply = dbus_message_new_method_return(message); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static void register_host(DBusConnection *connection, DBusMessage *message) { + DBusError error; + char *name; + + dbus_error_init(&error); + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error parsing method args: %s\n", error.message); + } + + sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name); + + // Don't add duplicate or not real host + if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) { + return; + } + if (!dbus_bus_name_has_owner(connection, name, &error)) { + return; + } + + list_add(hosts, strdup(name)); + host_registered_signal(connection); +} + +static void get_property(DBusConnection *connection, DBusMessage *message) { + DBusError error; + char *interface; + char *property; + + dbus_error_init(&error); + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message); + return; + } + + if (strcmp(property, "RegisteredStatusNotifierItems") == 0) { + sway_log(L_INFO, "Replying with items\n"); + DBusMessage *reply; + reply = dbus_message_new_method_return(message); + DBusMessageIter iter; + DBusMessageIter sub; + DBusMessageIter subsub; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, + "as", &sub); + dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, + "s", &subsub); + + for (int i = 0; i < items->length; ++i) { + dbus_message_iter_append_basic(&subsub, + DBUS_TYPE_STRING, &items->items[i]); + } + + dbus_message_iter_close_container(&sub, &subsub); + dbus_message_iter_close_container(&iter, &sub); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) { + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter sub; + int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1; + + reply = dbus_message_new_method_return(message); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, + "b", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_BOOLEAN, ®istered); + + dbus_message_iter_close_container(&iter, &sub); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } else if (strcmp(property, "ProtocolVersion") == 0) { + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter sub; + const int version = 0; + + reply = dbus_message_new_method_return(message); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, + "i", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_INT32, &version); + + dbus_message_iter_close_container(&iter, &sub); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } +} + +static void set_property(DBusConnection *connection, DBusMessage *message) { + // All properties are read only and we don't allow new properties + return; +} + +static void get_all(DBusConnection *connection, DBusMessage *message) { + DBusMessage *reply; + reply = dbus_message_new_method_return(message); + DBusMessageIter iter; /* a{v} */ + DBusMessageIter arr; + DBusMessageIter dict; + DBusMessageIter sub; + DBusMessageIter subsub; + int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1; + const int version = 0; + const char *prop; + + // Could clean this up with a function for each prop + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + "{sv}", &arr); + + prop = "RegisteredStatusNotifierItems"; + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, + NULL, &dict); + dbus_message_iter_append_basic(&dict, + DBUS_TYPE_STRING, &prop); + dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, + "as", &sub); + dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, + "s", &subsub); + for (int i = 0; i < items->length; ++i) { + dbus_message_iter_append_basic(&subsub, + DBUS_TYPE_STRING, &items->items[i]); + } + dbus_message_iter_close_container(&sub, &subsub); + dbus_message_iter_close_container(&dict, &sub); + dbus_message_iter_close_container(&arr, &dict); + + prop = "IsStatusNotifierHostRegistered"; + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, + NULL, &dict); + dbus_message_iter_append_basic(&dict, + DBUS_TYPE_STRING, &prop); + dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, + "b", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_BOOLEAN, ®istered); + dbus_message_iter_close_container(&dict, &sub); + dbus_message_iter_close_container(&arr, &dict); + + prop = "ProtocolVersion"; + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, + NULL, &dict); + dbus_message_iter_append_basic(&dict, + DBUS_TYPE_STRING, &prop); + dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, + "i", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_INT32, &version); + dbus_message_iter_close_container(&dict, &sub); + dbus_message_iter_close_container(&arr, &dict); + + dbus_message_iter_close_container(&iter, &arr); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static DBusHandlerResult message_handler(DBusConnection *connection, + DBusMessage *message, void *data) { + const char *interface_name = dbus_message_get_interface(message); + const char *member_name = dbus_message_get_member(message); + + // In order of the xml above + if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 && + strcmp(member_name, "Introspect") == 0) { + // We don't have an introspect for KDE + respond_to_introspect(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) { + if (strcmp(member_name, "Get") == 0) { + get_property(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(member_name, "Set") == 0) { + set_property(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(member_name, "GetAll") == 0) { + get_all(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 || + strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) { + if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) { + register_item(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) { + register_host(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult signal_handler(DBusConnection *connection, + DBusMessage *message, void *_data) { + if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) { + // Only eat the message if it is name that we are watching + const char *name; + const char *old_owner; + const char *new_owner; + int index; + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error getting LostName args"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + if (strcmp(new_owner, "") != 0) { + // Name is not lost + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) { + sway_log(L_INFO, "Status Notifier Item lost %s", name); + free(items->items[index]); + list_del(items, index); + item_unregistered_signal(connection, name); + + return DBUS_HANDLER_RESULT_HANDLED; + } + if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) { + sway_log(L_INFO, "Status Notifier Host lost %s", name); + free(hosts->items[index]); + list_del(hosts, index); + + return DBUS_HANDLER_RESULT_HANDLED; + } + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static const DBusObjectPathVTable vtable = { + .message_function = message_handler, + .unregister_function = NULL, +}; + +int init_sni_watcher() { + DBusError error; + dbus_error_init(&error); + if (!conn) { + sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher"); + return -1; + } + + items = create_list(); + hosts = create_list(); + + int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher", + DBUS_NAME_FLAG_REPLACE_EXISTING, + &error); + if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + sway_log(L_DEBUG, "Got watcher name"); + } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { + sway_log(L_INFO, "Could not get watcher name, it may start later"); + } + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message); + return -1; + } + + status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher", + DBUS_NAME_FLAG_REPLACE_EXISTING, + &error); + if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + sway_log(L_DEBUG, "Got kde watcher name"); + } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { + sway_log(L_INFO, "Could not get kde watcher name, it may start later"); + } + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message); + return -1; + } + + dbus_connection_try_register_object_path(conn, + "/StatusNotifierWatcher", + &vtable, NULL, &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err: %s\n", error.message); + return -1; + } + + dbus_bus_add_match(conn, + "type='signal',\ + sender='org.freedesktop.DBus',\ + interface='org.freedesktop.DBus',\ + member='NameOwnerChanged'", + &error); + + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "DBus error getting match args: %s", error.message); + } + + dbus_connection_add_filter(conn, signal_handler, NULL, NULL); + return 0; +} diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c new file mode 100644 index 00000000..9a709fe4 --- /dev/null +++ b/swaybar/tray/tray.c @@ -0,0 +1,279 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include "swaybar/bar.h" +#include "swaybar/tray/tray.h" +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni.h" +#include "swaybar/bar.h" +#include "list.h" +#include "log.h" + +struct tray *tray; + +static void register_host(char *name) { + DBusMessage *message; + + message = dbus_message_new_method_call( + "org.freedesktop.StatusNotifierWatcher", + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "RegisterStatusNotifierHost"); + if (!message) { + sway_log(L_ERROR, "Cannot allocate dbus method call"); + return; + } + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} + +static void get_items_reply(DBusPendingCall *pending, void *_data) { + DBusMessage *reply = dbus_pending_call_steal_reply(pending); + + if (!reply) { + sway_log(L_ERROR, "Got no items reply from sni watcher"); + goto bail; + } + + int message_type = dbus_message_get_type(reply); + + if (message_type == DBUS_MESSAGE_TYPE_ERROR) { + char *msg; + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &msg, + DBUS_TYPE_INVALID); + + sway_log(L_ERROR, "Message is error: %s", msg); + goto bail; + } + + DBusMessageIter iter; + DBusMessageIter variant; + DBusMessageIter array; + + dbus_message_iter_init(reply, &iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + sway_log(L_ERROR, "Replyed with wrong type, not v(as)"); + goto bail; + } + dbus_message_iter_recurse(&iter, &variant); + if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) { + sway_log(L_ERROR, "Replyed with wrong type, not v(as)"); + goto bail; + } + + // Clear list + list_foreach(tray->items, (void (*)(void *))sni_free); + list_free(tray->items); + tray->items = create_list(); + + // O(n) function, could be faster dynamically reading values + int len = dbus_message_iter_get_element_count(&variant); + + dbus_message_iter_recurse(&variant, &array); + for (int i = 0; i < len; i++) { + const char *name; + dbus_message_iter_get_basic(&array, &name); + + struct StatusNotifierItem *item = sni_create(name); + + sway_log(L_DEBUG, "Item registered with host: %s", name); + list_add(tray->items, item); + dirty = true; + } + +bail: + dbus_message_unref(reply); + return; +} +static void get_items() { + DBusPendingCall *pending; + DBusMessage *message = dbus_message_new_method_call( + "org.freedesktop.StatusNotifierWatcher", + "/StatusNotifierWatcher", + "org.freedesktop.DBus.Properties", + "Get"); + + const char *iface = "org.freedesktop.StatusNotifierWatcher"; + const char *prop = "RegisteredStatusNotifierItems"; + dbus_message_append_args(message, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + bool status = + dbus_connection_send_with_reply(conn, message, &pending, -1); + dbus_message_unref(message); + + if (!(pending || status)) { + sway_log(L_ERROR, "Could not get items"); + return; + } + + dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL); +} + +static DBusHandlerResult signal_handler(DBusConnection *connection, + DBusMessage *message, void *_data) { + if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemRegistered")) { + const char *name; + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (list_seq_find(tray->items, sni_str_cmp, name) == -1) { + struct StatusNotifierItem *item = sni_create(name); + + list_add(tray->items, item); + dirty = true; + } + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemUnregistered")) { + const char *name; + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + int index; + if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) { + sni_free(tray->items->items[index]); + list_del(tray->items, index); + dirty = true; + } else { + // If it's not in our list, then our list is incorrect. + // Fetch all items again + sway_log(L_INFO, "Host item list incorrect, refreshing"); + get_items(); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem", + "NewIcon") || dbus_message_is_signal(message, + "org.kde.StatusNotifierItem", "NewIcon")) { + const char *name; + int index; + struct StatusNotifierItem *item; + + name = dbus_message_get_sender(message); + if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) { + item = tray->items->items[index]; + get_icon(item); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int init_tray() { + tray = (struct tray *)malloc(sizeof(tray)); + + tray->items = create_list(); + + DBusError error; + dbus_error_init(&error); + char *name = NULL; + if (!conn) { + sway_log(L_ERROR, "Connection is null, cannot init SNI host"); + goto err; + } + name = calloc(sizeof(char), 256); + + if (!name) { + sway_log(L_ERROR, "Cannot allocate name"); + goto err; + } + + pid_t pid = getpid(); + if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid) + >= 256) { + sway_log(L_ERROR, "Cannot get host name because string is too short." + "This should not happen"); + goto err; + } + + // We want to be the sole owner of this name + if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, + &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + sway_log(L_ERROR, "Cannot get host name and start the tray"); + goto err; + } + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message); + goto err; + } + sway_log(L_DEBUG, "Got host name"); + + register_host(name); + + get_items(); + + // Perhaps use addmatch helper functions like wlc does? + dbus_bus_add_match(conn, + "type='signal',\ + sender='org.freedesktop.StatusNotifierWatcher',\ + member='StatusNotifierItemRegistered'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err: %s", error.message); + goto err; + } + dbus_bus_add_match(conn, + "type='signal',\ + sender='org.freedesktop.StatusNotifierWatcher',\ + member='StatusNotifierItemUnregistered'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err: %s", error.message); + return -1; + } + + // SNI matches + dbus_bus_add_match(conn, + "type='signal',\ + interface='org.freedesktop.StatusNotifierItem',\ + member='NewIcon'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err %s", error.message); + goto err; + } + dbus_bus_add_match(conn, + "type='signal',\ + interface='org.kde.StatusNotifierItem',\ + member='NewIcon'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err %s", error.message); + goto err; + } + + dbus_connection_add_filter(conn, signal_handler, NULL, NULL); + + free(name); + return 0; + +err: + // TODO better handle errors + free(name); + return -1; +} -- cgit v1.2.3-54-g00ecf