summaryrefslogtreecommitdiff
path: root/applets/notification_area/status-notifier/sn-item-v0.c
diff options
context:
space:
mode:
authorColomban Wendling <[email protected]>2017-01-19 18:46:10 +0100
committerlukefromdc <[email protected]>2017-01-23 13:49:34 -0500
commit7d39b2e82f46777efa67224f078c1cec9e827654 (patch)
tree7a415a153f4a3afb52358ed8a34ced08c25bdc5c /applets/notification_area/status-notifier/sn-item-v0.c
parenta506150684ad2e71b1f70190ee70fe9eda7a4ba9 (diff)
downloadmate-panel-7d39b2e82f46777efa67224f078c1cec9e827654.tar.bz2
mate-panel-7d39b2e82f46777efa67224f078c1cec9e827654.tar.xz
Add StatusNotifier support to the Notification Area applet
The StatusNotifier part of the implementation is based off gnome-panel's status-notifier applet.
Diffstat (limited to 'applets/notification_area/status-notifier/sn-item-v0.c')
-rw-r--r--applets/notification_area/status-notifier/sn-item-v0.c1355
1 files changed, 1355 insertions, 0 deletions
diff --git a/applets/notification_area/status-notifier/sn-item-v0.c b/applets/notification_area/status-notifier/sn-item-v0.c
new file mode 100644
index 00000000..eb5a95c2
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-item-v0.c
@@ -0,0 +1,1355 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ * Copyright (C) 2017 Colomban Wendling <[email protected]>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <math.h>
+
+#include "sn-item.h"
+#include "sn-item-v0.h"
+#include "sn-item-v0-gen.h"
+
+#define SN_ITEM_INTERFACE "org.kde.StatusNotifierItem"
+
+typedef struct
+{
+ cairo_surface_t *surface;
+ gint width;
+ gint height;
+} SnIconPixmap;
+
+typedef struct
+{
+ gchar *icon_name;
+ SnIconPixmap **icon_pixmap;
+ gchar *title;
+ gchar *text;
+} SnTooltip;
+
+struct _SnItemV0
+{
+ SnItem parent;
+
+ GtkWidget *image;
+ gint icon_size;
+ gint effective_icon_size;
+
+ GCancellable *cancellable;
+ SnItemV0Gen *proxy;
+
+ gchar *id;
+ gchar *category;
+ gchar *status;
+
+ gchar *title;
+ gint32 window_id;
+ gchar *icon_name;
+ SnIconPixmap **icon_pixmap;
+ gchar *overlay_icon_name;
+ SnIconPixmap **overlay_icon_pixmap;
+ gchar *attention_icon_name;
+ SnIconPixmap **attention_icon_pixmap;
+ gchar *attention_movie_name;
+ SnTooltip *tooltip;
+ gchar *icon_theme_path;
+ gchar *menu;
+ gboolean item_is_menu;
+
+ guint update_id;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_ICON_SIZE,
+
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
+G_DEFINE_TYPE (SnItemV0, sn_item_v0, SN_TYPE_ITEM)
+
+static cairo_surface_t *
+scale_surface (SnIconPixmap *pixmap,
+ GtkOrientation orientation,
+ gint size)
+{
+ gdouble ratio;
+ gdouble new_width;
+ gdouble new_height;
+ gdouble scale_x;
+ gdouble scale_y;
+ gint width;
+ gint height;
+ cairo_content_t content;
+ cairo_surface_t *scaled;
+ cairo_t *cr;
+
+ ratio = pixmap->width / (gdouble) pixmap->height;
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ new_height = (gdouble) size;
+ new_width = new_height * ratio;
+ }
+ else
+ {
+ new_width = (gdouble) size;
+ new_height = new_width * ratio;
+ }
+
+ scale_x = new_width / pixmap->width;
+ scale_y = new_height / pixmap->height;
+
+ width = ceil (new_width);
+ height = ceil (new_height);
+
+ content = CAIRO_CONTENT_COLOR_ALPHA;
+ scaled = cairo_surface_create_similar (pixmap->surface, content, width, height);
+ cr = cairo_create (scaled);
+
+ cairo_scale (cr, scale_x, scale_y);
+ cairo_set_source_surface (cr, pixmap->surface, 0, 0);
+ cairo_paint (cr);
+
+ cairo_destroy (cr);
+ return scaled;
+}
+
+static gint
+compare_size (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ SnIconPixmap *p1;
+ SnIconPixmap *p2;
+ GtkOrientation orientation;
+
+ p1 = (SnIconPixmap *) a;
+ p2 = (SnIconPixmap *) b;
+ orientation = GPOINTER_TO_UINT (user_data);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ return p1->height - p2->height;
+ else
+ return p1->width - p2->width;
+}
+
+static GList *
+get_pixmaps_sorted (SnItemV0 *v0,
+ GtkOrientation orientation,
+ gint size)
+{
+ GList *pixmaps;
+ guint i;
+
+ pixmaps = NULL;
+ for (i = 0; v0->icon_pixmap[i] != NULL; i++)
+ pixmaps = g_list_prepend (pixmaps, v0->icon_pixmap[i]);
+
+ return g_list_sort_with_data (pixmaps, compare_size,
+ GUINT_TO_POINTER (orientation));
+}
+
+static cairo_surface_t *
+get_surface (SnItemV0 *v0,
+ GtkOrientation orientation,
+ gint size)
+{
+ GList *pixmaps;
+ cairo_surface_t *surface;
+ SnIconPixmap *best;
+ GList *l;
+
+ g_assert (v0->icon_pixmap != NULL && v0->icon_pixmap[0] != NULL);
+
+ pixmaps = get_pixmaps_sorted (v0, orientation, size);
+ surface = NULL;
+
+ for (l = pixmaps; l != NULL; l = l->next)
+ {
+ SnIconPixmap *pixmap;
+
+ pixmap = (SnIconPixmap *) l->data;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (pixmap->height == size)
+ {
+ surface = pixmap->surface;
+ break;
+ }
+ else if (pixmap->height > size)
+ {
+ best = pixmap;
+ break;
+ }
+ }
+ else
+ {
+ if (pixmap->width == size)
+ {
+ surface = pixmap->surface;
+ break;
+ }
+ else if (pixmap->width > size)
+ {
+ best = pixmap;
+ break;
+ }
+ }
+
+ best = pixmap;
+ }
+
+ g_list_free (pixmaps);
+
+ if (surface != NULL)
+ return cairo_surface_reference (surface);
+
+ return scale_surface (best, orientation, size);
+}
+
+static void
+update (SnItemV0 *v0)
+{
+ AtkObject *accessible;
+ GtkImage *image;
+ SnTooltip *tip;
+ gboolean visible;
+ gint icon_size;
+
+ g_return_if_fail (SN_IS_ITEM_V0 (v0));
+
+ image = GTK_IMAGE (v0->image);
+
+ if (v0->icon_size > 0)
+ icon_size = v0->icon_size;
+ else
+ icon_size = MAX (1, v0->effective_icon_size);
+
+ if (v0->icon_name != NULL && *v0->icon_name != '\0')
+ {
+ GtkIconTheme *icon_theme;
+
+ icon_theme = gtk_icon_theme_get_default ();
+
+ gtk_icon_theme_rescan_if_needed (icon_theme);
+ gtk_image_set_from_icon_name (image, v0->icon_name, GTK_ICON_SIZE_MENU);
+ gtk_image_set_pixel_size (image, icon_size);
+ }
+ else if (v0->icon_pixmap != NULL && v0->icon_pixmap[0] != NULL)
+ {
+ cairo_surface_t *surface;
+
+ surface = get_surface (v0,
+ gtk_orientable_get_orientation (GTK_ORIENTABLE (v0)),
+ icon_size);
+ gtk_image_set_from_surface (image, surface);
+ cairo_surface_destroy (surface);
+ }
+ else
+ {
+ gtk_image_set_from_icon_name (image, "image-missing", GTK_ICON_SIZE_MENU);
+ gtk_image_set_pixel_size (image, icon_size);
+ }
+
+ tip = v0->tooltip;
+
+ if (tip != NULL)
+ {
+ gchar *markup;
+
+ markup = NULL;
+
+ if ((tip->title != NULL && *tip->title != '\0') &&
+ (tip->text != NULL && *tip->text != '\0'))
+ {
+ markup = g_strdup_printf ("%s\n%s", tip->title, tip->text);
+ }
+ else if (tip->title != NULL && *tip->title != '\0')
+ {
+ markup = g_strdup (tip->title);
+ }
+ else if (tip->text != NULL && *tip->text != '\0')
+ {
+ markup = g_strdup (tip->text);
+ }
+
+ gtk_widget_set_tooltip_markup (GTK_WIDGET (v0), markup);
+ g_free (markup);
+ }
+ else
+ {
+ gtk_widget_set_tooltip_markup (GTK_WIDGET (v0), NULL);
+ }
+
+ accessible = gtk_widget_get_accessible (GTK_WIDGET (v0));
+
+ if (v0->title != NULL && *v0->title != '\0')
+ atk_object_set_name (accessible, v0->title);
+ else
+ atk_object_set_name (accessible, v0->id);
+
+ visible = g_strcmp0 (v0->status, "Passive") != 0;
+ gtk_widget_set_visible (GTK_WIDGET (v0), visible);
+}
+
+static gboolean
+update_cb (gpointer user_data)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ v0->update_id = 0;
+ update (v0);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+queue_update (SnItemV0 *v0)
+{
+ if (v0->update_id != 0)
+ return;
+
+ v0->update_id = g_timeout_add (10, update_cb, v0);
+ g_source_set_name_by_id (v0->update_id, "[status-notifier] update_cb");
+}
+
+static cairo_surface_t *
+surface_from_variant (GVariant *variant,
+ gint width,
+ gint height)
+{
+ cairo_format_t format;
+ gint stride;
+ guint32 *data;
+
+ format = CAIRO_FORMAT_ARGB32;
+ stride = cairo_format_stride_for_width (format, width);
+ data = (guint32 *) g_variant_get_data (variant);
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ {
+ gint i;
+
+ for (i = 0; i < width * height; i++)
+ data[i] = GUINT32_FROM_BE (data[i]);
+ }
+#endif
+
+ return cairo_image_surface_create_for_data ((guchar *) data, format,
+ width, height, stride);
+}
+
+static cairo_surface_t *
+icon_surface_new (GVariant *variant,
+ gint width,
+ gint height)
+{
+ cairo_surface_t *surface;
+ cairo_surface_t *tmp;
+ cairo_t *cr;
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
+ return NULL;
+
+ tmp = surface_from_variant (variant, width, height);
+ if (cairo_surface_status (tmp) != CAIRO_STATUS_SUCCESS)
+ {
+ cairo_surface_destroy (surface);
+ return NULL;
+ }
+
+ cr = cairo_create (surface);
+ if (cairo_status (cr) != CAIRO_STATUS_SUCCESS)
+ {
+ cairo_surface_destroy (surface);
+ cairo_surface_destroy (tmp);
+ return NULL;
+ }
+
+ cairo_set_source_surface (cr, tmp, 0, 0);
+ cairo_paint (cr);
+
+ cairo_surface_destroy (tmp);
+ cairo_destroy (cr);
+
+ return surface;
+}
+
+static SnIconPixmap **
+icon_pixmap_new (GVariant *variant)
+{
+ GPtrArray *array;
+ GVariantIter iter;
+ gint width;
+ gint height;
+ GVariant *value;
+
+ if (variant == NULL || g_variant_iter_init (&iter, variant) == 0)
+ return NULL;
+
+ array = g_ptr_array_new ();
+ while (g_variant_iter_next (&iter, "(ii@ay)", &width, &height, &value))
+ {
+ cairo_surface_t *surface;
+
+ if (width == 0 || height == 0)
+ {
+ g_variant_unref (value);
+ continue;
+ }
+
+ surface = icon_surface_new (value, width, height);
+ g_variant_unref (value);
+
+ if (surface != NULL)
+ {
+ SnIconPixmap *pixmap;
+
+ pixmap = g_new0 (SnIconPixmap, 1);
+
+ pixmap->surface = surface;
+ pixmap->width = width;
+ pixmap->height = height;
+
+ g_ptr_array_add (array, pixmap);
+ }
+ }
+
+ g_ptr_array_add (array, NULL);
+ return (SnIconPixmap **) g_ptr_array_free (array, FALSE);
+}
+
+static void
+icon_pixmap_free (SnIconPixmap **data)
+{
+ gint i;
+
+ if (data == NULL)
+ return;
+
+ for (i = 0; data[i] != NULL; i++)
+ {
+ cairo_surface_destroy (data[i]->surface);
+ g_free (data[i]);
+ }
+
+ g_free (data);
+}
+
+static SnTooltip *
+sn_tooltip_new (GVariant *variant)
+{
+ const gchar *icon_name;
+ GVariant *icon_pixmap;
+ const gchar *title;
+ const gchar *text;
+ SnTooltip *tooltip;
+
+ if (variant == NULL)
+ return NULL;
+
+ g_variant_get (variant, "(&s@a(iiay)&s&s)",
+ &icon_name, &icon_pixmap,
+ &title, &text);
+
+ tooltip = g_new0 (SnTooltip, 1);
+
+ tooltip->icon_name = g_strdup (icon_name);
+ tooltip->icon_pixmap = icon_pixmap_new (icon_pixmap);
+ tooltip->title = g_strdup (title);
+ tooltip->text = g_strdup (text);
+
+ g_variant_unref (icon_pixmap);
+ return tooltip;
+}
+
+static void
+sn_tooltip_free (SnTooltip *tooltip)
+{
+ if (tooltip == NULL)
+ return;
+
+ g_free (tooltip->icon_name);
+ icon_pixmap_free (tooltip->icon_pixmap);
+ g_free (tooltip->title);
+ g_free (tooltip->text);
+
+ g_free (tooltip);
+}
+
+static GVariant *
+get_property (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data,
+ gboolean *cancelled)
+{
+ GVariant *variant;
+ GError *error;
+ GVariant *property;
+
+ error = NULL;
+ variant = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res, &error);
+
+ *cancelled = FALSE;
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ *cancelled = TRUE;
+ g_error_free (error);
+ return NULL;
+ }
+
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ g_variant_get (variant, "(v)", &property);
+ g_variant_unref (variant);
+
+ return property;
+}
+
+static void
+update_property (SnItemV0 *v0,
+ const gchar *property,
+ GAsyncReadyCallback callback)
+{
+ GDBusProxy *proxy;
+ SnItem *item;
+
+ proxy = G_DBUS_PROXY (v0->proxy);
+ item = SN_ITEM (v0);
+
+ g_dbus_connection_call (g_dbus_proxy_get_connection (proxy),
+ sn_item_get_bus_name (item),
+ sn_item_get_object_path (item),
+ "org.freedesktop.DBus.Properties", "Get",
+ g_variant_new ("(ss)", SN_ITEM_INTERFACE, property),
+ G_VARIANT_TYPE ("(v)"),
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ v0->cancellable, callback, v0);
+}
+
+static void
+update_title (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+ GVariant *variant;
+ gboolean cancelled;
+
+ variant = get_property (source_object, res, user_data, &cancelled);
+ if (cancelled)
+ return;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ g_clear_pointer (&v0->title, g_free);
+ v0->title = g_variant_dup_string (variant, NULL);
+ g_clear_pointer (&variant, g_variant_unref);
+
+ queue_update (v0);
+}
+
+static void
+new_title_cb (SnItemV0 *v0)
+{
+ update_property (v0, "Title", update_title);
+}
+
+static void
+update_icon_name (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+ GVariant *variant;
+ gboolean cancelled;
+
+ variant = get_property (source_object, res, user_data, &cancelled);
+ if (cancelled)
+ return;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ g_clear_pointer (&v0->icon_name, g_free);
+ v0->icon_name = g_variant_dup_string (variant, NULL);
+ g_clear_pointer (&variant, g_variant_unref);
+
+ queue_update (v0);
+}
+
+static void
+update_icon_pixmap (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+ GVariant *variant;
+ gboolean cancelled;
+
+ variant = get_property (source_object, res, user_data, &cancelled);
+ if (cancelled)
+ return;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ g_clear_pointer (&v0->icon_pixmap, icon_pixmap_free);
+ v0->icon_pixmap = icon_pixmap_new (variant);
+ g_clear_pointer (&variant, g_variant_unref);
+
+ queue_update (v0);
+}
+
+static void
+new_icon_cb (SnItemV0 *v0)
+{
+ update_property (v0, "IconName", update_icon_name);
+ update_property (v0, "IconPixmap", update_icon_pixmap);
+}
+
+static void
+update_overlay_icon_name (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+ GVariant *variant;
+ gboolean cancelled;
+
+ variant = get_property (source_object, res, user_data, &cancelled);
+ if (cancelled)
+ return;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ g_clear_pointer (&v0->overlay_icon_name, g_free);
+ v0->overlay_icon_name = g_variant_dup_string (variant, NULL);
+ g_clear_pointer (&variant, g_variant_unref);
+
+ queue_update (v0);
+}
+
+static void
+update_overlay_icon_pixmap (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+ GVariant *variant;
+ gboolean cancelled;
+
+ variant = get_property (source_object, res, user_data, &cancelled);
+ if (cancelled)
+ return;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ g_clear_pointer (&v0->overlay_icon_pixmap, icon_pixmap_free);
+ v0->overlay_icon_pixmap = icon_pixmap_new (variant);
+ g_clear_pointer (&variant, g_variant_unref);
+
+ queue_update (v0);
+}
+
+static void
+new_overlay_icon_cb (SnItemV0 *v0)
+{
+ update_property (v0, "OverlayIconName", update_overlay_icon_name);
+ update_property (v0, "OverlayIconPixmap", update_overlay_icon_pixmap);
+}
+
+static void
+update_attention_icon_name (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+ GVariant *variant;
+ gboolean cancelled;
+
+ variant = get_property (source_object, res, user_data, &cancelled);
+ if (cancelled)
+ return;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ g_clear_pointer (&v0->attention_icon_name, g_free);
+ v0->attention_icon_name = g_variant_dup_string (variant, NULL);
+ g_clear_pointer (&variant, g_variant_unref);
+
+ queue_update (v0);
+}
+
+static void
+update_attention_icon_pixmap (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+ GVariant *variant;
+ gboolean cancelled;
+
+ variant = get_property (source_object, res, user_data, &cancelled);
+ if (cancelled)
+ return;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ g_clear_pointer (&v0->attention_icon_pixmap, icon_pixmap_free);
+ v0->attention_icon_pixmap = icon_pixmap_new (variant);
+ g_clear_pointer (&variant, g_variant_unref);
+
+ queue_update (v0);
+}
+
+static void
+new_attention_icon_cb (SnItemV0 *v0)
+{
+ update_property (v0, "AttentionIconName", update_attention_icon_name);
+ update_property (v0, "AttentionIconPixmap", update_attention_icon_pixmap);
+}
+
+static void
+update_tooltip (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+ GVariant *variant;
+ gboolean cancelled;
+
+ variant = get_property (source_object, res, user_data, &cancelled);
+ if (cancelled)
+ return;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ g_clear_pointer (&v0->tooltip, sn_tooltip_free);
+ v0->tooltip = sn_tooltip_new (variant);
+ g_clear_pointer (&variant, g_variant_unref);
+
+ queue_update (v0);
+}
+
+static void
+new_tooltip_cb (SnItemV0 *v0)
+{
+ update_property (v0, "ToolTip", update_tooltip);
+}
+
+static void
+new_status_cb (SnItemV0 *v0,
+ GVariant *parameters)
+{
+ GVariant *variant;
+
+ variant = g_variant_get_child_value (parameters, 0);
+
+ g_free (v0->status);
+ v0->status = g_variant_dup_string (variant, NULL);
+ g_variant_unref (variant);
+
+ queue_update (v0);
+}
+
+static void
+new_icon_theme_path_cb (SnItemV0 *v0,
+ GVariant *parameters)
+{
+ GVariant *variant;
+
+ variant = g_variant_get_child_value (parameters, 0);
+
+ g_free (v0->icon_theme_path);
+ v0->icon_theme_path = g_variant_dup_string (variant, NULL);
+ g_variant_unref (variant);
+
+ if (v0->icon_theme_path != NULL)
+ {
+ GtkIconTheme *icon_theme;
+
+ icon_theme = gtk_icon_theme_get_default ();
+
+ gtk_icon_theme_append_search_path (icon_theme, v0->icon_theme_path);
+ }
+
+ queue_update (v0);
+}
+
+static void
+g_properties_changed_cb (GDBusProxy *proxy,
+ GVariant *changed_properties,
+ GStrv invalidated_properties,
+ SnItemV0 *v0)
+{
+ gchar *debug;
+
+ debug = g_variant_print (changed_properties, FALSE);
+ g_debug ("g_properties_changed_cb: %s", debug);
+ g_free (debug);
+}
+
+static void
+g_signal_cb (GDBusProxy *proxy,
+ gchar *sender_name,
+ gchar *signal_name,
+ GVariant *parameters,
+ SnItemV0 *v0)
+{
+ if (g_strcmp0 (signal_name, "NewTitle") == 0)
+ new_title_cb (v0);
+ else if (g_strcmp0 (signal_name, "NewIcon") == 0)
+ new_icon_cb (v0);
+ else if (g_strcmp0 (signal_name, "NewOverlayIcon") == 0)
+ new_overlay_icon_cb (v0);
+ else if (g_strcmp0 (signal_name, "NewAttentionIcon") == 0)
+ new_attention_icon_cb (v0);
+ else if (g_strcmp0 (signal_name, "NewToolTip") == 0)
+ new_tooltip_cb (v0);
+ else if (g_strcmp0 (signal_name, "NewStatus") == 0)
+ new_status_cb (v0, parameters);
+ else if (g_strcmp0 (signal_name, "NewIconThemePath") == 0)
+ new_icon_theme_path_cb (v0, parameters);
+ else
+ g_assert_not_reached ();
+}
+
+static void
+get_all_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+ GVariant *properties;
+ GError *error;
+ GVariantIter *iter;
+ gchar *key;
+ GVariant *value;
+
+ error = NULL;
+ properties = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_variant_get (properties, "(a{sv})", &iter);
+ while (g_variant_iter_next (iter, "{sv}", &key, &value))
+ {
+ if (g_strcmp0 (key, "Category") == 0)
+ v0->category = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (key, "Id") == 0)
+ v0->id = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (key, "Title") == 0)
+ v0->title = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (key, "Status") == 0)
+ v0->status = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (key, "WindowId") == 0)
+ v0->window_id = g_variant_get_int32 (value);
+ else if (g_strcmp0 (key, "IconName") == 0)
+ v0->icon_name = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (key, "IconPixmap") == 0)
+ v0->icon_pixmap = icon_pixmap_new (value);
+ else if (g_strcmp0 (key, "OverlayIconName") == 0)
+ v0->overlay_icon_name = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (key, "OverlayIconPixmap") == 0)
+ v0->overlay_icon_pixmap = icon_pixmap_new (value);
+ else if (g_strcmp0 (key, "AttentionIconName") == 0)
+ v0->attention_icon_name = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (key, "AttentionIconPixmap") == 0)
+ v0->attention_icon_pixmap = icon_pixmap_new (value);
+ else if (g_strcmp0 (key, "AttentionMovieName") == 0)
+ v0->attention_movie_name = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (key, "ToolTip") == 0)
+ v0->tooltip = sn_tooltip_new (value);
+ else if (g_strcmp0 (key, "IconThemePath") == 0)
+ v0->icon_theme_path = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (key, "Menu") == 0)
+ v0->menu = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (key, "ItemIsMenu") == 0)
+ v0->item_is_menu = g_variant_get_boolean (value);
+ else
+ g_debug ("property '%s' not handled!", key);
+
+ g_variant_unref (value);
+ }
+
+ g_variant_iter_free (iter);
+ g_variant_unref (properties);
+
+ if (v0->id == NULL || v0->category == NULL || v0->status == NULL)
+ {
+ SnItem *item;
+ const gchar *bus_name;
+ const gchar *object_path;
+
+ item = SN_ITEM (v0);
+ bus_name = sn_item_get_bus_name (item);
+ object_path = sn_item_get_object_path (item);
+
+ g_warning ("Invalid Status Notifier Item (%s, %s)",
+ bus_name, object_path);
+
+ return;
+ }
+
+ if (v0->icon_theme_path != NULL)
+ {
+ GtkIconTheme *icon_theme;
+
+ icon_theme = gtk_icon_theme_get_default ();
+
+ gtk_icon_theme_append_search_path (icon_theme, v0->icon_theme_path);
+ }
+
+ g_signal_connect (v0->proxy, "g-properties-changed",
+ G_CALLBACK (g_properties_changed_cb), v0);
+
+ g_signal_connect (v0->proxy, "g-signal",
+ G_CALLBACK (g_signal_cb), v0);
+
+ update (v0);
+ sn_item_emit_ready (SN_ITEM (v0));
+}
+
+static void
+proxy_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+ SnItemV0Gen *proxy;
+ GError *error;
+
+ error = NULL;
+ proxy = sn_item_v0_gen_proxy_new_for_bus_finish (res, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ v0 = SN_ITEM_V0 (user_data);
+ v0->proxy = proxy;
+
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_dbus_connection_call (g_dbus_proxy_get_connection (G_DBUS_PROXY (proxy)),
+ sn_item_get_bus_name (SN_ITEM (v0)),
+ sn_item_get_object_path (SN_ITEM (v0)),
+ "org.freedesktop.DBus.Properties", "GetAll",
+ g_variant_new ("(s)", SN_ITEM_INTERFACE),
+ G_VARIANT_TYPE ("(a{sv})"),
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ v0->cancellable, get_all_cb, v0);
+}
+
+static void
+sn_item_v0_constructed (GObject *object)
+{
+ SnItemV0 *v0;
+ SnItem *item;
+
+ v0 = SN_ITEM_V0 (object);
+ item = SN_ITEM (v0);
+
+ G_OBJECT_CLASS (sn_item_v0_parent_class)->constructed (object);
+
+ v0->cancellable = g_cancellable_new ();
+ sn_item_v0_gen_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ sn_item_get_bus_name (item),
+ sn_item_get_object_path (item),
+ v0->cancellable,
+ proxy_ready_cb, v0);
+}
+
+static void
+sn_item_v0_dispose (GObject *object)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (object);
+
+ g_cancellable_cancel (v0->cancellable);
+ g_clear_object (&v0->cancellable);
+ g_clear_object (&v0->proxy);
+
+ if (v0->update_id != 0)
+ {
+ g_source_remove (v0->update_id);
+ v0->update_id = 0;
+ }
+
+ G_OBJECT_CLASS (sn_item_v0_parent_class)->dispose (object);
+}
+
+static void
+sn_item_v0_finalize (GObject *object)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (object);
+
+ g_clear_pointer (&v0->id, g_free);
+ g_clear_pointer (&v0->category, g_free);
+ g_clear_pointer (&v0->status, g_free);
+
+ g_clear_pointer (&v0->title, g_free);
+ g_clear_pointer (&v0->icon_name, g_free);
+ g_clear_pointer (&v0->icon_pixmap, icon_pixmap_free);
+ g_clear_pointer (&v0->overlay_icon_name, g_free);
+ g_clear_pointer (&v0->overlay_icon_pixmap, icon_pixmap_free);
+ g_clear_pointer (&v0->attention_icon_name, g_free);
+ g_clear_pointer (&v0->attention_icon_pixmap, icon_pixmap_free);
+ g_clear_pointer (&v0->attention_movie_name, g_free);
+ g_clear_pointer (&v0->tooltip, sn_tooltip_free);
+ g_clear_pointer (&v0->icon_theme_path, g_free);
+ g_clear_pointer (&v0->menu, g_free);
+
+ G_OBJECT_CLASS (sn_item_v0_parent_class)->finalize (object);
+}
+
+static const gchar *
+sn_item_v0_get_id (SnItem *item)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (item);
+
+ return v0->id;
+}
+
+static const gchar *
+sn_item_v0_get_category (SnItem *item)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (item);
+
+ return v0->category;
+}
+
+static const gchar *
+sn_item_v0_get_menu (SnItem *item)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (item);
+
+ return v0->menu;
+}
+
+static void
+context_menu_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ sn_item_v0_gen_call_context_menu_finish (v0->proxy, res, NULL);
+}
+
+static void
+sn_item_v0_context_menu (SnItem *item,
+ gint x,
+ gint y)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (item);
+
+ sn_item_v0_gen_call_context_menu (v0->proxy, x, y, NULL,
+ context_menu_cb, v0);
+}
+
+static void
+activate_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ sn_item_v0_gen_call_activate_finish (v0->proxy, res, NULL);
+}
+
+static void
+sn_item_v0_activate (SnItem *item,
+ gint x,
+ gint y)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (item);
+
+ sn_item_v0_gen_call_activate (v0->proxy, x, y, NULL,
+ activate_cb, v0);
+}
+
+static void
+secondary_activate_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ sn_item_v0_gen_call_secondary_activate_finish (v0->proxy, res, NULL);
+}
+
+static void
+sn_item_v0_secondary_activate (SnItem *item,
+ gint x,
+ gint y)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (item);
+
+ sn_item_v0_gen_call_secondary_activate (v0->proxy, x, y, NULL,
+ secondary_activate_cb, v0);
+}
+
+static void
+scroll_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (user_data);
+
+ sn_item_v0_gen_call_scroll_finish (v0->proxy, res, NULL);
+}
+
+static void
+sn_item_v0_scroll (SnItem *item,
+ gint delta,
+ SnItemOrientation orientation)
+{
+ SnItemV0 *v0;
+ const gchar *tmp;
+
+ v0 = SN_ITEM_V0 (item);
+
+ switch (orientation)
+ {
+ case SN_ITEM_ORIENTATION_VERTICAL:
+ tmp = "Vertical";
+ break;
+
+ case SN_ITEM_ORIENTATION_HORIZONTAL:
+ default:
+ tmp = "Horizontal";
+ break;
+ }
+
+ sn_item_v0_gen_call_scroll (v0->proxy, delta, tmp, NULL, scroll_cb, v0);
+}
+
+static void
+sn_item_v0_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ SnItemV0 *v0 = SN_ITEM_V0 (widget);
+
+ GTK_WIDGET_CLASS (sn_item_v0_parent_class)->size_allocate (widget, allocation);
+
+ /* FIXME: this leads to grow-only size, unless there's underallocation.
+ * not a problem in the panel, but one in the test app.
+ * NOTE: actually, it *can* shrink, as we request a little less for padding
+ * around, but that only allows shrinking by steps of 2 */
+ if (v0->icon_size <= 0)
+ {
+ gint prev_effective_icon_size = v0->effective_icon_size;
+
+ if (gtk_orientable_get_orientation (GTK_ORIENTABLE (v0)) == GTK_ORIENTATION_HORIZONTAL)
+ v0->effective_icon_size = allocation->height;
+ else
+ v0->effective_icon_size = allocation->width;
+
+ /* leave some padding around */
+ if (v0->effective_icon_size > 2)
+ v0->effective_icon_size -= 2;
+
+ if (v0->effective_icon_size != prev_effective_icon_size)
+ queue_update (SN_ITEM_V0 (widget));
+ }
+}
+
+static void
+sn_item_v0_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (object);
+
+ switch (property_id)
+ {
+ case PROP_ICON_SIZE:
+ g_value_set_uint (value, v0->icon_size);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sn_item_v0_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SnItemV0 *v0;
+
+ v0 = SN_ITEM_V0 (object);
+
+ switch (property_id)
+ {
+ case PROP_ICON_SIZE:
+ sn_item_v0_set_icon_size (v0, g_value_get_int (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+install_properties (GObjectClass *object_class)
+{
+ properties[PROP_ICON_SIZE] =
+ g_param_spec_int ("icon-size", "Icon size", "Icon size", 0, G_MAXINT, 16,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+sn_item_v0_class_init (SnItemV0Class *v0_class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ SnItemClass *item_class;
+
+ object_class = G_OBJECT_CLASS (v0_class);
+ widget_class = GTK_WIDGET_CLASS (v0_class);
+ item_class = SN_ITEM_CLASS (v0_class);
+
+ object_class->constructed = sn_item_v0_constructed;
+ object_class->dispose = sn_item_v0_dispose;
+ object_class->finalize = sn_item_v0_finalize;
+ object_class->get_property = sn_item_v0_get_property;
+ object_class->set_property = sn_item_v0_set_property;
+
+ item_class->get_id = sn_item_v0_get_id;
+ item_class->get_category = sn_item_v0_get_category;
+ item_class->get_menu = sn_item_v0_get_menu;
+
+ item_class->context_menu = sn_item_v0_context_menu;
+ item_class->activate = sn_item_v0_activate;
+ item_class->secondary_activate = sn_item_v0_secondary_activate;
+ item_class->scroll = sn_item_v0_scroll;
+
+ widget_class->size_allocate = sn_item_v0_size_allocate;
+
+ gtk_widget_class_set_css_name (widget_class, "sn-item");
+
+ install_properties (object_class);
+}
+
+static void
+sn_item_v0_init (SnItemV0 *v0)
+{
+ v0->icon_size = 16;
+ v0->effective_icon_size = 0;
+ v0->image = gtk_image_new ();
+ gtk_container_add (GTK_CONTAINER (v0), v0->image);
+ gtk_widget_show (v0->image);
+}
+
+SnItem *
+sn_item_v0_new (const gchar *bus_name,
+ const gchar *object_path)
+{
+ return g_object_new (SN_TYPE_ITEM_V0,
+ "bus-name", bus_name,
+ "object-path", object_path,
+ NULL);
+}
+
+gint
+sn_item_v0_get_icon_size (SnItemV0 *v0)
+{
+ return v0->icon_size;
+}
+
+void
+sn_item_v0_set_icon_size (SnItemV0 *v0,
+ gint size)
+{
+ if (v0->icon_size != size)
+ {
+ v0->icon_size = size;
+ g_object_notify_by_pspec (G_OBJECT (v0), properties[PROP_ICON_SIZE]);
+
+ if (v0->id != NULL)
+ queue_update (v0);
+ }
+}