Merge pull request #1431 from 4e554c4c/sni_sucks

Support libappindicator
This commit is contained in:
Drew DeVault 2017-12-29 14:24:23 -05:00 committed by GitHub
commit 1e87c90923
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 601 additions and 299 deletions

View file

@ -21,6 +21,7 @@ struct bar {
struct output { struct output {
struct window *window; struct window *window;
struct registry *registry; struct registry *registry;
struct output_state *state;
list_t *workspaces; list_t *workspaces;
#ifdef ENABLE_TRAY #ifdef ENABLE_TRAY
list_t *items; list_t *items;
@ -28,6 +29,9 @@ struct output {
char *name; char *name;
int idx; int idx;
bool focused; bool focused;
#ifdef ENABLE_TRAY
bool active;
#endif
}; };
struct workspace { struct workspace {

View file

@ -5,6 +5,37 @@
#include <dbus/dbus.h> #include <dbus/dbus.h>
extern DBusConnection *conn; extern DBusConnection *conn;
enum property_status {
PROP_EXISTS, /* Will give iter */
PROP_ERROR, /* Will not give iter */
PROP_BAD_DATA, /* Will not give iter */
PROP_WRONG_SIG, /* Will give iter, please be careful */
};
/**
* Checks the signature of the given iter against `sig`. Prefer to
* `dbus_message_iter_get_signature` as this one frees the intermediate string.
*/
bool dbus_message_iter_check_signature(DBusMessageIter *iter, const char *sig);
/**
* Fetches the property and calls `callback` with a message iter pointing it.
* Performs error handling and signature checking.
*
* Returns: true if message is successfully sent and false otherwise. If there
* is an error getting a property, `callback` will still be run, but with
* `status` set to the error.
*
* NOTE: `expected_signature` must remain valid until the message reply is
* received, please only use 'static signatures.
*/
bool dbus_get_prop_async(const char *destination,
const char *path,
const char *iface,
const char *prop,
const char *expected_signature,
void(*callback)(DBusMessageIter *iter, void *data, enum property_status status),
void *data);
/** /**
* Should be called in main loop to dispatch events * Should be called in main loop to dispatch events
*/ */

View file

@ -9,6 +9,8 @@ struct StatusNotifierItem {
char *name; char *name;
/* Unique bus name, needed for determining signal origins */ /* Unique bus name, needed for determining signal origins */
char *unique_name; char *unique_name;
/* Object path, useful for items not registerd by well known name */
char *object_path;
bool kde_special_snowflake; bool kde_special_snowflake;
cairo_surface_t *image; cairo_surface_t *image;
@ -31,6 +33,12 @@ void sni_icon_ref_free(struct sni_icon_ref *sni_ref);
* May return `NULL` if `name` is not valid. * May return `NULL` if `name` is not valid.
*/ */
struct StatusNotifierItem *sni_create(const char *name); struct StatusNotifierItem *sni_create(const char *name);
/**
* Same as sni_create, but takes an object path and unique name instead of
* well-known name.
*/
struct StatusNotifierItem *sni_create_from_obj_path(const char *unique_name,
const char *object_path);
/** /**
* `item` must be a struct StatusNotifierItem * * `item` must be a struct StatusNotifierItem *
@ -46,6 +54,17 @@ int sni_str_cmp(const void *item, const void *str);
*/ */
int sni_uniq_cmp(const void *item, const void *str); int sni_uniq_cmp(const void *item, const void *str);
struct ObjName {
const void *obj_path;
const void *name;
};
/**
* Returns 0 if `item` has a name of `obj_name->name` and object path of
* `obj_name->obj_path`.
*/
int sni_obj_name_cmp(const void *item, const void *obj_name);
/** /**
* Gets an icon for the given item if found. * Gets an icon for the given item if found.
* *

View file

@ -247,6 +247,8 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) {
/* set window height */ /* set window height */
set_window_height(bar_output->window, bar->config->height); set_window_height(bar_output->window, bar->config->height);
bar_output->state = output;
} }
/* spawn status command */ /* spawn status command */
spawn_status_cmd_proc(bar); spawn_status_cmd_proc(bar);
@ -296,6 +298,11 @@ void bar_run(struct bar *bar) {
render(output, bar->config, bar->status); render(output, bar->config, bar->status);
window_render(output->window); window_render(output->window);
wl_display_flush(output->registry->display); wl_display_flush(output->registry->display);
#ifdef ENABLE_TRAY
output->active = true;
} else {
output->active = false;
#endif
} }
} }
} }

View file

