dwlb

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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:
Msystray/Makefile | 5+++--
Dsystray/dbusmenu.c | 434-------------------------------------------------------------------------------
Msystray/dwlbtray.c | 107++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Dsystray/dwlbtray.h | 211-------------------------------------------------------------------------------
Asystray/sndbusmenu.c | 592+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asystray/sndbusmenu.h | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asystray/snhost.c | 546+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asystray/snhost.h | 45+++++++++++++++++++++++++++++++++++++++++++++
Asystray/snitem.c | 754+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asystray/snitem.h | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsystray/statusnotifierhost.c | 431-------------------------------------------------------------------------------
Dsystray/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)", &param); + + 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)", &param); - - 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); - } -}