dwlb

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

commit b95878a8b148b9e9f7267500db7b41c08a28e544
parent 8c162e6157cc9c72d0b4f7150050c1d2eb786669
Author: Janne Veteläinen <janne.vetelainen@elisanet.fi>
Date:   Fri, 19 Jul 2024 08:30:05 +0300

Add path to an image file as possible icon source

This required a bit of refactoring. fixes #4

Diffstat:
Msystray/snitem.c | 818+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
1 file changed, 547 insertions(+), 271 deletions(-)

diff --git a/systray/snitem.c b/systray/snitem.c @@ -1,19 +1,20 @@ #include "snitem.h" -#include <stdlib.h> -#include <stdint.h> -#include <limits.h> -#include <string.h> +#include "sndbusmenu.h" -#include <glib.h> -#include <glib-object.h> -#include <gio/gio.h> -#include <gdk/gdk.h> #include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk/gdk.h> +#include <gio/gio.h> +#include <glib-object.h> +#include <glib.h> #include <gtk/gtk.h> -#include "sndbusmenu.h" - +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> struct _SnItem { @@ -22,23 +23,29 @@ struct _SnItem char* busname; char* busobj; - GDBusProxy* proxy; - char* iconname; - GVariant* iconpixmap; - SnDbusmenu* dbusmenu; - + GMenu* init_menu; + GtkGesture* lclick; + GtkGesture* rclick; GtkWidget* image; GtkWidget* popovermenu; - GMenu* init_menu; + + GDBusProxy* proxy; GSList* cachedicons; + GVariant* iconpixmap; + SnDbusmenu* dbusmenu; + char *iconpath; + char* iconname; - unsigned long popup_sig_id; + unsigned long lclick_id; + unsigned long popup_id; + unsigned long proxy_id; + unsigned long rclick_id; + int icon_source; int iconsize; - gboolean ready; - gboolean exiting; + int status; + gboolean in_destruction; gboolean menu_visible; - }; G_DEFINE_FINAL_TYPE(SnItem, sn_item, GTK_TYPE_WIDGET) @@ -48,7 +55,6 @@ enum PROP_BUSNAME = 1, PROP_BUSOBJ, PROP_ICONSIZE, - PROP_PROXY, PROP_DBUSMENU, PROP_MENUVISIBLE, N_PROPERTIES @@ -60,14 +66,23 @@ enum LAST_SIGNAL }; +enum icon_sources +{ + ICON_SOURCE_UNKNOWN, + ICON_SOURCE_NAME, + ICON_SOURCE_PATH, + ICON_SOURCE_PIXMAP, +}; + static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; static unsigned int signals[LAST_SIGNAL]; typedef struct { - GVariant* iconpixmap; char* iconname; + char *iconpath; + GVariant* iconpixmap; GdkPaintable* icondata; -} CachedIcon; +} cached_icon_t; static void sn_item_constructed (GObject *obj); static void sn_item_dispose (GObject *obj); @@ -86,29 +101,84 @@ static void sn_item_measure (GtkWidget *widget, int *minimum_baseline, int *natural_baseline); +static void request_newicon_name(SnItem *self); +static void request_newicon_pixmap(SnItem *self); +static void request_newicon_path(SnItem *self); + +static gboolean +validate_pixdata(GVariant *icondata) +{ + int32_t width, height; + GVariant *bytearr; + size_t size; + + g_variant_get_child(icondata, 0, "i", &width); + g_variant_get_child(icondata, 1, "i", &height); + bytearr = g_variant_get_child_value(icondata, 2); + size = g_variant_get_size(bytearr); + + g_variant_unref(bytearr); + + if (width == 0 || height == 0 || size == 0) + return false; + else + return true; +} 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; + 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 int +find_cached_icon_path(cached_icon_t *cicon, + const char *path) +{ + if (cicon->iconpath == NULL || path == NULL) + return -1; + + return strcmp(cicon->iconname, path); +} + +static int +find_cached_icon_name(cached_icon_t *cicon, + const char *name) +{ + if (cicon->iconname == NULL || name == NULL) + return -1; + + return strcmp(cicon->iconname, name); +} + +static int +find_cached_icon_pixmap(cached_icon_t *cicon, GVariant *pixmap) +{ + if (cicon->iconpixmap == NULL || pixmap == NULL) + return -1; + + if (g_variant_equal(cicon->iconpixmap, pixmap)) + return 0; + else + return 1; +} + static void cachedicons_free(void *data) { - CachedIcon *cicon = (CachedIcon*)data; + cached_icon_t *cicon = (cached_icon_t*)data; g_free(cicon->iconname); - if (cicon->iconpixmap) + g_free(cicon->iconpath); + if (cicon->iconpixmap != NULL) g_variant_unref(cicon->iconpixmap); - if (cicon->icondata) + if (cicon->icondata != NULL) g_object_unref(cicon->icondata); g_free(cicon); } @@ -120,7 +190,7 @@ pixbuf_destroy(unsigned char *pixeld, void *data) } static GVariant* -select_icon_by_size(GVariant *icondata_v, int32_t target_icon_size) +select_icon_by_size(GVariant *vicondata, int32_t target_icon_size) { // Apps broadcast icons as variant a(iiay) // Meaning array of tuples, tuple representing an icon @@ -132,7 +202,7 @@ select_icon_by_size(GVariant *icondata_v, int32_t target_icon_size) int current_index = 0; int32_t diff = INT32_MAX; GVariant *child; - g_variant_iter_init(&iter, icondata_v); + g_variant_iter_init(&iter, vicondata); while ((child = g_variant_iter_next_value(&iter))) { int32_t curwidth; g_variant_get_child(child, 0, "i", &curwidth); @@ -149,10 +219,53 @@ select_icon_by_size(GVariant *icondata_v, int32_t target_icon_size) g_variant_unref(child); } - GVariant *iconpixmap_v = g_variant_get_child_value(icondata_v, - (size_t)selected_index); + GVariant *selected = g_variant_get_child_value(vicondata, + (size_t)selected_index); - return iconpixmap_v; + // Discard if the array is empty + if (validate_pixdata(selected)) { + return selected; + } else { + g_variant_unref(selected); + return NULL; + } +} + +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 GdkPaintable* +get_paintable_from_path(const char *path, int32_t iconsize) +{ + GdkPaintable *paintable = NULL; + + GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(path, + iconsize, + iconsize, + NULL); + + GdkTexture* texture = gdk_texture_new_for_pixbuf(pixbuf); + paintable = GDK_PAINTABLE(texture); + + g_object_unref(pixbuf); + + return paintable; } static GdkPaintable* @@ -163,26 +276,30 @@ get_paintable_from_data(GVariant *iconpixmap_v, int32_t iconsize) int32_t width; int32_t height; - GVariant *icon_data_v; + GVariant *vicondata; 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); + vicondata = 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); + size_t size = g_variant_get_size(vicondata); + const void *icon_data_dup = g_variant_get_data(vicondata); unsigned char *icon_data = g_memdup2(icon_data_dup, size); argb_to_rgba(width, height, icon_data); + if (height == 0) { + g_variant_unref(vicondata); + return NULL; + } 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, + true, 8, width, height, @@ -194,32 +311,13 @@ get_paintable_from_data(GVariant *iconpixmap_v, int32_t iconsize) paintable = GDK_PAINTABLE(texture); g_object_unref(pixbuf); - g_variant_unref(icon_data_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); + g_variant_unref(vicondata); return paintable; } static void -sn_item_proxy_new_iconname_handler(GObject *obj, GAsyncResult *res, void *data) +new_iconname_handler(GObject *obj, GAsyncResult *res, void *data) { SnItem *self = SN_ITEM(data); GDBusProxy *proxy = G_DBUS_PROXY(obj); @@ -228,13 +326,20 @@ sn_item_proxy_new_iconname_handler(GObject *obj, GAsyncResult *res, void *data) 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); + if (err != NULL) { + switch (err->code) { + case G_DBUS_ERROR_UNKNOWN_OBJECT: + // Remote object went away while call was underway + break; + case G_DBUS_ERROR_UNKNOWN_PROPERTY: + // Expected when ICON_SOURCE_UNKNOWN + break; + default: + g_warning("%s\n", err->message); + break; + } g_error_free(err); + request_newicon_pixmap(self); g_object_unref(self); return; } @@ -245,9 +350,24 @@ sn_item_proxy_new_iconname_handler(GObject *obj, GAsyncResult *res, void *data) g_variant_get(iconname_v, "&s", &iconname); g_variant_unref(retvariant); + // New iconname invalid + if (iconname == NULL || strcmp(iconname, "") == 0) { + self->icon_source = ICON_SOURCE_UNKNOWN; + g_variant_unref(iconname_v); + g_object_unref(self); + request_newicon_pixmap(self); + return; + // New iconname is a path + } else if (access(iconname, R_OK) == 0) { + self->icon_source = ICON_SOURCE_UNKNOWN; + g_variant_unref(iconname_v); + g_object_unref(self); + request_newicon_path(self); + return; + } - if (strcmp(iconname, self->iconname) == 0) { // Icon didn't change + if (strcmp(iconname, self->iconname) == 0) { g_variant_unref(iconname_v); g_object_unref(self); return; @@ -255,39 +375,32 @@ sn_item_proxy_new_iconname_handler(GObject *obj, GAsyncResult *res, void *data) GSList *elem = g_slist_find_custom(self->cachedicons, iconname, - (GCompareFunc)strcmp); + (GCompareFunc)find_cached_icon_name); - if (elem) { // Cache hit - CachedIcon *cicon = (CachedIcon*)elem->data; + if (elem != NULL) { + cached_icon_t *cicon = (cached_icon_t*)elem->data; self->iconname = cicon->iconname; gtk_image_set_from_paintable(GTK_IMAGE(self->image), cicon->icondata); - } else { + g_debug("%s: Icon cache hit - iconname", self->busname); // Cache miss -> cache new icon - CachedIcon *cicon = g_malloc0(sizeof(CachedIcon)); + } else { + cached_icon_t *cicon = g_malloc0(sizeof(cached_icon_t)); self->iconname = g_strdup(iconname); cicon->iconname = self->iconname; cicon->icondata = get_paintable_from_name(self->iconname, self->iconsize); gtk_image_set_from_paintable(GTK_IMAGE(self->image), cicon->icondata); self->cachedicons = g_slist_prepend(self->cachedicons, cicon); + self->icon_source = ICON_SOURCE_NAME; } g_variant_unref(iconname_v); g_object_unref(self); } -static int -find_cached_pixmap(CachedIcon *cicon, GVariant *pixmap) -{ - if (cicon->iconpixmap && g_variant_equal(cicon->iconpixmap, pixmap)) - return 0; - else - return 1; -} - static void -sn_item_proxy_new_pixmaps_handler(GObject *obj, GAsyncResult *res, void *data) +new_iconpath_handler(GObject *obj, GAsyncResult *res, void *data) { SnItem *self = SN_ITEM(data); GDBusProxy *proxy = G_DBUS_PROXY(obj); @@ -296,12 +409,101 @@ sn_item_proxy_new_pixmaps_handler(GObject *obj, GAsyncResult *res, void *data) 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)) { + if (err != NULL) { + switch (err->code) { + case G_DBUS_ERROR_UNKNOWN_OBJECT: + // Remote object went away while call was underway + break; + case G_DBUS_ERROR_UNKNOWN_PROPERTY: + // Expected when ICON_SOURCE_UNKNOWN + break; + default: + g_warning("%s\n", err->message); + break; + } g_error_free(err); + request_newicon_pixmap(self); + g_object_unref(self); + return; + } + + GVariant *viconpath; + const char *iconpath = NULL; + g_variant_get(retvariant, "(v)", &viconpath); + g_variant_get(viconpath, "&s", &iconpath); + g_variant_unref(retvariant); + + // New iconpath is invalid + if (iconpath == NULL || strcmp(iconpath, "") == 0 || access(iconpath, R_OK) == 0) { + self->icon_source = ICON_SOURCE_UNKNOWN; + g_variant_unref(viconpath); + g_object_unref(self); + request_newicon_pixmap(self); + return; + // New iconpath is not a path but possibly an iconname + } else if (iconpath != NULL && strcmp(iconpath, "") != 0) { + self->icon_source = ICON_SOURCE_UNKNOWN; + g_variant_unref(viconpath); + g_object_unref(self); + request_newicon_name(self); + return; + } + + // Icon didn't change + if (strcmp(iconpath, self->iconpath) == 0) { + g_variant_unref(viconpath); + request_newicon_pixmap(self); g_object_unref(self); return; - } else if (err) { - g_warning("%s\n", err->message); + } + + GSList *elem = g_slist_find_custom(self->cachedicons, + iconpath, + (GCompareFunc)find_cached_icon_path); + + // Cache hit + if (elem != NULL) { + cached_icon_t *cicon = (cached_icon_t*)elem->data; + self->iconpath = cicon->iconpath; + gtk_image_set_from_paintable(GTK_IMAGE(self->image), cicon->icondata); + g_debug("%s: Icon cache hit - iconpath", self->busname); + // Cache miss -> cache new icon + } else { + cached_icon_t *cicon = g_malloc0(sizeof(cached_icon_t)); + self->iconpath = g_strdup(iconpath); + cicon->iconpath = self->iconpath; + cicon->icondata = get_paintable_from_name(self->iconpath, + self->iconsize); + gtk_image_set_from_paintable(GTK_IMAGE(self->image), cicon->icondata); + self->cachedicons = g_slist_prepend(self->cachedicons, cicon); + self->icon_source = ICON_SOURCE_PATH; + } + + g_variant_unref(viconpath); + g_object_unref(self); +} + +static void +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 != NULL) { + switch (err->code) { + case G_DBUS_ERROR_UNKNOWN_OBJECT: + // Remote object went away while call was underway + break; + case G_DBUS_ERROR_UNKNOWN_PROPERTY: + // Expected when ICON_SOURCE_UNKNOWN + break; + default: + g_warning("%s\n", err->message); + } g_error_free(err); g_object_unref(self); return; @@ -312,8 +514,17 @@ sn_item_proxy_new_pixmaps_handler(GObject *obj, GAsyncResult *res, void *data) GVariant *pixmap = select_icon_by_size(newpixmaps, self->iconsize); - if (g_variant_equal(pixmap, self->iconpixmap)) { + // No valid icon in data + if (pixmap == NULL) { + self->icon_source = ICON_SOURCE_UNKNOWN; + g_variant_unref(newpixmaps); + g_variant_unref(retvariant); + g_object_unref(self); + return; + } + // Icon didn't change + if (self->iconpixmap && g_variant_equal(pixmap, self->iconpixmap)) { g_variant_unref(pixmap); g_variant_unref(newpixmaps); g_variant_unref(retvariant); @@ -323,22 +534,24 @@ sn_item_proxy_new_pixmaps_handler(GObject *obj, GAsyncResult *res, void *data) GSList *elem = g_slist_find_custom(self->cachedicons, pixmap, - (GCompareFunc)find_cached_pixmap); + (GCompareFunc)find_cached_icon_pixmap); - if (elem) { // Cache hit - CachedIcon *cicon = (CachedIcon*)elem->data; + if (elem != NULL) { + cached_icon_t *cicon = (cached_icon_t*)elem->data; self->iconpixmap = cicon->iconpixmap; gtk_image_set_from_paintable(GTK_IMAGE(self->image), cicon->icondata); - } else { + g_debug("%s: Icon cache hit - pixmap", self->busname); // Cache miss -> cache new icon - CachedIcon *cicon = g_malloc0(sizeof(CachedIcon)); + } else { + cached_icon_t *cicon = g_malloc0(sizeof(cached_icon_t)); self->iconpixmap = g_variant_ref(pixmap); cicon->iconpixmap = self->iconpixmap; cicon->icondata = get_paintable_from_data(self->iconpixmap, self->iconsize); gtk_image_set_from_paintable(GTK_IMAGE(self->image), cicon->icondata); self->cachedicons = g_slist_prepend(self->cachedicons, cicon); + self->icon_source = ICON_SOURCE_PIXMAP; } g_variant_unref(pixmap); @@ -348,7 +561,52 @@ sn_item_proxy_new_pixmaps_handler(GObject *obj, GAsyncResult *res, void *data) } static void -sn_item_proxy_signal_handler(GDBusProxy *proxy, +request_newicon_name(SnItem *self) +{ + g_dbus_proxy_call(self->proxy, + "org.freedesktop.DBus.Properties.Get", + g_variant_new("(ss)", + "org.kde.StatusNotifierItem", + "IconName"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + new_iconname_handler, + g_object_ref(self)); +} + +static void +request_newicon_path(SnItem *self) +{ + g_dbus_proxy_call(self->proxy, + "org.freedesktop.DBus.Properties.Get", + g_variant_new("(ss)", + "org.kde.StatusNotifierItem", + "IconName"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + new_iconpath_handler, + g_object_ref(self)); +} + +static void +request_newicon_pixmap(SnItem *self) +{ + g_dbus_proxy_call(self->proxy, + "org.freedesktop.DBus.Properties.Get", + g_variant_new("(ss)", + "org.kde.StatusNotifierItem", + "IconPixmap"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + new_pixmaps_handler, + g_object_ref(self)); +} + +static void +proxy_signal_handler(GDBusProxy *proxy, const char *sender, const char *signal, GVariant *data_v, @@ -357,172 +615,218 @@ sn_item_proxy_signal_handler(GDBusProxy *proxy, SnItem *self = SN_ITEM(data); if (strcmp(signal, "NewIcon") == 0) { - if (self->iconpixmap) - 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)); + switch (self->icon_source) { + case ICON_SOURCE_NAME: + request_newicon_name(self); + break; + case ICON_SOURCE_PATH: + request_newicon_path(self); + break; + case ICON_SOURCE_PIXMAP: + request_newicon_pixmap(self); + break; + default: + request_newicon_name(self); + break; + } } } static void -sn_item_popup(SnDbusmenu *dbusmenu, SnItem *self) +popup_popover(SnDbusmenu *dbusmenu, SnItem *self) { - g_return_if_fail(SN_IS_ITEM(self) || GTK_IS_POPOVER_MENU(self->popovermenu)); + if (self->in_destruction) + return; - g_object_set(self, "menuvisible", TRUE, NULL); + g_object_set(self, "menuvisible", true, NULL); gtk_popover_popup(GTK_POPOVER(self->popovermenu)); } static void -sn_item_proxy_ready_handler(GObject *obj, GAsyncResult *res, void *data) +leftclick_handler(GtkGestureClick *click, + int n_press, + double x, + double y, + void *data) { SnItem *self = SN_ITEM(data); - GError *err = NULL; - GDBusProxy *proxy = g_dbus_proxy_new_for_bus_finish(res, &err); + g_dbus_proxy_call(self->proxy, + "Activate", + g_variant_new("(ii)", 0, 0), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); +} - if (err) { - g_warning("Failed to construct gdbusproxy for snitem: %s\n", err->message); - g_error_free(err); - g_object_unref(self); +static void +rightclick_handler(GtkGestureClick *click, + int n_press, + double x, + double y, + void *data) +{ + SnItem *self = SN_ITEM(data); + if (self->in_destruction) return; - } - g_debug("Created gdbusproxy for snitem %s %s", - g_dbus_proxy_get_name(proxy), - g_dbus_proxy_get_object_path(proxy)); - - g_object_set(self, "proxy", proxy, NULL); + g_signal_emit(self, signals[RIGHTCLICK], 0); +} - g_signal_connect(self->proxy, "g-signal", G_CALLBACK(sn_item_proxy_signal_handler), self); +static void +connect_to_menu(SnItem *self) +{ + if (self->in_destruction) + return; - 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); + const char *menu_buspath = NULL; + GVariant *vmenupath = g_dbus_proxy_get_cached_property(self->proxy, "Menu"); + + if (vmenupath != NULL) { + g_variant_get(vmenupath, "&o", &menu_buspath); + if (strcmp(menu_buspath, "") != 0) { + self->dbusmenu = sn_dbusmenu_new(self->busname, menu_buspath, self); + + self->rclick_id = g_signal_connect(self->rclick, + "pressed", + G_CALLBACK(rightclick_handler), + self); + + self->popup_id = g_signal_connect(self->dbusmenu, + "abouttoshowhandled", + G_CALLBACK(popup_popover), + self); + } + g_variant_unref(vmenupath); } +} - char *iconname = NULL; - GVariant *iconname_v = g_dbus_proxy_get_cached_property(proxy, "IconName"); - GVariant *iconpixmaps = 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; +static void +select_icon_source(SnItem *self) +{ + char *iconname_or_path = NULL; + GVariant *vname = g_dbus_proxy_get_cached_property(self->proxy, "IconName"); + GVariant *vpixmaps = g_dbus_proxy_get_cached_property(self->proxy, "IconPixmap"); + + if (vname != NULL) { + g_variant_get(vname, "s", &iconname_or_path); + if (strcmp(iconname_or_path, "") == 0) { + g_free(iconname_or_path); + iconname_or_path = NULL; } - g_variant_unref(iconname_v); } - if (iconname) { - self->iconname = iconname; - } else if (iconpixmaps) { - self->iconpixmap = select_icon_by_size(iconpixmaps, self->iconsize); + if (iconname_or_path != NULL && access(iconname_or_path, R_OK) == 0) { + self->iconpath = iconname_or_path; + self->icon_source = ICON_SOURCE_PATH; + } else if (iconname_or_path != NULL) { + self->iconname = iconname_or_path; + self->icon_source = ICON_SOURCE_NAME; + } else if (vpixmaps != NULL) { + GVariant *pixmap = select_icon_by_size(vpixmaps, self->iconsize); + if (pixmap != NULL) { + self->iconpixmap = pixmap; + self->icon_source = ICON_SOURCE_PIXMAP; + } } else { - self->iconname = g_strdup("noicon"); + self->iconname = g_strdup("missing-icon"); + self->icon_source = ICON_SOURCE_UNKNOWN; } - if (iconpixmaps) - g_variant_unref(iconpixmaps); + if (vname != NULL) + g_variant_unref(vname); + if (vpixmaps != NULL) + g_variant_unref(vpixmaps); +} - CachedIcon *cicon = g_malloc0(sizeof(CachedIcon)); +static void +add_icontheme_path(GDBusProxy *proxy) +{ + const char *iconthemepath; + GVariant *viconthemepath; + GtkIconTheme *theme; - GdkPaintable *paintable; - if (self->iconname) { - paintable = get_paintable_from_name(self->iconname, self->iconsize); - cicon->iconname = self->iconname; - cicon->icondata = paintable; - } else { - paintable = get_paintable_from_data(self->iconpixmap, self->iconsize); - cicon->iconpixmap = self->iconpixmap; - cicon->icondata = paintable; + viconthemepath = g_dbus_proxy_get_cached_property(proxy, "IconThemePath"); + theme = gtk_icon_theme_get_for_display(gdk_display_get_default()); + + if (viconthemepath != NULL) { + g_variant_get(viconthemepath, "&s", &iconthemepath); + gtk_icon_theme_add_search_path(theme, iconthemepath); + g_variant_unref(viconthemepath); } +} - self->cachedicons = g_slist_prepend(self->cachedicons, cicon); +static void +proxy_ready_handler(GObject *obj, GAsyncResult *res, void *data) +{ + SnItem *self = SN_ITEM(data); - gtk_image_set_from_paintable(GTK_IMAGE(self->image), paintable); + GError *err = NULL; + GDBusProxy *proxy = g_dbus_proxy_new_for_bus_finish(res, &err); - const char *menu_buspath = NULL; - GVariant *menu_buspath_v = g_dbus_proxy_get_cached_property(self->proxy, "Menu"); + if (err != NULL) { + g_warning("Failed to construct gdbusproxy for snitem: %s\n", err->message); + g_error_free(err); + g_object_unref(self); + return; + } + self->proxy = proxy; + self->proxy_id = g_signal_connect(self->proxy, + "g-signal", + G_CALLBACK(proxy_signal_handler), + self); - if (menu_buspath_v && !self->exiting) { - 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); + add_icontheme_path(proxy); + select_icon_source(self); - self->popup_sig_id = g_signal_connect(self->dbusmenu, - "abouttoshowhandled", - G_CALLBACK(sn_item_popup), - self); + GdkPaintable *paintable; + cached_icon_t *cicon = g_malloc0(sizeof(cached_icon_t)); - g_variant_unref(menu_buspath_v); + switch (self->icon_source) { + case ICON_SOURCE_NAME: + paintable = get_paintable_from_name(self->iconname, self->iconsize); + cicon->iconname = self->iconname; + cicon->icondata = paintable; + break; + case ICON_SOURCE_PIXMAP: + paintable = get_paintable_from_data(self->iconpixmap, self->iconsize); + cicon->iconpixmap = self->iconpixmap; + cicon->icondata = paintable; + break; + case ICON_SOURCE_PATH: + paintable = get_paintable_from_path(self->iconpath, self->iconsize); + cicon->iconpath = self->iconpath; + cicon->icondata = paintable; + break; + case ICON_SOURCE_UNKNOWN: + paintable = get_paintable_from_name(self->iconname, self->iconsize); + cicon->iconname = self->iconname; + cicon->icondata = paintable; + break; + default: + g_assert_not_reached(); + break; } - self->ready = TRUE; - g_object_unref(self); -} -static void -sn_item_notify_closed(GtkPopover *popover, void *data) -{ - SnItem *self = SN_ITEM(data); - g_object_set(self, "menuvisible", FALSE, NULL); -} + self->cachedicons = g_slist_prepend(self->cachedicons, cicon); + gtk_image_set_from_paintable(GTK_IMAGE(self->image), paintable); + self->lclick_id = g_signal_connect(self->lclick, + "pressed", + G_CALLBACK(leftclick_handler), + self); + connect_to_menu(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); + g_object_unref(self); } static void -sn_item_rightclick_handler(GtkGestureClick *click, - int n_press, - double x, - double y, - void *data) +sn_item_notify_closed(GtkPopover *popover, void *data) { SnItem *self = SN_ITEM(data); - if (!self->ready) - return; - - g_signal_emit(self, signals[RIGHTCLICK], 0); + g_object_set(self, "menuvisible", false, NULL); } static void @@ -578,9 +882,6 @@ sn_item_set_property(GObject *object, 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; @@ -662,12 +963,6 @@ sn_item_class_init(SnItemClass *klass) G_PARAM_CONSTRUCT_ONLY | 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, @@ -676,7 +971,7 @@ sn_item_class_init(SnItemClass *klass) obj_properties[PROP_MENUVISIBLE] = g_param_spec_boolean("menuvisible", NULL, NULL, - FALSE, + false, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); @@ -692,6 +987,8 @@ sn_item_class_init(SnItemClass *klass) NULL, G_TYPE_NONE, 0); + + gtk_widget_class_set_css_name(widget_class, "systray-item"); } static void @@ -699,32 +996,25 @@ sn_item_init(SnItem *self) { GtkWidget *widget = GTK_WIDGET(self); - self->exiting = FALSE; - - gtk_widget_set_hexpand(widget, TRUE); - gtk_widget_set_vexpand(widget, TRUE); + self->in_destruction = false; + self->icon_source = ICON_SOURCE_UNKNOWN; 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); self->init_menu = g_menu_new(); self->popovermenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(self->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_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)); + self->lclick = gtk_gesture_click_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(self->lclick), 1); + gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(self->lclick)); - 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)); + self->rclick = gtk_gesture_click_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(self->rclick), 3); + gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(self->rclick)); g_signal_connect(self->popovermenu, "closed", G_CALLBACK(sn_item_notify_closed), self); } @@ -742,45 +1032,46 @@ sn_item_constructed(GObject *obj) self->busobj, "org.kde.StatusNotifierItem", NULL, - sn_item_proxy_ready_handler, + 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); - self->exiting = TRUE; + self->in_destruction = true; + + g_clear_signal_handler(&self->lclick_id, self->lclick); + g_clear_signal_handler(&self->rclick_id, self->rclick); + g_clear_signal_handler(&self->proxy_id, self->proxy); + g_clear_signal_handler(&self->popup_id, self->dbusmenu); - if (self->dbusmenu) { + if (self->dbusmenu != NULL) { // Unref will be called from sndbusmenu dispose function g_object_ref(self); - g_signal_handler_disconnect(self->dbusmenu, self->popup_sig_id); - self->popup_sig_id = 0; g_object_unref(self->dbusmenu); self->dbusmenu = NULL; } - if (self->proxy) { + if (self->proxy != NULL) { g_object_unref(self->proxy); self->proxy = NULL; } - if (self->popovermenu) { + if (self->popovermenu != NULL) { gtk_widget_unparent(self->popovermenu); self->popovermenu = NULL; g_object_unref(self->init_menu); self->init_menu = NULL; } - if (self->image) { + if (self->image != NULL) { gtk_widget_unparent(self->image); self->image = NULL; } @@ -808,7 +1099,7 @@ sn_item_set_menu_model(SnItem *self, GMenu* menu) g_return_if_fail(SN_IS_ITEM(self)); g_return_if_fail(G_IS_MENU(menu)); - if (!self->popovermenu) + if (self->popovermenu == NULL) return; gtk_popover_menu_set_menu_model(GTK_POPOVER_MENU(self->popovermenu), G_MENU_MODEL(menu)); @@ -819,7 +1110,7 @@ sn_item_clear_menu_model(SnItem *self) { g_return_if_fail(SN_IS_ITEM(self)); - if (!self->popovermenu) + if (self->popovermenu == NULL) return; GtkPopoverMenu *popovermenu = GTK_POPOVER_MENU(self->popovermenu); @@ -849,27 +1140,12 @@ sn_item_clear_actiongroup(SnItem *self, const char *prefix) NULL); } -char* -sn_item_get_busname(SnItem *self) -{ - g_return_val_if_fail(SN_IS_ITEM(self), NULL); - - char *busname; - g_object_get(self, "busname", &busname, NULL); - - return busname; -} - gboolean sn_item_get_popover_visible(SnItem *self) { - g_return_val_if_fail(SN_IS_ITEM(self), FALSE); - - gboolean visible; - - g_object_get(self, "menuvisible", &visible, NULL); + g_return_val_if_fail(SN_IS_ITEM(self), true); - return visible; + return self->menu_visible; } SnItem*