@ -1,5 +1,6 @@
#define _XOPEN_SOURCE 700 #define _XOPEN_SOURCE 700
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
@ -135,7 +136,106 @@ static void dispatch_status(DBusConnection *connection, DBusDispatchStatus new_s
} }
} }
/* Public functions below */ struct async_prop_data {
char const *sig;
void(*callback)(DBusMessageIter *, void *, enum property_status);
void *usr_data;
};
static void get_prop_callback(DBusPendingCall *pending, void *_data) {
struct async_prop_data *data = _data;
DBusMessage *reply = dbus_pending_call_steal_reply(pending);
if (!reply) {
sway_log(L_INFO, "Got no icon name reply from item");
goto bail;
}
if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
char *msg;
dbus_message_get_args(reply, NULL,
DBUS_TYPE_STRING, &msg,
DBUS_TYPE_INVALID);
sway_log(L_INFO, "Failure to get property: %s", msg);
data->callback(NULL, data->usr_data, PROP_ERROR);
goto bail;
}
DBusMessageIter iter;
DBusMessageIter variant;
dbus_message_iter_init(reply, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
sway_log(L_ERROR, "Property relpy type incorrect");
data->callback(NULL, data->usr_data, PROP_BAD_DATA);
goto bail;
}
dbus_message_iter_recurse(&iter, &variant);
if (!dbus_message_iter_check_signature(&variant, data->sig)) {
sway_log(L_INFO, "Property returned has incorrect signatue.");
data->callback(&variant, data->usr_data, PROP_WRONG_SIG);
goto bail;
}
data->callback(&variant, data->usr_data, PROP_EXISTS);
bail:
if (reply) {
dbus_message_unref(reply);
}
dbus_pending_call_unref(pending);
}
/* Public functions below -- see header for docs*/
bool dbus_message_iter_check_signature(DBusMessageIter *iter, const char *sig) {
char *msg_sig = dbus_message_iter_get_signature(iter);
int result = strcmp(msg_sig, sig);
dbus_free(msg_sig);
return (result == 0);
}
bool dbus_get_prop_async(const char *destination,
const char *path, const char *iface,
const char *prop, const char *expected_signature,
void(*callback)(DBusMessageIter *, void *, enum property_status),
void *usr_data) {
struct async_prop_data *data = malloc(sizeof(struct async_prop_data));
if (!data) {
return false;
}
DBusPendingCall *pending;
DBusMessage *message = dbus_message_new_method_call(
destination, path,
"org.freedesktop.DBus.Properties",
"Get");
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 property");
return false;
}
data->sig = expected_signature;
data->callback = callback;
data->usr_data = usr_data;
dbus_pending_call_set_notify(pending, get_prop_callback, data, free);
return true;
}
void dispatch_dbus() { void dispatch_dbus() {
if (!should_dispatch || !conn) { if (!should_dispatch || !conn) {

View file

@ -80,6 +80,17 @@ static bool isdir(const char *path) {
} }
static bool isfile(const char *path) {
struct stat statbuf;
if (stat(path, &statbuf) != -1) {
if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) {
return true;
}
}
return false;
}
/** /**
* Returns the directory of a given theme if it exists. * Returns the directory of a given theme if it exists.
* The returned pointer must be freed. * The returned pointer must be freed.
@ -111,15 +122,28 @@ static char *find_theme_dir(const char *theme) {
} }
if ((basedir = getenv("XDG_DATA_DIRS"))) { if ((basedir = getenv("XDG_DATA_DIRS"))) {
if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) { if (!(basedir = strdup(basedir))) {
sway_log(L_ERROR, "Path too long to render"); sway_log_errno(L_ERROR, "Path too long to render");
// ditto
goto fail; goto fail;
} }
char *token = strtok(basedir, ":");
while (token) {
// By peeking at the spec, there should be a slash at
// the end of the data dir.
if (snprintf(icon_dir, 1024, "%sicons/%s", token, theme) >= 1024) {
sway_log(L_ERROR, "Path too long to render");
// ditto
free(basedir);
goto fail;
}
if (isdir(icon_dir)) { if (isdir(icon_dir)) {
return icon_dir; free(basedir);
return icon_dir;
}
token = strtok(NULL, ":");
} }
free(basedir);
} }
// Spec says use "/usr/share/pixmaps/", but I see everything in // Spec says use "/usr/share/pixmaps/", but I see everything in
@ -162,6 +186,15 @@ static list_t *find_all_theme_dirs(const char *theme) {
list_cat(dirs, inherits); list_cat(dirs, inherits);
list_free(inherits); list_free(inherits);
} }
// 'default' usually inherits the default theme. I don't believe it has
// any icons, but look for them anyway
dir = find_theme_dir("default");
if (dir) {
list_add(dirs, dir);
list_t *inherits = find_inherits(dir);
list_cat(dirs, inherits);
list_free(inherits);
}
dir = find_theme_dir("hicolor"); dir = find_theme_dir("hicolor");
if (dir) { if (dir) {
list_add(dirs, dir); list_add(dirs, dir);
@ -290,6 +323,24 @@ fail:
return dirs; return dirs;
} }
/* Returns true if full path and file exists */
static bool is_valid_path(const char *file) {
if (strstr(file, "/") == NULL || !isfile(file)) {
return false;
}
#ifdef WITH_GDK_PIXBUF
if (strstr(file, ".png") == NULL &&
strstr(file, ".xpm") == NULL &&
strstr(file, ".svg") == NULL) {
#else
if (strstr(file, ".png") == NULL) {
#endif
return false;
}
return true;
}
/* Returns the file of an icon given its name and size */ /* Returns the file of an icon given its name and size */
static char *find_icon_file(const char *name, int size) { static char *find_icon_file(const char *name, int size) {
int namelen = strlen(name); int namelen = strlen(name);
@ -372,7 +423,12 @@ static char *find_icon_file(const char *name, int size) {
} }
cairo_surface_t *find_icon(const char *name, int size) { cairo_surface_t *find_icon(const char *name, int size) {
char *image_path = find_icon_file(name, size); char *image_path;
if (is_valid_path(name)) {
image_path = strdup(name);
} else {
image_path = find_icon_file(name, size);
}
if (image_path == NULL) { if (image_path == NULL) {
return NULL; return NULL;
} }

View file

@ -14,6 +14,9 @@
#include "client/cairo.h" #include "client/cairo.h"
#include "log.h" #include "log.h"
static const char *KDE_IFACE = "org.kde.StatusNotifierItem";
static const char *FD_IFACE = "org.freedesktop.StatusNotifierItem";
// Not sure what this is but cairo needs it. // Not sure what this is but cairo needs it.
static const cairo_user_data_key_t cairo_user_data_key; static const cairo_user_data_key_t cairo_user_data_key;
@ -38,90 +41,53 @@ void sni_icon_ref_free(struct sni_icon_ref *sni_ref) {
} }
/* Gets the pixmap of an icon */ /* Gets the pixmap of an icon */
static void reply_icon(DBusPendingCall *pending, void *_data) { static void reply_icon(DBusMessageIter *iter /* a(iiay) */, void *_data, enum property_status status) {
if (status != PROP_EXISTS) {
return;
}
struct StatusNotifierItem *item = _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 d_struct; /* (iiay) */
DBusMessageIter icon; /* ay */ DBusMessageIter struct_items;
DBusMessageIter icon;
dbus_message_iter_init(reply, &iter); if (dbus_message_iter_get_element_count(iter) == 0) {
// 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 // Can't recurse if there are no items
sway_log(L_INFO, "Item has no icon"); sway_log(L_INFO, "Item has no icon");
goto bail; return;
} }
dbus_message_iter_recurse(&variant, &array);
dbus_message_iter_recurse(&array, &d_struct); dbus_message_iter_recurse(iter, &d_struct);
dbus_message_iter_recurse(&d_struct, &struct_items);
int width; int width;
dbus_message_iter_get_basic(&d_struct, &width); dbus_message_iter_get_basic(&struct_items, &width);
dbus_message_iter_next(&d_struct); dbus_message_iter_next(&struct_items);
int height; int height;
dbus_message_iter_get_basic(&d_struct, &height); dbus_message_iter_get_basic(&struct_items, &height);
dbus_message_iter_next(&d_struct); dbus_message_iter_next(&struct_items);
int len = dbus_message_iter_get_element_count(&d_struct); int len = dbus_message_iter_get_element_count(&struct_items);
if (!len) { if (!len) {
sway_log(L_ERROR, "No icon data"); sway_log(L_ERROR, "No icon data");
goto bail; return;
} }
// Also implies len % 4 == 0, useful below // Also implies len % 4 == 0, useful below
if (len != width * height * 4) { if (len != width * height * 4) {
sway_log(L_ERROR, "Incorrect array size passed"); sway_log(L_ERROR, "Incorrect array size passed");
goto bail; return;
} }
dbus_message_iter_recurse(&d_struct, &icon); dbus_message_iter_recurse(&struct_items, &icon);
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
// FIXME support a variable stride // FIXME support a variable stride
// (works on my machine though for all tested widths) // (works on my machine though for all tested widths)
if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) { if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) {
goto bail; return;
} }
// Data is by reference, no need to free // Data is by reference, no need to free
@ -131,7 +97,7 @@ static void reply_icon(DBusPendingCall *pending, void *_data) {
uint8_t *image_data = malloc(stride * height); uint8_t *image_data = malloc(stride * height);
if (!image_data) { if (!image_data) {
sway_log(L_ERROR, "Could not allocate memory for icon"); sway_log(L_ERROR, "Could not allocate memory for icon");
goto bail; return;
} }
// Transform from network byte order to host byte order // Transform from network byte order to host byte order
@ -159,101 +125,29 @@ static void reply_icon(DBusPendingCall *pending, void *_data) {
item->dirty = true; item->dirty = true;
dirty = true; dirty = true;
dbus_message_unref(reply);
dbus_pending_call_unref(pending);
return; return;
} else { } else {
sway_log(L_ERROR, "Could not create image surface"); sway_log(L_ERROR, "Could not create image surface");
free(image_data); free(image_data);
} }
bail:
if (reply) {
dbus_message_unref(reply);
}
dbus_pending_call_unref(pending);
sway_log(L_ERROR, "Could not get icon from item"); sway_log(L_ERROR, "Could not get icon from item");
return; 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, /* Get an icon by its name */
DBUS_TYPE_STRING, &iface, static void reply_icon_name(DBusMessageIter *iter, void *_data, enum property_status status) {
DBUS_TYPE_STRING, &prop, struct StatusNotifierItem *item = _data;
DBUS_TYPE_INVALID);
bool status = if (status != PROP_EXISTS) {
dbus_connection_send_with_reply(conn, message, &pending, -1); dbus_get_prop_async(item->name, item->object_path,
(item->kde_special_snowflake ? KDE_IFACE : FD_IFACE),
dbus_message_unref(message); "IconPixmap", "a(iiay)", reply_icon, item);
if (!(pending || status)) {
sway_log(L_ERROR, "Could not get item icon");
return; 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; char *icon_name;
dbus_message_iter_get_basic(&variant, &icon_name); dbus_message_iter_get_basic(iter, &icon_name);
cairo_surface_t *image = find_icon(icon_name, 256); cairo_surface_t *image = find_icon(icon_name, 256);
@ -267,55 +161,19 @@ static void reply_icon_name(DBusPendingCall *pending, void *_data) {
item->dirty = true; item->dirty = true;
dirty = true; dirty = true;
dbus_message_unref(reply);
dbus_pending_call_unref(pending);
return; return;
} }
bail:
if (reply) {
dbus_message_unref(reply);
}
dbus_pending_call_unref(pending);
// Now try the pixmap // Now try the pixmap
send_icon_msg(item); dbus_get_prop_async(item->name, item->object_path,
return; (item->kde_special_snowflake ? KDE_IFACE : FD_IFACE),
} "IconPixmap", "a(iiay)", reply_icon, item);
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) { void get_icon(struct StatusNotifierItem *item) {
send_icon_name_msg(item); dbus_get_prop_async(item->name, item->object_path,
(item->kde_special_snowflake ? KDE_IFACE : FD_IFACE),
"IconName", "s", reply_icon_name, item);
} }
void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
@ -324,7 +182,7 @@ void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
: "org.freedesktop.StatusNotifierItem"); : "org.freedesktop.StatusNotifierItem");
DBusMessage *message = dbus_message_new_method_call( DBusMessage *message = dbus_message_new_method_call(
item->name, item->name,
"/StatusNotifierItem", item->object_path,
iface, iface,
"Activate"); "Activate");
@ -342,9 +200,10 @@ void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
const char *iface = const char *iface =
(item->kde_special_snowflake ? "org.kde.StatusNotifierItem" (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
: "org.freedesktop.StatusNotifierItem"); : "org.freedesktop.StatusNotifierItem");
sway_log(L_INFO, "Activating context menu for item: (%s,%s)", item->name, item->object_path);
DBusMessage *message = dbus_message_new_method_call( DBusMessage *message = dbus_message_new_method_call(
item->name, item->name,
"/StatusNotifierItem", item->object_path,
iface, iface,
"ContextMenu"); "ContextMenu");
@ -363,7 +222,7 @@ void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
: "org.freedesktop.StatusNotifierItem"); : "org.freedesktop.StatusNotifierItem");
DBusMessage *message = dbus_message_new_method_call( DBusMessage *message = dbus_message_new_method_call(
item->name, item->name,
"/StatusNotifierItem", item->object_path,
iface, iface,
"SecondaryActivate"); "SecondaryActivate");
@ -426,6 +285,8 @@ struct StatusNotifierItem *sni_create(const char *name) {
struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem)); struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem));
item->name = strdup(name); item->name = strdup(name);
item->unique_name = NULL; item->unique_name = NULL;
// TODO use static str if the default path instead of all these god-damn strdups
item->object_path = strdup("/StatusNotifierItem");
item->image = NULL; item->image = NULL;
item->dirty = false; item->dirty = false;
@ -449,6 +310,21 @@ struct StatusNotifierItem *sni_create(const char *name) {
return item; return item;
} }
struct StatusNotifierItem *sni_create_from_obj_path(const char *unique_name,
const char *object_path) {
struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem));
// XXX strdup-ing twice to avoid a double-free; see above todo
item->name = strdup(unique_name);
item->unique_name = strdup(unique_name);
item->object_path = strdup(object_path);
item->image = NULL;
item->dirty = false;
// If they're registering by obj-path they're a special snowflake
item->kde_special_snowflake = true;
get_icon(item);
return item;
}
/* Return 0 if `item` has a name of `str` */ /* Return 0 if `item` has a name of `str` */
int sni_str_cmp(const void *_item, const void *_str) { int sni_str_cmp(const void *_item, const void *_str) {
const struct StatusNotifierItem *item = _item; const struct StatusNotifierItem *item = _item;
@ -466,14 +342,24 @@ int sni_uniq_cmp(const void *_item, const void *_str) {
} }
return strcmp(item->unique_name, str); return strcmp(item->unique_name, str);
} }
int sni_obj_name_cmp(const void *_item, const void *_obj_name) {
const struct StatusNotifierItem *item = _item;
const struct ObjName *obj_name = _obj_name;
if (strcmp(item->unique_name, obj_name->name) == 0 &&
strcmp(item->object_path, obj_name->obj_path) == 0) {
return 0;
}
return 1;
}
void sni_free(struct StatusNotifierItem *item) { void sni_free(struct StatusNotifierItem *item) {
if (!item) { if (!item) {
return; return;
} }
free(item->name); free(item->name);
if (item->unique_name) { free(item->unique_name);
free(item->unique_name); free(item->object_path);
}
if (item->image) { if (item->image) {
cairo_surface_destroy(item->image); cairo_surface_destroy(item->image);
} }

View file

@ -11,6 +11,7 @@
static list_t *items = NULL; static list_t *items = NULL;
static list_t *hosts = NULL; static list_t *hosts = NULL;
static list_t *object_path_items = NULL;
/** /**
* Describes the function of the StatusNotifierWatcher * Describes the function of the StatusNotifierWatcher
@ -18,6 +19,10 @@ static list_t *hosts = NULL;
* *
* We also implement KDE's special snowflake protocol, it's like this but with * 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. * all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect.
*
* We _also_ support registering items by object path (even though this is a
* huge pain in the ass). Hosts that would like to subscribe to these items have
* to go through the `org.swaywm.LessSuckyStatusNotifierWatcher` interface.
*/ */
static const char *interface_xml = static const char *interface_xml =
"<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'" "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'"
@ -64,8 +69,59 @@ static const char *interface_xml =
" <arg type='' name='service' direction='out'/>" " <arg type='' name='service' direction='out'/>"
" </signal>" " </signal>"
" </interface>" " </interface>"
" <interface name='org.swaywm.LessSuckyStatusNotifierWatcher'>"
" <property name='RegisteredObjectPathItems' type='a(os)' access='read'/>"
" <signal name='ObjPathItemRegistered'>"
" <arg type='os' name='service' direction='out'/>"
" </signal>"
" </interface>"
"</node>"; "</node>";
struct ObjPathItem {
char *obj_path;
char *unique_name;
};
static void free_obj_path_item(struct ObjPathItem *item) {
if (!item) {
return;
}
free(item->unique_name);
free(item->obj_path);
free(item);
}
static struct ObjPathItem *create_obj_path_item(const char *unique_name, const char *obj_path) {
struct ObjPathItem *item = malloc(sizeof(struct ObjPathItem));
if (!item) {
return NULL;
}
item->unique_name = strdup(unique_name);
item->obj_path = strdup(obj_path);
if (!item->unique_name || !item->obj_path) {
free_obj_path_item(item);
return NULL;
}
return item;
}
/**
* NOTE: This compare function does have ordering, this is because it has to
* comapre two strings.
*/
static int obj_path_item_cmp(const void *_item1, const void *_item2) {
const struct ObjPathItem *item1 = _item1;
const struct ObjPathItem *item2 = _item2;
if (strcmp(item1->unique_name,item2->unique_name) == 0 &&
strcmp(item1->obj_path,item2->obj_path) == 0) {
return 0;
}
return -1;
}
static int obj_path_unique_name_cmp(const void *_item, const void *_unique_name) {
const struct ObjPathItem *item = _item;
const char *unique_name = _unique_name;
return strcmp(item->unique_name, unique_name);
}
static void host_registered_signal(DBusConnection *connection) { static void host_registered_signal(DBusConnection *connection) {
// Send one signal for each protocol // Send one signal for each protocol
DBusMessage *signal = dbus_message_new_signal( DBusMessage *signal = dbus_message_new_signal(
@ -128,6 +184,19 @@ static void item_unregistered_signal(DBusConnection *connection, const char *nam
dbus_message_unref(signal); dbus_message_unref(signal);
} }
static void obj_path_item_registered_signal(DBusConnection *connection, const struct ObjPathItem *item) {
DBusMessage *signal = dbus_message_new_signal(
"/StatusNotifierWatcher",
"org.swaywm.LessSuckyStatusNotifierWatcher",
"ObjPathItemRegistered");
dbus_message_append_args(signal,
DBUS_TYPE_OBJECT_PATH, &item->obj_path,
DBUS_TYPE_STRING, &item->unique_name,
DBUS_TYPE_INVALID);
dbus_connection_send(connection, signal, NULL);
dbus_message_unref(signal);
}
static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) { static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) {
DBusMessage *reply; DBusMessage *reply;
@ -141,35 +210,53 @@ static void respond_to_introspect(DBusConnection *connection, DBusMessage *reque
static void register_item(DBusConnection *connection, DBusMessage *message) { static void register_item(DBusConnection *connection, DBusMessage *message) {
DBusError error; DBusError error;
DBusMessage *reply;
char *name; char *name;
dbus_error_init(&error); dbus_error_init(&error);
if (!dbus_message_get_args(message, &error, if (!dbus_message_get_args(message, &error,
DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) { DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error parsing method args: %s\n", error.message); sway_log(L_ERROR, "Error parsing method args: %s", error.message);
} }
sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name); sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"", name);
// Don't add duplicate or not real item // Don't add duplicate or not real item
if (!dbus_validate_bus_name(name, NULL)) { if (!dbus_validate_bus_name(name, NULL)) {
sway_log(L_INFO, "This item is not valid, we cannot keep track of it."); if (dbus_validate_path(name, NULL)) {
return; // Item is registered by object path
struct ObjPathItem *item =
create_obj_path_item(dbus_message_get_sender(message), name);
// Add ObjPathItem
if (list_seq_find(object_path_items, obj_path_item_cmp, item) != -1) {
free_obj_path_item(item);
return;
}
list_add(object_path_items, item);
obj_path_item_registered_signal(connection, item);
goto send_reply;
} else {
sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
return;
}
} else {
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, strdup(name));
item_registered_signal(connection, name);
} }
if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) { send_reply:
return; // It's silly, but clients want a reply for this function
} reply = dbus_message_new_method_return(message);
if (!dbus_bus_name_has_owner(connection, name, &error)) {
return;
}
list_add(items, strdup(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_connection_send(connection, reply, NULL);
dbus_message_unref(reply); dbus_message_unref(reply);
} }
@ -182,10 +269,10 @@ static void register_host(DBusConnection *connection, DBusMessage *message) {
if (!dbus_message_get_args(message, &error, if (!dbus_message_get_args(message, &error,
DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) { DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error parsing method args: %s\n", error.message); sway_log(L_ERROR, "Error parsing method args: %s", error.message);
} }
sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name); sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"", name);
// Don't add duplicate or not real host // Don't add duplicate or not real host
if (!dbus_validate_bus_name(name, NULL)) { if (!dbus_validate_bus_name(name, NULL)) {
@ -215,12 +302,12 @@ static void get_property(DBusConnection *connection, DBusMessage *message) {
DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &interface,
DBUS_TYPE_STRING, &property, DBUS_TYPE_STRING, &property,
DBUS_TYPE_INVALID)) { DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message); sway_log(L_ERROR, "Error parsing prop args: %s", error.message);
return; return;
} }
if (strcmp(property, "RegisteredStatusNotifierItems") == 0) { if (strcmp(property, "RegisteredStatusNotifierItems") == 0) {
sway_log(L_INFO, "Replying with items\n"); sway_log(L_INFO, "Replying with items");
DBusMessage *reply; DBusMessage *reply;
reply = dbus_message_new_method_return(message); reply = dbus_message_new_method_return(message);
DBusMessageIter iter; DBusMessageIter iter;
@ -279,6 +366,41 @@ static void get_property(DBusConnection *connection, DBusMessage *message) {
DBUS_TYPE_INT32, &version); DBUS_TYPE_INT32, &version);
dbus_message_iter_close_container(&iter, &sub); dbus_message_iter_close_container(&iter, &sub);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
} else if (strcmp(property, "RegisteredObjectPathItems") == 0) {
sway_log(L_INFO, "Replying with ObjPathItems");
DBusMessage *reply;
reply = dbus_message_new_method_return(message);
DBusMessageIter iter;
DBusMessageIter variant;
DBusMessageIter array;
DBusMessageIter dstruct;
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
"a(os)", &variant);
dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
"(os)", &array);
for (int i = 0; i < object_path_items->length; ++i) {
struct ObjPathItem *item = object_path_items->items[i];
dbus_message_iter_open_container(&array,
DBUS_TYPE_STRUCT, NULL, &dstruct);
dbus_message_iter_append_basic(&dstruct,
DBUS_TYPE_OBJECT_PATH, &item->obj_path);
dbus_message_iter_append_basic(&dstruct,
DBUS_TYPE_STRING, &item->unique_name);
dbus_message_iter_close_container(&array, &dstruct);
}
dbus_message_iter_close_container(&variant, &array);
dbus_message_iter_close_container(&iter, &variant);
dbus_connection_send(connection, reply, NULL); dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply); dbus_message_unref(reply);
} }
@ -289,6 +411,8 @@ static void set_property(DBusConnection *connection, DBusMessage *message) {
return; return;
} }
// TODO clean me up please or get rid of me
// also add LessSuckyStatusNotifierWatcher props
static void get_all(DBusConnection *connection, DBusMessage *message) { static void get_all(DBusConnection *connection, DBusMessage *message) {
DBusMessage *reply; DBusMessage *reply;
reply = dbus_message_new_method_return(message); reply = dbus_message_new_method_return(message);
@ -400,6 +524,8 @@ static DBusHandlerResult signal_handler(DBusConnection *connection,
const char *old_owner; const char *old_owner;
const char *new_owner; const char *new_owner;
int index; int index;
bool found_obj_path_item = false;
if (!dbus_message_get_args(message, NULL, if (!dbus_message_get_args(message, NULL,
DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &old_owner, DBUS_TYPE_STRING, &old_owner,
@ -427,6 +553,17 @@ static DBusHandlerResult signal_handler(DBusConnection *connection,
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
while ((index = list_seq_find(object_path_items, obj_path_unique_name_cmp, name)) != -1) {
found_obj_path_item = true;
struct ObjPathItem *item = object_path_items->items[index];
sway_log(L_INFO, "ObjPathItem lost %s", item->obj_path);
list_del(object_path_items, index);
free_obj_path_item(item);
}
if (found_obj_path_item) {
item_unregistered_signal(connection, name);
return DBUS_HANDLER_RESULT_HANDLED;
}
} }
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
} }
@ -446,6 +583,7 @@ int init_sni_watcher() {
items = create_list(); items = create_list();
hosts = create_list(); hosts = create_list();
object_path_items = create_list();
int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher", int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher",
DBUS_NAME_FLAG_REPLACE_EXISTING, DBUS_NAME_FLAG_REPLACE_EXISTING,
@ -456,7 +594,7 @@ int init_sni_watcher() {
sway_log(L_INFO, "Could not get watcher name, it may start later"); sway_log(L_INFO, "Could not get watcher name, it may start later");
} }
if (dbus_error_is_set(&error)) { if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message); sway_log(L_ERROR, "dbus err getting watcher name: %s", error.message);
return -1; return -1;
} }
@ -469,7 +607,7 @@ int init_sni_watcher() {
sway_log(L_INFO, "Could not get kde watcher name, it may start later"); sway_log(L_INFO, "Could not get kde watcher name, it may start later");
} }
if (dbus_error_is_set(&error)) { if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message); sway_log(L_ERROR, "dbus err getting kde watcher name: %s", error.message);
return -1; return -1;
} }
@ -477,7 +615,7 @@ int init_sni_watcher() {
"/StatusNotifierWatcher", "/StatusNotifierWatcher",
&vtable, NULL, &error); &vtable, NULL, &error);
if (dbus_error_is_set(&error)) { if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err: %s\n", error.message); sway_log(L_ERROR, "dbus_err: %s", error.message);
return -1; return -1;
} }

