sway-patched-tray-menu-github/swaybar/tray/tray.c
Calvin Lee 0a71aa6e97 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.
2017-06-08 08:24:35 -07:00

409 lines
10 KiB
C

#define _XOPEN_SOURCE 500
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <dbus/dbus.h>
#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];
sway_log(L_INFO, "NewIcon signal from item %s", item->name);
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);
}
}