commit fd426d39d83a03a4bf04e2d85f34e2157f6f1450
parent f5e8c4a0a242135a4299f6eee2b6f3636f7c2085
Author: Janne Veteläinen <janne.vetelainen@elisanet.fi>
Date: Tue, 14 May 2024 20:35:35 +0300
Rewrite in gobject-oriented style
Let's us benefit from the refcounting system.
Diffstat:
| M | systray/Makefile | | | 5 | +++-- |
| D | systray/dbusmenu.c | | | 434 | ------------------------------------------------------------------------------- |
| M | systray/dwlbtray.c | | | 107 | ++++++++++++++++++++++++++++++++++++++++++++----------------------------------- |
| D | systray/dwlbtray.h | | | 211 | ------------------------------------------------------------------------------- |
| A | systray/sndbusmenu.c | | | 592 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | systray/sndbusmenu.h | | | 89 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | systray/snhost.c | | | 546 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | systray/snhost.h | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| A | systray/snitem.c | | | 754 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | systray/snitem.h | | | 78 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| D | systray/statusnotifierhost.c | | | 431 | ------------------------------------------------------------------------------- |
| D | systray/statusnotifieritem.c | | | 441 | ------------------------------------------------------------------------------- |
12 files changed, 2166 insertions(+), 1567 deletions(-)
diff --git a/systray/Makefile b/systray/Makefile
@@ -1,5 +1,6 @@
BINS = dwlbtray
-OBJS = statusnotifierhost.o statusnotifieritem.o dbusmenu.o dwlbtray.o
+OBJS = dwlbtray.o snhost.o snitem.o sndbusmenu.o
+DEPS = snhost.h snitem.h sndbusmenu.h
PREFIX ?= /usr/local
CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -g
@@ -12,7 +13,7 @@ clean:
install: all
install -D -t $(PREFIX)/bin $(BINS)
-%.o: dwlbtray.h
+%.o: $(DEPS)
dwlbtray: $(OBJS)
dwlbtray: CFLAGS+=$(shell pkg-config --cflags glib-2.0 gtk4 gtk4-layer-shell-0)
diff --git a/systray/dbusmenu.c b/systray/dbusmenu.c
@@ -1,434 +0,0 @@
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <time.h>
-
-#include <glib.h>
-#include <glib-object.h>
-#include <gio/gio.h>
-#include <gtk/gtk.h>
-
-#include "dwlbtray.h"
-
-
-static GMenu* create_menumodel(GVariant *data, StatusNotifierItem *snitem);
-
-
-static void
-action_activated_cb(GSimpleAction *action, GVariant* param, ActionCallbackData *data)
-{
- g_dbus_proxy_call(data->proxy,
- "Event",
- g_variant_new("(isvu)",
- data->id,
- "clicked",
- g_variant_new_string(""),
- time(NULL)),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- NULL,
- NULL,
- NULL);
-}
-
-
-static void
-actioncbdata_finalize(ActionCallbackData *data, GClosure *closure)
-{
- g_free(data);
- data = NULL;
-}
-
-
-static GSimpleAction*
-create_action(uint32_t id, StatusNotifierItem *snitem)
-{
- char *action_name = g_strdup_printf("%u", id);
- GSimpleAction *action = g_simple_action_new(action_name, NULL);
-
- ActionCallbackData *data = g_malloc(sizeof(ActionCallbackData));
- data->id = id;
- data->proxy = snitem->menuproxy;
-
- g_signal_connect_data(action,
- "activate",
- G_CALLBACK(action_activated_cb),
- data,
- (GClosureNotify)actioncbdata_finalize,
- G_CONNECT_DEFAULT);
-
- g_free(action_name);
-
- return action;
-}
-
-
-static gboolean
-check_has_sections(GVariant *data)
-{
- gboolean ret = FALSE;
- char *val;
- GVariant *menu_data;
-
- GVariantIter iter;
- g_variant_iter_init(&iter, data);
- while ((g_variant_iter_next(&iter, "v", &menu_data))) {
- GVariant *menuitem_data = g_variant_get_child_value(menu_data, 1);
- gboolean check = g_variant_lookup(menuitem_data, "type", "&s", &val);
- if (check && strcmp(val, "separator") == 0)
- ret = TRUE;
- g_variant_unref(menuitem_data);
- g_variant_unref(menu_data);
- }
-
- return ret;
-}
-
-
-static gboolean
-check_menuitem_visible(GVariant *data)
-{
- gboolean isvisible = TRUE;
- GVariant *menu_data = g_variant_get_child_value(data, 1);
- g_variant_lookup(menu_data, "visible", "b", &isvisible);
-
- g_variant_unref(menu_data);
-
- return isvisible;
-}
-
-
-static GMenuItem*
-create_menuitem(GVariant *data, StatusNotifierItem *snitem)
-{
- // (ia{sv}av)
- // GVariant *data
- GMenuItem *menuitem = NULL;
-
- char *actiongroupname = g_strdelimit(g_strdup(snitem->busname), ".", '_');
- actiongroupname = g_strdelimit(actiongroupname, ":", '_');
-
- int32_t id;
- // a{sv]
- GVariant *menu_data;
-
- g_variant_get_child(data, 0, "i", &id);
- menu_data = g_variant_get_child_value(data, 1);
-
- const char *label = NULL;
- const char *type = NULL;
- gboolean isenabled = TRUE;
- gboolean isvisible = TRUE;
- gboolean has_submenu = FALSE;
-
- /*
- * gboolean ischeckmark = FALSE;
- * gboolean isradio = FALSE;
- * int32_t toggle_state = 99;
- * const char *toggle_type = NULL;
- */
-
- const char *has_submenu_s = NULL;
- GVariantDict dict;
- g_variant_dict_init(&dict, menu_data);
- g_variant_dict_lookup(&dict, "label", "&s", &label);
- g_variant_dict_lookup(&dict, "type", "&s", &type);
- g_variant_dict_lookup(&dict, "enabled", "b", &isenabled);
- g_variant_dict_lookup(&dict, "visible", "b", &isvisible);
- g_variant_dict_lookup(&dict, "children-display", "&s", &has_submenu_s);
-
- /*
- * g_variant_dict_lookup(&dict, "toggle-type", "&s", &toggle_type);
- * g_variant_dict_lookup(&dict, "toggle-state", "i", &toggle_state);
- */
-
- g_variant_dict_clear(&dict);
-
- if (has_submenu_s && strcmp(has_submenu_s, "submenu") == 0)
- has_submenu = TRUE;
-
- /*
- * if (toggle_type && strcmp(toggle_type, "checkmark") == 0)
- * ischeckmark = TRUE;
- * else if (toggle_type && strcmp(toggle_type, "radio") == 0)
- * isradio = TRUE;
- */
-
- if ((label && isvisible && isenabled) && !(type && strcmp(type, "separator") == 0)) {
- GSimpleAction *action = create_action(id, snitem);
- char *action_name = g_strdup_printf("%s.%u", actiongroupname, id);
- g_action_map_add_action(G_ACTION_MAP(snitem->actiongroup),
- G_ACTION(action));
- menuitem = g_menu_item_new(label, action_name);
-
- g_free(action_name);
- g_object_unref(action);
-
- } else if ((label && !(type && strcmp(type, "separator") == 0))) {
- GSimpleAction *action = create_action(id, snitem);
- g_simple_action_set_enabled(action, FALSE);
- char *action_name = g_strdup_printf("%s.%u", actiongroupname, id);
- g_action_map_add_action(G_ACTION_MAP(snitem->actiongroup),
- G_ACTION(action));
- menuitem = g_menu_item_new(label, action_name);
-
- g_free(action_name);
- g_object_unref(action);
- }
-
- if (has_submenu) {
- GVariant *submenu_data = g_variant_get_child_value(data, 2);
- GMenu *submenu = create_menumodel(submenu_data, snitem);
- g_menu_item_set_submenu(menuitem, G_MENU_MODEL(submenu));
- g_object_unref(submenu);
- g_variant_unref(submenu_data);
- }
-
- g_variant_unref(menu_data);
- g_free(actiongroupname);
-
- return menuitem;
-}
-
-
-static GMenu*
-create_menumodel(GVariant *data, StatusNotifierItem *snitem)
-{
- GMenu *ret = g_menu_new();
- GVariantIter iter;
- GVariant *menuitem_data;
- gboolean has_sections = check_has_sections(data);
-
- if (has_sections) {
- GMenu *section = g_menu_new();
- g_variant_iter_init(&iter, data);
- while ((g_variant_iter_next(&iter, "v", &menuitem_data))) {
- if (!check_menuitem_visible(menuitem_data)) {
- g_variant_unref(menuitem_data);
- continue;
- }
-
- GMenuItem *menuitem = create_menuitem(menuitem_data, snitem);
- if (menuitem) {
- g_menu_append_item(section, menuitem);
- g_object_unref(menuitem);
- }
- // menuitem == NULL means menuitem is a separator
- else {
- g_menu_append_section(ret, NULL, G_MENU_MODEL(section));
- g_object_unref(section);
- section = g_menu_new();
- }
- g_variant_unref(menuitem_data);
- }
- g_menu_append_section(ret, NULL, G_MENU_MODEL(section));
- g_object_unref(section);
-
- } else {
- g_variant_iter_init(&iter, data);
- while ((g_variant_iter_next(&iter, "v", &menuitem_data))) {
- GMenuItem *menuitem = create_menuitem(menuitem_data, snitem);
- if (menuitem) {
- g_menu_append_item(ret, menuitem);
- g_object_unref(menuitem);
- }
- g_variant_unref(menuitem_data);
- }
- }
-
- return ret;
-}
-
-
-static void
-on_menulayout_ready(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem)
-{
- GError *err = NULL;
- GVariant *data = g_dbus_proxy_call_finish(proxy, res, &err);
- // (u(ia{sv}av))
-
- // "No such object path '/NO_DBUSMENU'"
- // generated by QBittorrent when it sends a broken trayitem on startup
- // and replaces it later
- if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) {
- g_error_free(err);
- g_bit_unlock(&snitem->lock, 0);
- return;
- } else if (err) {
- g_warning("%s\n", err->message);
- g_error_free(err);
- g_bit_unlock(&snitem->lock, 0);
- return;
- }
-
- uint32_t revision = 0;
- GVariant *layout;
- GVariant *menuitems;
-
- g_variant_get_child(data, 0, "u", &revision);
-
- layout = g_variant_get_child_value(data, 1);
- menuitems = g_variant_get_child_value(layout, 2);
-
- GMenu *menu = create_menumodel(menuitems, snitem);
- GtkWidget *popovermenu = gtk_popover_menu_new_from_model_full(G_MENU_MODEL(menu), GTK_POPOVER_MENU_NESTED);
- gtk_popover_set_has_arrow(GTK_POPOVER(popovermenu), FALSE);
- gtk_widget_set_parent(popovermenu, snitem->host->box);
-
- snitem->popovermenu = popovermenu;
-
- g_object_unref(menu);
- g_variant_unref(menuitems);
- g_variant_unref(layout);
- g_variant_unref(data);
-
- g_bit_unlock(&snitem->lock, 0);
-}
-
-
-static void
-on_layout_updated(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem)
-{
- GError *err = NULL;
- GVariant *data = g_dbus_proxy_call_finish(proxy, res, &err);
-
- // Errors which might occur when the tray is running slowly (eg under valgrind)
- // and user is spam clicking already exited icons
-
- // "No such object path '/MenuBar'
- if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) {
- g_error_free(err);
- return;
-
- // "The name is not activatable"
- } else if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
- g_error_free(err);
- return;
-
- // "Remote peer disconnected"
- } else if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY)) {
- g_error_free(err);
- return;
-
- } else if (err) {
- g_warning("%s\n", err->message);
- g_error_free(err);
- return;
- }
-
- GVariant *layout;
- GVariant *menuitems;
-
- layout = g_variant_get_child_value(data, 1);
- menuitems = g_variant_get_child_value(layout, 2);
-
-
- if (snitem && snitem->icon && GTK_IS_WIDGET(snitem->icon) &&
- snitem->popovermenu && GTK_IS_WIDGET(snitem->popovermenu)) {
- GMenu *newmenu = create_menumodel(menuitems, snitem);
-
- gtk_popover_menu_set_menu_model(GTK_POPOVER_MENU(snitem->popovermenu), G_MENU_MODEL(newmenu));
- }
-
- g_variant_unref(menuitems);
- g_variant_unref(layout);
- g_variant_unref(data);
-}
-
-
-// We just rebuild the entire menu every time...
-// ItemsPropertiesUpdated signal would carry data of
-// which menuitems got updated and which got removed
-// but the GMenu api doesn't allow easy manipulation as it is
-static void
-on_menuproxy_signal(GDBusProxy *proxy,
- const char *sender,
- const char *signal,
- GVariant *params,
- StatusNotifierItem *snitem)
-{
- if (strcmp(signal, "LayoutUpdated") == 0) {
- uint32_t revision = UINT32_MAX;
- int32_t parentid;
- g_variant_get(params, "(ui)", &revision, &parentid);
- if (snitem->menurevision != UINT32_MAX && revision <= snitem->menurevision) {
- // g_debug("%s got %s, but menurevision didn't change. Ignoring\n", snitem->busname, signal);
- return;
- } else if (!snitem || !snitem->icon || !GTK_IS_WIDGET(snitem->icon) ||
- !snitem->popovermenu || !GTK_IS_WIDGET(snitem->popovermenu)) {
- // g_debug("%s got %s, but menu was already in destruction. Ignoring\n", snitem->busname, signal);
- return;
- } else {
- snitem->menurevision = revision;
- }
-
- g_dbus_proxy_call(snitem->menuproxy,
- "GetLayout",
- g_variant_new("(iias)", 0, -1, NULL),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- NULL,
- (GAsyncReadyCallback)on_layout_updated,
- snitem);
-
- } else if (strcmp(signal, "ItemsPropertiesUpdated") == 0) {
- if (!snitem || !snitem->icon || !GTK_IS_WIDGET(snitem->icon) ||
- !snitem->popovermenu || !GTK_IS_WIDGET(snitem->popovermenu)) {
- // g_debug("Menu was already in destruction\n");
- return;
- }
- g_dbus_proxy_call(snitem->menuproxy,
- "GetLayout",
- g_variant_new("(iias)", 0, -1, NULL),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- NULL,
- (GAsyncReadyCallback)on_layout_updated,
- snitem);
- }
-}
-
-
-void
-create_menu(GObject *obj, GAsyncResult *res, StatusNotifierItem *snitem)
-{
- GError *err = NULL;
- GDBusProxy *proxy = g_dbus_proxy_new_for_bus_finish(res, &err);
- snitem->menuproxy = proxy;
- snitem->menurevision = UINT32_MAX;
-
- GSimpleActionGroup *actiongroup;
-
- char *actiongroupname = g_strdelimit(g_strdup(snitem->busname), ".", '_');
- actiongroupname = g_strdelimit(actiongroupname, ":", '_');
-
- actiongroup = g_simple_action_group_new();
- gtk_widget_insert_action_group(snitem->host->box,
- actiongroupname,
- G_ACTION_GROUP(actiongroup));
- snitem->actiongroup = actiongroup;
-
- g_free(actiongroupname);
-
-
- if (err) {
- g_warning("%s\n", err->message);
- g_error_free(err);
- g_bit_unlock(&snitem->lock, 0);
- return;
- }
-
- g_dbus_proxy_call(proxy,
- "GetLayout",
- g_variant_new ("(iias)", 0, -1, NULL),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- NULL,
- (GAsyncReadyCallback)on_menulayout_ready,
- snitem);
-
- g_signal_connect(proxy,
- "g-signal",
- G_CALLBACK (on_menuproxy_signal),
- snitem);
-}
diff --git a/systray/dwlbtray.c b/systray/dwlbtray.c
@@ -8,34 +8,49 @@
#include <gtk/gtk.h>
#include <gtk4-layer-shell.h>
-#include "dwlbtray.h"
+#include "snhost.h"
+typedef struct args_parsed {
+ int barheight;
+ char traymon[1024];
+ char cssdata[1024];
+ int position;
+} args_parsed;
+
+static int margin = 4;
+static int spacing = 4;
static void
-activate(GtkApplication* app, StatusNotifierHost *snhost)
+activate(GtkApplication* app, void *data)
{
+ args_parsed *args = (args_parsed*)data;
+
GdkDisplay *display = gdk_display_get_default();
+ int iconsize, win_default_width, win_default_height;
+
+ iconsize = args->barheight - 2 * margin;
+ win_default_width = args->barheight;
+ win_default_height = args->barheight;
+
GtkWindow *window = GTK_WINDOW(gtk_window_new());
gtk_window_set_decorated(window, FALSE);
- gtk_window_set_default_size(window, 22, snhost->height);
+ gtk_window_set_default_size(window, win_default_width, win_default_height);
gtk_window_set_application(window, app);
GtkCssProvider *css = gtk_css_provider_new();
- gtk_css_provider_load_from_string(css, snhost->cssdata);
+ gtk_css_provider_load_from_string(css, args->cssdata);
gtk_style_context_add_provider_for_display(display,
GTK_STYLE_PROVIDER(css),
GTK_STYLE_PROVIDER_PRIORITY_USER);
gtk_widget_add_css_class(GTK_WIDGET(window), "dwlbtray");
- g_free(snhost->cssdata);
- snhost->cssdata = NULL;
-
+ g_object_unref(css);
gtk_layer_init_for_window(window);
gtk_layer_set_layer(window, GTK_LAYER_SHELL_LAYER_BOTTOM);
gtk_layer_set_exclusive_zone(window, -1);
static gboolean anchors[] = {FALSE, TRUE, TRUE, FALSE};
- if (snhost->position == 1) {
+ if (args->position == 1) {
anchors[0] = FALSE; // left
anchors[1] = TRUE; // right
anchors[2] = FALSE; // top
@@ -45,67 +60,66 @@ activate(GtkApplication* app, StatusNotifierHost *snhost)
gtk_layer_set_anchor(window, i, anchors[i]);
}
- if (snhost->traymon) {
- GListModel *mons = gdk_display_get_monitors(display);
+ const char *traymon = NULL;
+
+ if (strcmp(args->traymon, "") != 0) {
+ traymon = args->traymon;
+ }
+
+ GListModel *mons = gdk_display_get_monitors(display);
+ if (traymon) {
for (uint i = 0; i < g_list_model_get_n_items(mons); i++) {
GdkMonitor *mon = g_list_model_get_item(mons, i);
const char *conn = gdk_monitor_get_connector(mon);
- if (strcmp(conn, snhost->traymon) == 0) {
+ if (strcmp(conn, traymon) == 0) {
gtk_layer_set_monitor(window, mon);
}
}
+ } else {
+ GdkMonitor *mon = g_list_model_get_item(mons, 0);
+ const char *conn = gdk_monitor_get_connector(mon);
+ traymon = conn;
+ gtk_layer_set_monitor(window, mon);
}
- GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
- gtk_widget_set_vexpand(box, TRUE);
- gtk_widget_set_hexpand(box, TRUE);
- gtk_widget_set_margin_start(box, snhost->margin);
- gtk_widget_set_margin_end(box, snhost->margin);
- gtk_window_set_child(window, box);
- dwlb_request_resize(snhost);
- gtk_window_present(window);
+ SnHost *host = sn_host_new(traymon, iconsize, margin, spacing);
+ GtkWidget *widget = GTK_WIDGET(host);
+ gtk_window_set_child(window, widget);
- snhost->box = box;
- snhost->window = window;
+ dwlb_request_resize(host);
+ gtk_window_present(window);
}
static gboolean
-terminate_app(StatusNotifierHost *snhost)
+terminate_app(GtkApplication *app)
{
- terminate_statusnotifierhost(snhost);
+ GtkWindow *win = gtk_application_get_active_window(app);
+ gtk_window_close(win);
return G_SOURCE_REMOVE;
}
-
int
main(int argc, char *argv[])
{
-
- StatusNotifierHost snhost;
- snhost.height = 22;
- snhost.curwidth = 22;
- snhost.margin = 4;
- snhost.noitems = 0;
- snhost.position = 0;
- snhost.cssdata = NULL;
- snhost.traymon = NULL;
- snhost.trayitems = NULL;
- snhost.in_exit = FALSE;
+ args_parsed args;
+ args.barheight = 22;
+ args.position = 0;
+ *args.traymon = '\0';
+ *args.cssdata = '\0';
char *bgcolor = NULL;
- char *cssdata;
int position = 0;
int i = 1;
for (; i < argc; i++) {
char **strings = g_strsplit(argv[i], "=", 0);
if (strcmp(strings[0], "--height") == 0) {
- snhost.height = atoi(strings[1]);
+ args.barheight = atoi(strings[1]);
} else if (strcmp(strings[0], "--traymon") == 0) {
- snhost.traymon = g_strdup(strings[1]);
+ strncpy(args.traymon, strings[1], sizeof(args.traymon));
} else if (strcmp(strings[0], "--bg-color") == 0) {
bgcolor = strdup(strings[1]);
} else if (strcmp(strings[0], "--position") == 0) {
@@ -115,28 +129,25 @@ main(int argc, char *argv[])
g_strfreev(strings);
}
- snhost.position = position;
+ args.position = position;
if (bgcolor)
- cssdata = g_strdup_printf("window.dwlbtray{background-color:%s;}", bgcolor);
+ snprintf(args.cssdata, sizeof(args.cssdata), "window.dwlbtray{background-color:%s;}", bgcolor);
else
- cssdata = g_strdup_printf("window.dwlbtray{background-color:%s;}", "#222222");
-
- snhost.cssdata = cssdata;
- g_free(bgcolor);
+ snprintf(args.cssdata, sizeof(args.cssdata), "window.dwlbtray{background-color:%s;}", "#222222");
GtkApplication *app = gtk_application_new("org.dwlb.dwlbtray",
G_APPLICATION_DEFAULT_FLAGS);
- g_signal_connect(app, "activate", G_CALLBACK(activate), &snhost);
+ g_signal_connect(app, "activate", G_CALLBACK(activate), &args);
- g_unix_signal_add(SIGINT, (GSourceFunc)terminate_app, &snhost);
- g_unix_signal_add(SIGTERM, (GSourceFunc)terminate_app, &snhost);
+ g_unix_signal_add(SIGINT, (GSourceFunc)terminate_app, app);
+ g_unix_signal_add(SIGTERM, (GSourceFunc)terminate_app, app);
- start_statusnotifierhost(&snhost);
char *argv_inner[] = { argv[0], NULL };
int status = g_application_run(G_APPLICATION(app), 1, argv_inner);
g_object_unref(app);
+
return status;
}
diff --git a/systray/dwlbtray.h b/systray/dwlbtray.h
@@ -1,211 +0,0 @@
-#ifndef DWLBTRAY_H
-#define DWLBTRAY_H
-
-#include <stdint.h>
-
-#include <glib.h>
-#include <glib-object.h>
-#include <gio/gio.h>
-#include <gtk/gtk.h>
-
-typedef struct {
- uint32_t id;
- GDBusProxy* proxy;
-} ActionCallbackData;
-
-
-typedef struct StatusNotifierHost {
- GDBusConnection *conn;
- GSList *trayitems;
- GtkWidget *box;
- GtkWindow *window;
- char *cssdata;
- char *traymon;
- gboolean in_exit;
- int position;
- int curwidth;
- int height;
- int margin;
- int noitems;
- int obj_id;
- int owner_id;
- int sub_id;
-} StatusNotifierHost;
-
-
-typedef struct StatusNotifierItem {
- GSimpleActionGroup *actiongroup;
- GDBusProxy *menuproxy;
- GDBusProxy *proxy;
- GVariant *iconpixmap_v;
- GdkPaintable *paintable;
- GtkWidget *icon;
- GtkWidget *popovermenu;
- StatusNotifierHost *host;
- char *busname;
- char *iconname;
- gboolean isclosing;
- uint32_t menurevision;
- int lock;
-} StatusNotifierItem;
-
-
-void create_trayitem(GObject *obj, GAsyncResult *res, StatusNotifierItem *snitem);
-void create_menu(GObject *obj, GAsyncResult *res, StatusNotifierItem *snitem);
-//StatusNotifierHost* start_statusnotifierhost();
-void start_statusnotifierhost(StatusNotifierHost *snhost);
-void dwlb_request_resize(StatusNotifierHost *snhost);
-void terminate_statusnotifierhost(StatusNotifierHost *snhost);
-
-
-#define DBUSMENU_XML \
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
- "<node>\n" \
- " <interface name=\"com.canonical.dbusmenu\">\n" \
- " <!-- methods -->\n" \
- " <method name=\"GetLayout\">\n" \
- " <arg type=\"i\" name=\"parentId\" direction=\"in\"/>\n" \
- " <arg type=\"i\" name=\"recursionDepth\" direction=\"in\"/>\n" \
- " <arg type=\"as\" name=\"propertyNames\" direction=\"in\"/>\n" \
- " <arg type=\"u\" name=\"revision\" direction=\"out\"/>\n" \
- " <arg type=\"(ia{sv}av)\" name=\"layout\" direction=\"out\"/>\n" \
- " </method>\n" \
- " <method name=\"Event\">\n" \
- " <arg type=\"i\" name=\"id\" direction=\"in\"/>\n" \
- " <arg type=\"s\" name=\"eventId\" direction=\"in\"/>\n" \
- " <arg type=\"v\" name=\"data\" direction=\"in\"/>\n" \
- " <arg type=\"u\" name=\"timestamp\" direction=\"in\"/>\n" \
- " </method>\n" \
- " <method name=\"AboutToShow\">\n" \
- " <arg type=\"i\" name=\"id\" direction=\"in\"/>\n" \
- " <arg type=\"b\" name=\"needUpdate\" direction=\"out\"/>\n" \
- " </method>\n" \
- " <!--\n" \
- " <method name=\"AboutToShowGroup\">\n" \
- " <arg type=\"ai\" name=\"ids\" direction=\"in\"/>\n" \
- " <arg type=\"ai\" name=\"updatesNeeded\" direction=\"out\"/>\n" \
- " <arg type=\"ai\" name=\"idErrors\" direction=\"out\"/>\n" \
- " </method>\n" \
- " <method name=\"GetGroupProperties\">\n" \
- " <arg type=\"ai\" name=\"ids\" direction=\"in\"/>\n" \
- " <arg type=\"as\" name=\"propertyNames\" direction=\"in\"/>\n" \
- " <arg type=\"a(ia{sv})\" name=\"properties\" direction=\"out\"/>\n" \
- " </method>\n" \
- " <method name=\"GetProperty\">\n" \
- " <arg type=\"i\" name=\"id\" direction=\"in\"/>\n" \
- " <arg type=\"s\" name=\"name\" direction=\"in\"/>\n" \
- " <arg type=\"v\" name=\"value\" direction=\"out\"/>\n" \
- " </method>\n" \
- " <method name=\"EventGroup\">\n" \
- " <arg type=\"a(isvu)\" name=\"events\" direction=\"in\"/>\n" \
- " <arg type=\"ai\" name=\"idErrors\" direction=\"out\"/>\n" \
- " </method>\n" \
- " -->\n" \
- " <!-- properties -->\n" \
- " <!--\n" \
- " <property name=\"Version\" type=\"u\" access=\"read\"/>\n" \
- " <property name=\"TextDirection\" type=\"s\" access=\"read\"/>\n" \
- " <property name=\"Status\" type=\"s\" access=\"read\"/>\n" \
- " <property name=\"IconThemePath\" type=\"as\" access=\"read\"/>\n" \
- " -->\n" \
- " <!-- Signals -->\n" \
- " <signal name=\"ItemsPropertiesUpdated\">\n" \
- " <arg type=\"a(ia{sv})\" name=\"updatedProps\" direction=\"out\"/>\n" \
- " <arg type=\"a(ias)\" name=\"removedProps\" direction=\"out\"/>\n" \
- " </signal>\n" \
- " <signal name=\"LayoutUpdated\">\n" \
- " <arg type=\"u\" name=\"revision\" direction=\"out\"/>\n" \
- " <arg type=\"i\" name=\"parent\" direction=\"out\"/>\n" \
- " </signal>\n" \
- " <!--\n" \
- " <signal name=\"ItemActivationRequested\">\n" \
- " <arg type=\"i\" name=\"id\" direction=\"out\"/>\n" \
- " <arg type=\"u\" name=\"timestamp\" direction=\"out\"/>\n" \
- " </signal>\n" \
- " -->\n" \
- " </interface>\n" \
- "</node>\n"
-
-
-#define STATUSNOTIFIERITEM_XML \
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
- "<node>\n" \
- " <interface name=\"org.kde.StatusNotifierItem\">\n" \
- " <!-- methods -->\n" \
- " <method name=\"Activate\">\n" \
- " <arg name=\"x\" type=\"i\" direction=\"in\"/>\n" \
- " <arg name=\"y\" type=\"i\" direction=\"in\"/>\n" \
- " </method>\n" \
- " <!--\n" \
- " <method name=\"Scroll\">\n" \
- " <arg name=\"delta\" type=\"i\" direction=\"in\"/>\n" \
- " <arg name=\"orientation\" type=\"s\" direction=\"in\"/>\n" \
- " </method>\n" \
- " <method name=\"ContextMenu\">\n" \
- " <arg name=\"x\" type=\"i\" direction=\"in\"/>\n" \
- " <arg name=\"y\" type=\"i\" direction=\"in\"/>\n" \
- " </method>\n" \
- " <method name=\"SecondaryActivate\">\n" \
- " <arg name=\"x\" type=\"i\" direction=\"in\"/>\n" \
- " <arg name=\"y\" type=\"i\" direction=\"in\"/>\n" \
- " </method>\n" \
- " -->\n" \
- " <!-- properties -->\n" \
- " <property name=\"Menu\" type=\"o\" access=\"read\"/>\n" \
- " <property name=\"IconName\" type=\"s\" access=\"read\"/>\n" \
- " <property name=\"IconPixmap\" type=\"a(iiay)\" access=\"read\"/>\n" \
- " <property name=\"IconThemePath\" type=\"s\" access=\"read\"/>\n" \
- " <!--\n" \
- " <property name=\"OverlayIconName\" type=\"s\" access=\"read\"/>\n" \
- " <property name=\"OverlayIconPixmap\" type=\"a(iiay)\" access=\"read\"/>\n" \
- " <property name=\"AttentionIconName\" type=\"s\" access=\"read\"/>\n" \
- " <property name=\"AttentionIconPixmap\" type=\"a(iiay)\" access=\"read\"/>\n" \
- " <property name=\"Category\" type=\"s\" access=\"read\"/>\n" \
- " <property name=\"Id\" type=\"s\" access=\"read\"/>\n" \
- " <property name=\"Title\" type=\"s\" access=\"read\"/>\n" \
- " <property name=\"Status\" type=\"s\" access=\"read\"/>\n" \
- " <property name=\"WindowId\" type=\"i\" access=\"read\"/>\n" \
- " <property name=\"ItemIsMenu\" type=\"b\" access=\"read\"/>\n" \
- " <property name=\"AttentionMovieName\" type=\"s\" access=\"read\"/>\n" \
- " <property name=\"ToolTip\" type=\"(sa(iiay)ss)\" access=\"read\"/>\n" \
- " -->\n" \
- " <!-- signals -->\n" \
- " <signal name=\"NewIcon\"/>\n" \
- " <!--\n" \
- " <signal name=\"NewAttentionIcon\"/>\n" \
- " <signal name=\"NewOverlayIcon\"/>\n" \
- " <signal name=\"NewTitle\"/>\n" \
- " <signal name=\"NewToolTip\"/>\n" \
- " <signal name=\"NewStatus\">\n" \
- " <arg name=\"status\" type=\"s\"/>\n" \
- " </signal>\n" \
- " -->\n" \
- " </interface>\n" \
- "</node>\n"
-
-
-#define STATUSNOTIFIERWATCHER_XML \
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
- "<node>\n" \
- " <interface name=\"org.kde.StatusNotifierWatcher\">\n" \
- " <!-- methods -->\n" \
- " <method name=\"RegisterStatusNotifierItem\">\n" \
- " <arg name=\"service\" type=\"s\" direction=\"in\" />\n" \
- " </method>\n" \
- " <!-- properties -->\n" \
- " <property name=\"RegisteredStatusNotifierItems\" type=\"as\" access=\"read\" />\n" \
- " <property name=\"IsStatusNotifierHostRegistered\" type=\"b\" access=\"read\" />\n" \
- " <property name=\"ProtocolVersion\" type=\"i\" access=\"read\" />\n" \
- " <!-- signals -->\n" \
- " <signal name=\"StatusNotifierItemRegistered\">\n" \
- " <arg type=\"s\"/>\n" \
- " </signal>\n" \
- " <signal name=\"StatusNotifierItemUnregistered\">\n" \
- " <arg type=\"s\"/>\n" \
- " </signal>\n" \
- " <signal name=\"StatusNotifierHostRegistered\">\n" \
- " </signal>\n" \
- " </interface>\n" \
- "</node>\n"
-
-#endif /* DWLBTRAY_H */
diff --git a/systray/sndbusmenu.c b/systray/sndbusmenu.c
@@ -0,0 +1,592 @@
+#include <stdint.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "snitem.h"
+#include "sndbusmenu.h"
+
+
+struct _SnDbusmenu {
+ GObject parent_instance;
+
+ char *busname;
+ char *busobj;
+ SnItem *snitem;
+
+ GDBusProxy *proxy;
+
+ uint32_t revision;
+};
+
+G_DEFINE_FINAL_TYPE(SnDbusmenu, sn_dbusmenu, G_TYPE_OBJECT)
+
+enum
+{
+ PROP_BUSNAME=1,
+ PROP_BUSOBJ,
+ PROP_SNITEM,
+ PROP_PROXY,
+ N_PROPERTIES
+};
+
+static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
+
+
+typedef struct {
+ uint32_t id;
+ GDBusProxy* proxy;
+} ActionCallbackData;
+
+static void sn_dbusmenu_constructed (GObject *object);
+static void sn_dbusmenu_dispose (GObject *object);
+static void sn_dbusmenu_finalize (GObject *object);
+
+static void sn_dbusmenu_get_property (GObject *object,
+ uint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void sn_dbusmenu_set_property (GObject *object,
+ uint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static GMenu* create_menumodel (GVariant *data, SnDbusmenu *self);
+
+
+static gboolean
+check_menuitem_visible(GVariant *data)
+{
+ gboolean isvisible = TRUE;
+ GVariant *menu_data = g_variant_get_child_value(data, 1);
+ g_variant_lookup(menu_data, "visible", "b", &isvisible);
+
+ g_variant_unref(menu_data);
+
+ return isvisible;
+}
+
+static gboolean
+check_has_sections(GVariant *data)
+{
+ gboolean ret = FALSE;
+ char *val;
+ GVariant *menu_data;
+
+ GVariantIter iter;
+ g_variant_iter_init(&iter, data);
+ while ((g_variant_iter_next(&iter, "v", &menu_data))) {
+ GVariant *menuitem_data = g_variant_get_child_value(menu_data, 1);
+ gboolean check = g_variant_lookup(menuitem_data, "type", "&s", &val);
+ if (check && strcmp(val, "separator") == 0)
+ ret = TRUE;
+ g_variant_unref(menuitem_data);
+ g_variant_unref(menu_data);
+ }
+
+ return ret;
+}
+
+static void
+action_activated_cb(GSimpleAction *action, GVariant* param, ActionCallbackData *data)
+{
+ g_dbus_proxy_call(data->proxy,
+ "Event",
+ g_variant_new("(isvu)",
+ data->id,
+ "clicked",
+ g_variant_new_string(""),
+ time(NULL)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL,
+ NULL);
+}
+
+static void
+action_free(void *data, GClosure *closure)
+{
+ ActionCallbackData *acbd = (ActionCallbackData *)data;
+ g_free(acbd);
+}
+
+static GSimpleAction*
+create_action(uint32_t id, SnDbusmenu *self)
+{
+ char *action_name = g_strdup_printf("%u", id);
+ GSimpleAction *action = g_simple_action_new(action_name, NULL);
+
+ ActionCallbackData *data = g_malloc(sizeof(ActionCallbackData));
+ data->id = id;
+ data->proxy = self->proxy;
+
+ g_signal_connect_data(action,
+ "activate",
+ G_CALLBACK(action_activated_cb),
+ data,
+ action_free,
+ G_CONNECT_DEFAULT);
+
+ g_free(action_name);
+
+ return action;
+}
+
+static GMenuItem*
+create_menuitem(GVariant *data, SnDbusmenu *self)
+{
+ // (ia{sv}av)
+ // GVariant *data
+ GMenuItem *menuitem = NULL;
+
+ int32_t id;
+ // a{sv]
+ GVariant *menu_data;
+
+ g_variant_get_child(data, 0, "i", &id);
+ menu_data = g_variant_get_child_value(data, 1);
+
+ const char *label = NULL;
+ const char *type = NULL;
+ gboolean isenabled = TRUE;
+ gboolean isvisible = TRUE;
+ gboolean has_submenu = FALSE;
+
+ /*
+ * gboolean ischeckmark = FALSE;
+ * gboolean isradio = FALSE;
+ * int32_t toggle_state = 99;
+ * const char *toggle_type = NULL;
+ */
+
+ const char *has_submenu_s = NULL;
+ GVariantDict dict;
+ g_variant_dict_init(&dict, menu_data);
+ g_variant_dict_lookup(&dict, "label", "&s", &label);
+ g_variant_dict_lookup(&dict, "type", "&s", &type);
+ g_variant_dict_lookup(&dict, "enabled", "b", &isenabled);
+ g_variant_dict_lookup(&dict, "visible", "b", &isvisible);
+ g_variant_dict_lookup(&dict, "children-display", "&s", &has_submenu_s);
+
+ /*
+ * g_variant_dict_lookup(&dict, "toggle-type", "&s", &toggle_type);
+ * g_variant_dict_lookup(&dict, "toggle-state", "i", &toggle_state);
+ */
+
+ g_variant_dict_clear(&dict);
+
+ if (has_submenu_s && strcmp(has_submenu_s, "submenu") == 0)
+ has_submenu = TRUE;
+
+ /*
+ * if (toggle_type && strcmp(toggle_type, "checkmark") == 0)
+ * ischeckmark = TRUE;
+ * else if (toggle_type && strcmp(toggle_type, "radio") == 0)
+ * isradio = TRUE;
+ */
+
+ if ((label && isvisible && isenabled) && !(type && strcmp(type, "separator") == 0)) {
+ GSimpleAction *action = create_action(id, self);
+ char *action_name = g_strdup_printf("%s.%u", "menuitem", id);
+ sn_item_add_action(self->snitem, action);
+ menuitem = g_menu_item_new(label, action_name);
+
+ g_free(action_name);
+ g_object_unref(action);
+
+ } else if ((label && !(type && strcmp(type, "separator") == 0))) {
+ GSimpleAction *action = create_action(id, self);
+ g_simple_action_set_enabled(action, FALSE);
+ char *action_name = g_strdup_printf("%s.%u", "menuitem", id);
+ sn_item_add_action(self->snitem, action);
+ menuitem = g_menu_item_new(label, action_name);
+
+ g_free(action_name);
+ g_object_unref(action);
+ }
+
+ if (has_submenu) {
+ GVariant *submenu_data = g_variant_get_child_value(data, 2);
+ GMenu *submenu = create_menumodel(submenu_data, self);
+ g_menu_item_set_submenu(menuitem, G_MENU_MODEL(submenu));
+ g_object_unref(submenu);
+ g_variant_unref(submenu_data);
+ }
+
+ g_variant_unref(menu_data);
+
+ return menuitem;
+}
+
+static GMenu*
+create_menumodel(GVariant *data, SnDbusmenu *self)
+{
+ GMenu *ret = g_menu_new();
+ GVariantIter iter;
+ GVariant *menuitem_data;
+ gboolean has_sections = check_has_sections(data);
+
+ if (has_sections) {
+ GMenu *section = g_menu_new();
+ g_variant_iter_init(&iter, data);
+ while ((g_variant_iter_next(&iter, "v", &menuitem_data))) {
+ if (!check_menuitem_visible(menuitem_data)) {
+ g_variant_unref(menuitem_data);
+ continue;
+ }
+
+ GMenuItem *menuitem = create_menuitem(menuitem_data, self);
+ if (menuitem) {
+ g_menu_append_item(section, menuitem);
+ g_object_unref(menuitem);
+ }
+ // menuitem == NULL means menuitem is a separator
+ else {
+ g_menu_append_section(ret, NULL, G_MENU_MODEL(section));
+ g_object_unref(section);
+ section = g_menu_new();
+ }
+ g_variant_unref(menuitem_data);
+ }
+ g_menu_append_section(ret, NULL, G_MENU_MODEL(section));
+ g_object_unref(section);
+
+ } else {
+ g_variant_iter_init(&iter, data);
+ while ((g_variant_iter_next(&iter, "v", &menuitem_data))) {
+ GMenuItem *menuitem = create_menuitem(menuitem_data, self);
+ if (menuitem) {
+ g_menu_append_item(ret, menuitem);
+ g_object_unref(menuitem);
+ }
+ g_variant_unref(menuitem_data);
+ }
+ }
+
+ return ret;
+}
+
+static void
+on_layout_updated(GDBusProxy *proxy, GAsyncResult *res, void *data)
+{
+ SnDbusmenu *dbusmenu = SN_DBUSMENU(data);
+
+ GError *err = NULL;
+ GVariant *retvariant = g_dbus_proxy_call_finish(proxy, res, &err);
+
+ // Errors which might occur when the tray is running slowly (eg under valgrind)
+ // and user is spam clicking already exited icons
+
+ // "No such object path '/MenuBar'
+ if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) {
+ g_error_free(err);
+ g_object_unref(dbusmenu);
+ return;
+
+ // "The name is not activatable"
+ } else if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
+ g_error_free(err);
+ g_object_unref(dbusmenu);
+ return;
+
+ // "Remote peer disconnected"
+ } else if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY)) {
+ g_error_free(err);
+ g_object_unref(dbusmenu);
+ return;
+
+ } else if (err) {
+ g_warning("%s\n", err->message);
+ g_error_free(err);
+ g_object_unref(dbusmenu);
+ return;
+ }
+
+ GVariant *layout;
+ GVariant *menuitems;
+
+ layout = g_variant_get_child_value(retvariant, 1);
+ menuitems = g_variant_get_child_value(layout, 2);
+
+ GMenu *newmenu = create_menumodel(menuitems, dbusmenu);
+ sn_item_set_menu_model(dbusmenu->snitem, newmenu);
+
+ g_variant_unref(menuitems);
+ g_variant_unref(layout);
+ g_variant_unref(retvariant);
+ g_object_unref(dbusmenu);
+}
+
+
+// We just rebuild the entire menu every time...
+// ItemsPropertiesUpdated signal would carry data of
+// which menuitems got updated and which got removed
+// but the GMenu api doesn't allow easy manipulation as it is
+static void
+on_menuproxy_signal(GDBusProxy *proxy,
+ const char *sender,
+ const char *signal,
+ GVariant *params,
+ void *data)
+{
+ SnDbusmenu *dbusmenu = SN_DBUSMENU(data);
+ if (strcmp(signal, "LayoutUpdated") == 0) {
+ uint32_t revision = UINT32_MAX;
+ int32_t parentid;
+ g_variant_get(params, "(ui)", &revision, &parentid);
+ if (dbusmenu->revision != UINT32_MAX && revision <= dbusmenu->revision) {
+ // g_debug("%s got %s, but menurevision didn't change. Ignoring\n", snitem->busname, signal);
+ return;
+ } else {
+ dbusmenu->revision = revision;
+ }
+
+ g_dbus_proxy_call(dbusmenu->proxy,
+ "GetLayout",
+ g_variant_new("(iias)", 0, -1, NULL),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback)on_layout_updated,
+ g_object_ref(dbusmenu));
+
+ } else if (strcmp(signal, "ItemsPropertiesUpdated") == 0) {
+ g_dbus_proxy_call(dbusmenu->proxy,
+ "GetLayout",
+ g_variant_new("(iias)", 0, -1, NULL),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback)on_layout_updated,
+ g_object_ref(dbusmenu));
+ }
+}
+
+static void
+on_menulayout_ready(GDBusProxy *proxy, GAsyncResult *res, void *data)
+{
+ SnDbusmenu *dbusmenu = SN_DBUSMENU(data);
+
+ GError *err = NULL;
+ GVariant *retvariant = g_dbus_proxy_call_finish(proxy, res, &err);
+ // (u(ia{sv}av))
+
+ // "No such object path '/NO_DBUSMENU'"
+ // generated by QBittorrent when it sends a broken trayitem on startup
+ // and replaces it later
+ if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) {
+ g_error_free(err);
+ g_object_unref(dbusmenu);
+ return;
+ } else if (err) {
+ g_warning("%s\n", err->message);
+ g_error_free(err);
+ g_object_unref(dbusmenu);
+ return;
+ }
+
+ uint32_t revision = 0;
+ GVariant *layout;
+ GVariant *menuitems;
+
+ g_variant_get_child(retvariant, 0, "u", &revision);
+
+ layout = g_variant_get_child_value(retvariant, 1);
+ menuitems = g_variant_get_child_value(layout, 2);
+
+ GMenu *menu = create_menumodel(menuitems, dbusmenu);
+ sn_item_set_menu_model(dbusmenu->snitem, menu);
+
+ g_object_unref(menu);
+ g_variant_unref(menuitems);
+ g_variant_unref(layout);
+ g_variant_unref(retvariant);
+ g_object_unref(dbusmenu);
+}
+
+static void
+sn_dbusmenu_get_property(GObject *object, uint property_id, GValue *value, GParamSpec *pspec)
+{
+ SnDbusmenu *self = SN_DBUSMENU(object);
+
+ switch (property_id) {
+ case PROP_PROXY:
+ g_value_set_object(value, self->proxy);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+sn_dbusmenu_set_property(GObject *object, uint property_id, const GValue *value, GParamSpec *pspec)
+{
+ SnDbusmenu *self = SN_DBUSMENU(object);
+
+ switch (property_id) {
+ case PROP_BUSNAME:
+ self->busname = g_strdup(g_value_get_string(value));
+ break;
+ case PROP_BUSOBJ:
+ self->busobj = g_strdup(g_value_get_string(value));
+ break;
+ case PROP_SNITEM:
+ self->snitem = g_value_get_object(value);
+ break;
+ case PROP_PROXY:
+ self->proxy = g_value_get_object(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+sn_dbusmenu_class_init(SnDbusmenuClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ obj_properties[PROP_BUSNAME] =
+ g_param_spec_string("busname", NULL, NULL,
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+ obj_properties[PROP_BUSOBJ] =
+ g_param_spec_string("busobj", NULL, NULL,
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_SNITEM] =
+ g_param_spec_object("snitem", NULL, NULL,
+ SN_TYPE_ITEM,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_PROXY] =
+ g_param_spec_object("proxy", NULL, NULL,
+ G_TYPE_DBUS_PROXY,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+
+ object_class->set_property = sn_dbusmenu_set_property;
+ object_class->get_property = sn_dbusmenu_get_property;
+
+ object_class->constructed = sn_dbusmenu_constructed;
+ object_class->dispose = sn_dbusmenu_dispose;
+ object_class->finalize = sn_dbusmenu_finalize;
+
+ g_object_class_install_properties(object_class, N_PROPERTIES, obj_properties);
+}
+
+static void
+menuproxy_ready_cb(GObject *obj, GAsyncResult *res, void *data)
+{
+ SnDbusmenu *self = SN_DBUSMENU(data);
+
+ GError *err = NULL;
+ GDBusProxy *proxy = g_dbus_proxy_new_for_bus_finish(res, &err);
+
+ if (err) {
+ g_warning("Failed to construct gdbusproxy for menu: %s\n", err->message);
+ g_error_free(err);
+ g_object_unref(self);
+ return;
+ }
+
+ g_debug("Created gdbusproxy for menu %s %s",
+ g_dbus_proxy_get_name(proxy),
+ g_dbus_proxy_get_object_path(proxy));
+
+ g_object_set(self, "proxy", proxy, NULL);
+
+ g_dbus_proxy_call(self->proxy,
+ "GetLayout",
+ g_variant_new ("(iias)", 0, -1, NULL),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback)on_menulayout_ready,
+ g_object_ref(self));
+
+ g_signal_connect(self->proxy, "g-signal", G_CALLBACK(on_menuproxy_signal), self);
+ g_object_unref(self);
+}
+
+
+static void
+sn_dbusmenu_init(SnDbusmenu *self)
+{
+}
+
+static void
+sn_dbusmenu_constructed(GObject *obj)
+{
+ SnDbusmenu *self = SN_DBUSMENU(obj);
+
+ g_object_ref(self->snitem);
+
+ GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(DBUSMENU_XML, NULL);
+ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ nodeinfo->interfaces[0],
+ self->busname,
+ self->busobj,
+ "com.canonical.dbusmenu",
+ NULL,
+ (GAsyncReadyCallback)menuproxy_ready_cb,
+ g_object_ref(self));
+ g_dbus_node_info_unref(nodeinfo);
+
+ G_OBJECT_CLASS(sn_dbusmenu_parent_class)->constructed(obj);
+}
+
+static void
+sn_dbusmenu_dispose(GObject *obj)
+{
+ SnDbusmenu *self = SN_DBUSMENU(obj);
+
+ g_debug("Disposing sndbusmenu %s %s",
+ self->busname,
+ self->busobj);
+
+ g_object_unref(self->proxy);
+ g_object_unref(self->snitem);
+
+ G_OBJECT_CLASS(sn_dbusmenu_parent_class)->dispose(obj);
+}
+
+static void
+sn_dbusmenu_finalize(GObject *obj)
+{
+ SnDbusmenu *self = SN_DBUSMENU(obj);
+ g_free(self->busname);
+ g_free(self->busobj);
+ G_OBJECT_CLASS(sn_dbusmenu_parent_class)->finalize(obj);
+}
+
+GDBusProxy*
+sn_dbusmenu_get_proxy(SnDbusmenu *self)
+{
+ GDBusProxy *proxy;
+ g_object_get(self, "proxy", &proxy, NULL);
+
+ return proxy;
+}
+
+SnDbusmenu*
+sn_dbusmenu_new(const char *busname, const char *busobj, SnItem *snitem)
+{
+ return g_object_new(SN_TYPE_DBUSMENU,
+ "busname", busname,
+ "busobj", busobj,
+ "snitem", snitem,
+ NULL);
+}
diff --git a/systray/sndbusmenu.h b/systray/sndbusmenu.h
@@ -0,0 +1,89 @@
+#ifndef SNDBUSMENU_H
+#define SNDBUSMENU_H
+
+#include <glib-object.h>
+
+#include "snitem.h"
+
+G_BEGIN_DECLS
+
+#define SN_TYPE_DBUSMENU sn_dbusmenu_get_type()
+G_DECLARE_FINAL_TYPE(SnDbusmenu, sn_dbusmenu, SN, DBUSMENU, GObject);
+
+SnDbusmenu* sn_dbusmenu_new (const char *busname,
+ const char *busobj,
+ SnItem *snitem);
+
+GDBusProxy* sn_dbusmenu_get_proxy (SnDbusmenu *self);
+
+G_END_DECLS
+
+#define DBUSMENU_XML \
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
+ "<node>\n" \
+ " <interface name=\"com.canonical.dbusmenu\">\n" \
+ " <!-- methods -->\n" \
+ " <method name=\"GetLayout\">\n" \
+ " <arg type=\"i\" name=\"parentId\" direction=\"in\"/>\n" \
+ " <arg type=\"i\" name=\"recursionDepth\" direction=\"in\"/>\n" \
+ " <arg type=\"as\" name=\"propertyNames\" direction=\"in\"/>\n" \
+ " <arg type=\"u\" name=\"revision\" direction=\"out\"/>\n" \
+ " <arg type=\"(ia{sv}av)\" name=\"layout\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"Event\">\n" \
+ " <arg type=\"i\" name=\"id\" direction=\"in\"/>\n" \
+ " <arg type=\"s\" name=\"eventId\" direction=\"in\"/>\n" \
+ " <arg type=\"v\" name=\"data\" direction=\"in\"/>\n" \
+ " <arg type=\"u\" name=\"timestamp\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <method name=\"AboutToShow\">\n" \
+ " <arg type=\"i\" name=\"id\" direction=\"in\"/>\n" \
+ " <arg type=\"b\" name=\"needUpdate\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <!--\n" \
+ " <method name=\"AboutToShowGroup\">\n" \
+ " <arg type=\"ai\" name=\"ids\" direction=\"in\"/>\n" \
+ " <arg type=\"ai\" name=\"updatesNeeded\" direction=\"out\"/>\n" \
+ " <arg type=\"ai\" name=\"idErrors\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"GetGroupProperties\">\n" \
+ " <arg type=\"ai\" name=\"ids\" direction=\"in\"/>\n" \
+ " <arg type=\"as\" name=\"propertyNames\" direction=\"in\"/>\n" \
+ " <arg type=\"a(ia{sv})\" name=\"properties\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"GetProperty\">\n" \
+ " <arg type=\"i\" name=\"id\" direction=\"in\"/>\n" \
+ " <arg type=\"s\" name=\"name\" direction=\"in\"/>\n" \
+ " <arg type=\"v\" name=\"value\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"EventGroup\">\n" \
+ " <arg type=\"a(isvu)\" name=\"events\" direction=\"in\"/>\n" \
+ " <arg type=\"ai\" name=\"idErrors\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " -->\n" \
+ " <!-- properties -->\n" \
+ " <!--\n" \
+ " <property name=\"Version\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"TextDirection\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Status\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"IconThemePath\" type=\"as\" access=\"read\"/>\n" \
+ " -->\n" \
+ " <!-- Signals -->\n" \
+ " <signal name=\"ItemsPropertiesUpdated\">\n" \
+ " <arg type=\"a(ia{sv})\" name=\"updatedProps\" direction=\"out\"/>\n" \
+ " <arg type=\"a(ias)\" name=\"removedProps\" direction=\"out\"/>\n" \
+ " </signal>\n" \
+ " <signal name=\"LayoutUpdated\">\n" \
+ " <arg type=\"u\" name=\"revision\" direction=\"out\"/>\n" \
+ " <arg type=\"i\" name=\"parent\" direction=\"out\"/>\n" \
+ " </signal>\n" \
+ " <!--\n" \
+ " <signal name=\"ItemActivationRequested\">\n" \
+ " <arg type=\"i\" name=\"id\" direction=\"out\"/>\n" \
+ " <arg type=\"u\" name=\"timestamp\" direction=\"out\"/>\n" \
+ " </signal>\n" \
+ " -->\n" \
+ " </interface>\n" \
+ "</node>\n"
+
+#endif /* SNDBUSMENU_H */
diff --git a/systray/snhost.c b/systray/snhost.c
@@ -0,0 +1,546 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "snhost.h"
+#include "snitem.h"
+
+
+struct _SnHost
+{
+ GtkWidget parent_instance;
+
+ char *traymon;
+ int iconsize;
+ int margins;
+ int spacing;
+
+ GDBusConnection *conn;
+ int owner_id;
+ int obj_reg_id;
+ int sig_sub_id;
+
+ int nitems;
+ int curwidth;
+ GSList *trayitems;
+ gboolean exiting;
+};
+
+G_DEFINE_FINAL_TYPE(SnHost, sn_host, GTK_TYPE_BOX)
+
+enum
+{
+ PROP_TRAYMON = 1,
+ PROP_ICONSIZE,
+ PROP_MARGINS,
+ PROP_SPACING,
+ N_PROPERTIES
+};
+
+static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
+
+
+static void sn_host_constructed (GObject *obj);
+static void sn_host_dispose (GObject *obj);
+static void sn_host_finalize (GObject *obj);
+
+static void sn_host_bus_call_method_handler (GDBusConnection *conn,
+ const char *sender,
+ const char *object_path,
+ const char *interface_name,
+ const char *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ void *data);
+
+static GVariant* sn_host_bus_prop_get_handler (GDBusConnection* conn,
+ const char* sender,
+ const char* object_path,
+ const char* interface_name,
+ const char* property_name,
+ GError** err,
+ void *data);
+
+static GDBusInterfaceVTable interface_vtable = {
+ sn_host_bus_call_method_handler,
+ sn_host_bus_prop_get_handler,
+ NULL
+};
+
+
+void
+dwlb_request_resize(SnHost *self)
+{
+ if (self->exiting)
+ self->curwidth = 0;
+ else if (self->nitems <= 1)
+ self->curwidth = self->iconsize + 2 * self->margins;
+ else
+ self->curwidth = self->nitems * self->iconsize +
+ (self->nitems - 1) * self->spacing +
+ 2 * self->margins;
+
+ struct sockaddr_un sockaddr;
+ sockaddr.sun_family = AF_UNIX;
+ char *socketpath = g_strdup_printf("%s/dwlb/dwlb-0", g_get_user_runtime_dir());
+ snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", socketpath);
+ char *sockbuf = NULL;
+ if (self->traymon) {
+ sockbuf = g_strdup_printf("%s %s %i", self->traymon, "resize", self->curwidth);
+ }
+ else {
+ sockbuf = g_strdup_printf("%s %s %i", "all", "resize", self->curwidth);
+ }
+
+ size_t len = strlen(sockbuf);
+ int sock_fd = socket(AF_UNIX, SOCK_STREAM, 1);
+
+ int connstatus = connect(sock_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
+ if (connstatus != 0) {
+ g_error("Error connecting to dwlb socket");
+ }
+
+ if (send(sock_fd, sockbuf, len, 0) == -1)
+ g_error("Could not send size update to %s", sockaddr.sun_path);
+ close(sock_fd);
+
+ g_free(socketpath);
+ g_free(sockbuf);
+}
+
+static void
+sn_host_register_item(SnHost *self,
+ const char *busname,
+ const char *busobj)
+{
+ g_debug("Registering %s", busname);
+ SnItem *snitem = sn_item_new(busname, busobj, self->iconsize);
+ gtk_box_append(GTK_BOX(self), GTK_WIDGET(snitem));
+ self->nitems = self->nitems + 1;
+ self->trayitems = g_slist_prepend(self->trayitems, snitem);
+ dwlb_request_resize(self);
+
+ GError *err = NULL;
+ g_dbus_connection_emit_signal(self->conn,
+ NULL,
+ "/StatusNotifierWatcher",
+ "org.kde.StatusNotifierWatcher",
+ "StatusNotifierItemRegistered",
+ g_variant_new("(s)", busname),
+ &err);
+ if (err) {
+ g_warning("%s", err->message);
+ g_error_free(err);
+ }
+}
+
+static void
+sn_host_unregister_item(SnHost *self, SnItem *snitem)
+{
+ char *busname = sn_item_get_busname(snitem);
+ g_debug("Unregistering %s", busname);
+
+ gtk_box_remove(GTK_BOX(self), GTK_WIDGET(snitem));
+ g_object_run_dispose(G_OBJECT(snitem));
+
+ self->trayitems = g_slist_remove(self->trayitems, snitem);
+ self->nitems = self->nitems - 1;
+
+ dwlb_request_resize(self);
+
+ GError *err = NULL;
+
+ g_dbus_connection_emit_signal(self->conn,
+ NULL,
+ "/StatusNotifierWatcher",
+ "org.kde.StatusNotifierWatcher",
+ "StatusNotifierItemUnregistered",
+ g_variant_new("(s)", busname),
+ &err);
+ if (err) {
+ g_warning("%s", err->message);
+ g_error_free(err);
+ }
+
+ g_free(busname);
+}
+
+static int
+nameowner_find_helper(SnItem *snitem, const char *busname_match)
+{
+ char *busname = sn_item_get_busname(snitem);
+ int ret = 1;
+ if (strcmp(busname, busname_match) == 0)
+ ret = 0;
+ else
+ ret = -1;
+
+ g_free(busname);
+ return ret;
+}
+
+static void
+sn_host_bus_monitor(GDBusConnection* conn,
+ const char* sender,
+ const char* objpath,
+ const char* iface_name,
+ const char* signame,
+ GVariant *params,
+ void *data)
+{
+ SnHost *self = SN_HOST(data);
+
+ if (strcmp(signame, "NameOwnerChanged") == 0) {
+ if (!self->trayitems)
+ return;
+
+ const char *name;
+ const char *old_owner;
+ const char *new_owner;
+ g_variant_get(params, "(&s&s&s)", &name, &old_owner, &new_owner);
+ if (strcmp(new_owner, "") == 0) {
+ GSList *pmatch = g_slist_find_custom(self->trayitems, name, (GCompareFunc)nameowner_find_helper);
+ if (pmatch) {
+ SnItem *snitem = pmatch->data;
+ sn_host_unregister_item(self, snitem);
+ }
+ }
+
+ }
+}
+
+static void
+unregister_all_helper(SnItem* item, SnHost *host)
+{
+ sn_host_unregister_item(host, item);
+}
+
+
+static void
+sn_host_unregister_all(SnHost *self)
+{
+ g_slist_foreach(self->trayitems, (GFunc)unregister_all_helper, self);
+}
+
+static void
+sn_host_bus_call_method_handler(GDBusConnection *conn,
+ const char *sender,
+ const char *obj_path,
+ const char *iface_name,
+ const char *method_name,
+ GVariant *params,
+ GDBusMethodInvocation *invoc,
+ void *data)
+{
+ SnHost *self = SN_HOST(data);
+
+ if (strcmp(method_name, "RegisterStatusNotifierItem") == 0) {
+ const char *param;
+ const char *busobj;
+ const char *registree_name;
+
+ g_variant_get(params, "(&s)", ¶m);
+
+ if (g_str_has_prefix(param, "/")) {
+ busobj = param;
+ } else {
+ busobj = "/StatusNotifierItem";
+ }
+
+ if (g_str_has_prefix(param, ":") && strcmp(sender, param) != 0)
+ registree_name = param;
+ else
+ registree_name = sender;
+
+ sn_host_register_item(self, registree_name, busobj);
+ g_dbus_method_invocation_return_value(invoc, NULL);
+
+ } else {
+ g_dbus_method_invocation_return_dbus_error(invoc,
+ "org.freedesktop.DBus.Error.UnknownMethod",
+ "Unknown method");
+ }
+}
+
+static void
+bus_get_snitems_helper(void *data, void *udata)
+{
+ SnItem *item = SN_ITEM(data);
+ GVariantBuilder *builder = (GVariantBuilder*)udata;
+
+ char *busname = sn_item_get_busname(item);
+ g_variant_builder_add_value(builder, g_variant_new_string(busname));
+ g_free(busname);
+}
+
+static GVariant*
+sn_host_bus_prop_get_handler(GDBusConnection* conn,
+ const char* sender,
+ const char* object_path,
+ const char* interface_name,
+ const char* property_name,
+ GError** err,
+ void *data)
+{
+ SnHost *self = SN_HOST(data);
+
+ if (strcmp(property_name, "ProtocolVersion") == 0) {
+ return g_variant_new("i", 0);
+ } else if (strcmp(property_name, "IsStatusNotifierHostRegistered") == 0) {
+ return g_variant_new("b", TRUE);
+ } else if (strcmp(property_name, "RegisteredStatusNotifierItems") == 0) {
+ if (!self->trayitems)
+ return g_variant_new("as", NULL);
+
+ GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
+ g_slist_foreach(self->trayitems, bus_get_snitems_helper, builder);
+ GVariant *as = g_variant_builder_end(builder);
+
+ g_variant_builder_unref(builder);
+
+ return as;
+ } else {
+ g_set_error(err,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_UNKNOWN_PROPERTY,
+ "Unknown property '%s'.",
+ property_name);
+ return NULL;
+ }
+}
+
+static void
+sn_host_bus_acquired_handler(GDBusConnection *conn, const char *busname, void *data)
+{
+ SnHost *self = SN_HOST(data);
+
+ self->conn = conn;
+
+ GError *err = NULL;
+ GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(STATUSNOTIFIERWATCHER_XML, NULL);
+
+ self->obj_reg_id =
+ g_dbus_connection_register_object(self->conn,
+ "/StatusNotifierWatcher",
+ nodeinfo->interfaces[0],
+ &interface_vtable,
+ self,
+ NULL,
+ &err);
+
+ g_dbus_node_info_unref(nodeinfo);
+
+ if (err) {
+ g_error("%s", err->message);
+ g_error_free(err);
+ exit(-1);
+ }
+
+ self->sig_sub_id =
+ g_dbus_connection_signal_subscribe(self->conn,
+ NULL, // Listen to all senders);
+ "org.freedesktop.DBus",
+ "NameOwnerChanged",
+ NULL, // Match all obj paths
+ NULL, // Match all arg0s
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ sn_host_bus_monitor,
+ self,
+ NULL);
+}
+
+static void
+sn_host_bus_name_acquired_handler(GDBusConnection *conn, const char *busname, void *data)
+{
+ SnHost *self = SN_HOST(data);
+
+ GError *err = NULL;
+
+ g_dbus_connection_emit_signal(self->conn,
+ NULL,
+ "/StatusNotifierWatcher",
+ "org.kde.StatusNotifierWatcher",
+ "StatusNotifierHostRegistered",
+ NULL,
+ &err);
+
+ if (err) {
+ g_warning("%s", err->message);
+ g_error_free(err);
+ }
+}
+
+static void
+sn_host_bus_name_lost_handler(GDBusConnection *conn, const char *busname, void *data)
+{
+ g_error("Could not acquire %s, maybe another instance is running?", busname);
+ exit(-1);
+}
+
+static void
+sn_host_set_property(GObject *object, uint property_id, const GValue *value, GParamSpec *pspec)
+{
+ SnHost *self = SN_HOST(object);
+
+ switch (property_id) {
+ case PROP_TRAYMON:
+ self->traymon = g_strdup(g_value_get_string(value));
+ break;
+ case PROP_ICONSIZE:
+ self->iconsize = g_value_get_int(value);
+ break;
+ case PROP_MARGINS:
+ self->margins = g_value_get_int(value);
+ break;
+ case PROP_SPACING:
+ self->spacing = g_value_get_int(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+sn_host_get_property(GObject *object, uint property_id, GValue *value, GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+}
+
+static void
+sn_host_class_init(SnHostClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->set_property = sn_host_set_property;
+ object_class->get_property = sn_host_get_property;
+ object_class->constructed = sn_host_constructed;
+ object_class->dispose = sn_host_dispose;
+ object_class->finalize = sn_host_finalize;
+
+ obj_properties[PROP_ICONSIZE] =
+ g_param_spec_int("iconsize", NULL, NULL,
+ G_MININT,
+ G_MAXINT,
+ 22,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_MARGINS] =
+ g_param_spec_int("margins", NULL, NULL,
+ G_MININT,
+ G_MAXINT,
+ 4,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_SPACING] =
+ g_param_spec_int("spacing", NULL, NULL,
+ G_MININT,
+ G_MAXINT,
+ 4,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+ obj_properties[PROP_TRAYMON] =
+ g_param_spec_string("traymon", NULL, NULL,
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(object_class, N_PROPERTIES, obj_properties);
+}
+
+static void
+sn_host_init(SnHost *self)
+{
+ self->exiting = FALSE;
+ self->nitems = 0;
+ self->trayitems = NULL;
+
+ self->owner_id =
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "org.kde.StatusNotifierWatcher",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ sn_host_bus_acquired_handler,
+ sn_host_bus_name_acquired_handler,
+ sn_host_bus_name_lost_handler,
+ self,
+ NULL);
+}
+
+static void
+sn_host_constructed(GObject *obj)
+{
+ SnHost *self = SN_HOST(obj);
+ GtkWidget *widget = GTK_WIDGET(obj);
+ gtk_widget_set_vexpand(widget, TRUE);
+ gtk_widget_set_hexpand(widget, TRUE);
+ gtk_widget_set_margin_start(widget, self->margins);
+ gtk_widget_set_margin_end(widget, self->margins);
+ gtk_widget_set_margin_top(widget, self->margins);
+ gtk_widget_set_margin_bottom(widget, self->margins);
+ gtk_box_set_homogeneous(GTK_BOX(obj), TRUE);
+ gtk_box_set_spacing(GTK_BOX(obj), self->margins);
+
+ G_OBJECT_CLASS(sn_host_parent_class)->constructed(obj);
+}
+
+static void
+sn_host_dispose(GObject *obj)
+{
+ g_debug("Disposing snhost");
+ SnHost *self = SN_HOST(obj);
+ self->exiting = TRUE;
+
+ sn_host_unregister_all(self);
+
+ if (self->sig_sub_id > 0) {
+ g_dbus_connection_signal_unsubscribe(self->conn, self->sig_sub_id);
+ self->sig_sub_id = 0;
+ }
+
+ if (self->obj_reg_id > 0) {
+ g_dbus_connection_unregister_object(self->conn, self->obj_reg_id);
+ self->obj_reg_id = 0;
+ }
+
+ if (self->owner_id > 0) {
+ g_bus_unown_name(self->owner_id);
+ self->owner_id = 0;
+ self->conn = NULL;
+ }
+
+ dwlb_request_resize(self);
+
+ G_OBJECT_CLASS(sn_host_parent_class)->dispose(obj);
+}
+
+static void
+sn_host_finalize(GObject *obj)
+{
+ SnHost *self = SN_HOST(obj);
+
+ g_free(self->traymon);
+ g_slist_free(self->trayitems);
+
+ G_OBJECT_CLASS(sn_host_parent_class)->finalize(obj);
+}
+
+SnHost*
+sn_host_new(const char *traymon, int iconsize, int margins, int spacing)
+{
+ return g_object_new(SN_TYPE_HOST,
+ "traymon", traymon,
+ "iconsize", iconsize,
+ "margins", margins,
+ "spacing", spacing,
+ NULL);
+}
diff --git a/systray/snhost.h b/systray/snhost.h
@@ -0,0 +1,45 @@
+#ifndef SNHOST_H
+#define SNHOST_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SN_TYPE_HOST sn_host_get_type()
+G_DECLARE_FINAL_TYPE(SnHost, sn_host, SN, HOST, GtkBox)
+
+SnHost *sn_host_new (const char *traymon,
+ int iconsize,
+ int margins,
+ int spacing);
+
+void dwlb_request_resize (SnHost *self);
+
+G_END_DECLS
+
+#define STATUSNOTIFIERWATCHER_XML \
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
+ "<node>\n" \
+ " <interface name=\"org.kde.StatusNotifierWatcher\">\n" \
+ " <!-- methods -->\n" \
+ " <method name=\"RegisterStatusNotifierItem\">\n" \
+ " <arg name=\"service\" type=\"s\" direction=\"in\" />\n" \
+ " </method>\n" \
+ " <!-- properties -->\n" \
+ " <property name=\"IsStatusNotifierHostRegistered\" type=\"b\" access=\"read\" />\n" \
+ " <property name=\"ProtocolVersion\" type=\"i\" access=\"read\" />\n" \
+ " <property name=\"RegisteredStatusNotifierItems\" type=\"as\" access=\"read\" />\n" \
+ " <!-- signals -->\n" \
+ " <signal name=\"StatusNotifierItemRegistered\">\n" \
+ " <arg type=\"s\"/>\n" \
+ " </signal>\n" \
+ " <signal name=\"StatusNotifierItemUnregistered\">\n" \
+ " <arg type=\"s\"/>\n" \
+ " </signal>\n" \
+ " <signal name=\"StatusNotifierHostRegistered\">\n" \
+ " </signal>\n" \
+ " </interface>\n" \
+ "</node>\n"
+
+#endif /* SNHOST_H */
diff --git a/systray/snitem.c b/systray/snitem.c
@@ -0,0 +1,754 @@
+#include <stdint.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "snitem.h"
+#include "sndbusmenu.h"
+
+
+struct _SnItem
+{
+ GtkWidget parent_instance;
+
+ char *busname;
+ char *busobj;
+ int iconsize;
+
+ GDBusProxy *proxy;
+ char *iconname;
+ GVariant *iconpixmaps;
+ SnDbusmenu *dbusmenu;
+
+ GtkWidget *image;
+ GtkWidget *popovermenu;
+ GSimpleActionGroup *actiongroup;
+
+ gboolean ready;
+};
+
+G_DEFINE_FINAL_TYPE(SnItem, sn_item, GTK_TYPE_WIDGET)
+
+enum
+{
+ PROP_BUSNAME = 1,
+ PROP_BUSOBJ,
+ PROP_ICONSIZE,
+ PROP_PROXY,
+ PROP_ACTIONGROUP,
+ PROP_DBUSMENU,
+ N_PROPERTIES
+};
+
+static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
+
+static void sn_item_constructed (GObject *obj);
+static void sn_item_dispose (GObject *obj);
+static void sn_item_finalize (GObject *obj);
+
+static void sn_item_size_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline);
+
+static void sn_item_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline);
+
+
+static void
+argb_to_rgba(int32_t width, int32_t height, unsigned char *icon_data)
+{
+ // Icon data is ARGB, gdk textures are RGBA. Flip the channels
+ // Shamelessly copied from Waybar
+ for (int32_t i = 0; i < 4 * width * height; i += 4) {
+ unsigned char alpha = icon_data[i];
+ icon_data[i] = icon_data[i + 1];
+ icon_data[i + 1] = icon_data[i + 2];
+ icon_data[i + 2] = icon_data[i + 3];
+ icon_data[i + 3] = alpha;
+ }
+}
+
+static void
+pixbuf_destroy(unsigned char *pixeld, void *data)
+{
+ g_free(pixeld);
+}
+
+static GVariant*
+select_icon_by_size(GVariant *icondata_v, int32_t target_icon_size)
+{
+ // Apps broadcast icons as variant a(iiay)
+ // Meaning array of tuples, tuple representing an icon
+ // first 2 members ii in each tuple are width and height
+ // We iterate the array and pick the icon size closest to
+ // the target based on its width and save the index
+ GVariantIter iter;
+ int selected_index = 0;
+ int current_index = 0;
+ int32_t diff = INT32_MAX;
+ GVariant *child;
+ g_variant_iter_init(&iter, icondata_v);
+ while ((child = g_variant_iter_next_value(&iter))) {
+ int32_t curwidth;
+ g_variant_get_child(child, 0, "i", &curwidth);
+ int32_t curdiff;
+ if (curwidth > target_icon_size)
+ curdiff = curwidth - target_icon_size;
+ else
+ curdiff = target_icon_size - curwidth;
+
+ if (curdiff < diff)
+ selected_index = current_index;
+
+ current_index = current_index + 1;
+ g_variant_unref(child);
+ }
+
+ GVariant *iconpixmap_v = g_variant_get_child_value(icondata_v,
+ (size_t)selected_index);
+
+ return iconpixmap_v;
+}
+
+static GdkPaintable*
+get_paintable_from_data(GVariant *icons, int32_t iconsize)
+{
+ GdkPaintable *paintable;
+ GVariantIter iter;
+ GVariant *iconpixmap_v = select_icon_by_size(icons, iconsize);
+
+ int32_t width;
+ int32_t height;
+ GVariant *icon_data_v;
+
+ g_variant_iter_init(&iter, iconpixmap_v);
+
+ g_variant_iter_next(&iter, "i", &width);
+ g_variant_iter_next(&iter, "i", &height);
+ icon_data_v = g_variant_iter_next_value(&iter);
+
+ size_t size = g_variant_get_size(icon_data_v);
+ const void *icon_data_dup = g_variant_get_data(icon_data_v);
+
+ unsigned char *icon_data = g_memdup2(icon_data_dup, size);
+ argb_to_rgba(width, height, icon_data);
+
+ int32_t padding = size / height - 4 * width;
+ int32_t rowstride = 4 * width + padding;
+
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(icon_data,
+ GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ width,
+ height,
+ rowstride,
+ (GdkPixbufDestroyNotify)pixbuf_destroy,
+ NULL);
+
+ GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf);
+ paintable = GDK_PAINTABLE(texture);
+
+ g_object_unref(pixbuf);
+ g_variant_unref(icon_data_v);
+ g_variant_unref(iconpixmap_v);
+
+ return paintable;
+}
+
+static GdkPaintable*
+get_paintable_from_name(const char *iconname, int32_t iconsize)
+{
+ GdkPaintable *paintable = NULL;
+ GtkIconPaintable *icon;
+
+ GtkIconTheme *theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
+ icon = gtk_icon_theme_lookup_icon(theme,
+ iconname,
+ NULL, // const char **fallbacks
+ iconsize,
+ 1,
+ GTK_TEXT_DIR_LTR,
+ 0); // GtkIconLookupFlags
+ paintable = GDK_PAINTABLE(icon);
+
+ return paintable;
+}
+
+static void
+sn_item_proxy_new_iconname_handler(GObject *obj, GAsyncResult *res, void *data)
+{
+ SnItem *self = SN_ITEM(data);
+ GDBusProxy *proxy = G_DBUS_PROXY(obj);
+
+ GError *err = NULL;
+ GVariant *retvariant = g_dbus_proxy_call_finish(proxy, res, &err);
+ // (v)
+
+ if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) {
+ g_error_free(err);
+ g_object_unref(self);
+ return;
+ } else if (err) {
+ g_warning("%s\n", err->message);
+ g_error_free(err);
+ g_object_unref(self);
+ return;
+ }
+
+ GVariant *iconname_v;
+ const char *iconname = NULL;
+ g_variant_get(retvariant, "(v)", &iconname_v);
+ g_variant_get(iconname_v, "&s", &iconname);
+ g_variant_unref(iconname_v);
+
+ if (strcmp(iconname, self->iconname) == 0) {
+ // g_debug("%s got NewIcon, but iconname didn't change. Ignoring\n", snitem->busname);
+ g_variant_unref(retvariant);
+ g_object_unref(self);
+ return;
+ }
+
+ g_free(self->iconname);
+
+ self->iconname = g_strdup(iconname);
+ GdkPaintable *paintable = get_paintable_from_name(self->iconname, self->iconsize);
+ gtk_image_set_from_paintable(GTK_IMAGE(self->image), paintable);
+ g_object_unref(paintable);
+
+ g_variant_unref(retvariant);
+ g_object_unref(self);
+}
+
+static void
+sn_item_proxy_new_pixmaps_handler(GObject *obj, GAsyncResult *res, void *data)
+{
+ SnItem *self = SN_ITEM(data);
+ GDBusProxy *proxy = G_DBUS_PROXY(obj);
+
+ GError *err = NULL;
+ GVariant *retvariant = g_dbus_proxy_call_finish(proxy, res, &err);
+ // (v)
+
+ if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) {
+ g_error_free(err);
+ g_object_unref(self);
+ return;
+ } else if (err) {
+ g_warning("%s\n", err->message);
+ g_error_free(err);
+ g_object_unref(self);
+ return;
+ }
+
+ GVariant *newpixmap_v;
+ g_variant_get(retvariant, "(v)", &newpixmap_v);
+ g_variant_unref(retvariant);
+
+ if (g_variant_equal(newpixmap_v, self->iconpixmaps)) {
+ // g_debug ("%s got NewIcon, but iconpixmap didn't change. Ignoring\n", snitem->busname);
+ g_variant_unref(newpixmap_v);
+ g_object_unref(self);
+ return;
+ }
+
+ g_variant_unref(self->iconpixmaps);
+ self->iconpixmaps = newpixmap_v;
+ GdkPaintable *paintable = get_paintable_from_data(self->iconpixmaps,
+ self->iconsize);
+ gtk_image_set_from_paintable(GTK_IMAGE(self->image), paintable);
+ g_object_unref(paintable);
+ g_object_unref(self);
+}
+
+static void
+sn_item_proxy_signal_handler(GDBusProxy *proxy,
+ const char *sender,
+ const char *signal,
+ GVariant *data_v,
+ void *data)
+{
+ SnItem *self = SN_ITEM(data);
+
+ if (strcmp(signal, "NewIcon") == 0) {
+ if (self->iconpixmaps)
+ g_dbus_proxy_call(proxy,
+ "org.freedesktop.DBus.Properties.Get",
+ g_variant_new("(ss)", "org.kde.StatusNotifierItem", "IconPixmap"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ sn_item_proxy_new_pixmaps_handler,
+ g_object_ref(self));
+
+ if (self->iconname)
+ g_dbus_proxy_call(proxy,
+ "org.freedesktop.DBus.Properties.Get",
+ g_variant_new("(ss)", "org.kde.StatusNotifierItem", "IconName"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ sn_item_proxy_new_iconname_handler,
+ g_object_ref(self));
+ }
+}
+
+static void
+sn_item_proxy_ready_handler(GObject *obj, GAsyncResult *res, void *data)
+{
+ SnItem *self = SN_ITEM(data);
+
+ GError *err = NULL;
+ GDBusProxy *proxy = g_dbus_proxy_new_for_bus_finish(res, &err);
+
+ if (err) {
+ g_warning("Failed to construct gdbusproxy for snitem: %s\n", err->message);
+ g_error_free(err);
+ g_object_unref(self);
+ return;
+ }
+
+ g_debug("Proxy created for snitem at: %s, obj path: %s",
+ g_dbus_proxy_get_name(proxy),
+ g_dbus_proxy_get_object_path(proxy));
+
+ g_object_set(self, "proxy", proxy, NULL);
+
+ g_signal_connect(self->proxy, "g-signal", G_CALLBACK(sn_item_proxy_signal_handler), self);
+
+ const char *iconthemepath;
+ GVariant *iconthemepath_v = g_dbus_proxy_get_cached_property(self->proxy, "IconThemePath");
+ GtkIconTheme *theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
+ if (iconthemepath_v) {
+ g_variant_get(iconthemepath_v, "&s", &iconthemepath);
+ gtk_icon_theme_add_search_path(theme, iconthemepath);
+ g_variant_unref(iconthemepath_v);
+ }
+
+ char *iconname = NULL;
+ GVariant *iconname_v = g_dbus_proxy_get_cached_property(proxy, "IconName");
+ GVariant *iconpixmap_v = g_dbus_proxy_get_cached_property(proxy, "IconPixmap");
+
+ if (iconname_v) {
+ g_variant_get(iconname_v, "s", &iconname);
+ if (strcmp(iconname, "") == 0) {
+ g_free(iconname);
+ iconname = NULL;
+ }
+ g_variant_unref(iconname_v);
+ }
+
+ if (iconname) {
+ self->iconname = iconname;
+ } else if (iconpixmap_v) {
+ g_variant_ref(iconpixmap_v);
+ self->iconpixmaps = iconpixmap_v;
+ } else {
+ self->iconname = g_strdup("noicon");
+ }
+
+ if (iconpixmap_v)
+ g_variant_unref(iconpixmap_v);
+
+ GdkPaintable *paintable;
+ if (self->iconname) {
+ paintable = get_paintable_from_name(self->iconname, self->iconsize);
+
+ } else {
+ paintable = get_paintable_from_data(self->iconpixmaps, self->iconsize);
+ }
+
+ gtk_image_set_from_paintable(GTK_IMAGE(self->image), paintable);
+ g_object_unref(paintable);
+
+ const char *menu_buspath = NULL;
+ GVariant *menu_buspath_v = g_dbus_proxy_get_cached_property(self->proxy, "Menu");
+
+ if (menu_buspath_v) {
+ g_variant_get(menu_buspath_v, "&o", &menu_buspath);
+ SnDbusmenu *dbusmenu = sn_dbusmenu_new(self->busname, menu_buspath, self);
+ g_object_set(self, "dbusmenu", dbusmenu, NULL);
+ g_variant_unref(menu_buspath_v);
+ }
+ self->ready = TRUE;
+ g_object_unref(self);
+}
+
+static void
+sn_item_leftclick_handler(GtkGestureClick *click,
+ int n_press,
+ double x,
+ double y,
+ void *data)
+{
+ SnItem *self = SN_ITEM(data);
+
+ g_dbus_proxy_call(self->proxy,
+ "Activate",
+ g_variant_new("(ii)", 0, 0),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL,
+ NULL);
+}
+
+static void
+sn_item_rightclick_handler_helper(GObject *obj, GAsyncResult *res, void *data)
+{
+ SnItem *self = SN_ITEM(data);
+ if (!self->ready)
+ return;
+ GDBusProxy *proxy = G_DBUS_PROXY(obj);
+
+ GError *err = NULL;
+ GVariant *val = g_dbus_proxy_call_finish(proxy, res, &err);
+
+ // This error is generated when answer for the call arrives after
+ // icon was finalized.
+ if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY)) {
+ g_error_free(err);
+ g_object_unref(self);
+ return;
+
+ // Discord generates the following error here:
+ // 'G_DBUS_ERROR' 'G_DBUS_ERROR_FAILED' 'error occurred in AboutToShow'
+ // We ignore it.
+ } else if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_FAILED) &&
+ g_strrstr(err->message, "error occured in AboutToShow") == 0) {
+ g_error_free(err);
+
+ gtk_popover_popup(GTK_POPOVER(self->popovermenu));
+
+ // Report rest of possible errors
+ } else if (err) {
+ g_warning("%s\n", err->message);
+ g_error_free(err);
+
+ } else {
+ g_variant_unref(val);
+
+ gtk_popover_popup(GTK_POPOVER(self->popovermenu));
+ }
+
+ g_object_unref(self);
+}
+
+static void
+sn_item_rightclick_handler(GtkGestureClick *click,
+ int n_press,
+ double x,
+ double y,
+ void *data)
+{
+ SnItem *self = SN_ITEM(data);
+ if (!self->ready)
+ return;
+
+ GDBusProxy *menuproxy = sn_dbusmenu_get_proxy(self->dbusmenu);
+ if (!G_IS_DBUS_PROXY(menuproxy))
+ return;
+
+ g_dbus_proxy_call(menuproxy,
+ "AboutToShow",
+ g_variant_new("(i)", 0),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ sn_item_rightclick_handler_helper,
+ g_object_ref(self));
+ g_object_unref(menuproxy);
+}
+
+static void sn_item_measure(GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ SnItem *self = SN_ITEM(widget);
+
+ switch (orientation) {
+ case GTK_ORIENTATION_HORIZONTAL:
+ *natural = self->iconsize;
+ *minimum = self->iconsize;
+ *minimum_baseline = -1;
+ *natural_baseline = -1;
+ break;
+ case GTK_ORIENTATION_VERTICAL:
+ *natural = self->iconsize;
+ *minimum = self->iconsize;
+ *minimum_baseline = -1;
+ *natural_baseline = -1;
+ break;
+ }
+}
+
+static void sn_item_size_allocate(GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ SnItem *self = SN_ITEM(widget);
+ gtk_widget_size_allocate(self->image, &(GtkAllocation) {0, 0, width, height}, -1);
+ gtk_popover_present(GTK_POPOVER(self->popovermenu));
+}
+
+static void
+sn_item_set_property(GObject *object, uint property_id, const GValue *value, GParamSpec *pspec)
+{
+ SnItem *self = SN_ITEM(object);
+
+ switch (property_id) {
+ case PROP_BUSNAME:
+ self->busname = g_strdup(g_value_get_string(value));
+ break;
+ case PROP_BUSOBJ:
+ self->busobj = g_strdup(g_value_get_string(value));
+ break;
+ case PROP_PROXY:
+ self->proxy = g_value_get_object(value);
+ break;
+ case PROP_ICONSIZE:
+ self->iconsize = g_value_get_int(value);
+ break;
+ case PROP_ACTIONGROUP:
+ self->actiongroup = g_simple_action_group_new();
+ gtk_widget_insert_action_group(GTK_WIDGET(self),
+ "menuitem",
+ G_ACTION_GROUP(self->actiongroup));
+ break;
+ case PROP_DBUSMENU:
+ self->dbusmenu = g_value_get_object(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+sn_item_get_property(GObject *object, uint property_id, GValue *value, GParamSpec *pspec)
+{
+ SnItem *self = SN_ITEM(object);
+
+ switch (property_id) {
+ case PROP_BUSNAME:
+ g_value_set_string(value, self->busname);
+ break;
+ case PROP_BUSOBJ:
+ g_value_set_string(value, self->busobj);
+ break;
+ case PROP_ICONSIZE:
+ g_value_set_int(value, self->iconsize);
+ break;
+ case PROP_ACTIONGROUP:
+ g_value_set_object(value, self->actiongroup);
+ break;
+ case PROP_DBUSMENU:
+ g_value_set_object(value, self->dbusmenu);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+sn_item_class_init(SnItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+ obj_properties[PROP_BUSNAME] =
+ g_param_spec_string("busname", NULL, NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_BUSOBJ] =
+ g_param_spec_string("busobj", NULL, NULL,
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_ICONSIZE] =
+ g_param_spec_int("iconsize", NULL, NULL,
+ G_MININT,
+ G_MAXINT,
+ 22,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_ACTIONGROUP] =
+ g_param_spec_object("actiongroup", NULL, NULL,
+ G_TYPE_SIMPLE_ACTION_GROUP,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_PROXY] =
+ g_param_spec_object("proxy", NULL, NULL,
+ G_TYPE_DBUS_PROXY,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_DBUSMENU] =
+ g_param_spec_object("dbusmenu", NULL, NULL,
+ SN_TYPE_DBUSMENU,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ widget_class->measure = sn_item_measure;
+ widget_class->size_allocate = sn_item_size_allocate;
+
+ object_class->set_property = sn_item_set_property;
+ object_class->get_property = sn_item_get_property;
+
+ object_class->constructed = sn_item_constructed;
+ object_class->dispose = sn_item_dispose;
+ object_class->finalize = sn_item_finalize;
+
+ g_object_class_install_properties(object_class, N_PROPERTIES, obj_properties);
+}
+
+static void
+sn_item_init(SnItem *self)
+{
+ GtkWidget *widget = GTK_WIDGET(self);
+
+ gtk_widget_set_hexpand(widget, TRUE);
+ gtk_widget_set_vexpand(widget, TRUE);
+
+ self->image = gtk_image_new();
+ gtk_widget_set_hexpand(self->image, TRUE);
+ gtk_widget_set_vexpand(self->image, TRUE);
+
+ gtk_widget_set_parent(self->image, widget);
+
+ GMenu *init_menu = g_menu_new();
+ self->popovermenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(init_menu));
+ g_object_unref(init_menu);
+ gtk_popover_menu_set_flags(GTK_POPOVER_MENU(self->popovermenu), GTK_POPOVER_MENU_NESTED);
+ gtk_popover_set_has_arrow(GTK_POPOVER(self->popovermenu), FALSE);
+ gtk_widget_set_parent(self->popovermenu, widget);
+
+ GtkGesture *leftclick = gtk_gesture_click_new();
+ gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(leftclick), 1);
+ g_signal_connect(leftclick, "pressed", G_CALLBACK(sn_item_leftclick_handler), self);
+ gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(leftclick));
+
+ GtkGesture *rightclick = gtk_gesture_click_new();
+ gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(rightclick), 3);
+ g_signal_connect(rightclick, "pressed", G_CALLBACK(sn_item_rightclick_handler), self);
+ gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(rightclick));
+}
+
+static void
+sn_item_constructed(GObject *obj)
+{
+ SnItem *self = SN_ITEM(obj);
+
+ GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(STATUSNOTIFIERITEM_XML, NULL);
+ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ nodeinfo->interfaces[0],
+ self->busname,
+ self->busobj,
+ "org.kde.StatusNotifierItem",
+ NULL,
+ sn_item_proxy_ready_handler,
+ g_object_ref(self));
+ g_dbus_node_info_unref(nodeinfo);
+
+ G_OBJECT_CLASS(sn_item_parent_class)->constructed(obj);
+}
+
+
+static void
+sn_item_dispose(GObject *obj)
+{
+ SnItem *self = SN_ITEM(obj);
+ g_debug("Disposing snitem %s %s",
+ self->busname, self->busobj);
+
+ if (self->dbusmenu) {
+ g_object_unref(self->dbusmenu);
+ self->dbusmenu = NULL;
+ }
+
+ G_OBJECT_CLASS(sn_item_parent_class)->dispose(obj);
+}
+
+static void
+sn_item_finalize(GObject *object)
+{
+ SnItem *self = SN_ITEM(object);
+
+ gtk_widget_unparent(self->popovermenu);
+ gtk_widget_unparent(self->image);
+
+ g_free(self->busname);
+ g_free(self->busobj);
+ g_free(self->iconname);
+
+ if (self->iconpixmaps) {
+ g_variant_unref(self->iconpixmaps);
+ }
+
+ G_OBJECT_CLASS(sn_item_parent_class)->finalize(object);
+}
+
+/* PUBLIC METHODS */
+void
+sn_item_set_menu_model(SnItem *self, GMenu* menu)
+{
+ gtk_popover_menu_set_menu_model(GTK_POPOVER_MENU(self->popovermenu), G_MENU_MODEL(menu));
+}
+
+GSimpleActionGroup*
+sn_item_get_actiongroup(SnItem *self)
+{
+ GSimpleActionGroup *actiongroup;
+ g_object_get(self, "actiongroup", &actiongroup, NULL);
+
+ return actiongroup;
+}
+
+void
+sn_item_add_action(SnItem *self, GSimpleAction *action)
+{
+ g_action_map_add_action(G_ACTION_MAP(self->actiongroup), G_ACTION(action));
+}
+
+char*
+sn_item_get_busname(SnItem *self)
+{
+ char *busname;
+ g_object_get(self, "busname", &busname, NULL);
+
+ return busname;
+}
+
+SnItem*
+sn_item_new(const char *busname, const char *busobj, int iconsize)
+{
+ return g_object_new(SN_TYPE_ITEM,
+ "busname", busname,
+ "busobj", busobj,
+ "iconsize", iconsize,
+ NULL);
+}
+/* PUBLIC METHODS */
diff --git a/systray/snitem.h b/systray/snitem.h
@@ -0,0 +1,78 @@
+#ifndef SNITEM_H
+#define SNITEM_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SN_TYPE_ITEM sn_item_get_type()
+G_DECLARE_FINAL_TYPE(SnItem, sn_item, SN, ITEM, GtkWidget)
+
+SnItem* sn_item_new (const char *busname,
+ const char *busobj,
+ int iconsize);
+
+void sn_item_set_menu_model (SnItem *widget, GMenu *menu);
+void sn_item_add_action (SnItem *self, GSimpleAction *action);
+char* sn_item_get_busname (SnItem *self);
+
+G_END_DECLS
+
+#define STATUSNOTIFIERITEM_XML \
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
+ "<node>\n" \
+ " <interface name=\"org.kde.StatusNotifierItem\">\n" \
+ " <!-- methods -->\n" \
+ " <method name=\"Activate\">\n" \
+ " <arg name=\"x\" type=\"i\" direction=\"in\"/>\n" \
+ " <arg name=\"y\" type=\"i\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <!--\n" \
+ " <method name=\"Scroll\">\n" \
+ " <arg name=\"delta\" type=\"i\" direction=\"in\"/>\n" \
+ " <arg name=\"orientation\" type=\"s\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ContextMenu\">\n" \
+ " <arg name=\"x\" type=\"i\" direction=\"in\"/>\n" \
+ " <arg name=\"y\" type=\"i\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <method name=\"SecondaryActivate\">\n" \
+ " <arg name=\"x\" type=\"i\" direction=\"in\"/>\n" \
+ " <arg name=\"y\" type=\"i\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " -->\n" \
+ " <!-- properties -->\n" \
+ " <property name=\"Menu\" type=\"o\" access=\"read\"/>\n" \
+ " <property name=\"IconName\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"IconPixmap\" type=\"a(iiay)\" access=\"read\"/>\n" \
+ " <property name=\"IconThemePath\" type=\"s\" access=\"read\"/>\n" \
+ " <!--\n" \
+ " <property name=\"OverlayIconName\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"OverlayIconPixmap\" type=\"a(iiay)\" access=\"read\"/>\n" \
+ " <property name=\"AttentionIconName\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"AttentionIconPixmap\" type=\"a(iiay)\" access=\"read\"/>\n" \
+ " <property name=\"Category\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Id\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Title\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Status\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"WindowId\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"ItemIsMenu\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"AttentionMovieName\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"ToolTip\" type=\"(sa(iiay)ss)\" access=\"read\"/>\n" \
+ " -->\n" \
+ " <!-- signals -->\n" \
+ " <signal name=\"NewIcon\"/>\n" \
+ " <!--\n" \
+ " <signal name=\"NewAttentionIcon\"/>\n" \
+ " <signal name=\"NewOverlayIcon\"/>\n" \
+ " <signal name=\"NewTitle\"/>\n" \
+ " <signal name=\"NewToolTip\"/>\n" \
+ " <signal name=\"NewStatus\">\n" \
+ " <arg name=\"status\" type=\"s\"/>\n" \
+ " </signal>\n" \
+ " -->\n" \
+ " </interface>\n" \
+ "</node>\n"
+
+#endif /* SNITEM_H */
diff --git a/systray/statusnotifierhost.c b/systray/statusnotifierhost.c
@@ -1,431 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <string.h>
-
-#include <glib.h>
-#include <glib-object.h>
-#include <gio/gio.h>
-#include <gtk/gtk.h>
-
-#include "dwlbtray.h"
-
-static void unregister_all(StatusNotifierHost *snhost);
-static void sub_finalize(StatusNotifierHost *snhost);
-static void busobj_finalize(StatusNotifierHost *snhost);
-static void unregister_statusnotifieritem(StatusNotifierItem *snitem);
-static void handle_method_call(GDBusConnection* conn, const char* sender,
- const char* object_path, const char* iface, const char* method,
- GVariant* parameters, GDBusMethodInvocation* invocation,
- StatusNotifierHost* snhost
-);
-static GVariant* handle_get_prop(GDBusConnection* conn, const char* sender,
- const char* obj_path, const char* iface_name,
- const char* prop, GError** error, StatusNotifierHost* snhost
-);
-
-
-static GDBusInterfaceVTable interface_vtable = {
- (GDBusInterfaceMethodCallFunc)handle_method_call,
- (GDBusInterfaceGetPropertyFunc)handle_get_prop,
- NULL
-};
-
-
-static void
-add_trayitem_name_to_builder(StatusNotifierItem *snitem, GVariantBuilder *builder)
-{
- g_variant_builder_add_value(builder, g_variant_new_string(snitem->busname));
-}
-
-
-static int
-find_snitem(StatusNotifierItem *snitem, const char *busname_match)
-{
- if (strcmp(snitem->busname, busname_match) == 0)
- return 0;
- else
- return -1;
-}
-
-
-static void
-unregister_all_wrap(StatusNotifierItem *snitem, void *data)
-{
- unregister_statusnotifieritem(snitem);
-}
-
-
-static void
-unregister_all(StatusNotifierHost *snhost)
-{
- g_slist_foreach(snhost->trayitems, (GFunc)unregister_all_wrap, NULL);
-}
-
-
-void
-dwlb_request_resize(StatusNotifierHost *snhost)
-{
- if (snhost->in_exit)
- snhost->curwidth = 0;
- else if (snhost->noitems <= 1)
- snhost->curwidth = 22;
- else
- snhost->curwidth = 22 * snhost->noitems - 6; // dunno why substract 6 to make it align, just trial and error until it worked
-
- struct sockaddr_un sockaddr;
- sockaddr.sun_family = AF_UNIX;
- char *socketpath = g_strdup_printf("%s/dwlb/dwlb-0", g_get_user_runtime_dir());
- snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", socketpath);
- char *sockbuf = NULL;
- if (snhost->traymon) {
- sockbuf = g_strdup_printf("%s %s %i", snhost->traymon, "resize", snhost->curwidth);
- }
- else {
- sockbuf = g_strdup_printf("%s %s %i", "all", "resize", snhost->curwidth);
- }
-
- size_t len = strlen(sockbuf);
- int sock_fd = socket(AF_UNIX, SOCK_STREAM, 1);
-
- int connstatus = connect(sock_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
- if (connstatus != 0) {
- g_error("Error connecting to dwlb socket");
- }
-
- if (send(sock_fd, sockbuf, len, 0) == -1)
- g_error("Could not send size update to %s\n", sockaddr.sun_path);
- close(sock_fd);
-
- g_free(socketpath);
- g_free(sockbuf);
-}
-
-
-static void
-register_statusnotifieritem(GDBusConnection *conn,
- const char *busname,
- const char *busobj,
- StatusNotifierHost *snhost)
-{
- g_debug("Registering %s\n", busname);
- StatusNotifierItem *snitem;
- snitem = g_malloc0(sizeof(StatusNotifierItem));
- g_bit_lock(&snitem->lock, 0);
- snitem->host = snhost;
- snitem->busname = g_strdup(busname);
- snitem->isclosing = FALSE;
- snitem->host->noitems = snitem->host->noitems + 1;
-
- snhost->trayitems = g_slist_prepend(snhost->trayitems, snitem);
- dwlb_request_resize(snitem->host);
-
- GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(STATUSNOTIFIERITEM_XML, NULL);
-
- g_dbus_proxy_new(conn,
- G_DBUS_PROXY_FLAGS_NONE,
- nodeinfo->interfaces[0],
- snitem->busname,
- busobj,
- "org.kde.StatusNotifierItem",
- NULL,
- (GAsyncReadyCallback)create_trayitem,
- snitem);
-
- g_dbus_node_info_unref(nodeinfo);
-
- GError *err = NULL;
- g_dbus_connection_emit_signal(conn,
- NULL,
- "/StatusNotifierWatcher",
- "org.kde.StatusNotifierWatcher",
- "StatusNotifierItemRegistered",
- g_variant_new("(s)", snitem->busname),
- &err);
- if (err) {
- g_warning("%s\n", err->message);
- g_error_free(err);
- }
-}
-
-
-static void
-unregister_statusnotifieritem(StatusNotifierItem *snitem)
-{
- g_bit_lock(&snitem->lock, 0);
- g_debug("Unregistering %s\n", snitem->busname);
- if (snitem->popovermenu) {
- gtk_popover_menu_set_menu_model(GTK_POPOVER_MENU(snitem->popovermenu), NULL);
- gtk_widget_unparent(snitem->popovermenu);
- snitem->popovermenu = NULL;
- }
-
- char *actiongroupname = g_strdelimit(g_strdup(snitem->busname), ".", '_');
- actiongroupname = g_strdelimit(actiongroupname, ":", '_');
-
- if (snitem->icon) {
- gtk_widget_insert_action_group(snitem->icon, actiongroupname, NULL);
- gtk_box_remove(GTK_BOX(snitem->host->box), snitem->icon);
- snitem->icon = NULL;
- }
-
- g_free(actiongroupname);
-
- g_dbus_connection_emit_signal(snitem->host->conn,
- NULL,
- "/StatusNotifierWatcher",
- "org.kde.StatusNotifierWatcher",
- "StatusNotifierItemUnregistered",
- g_variant_new("(s)", snitem->busname),
- NULL);
-
- if (snitem->menuproxy)
- g_object_unref(snitem->menuproxy);
- if (snitem->proxy)
- g_object_unref(snitem->proxy);
-
- if (snitem->paintable) {
- g_object_unref(snitem->paintable);
- if (snitem->iconpixmap_v)
- g_variant_unref(snitem->iconpixmap_v);
- if (snitem->iconname)
- g_free(snitem->iconname);
- }
-
- g_free(snitem->busname);
-
- snitem->host->trayitems = g_slist_remove(snitem->host->trayitems, snitem);
- snitem->host->noitems = snitem->host->noitems - 1;
- dwlb_request_resize(snitem->host);
- g_free(snitem);
- snitem = NULL;
-
-}
-
-
-static void
-handle_method_call(GDBusConnection *conn,
- const char *sender,
- const char *object_path,
- const char *interface_name,
- const char *method_name,
- GVariant *parameters,
- GDBusMethodInvocation *invocation,
- StatusNotifierHost *snhost)
-{
- if (strcmp(method_name, "RegisterStatusNotifierItem") == 0) {
- const char *param;
- const char *busobj;
- const char *registree_name;
-
- g_variant_get(parameters, "(&s)", ¶m);
-
- if (g_str_has_prefix(param, "/")) {
- busobj = param;
- } else {
- busobj = "/StatusNotifierItem";
- }
-
- if (g_str_has_prefix(param, ":") && strcmp(sender, param) != 0)
- registree_name = param;
- else
- registree_name = sender;
-
- register_statusnotifieritem(conn, registree_name, busobj, snhost);
- g_dbus_method_invocation_return_value(invocation, NULL);
-
- } else {
- g_dbus_method_invocation_return_dbus_error(invocation,
- "org.freedesktop.DBus.Error.UnknownMethod",
- "Unknown method");
- }
-}
-
-
-static GVariant*
-handle_get_prop(GDBusConnection* conn,
- const char* sender,
- const char* object_path,
- const char* interface_name,
- const char* property_name,
- GError** err,
- StatusNotifierHost *snhost)
-{
- if (strcmp(property_name, "ProtocolVersion") == 0) {
- return g_variant_new("i", 0);
- } else if (strcmp(property_name, "IsStatusNotifierHostRegistered") == 0) {
- return g_variant_new("b", TRUE);
- } else if (strcmp(property_name, "RegisteredStatusNotifierItems") == 0) {
- if (!snhost->trayitems)
- return g_variant_new("as", NULL);
-
- GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
- g_slist_foreach(snhost->trayitems, (GFunc)add_trayitem_name_to_builder, builder);
- GVariant *as = g_variant_builder_end(builder);
-
- g_variant_builder_unref(builder);
-
- return as;
- } else {
- g_set_error(err,
- G_DBUS_ERROR,
- G_DBUS_ERROR_UNKNOWN_PROPERTY,
- "Unknown property '%s'.",
- property_name);
- g_error_free(*err);
- return NULL;
- }
-}
-
-// ugly
-static gboolean
-unregister_after_timeout(StatusNotifierItem *snitem)
-{
- unregister_statusnotifieritem(snitem);
- return G_SOURCE_REMOVE;
-}
-
-
-// Finds trayitems which dropped from the bus and untracks them
-static void
-monitor_bus(GDBusConnection* conn,
- const char* sender,
- const char* objpath,
- const char* iface_name,
- const char* signame,
- GVariant *params,
- StatusNotifierHost *snhost)
-{
- if (strcmp(signame, "NameOwnerChanged") == 0) {
- if (!snhost->trayitems)
- return;
-
- const char *name;
- const char *old_owner;
- const char *new_owner;
- g_variant_get(params, "(&s&s&s)", &name, &old_owner, &new_owner);
- if (strcmp(new_owner, "") == 0) {
- GSList *pmatch = g_slist_find_custom(snhost->trayitems, name, (GCompareFunc)find_snitem);
- if (pmatch) {
- StatusNotifierItem *snitem = pmatch->data;
- snitem->isclosing = TRUE;
- // ugly
- g_timeout_add_seconds(2, (GSourceFunc)unregister_after_timeout, snitem);
- }
- }
-
- }
-}
-
-
-static void
-bus_acquired_handler(GDBusConnection *conn, const char *busname, StatusNotifierHost *snhost)
-{
- GError *err = NULL;
- GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(STATUSNOTIFIERWATCHER_XML, NULL);
-
- snhost->obj_id = g_dbus_connection_register_object(conn,
- "/StatusNotifierWatcher",
- nodeinfo->interfaces[0],
- &interface_vtable,
- snhost, // udata
- (GDestroyNotify)busobj_finalize, // udata_free_func
- &err);
-
- g_dbus_node_info_unref(nodeinfo);
-
- if (err) {
- g_error("%s\n", err->message);
- g_error_free(err);
- exit(-1);
- }
-
- snhost->sub_id = g_dbus_connection_signal_subscribe(conn,
- NULL, // Listen to all senders);
- "org.freedesktop.DBus",
- "NameOwnerChanged",
- NULL, // Match all obj paths
- NULL, // Match all arg0s
- G_DBUS_SIGNAL_FLAGS_NONE,
- (GDBusSignalCallback)monitor_bus,
- snhost,
- (GDestroyNotify)sub_finalize);
-}
-
-
-static void
-name_acquired_handler(GDBusConnection *conn, const char *busname, StatusNotifierHost *snhost)
-{
- GError *err = NULL;
- snhost->conn = conn;
-
- g_dbus_connection_emit_signal(conn,
- NULL,
- "/StatusNotifierWatcher",
- "org.kde.StatusNotifierWatcher",
- "StatusNotifierHostRegistered",
- NULL,
- &err);
-
- if (err) {
- g_warning("%s\n", err->message);
- g_error_free(err);
- }
-}
-
-
-static void
-name_lost_handler(GDBusConnection *conn, const char *busname, StatusNotifierHost *snhost)
-{
- g_error("Could not acquire %s\n", busname);
- exit(-1);
-}
-
-
-static void
-snhost_finalize(StatusNotifierHost *snhost)
-{
- snhost->in_exit = TRUE;
- dwlb_request_resize(snhost);
- gtk_window_close(snhost->window);
- snhost = NULL;
-}
-
-
-static void
-busobj_finalize(StatusNotifierHost *snhost)
-{
- unregister_all(snhost);
- g_slist_free(snhost->trayitems);
- g_bus_unown_name(snhost->owner_id);
-}
-
-
-static void
-sub_finalize(StatusNotifierHost *snhost)
-{
- g_dbus_connection_unregister_object(snhost->conn, snhost->obj_id);
-}
-
-
-void
-terminate_statusnotifierhost(StatusNotifierHost *snhost)
-{
- g_dbus_connection_signal_unsubscribe(snhost->conn, snhost->sub_id);
-}
-
-
-void
-start_statusnotifierhost(StatusNotifierHost *snhost)
-{
- snhost->owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
- "org.kde.StatusNotifierWatcher",
- G_BUS_NAME_OWNER_FLAGS_NONE,
- (GBusAcquiredCallback)bus_acquired_handler,
- (GBusNameAcquiredCallback)name_acquired_handler,
- (GBusNameLostCallback)name_lost_handler,
- snhost,
- (GDestroyNotify)snhost_finalize);
-}
diff --git a/systray/statusnotifieritem.c b/systray/statusnotifieritem.c
@@ -1,441 +0,0 @@
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-
-#include <glib.h>
-#include <glib-object.h>
-#include <gio/gio.h>
-#include <gdk/gdk.h>
-#include <gdk-pixbuf/gdk-pixbuf.h>
-#include <gtk/gtk.h>
-
-#include "dwlbtray.h"
-
-
-static void
-argb_to_rgba(int32_t width, int32_t height, unsigned char *icon_data)
-{
- // Icon data is ARGB, gdk textures are RGBA. Flip the channels
- // Shamelessly copied from Waybar
- for (int32_t i = 0; i < 4 * width * height; i += 4) {
- unsigned char alpha = icon_data[i];
- icon_data[i] = icon_data[i + 1];
- icon_data[i + 1] = icon_data[i + 2];
- icon_data[i + 2] = icon_data[i + 3];
- icon_data[i + 3] = alpha;
- }
-}
-
-
-static void
-on_leftclick_cb(GtkGestureClick *click,
- int n_press,
- double x,
- double y,
- StatusNotifierItem *snitem)
-{
- if (snitem && snitem->menuproxy && !snitem->isclosing)
- g_dbus_proxy_call(snitem->proxy,
- "Activate",
- g_variant_new("(ii)", 0, 0),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- NULL,
- NULL,
- NULL);
-}
-
-
-static void
-rightclick_validate(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem)
-{
- GError *err = NULL;
- GVariant *val = g_dbus_proxy_call_finish(proxy, res, &err);
-
- // This error is generated when answer for the call arrives after
- // icon was finalized.
- if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY)) {
- g_error_free(err);
- return;
-
- // Discord generates the following error here:
- // 'G_DBUS_ERROR' 'G_DBUS_ERROR_FAILED' 'error occurred in AboutToShow'
- // We ignore it.
- } else if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_FAILED) &&
- g_strrstr(err->message, "error occured in AboutToShow") == 0) {
- g_error_free(err);
-
- if (snitem && snitem->icon && GTK_IS_WIDGET(snitem->icon) &&
- snitem->popovermenu && GTK_IS_WIDGET(snitem->popovermenu))
- gtk_popover_popup(GTK_POPOVER(snitem->popovermenu));
-
- // Report rest of possible errors
- } else if (err) {
- g_warning("%s\n", err->message);
- g_error_free(err);
-
- } else {
- g_variant_unref(val);
- if (snitem && snitem->icon && GTK_IS_WIDGET(snitem->icon) &&
- snitem->popovermenu && GTK_IS_WIDGET(snitem->popovermenu))
- gtk_popover_popup(GTK_POPOVER(snitem->popovermenu));
- }
-}
-
-
-static void
-on_rightclick_cb(GtkGestureClick *click,
- int n_press,
- double x,
- double y,
- StatusNotifierItem *snitem)
-{
- if (snitem && snitem->menuproxy && !snitem->isclosing) {
- if (snitem && snitem->icon && GTK_IS_WIDGET(snitem->icon) &&
- snitem->popovermenu && GTK_IS_WIDGET(snitem->popovermenu))
- gtk_popover_popdown(GTK_POPOVER(snitem->popovermenu));
-
- g_dbus_proxy_call(snitem->menuproxy,
- "AboutToShow",
- g_variant_new("(i)", 0),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- NULL,
- (GAsyncReadyCallback)rightclick_validate,
- snitem);
- }
-}
-
-
-static void
-pb_destroy(unsigned char *pixeld, void *data)
-{
- g_free(pixeld);
-}
-
-
-static GVariant*
-select_icon_by_size(GVariant *icondata_v, int target_size)
-{
- // Apps broadcast icons as variant a(iiay)
- // Meaning array of tuples, tuple representing an icon
- // first 2 members ii in each tuple are width and height
- // We iterate the array and pick the icon size closest to
- // the target based on its width and save the index
- GVariantIter iter;
- int selected_index = 0;
- int current_index = 0;
- int32_t target_icon_size = (int32_t)target_size;
- int32_t diff = INT32_MAX;
- GVariant *child;
- g_variant_iter_init(&iter, icondata_v);
- while ((child = g_variant_iter_next_value(&iter))) {
- int32_t curwidth;
- g_variant_get_child(child, 0, "i", &curwidth);
- int32_t curdiff;
- if (curwidth > target_icon_size)
- curdiff = curwidth - target_icon_size;
- else
- curdiff = target_icon_size - curwidth;
-
- if (curdiff < diff)
- selected_index = current_index;
-
- current_index = current_index + 1;
- g_variant_unref(child);
- }
-
- GVariant *iconpixmap_v = g_variant_get_child_value(icondata_v,
- (size_t)selected_index);
-
- return iconpixmap_v;
-}
-
-
-static GdkPaintable*
-get_paintable_from_data(GVariant *icondata_v, int target_size)
-{
- GdkPaintable *paintable;
- GVariantIter iter;
- GVariant *iconpixmap_v = select_icon_by_size(icondata_v, target_size);
-
- int32_t width;
- int32_t height;
- GVariant *icon_data_v;
-
- g_variant_iter_init(&iter, iconpixmap_v);
-
- g_variant_iter_next(&iter, "i", &width);
- g_variant_iter_next(&iter, "i", &height);
- icon_data_v = g_variant_iter_next_value(&iter);
-
- size_t size = g_variant_get_size(icon_data_v);
- const void *icon_data_dup = g_variant_get_data(icon_data_v);
-
- unsigned char *icon_data = g_memdup2(icon_data_dup, size);
- argb_to_rgba(width, height, icon_data);
-
- int32_t padding = size / height - 4 * width;
- int32_t rowstride = 4 * width + padding;
-
- GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(icon_data,
- GDK_COLORSPACE_RGB,
- TRUE,
- 8,
- width,
- height,
- rowstride,
- (GdkPixbufDestroyNotify)pb_destroy,
- NULL);
-
- GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf);
- paintable = GDK_PAINTABLE(texture);
-
- g_object_unref(pixbuf);
- g_variant_unref(icon_data_v);
- g_variant_unref(iconpixmap_v);
-
- return paintable;
-}
-
-
-static GdkPaintable*
-get_paintable_from_name(const char *iconname, int target_size)
-{
- GdkPaintable *paintable = NULL;
- GtkIconPaintable *icon;
-
- GtkIconTheme *theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
- icon = gtk_icon_theme_lookup_icon(theme,
- iconname,
- NULL, // const char **fallbacks
- target_size,
- 1,
- GTK_TEXT_DIR_LTR,
- 0); // GtkIconLookupFlags
- paintable = GDK_PAINTABLE(icon);
-
- return paintable;
-}
-
-
-static void
-new_iconname_handler(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem)
-{
- GError *err = NULL;
- GVariant *data = g_dbus_proxy_call_finish(proxy, res, &err);
- // (v)
-
- if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) {
- g_error_free(err);
- return;
- } else if (err) {
- g_warning("%s\n", err->message);
- g_error_free(err);
- return;
- }
-
- GVariant *iconname_v;
- const char *iconname = NULL;
- g_variant_get(data, "(v)", &iconname_v);
- g_variant_get(iconname_v, "&s", &iconname);
- g_variant_unref(iconname_v);
-
- if (strcmp(iconname, snitem->iconname) == 0) {
- // g_debug("%s got NewIcon, but iconname didn't change. Ignoring\n", snitem->busname);
- g_variant_unref(data);
- return;
- }
-
- g_free(snitem->iconname);
- g_object_unref(snitem->paintable);
-
- snitem->iconname = g_strdup(iconname);
- snitem->paintable = get_paintable_from_name(snitem->iconname, snitem->host->height);
- gtk_image_set_from_paintable(GTK_IMAGE(snitem->icon), snitem->paintable);
-
- g_variant_unref(data);
-}
-
-
-static void
-new_iconpixmap_handler(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem)
-{
- GError *err = NULL;
- GVariant *data = g_dbus_proxy_call_finish(proxy, res, &err);
- // (v)
-
- if (err && g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT)) {
- g_error_free(err);
- return;
- } else if (err) {
- g_warning("%s\n", err->message);
- g_error_free(err);
- return;
- }
-
- GVariant *newpixmap_v;
- g_variant_get(data, "(v)", &newpixmap_v);
- g_variant_unref(data);
-
- if (g_variant_equal(newpixmap_v, snitem->iconpixmap_v)) {
- // g_debug ("%s got NewIcon, but iconpixmap didn't change. Ignoring\n", snitem->busname);
- g_variant_unref(newpixmap_v);
- return;
- }
-
- g_object_unref(snitem->paintable);
- g_variant_unref(snitem->iconpixmap_v);
- snitem->iconpixmap_v = newpixmap_v;
- GdkPaintable *paintable = get_paintable_from_data(snitem->iconpixmap_v,
- snitem->host->height);
- gtk_image_set_from_paintable(GTK_IMAGE (snitem->icon), paintable);
- snitem->paintable = paintable;
-}
-
-
-static void
-trayitem_signal_handler(GDBusProxy *proxy,
- const char *sender,
- const char *signal,
- GVariant *data_v,
- StatusNotifierItem *snitem)
-{
- if (strcmp(signal, "NewIcon") == 0) {
- if (snitem->iconpixmap_v)
- g_dbus_proxy_call(proxy,
- "org.freedesktop.DBus.Properties.Get",
- g_variant_new("(ss)", "org.kde.StatusNotifierItem", "IconPixmap"),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- NULL,
- (GAsyncReadyCallback)new_iconpixmap_handler,
- snitem);
-
- if (snitem->iconname)
- g_dbus_proxy_call(proxy,
- "org.freedesktop.DBus.Properties.Get",
- g_variant_new("(ss)", "org.kde.StatusNotifierItem", "IconName"),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- NULL,
- (GAsyncReadyCallback) new_iconname_handler,
- snitem);
- }
-}
-
-
-static GtkWidget*
-create_icon(GDBusProxy *proxy, StatusNotifierItem *snitem)
-{
-
- GtkWidget *image = NULL;
-
- const char *iconname = NULL;
- GdkPaintable *paintable = NULL;
- GVariant *iconname_v = g_dbus_proxy_get_cached_property(proxy, "IconName");
- GVariant *iconpixmap_v = g_dbus_proxy_get_cached_property(proxy, "IconPixmap");
-
- if (iconname_v) {
- g_variant_get(iconname_v, "&s", &iconname);
- g_variant_unref(iconname_v);
- }
-
- if (iconname && strcmp(iconname, "") != 0) {
- paintable = get_paintable_from_name(iconname, snitem->host->height);
-
- snitem->iconname = g_strdup(iconname);
-
- if (iconpixmap_v)
- g_variant_unref(iconpixmap_v);
-
- } else if (iconpixmap_v) {
- paintable = get_paintable_from_data(iconpixmap_v, snitem->host->height);
-
- snitem->iconpixmap_v = iconpixmap_v;
- } else {
- paintable = get_paintable_from_name("missingicon", snitem->host->height);
- }
-
- image = gtk_image_new_from_paintable(paintable);
- snitem->paintable = paintable;
-
- return image;
-}
-
-
-void
-create_trayitem(GObject *obj, GAsyncResult *res, StatusNotifierItem *snitem)
-{
- GError *err = NULL;
- GDBusProxy *proxy = g_dbus_proxy_new_finish(res, &err);
-
- if (err) {
- g_error("%s\n", err->message);
- g_error_free(err);
- }
-
- // If this happens for whatever reason, we lose track of
- // our window size (there will be a gap between systray and bar)
- if (!snitem && proxy) {
- g_object_unref(proxy);
- return;
- } else if (!snitem) {
- return;
- }
-
- snitem->proxy = proxy;
-
- GVariant *iconthemepath_v;
- const char *iconthemepath;
- GtkIconTheme *theme;
- GtkGesture *leftclick;
- GtkGesture *rightclick;
- GVariant *menu_buspath_v;
- const char *menu_buspath;
- GtkWidget *icon;
-
- g_signal_connect(proxy, "g-signal", G_CALLBACK(trayitem_signal_handler), snitem);
-
- iconthemepath_v = g_dbus_proxy_get_cached_property(proxy, "IconThemePath");
- theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
- if (iconthemepath_v) {
- g_variant_get(iconthemepath_v, "&s", &iconthemepath);
- gtk_icon_theme_add_search_path(theme, iconthemepath);
- g_variant_unref(iconthemepath_v);
- }
-
- icon = create_icon(proxy, snitem);
- snitem->icon = icon;
-
- leftclick = gtk_gesture_click_new();
- gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(leftclick), 1);
- g_signal_connect(leftclick, "pressed", G_CALLBACK(on_leftclick_cb), snitem);
-
- rightclick = gtk_gesture_click_new();
- gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(rightclick), 3);
- g_signal_connect(rightclick, "pressed", G_CALLBACK(on_rightclick_cb), snitem);
-
- gtk_widget_add_controller(icon, GTK_EVENT_CONTROLLER(leftclick));
- gtk_widget_add_controller(icon, GTK_EVENT_CONTROLLER(rightclick));
- gtk_box_append(GTK_BOX(snitem->host->box), icon);
-
- menu_buspath_v = g_dbus_proxy_get_cached_property(proxy, "Menu");
- if (menu_buspath_v) {
- g_variant_get(menu_buspath_v, "&o", &menu_buspath);
- GDBusNodeInfo *nodeinfo = g_dbus_node_info_new_for_xml(DBUSMENU_XML, NULL);
- g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
- G_DBUS_PROXY_FLAGS_NONE,
- nodeinfo->interfaces[0],
- snitem->busname,
- menu_buspath,
- "com.canonical.dbusmenu",
- NULL,
- (GAsyncReadyCallback)create_menu,
- snitem);
- g_dbus_node_info_unref(nodeinfo);
- g_variant_unref(menu_buspath_v);
- } else {
- g_bit_unlock(&snitem->lock, 0);
- }
-}