View file

@ -38,95 +38,81 @@ static void register_host(char *name) {
dbus_message_unref(message); dbus_message_unref(message);
} }
static void get_items_reply(DBusPendingCall *pending, void *_data) { static void get_items_reply(DBusMessageIter *iter, void *_data, enum property_status status) {
DBusMessage *reply = dbus_pending_call_steal_reply(pending); if (status != PROP_EXISTS) {
return;
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; DBusMessageIter array;
dbus_message_iter_init(reply, &iter); // O(n) function, could be faster dynamically reading values
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { int len = dbus_message_iter_get_element_count(iter);
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;
}
dbus_message_iter_recurse(iter, &array);
for (int i = 0; i < len; i++) {
const char *name;
dbus_message_iter_get_basic(&array, &name);
if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
struct StatusNotifierItem *item = sni_create(name);
if (item) {
sway_log(L_DEBUG, "Item registered with host: %s", name);
list_add(tray->items, item);
dirty = true;
}
}
}
}
static void get_obj_items_reply(DBusMessageIter *iter, void *_data, enum property_status status) {
if (status != PROP_EXISTS) {
return;
}
DBusMessageIter array;
DBusMessageIter dstruct;
int len = dbus_message_iter_get_element_count(iter);
dbus_message_iter_recurse(iter, &array);
for (int i = 0; i < len; i++) {
const char *object_path;
const char *unique_name;
dbus_message_iter_recurse(&array, &dstruct);
dbus_message_iter_get_basic(&dstruct, &object_path);
dbus_message_iter_next(&dstruct);
dbus_message_iter_get_basic(&dstruct, &unique_name);
struct ObjName obj_name = {
object_path,
unique_name,
};
if (list_seq_find(tray->items, sni_obj_name_cmp, &obj_name) == -1) {
struct StatusNotifierItem *item =
sni_create_from_obj_path(unique_name, object_path);
if (item) {
sway_log(L_DEBUG, "Item registered with host: %s", unique_name);
list_add(tray->items, item);
dirty = true;
}
}
}
}
static void get_items() {
// Clear list // Clear list
list_foreach(tray->items, (void (*)(void *))sni_free); list_foreach(tray->items, (void (*)(void *))sni_free);
list_free(tray->items); list_free(tray->items);
tray->items = create_list(); tray->items = create_list();
// O(n) function, could be faster dynamically reading values dbus_get_prop_async("org.freedesktop.StatusNotifierWatcher",
int len = dbus_message_iter_get_element_count(&variant); "/StatusNotifierWatcher","org.freedesktop.StatusNotifierWatcher",
"RegisteredStatusNotifierItems", "as", get_items_reply, NULL);
dbus_message_iter_recurse(&variant, &array); dbus_get_prop_async("org.freedesktop.StatusNotifierWatcher",
for (int i = 0; i < len; i++) { "/StatusNotifierWatcher","org.swaywm.LessSuckyStatusNotifierWatcher",
const char *name; "RegisteredObjectPathItems", "a(os)", get_obj_items_reply, NULL);
dbus_message_iter_get_basic(&array, &name);
struct StatusNotifierItem *item = sni_create(name);
if (item) {
sway_log(L_DEBUG, "Item registered with host: %s", name);
list_add(tray->items, item);
dirty = true;
}
}
bail:
dbus_message_unref(reply);
dbus_pending_call_unref(pending);
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, static DBusHandlerResult signal_handler(DBusConnection *connection,
@ -162,11 +148,14 @@ static DBusHandlerResult signal_handler(DBusConnection *connection,
} }
int index; int index;
if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) { bool found_item = false;
while ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
found_item = true;
sni_free(tray->items->items[index]); sni_free(tray->items->items[index]);
list_del(tray->items, index); list_del(tray->items, index);
dirty = true; dirty = true;
} else { }
if (found_item == false) {
// If it's not in our list, then our list is incorrect. // If it's not in our list, then our list is incorrect.
// Fetch all items again // Fetch all items again
sway_log(L_INFO, "Host item list incorrect, refreshing"); sway_log(L_INFO, "Host item list incorrect, refreshing");
@ -178,16 +167,51 @@ static DBusHandlerResult signal_handler(DBusConnection *connection,
"NewIcon") || dbus_message_is_signal(message, "NewIcon") || dbus_message_is_signal(message,
"org.kde.StatusNotifierItem", "NewIcon")) { "org.kde.StatusNotifierItem", "NewIcon")) {
const char *name; const char *name;
const char *obj_path;
int index; int index;
struct StatusNotifierItem *item; struct StatusNotifierItem *item;
name = dbus_message_get_sender(message); name = dbus_message_get_sender(message);
if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) { obj_path = dbus_message_get_path(message);
struct ObjName obj_name = {
obj_path,
name,
};
if ((index = list_seq_find(tray->items, sni_obj_name_cmp, &obj_name)) != -1) {
item = tray->items->items[index]; item = tray->items->items[index];
sway_log(L_INFO, "NewIcon signal from item %s", item->name); sway_log(L_INFO, "NewIcon signal from item %s", item->name);
get_icon(item); get_icon(item);
} }
return DBUS_HANDLER_RESULT_HANDLED;
} else if (dbus_message_is_signal(message,
"org.swaywm.LessSuckyStatusNotifierWatcher",
"ObjPathItemRegistered")) {
const char *object_path;
const char *unique_name;
if (!dbus_message_get_args(message, NULL,
DBUS_TYPE_OBJECT_PATH, &object_path,
DBUS_TYPE_STRING, &unique_name,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error getting ObjPathItemRegistered args");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
struct ObjName obj_name = {
object_path,
unique_name,
};
if (list_seq_find(tray->items, sni_obj_name_cmp, &obj_name) == -1) {
struct StatusNotifierItem *item =
sni_create_from_obj_path(unique_name,
object_path);
if (item) {
list_add(tray->items, item);
dirty = true;
}
}
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
@ -234,6 +258,9 @@ static int init_host() {
register_host(name); register_host(name);
// Chances are if an item is already running, we'll get it two times.
// Once from this and another time from queued signals. Still we want
// to do this to be a complient sni host just in case.
get_items(); get_items();
// Perhaps use addmatch helper functions like wlc does? // Perhaps use addmatch helper functions like wlc does?
@ -255,6 +282,15 @@ static int init_host() {
sway_log(L_ERROR, "dbus_err: %s", error.message); sway_log(L_ERROR, "dbus_err: %s", error.message);
return -1; return -1;
} }
dbus_bus_add_match(conn,
"type='signal',\
sender='org.freedesktop.StatusNotifierWatcher',\
member='ObjPathItemRegistered'",
&error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err: %s", error.message);
return -1;
}
// SNI matches // SNI matches
dbus_bus_add_match(conn, dbus_bus_add_match(conn,
@ -287,9 +323,13 @@ err:
return -1; return -1;
} }
void tray_mouse_event(struct output *output, int x, int y, void tray_mouse_event(struct output *output, int rel_x, int rel_y,
uint32_t button, uint32_t state) { uint32_t button, uint32_t state) {
int x = rel_x;
int y = rel_y + (swaybar.config->position == DESKTOP_SHELL_PANEL_POSITION_TOP
? 0 : (output->state->height - output->window->height));
struct window *window = output->window; struct window *window = output->window;
uint32_t tray_padding = swaybar.config->tray_padding; uint32_t tray_padding = swaybar.config->tray_padding;
int tray_width = window->width * window->scale; int tray_width = window->width * window->scale;
@ -332,6 +372,24 @@ uint32_t tray_render(struct output *output, struct config *config) {
return tray_width; return tray_width;
} }
bool clean_item = false;
// Clean item if only one output has tray or this is the last output
if (swaybar.outputs->length == 1 || config->tray_output || output->idx == swaybar.outputs->length-1) {
clean_item = true;
// More trickery is needed in case you plug off secondary outputs on live
} else {
int active_outputs = 0;
for (int i = 0; i < swaybar.outputs->length; i++) {
struct output *output = swaybar.outputs->items[i];
if (output->active) {
active_outputs++;
}
}
if (active_outputs == 1) {
clean_item = true;
}
}
for (int i = 0; i < tray->items->length; ++i) { for (int i = 0; i < tray->items->length; ++i) {
struct StatusNotifierItem *item = struct StatusNotifierItem *item =
tray->items->items[i]; tray->items->items[i];
@ -358,6 +416,7 @@ uint32_t tray_render(struct output *output, struct config *config) {
list_add(output->items, render_item); list_add(output->items, render_item);
} else if (item->dirty) { } else if (item->dirty) {
// item needs re-render // item needs re-render
sway_log(L_DEBUG, "Redrawing item %d for output %d", i, output->idx);
sni_icon_ref_free(render_item); sni_icon_ref_free(render_item);
output->items->items[j] = render_item = output->items->items[j] = render_item =
sni_icon_ref_create(item, item_size); sni_icon_ref_create(item, item_size);
@ -373,7 +432,9 @@ uint32_t tray_render(struct output *output, struct config *config) {
cairo_fill(cairo); cairo_fill(cairo);
cairo_set_operator(cairo, op); cairo_set_operator(cairo, op);
item->dirty = false; if (clean_item) {
item->dirty = false;
}
} }