#define _XOPEN_SOURCE 500 #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" 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; } static int init_host() { 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; } 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); } }