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/tray.c | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 swaybar/tray/tray.c (limited to 'swaybar/tray/tray.c') 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 From 1451ee8fd13dd35227d11e393c80871c70ad90f0 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Wed, 7 Jun 2017 21:32:48 -0700 Subject: Reorganize Tray Code Remove tray code from bar.c and render.c --- swaybar/tray/tray.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) (limited to 'swaybar/tray/tray.c') diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c index 9a709fe4..ca8b1341 100644 --- a/swaybar/tray/tray.c +++ b/swaybar/tray/tray.c @@ -2,12 +2,15 @@ #include #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/tray/sni_watcher.h" #include "swaybar/bar.h" +#include "swaybar/config.h" #include "list.h" #include "log.h" @@ -184,7 +187,7 @@ static DBusHandlerResult signal_handler(DBusConnection *connection, return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } -int init_tray() { +static int init_host() { tray = (struct tray *)malloc(sizeof(tray)); tray->items = create_list(); @@ -277,3 +280,129 @@ err: free(name); return -1; } + +void tray_mouse_event(struct output *output, int x, int y, + uint32_t button, uint32_t state) { + + struct window *window = output->window; + uint32_t tray_padding = swaybar.config->tray_padding; + int tray_width = window->width * window->scale; + + for (int i = 0; i < output->items->length; ++i) { + struct sni_icon_ref *item = + output->items->items[i]; + int icon_width = cairo_image_surface_get_width(item->icon); + + tray_width -= tray_padding; + if (x <= tray_width && x >= tray_width - icon_width) { + if (button == swaybar.config->activate_button) { + sni_activate(item->ref, x, y); + } else if (button == swaybar.config->context_button) { + sni_context_menu(item->ref, x, y); + } else if (button == swaybar.config->secondary_button) { + sni_secondary(item->ref, x, y); + } + break; + } + tray_width -= icon_width; + } +} + +uint32_t tray_render(struct output *output, struct config *config) { + struct window *window = output->window; + cairo_t *cairo = window->cairo; + + // Tray icons + uint32_t tray_padding = config->tray_padding; + uint32_t tray_width = window->width * window->scale; + const int item_size = (window->height * window->scale) - (2 * tray_padding); + + if (item_size < 0) { + // Can't render items if the padding is too large + return tray_width; + } + + if (config->tray_output && strcmp(config->tray_output, output->name) != 0) { + return tray_width; + } + + for (int i = 0; i < tray->items->length; ++i) { + struct StatusNotifierItem *item = + tray->items->items[i]; + if (!item->image) { + continue; + } + + struct sni_icon_ref *render_item = NULL; + int j; + for (j = i; j < output->items->length; ++j) { + struct sni_icon_ref *ref = + output->items->items[j]; + if (ref->ref == item) { + render_item = ref; + break; + } else { + sni_icon_ref_free(ref); + list_del(output->items, j); + } + } + + if (!render_item) { + render_item = sni_icon_ref_create(item, item_size); + list_add(output->items, render_item); + } else if (item->dirty) { + // item needs re-render + sni_icon_ref_free(render_item); + output->items->items[j] = render_item = + sni_icon_ref_create(item, item_size); + } + + tray_width -= tray_padding; + tray_width -= item_size; + + cairo_operator_t op = cairo_get_operator(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding); + cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size); + cairo_fill(cairo); + cairo_set_operator(cairo, op); + + item->dirty = false; + } + + + if (tray_width != window->width * window->scale) { + tray_width -= tray_padding; + } + + return tray_width; +} + +void tray_upkeep(struct bar *bar) { + if (!bar->xembed_pid || + (bar->xembed_pid == waitpid(bar->xembed_pid, NULL, WNOHANG))) { + pid_t pid = fork(); + if (pid == 0) { + execlp("xembedsniproxy", "xembedsniproxy", NULL); + _exit(EXIT_FAILURE); + } else { + bar->xembed_pid = pid; + } + } +} + +void init_tray(struct bar *bar) { + if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) { + /* Connect to the D-Bus */ + dbus_init(); + + /* Start the SNI watcher */ + init_sni_watcher(); + + /* Start the SNI host */ + init_host(); + + /* Start xembedsniproxy */ + tray_upkeep(bar); + } +} -- cgit v1.2.3 From 0a71aa6e97a96ffbd34fe18ec42b27d8fe5952e8 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Thu, 8 Jun 2017 05:36:17 -0700 Subject: Fix Catching NewIcon Signal The unique name was not copied out of the wire marshalled DBus message data so `sni_uniq_cmp` would always match against junk data. --- swaybar/tray/tray.c | 1 + 1 file changed, 1 insertion(+) (limited to 'swaybar/tray/tray.c') diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c index ca8b1341..b2fa647e 100644 --- a/swaybar/tray/tray.c +++ b/swaybar/tray/tray.c @@ -179,6 +179,7 @@ static DBusHandlerResult signal_handler(DBusConnection *connection, name = dbus_message_get_sender(message); if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) { item = tray->items->items[index]; + sway_log(L_INFO, "NewIcon signal from item %s", item->name); get_icon(item); } -- cgit v1.2.3 From 33fdae2001f489c40667797ce3bc50eedb352ee0 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 13 Jun 2017 12:42:11 -0700 Subject: Remove Xembed Support Xembed support is premature in sway and should be postponed. This commit only removes swaybar starting xembedsniproxy, if users would like, they can still start xembedsniproxy manually, however there will be no official support. --- swaybar/tray/tray.c | 16 ---------------- 1 file changed, 16 deletions(-) (limited to 'swaybar/tray/tray.c') diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c index b2fa647e..00f1a44f 100644 --- a/swaybar/tray/tray.c +++ b/swaybar/tray/tray.c @@ -379,19 +379,6 @@ uint32_t tray_render(struct output *output, struct config *config) { return tray_width; } -void tray_upkeep(struct bar *bar) { - if (!bar->xembed_pid || - (bar->xembed_pid == waitpid(bar->xembed_pid, NULL, WNOHANG))) { - pid_t pid = fork(); - if (pid == 0) { - execlp("xembedsniproxy", "xembedsniproxy", NULL); - _exit(EXIT_FAILURE); - } else { - bar->xembed_pid = pid; - } - } -} - void init_tray(struct bar *bar) { if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) { /* Connect to the D-Bus */ @@ -402,8 +389,5 @@ void init_tray(struct bar *bar) { /* Start the SNI host */ init_host(); - - /* Start xembedsniproxy */ - tray_upkeep(bar); } } -- cgit v1.2.3