commit f19f1105ed8220e81fa89884fcba1a841175242f
parent 78a53029bdaf30f92de695c0a7bf66be7d606361
Author: Janne Veteläinen <janne.vetelainen@elisanet.fi>
Date: Fri, 22 Mar 2024 00:29:11 +0200
Add statusnotifieritem based systray
Diffstat:
13 files changed, 1581 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -9,3 +9,7 @@ xdg-output-unstable-v1-protocol.c
xdg-output-unstable-v1-protocol.h
dwl-ipc-unstable-v2-protocol.c
dwl-ipc-unstable-v2-protocol.h
+dwlbtray
+valgrind*
+.cache
+compile_commands.json
diff --git a/Makefile b/Makefile
@@ -1,8 +1,10 @@
-BINS = dwlb
+BINS = dwlb dwlbtray
MANS = dwlb.1
+RESOURCES = Resources/DBusMenu.xml Resources/StatusNotifierItem.xml Resources/StatusNotifierWatcher.xml Resources/boxbg.css
PREFIX ?= /usr/local
-CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-format-truncation -g
+CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-format-truncation -g \
+ -DBUILD_DIR=\"$(shell pwd)/\"
all: $(BINS)
@@ -15,6 +17,7 @@ clean:
install: all
install -D -t $(PREFIX)/bin $(BINS)
install -D -m0644 -t $(PREFIX)/share/man/man1 $(MANS)
+ install -D -m0644 -t $(PREFIX)/share/dwlb $(RESOURCES)
WAYLAND_PROTOCOLS=$(shell pkg-config --variable=pkgdatadir wayland-protocols)
WAYLAND_SCANNER=$(shell pkg-config --variable=wayland_scanner wayland-scanner)
@@ -48,8 +51,16 @@ dwlb.o: utf8.h config.h xdg-shell-protocol.h xdg-output-unstable-v1-protocol.h w
# Protocol dependencies
dwlb: xdg-shell-protocol.o xdg-output-unstable-v1-protocol.o wlr-layer-shell-unstable-v1-protocol.o dwl-ipc-unstable-v2-protocol.o
+statusnotifierhost.o: dwlbtray.h
+statusnotifieritem.o: dwlbtray.h
+dbusmenu.o: dwlbtray.h
+dwlbtray.o: dwlbtray.h
+dwlbtray: dwlbtray.o statusnotifierhost.o statusnotifieritem.o dbusmenu.o
+
# Library dependencies
dwlb: CFLAGS+=$(shell pkg-config --cflags wayland-client wayland-cursor fcft pixman-1)
dwlb: LDLIBS+=$(shell pkg-config --libs wayland-client wayland-cursor fcft pixman-1) -lrt
+dwlbtray: CFLAGS+=$(shell pkg-config --cflags glib-2.0 gtk4 gtk4-layer-shell-0)
+dwlbtray: LDLIBS+=$(shell pkg-config --libs glib-2.0 gtk4 gtk4-layer-shell-0) -lrt
.PHONY: all clean install
diff --git a/README.md b/README.md
@@ -3,6 +3,8 @@
A fast, feature-complete bar for [dwl](https://github.com/djpohly/dwl).
+This fork adds the systemtray feature implementing KDE's KStatusNotifierItem spec
+


</div>
@@ -12,6 +14,8 @@ A fast, feature-complete bar for [dwl](https://github.com/djpohly/dwl).
* libwayland-cursor
* pixman
* fcft
+* gtk4
+* [gtk4-layer-shell](https://github.com/wmww/gtk4-layer-shell)
## Installation
```bash
@@ -27,6 +31,11 @@ Pass `dwlb` as an argument to dwl's `-s` flag. This will populate each connected
dwl -s 'dwlb -font "monospace:size=16"'
```
+To specify on which monitor the systray will appear, append option `-traymon [OUTPUT]` to the command line:
+```bash
+dwl -s 'dwlb -font "monospace:size=16" -traymon DP-1
+```
+
## Ipc
If dwl is [patched](https://github.com/djpohly/dwl/wiki/ipc) appropriately, dwlb is capable of communicating directly with dwl. When ipc is enabled with `-ipc`, dwlb does not read from stdin, and clicking tags functions as you would expect. Ipc can be disabled with `-no-ipc`.
diff --git a/Resources/DBusMenu.xml b/Resources/DBusMenu.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node>
+ <interface name="com.canonical.dbusmenu">
+
+ <!-- methods -->
+ <method name="GetLayout">
+ <arg type="i" name="parentId" direction="in"/>
+ <arg type="i" name="recursionDepth" direction="in"/>
+ <arg type="as" name="propertyNames" direction="in"/>
+ <arg type="u" name="revision" direction="out"/>
+ <arg type="(ia{sv}av)" name="layout" direction="out"/>
+ </method>
+ <method name="Event">
+ <arg type="i" name="id" direction="in"/>
+ <arg type="s" name="eventId" direction="in"/>
+ <arg type="v" name="data" direction="in"/>
+ <arg type="u" name="timestamp" direction="in"/>
+ </method>
+ <method name="AboutToShow">
+ <arg type="i" name="id" direction="in"/>
+ <arg type="b" name="needUpdate" direction="out"/>
+ </method>
+ <!--
+ <method name="AboutToShowGroup">
+ <arg type="ai" name="ids" direction="in"/>
+ <arg type="ai" name="updatesNeeded" direction="out"/>
+ <arg type="ai" name="idErrors" direction="out"/>
+ </method>
+ <method name="GetGroupProperties">
+ <arg type="ai" name="ids" direction="in"/>
+ <arg type="as" name="propertyNames" direction="in"/>
+ <arg type="a(ia{sv})" name="properties" direction="out"/>
+ </method>
+ <method name="GetProperty">
+ <arg type="i" name="id" direction="in"/>
+ <arg type="s" name="name" direction="in"/>
+ <arg type="v" name="value" direction="out"/>
+ </method>
+ <method name="EventGroup">
+ <arg type="a(isvu)" name="events" direction="in"/>
+ <arg type="ai" name="idErrors" direction="out"/>
+ </method>
+ -->
+
+ <!-- properties -->
+ <!--
+ <property name="Version" type="u" access="read"/>
+ <property name="TextDirection" type="s" access="read"/>
+ <property name="Status" type="s" access="read"/>
+ <property name="IconThemePath" type="as" access="read"/>
+ -->
+
+ <!-- Signals -->
+ <signal name="ItemsPropertiesUpdated">
+ <arg type="a(ia{sv})" name="updatedProps" direction="out"/>
+ <arg type="a(ias)" name="removedProps" direction="out"/>
+ </signal>
+ <signal name="LayoutUpdated">
+ <arg type="u" name="revision" direction="out"/>
+ <arg type="i" name="parent" direction="out"/>
+ </signal>
+ <!--
+ <signal name="ItemActivationRequested">
+ <arg type="i" name="id" direction="out"/>
+ <arg type="u" name="timestamp" direction="out"/>
+ </signal>
+ -->
+
+ </interface>
+</node>
diff --git a/Resources/StatusNotifierItem.xml b/Resources/StatusNotifierItem.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node>
+ <interface name="org.kde.StatusNotifierItem">
+
+ <!-- methods -->
+ <method name="Activate">
+ <arg name="x" type="i" direction="in"/>
+ <arg name="y" type="i" direction="in"/>
+ </method>
+ <!--
+ <method name="Scroll">
+ <arg name="delta" type="i" direction="in"/>
+ <arg name="orientation" type="s" direction="in"/>
+ </method>
+ <method name="ContextMenu">
+ <arg name="x" type="i" direction="in"/>
+ <arg name="y" type="i" direction="in"/>
+ </method>
+ <method name="SecondaryActivate">
+ <arg name="x" type="i" direction="in"/>
+ <arg name="y" type="i" direction="in"/>
+ </method>
+ -->
+
+ <!-- properties -->
+ <property name="Menu" type="o" access="read"/>
+ <property name="IconName" type="s" access="read"/>
+ <property name="IconPixmap" type="a(iiay)" access="read"/>
+ <property name="IconThemePath" type="s" access="read"/>
+ <!--
+ <property name="OverlayIconName" type="s" access="read"/>
+ <property name="OverlayIconPixmap" type="a(iiay)" access="read"/>
+ <property name="AttentionIconName" type="s" access="read"/>
+ <property name="AttentionIconPixmap" type="a(iiay)" access="read"/>
+ <property name="Category" type="s" access="read"/>
+ <property name="Id" type="s" access="read"/>
+ <property name="Title" type="s" access="read"/>
+ <property name="Status" type="s" access="read"/>
+ <property name="WindowId" type="i" access="read"/>
+ <property name="ItemIsMenu" type="b" access="read"/>
+ <property name="AttentionMovieName" type="s" access="read"/>
+ <property name="ToolTip" type="(sa(iiay)ss)" access="read"/>
+ -->
+
+ <!-- signals -->
+ <signal name="NewIcon"/>
+ <!--
+ <signal name="NewAttentionIcon"/>
+ <signal name="NewOverlayIcon"/>
+ <signal name="NewTitle"/>
+ <signal name="NewToolTip"/>
+ <signal name="NewStatus">
+ <arg name="status" type="s"/>
+ </signal>
+ -->
+
+ </interface>
+</node>
diff --git a/Resources/StatusNotifierWatcher.xml b/Resources/StatusNotifierWatcher.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node>
+ <interface name="org.kde.StatusNotifierWatcher">
+ <!-- methods -->
+ <method name="RegisterStatusNotifierItem">
+ <arg name="service" type="s" direction="in" />
+ </method>
+
+ <!-- properties -->
+ <property name="RegisteredStatusNotifierItems" type="as" access="read" />
+ <property name="IsStatusNotifierHostRegistered" type="b" access="read" />
+ <property name="ProtocolVersion" type="i" access="read" />
+
+ <!-- signals -->
+ <signal name="StatusNotifierItemRegistered">
+ <arg type="s"/>
+ </signal>
+ <signal name="StatusNotifierItemUnregistered">
+ <arg type="s"/>
+ </signal>
+ <signal name="StatusNotifierHostRegistered">
+ </signal>
+ </interface>
+</node>
diff --git a/Resources/boxbg.css b/Resources/boxbg.css
@@ -0,0 +1,3 @@
+window {
+ background-color: #222222;
+}
diff --git a/dbusmenu.c b/dbusmenu.c
@@ -0,0 +1,307 @@
+#include <time.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#include "dwlbtray.h"
+
+static GMenu* create_menumodel(GVariant *data, StatusNotifierItem *snitem);
+
+
+static void
+action_activated_cb(GSimpleAction *action, GVariant* param, ActionCallbackData *data)
+{
+ // GError *err = NULL;
+
+ 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);
+ /*
+ GVariant *retval = g_dbus_proxy_call_sync(data->proxy,
+ "Event",
+ g_variant_new("(isvu)",
+ data->id,
+ "clicked",
+ g_variant_new_string(""),
+ time(NULL)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &err);
+
+ if (err) {
+ fprintf(stderr, "%s\n", err->message);
+ g_error_free(err);
+ } else {
+ g_variant_unref(retval);
+ }
+ */
+}
+
+
+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;
+ snitem->action_cb_data_slist = g_slist_prepend(snitem->action_cb_data_slist, data);
+
+ g_signal_connect(action, "activate", G_CALLBACK(action_activated_cb), data);
+
+ g_free(action_name);
+
+ return action;
+}
+
+
+static GMenuItem*
+create_menuitem(GVariant *data, StatusNotifierItem *snitem)
+{
+ // (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;
+
+ /* TODO: dynamic menu updates
+ * 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);
+ /* TODO: dynamic menu updates
+ * 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;
+
+ /* TODO: dynamic menu updates
+ * 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", "menuitem", 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 && (!isvisible || !isenabled)) && !(type && strcmp(type, "separator") == 0)) {
+ if (!isvisible)
+ g_debug("menuitem %i, label %s should be invisible\n", id, label);
+ GSimpleAction *action = create_action(id, snitem);
+ g_simple_action_set_enabled(action, FALSE);
+ char *action_name = g_strdup_printf("%s.%u", "menuitem", 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);
+
+ return menuitem;
+}
+
+
+static GMenu*
+create_menumodel(GVariant *data, StatusNotifierItem *snitem)
+{
+ GMenu *ret = NULL;
+
+ GVariantIter iter;
+ GVariant *menuitem_data;
+ gboolean has_sections = FALSE;
+ GMenu *menu = g_menu_new();
+ GMenu *section = g_menu_new();
+ 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(section, menuitem);
+ g_object_unref(menuitem);
+ }
+ else {
+ g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
+ g_object_unref(section);
+ has_sections = TRUE;
+ section = g_menu_new();
+ }
+
+ g_variant_unref(menuitem_data);
+ }
+
+ if (has_sections) {
+ g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
+ ret = menu;
+ g_object_unref(section);
+ } else {
+ ret = section;
+ g_object_unref(menu);
+ }
+
+ 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))
+
+ if (err) {
+ // Sometimes apps send update messages just as they are closing
+ // So the dbus object might not exist in this case
+ g_debug("%s\n", err->message);
+ g_error_free(err);
+
+ return;
+ }
+
+
+ uint32_t revision;
+ GVariant *layout;
+ // (ia{sv}av)
+ // uint32_t GVariant * GVariant *
+ // 0, {"children-display": "submenu"}, {GVariant *menuitem, GVariant *menuitem ...}
+ GVariant *menuitems;
+ // (ia{sv}av)
+ // uint32_t GVariant * GVariant *
+ // 1, {"label": "foobar", ...}, {GVariant *menuitem, GVariant *menuitem ...}
+
+ g_variant_get_child(data, 0, "u", &revision);
+ if (snitem->menurevision != UINT32_MAX && revision <= snitem->menurevision) {
+ g_variant_unref(data);
+ return;
+ } else {
+ snitem->menurevision = revision;
+ }
+
+ if (snitem->menu && snitem->popovermenu) {
+ gtk_widget_unparent(snitem->popovermenu);
+ g_menu_remove_all(snitem->menu);
+ }
+ layout = g_variant_get_child_value(data, 1);
+
+ menuitems = g_variant_get_child_value(layout, 2);
+
+ GMenu *menu = create_menumodel(menuitems, snitem);
+ snitem->menu = menu;
+
+ snitem->popovermenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(snitem->menu));
+ gtk_popover_set_has_arrow(GTK_POPOVER(snitem->popovermenu), FALSE);
+ gtk_popover_menu_set_menu_model(GTK_POPOVER_MENU(snitem->popovermenu),
+ G_MENU_MODEL(snitem->menu));
+ gtk_widget_set_parent(snitem->popovermenu, snitem->icon);
+
+ g_variant_unref(menuitems);
+ g_variant_unref(layout);
+ g_variant_unref(data);
+}
+
+
+static void
+on_menuproxy_signal(GDBusProxy *proxy,
+ const char *sender,
+ const char *signal,
+ GVariant *params,
+ StatusNotifierItem *snitem)
+{
+ if (strcmp(signal, "LayoutUpdated") == 0) {
+ g_debug("%s's menu got LayoutUpdated\n", snitem->busname);
+ g_dbus_proxy_call(snitem->menuproxy,
+ "GetLayout",
+ g_variant_new("(iias)", 0, -1, NULL),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback)on_menulayout_ready,
+ snitem);
+ // TODO: dynamic menu updates
+ } else if (strcmp(signal, "ItemsPropertiesUpdated") == 0) {
+ g_debug("%s's menu got LayoutUpdated\n", snitem->busname);
+ }
+}
+
+
+void
+create_menu(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem)
+{
+ GError *err = NULL;
+ snitem->menuproxy = g_dbus_proxy_new_for_bus_finish(res, &err);
+ snitem->menurevision = UINT32_MAX;
+
+ if (err) {
+ g_debug("%s\n", err->message);
+ g_error_free(err);
+ return;
+ }
+
+ g_dbus_proxy_call(snitem->menuproxy,
+ "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/dwlb.c b/dwlb.c
@@ -109,6 +109,7 @@
" -set-top [OUTPUT] draw bar at the top\n" \
" -set-bottom [OUTPUT] draw bar at the bottom\n" \
" -toggle-location [OUTPUT] toggle bar location\n" \
+ " -traymon [OUTPUT] set monitor name where systray will appear\n" \
"Other\n" \
" -v get version information\n" \
" -h view this help text\n"
@@ -150,6 +151,7 @@ typedef struct {
bool configured;
uint32_t width, height;
+ uint32_t width_orig;
uint32_t textpadding;
uint32_t stride, bufsize;
@@ -497,10 +499,11 @@ layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface,
Bar *bar = (Bar *)data;
- if (bar->configured && w == bar->width && h == bar->height)
+ if (bar->configured && w == bar->width_orig && h == bar->height)
return;
bar->width = w;
+ bar->width_orig = w;
bar->height = h;
bar->stride = bar->width * 4;
bar->bufsize = bar->stride * bar->height;
@@ -1427,6 +1430,17 @@ copy_customtext(CustomText *from, CustomText *to)
}
static void
+request_resize(Bar *bar, char *data)
+{
+ uint32_t traywidth = (uint32_t)atoi(data);
+
+ bar->width = bar->width_orig - traywidth;
+ bar->stride = bar->width * 4;
+ bar->bufsize = bar->stride * bar->height;
+ bar->redraw = true;
+}
+
+static void
read_socket(void)
{
int cli_fd;
@@ -1571,6 +1585,8 @@ read_socket(void)
else
set_bottom(bar);
}
+ } else if (!strcmp(wordbeg, "resize")) {
+ request_resize(bar, wordend);
}
}
@@ -1669,6 +1685,7 @@ main(int argc, char **argv)
struct sockaddr_un sock_address;
Bar *bar, *bar2;
Seat *seat, *seat2;
+ char *traymon = NULL;
/* Establish socket directory */
if (!(xdgruntimedir = getenv("XDG_RUNTIME_DIR")))
@@ -1848,6 +1865,10 @@ main(int argc, char **argv)
if (++i >= argc)
DIE("Option -scale requires an argument");
buffer_scale = strtoul(argv[i], &argv[i] + strlen(argv[i]), 10);
+ } else if (!strcmp(argv[i], "-traymon")) {
+ if (++i >= argc)
+ DIE("Option -traymon requires an argument");
+ traymon = argv[i];
} else if (!strcmp(argv[i], "-v")) {
fprintf(stderr, PROGRAM " " VERSION "\n");
return 0;
@@ -1940,6 +1961,34 @@ main(int argc, char **argv)
signal(SIGTERM, sig_handler);
signal(SIGCHLD, SIG_IGN);
+ /* Start tray program */
+ char progname[PATH_MAX];
+ ssize_t len = readlink("/proc/self/exe", progname, sizeof(progname));
+
+ if (len != -1)
+ progname[len] = '\0';
+ else
+ exit(-1);
+
+ char tray_exe_path[PATH_MAX];
+ if (strncmp(progname, BUILD_DIR, strlen(BUILD_DIR)) == 0) {
+ strcpy(tray_exe_path, BUILD_DIR);
+ strcat(tray_exe_path, "dwlbtray");
+ } else {
+ strcpy(tray_exe_path, "dwlbtray");
+ }
+ int child_pid = fork();
+ if (child_pid == 0) {
+ char height_param[64];
+ char traymon_param[64];
+ snprintf(height_param, sizeof(height_param), "--height=%u", height);
+ snprintf(traymon_param, sizeof(traymon_param), "--traymon=%s", traymon);
+ char *args[] = { tray_exe_path, height_param, traymon_param, NULL};
+ if (!traymon)
+ args[2] = NULL;
+ execvp(args[0], args);
+ }
+
/* Run */
run_display = true;
event_loop();
diff --git a/dwlbtray.c b/dwlbtray.c
@@ -0,0 +1,140 @@
+#include <stdlib.h>
+#include <unistd.h>
+// #include <limits.h>
+
+#include <glib.h>
+#include <gtk4-layer-shell.h>
+#include <gtk/gtk.h>
+// #include "glib-unix.h"
+
+#include "dwlbtray.h"
+
+
+const char *RESOURCE_PATH;
+
+
+static void
+activate(GtkApplication* app, StatusNotifierHost *snhost)
+{
+ GtkWindow *window = GTK_WINDOW(gtk_application_window_new(app));
+ snhost->window = window;
+
+ gtk_layer_init_for_window(window);
+ gtk_layer_set_layer(window, GTK_LAYER_SHELL_LAYER_BOTTOM);
+ gtk_layer_set_exclusive_zone(window, -1);
+ static const gboolean anchors[] = {FALSE, TRUE, TRUE, FALSE};
+ for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++) {
+ gtk_layer_set_anchor(window, i, anchors[i]);
+ }
+
+ if (snhost->traymon) {
+ GListModel *mons = gdk_display_get_monitors(gdk_display_get_default());
+ 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) {
+ gtk_layer_set_monitor(window, mon);
+ }
+ }
+ }
+
+ GtkCssProvider *css = gtk_css_provider_new();
+ char *css_path = g_strdup_printf("%s%s", RESOURCE_PATH, "/boxbg.css");
+ gtk_css_provider_load_from_path(css, css_path);
+ gtk_style_context_add_provider_for_display(gdk_display_get_default(),
+ GTK_STYLE_PROVIDER(css),
+ GTK_STYLE_PROVIDER_PRIORITY_USER);
+ g_free(css_path);
+
+ 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_default_size(GTK_WINDOW(window), 22, snhost->height);
+ dwlb_request_resize(snhost);
+
+ snhost->box = box;
+
+ gtk_window_set_child(window, box);
+
+ gtk_window_present(window);
+
+}
+
+
+/*
+ * gboolean
+ * terminate_app(StatusNotifierHost *snhost)
+ * {
+ * terminate_statusnotifierhost(snhost);
+ *
+ * GApplication *app = g_application_get_default();
+ * g_application_release(app);
+ *
+ * return G_SOURCE_REMOVE;
+ * }
+ */
+
+int
+main(int argc, char *argv[])
+{
+ char progname[PATH_MAX];
+ ssize_t len = readlink("/proc/self/exe", progname, sizeof(progname));
+
+ if (len != -1) {
+ progname[len] = '\0';
+ }
+ else {
+ fprintf(stderr, "bad progname, exiting\n");
+ exit(-1);
+ }
+
+ if (strncmp(progname, BUILD_DIR, strlen(BUILD_DIR)) == 0) {
+ RESOURCE_PATH = g_strdup_printf("%s%s", BUILD_DIR, "Resources");
+ } else {
+ const char * const *datadirs = g_get_system_data_dirs();
+ int i;
+
+ for (i = 0; datadirs[i]; i++) {
+ char *test = g_build_filename(datadirs[i], "dwlb", "boxbg.css", NULL);
+ if (g_file_test(test, G_FILE_TEST_EXISTS))
+ RESOURCE_PATH = g_path_get_dirname(test);
+ }
+ }
+
+ if (!RESOURCE_PATH)
+ RESOURCE_PATH = "/usr/local/dwlb";
+
+ StatusNotifierHost *snhost = start_statusnotifierhost();
+
+ 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]);
+ } else if (strcmp(strings[0], "--traymon") == 0) {
+ snhost->traymon = g_strdup(strings[1]);
+ }
+ g_strfreev(strings);
+ }
+
+ GtkApplication *app = gtk_application_new("com.vetu104.Gtktray",
+ G_APPLICATION_DEFAULT_FLAGS);
+
+ g_signal_connect(app, "activate", G_CALLBACK(activate), snhost);
+
+ /*
+ * g_unix_signal_add(SIGINT, (GSourceFunc)terminate_app, snhost);
+ * g_unix_signal_add(SIGTERM, (GSourceFunc)terminate_app, snhost);
+ */
+
+ char *argv_inner[] = { progname, NULL };
+ int status = g_application_run(G_APPLICATION(app), 1, argv_inner);
+
+ g_object_unref(app);
+ return status;
+}
diff --git a/dwlbtray.h b/dwlbtray.h
@@ -0,0 +1,63 @@
+#ifndef GTKTRAY_H
+#define GTKTRAY_H
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+extern const char *RESOURCE_PATH;
+
+typedef struct {
+ uint32_t id;
+ GDBusProxy* proxy;
+} ActionCallbackData;
+
+
+typedef struct StatusNotifierHost {
+ GDBusConnection *conn;
+ GDBusNodeInfo *nodeinfo;
+ GDBusProxy *watcherproxy;
+ GSList *trayitems;
+ GtkWidget *box;
+ GtkWindow *window;
+ int bus_obj_reg_id;
+ int cursize;
+ int height;
+ int margin;
+ int noitems;
+ int owner_id;
+ uint nameowner_sig_sub_id;
+ uint watcher_id;
+ char *traymon;
+} StatusNotifierHost;
+
+typedef struct StatusNotifierItem {
+ GDBusNodeInfo *menunodeinfo;
+ GDBusNodeInfo *nodeinfo;
+ GDBusProxy *menuproxy;
+ GDBusProxy *proxy;
+ GMenu *menu;
+ GSList *action_cb_data_slist;
+ GSimpleActionGroup *actiongroup;
+ GVariant *iconpixmap_v;
+ GdkPaintable *paintable;
+ GtkWidget *icon;
+ GtkWidget *popovermenu;
+ StatusNotifierHost *host;
+ char *busname;
+ char *busobj;
+ char *iconname;
+ char *menuobj;
+ uint32_t menurevision;
+ unsigned char *icon_data;
+} StatusNotifierItem;
+
+
+void create_trayitem(GDBusConnection *conn, GAsyncResult *res, StatusNotifierItem *snitem);
+void create_menu(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem);
+StatusNotifierHost* start_statusnotifierhost();
+// void terminate_statusnotifierhost(StatusNotifierHost *snhost);
+GDBusNodeInfo* get_interface_info(const char *xmlpath);
+void dwlb_request_resize(StatusNotifierHost *snhost);
+
+#endif /* STATUSNOTIFIERHOST_H */
diff --git a/statusnotifierhost.c b/statusnotifierhost.c
@@ -0,0 +1,435 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <gtk4-layer-shell.h>
+
+#include "dwlbtray.h"
+
+
+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
+};
+
+
+void
+dwlb_request_resize(StatusNotifierHost *snhost)
+{
+ if (snhost->noitems <= 1)
+ snhost->cursize = 22;
+ else
+ snhost->cursize = 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->cursize);
+ }
+ else {
+ sockbuf = g_strdup_printf("%s %s %i", "selected", "resize", snhost->cursize);
+ }
+
+ 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)
+ fprintf(stderr, "Could not send size update to %s\n", sockaddr.sun_path);
+ close(sock_fd);
+
+ g_free(sockbuf);
+ g_free(socketpath);
+}
+
+GDBusNodeInfo*
+get_interface_info(const char *xmlpath)
+{
+ char *contents;
+ GError *err = NULL;
+
+ g_file_get_contents(xmlpath, &contents, NULL, &err);
+
+ if (err) {
+ fprintf(stderr, "%s\n", err->message);
+ g_error_free(err);
+ return NULL;
+ }
+
+ GDBusNodeInfo *node = g_dbus_node_info_new_for_xml(contents, &err);
+ g_free(contents);
+
+ if (err) {
+ fprintf(stderr, "%s\n", err->message);
+ g_error_free(err);
+ return NULL;
+ }
+
+ return node;
+}
+
+
+
+static void
+register_statusnotifieritem(const char *busname,
+ const char *busobj,
+ StatusNotifierHost *snhost)
+{
+ StatusNotifierItem *snitem;
+ snitem = g_malloc(sizeof(StatusNotifierItem));
+
+ snitem->busname = g_strdup(busname);
+ snitem->busobj = g_strdup(busobj);
+
+ snitem->host = snhost;
+
+ char *xml_path = g_strdup_printf("%s%s", RESOURCE_PATH, "/StatusNotifierItem.xml");
+ snitem->nodeinfo = get_interface_info(xml_path);
+ g_free(xml_path);
+
+ snitem->action_cb_data_slist = NULL;
+ snitem->actiongroup = NULL;
+ snitem->icon = NULL;
+ snitem->iconname = NULL;
+ snitem->iconpixmap_v = NULL;
+ snitem->menu = NULL;
+ snitem->menunodeinfo = NULL;
+ snitem->menuobj = NULL;
+ snitem->menuproxy = NULL;
+ snitem->paintable = NULL;
+ snitem->popovermenu = NULL;
+
+ snhost->noitems = snhost->noitems + 1;
+
+ dwlb_request_resize(snhost);
+
+ snhost->trayitems = g_slist_prepend(snhost->trayitems, snitem);
+
+ g_dbus_proxy_new(snhost->conn,
+ G_DBUS_PROXY_FLAGS_NONE,
+ snitem->nodeinfo->interfaces[0],
+ snitem->busname,
+ snitem->busobj,
+ "org.kde.StatusNotifierItem",
+ NULL,
+ (GAsyncReadyCallback)create_trayitem,
+ snitem);
+
+ GError *err = NULL;
+ g_dbus_connection_emit_signal(snhost->conn,
+ NULL,
+ "/StatusNotifierWatcher",
+ "org.kde.StatusNotifierWatcher",
+ "StatusNotifierItemRegistered",
+ g_variant_new("(s)", snitem->busname),
+ &err);
+ if (err) {
+ g_debug("%s\n", err->message);
+ g_error_free(err);
+ }
+}
+
+
+static int
+find_snitem(StatusNotifierItem *snitem, char *busname_match)
+{
+ if (strcmp(snitem->busname, busname_match) == 0)
+ return 0;
+ else
+ return -1;
+}
+
+
+static void
+unregister_statusnotifieritem(StatusNotifierItem *snitem, StatusNotifierHost *snhost)
+{
+ g_debug("item %s is closing\n", snitem->busname);
+ if (snitem->popovermenu)
+ gtk_widget_unparent(snitem->popovermenu);
+
+ if (snitem->icon)
+ gtk_box_remove(GTK_BOX(snhost->box), snitem->icon);
+
+ GError *err = NULL;
+ g_dbus_connection_emit_signal(snhost->conn,
+ NULL,
+ "/StatusNotifierWatcher",
+ "org.kde.StatusNotifierWatcher",
+ "StatusNotifierItemUnregistered",
+ g_variant_new("(s)", snitem->busname),
+ &err);
+ if (err) {
+ g_debug("%s\n", err->message);
+ g_error_free(err);
+ }
+
+ if (snitem->menu) {
+ g_object_unref(snitem->menuproxy);
+ g_slist_free_full(snitem->action_cb_data_slist, g_free);
+ g_object_unref(snitem->menu);
+ }
+
+ g_object_unref(snitem->paintable);
+ if (snitem->iconpixmap_v)
+ g_variant_unref(snitem->iconpixmap_v);
+
+ g_object_unref(snitem->proxy);
+ g_object_unref(snitem->actiongroup);
+ g_dbus_node_info_unref(snitem->menunodeinfo);
+ g_dbus_node_info_unref(snitem->nodeinfo);
+ g_free(snitem->busname);
+ g_free(snitem->busobj);
+ g_free(snitem->menuobj);
+ snhost->trayitems = g_slist_remove(snhost->trayitems, snitem);
+ g_free(snitem);
+ snitem = NULL;
+
+ snhost->noitems = snhost->noitems - 1;
+ dwlb_request_resize(snhost);
+}
+
+
+/*
+ * static void
+ * unregister_all(StatusNotifierHost *snhost)
+ * {
+ * g_slist_foreach(snhost->trayitems, (GFunc)unregister_statusnotifieritem, snhost);
+ * }
+*/
+
+
+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) {
+ char *param;
+ const char *busobj;
+
+ g_variant_get(parameters, "(s)", ¶m);
+
+ if (g_str_has_prefix(param, "/")) {
+ busobj = param;
+ } else {
+ busobj = "/StatusNotifierItem";
+ }
+
+ register_statusnotifieritem(sender, busobj, snhost);
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ g_free(param);
+ } else {
+ g_dbus_method_invocation_return_dbus_error(invocation,
+ "org.freedesktop.DBus.Error.UnknownMethod",
+ "Unknown method");
+ }
+}
+
+
+static void
+add_trayitem_name_to_builder(StatusNotifierItem *snitem, GVariantBuilder *builder)
+{
+ g_variant_builder_add_value(builder, g_variant_new_string(snitem->busname));
+}
+
+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;
+ }
+}
+
+/*
+ * void
+ * terminate_statusnotifierhost(StatusNotifierHost *snhost)
+ * {
+ * g_dbus_connection_signal_unsubscribe(snhost->conn, snhost->nameowner_sig_sub_id);
+ * g_bus_unown_name(snhost->owner_id);
+ * unregister_all(snhost);
+ * g_slist_free(snhost->trayitems);
+ * g_dbus_node_info_unref(snhost->nodeinfo);
+ * g_free(snhost);
+ * }
+ */
+
+// 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;
+
+ char *name;
+ char *old_owner;
+ char *new_owner;
+
+ g_variant_get(params, "(sss)", &name, &old_owner, &new_owner);
+
+ if (strcmp(new_owner, "") == 0) {
+ GSList *pmatch = g_slist_find_custom(snhost->trayitems, name, (GCompareFunc)find_snitem);
+
+
+ if (!pmatch) {
+ g_free(name);
+ g_free(old_owner);
+ g_free(new_owner);
+ return;
+ }
+
+ StatusNotifierItem *snitem = pmatch->data;
+ unregister_statusnotifieritem(snitem, snhost);
+ }
+ g_free(name);
+ g_free(old_owner);
+ g_free(new_owner);
+ }
+}
+
+
+static void
+bus_acquired_handler(GDBusConnection *conn, const char *busname, StatusNotifierHost *snhost)
+{
+ snhost->conn = conn;
+ GError *err = NULL;
+ snhost->bus_obj_reg_id = g_dbus_connection_register_object(conn,
+ "/StatusNotifierWatcher",
+ snhost->nodeinfo->interfaces[0],
+ &interface_vtable,
+ snhost, // udata
+ NULL, // udata_free_func
+ &err);
+
+ if (err) {
+ g_error("%s\n", err->message);
+ g_error_free(err);
+ exit(-1);
+ }
+
+ snhost->nameowner_sig_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, // udata
+ NULL); // udata free func
+}
+
+
+static void
+name_acquired_handler(GDBusConnection *conn, const char *busname, StatusNotifierHost *snhost)
+{
+ GError *err = NULL;
+
+ g_dbus_connection_emit_signal(conn,
+ NULL,
+ "/StatusNotifierWatcher",
+ "org.kde.StatusNotifierWatcher",
+ "StatusNotifierHostRegistered",
+ NULL,
+ &err);
+
+ if (err) {
+ g_debug("%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);
+}
+
+
+StatusNotifierHost*
+start_statusnotifierhost()
+{
+ StatusNotifierHost *snhost = g_malloc0(sizeof(StatusNotifierHost));
+ char *xml_path = g_strdup_printf("%s%s", RESOURCE_PATH, "/StatusNotifierWatcher.xml");
+ snhost->nodeinfo = get_interface_info(xml_path);
+ g_free(xml_path);
+
+ snhost->height = 22;
+ snhost->margin = 4;
+ snhost->noitems = 0;
+
+ 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,
+ NULL); // (GDestroyNotify)snhost_finalize);
+
+ return snhost;
+}
diff --git a/statusnotifieritem.c b/statusnotifieritem.c
@@ -0,0 +1,405 @@
+#include <glib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <gtk4-layer-shell.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
+about_to_show_cb(GDBusProxy *proxy, GAsyncResult *res, StatusNotifierItem *snitem)
+{
+ GError *err = NULL;
+ GVariant *retval = g_dbus_proxy_call_finish(proxy, res, &err);
+
+ if (err) {
+ // Some apps require "AboutToShow" to be called before activating a menuitem
+ // Others do not implement this method
+ // In case of the latter we log a debug warning and continue
+ g_debug("%s\n", err->message);
+ g_error_free(err);
+ } else {
+ g_variant_unref(retval);
+ }
+
+ // Widget may be about to be destroyed
+ if (GTK_IS_WIDGET(snitem->popovermenu))
+ gtk_popover_popup(GTK_POPOVER(snitem->popovermenu));
+}
+
+
+static void
+on_leftclick_cb(GtkGestureClick *click,
+ int n_press,
+ double x,
+ double y,
+ StatusNotifierItem *snitem)
+{
+ g_dbus_proxy_call(snitem->proxy,
+ "Activate",
+ g_variant_new("(ii)", 0, 0),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL,
+ snitem);
+}
+
+
+
+
+static void
+on_rightclick_cb(GtkGestureClick *click,
+ int n_press,
+ double x,
+ double y,
+ StatusNotifierItem *snitem)
+{
+ g_dbus_proxy_call(snitem->menuproxy,
+ "AboutToShow",
+ g_variant_new("(i)", 0),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback)about_to_show_cb,
+ snitem);
+}
+
+
+static void
+pb_destroy(unsigned char *pixeld, void *data)
+{
+ g_free(pixeld);
+}
+
+
+static GVariant*
+select_icon_by_size(GVariant *icondata_v)
+{
+ // 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 = 22;
+ 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)
+{
+ GdkPaintable *paintable;
+ GVariantIter iter;
+ GVariant *iconpixmap_v = select_icon_by_size(icondata_v);
+
+ 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)
+{
+ 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
+ 22,
+ 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);
+ // (s)
+
+ if (err) {
+ g_debug("%s\n", err->message);
+ g_error_free(err);
+ return;
+ }
+
+ const char *iconname = NULL;
+ g_variant_get(data, "(&s)", &iconname);
+
+ if (strcmp(iconname, snitem->iconname) == 0) {
+ g_debug ("%s\n", "pixmap didnt change, nothing to");
+ g_variant_unref(data);
+ return;
+ }
+
+ g_object_unref(snitem->paintable);
+ snitem->iconname = iconname;
+ snitem->paintable = get_paintable_from_name(snitem->iconname);
+ 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) {
+ fprintf(stderr, "%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\n", "pixmap didnt change, nothing to");
+ g_variant_unref(newpixmap_v);
+ return;
+ }
+
+ g_object_unref(snitem->paintable);
+ g_variant_unref(snitem->iconpixmap_v);
+ snitem->iconpixmap_v = newpixmap_v;
+ snitem->paintable = get_paintable_from_data(snitem->iconpixmap_v);
+ gtk_image_set_from_paintable(GTK_IMAGE (snitem->icon), snitem->paintable);
+}
+
+
+static void
+trayitem_signal_handler(GDBusProxy *proxy,
+ const char *sender,
+ const char *signal,
+ GVariant *data_v,
+ StatusNotifierItem *snitem)
+{
+ // TODO: this can fire many times in a short amount of time
+ 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");
+
+ if (iconname_v) {
+ iconname = g_variant_get_string(iconname_v, NULL);
+ g_variant_unref(iconname_v);
+ }
+
+ if (iconname && strcmp(iconname, "") != 0) {
+ paintable = get_paintable_from_name(iconname);
+
+ snitem->iconname = iconname;
+
+ } else {
+ GVariant *iconpixmap_v = g_dbus_proxy_get_cached_property(proxy, "IconPixmap");
+ if (!iconpixmap_v)
+ return NULL;
+ paintable = get_paintable_from_data(iconpixmap_v);
+
+ snitem->iconpixmap_v = iconpixmap_v;
+ }
+
+ image = gtk_image_new_from_paintable(paintable);
+ snitem->paintable = paintable;
+
+ return image;
+}
+
+
+void
+create_trayitem(GDBusConnection *conn, GAsyncResult *res, StatusNotifierItem *snitem)
+{
+ GError *err = NULL;
+ snitem->proxy = g_dbus_proxy_new_finish(res, &err);
+
+ if (err) {
+ fprintf(stderr, "%s\n", err->message);
+ g_error_free(err);
+ return;
+ }
+
+ GtkIconTheme *theme = gtk_icon_theme_get_for_display(gdk_display_get_default ());
+ GVariant *iconthemepath_v = g_dbus_proxy_get_cached_property(snitem->proxy, "IconThemePath");
+
+ if (iconthemepath_v) {
+ const char *path = g_variant_get_string(iconthemepath_v, NULL);
+ gtk_icon_theme_add_search_path(theme, path);
+ g_variant_unref(iconthemepath_v);
+ }
+
+ GtkGesture *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);
+
+ GtkGesture *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);
+
+ const char *valid_menuobjects[] = {
+ "/MenuBar",
+ "/com/canonical/dbusmenu",
+ "/org/ayatana/NotificationItem",
+ NULL
+ };
+
+ GVariant *menuobj_v = g_dbus_proxy_get_cached_property(snitem->proxy, "Menu");
+ if (menuobj_v) {
+ snitem->menuobj = g_strdup(g_variant_get_string(menuobj_v, NULL));
+ g_variant_unref(menuobj_v);
+ } else {
+ snitem->menuobj = g_strdup("Invalid_menuobj");
+ }
+ snitem->actiongroup = g_simple_action_group_new();
+
+ char *xml_path = g_strdup_printf("%s%s", RESOURCE_PATH, "/DBusMenu.xml");
+ snitem->menunodeinfo = get_interface_info(xml_path);
+ g_free(xml_path);
+
+ // gtk_popover_menu_set_menu_model(GTK_POPOVER_MENU(snitem->popovermenu),
+ // G_MENU_MODEL(snitem->menu));
+
+ g_signal_connect(snitem->proxy, "g-signal", G_CALLBACK(trayitem_signal_handler), snitem);
+
+ for (int i = 0; valid_menuobjects[i] != NULL; i++) {
+ if (g_strrstr(snitem->menuobj, valid_menuobjects[i]) != NULL) {
+ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ snitem->menunodeinfo->interfaces[0],
+ snitem->busname,
+ snitem->menuobj,
+ "com.canonical.dbusmenu",
+ NULL,
+ (GAsyncReadyCallback)create_menu,
+ snitem);
+ }
+ }
+
+ GtkWidget *image = create_icon(snitem->proxy, snitem);
+ if (!image)
+ return;
+
+ snitem->icon = image;
+ gtk_box_append(GTK_BOX(snitem->host->box), snitem->icon);
+
+ gtk_widget_add_controller(snitem->icon, GTK_EVENT_CONTROLLER(leftclick));
+ gtk_widget_add_controller(snitem->icon, GTK_EVENT_CONTROLLER(rightclick));
+ gtk_widget_insert_action_group(snitem->icon,
+ "menuitem",
+ G_ACTION_GROUP (snitem->actiongroup));
+}