summaryrefslogtreecommitdiff
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
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.
-rw-r--r--applets/notification_area/Makefile.am43
-rw-r--r--applets/notification_area/libstatus-notifier-watcher/Makefile.am45
-rw-r--r--applets/notification_area/libstatus-notifier-watcher/README2
-rw-r--r--applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.c395
-rw-r--r--applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.h33
-rw-r--r--applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.c64
-rw-r--r--applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.h33
-rw-r--r--applets/notification_area/libstatus-notifier-watcher/org.kde.StatusNotifierWatcher.xml42
-rw-r--r--applets/notification_area/main.c166
-rw-r--r--applets/notification_area/na-box.c366
-rw-r--r--applets/notification_area/na-box.h56
-rw-r--r--applets/notification_area/na-host.c105
-rw-r--r--applets/notification_area/na-host.h48
-rw-r--r--applets/notification_area/na-item.c91
-rw-r--r--applets/notification_area/na-item.h64
-rw-r--r--applets/notification_area/status-notifier/Makefile.am83
-rw-r--r--applets/notification_area/status-notifier/README2
-rw-r--r--applets/notification_area/status-notifier/com.canonical.dbusmenu.xml74
-rw-r--r--applets/notification_area/status-notifier/org.kde.StatusNotifierHost.xml8
-rw-r--r--applets/notification_area/status-notifier/org.kde.StatusNotifierItem.xml80
-rw-r--r--applets/notification_area/status-notifier/org.kde.StatusNotifierWatcher.xml42
-rw-r--r--applets/notification_area/status-notifier/sn-dbus-menu-item.c472
-rw-r--r--applets/notification_area/status-notifier/sn-dbus-menu-item.h64
-rw-r--r--applets/notification_area/status-notifier/sn-dbus-menu.c472
-rw-r--r--applets/notification_area/status-notifier/sn-dbus-menu.h33
-rw-r--r--applets/notification_area/status-notifier/sn-host-v0.c487
-rw-r--r--applets/notification_area/status-notifier/sn-host-v0.h33
-rw-r--r--applets/notification_area/status-notifier/sn-image-menu-item.c216
-rw-r--r--applets/notification_area/status-notifier/sn-image-menu-item.h41
-rw-r--r--applets/notification_area/status-notifier/sn-item-v0.c1355
-rw-r--r--applets/notification_area/status-notifier/sn-item-v0.h37
-rw-r--r--applets/notification_area/status-notifier/sn-item.c491
-rw-r--r--applets/notification_area/status-notifier/sn-item.h70
-rw-r--r--applets/notification_area/system-tray/Makefile.am42
-rw-r--r--applets/notification_area/system-tray/fixedtip.c (renamed from applets/notification_area/fixedtip.c)0
-rw-r--r--applets/notification_area/system-tray/fixedtip.h (renamed from applets/notification_area/fixedtip.h)0
-rw-r--r--applets/notification_area/system-tray/na-marshal.list (renamed from applets/notification_area/na-marshal.list)0
-rw-r--r--applets/notification_area/system-tray/na-tray-child.c (renamed from applets/notification_area/na-tray-child.c)179
-rw-r--r--applets/notification_area/system-tray/na-tray-child.h (renamed from applets/notification_area/na-tray-child.h)2
-rw-r--r--applets/notification_area/system-tray/na-tray-manager.c (renamed from applets/notification_area/na-tray-manager.c)0
-rw-r--r--applets/notification_area/system-tray/na-tray-manager.h (renamed from applets/notification_area/na-tray-manager.h)0
-rw-r--r--applets/notification_area/system-tray/na-tray.c (renamed from applets/notification_area/na-tray.c)372
-rw-r--r--applets/notification_area/system-tray/na-tray.h (renamed from applets/notification_area/na-tray.h)17
-rw-r--r--applets/notification_area/testtray.c88
-rw-r--r--configure.ac6
45 files changed, 5952 insertions, 367 deletions
diff --git a/applets/notification_area/Makefile.am b/applets/notification_area/Makefile.am
index cbd9d0b6..4e7272e5 100644
--- a/applets/notification_area/Makefile.am
+++ b/applets/notification_area/Makefile.am
@@ -1,3 +1,8 @@
+SUBDIRS = \
+ libstatus-notifier-watcher \
+ status-notifier \
+ system-tray
+
noinst_LTLIBRARIES = libtray.la
noinst_PROGRAMS = testtray
@@ -10,28 +15,29 @@ AM_CPPFLAGS = \
-DMATELOCALEDIR=\""$(datadir)/locale"\" \
-DG_LOG_DOMAIN=\""notification-area-applet"\" \
-DNOTIFICATION_AREA_MENU_UI_DIR=\""$(uidir)"\" \
+ -DPROVIDE_WATCHER_SERVICE=1 \
$(DISABLE_DEPRECATED_CFLAGS)
AM_CFLAGS = $(WARN_CFLAGS)
-libtray_la_SOURCES = \
- fixedtip.h \
- fixedtip.c \
- na-marshal.c \
- na-marshal.h \
- na-tray.c \
- na-tray.h \
- na-tray-child.c \
- na-tray-child.h \
- na-tray-manager.c \
- na-tray-manager.h
+libtray_la_SOURCES = \
+ na-box.c \
+ na-box.h \
+ na-host.c \
+ na-host.h \
+ na-item.c \
+ na-item.h
+
+libtray_la_LIBADD = \
+ libstatus-notifier-watcher/libstatus-notifier-watcher.la \
+ status-notifier/libstatus-notifier.la \
+ system-tray/libsystem-tray.la
NOTIFICATION_AREA_SOURCES = main.c main.h
NOTIFICATION_AREA_LDADD = \
../../libmate-panel-applet/libmate-panel-applet-4.la \
libtray.la \
- $(X_LIBS) \
$(NOTIFICATION_AREA_LIBS) \
$(LIBMATE_PANEL_APPLET_LIBS)
@@ -39,7 +45,6 @@ NOTIFICATION_AREA_LDADD = \
testtray_SOURCES = testtray.c
testtray_LDADD = \
libtray.la \
- $(X_LIBS) \
$(NOTIFICATION_AREA_LIBS)
if NOTIFICATION_AREA_INPROCESS
@@ -62,15 +67,6 @@ notification_area_applet_LDADD = $(NOTIFICATION_AREA_LDADD)
notification_area_applet_CFLAGS = $(AM_CFLAGS)
endif
-na-marshal.h: na-marshal.list $(GLIB_GENMARSHAL)
- $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --header --prefix=_na_marshal > $@
-
-na-marshal.c: na-marshal.list $(GLIB_GENMARSHAL)
- $(AM_V_GEN)echo "#include \"na-marshal.h\"" > $@ && \
- $(GLIB_GENMARSHAL) $< --body --prefix=_na_marshal >> $@
-
-BUILT_SOURCES = na-marshal.c na-marshal.h
-
appletdir = $(datadir)/mate-panel/applets
applet_in_files = org.mate.panel.NotificationAreaApplet.mate-panel-applet.in
applet_DATA = $(applet_in_files:.mate-panel-applet.in=.mate-panel-applet)
@@ -101,8 +97,7 @@ ui_DATA = notification-area-menu.xml
EXTRA_DIST = \
org.mate.panel.NotificationAreaApplet.mate-panel-applet.in.in \
$(ui_DATA) \
- $(service_in_files) \
- na-marshal.list
+ $(service_in_files)
CLEANFILES = \
$(applet_DATA) \
diff --git a/applets/notification_area/libstatus-notifier-watcher/Makefile.am b/applets/notification_area/libstatus-notifier-watcher/Makefile.am
new file mode 100644
index 00000000..eb06ce83
--- /dev/null
+++ b/applets/notification_area/libstatus-notifier-watcher/Makefile.am
@@ -0,0 +1,45 @@
+NULL =
+
+noinst_LTLIBRARIES = \
+ libstatus-notifier-watcher.la \
+ $(NULL)
+
+AM_CPPFLAGS = \
+ $(NOTIFICATION_AREA_CFLAGS) \
+ -DG_LOG_DOMAIN=\"status-notifier-watcher\" \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+AM_CFLAGS = $(WARN_CFLAGS)
+
+libstatus_notifier_watcher_la_SOURCES = \
+ gf-sn-watcher-v0.c \
+ gf-sn-watcher-v0.h \
+ gf-status-notifier-watcher.c \
+ gf-status-notifier-watcher.h \
+ $(BUILT_SOURCES) \
+ $(NULL)
+
+libstatus_notifier_watcher_la_LIBADD = \
+ $(NOTIFICATION_AREA_LIBS) \
+ $(NULL)
+
+gf-sn-watcher-v0-gen.h:
+gf-sn-watcher-v0-gen.c: org.kde.StatusNotifierWatcher.xml
+ $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Gf \
+ --generate-c-code gf-sn-watcher-v0-gen \
+ $(srcdir)/org.kde.StatusNotifierWatcher.xml
+
+BUILT_SOURCES = \
+ gf-sn-watcher-v0-gen.c \
+ gf-sn-watcher-v0-gen.h \
+ $(NULL)
+
+EXTRA_DIST = \
+ org.kde.StatusNotifierWatcher.xml \
+ $(NULL)
+
+CLEANFILES = \
+ $(BUILT_SOURCES) \
+ $(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/applets/notification_area/libstatus-notifier-watcher/README b/applets/notification_area/libstatus-notifier-watcher/README
new file mode 100644
index 00000000..706b2a82
--- /dev/null
+++ b/applets/notification_area/libstatus-notifier-watcher/README
@@ -0,0 +1,2 @@
+Borrowed from gnome-flashback:
+https://git.gnome.org/browse/gnome-flashback/tree/gnome-flashback/libstatus-notifier-watcher
diff --git a/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.c b/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.c
new file mode 100644
index 00000000..8e82c10f
--- /dev/null
+++ b/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.c
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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 3 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 "gf-sn-watcher-v0.h"
+
+struct _GfSnWatcherV0
+{
+ GfSnWatcherV0GenSkeleton parent;
+
+ guint bus_name_id;
+
+ GSList *hosts;
+ GSList *items;
+};
+
+typedef enum
+{
+ GF_WATCH_TYPE_HOST,
+ GF_WATCH_TYPE_ITEM
+} GfWatchType;
+
+typedef struct
+{
+ GfSnWatcherV0 *v0;
+ GfWatchType type;
+
+ gchar *service;
+ gchar *bus_name;
+ gchar *object_path;
+ guint watch_id;
+} GfWatch;
+
+static void gf_sn_watcher_v0_gen_init (GfSnWatcherV0GenIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GfSnWatcherV0, gf_sn_watcher_v0, GF_TYPE_SN_WATCHER_V0_GEN_SKELETON,
+ G_IMPLEMENT_INTERFACE (GF_TYPE_SN_WATCHER_V0_GEN, gf_sn_watcher_v0_gen_init))
+
+static void
+update_registered_items (GfSnWatcherV0 *v0)
+{
+ GVariantBuilder builder;
+ GSList *l;
+ GVariant *variant;
+ const gchar **items;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+
+ for (l = v0->items; l != NULL; l = g_slist_next (l))
+ {
+ GfWatch *watch;
+ gchar *item;
+
+ watch = (GfWatch *) l->data;
+
+ item = g_strdup_printf ("%s%s", watch->bus_name, watch->object_path);
+ g_variant_builder_add (&builder, "s", item);
+ g_free (item);
+ }
+
+ variant = g_variant_builder_end (&builder);
+ items = g_variant_get_strv (variant, NULL);
+
+ gf_sn_watcher_v0_gen_set_registered_items (GF_SN_WATCHER_V0_GEN (v0), items);
+ g_variant_unref (variant);
+}
+
+static void
+gf_watch_free (gpointer data)
+{
+ GfWatch *watch;
+
+ watch = (GfWatch *) data;
+
+ if (watch->watch_id > 0)
+ g_bus_unwatch_name (watch->watch_id);
+
+ g_free (watch->service);
+ g_free (watch->bus_name);
+ g_free (watch->object_path);
+
+ g_free (watch);
+}
+
+static void
+name_vanished_cb (GDBusConnection *connection,
+ const char *name,
+ gpointer user_data)
+{
+ GfWatch *watch;
+ GfSnWatcherV0 *v0;
+ GfSnWatcherV0Gen *gen;
+
+ watch = (GfWatch *) user_data;
+ v0 = watch->v0;
+ gen = GF_SN_WATCHER_V0_GEN (v0);
+
+ if (watch->type == GF_WATCH_TYPE_HOST)
+ {
+ v0->hosts = g_slist_remove (v0->hosts, watch);
+
+ if (v0->hosts == NULL)
+ {
+ gf_sn_watcher_v0_gen_set_is_host_registered (gen, FALSE);
+ gf_sn_watcher_v0_gen_emit_host_registered (gen);
+ }
+ }
+ else if (watch->type == GF_WATCH_TYPE_ITEM)
+ {
+ gchar *tmp;
+
+ v0->items = g_slist_remove (v0->items, watch);
+
+ update_registered_items (v0);
+
+ tmp = g_strdup_printf ("%s%s", watch->bus_name, watch->object_path);
+ gf_sn_watcher_v0_gen_emit_item_unregistered (gen, tmp);
+ g_free (tmp);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ gf_watch_free (watch);
+}
+
+static GfWatch *
+gf_watch_new (GfSnWatcherV0 *v0,
+ GfWatchType type,
+ const gchar *service,
+ const gchar *bus_name,
+ const gchar *object_path)
+{
+ GfWatch *watch;
+
+ watch = g_new0 (GfWatch, 1);
+
+ watch->v0 = v0;
+ watch->type = type;
+
+ watch->service = g_strdup (service);
+ watch->bus_name = g_strdup (bus_name);
+ watch->object_path = g_strdup (object_path);
+ watch->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, bus_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE, NULL,
+ name_vanished_cb, watch, NULL);
+
+ return watch;
+}
+
+static GfWatch *
+gf_watch_find (GSList *list,
+ const gchar *bus_name,
+ const gchar *object_path)
+{
+ GSList *l;
+
+ for (l = list; l != NULL; l = g_slist_next (l))
+ {
+ GfWatch *watch;
+
+ watch = (GfWatch *) l->data;
+
+ if (g_strcmp0 (watch->bus_name, bus_name) == 0 &&
+ g_strcmp0 (watch->object_path, object_path) == 0)
+ {
+ return watch;
+ }
+ }
+
+ return NULL;
+}
+
+static gboolean
+gf_sn_watcher_v0_handle_register_host (GfSnWatcherV0Gen *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *service)
+{
+ GfSnWatcherV0 *v0;
+ const gchar *bus_name;
+ const gchar *object_path;
+ GfWatch *watch;
+
+ v0 = GF_SN_WATCHER_V0 (object);
+
+ if (*service == '/')
+ {
+ bus_name = g_dbus_method_invocation_get_sender (invocation);
+ object_path = service;
+ }
+ else
+ {
+ bus_name = service;
+ object_path = "/StatusNotifierHost";
+ }
+
+ if (g_dbus_is_name (bus_name) == FALSE)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "D-Bus bus name '%s' is not valid",
+ bus_name);
+
+ return TRUE;
+ }
+
+ watch = gf_watch_find (v0->hosts, bus_name, object_path);
+
+ if (watch != NULL)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Status Notifier Host with bus name '%s' and object path '%s' is already registered",
+ bus_name, object_path);
+
+ return TRUE;
+ }
+
+ watch = gf_watch_new (v0, GF_WATCH_TYPE_HOST, service, bus_name, object_path);
+ v0->hosts = g_slist_prepend (v0->hosts, watch);
+
+ if (!gf_sn_watcher_v0_gen_get_is_host_registered (object))
+ {
+ gf_sn_watcher_v0_gen_set_is_host_registered (object, TRUE);
+ gf_sn_watcher_v0_gen_emit_host_registered (object);
+ }
+
+ gf_sn_watcher_v0_gen_complete_register_host (object, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+gf_sn_watcher_v0_handle_register_item (GfSnWatcherV0Gen *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *service)
+{
+ GfSnWatcherV0 *v0;
+ const gchar *bus_name;
+ const gchar *object_path;
+ GfWatch *watch;
+ gchar *tmp;
+
+ v0 = GF_SN_WATCHER_V0 (object);
+
+ if (*service == '/')
+ {
+ bus_name = g_dbus_method_invocation_get_sender (invocation);
+ object_path = service;
+ }
+ else
+ {
+ bus_name = service;
+ object_path = "/StatusNotifierItem";
+ }
+
+ if (g_dbus_is_name (bus_name) == FALSE)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "D-Bus bus name '%s' is not valid",
+ bus_name);
+
+ return TRUE;
+ }
+
+ watch = gf_watch_find (v0->items, bus_name, object_path);
+
+ if (watch != NULL)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Status Notifier Item with bus name '%s' and object path '%s' is already registered",
+ bus_name, object_path);
+
+ return TRUE;
+ }
+
+ watch = gf_watch_new (v0, GF_WATCH_TYPE_ITEM, service, bus_name, object_path);
+ v0->items = g_slist_prepend (v0->items, watch);
+
+ update_registered_items (v0);
+
+ tmp = g_strdup_printf ("%s%s", bus_name, object_path);
+ gf_sn_watcher_v0_gen_emit_item_registered (object, tmp);
+ g_free (tmp);
+
+ gf_sn_watcher_v0_gen_complete_register_item (object, invocation);
+
+ return TRUE;
+}
+
+static void
+gf_sn_watcher_v0_gen_init (GfSnWatcherV0GenIface *iface)
+{
+ iface->handle_register_host = gf_sn_watcher_v0_handle_register_host;
+ iface->handle_register_item = gf_sn_watcher_v0_handle_register_item;
+}
+
+static void
+bus_acquired_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GfSnWatcherV0 *v0;
+ GDBusInterfaceSkeleton *skeleton;
+ GError *error;
+
+ v0 = GF_SN_WATCHER_V0 (user_data);
+ skeleton = G_DBUS_INTERFACE_SKELETON (v0);
+
+ error = NULL;
+ g_dbus_interface_skeleton_export (skeleton, connection,
+ "/StatusNotifierWatcher", &error);
+
+ if (error != NULL)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+static void
+gf_sn_watcher_v0_dispose (GObject *object)
+{
+ GfSnWatcherV0 *v0;
+
+ v0 = GF_SN_WATCHER_V0 (object);
+
+ if (v0->bus_name_id > 0)
+ {
+ g_bus_unown_name (v0->bus_name_id);
+ v0->bus_name_id = 0;
+ }
+
+ if (v0->hosts != NULL)
+ {
+ g_slist_free_full (v0->hosts, gf_watch_free);
+ v0->hosts = NULL;
+ }
+
+ if (v0->items != NULL)
+ {
+ g_slist_free_full (v0->items, gf_watch_free);
+ v0->items = NULL;
+ }
+
+ G_OBJECT_CLASS (gf_sn_watcher_v0_parent_class)->dispose (object);
+}
+
+static void
+gf_sn_watcher_v0_class_init (GfSnWatcherV0Class *v0_class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (v0_class);
+
+ object_class->dispose = gf_sn_watcher_v0_dispose;
+}
+
+static void
+gf_sn_watcher_v0_init (GfSnWatcherV0 *v0)
+{
+ GBusNameOwnerFlags flags;
+
+ flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+ G_BUS_NAME_OWNER_FLAGS_REPLACE;
+
+ v0->bus_name_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ "org.kde.StatusNotifierWatcher", flags,
+ bus_acquired_cb, NULL, NULL, v0, NULL);
+}
+
+GfSnWatcherV0 *
+gf_sn_watcher_v0_new (void)
+{
+ return g_object_new (GF_TYPE_SN_WATCHER_V0, NULL);
+}
diff --git a/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.h b/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.h
new file mode 100644
index 00000000..73bf6d04
--- /dev/null
+++ b/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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 3 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/>.
+ */
+
+#ifndef GF_SN_WATCHER_V0_H
+#define GF_SN_WATCHER_V0_H
+
+#include "gf-sn-watcher-v0-gen.h"
+
+G_BEGIN_DECLS
+
+#define GF_TYPE_SN_WATCHER_V0 gf_sn_watcher_v0_get_type ()
+G_DECLARE_FINAL_TYPE (GfSnWatcherV0, gf_sn_watcher_v0,
+ GF, SN_WATCHER_V0, GfSnWatcherV0GenSkeleton)
+
+GfSnWatcherV0 *gf_sn_watcher_v0_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.c b/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.c
new file mode 100644
index 00000000..e8b2c654
--- /dev/null
+++ b/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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 3 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 "gf-sn-watcher-v0.h"
+#include "gf-status-notifier-watcher.h"
+
+struct _GfStatusNotifierWatcher
+{
+ GObject parent;
+
+ GfSnWatcherV0 *v0;
+};
+
+G_DEFINE_TYPE (GfStatusNotifierWatcher, gf_status_notifier_watcher, G_TYPE_OBJECT)
+
+static void
+gf_status_notifier_watcher_dispose (GObject *object)
+{
+ GfStatusNotifierWatcher *watcher;
+
+ watcher = GF_STATUS_NOTIFIER_WATCHER (object);
+
+ g_clear_object (&watcher->v0);
+
+ G_OBJECT_CLASS (gf_status_notifier_watcher_parent_class)->dispose (object);
+}
+
+static void
+gf_status_notifier_watcher_class_init (GfStatusNotifierWatcherClass *watcher_class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (watcher_class);
+
+ object_class->dispose = gf_status_notifier_watcher_dispose;
+}
+
+static void
+gf_status_notifier_watcher_init (GfStatusNotifierWatcher *watcher)
+{
+ watcher->v0 = gf_sn_watcher_v0_new ();
+}
+
+GfStatusNotifierWatcher *
+gf_status_notifier_watcher_new (void)
+{
+ return g_object_new (GF_TYPE_STATUS_NOTIFIER_WATCHER, NULL);
+}
diff --git a/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.h b/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.h
new file mode 100644
index 00000000..fd871a42
--- /dev/null
+++ b/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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 3 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/>.
+ */
+
+#ifndef GF_STATUS_NOTIFIER_WATCHER_H
+#define GF_STATUS_NOTIFIER_WATCHER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GF_TYPE_STATUS_NOTIFIER_WATCHER gf_status_notifier_watcher_get_type ()
+G_DECLARE_FINAL_TYPE (GfStatusNotifierWatcher, gf_status_notifier_watcher,
+ GF, STATUS_NOTIFIER_WATCHER, GObject)
+
+GfStatusNotifierWatcher *gf_status_notifier_watcher_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/applets/notification_area/libstatus-notifier-watcher/org.kde.StatusNotifierWatcher.xml b/applets/notification_area/libstatus-notifier-watcher/org.kde.StatusNotifierWatcher.xml
new file mode 100644
index 00000000..30d3f774
--- /dev/null
+++ b/applets/notification_area/libstatus-notifier-watcher/org.kde.StatusNotifierWatcher.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.StatusNotifierWatcher">
+ <annotation name="org.gtk.GDBus.C.Name" value="SnWatcherV0Gen" />
+
+ <method name="RegisterStatusNotifierItem">
+ <annotation name="org.gtk.GDBus.C.Name" value="RegisterItem" />
+ <arg type="s" direction="in" name="service" />
+ </method>
+
+ <method name="RegisterStatusNotifierHost">
+ <annotation name="org.gtk.GDBus.C.Name" value="RegisterHost" />
+ <arg type="s" direction="in" name="service" />
+ </method>
+
+ <property name="RegisteredStatusNotifierItems" type="as" access="read">
+ <annotation name="org.gtk.GDBus.C.Name" value="RegisteredItems" />
+ </property>
+
+ <property name="IsStatusNotifierHostRegistered" type="b" access="read">
+ <annotation name="org.gtk.GDBus.C.Name" value="IsHostRegistered" />
+ </property>
+
+ <property name="ProtocolVersion" type="i" access="read" />
+
+ <signal name="StatusNotifierItemRegistered">
+ <annotation name="org.gtk.GDBus.C.Name" value="ItemRegistered" />
+ <arg type="s" direction="out" name="service" />
+ </signal>
+
+ <signal name="StatusNotifierItemUnregistered">
+ <annotation name="org.gtk.GDBus.C.Name" value="ItemUnregistered" />
+ <arg type="s" direction="out" name="service" />
+ </signal>
+
+ <signal name="StatusNotifierHostRegistered">
+ <annotation name="org.gtk.GDBus.C.Name" value="HostRegistered" />
+ </signal>
+ </interface>
+</node>
diff --git a/applets/notification_area/main.c b/applets/notification_area/main.c
index 54080a20..f4de7d01 100644
--- a/applets/notification_area/main.c
+++ b/applets/notification_area/main.c
@@ -30,26 +30,54 @@
#include <gtk/gtk.h>
#include "main.h"
-#include "na-tray-manager.h"
-#include "na-tray.h"
-#include "fixedtip.h"
+#include "na-box.h"
+
+#ifdef PROVIDE_WATCHER_SERVICE
+# include "libstatus-notifier-watcher/gf-status-notifier-watcher.h"
+#endif
#define NOTIFICATION_AREA_ICON "mate-panel-notification-area"
struct _NaTrayAppletPrivate
{
- NaTray *tray;
+ GtkWidget *box;
+
+#ifdef PROVIDE_WATCHER_SERVICE
+ GfStatusNotifierWatcher *sn_watcher;
+#endif
};
G_DEFINE_TYPE (NaTrayApplet, na_tray_applet, PANEL_TYPE_APPLET)
static void (*parent_class_realize) (GtkWidget *widget);
-static void (*parent_class_unrealize) (GtkWidget *widget);
static void (*parent_class_style_updated) (GtkWidget *widget);
static void (*parent_class_change_background)(MatePanelApplet* panel_applet, MatePanelAppletBackgroundType type, GdkRGBA* color, cairo_pattern_t* pattern);
static void (*parent_class_change_orient)(MatePanelApplet *panel_applet, MatePanelAppletOrient orient);
+#ifdef PROVIDE_WATCHER_SERVICE
+/* Quite dirty way of providing the org.kde.StatusNotifierWatcher service
+ * ourselves, in case the session doesn't already */
+
+static GfStatusNotifierWatcher *sn_watcher_service = NULL;
+
+static GfStatusNotifierWatcher *
+sn_watcher_service_ref (void)
+{
+ if (sn_watcher_service != NULL)
+ g_object_ref (sn_watcher_service);
+ else
+ {
+ sn_watcher_service = gf_status_notifier_watcher_new ();
+ g_object_add_weak_pointer ((GObject *) sn_watcher_service,
+ (gpointer *) &sn_watcher_service);
+ }
+
+ return sn_watcher_service;
+}
+#endif
+
+
static GtkOrientation
get_gtk_orientation_from_applet_orient (MatePanelAppletOrient orient)
{
@@ -116,6 +144,8 @@ static void about_cb(GtkAction* action, NaTrayApplet* applet)
"Havoc Pennington <[email protected]>",
"Anders Carlsson <[email protected]>",
"Vincent Untz <[email protected]>",
+ "Alberts Muktupāvels",
+ "Colomban Wendling <[email protected]>",
NULL
};
@@ -128,7 +158,7 @@ static void about_cb(GtkAction* action, NaTrayApplet* applet)
"Copyright \xc2\xa9 2002 Red Hat, Inc.\n"
"Copyright \xc2\xa9 2003-2006 Vincent Untz\n"
"Copyright \xc2\xa9 2011 Perberos\n"
- "Copyright \xc2\xa9 2012-2016 MATE developers";
+ "Copyright \xc2\xa9 2012-2017 MATE developers";
gtk_show_about_dialog(NULL,
"program-name", _("Notification Area"),
@@ -156,21 +186,10 @@ static void
na_tray_applet_realize (GtkWidget *widget)
{
NaTrayApplet *applet = NA_TRAY_APPLET (widget);
- MatePanelAppletOrient orient;
if (parent_class_realize)
parent_class_realize (widget);
- g_assert (applet->priv->tray == NULL);
-
- orient = mate_panel_applet_get_orient (MATE_PANEL_APPLET (widget));
-
- applet->priv->tray = na_tray_new_for_screen (gtk_widget_get_screen (widget),
- get_gtk_orientation_from_applet_orient (orient));
-
- gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (applet->priv->tray));
- gtk_widget_show (GTK_WIDGET (applet->priv->tray));
-
GtkActionGroup* action_group;
gchar* ui_path;
action_group = gtk_action_group_new("NA Applet Menu Actions");
@@ -183,60 +202,36 @@ na_tray_applet_realize (GtkWidget *widget)
}
static void
-na_tray_applet_unrealize (GtkWidget *widget)
+na_tray_applet_dispose (GObject *object)
{
- NaTrayApplet *applet = NA_TRAY_APPLET (widget);
-
- g_assert (applet->priv->tray != NULL);
-
- gtk_widget_destroy (GTK_WIDGET (applet->priv->tray));
- applet->priv->tray = NULL;
+#ifdef PROVIDE_WATCHER_SERVICE
+ g_clear_object (&NA_TRAY_APPLET (object)->priv->sn_watcher);
+#endif
- if (parent_class_unrealize)
- parent_class_unrealize (widget);
+ G_OBJECT_CLASS (na_tray_applet_parent_class)->dispose (object);
}
static void
na_tray_applet_style_updated (GtkWidget *widget)
{
NaTrayApplet *applet = NA_TRAY_APPLET (widget);
- GtkStyleContext *context;
- GdkRGBA fg;
- GdkRGBA error;
- GdkRGBA warning;
- GdkRGBA success;
gint padding;
gint icon_size;
if (parent_class_style_updated)
parent_class_style_updated (widget);
- if (!applet->priv->tray)
+ if (!applet->priv->box)
return;
- context = gtk_widget_get_style_context (widget);
-
- gtk_style_context_save (context);
- gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
-
- gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &fg);
-
- if (!gtk_style_context_lookup_color (context, "error_color", &error))
- error = fg;
- if (!gtk_style_context_lookup_color (context, "warning_color", &warning))
- warning = fg;
- if (!gtk_style_context_lookup_color (context, "success_color", &success))
- success = fg;
-
- gtk_style_context_restore (context);
-
- na_tray_set_colors (applet->priv->tray, &fg, &error, &warning, &success);
-
- gtk_widget_style_get (widget, "icon-padding", &padding, NULL);
- na_tray_set_padding (applet->priv->tray, padding);
-
- gtk_widget_style_get (widget, "icon-size", &icon_size, NULL);
- na_tray_set_icon_size (applet->priv->tray, icon_size);
+ gtk_widget_style_get (widget,
+ "icon-padding", &padding,
+ "icon-size", &icon_size,
+ NULL);
+ g_object_set (applet->priv->box,
+ "icon-padding", padding,
+ "icon-size", icon_size,
+ NULL);
}
static void
@@ -248,10 +243,8 @@ na_tray_applet_change_background(MatePanelApplet* panel_applet, MatePanelAppletB
parent_class_change_background (panel_applet, type, color, pattern);
}
- if (!applet->priv->tray)
- return;
-
- na_tray_force_redraw (applet->priv->tray);
+ if (applet->priv->box)
+ na_box_force_redraw (NA_BOX (applet->priv->box));
}
static void
@@ -263,11 +256,39 @@ na_tray_applet_change_orient (MatePanelApplet *panel_applet,
if (parent_class_change_orient)
parent_class_change_orient (panel_applet, orient);
- if (!applet->priv->tray)
+ if (!applet->priv->box)
return;
- na_tray_set_orientation (applet->priv->tray,
- get_gtk_orientation_from_applet_orient (orient));
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (applet->priv->box),
+ get_gtk_orientation_from_applet_orient (orient));
+}
+
+static gboolean
+na_tray_applet_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ /* Prevent the panel from poping up the applet's popup on the the items,
+ * which may also popup a menu which then conflicts.
+ * This doesn't prevent the menu from poping up on the applet handle. */
+ if (event->button == 3)
+ return TRUE;
+
+ return GTK_WIDGET_CLASS (na_tray_applet_parent_class)->button_press_event (widget, event);
+}
+
+static gboolean
+na_tray_applet_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ NaTrayApplet *applet = NA_TRAY_APPLET (widget);
+
+ /* We let the box handle the focus movement because we behave more like a
+ * container than a single applet. But if focus didn't move, we let the
+ * applet do its thing. */
+ if (gtk_widget_child_focus (applet->priv->box, direction))
+ return TRUE;
+
+ return GTK_WIDGET_CLASS (na_tray_applet_parent_class)->focus (widget, direction);
}
#if !GTK_CHECK_VERSION (3, 20, 0)
@@ -293,19 +314,23 @@ force_no_focus_padding (GtkWidget *widget)
static void
na_tray_applet_class_init (NaTrayAppletClass *class)
{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
MatePanelAppletClass *applet_class = MATE_PANEL_APPLET_CLASS (class);
+ object_class->dispose = na_tray_applet_dispose;
+
parent_class_realize = widget_class->realize;
widget_class->realize = na_tray_applet_realize;
- parent_class_unrealize = widget_class->unrealize;
- widget_class->unrealize = na_tray_applet_unrealize;
parent_class_style_updated = widget_class->style_updated;
widget_class->style_updated = na_tray_applet_style_updated;
parent_class_change_background = applet_class->change_background;
applet_class->change_background = na_tray_applet_change_background;
+ widget_class->button_press_event = na_tray_applet_button_press_event;
+ widget_class->focus = na_tray_applet_focus;
+
parent_class_change_orient = applet_class->change_orient;
applet_class->change_orient = na_tray_applet_change_orient;
@@ -335,14 +360,21 @@ na_tray_applet_class_init (NaTrayAppletClass *class)
static void
na_tray_applet_init (NaTrayApplet *applet)
{
+ MatePanelAppletOrient orient;
AtkObject *atko;
applet->priv = G_TYPE_INSTANCE_GET_PRIVATE (applet, NA_TYPE_TRAY_APPLET,
NaTrayAppletPrivate);
- /* Defer creating NaTray until applet is added to panel so
- * gtk_widget_get_screen returns correct information */
- applet->priv->tray = NULL;
+#ifdef PROVIDE_WATCHER_SERVICE
+ applet->priv->sn_watcher = sn_watcher_service_ref ();
+#endif
+
+ orient = mate_panel_applet_get_orient (MATE_PANEL_APPLET (applet));
+ applet->priv->box = na_box_new (get_gtk_orientation_from_applet_orient (orient));
+
+ gtk_container_add (GTK_CONTAINER (applet), GTK_WIDGET (applet->priv->box));
+ gtk_widget_show (GTK_WIDGET (applet->priv->box));
atko = gtk_widget_get_accessible (GTK_WIDGET (applet));
atk_object_set_name (atko, _("Panel Notification Area"));
diff --git a/applets/notification_area/na-box.c b/applets/notification_area/na-box.c
new file mode 100644
index 00000000..25933eea
--- /dev/null
+++ b/applets/notification_area/na-box.c
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2002 Red Hat, Inc.
+ * Copyright (C) 2003-2006 Vincent Untz
+ * Copyright (C) 2007 Christian Persch
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/* Well, actuall'y is the Tray itself, the container for the items. But
+ * NaTray is already taken for the XEMBED part, so for now it's called NaBox,
+ * don't make a big deal out of it. */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#include "na-box.h"
+
+#include "system-tray/na-tray.h"
+#include "status-notifier/sn-host-v0.h"
+
+#define ICON_SPACING 1
+#define MIN_BOX_SIZE 3
+
+struct _NaBox
+{
+ GtkBox parent;
+
+ gint icon_padding;
+ gint icon_size;
+
+ GSList *hosts;
+ GSList *items;
+};
+
+enum
+{
+ PROP_0,
+ PROP_ICON_PADDING,
+ PROP_ICON_SIZE
+};
+
+G_DEFINE_TYPE (NaBox, na_box, GTK_TYPE_BOX)
+
+static gint
+compare_items (gconstpointer a,
+ gconstpointer b)
+{
+ NaItem *item1;
+ NaItem *item2;
+ NaItemCategory c1;
+ NaItemCategory c2;
+ const gchar *id1;
+ const gchar *id2;
+
+ item1 = (NaItem *) a;
+ item2 = (NaItem *) b;
+
+ c1 = na_item_get_category (item1);
+ c2 = na_item_get_category (item2);
+
+ if (c1 < c2)
+ return -1;
+ else if (c1 > c2)
+ return 1;
+
+ id1 = na_item_get_id (item1);
+ id2 = na_item_get_id (item2);
+
+ return g_strcmp0 (id1, id2);
+}
+
+static void
+reorder_items (GtkWidget *widget,
+ gpointer user_data)
+{
+ NaBox *nb;
+ gint position;
+
+ nb = NA_BOX (user_data);
+
+ position = g_slist_index (nb->items, widget);
+ gtk_box_reorder_child (GTK_BOX (nb), widget, position);
+}
+
+static void
+item_added_cb (NaHost *host,
+ NaItem *item,
+ NaBox *self)
+{
+ g_return_if_fail (NA_IS_HOST (host));
+ g_return_if_fail (NA_IS_ITEM (item));
+ g_return_if_fail (NA_IS_BOX (self));
+
+ self->items = g_slist_prepend (self->items, item);
+ gtk_box_pack_start (GTK_BOX (self), GTK_WIDGET (item), FALSE, FALSE, 0);
+
+ self->items = g_slist_sort (self->items, compare_items);
+ gtk_container_foreach (GTK_CONTAINER (self), reorder_items, self);
+
+ g_object_bind_property (self, "orientation",
+ item, "orientation",
+ G_BINDING_DEFAULT);
+}
+
+static void
+item_removed_cb (NaHost *host,
+ NaItem *item,
+ NaBox *self)
+{
+ g_return_if_fail (NA_IS_HOST (host));
+ g_return_if_fail (NA_IS_ITEM (item));
+ g_return_if_fail (NA_IS_BOX (self));
+
+ gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (item));
+ self->items = g_slist_remove (self->items, item);
+}
+
+static void
+update_size_and_orientation (NaBox *self,
+ GtkOrientation orientation)
+{
+ /* FIXME: do we really need that? comes from NaTray */
+ /* FIXME: if we do, do that in overridden preferred size handlers */
+
+ /* note, you want this larger if the frame has non-NONE relief by default. */
+ switch (orientation)
+ {
+ case GTK_ORIENTATION_VERTICAL:
+ /* Give box a min size so the frame doesn't look dumb */
+ gtk_widget_set_size_request (GTK_WIDGET (self), MIN_BOX_SIZE, -1);
+ break;
+ case GTK_ORIENTATION_HORIZONTAL:
+ gtk_widget_set_size_request (GTK_WIDGET (self), -1, MIN_BOX_SIZE);
+ break;
+ }
+}
+
+static void
+orientation_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ update_size_and_orientation (NA_BOX (object),
+ gtk_orientable_get_orientation (GTK_ORIENTABLE (object)));
+}
+
+static void
+na_box_init (NaBox *self)
+{
+ GtkOrientation orientation;
+
+ self->icon_padding = 0;
+ self->icon_size = 0;
+
+ self->hosts = NULL;
+ self->items = NULL;
+
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self));
+ update_size_and_orientation (self, orientation);
+
+ g_signal_connect (self, "notify::orientation", G_CALLBACK (orientation_notify), NULL);
+}
+
+static void
+add_host (NaBox *self,
+ NaHost *host)
+{
+ self->hosts = g_slist_prepend (self->hosts, host);
+
+ g_object_bind_property (self, "icon-padding", host, "icon-padding",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ g_object_bind_property (self, "icon-size", host, "icon-size",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ g_signal_connect_object (host, "item-added",
+ G_CALLBACK (item_added_cb), self, 0);
+ g_signal_connect_object (host, "item-removed",
+ G_CALLBACK (item_removed_cb), self, 0);
+}
+
+static void
+na_box_style_updated (GtkWidget *widget)
+{
+ NaBox *self = NA_BOX (widget);
+ GtkStyleContext *context;
+ GSList *node;
+
+ if (GTK_WIDGET_CLASS (na_box_parent_class)->style_updated)
+ GTK_WIDGET_CLASS (na_box_parent_class)->style_updated (widget);
+
+ context = gtk_widget_get_style_context (widget);
+
+ for (node = self->hosts; node; node = node->next)
+ {
+ gtk_style_context_save (context);
+ na_host_style_updated (node->data, context);
+ gtk_style_context_restore (context);
+ }
+}
+
+/* Custom drawing because system-tray items need weird stuff. */
+static gboolean
+na_box_draw (GtkWidget *box,
+ cairo_t *cr)
+{
+ GList *child;
+ GList *children = gtk_container_get_children (GTK_CONTAINER (box));
+
+ for (child = children; child; child = child->next)
+ {
+ if (! NA_IS_ITEM (child->data) ||
+ ! na_item_draw_on_parent (child->data, box, cr))
+ {
+ if (gtk_widget_is_drawable (child->data) &&
+ gtk_cairo_should_draw_window (cr, gtk_widget_get_window (child->data)))
+ gtk_container_propagate_draw (GTK_CONTAINER (box), child->data, cr);
+ }
+ }
+
+ g_list_free (children);
+
+ return TRUE;
+}
+
+static void
+na_box_realize (GtkWidget *widget)
+{
+ NaBox *self = NA_BOX (widget);
+ GdkScreen *screen;
+ GtkOrientation orientation;
+
+ GTK_WIDGET_CLASS (na_box_parent_class)->realize (widget);
+
+ /* Instantiate the hosts now we have a screen */
+ screen = gtk_widget_get_screen (GTK_WIDGET (self));
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self));
+
+ add_host (self, na_tray_new_for_screen (screen, orientation));
+ add_host (self, sn_host_v0_new ());
+}
+
+static void
+na_box_unrealize (GtkWidget *widget)
+{
+ NaBox *self = NA_BOX (widget);
+
+ if (self->hosts != NULL)
+ {
+ g_slist_free_full (self->hosts, g_object_unref);
+ self->hosts = NULL;
+ }
+
+ g_clear_pointer (&self->items, g_slist_free);
+
+ GTK_WIDGET_CLASS (na_box_parent_class)->unrealize (widget);
+}
+
+static void
+na_box_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NaBox *self = NA_BOX (object);
+
+ switch (property_id)
+ {
+ case PROP_ICON_PADDING:
+ g_value_set_int (value, self->icon_padding);
+ break;
+
+ case PROP_ICON_SIZE:
+ g_value_set_int (value, self->icon_size);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+na_box_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NaBox *self = NA_BOX (object);
+
+ switch (property_id)
+ {
+ case PROP_ICON_PADDING:
+ self->icon_padding = g_value_get_int (value);
+ break;
+
+ case PROP_ICON_SIZE:
+ self->icon_size = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+na_box_class_init (NaBoxClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gobject_class->get_property = na_box_get_property;
+ gobject_class->set_property = na_box_set_property;
+
+ widget_class->draw = na_box_draw;
+ widget_class->realize = na_box_realize;
+ widget_class->unrealize = na_box_unrealize;
+ widget_class->style_updated = na_box_style_updated;
+
+ g_object_class_install_property (gobject_class, PROP_ICON_PADDING,
+ g_param_spec_int ("icon-padding",
+ "Padding around icons",
+ "Padding that should be put around icons, in pixels",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ICON_SIZE,
+ g_param_spec_int ("icon-size",
+ "Icon size",
+ "If non-zero, hardcodes the size of the icons in pixels",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+GtkWidget *
+na_box_new (GtkOrientation orientation)
+{
+ return g_object_new (NA_TYPE_BOX,
+ "orientation", orientation,
+ "spacing", ICON_SPACING,
+ NULL);
+}
+
+void
+na_box_force_redraw (NaBox *box)
+{
+ GSList *node;
+
+ for (node = box->hosts; node; node = node->next)
+ na_host_force_redraw (node->data);
+}
diff --git a/applets/notification_area/na-box.h b/applets/notification_area/na-box.h
new file mode 100644
index 00000000..5e13b488
--- /dev/null
+++ b/applets/notification_area/na-box.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* na-tray-tray.h
+ * Copyright (C) 2002 Anders Carlsson <[email protected]>
+ * Copyright (C) 2003-2006 Vincent Untz
+ * Copyright (C) 2017 Colomban Wendling <[email protected]>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Used to be: eggtraytray.h
+ */
+
+#ifndef NA_BOX_H
+#define NA_BOX_H
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NA_TYPE_BOX (na_box_get_type ())
+#define NA_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_BOX, NaBox))
+#define NA_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_BOX, NaBoxClass))
+#define NA_IS_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_BOX))
+#define NA_IS_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_BOX))
+#define NA_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_BOX, NaBoxClass))
+
+typedef struct _NaBox NaBox;
+typedef struct _NaBoxClass NaBoxClass;
+
+struct _NaBoxClass
+{
+ GtkBoxClass parent_class;
+};
+
+GType na_box_get_type (void);
+GtkWidget *na_box_new (GtkOrientation orientation);
+void na_box_force_redraw (NaBox *box);
+
+G_END_DECLS
+
+#endif /* __NA_TRAY_H__ */
diff --git a/applets/notification_area/na-host.c b/applets/notification_area/na-host.c
new file mode 100644
index 00000000..969a6c70
--- /dev/null
+++ b/applets/notification_area/na-host.c
@@ -0,0 +1,105 @@
+/*
+ * 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 "na-host.h"
+#include "na-item.h"
+
+enum
+{
+ SIGNAL_ITEM_ADDED,
+ SIGNAL_ITEM_REMOVED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_INTERFACE (NaHost, na_host, G_TYPE_OBJECT)
+
+static void
+na_host_default_init (NaHostInterface *iface)
+{
+ signals[SIGNAL_ITEM_ADDED] =
+ g_signal_new ("item-added", G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, NA_TYPE_ITEM);
+
+ signals[SIGNAL_ITEM_REMOVED] =
+ g_signal_new ("item-removed", G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, NA_TYPE_ITEM);
+
+ g_object_interface_install_property (iface,
+ g_param_spec_int ("icon-padding",
+ "Padding around icons",
+ "Padding that should be put around icons, in pixels",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_interface_install_property (iface,
+ g_param_spec_int ("icon-size",
+ "Icon size",
+ "If non-zero, hardcodes the size of the icons in pixels",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ iface->style_updated = NULL;
+}
+
+void
+na_host_force_redraw (NaHost *host)
+{
+ NaHostInterface *iface;
+
+ g_return_if_fail (NA_IS_HOST (host));
+
+ iface = NA_HOST_GET_IFACE (host);
+
+ if (iface->force_redraw != NULL)
+ iface->force_redraw (host);
+}
+
+void
+na_host_style_updated (NaHost *host,
+ GtkStyleContext *context)
+{
+ NaHostInterface *iface;
+
+ g_return_if_fail (NA_IS_HOST (host));
+
+ iface = NA_HOST_GET_IFACE (host);
+
+ if (iface->style_updated != NULL)
+ iface->style_updated (host, context);
+}
+
+void
+na_host_emit_item_added (NaHost *host,
+ NaItem *item)
+{
+ g_signal_emit (host, signals[SIGNAL_ITEM_ADDED], 0, item);
+}
+
+void
+na_host_emit_item_removed (NaHost *host,
+ NaItem *item)
+{
+ g_signal_emit (host, signals[SIGNAL_ITEM_REMOVED], 0, item);
+}
diff --git a/applets/notification_area/na-host.h b/applets/notification_area/na-host.h
new file mode 100644
index 00000000..7f06678a
--- /dev/null
+++ b/applets/notification_area/na-host.h
@@ -0,0 +1,48 @@
+/*
+ * 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/>.
+ */
+
+#ifndef NA_HOST_H
+#define NA_HOST_H
+
+#include "na-item.h"
+
+G_BEGIN_DECLS
+
+#define NA_TYPE_HOST na_host_get_type ()
+G_DECLARE_INTERFACE (NaHost, na_host, NA, HOST, GObject)
+
+struct _NaHostInterface
+{
+ GTypeInterface parent;
+
+ void (*force_redraw) (NaHost *host);
+ void (*style_updated) (NaHost *host,
+ GtkStyleContext *context);
+};
+
+void na_host_force_redraw (NaHost *host);
+void na_host_style_updated (NaHost *host,
+ GtkStyleContext *context);
+void na_host_emit_item_added (NaHost *host,
+ NaItem *item);
+void na_host_emit_item_removed (NaHost *host,
+ NaItem *item);
+
+G_END_DECLS
+
+#endif
diff --git a/applets/notification_area/na-item.c b/applets/notification_area/na-item.c
new file mode 100644
index 00000000..497e4102
--- /dev/null
+++ b/applets/notification_area/na-item.c
@@ -0,0 +1,91 @@
+/*
+ * 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 "na-item.h"
+
+G_DEFINE_INTERFACE_WITH_CODE (NaItem, na_item, GTK_TYPE_WIDGET,
+ g_type_interface_add_prerequisite (g_define_type_id,
+ GTK_TYPE_ORIENTABLE);)
+
+static gboolean
+na_item_draw_on_parent_default (NaItem *item,
+ GtkWidget *parent,
+ cairo_t *parent_cr)
+{
+ return FALSE;
+}
+
+static void
+na_item_default_init (NaItemInterface *iface)
+{
+ iface->draw_on_parent = na_item_draw_on_parent_default;
+}
+
+const gchar *
+na_item_get_id (NaItem *item)
+{
+ NaItemInterface *iface;
+
+ g_return_val_if_fail (NA_IS_ITEM (item), NULL);
+
+ iface = NA_ITEM_GET_IFACE (item);
+ g_return_val_if_fail (iface->get_id != NULL, NULL);
+
+ return iface->get_id (item);
+}
+
+NaItemCategory
+na_item_get_category (NaItem *item)
+{
+ NaItemInterface *iface;
+
+ g_return_val_if_fail (NA_IS_ITEM (item),
+ NA_ITEM_CATEGORY_APPLICATION_STATUS);
+
+ iface = NA_ITEM_GET_IFACE (item);
+ g_return_val_if_fail (iface->get_category != NULL,
+ NA_ITEM_CATEGORY_APPLICATION_STATUS);
+
+ return iface->get_category (item);
+}
+
+/*
+ * Fairly ugly hack because system-tray/NaTrayChild uses a weird hack for
+ * drawing itself. I'm not sure it's still needed with the current GTK3
+ * drawing where not all widgets have an own window, but well.
+ *
+ * Should return %TRUE if it handled itself, or %FALSE if the parent should
+ * draw normally. Default is to draw normally.
+ */
+gboolean
+na_item_draw_on_parent (NaItem *item,
+ GtkWidget *parent,
+ cairo_t *parent_cr)
+{
+ NaItemInterface *iface;
+
+ g_return_val_if_fail (NA_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (GTK_IS_WIDGET (parent), FALSE);
+
+ iface = NA_ITEM_GET_IFACE (item);
+ g_return_val_if_fail (iface->draw_on_parent != NULL, FALSE);
+
+ return iface->draw_on_parent (item, parent, parent_cr);
+}
diff --git a/applets/notification_area/na-item.h b/applets/notification_area/na-item.h
new file mode 100644
index 00000000..30e8e676
--- /dev/null
+++ b/applets/notification_area/na-item.h
@@ -0,0 +1,64 @@
+/*
+ * 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/>.
+ */
+
+#ifndef NA_ITEM_H
+#define NA_ITEM_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NA_TYPE_ITEM na_item_get_type ()
+G_DECLARE_INTERFACE (NaItem, na_item, NA, ITEM, GtkWidget)
+
+typedef enum
+{
+ NA_ITEM_CATEGORY_APPLICATION_STATUS,
+ NA_ITEM_CATEGORY_COMMUNICATIONS,
+ NA_ITEM_CATEGORY_SYSTEM_SERVICES,
+ NA_ITEM_CATEGORY_HARDWARE,
+ /* FIXME: use proper categories for system tray stuff, too.
+ * See na_tray_child_get_category() in na-tray-child.c */
+ NA_ITEM_CATEGORY_FAKE_BATTERY,
+ NA_ITEM_CATEGORY_FAKE_NETWORK,
+ NA_ITEM_CATEGORY_FAKE_BLUETOOTH,
+ NA_ITEM_CATEGORY_FAKE_VOLUME,
+ NA_ITEM_CATEGORY_FAKE_KEYBOARD,
+} NaItemCategory;
+
+struct _NaItemInterface
+{
+ GTypeInterface g_iface;
+
+ const gchar * (* get_id) (NaItem *item);
+ NaItemCategory (* get_category) (NaItem *item);
+
+ gboolean (* draw_on_parent) (NaItem *item,
+ GtkWidget *parent,
+ cairo_t *parent_cr);
+};
+
+const gchar *na_item_get_id (NaItem *item);
+NaItemCategory na_item_get_category (NaItem *item);
+gboolean na_item_draw_on_parent (NaItem *item,
+ GtkWidget *parent,
+ cairo_t *parent_cr);
+
+G_END_DECLS
+
+#endif
diff --git a/applets/notification_area/status-notifier/Makefile.am b/applets/notification_area/status-notifier/Makefile.am
new file mode 100644
index 00000000..78d0a7fb
--- /dev/null
+++ b/applets/notification_area/status-notifier/Makefile.am
@@ -0,0 +1,83 @@
+NULL =
+
+noinst_LTLIBRARIES = libstatus-notifier.la
+
+AM_CPPFLAGS = \
+ $(NOTIFICATION_AREA_CFLAGS) \
+ $(LIBMATE_PANEL_APPLET_CFLAGS) \
+ -I$(srcdir) \
+ -I$(srcdir)/.. \
+ -DMATELOCALEDIR=\""$(datadir)/locale"\" \
+ -DG_LOG_DOMAIN=\""notification-area-applet"\" \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+AM_CFLAGS = $(WARN_CFLAGS)
+
+libstatus_notifier_la_SOURCES = \
+ sn-dbus-menu.c \
+ sn-dbus-menu.h \
+ sn-dbus-menu-item.c \
+ sn-dbus-menu-item.h \
+ sn-host-v0.c \
+ sn-host-v0.h \
+ sn-image-menu-item.c \
+ sn-image-menu-item.h \
+ sn-item.c \
+ sn-item.h \
+ sn-item-v0.c \
+ sn-item-v0.h \
+ $(BUILT_SOURCES) \
+ $(NULL)
+
+libstatus_notifier_la_LIBADD = \
+ $(LIBM) \
+ $(NOTIFICATION_AREA_LIBS) \
+ $(NULL)
+
+sn-dbus-menu-gen.h:
+sn-dbus-menu-gen.c: com.canonical.dbusmenu.xml
+ $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Sn \
+ --generate-c-code sn-dbus-menu-gen \
+ $(srcdir)/com.canonical.dbusmenu.xml
+
+sn-host-v0-gen.h:
+sn-host-v0-gen.c: org.kde.StatusNotifierHost.xml
+ $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Sn \
+ --generate-c-code sn-host-v0-gen \
+ $(srcdir)/org.kde.StatusNotifierHost.xml
+
+sn-item-v0-gen.h:
+sn-item-v0-gen.c: org.kde.StatusNotifierItem.xml
+ $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Sn \
+ --generate-c-code sn-item-v0-gen \
+ $(srcdir)/org.kde.StatusNotifierItem.xml
+
+sn-watcher-v0-gen.h:
+sn-watcher-v0-gen.c: org.kde.StatusNotifierWatcher.xml
+ $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Sn \
+ --generate-c-code sn-watcher-v0-gen \
+ $(srcdir)/org.kde.StatusNotifierWatcher.xml
+
+BUILT_SOURCES = \
+ sn-dbus-menu-gen.c \
+ sn-dbus-menu-gen.h \
+ sn-host-v0-gen.c \
+ sn-host-v0-gen.h \
+ sn-item-v0-gen.c \
+ sn-item-v0-gen.h \
+ sn-watcher-v0-gen.c \
+ sn-watcher-v0-gen.h \
+ $(NULL)
+
+EXTRA_DIST = \
+ com.canonical.dbusmenu.xml \
+ org.kde.StatusNotifierHost.xml \
+ org.kde.StatusNotifierItem.xml \
+ org.kde.StatusNotifierWatcher.xml \
+ $(NULL)
+
+CLEANFILES = \
+ $(BUILT_SOURCES) \
+ $(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/applets/notification_area/status-notifier/README b/applets/notification_area/status-notifier/README
new file mode 100644
index 00000000..8b89ae98
--- /dev/null
+++ b/applets/notification_area/status-notifier/README
@@ -0,0 +1,2 @@
+Based off gnome-panel's status-notifier applet:
+https://git.gnome.org/browse/gnome-panel/tree/modules/external/status-notifier
diff --git a/applets/notification_area/status-notifier/com.canonical.dbusmenu.xml b/applets/notification_area/status-notifier/com.canonical.dbusmenu.xml
new file mode 100644
index 00000000..f8f72d10
--- /dev/null
+++ b/applets/notification_area/status-notifier/com.canonical.dbusmenu.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="com.canonical.dbusmenu">
+ <annotation name="org.gtk.GDBus.C.Name" value="DBusMenuGen" />
+
+ <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="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="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="EventGroup">
+ <arg type="a(isvu)" name="events" direction="in" />
+ <arg type="ai" name="idErrors" direction="out" />
+ </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>
+
+ <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" />
+
+ <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/applets/notification_area/status-notifier/org.kde.StatusNotifierHost.xml b/applets/notification_area/status-notifier/org.kde.StatusNotifierHost.xml
new file mode 100644
index 00000000..3e8b5625
--- /dev/null
+++ b/applets/notification_area/status-notifier/org.kde.StatusNotifierHost.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.StatusNotifierHost">
+ <annotation name="org.gtk.GDBus.C.Name" value="HostV0Gen" />
+ </interface>
+</node>
diff --git a/applets/notification_area/status-notifier/org.kde.StatusNotifierItem.xml b/applets/notification_area/status-notifier/org.kde.StatusNotifierItem.xml
new file mode 100644
index 00000000..609d3050
--- /dev/null
+++ b/applets/notification_area/status-notifier/org.kde.StatusNotifierItem.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.StatusNotifierItem">
+ <annotation name="org.gtk.GDBus.C.Name" value="ItemV0Gen" />
+
+ <method name="ContextMenu">
+ <arg name="x" type="i" direction="in" />
+ <arg name="y" type="i" direction="in" />
+ </method>
+
+ <method name="Activate">
+ <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>
+
+ <method name="Scroll">
+ <arg name="delta" type="i" direction="in" />
+ <arg name="orientation" type="s" direction="in" />
+ </method>
+
+ <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="IconName" type="s" access="read"/>
+
+ <property name="IconPixmap" type="a(iiay)" 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="AttentionMovieName" type="s" access="read" />
+
+ <property name="ToolTip" type="(sa(iiay)ss)" access="read" />
+
+ <signal name="NewTitle" />
+
+ <signal name="NewIcon" />
+
+ <signal name="NewAttentionIcon" />
+
+ <signal name="NewOverlayIcon" />
+
+ <signal name="NewToolTip" />
+
+ <signal name="NewStatus">
+ <arg name="status" type="s" />
+ </signal>
+
+ <!-- Properties and signals that is not part of specification -->
+
+ <property name="IconThemePath" type="s" access="read" />
+
+ <property name="Menu" type="o" access="read" />
+
+ <property name="ItemIsMenu" type="b" access="read" />
+
+ <signal name="NewIconThemePath">
+ <arg name="icon_theme_path" type="s" />
+ </signal>
+ </interface>
+</node>
diff --git a/applets/notification_area/status-notifier/org.kde.StatusNotifierWatcher.xml b/applets/notification_area/status-notifier/org.kde.StatusNotifierWatcher.xml
new file mode 100644
index 00000000..3b0f135a
--- /dev/null
+++ b/applets/notification_area/status-notifier/org.kde.StatusNotifierWatcher.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.StatusNotifierWatcher">
+ <annotation name="org.gtk.GDBus.C.Name" value="WatcherV0Gen" />
+
+ <method name="RegisterStatusNotifierItem">
+ <annotation name="org.gtk.GDBus.C.Name" value="RegisterItem" />
+ <arg type="s" direction="in" name="service" />
+ </method>
+
+ <method name="RegisterStatusNotifierHost">
+ <annotation name="org.gtk.GDBus.C.Name" value="RegisterHost" />
+ <arg type="s" direction="in" name="service" />
+ </method>
+
+ <property name="RegisteredStatusNotifierItems" type="as" access="read">
+ <annotation name="org.gtk.GDBus.C.Name" value="RegisteredItems" />
+ </property>
+
+ <property name="IsStatusNotifierHostRegistered" type="b" access="read">
+ <annotation name="org.gtk.GDBus.C.Name" value="IsHostRegistered" />
+ </property>
+
+ <property name="ProtocolVersion" type="i" access="read" />
+
+ <signal name="StatusNotifierItemRegistered">
+ <annotation name="org.gtk.GDBus.C.Name" value="ItemRegistered" />
+ <arg type="s" direction="out" name="service" />
+ </signal>
+
+ <signal name="StatusNotifierItemUnregistered">
+ <annotation name="org.gtk.GDBus.C.Name" value="ItemUnregistered" />
+ <arg type="s" direction="out" name="service" />
+ </signal>
+
+ <signal name="StatusNotifierHostRegistered">
+ <annotation name="org.gtk.GDBus.C.Name" value="HostRegistered" />
+ </signal>
+ </interface>
+</node>
diff --git a/applets/notification_area/status-notifier/sn-dbus-menu-item.c b/applets/notification_area/status-notifier/sn-dbus-menu-item.c
new file mode 100644
index 00000000..3e740b1c
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-dbus-menu-item.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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 "sn-dbus-menu-item.h"
+#include "sn-image-menu-item.h"
+
+static GdkPixbuf *
+pxibuf_new (GVariant *variant)
+{
+ gsize length;
+ const guchar *data;
+ GInputStream *stream;
+ GdkPixbuf *pixbuf;
+ GError *error;
+
+ data = g_variant_get_fixed_array (variant, &length, sizeof (guchar));
+
+ if (length == 0)
+ return NULL;
+
+ stream = g_memory_input_stream_new_from_data (data, length, NULL);
+
+ if (stream == NULL)
+ return NULL;
+
+ error = NULL;
+ pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, &error);
+ g_object_unref (stream);
+
+ if (error != NULL)
+ {
+ g_warning ("Unable to build GdkPixbuf from icon data: %s", error->message);
+ g_error_free (error);
+ }
+
+ return pixbuf;
+}
+
+static SnShortcut *
+sn_shortcut_new (guint key,
+ GdkModifierType mask)
+{
+ SnShortcut *shortcut;
+
+ shortcut = g_new0 (SnShortcut, 1);
+
+ shortcut->key = key;
+ shortcut->mask = mask;
+
+ return shortcut;
+}
+
+static SnShortcut **
+sn_shortcuts_new (GVariant *variant)
+{
+ GPtrArray *array;
+ GVariantIter shortcuts;
+ GVariantIter *shortcut;
+
+ if (variant == NULL || g_variant_iter_init (&shortcuts, variant) == 0)
+ return NULL;
+
+ array = g_ptr_array_new ();
+ while (g_variant_iter_next (&shortcuts, "as", &shortcut))
+ {
+ guint key;
+ GdkModifierType mask;
+ const gchar *string;
+
+ key = 0;
+ mask = 0;
+
+ while (g_variant_iter_next (shortcut, "&s", &string))
+ {
+ if (g_strcmp0 (string, "Control") == 0)
+ mask |= GDK_CONTROL_MASK;
+ else if (g_strcmp0 (string, "Alt") == 0)
+ mask |= GDK_MOD1_MASK;
+ else if (g_strcmp0 (string, "Shift") == 0)
+ mask |= GDK_SHIFT_MASK;
+ else if (g_strcmp0 (string, "Super") == 0)
+ mask |= GDK_SUPER_MASK;
+ else
+ gtk_accelerator_parse (string, &key, NULL);
+ }
+
+ g_ptr_array_add (array,sn_shortcut_new (key, mask));
+ g_variant_iter_free (shortcut);
+ }
+
+ g_ptr_array_add (array, NULL);
+ return (SnShortcut **) g_ptr_array_free (array, FALSE);
+}
+
+static void
+sn_shortcuts_free (SnShortcut **shortcuts)
+{
+ guint i;
+
+ if (shortcuts == NULL)
+ return;
+
+ for (i = 0; shortcuts[i] != NULL; i++)
+ g_free (shortcuts[i]);
+
+ g_free (shortcuts);
+}
+
+SnDBusMenuItem *
+sn_dbus_menu_item_new (GVariant *props)
+{
+ SnDBusMenuItem *item;
+ GVariantIter iter;
+ const gchar *prop;
+ GVariant *value;
+
+ item = g_new0 (SnDBusMenuItem, 1);
+
+ item->enabled = TRUE;
+ item->toggle_state = -1;
+ item->visible = TRUE;
+
+ g_variant_iter_init (&iter, props);
+ while (g_variant_iter_next (&iter, "{&sv}", &prop, &value))
+ {
+ if (g_strcmp0 (prop, "accessible-desc") == 0)
+ item->accessible_desc = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (prop, "children-display") == 0)
+ item->children_display = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (prop, "disposition") == 0)
+ item->disposition = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (prop, "enabled") == 0)
+ item->enabled = g_variant_get_boolean (value);
+ else if (g_strcmp0 (prop, "icon-name") == 0)
+ item->icon_name = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (prop, "icon-data") == 0)
+ item->icon_data = pxibuf_new (value);
+ else if (g_strcmp0 (prop, "label") == 0)
+ item->label = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (prop, "shortcut") == 0)
+ item->shortcuts = sn_shortcuts_new (value);
+ else if (g_strcmp0 (prop, "toggle-type") == 0)
+ item->toggle_type = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (prop, "toggle-state") == 0)
+ item->toggle_state = g_variant_get_int32 (value);
+ else if (g_strcmp0 (prop, "type") == 0)
+ item->type = g_variant_dup_string (value, NULL);
+ else if (g_strcmp0 (prop, "visible") == 0)
+ item->visible = g_variant_get_boolean (value);
+ else
+ g_debug ("unknown property '%s'", prop);
+
+ g_variant_unref (value);
+ }
+
+ if (g_strcmp0 (item->type, "separator") == 0)
+ {
+ item->item = gtk_separator_menu_item_new ();
+ }
+ else
+ {
+ if (g_strcmp0 (item->toggle_type, "checkmark") == 0)
+ {
+ item->item = gtk_check_menu_item_new ();
+ gtk_menu_item_set_use_underline (GTK_MENU_ITEM (item->item), TRUE);
+ }
+ else if (g_strcmp0 (item->toggle_type, "radio") == 0)
+ {
+ item->item = gtk_radio_menu_item_new (NULL);
+ gtk_menu_item_set_use_underline (GTK_MENU_ITEM (item->item), TRUE);
+ }
+ else
+ {
+ SnImageMenuItem *image_item;
+
+ item->item = sn_image_menu_item_new ();
+ image_item = SN_IMAGE_MENU_ITEM (item->item);
+
+ if (item->icon_name)
+ {
+ sn_image_menu_item_set_image_from_icon_name (image_item,
+ item->icon_name);
+ }
+ else if (item->icon_data)
+ {
+ sn_image_menu_item_set_image_from_icon_pixbuf (image_item,
+ item->icon_data);
+ }
+ }
+
+ if (g_strcmp0 (item->children_display, "submenu") == 0)
+ {
+ GtkWidget *submenu;
+
+ submenu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (item->item), submenu);
+
+ item->submenu = GTK_MENU (submenu);
+ g_object_ref_sink (item->submenu);
+ }
+
+ gtk_menu_item_set_label (GTK_MENU_ITEM (item->item), item->label);
+
+ if (item->shortcuts)
+ {
+ guint i;
+
+ for (i = 0; item->shortcuts[i] != NULL; i++)
+ {
+ }
+ }
+
+ if (item->toggle_state != -1 && GTK_IS_CHECK_MENU_ITEM (item->item))
+ {
+ GtkCheckMenuItem *check;
+
+ check = GTK_CHECK_MENU_ITEM (item->item);
+
+ if (item->toggle_state == 1)
+ gtk_check_menu_item_set_active (check, TRUE);
+ else if (item->toggle_state == 0)
+ gtk_check_menu_item_set_active (check, FALSE);
+ }
+ }
+
+ gtk_widget_set_sensitive (item->item, item->enabled);
+ gtk_widget_set_visible (item->item, item->visible);
+
+ g_object_ref_sink (item->item);
+ return item;
+}
+
+void
+sn_dubs_menu_item_free (gpointer data)
+{
+ SnDBusMenuItem *item;
+
+ item = (SnDBusMenuItem *) data;
+ if (item == NULL)
+ return;
+
+ g_clear_pointer (&item->accessible_desc, g_free);
+ g_clear_pointer (&item->children_display, g_free);
+ g_clear_pointer (&item->disposition, g_free);
+ g_clear_pointer (&item->icon_name, g_free);
+ g_clear_object (&item->icon_data);
+ g_clear_pointer (&item->label, g_free);
+ g_clear_pointer (&item->shortcuts, sn_shortcuts_free);
+ g_clear_pointer (&item->toggle_type, g_free);
+ g_clear_pointer (&item->type, g_free);
+
+ gtk_widget_destroy (item->item);
+ g_clear_object (&item->item);
+ g_clear_object (&item->submenu);
+
+ g_free (item);
+}
+
+void
+sn_dbus_menu_item_update_props (SnDBusMenuItem *item,
+ GVariant *props)
+{
+ GVariantIter iter;
+ const gchar *prop;
+ GVariant *value;
+
+ g_variant_iter_init (&iter, props);
+ while (g_variant_iter_next (&iter, "{&sv}", &prop, &value))
+ {
+ if (g_strcmp0 (prop, "accessible-desc") == 0)
+ {
+ g_free (item->accessible_desc);
+ item->accessible_desc = g_variant_dup_string (value, NULL);
+ }
+ else if (g_strcmp0 (prop, "children-display") == 0)
+ {
+ g_free (item->children_display);
+ item->children_display = g_variant_dup_string (value, NULL);
+ }
+ else if (g_strcmp0 (prop, "disposition") == 0)
+ {
+ g_free (item->disposition);
+ item->disposition = g_variant_dup_string (value, NULL);
+ }
+ else if (g_strcmp0 (prop, "enabled") == 0)
+ {
+ item->enabled = g_variant_get_boolean (value);
+ gtk_widget_set_sensitive (item->item, item->enabled);
+ }
+ else if (g_strcmp0 (prop, "icon-name") == 0)
+ {
+ SnImageMenuItem *image_item;
+
+ g_free (item->icon_name);
+ item->icon_name = g_variant_dup_string (value, NULL);
+
+ image_item = SN_IMAGE_MENU_ITEM (item->item);
+
+ if (item->icon_name)
+ {
+ sn_image_menu_item_set_image_from_icon_name (image_item,
+ item->icon_name);
+ }
+ else
+ {
+ sn_image_menu_item_unset_image (image_item);
+ }
+ }
+ else if (g_strcmp0 (prop, "icon-data") == 0)
+ {
+ SnImageMenuItem *image_item;
+
+ g_clear_object (&item->icon_data);
+ item->icon_data = pxibuf_new (value);
+
+ image_item = SN_IMAGE_MENU_ITEM (item->item);
+
+ if (item->icon_data)
+ {
+ sn_image_menu_item_set_image_from_icon_pixbuf (image_item,
+ item->icon_data);
+ }
+ else
+ {
+ sn_image_menu_item_unset_image (image_item);
+ }
+ }
+ else if (g_strcmp0 (prop, "label") == 0)
+ {
+ g_free (item->label);
+ item->label = g_variant_dup_string (value, NULL);
+
+ if (!GTK_IS_SEPARATOR_MENU_ITEM (item->item))
+ gtk_menu_item_set_label (GTK_MENU_ITEM (item->item), item->label);
+ }
+ else if (g_strcmp0 (prop, "shortcut") == 0)
+ {
+ sn_shortcuts_free (item->shortcuts);
+ item->shortcuts = sn_shortcuts_new (value);
+ }
+ else if (g_strcmp0 (prop, "toggle-type") == 0)
+ {
+ g_free (item->toggle_type);
+ item->toggle_type = g_variant_dup_string (value, NULL);
+ }
+ else if (g_strcmp0 (prop, "toggle-state") == 0)
+ {
+ item->toggle_state = g_variant_get_int32 (value);
+
+ if (item->toggle_state != -1 && GTK_IS_CHECK_MENU_ITEM (item->item))
+ {
+ GtkCheckMenuItem *check;
+
+ check = GTK_CHECK_MENU_ITEM (item->item);
+
+ g_signal_handler_block (item->item, item->activate_id);
+
+ if (item->toggle_state == 1)
+ gtk_check_menu_item_set_active (check, TRUE);
+ else if (item->toggle_state == 0)
+ gtk_check_menu_item_set_active (check, FALSE);
+
+ g_signal_handler_unblock (item->item, item->activate_id);
+ }
+ }
+ else if (g_strcmp0 (prop, "type") == 0)
+ {
+ g_free (item->type);
+ item->type = g_variant_dup_string (value, NULL);
+ }
+ else if (g_strcmp0 (prop, "visible") == 0)
+ {
+ item->visible = g_variant_get_boolean (value);
+ gtk_widget_set_visible (item->item, item->visible);
+ }
+ else
+ {
+ g_debug ("updating unknown property - '%s'", prop);
+ }
+
+ g_variant_unref (value);
+ }
+}
+
+void
+sn_dbus_menu_item_remove_props (SnDBusMenuItem *item,
+ GVariant *props)
+{
+ GVariantIter iter;
+ const gchar *prop;
+
+ g_variant_iter_init (&iter, props);
+ while (g_variant_iter_next (&iter, "&s", &prop))
+ {
+ if (g_strcmp0 (prop, "accessible-desc") == 0)
+ {
+ g_clear_pointer (&item->accessible_desc, g_free);
+ }
+ else if (g_strcmp0 (prop, "children-display") == 0)
+ {
+ g_clear_pointer (&item->children_display, g_free);
+ }
+ else if (g_strcmp0 (prop, "disposition") == 0)
+ {
+ g_clear_pointer (&item->disposition, g_free);
+ }
+ else if (g_strcmp0 (prop, "enabled") == 0)
+ {
+ item->enabled = TRUE;
+ gtk_widget_set_sensitive (item->item, item->enabled);
+ }
+ else if (g_strcmp0 (prop, "icon-name") == 0)
+ {
+ g_clear_pointer (&item->icon_name, g_free);
+ if (SN_IS_IMAGE_MENU_ITEM (item->item))
+ sn_image_menu_item_unset_image (SN_IMAGE_MENU_ITEM (item->item));
+ }
+ else if (g_strcmp0 (prop, "icon-data") == 0)
+ {
+ g_clear_object (&item->icon_data);
+ if (SN_IS_IMAGE_MENU_ITEM (item->item))
+ sn_image_menu_item_unset_image (SN_IMAGE_MENU_ITEM (item->item));
+ }
+ else if (g_strcmp0 (prop, "label") == 0)
+ {
+ g_clear_pointer (&item->label, g_free);
+ if (!GTK_IS_SEPARATOR_MENU_ITEM (item->item))
+ gtk_menu_item_set_label (GTK_MENU_ITEM (item->item), item->label);
+ }
+ else if (g_strcmp0 (prop, "shortcut") == 0)
+ {
+ g_clear_pointer (&item->shortcuts, sn_shortcuts_free);
+ }
+ else if (g_strcmp0 (prop, "toggle-type") == 0)
+ {
+ g_clear_pointer (&item->toggle_type, g_free);
+ }
+ else if (g_strcmp0 (prop, "toggle-state") == 0)
+ {
+ item->toggle_state = -1;
+ }
+ else if (g_strcmp0 (prop, "type") == 0)
+ {
+ g_clear_pointer (&item->type, g_free);
+ }
+ else if (g_strcmp0 (prop, "visible") == 0)
+ {
+ item->visible = TRUE;
+ gtk_widget_set_visible (item->item, item->visible);
+ }
+ else
+ {
+ g_debug ("removing unknown property - '%s'", prop);
+ }
+ }
+}
diff --git a/applets/notification_area/status-notifier/sn-dbus-menu-item.h b/applets/notification_area/status-notifier/sn-dbus-menu-item.h
new file mode 100644
index 00000000..c89de5c3
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-dbus-menu-item.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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/>.
+ */
+
+#ifndef SN_DBUS_MENU_ITEM_H
+#define SN_DUBS_MENU_ITEM_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+ guint key;
+ GdkModifierType mask;
+} SnShortcut;
+
+typedef struct
+{
+ gchar *accessible_desc;
+ gchar *children_display;
+ gchar *disposition;
+ gboolean enabled;
+ gchar *icon_name;
+ GdkPixbuf *icon_data;
+ gchar *label;
+ SnShortcut **shortcuts;
+ gchar *toggle_type;
+ gint32 toggle_state;
+ gchar *type;
+ gboolean visible;
+
+ GtkWidget *item;
+ GtkMenu *submenu;
+
+ gulong activate_id;
+} SnDBusMenuItem;
+
+SnDBusMenuItem *sn_dbus_menu_item_new (GVariant *props);
+
+void sn_dubs_menu_item_free (gpointer data);
+
+void sn_dbus_menu_item_update_props (SnDBusMenuItem *item,
+ GVariant *props);
+
+void sn_dbus_menu_item_remove_props (SnDBusMenuItem *item,
+ GVariant *props);
+
+G_END_DECLS
+
+#endif
diff --git a/applets/notification_area/status-notifier/sn-dbus-menu.c b/applets/notification_area/status-notifier/sn-dbus-menu.c
new file mode 100644
index 00000000..9cbab345
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-dbus-menu.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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 "sn-dbus-menu.h"
+#include "sn-dbus-menu-gen.h"
+#include "sn-dbus-menu-item.h"
+
+struct _SnDBusMenu
+{
+ GtkMenu parent;
+
+ GHashTable *items;
+
+ GCancellable *cancellable;
+
+ gchar *bus_name;
+ gchar *object_path;
+ guint name_id;
+
+ SnDBusMenuGen *proxy;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_BUS_NAME,
+ PROP_OBJECT_PATH,
+
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
+static const gchar *property_names[] =
+{
+ "accessible-desc",
+ "children-display",
+ "disposition",
+ "enabled",
+ "icon-data",
+ "icon-name",
+ "label",
+ "shortcut",
+ "toggle-type",
+ "toggle-state",
+ "type",
+ "visible",
+ NULL
+};
+
+G_DEFINE_TYPE (SnDBusMenu, sn_dbus_menu, GTK_TYPE_MENU)
+
+static void
+activate_cb (GtkWidget *widget,
+ SnDBusMenu *menu)
+{
+ guint id;
+
+ if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)) != NULL)
+ return;
+
+ id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "item-id"));
+ sn_dbus_menu_gen_call_event_sync (menu->proxy, id, "clicked",
+ g_variant_new ("v", g_variant_new_int32 (0)),
+ gtk_get_current_event_time (), NULL, NULL);
+}
+
+static GtkMenu *
+layout_update_item (SnDBusMenu *menu,
+ GtkMenu *gtk_menu,
+ guint id,
+ GVariant *props)
+{
+ SnDBusMenuItem *item;
+
+ if (id == 0)
+ return gtk_menu;
+
+ item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id));
+
+ if (item == NULL)
+ {
+ item = sn_dbus_menu_item_new (props);
+
+ g_object_set_data (G_OBJECT (item->item), "item-id", GUINT_TO_POINTER (id));
+ gtk_menu_shell_append (GTK_MENU_SHELL (gtk_menu), item->item);
+
+ item->activate_id = g_signal_connect (item->item, "activate",
+ G_CALLBACK (activate_cb), menu);
+
+ g_hash_table_replace (menu->items, GUINT_TO_POINTER (id), item);
+ }
+ else
+ {
+ sn_dbus_menu_item_update_props (item, props);
+ }
+
+ return item->submenu;
+}
+
+static void
+layout_parse (SnDBusMenu *menu,
+ GVariant *layout,
+ GtkMenu *gtk_menu)
+{
+ guint id;
+ GVariant *props;
+ GVariant *items;
+ GtkMenu *submenu;
+ GVariantIter iter;
+ GVariant *child;
+
+ g_variant_get (layout, "(i@a{sv}@av)", &id, &props, &items);
+
+ submenu = layout_update_item (menu, gtk_menu, id, props);
+ g_variant_unref (props);
+
+ g_variant_iter_init (&iter, items);
+ while ((child = g_variant_iter_next_value (&iter)))
+ {
+ GVariant *value;
+
+ value = g_variant_get_variant (child);
+
+ layout_parse (menu, value, submenu);
+ g_variant_unref (value);
+
+ g_variant_unref (child);
+ }
+
+ g_variant_unref (items);
+}
+
+static void
+get_layout_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GVariant *layout;
+ guint revision;
+ GError *error;
+ SnDBusMenu *menu;
+
+ error = NULL;
+ sn_dbus_menu_gen_call_get_layout_finish (SN_DBUS_MENU_GEN (source_object),
+ &revision, &layout, res, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ menu = SN_DBUS_MENU (user_data);
+
+ if (error != NULL)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_hash_table_remove_all (menu->items);
+ layout_parse (menu, layout, GTK_MENU (menu));
+ g_variant_unref (layout);
+}
+
+static void
+update_layout (SnDBusMenu *menu,
+ gint parent)
+{
+ gint depth;
+
+ parent = 0;
+ depth = -1;
+
+ sn_dbus_menu_gen_call_get_layout (menu->proxy, parent, depth,
+ property_names, menu->cancellable,
+ get_layout_cb, menu);
+}
+
+static void
+items_properties_updated_cb (SnDBusMenuGen *proxy,
+ GVariant *updated_props,
+ GVariant *removed_props,
+ SnDBusMenu *menu)
+{
+ GVariantIter iter;
+ guint id;
+ GVariant *props;
+ SnDBusMenuItem *item;
+
+ g_variant_iter_init (&iter, updated_props);
+ while (g_variant_iter_next (&iter, "(i@a{sv})", &id, &props))
+ {
+ item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id));
+
+ if (item != NULL)
+ sn_dbus_menu_item_update_props (item, props);
+
+ g_variant_unref (props);
+ }
+
+ g_variant_iter_init (&iter, removed_props);
+ while (g_variant_iter_next (&iter, "(i@as)", &id, &props))
+ {
+ item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id));
+
+ if (item != NULL)
+ sn_dbus_menu_item_remove_props (item, props);
+
+ g_variant_unref (props);
+ }
+}
+
+static void
+layout_updated_cb (SnDBusMenuGen *proxy,
+ guint revision,
+ gint parent,
+ SnDBusMenu *menu)
+{
+ update_layout (menu, parent);
+}
+
+static void
+item_activation_requested_cb (SnDBusMenuGen *proxy,
+ gint id,
+ guint timestamp,
+ SnDBusMenu *menu)
+{
+ g_debug ("activation requested: id - %d, timestamp - %d", id, timestamp);
+}
+
+static void
+map_cb (GtkWidget *widget,
+ SnDBusMenu *menu)
+{
+ gboolean need_update;
+
+ sn_dbus_menu_gen_call_event_sync (menu->proxy, 0, "opened",
+ g_variant_new ("v", g_variant_new_int32 (0)),
+ gtk_get_current_event_time (),
+ NULL, NULL);
+
+ sn_dbus_menu_gen_call_about_to_show_sync (menu->proxy, 0, &need_update,
+ NULL, NULL);
+
+ if (need_update)
+ {
+ update_layout (menu, 0);
+ }
+}
+
+static void
+unmap_cb (GtkWidget *widget,
+ SnDBusMenu *menu)
+{
+ sn_dbus_menu_gen_call_event_sync (menu->proxy, 0, "closed",
+ g_variant_new ("v", g_variant_new_int32 (0)),
+ gtk_get_current_event_time (),
+ NULL, NULL);
+}
+
+static void
+proxy_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnDBusMenuGen *proxy;
+ GError *error;
+ SnDBusMenu *menu;
+
+ error = NULL;
+ proxy = sn_dbus_menu_gen_proxy_new_finish (res, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ menu = SN_DBUS_MENU (user_data);
+ menu->proxy = proxy;
+
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_signal_connect (proxy, "items-properties-updated",
+ G_CALLBACK (items_properties_updated_cb), menu);
+
+ g_signal_connect (proxy, "layout-updated",
+ G_CALLBACK (layout_updated_cb), menu);
+
+ g_signal_connect (proxy, "item-activation-requested",
+ G_CALLBACK (item_activation_requested_cb), menu);
+
+ g_signal_connect (menu, "map", G_CALLBACK (map_cb), menu);
+ g_signal_connect (menu, "unmap", G_CALLBACK (unmap_cb), menu);
+
+ update_layout (menu, 0);
+}
+
+static void
+name_appeared_cb (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ SnDBusMenu *menu;
+
+ menu = SN_DBUS_MENU (user_data);
+
+ sn_dbus_menu_gen_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE,
+ menu->bus_name, menu->object_path,
+ menu->cancellable, proxy_ready_cb, menu);
+}
+
+static void
+name_vanished_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ SnDBusMenu *menu;
+
+ menu = SN_DBUS_MENU (user_data);
+
+ g_clear_object (&menu->proxy);
+}
+
+static void
+sn_dbus_menu_constructed (GObject *object)
+{
+ SnDBusMenu *menu;
+
+ G_OBJECT_CLASS (sn_dbus_menu_parent_class)->constructed (object);
+ menu = SN_DBUS_MENU (object);
+
+ menu->name_id = g_bus_watch_name (G_BUS_TYPE_SESSION, menu->bus_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ name_appeared_cb, name_vanished_cb,
+ menu, NULL);
+}
+
+static void
+sn_dbus_menu_dispose (GObject *object)
+{
+ SnDBusMenu *menu;
+
+ menu = SN_DBUS_MENU (object);
+
+ if (menu->name_id > 0)
+ {
+ g_bus_unwatch_name (menu->name_id);
+ menu->name_id = 0;
+ }
+
+ g_clear_pointer (&menu->items, g_hash_table_destroy);
+
+ g_cancellable_cancel (menu->cancellable);
+ g_clear_object (&menu->cancellable);
+ g_clear_object (&menu->proxy);
+
+ G_OBJECT_CLASS (sn_dbus_menu_parent_class)->dispose (object);
+}
+
+static void
+sn_dbus_menu_finalize (GObject *object)
+{
+ SnDBusMenu *menu;
+
+ menu = SN_DBUS_MENU (object);
+
+ g_free (menu->bus_name);
+ g_free (menu->object_path);
+
+ G_OBJECT_CLASS (sn_dbus_menu_parent_class)->finalize (object);
+}
+
+static void
+sn_dbus_menu_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SnDBusMenu *menu;
+
+ menu = SN_DBUS_MENU (object);
+
+ switch (property_id)
+ {
+ case PROP_BUS_NAME:
+ menu->bus_name = g_value_dup_string (value);
+ break;
+
+ case PROP_OBJECT_PATH:
+ menu->object_path = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+install_properties (GObjectClass *object_class)
+{
+ properties[PROP_BUS_NAME] =
+ g_param_spec_string ("bus-name", "bus-name", "bus-name", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_OBJECT_PATH] =
+ g_param_spec_string ("object-path", "object-path", "object-path", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+sn_dbus_menu_class_init (SnDBusMenuClass *menu_class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (menu_class);
+
+ object_class->constructed = sn_dbus_menu_constructed;
+ object_class->dispose = sn_dbus_menu_dispose;
+ object_class->finalize = sn_dbus_menu_finalize;
+ object_class->set_property = sn_dbus_menu_set_property;
+
+ install_properties (object_class);
+}
+
+static void
+sn_dbus_menu_init (SnDBusMenu *menu)
+{
+ menu->items = g_hash_table_new_full (NULL, NULL, NULL, sn_dubs_menu_item_free);
+ menu->cancellable = g_cancellable_new ();
+}
+
+GtkMenu *
+sn_dbus_menu_new (const gchar *bus_name,
+ const gchar *object_path)
+{
+ return g_object_new (SN_TYPE_DBUS_MENU,
+ "bus-name", bus_name,
+ "object-path", object_path,
+ NULL);
+}
diff --git a/applets/notification_area/status-notifier/sn-dbus-menu.h b/applets/notification_area/status-notifier/sn-dbus-menu.h
new file mode 100644
index 00000000..39907d07
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-dbus-menu.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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/>.
+ */
+
+#ifndef SN_DBUS_MENU_H
+#define SN_DBUS_MENU_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SN_TYPE_DBUS_MENU sn_dbus_menu_get_type ()
+G_DECLARE_FINAL_TYPE (SnDBusMenu, sn_dbus_menu, SN, DBUS_MENU, GtkMenu)
+
+GtkMenu *sn_dbus_menu_new (const gchar *bus_name,
+ const gchar *object_path);
+
+G_END_DECLS
+
+#endif
diff --git a/applets/notification_area/status-notifier/sn-host-v0.c b/applets/notification_area/status-notifier/sn-host-v0.c
new file mode 100644
index 00000000..eeab0254
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-host-v0.c
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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 "sn-host-v0.h"
+#include "sn-item-v0.h"
+#include "sn-watcher-v0-gen.h"
+
+#define SN_HOST_BUS_NAME "org.kde.StatusNotifierHost"
+#define SN_HOST_OBJECT_PATH "/StatusNotifierHost"
+#define SN_ITEM_OBJECT_PATH "/StatusNotifierItem"
+#define SN_WATCHER_BUS_NAME "org.kde.StatusNotifierWatcher"
+#define SN_WATCHER_OBJECT_PATH "/StatusNotifierWatcher"
+
+struct _SnHostV0
+{
+ SnHostV0GenSkeleton parent;
+
+ gchar *bus_name;
+ gchar *object_path;
+ guint bus_name_id;
+
+ GCancellable *cancellable;
+
+ guint watcher_id;
+ SnWatcherV0Gen *watcher;
+
+ GSList *items;
+
+ gint icon_padding;
+ gint icon_size;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_ICON_PADDING,
+ PROP_ICON_SIZE,
+
+ LAST_PROP
+};
+
+static void sn_host_v0_gen_init (SnHostV0GenIface *iface);
+static void na_host_init (NaHostInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (SnHostV0, sn_host_v0, SN_TYPE_HOST_V0_GEN_SKELETON,
+ G_IMPLEMENT_INTERFACE (SN_TYPE_HOST_V0_GEN, sn_host_v0_gen_init)
+ G_IMPLEMENT_INTERFACE (NA_TYPE_HOST, na_host_init))
+
+static void
+sn_host_v0_gen_init (SnHostV0GenIface *iface)
+{
+}
+
+static void
+na_host_init (NaHostInterface *iface)
+{
+}
+
+static void
+get_bus_name_and_object_path (const gchar *service,
+ gchar **bus_name,
+ gchar **object_path)
+{
+ gchar *tmp;
+
+ g_assert (*bus_name == NULL);
+ g_assert (*object_path == NULL);
+
+ tmp = g_strstr_len (service, -1, "/");
+ if (tmp != NULL)
+ {
+ gchar **strings;
+
+ strings = g_strsplit (service, "/", 2);
+
+ *bus_name = g_strdup (strings[0]);
+ *object_path = g_strdup (tmp);
+
+ g_strfreev (strings);
+ }
+ else
+ {
+ *bus_name = g_strdup (service);
+ *object_path = g_strdup (SN_ITEM_OBJECT_PATH);
+ }
+}
+
+static void
+ready_cb (SnItem *item,
+ SnHostV0 *v0)
+{
+ na_host_emit_item_added (NA_HOST (v0), NA_ITEM (item));
+}
+
+static void
+add_registered_item (SnHostV0 *v0,
+ const gchar *service)
+{
+ gchar *bus_name;
+ gchar *object_path;
+ SnItem *item;
+
+ bus_name = NULL;
+ object_path = NULL;
+
+ get_bus_name_and_object_path (service, &bus_name, &object_path);
+
+ item = sn_item_v0_new (bus_name, object_path);
+ g_object_ref_sink (item);
+
+ g_object_bind_property (v0, "icon-padding", item, "margin",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ g_object_bind_property (v0, "icon-size", item, "icon-size",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ v0->items = g_slist_prepend (v0->items, item);
+ g_signal_connect (item, "ready", G_CALLBACK (ready_cb), v0);
+
+ g_free (bus_name);
+ g_free (object_path);
+}
+
+static void
+item_registered_cb (SnWatcherV0Gen *watcher,
+ const gchar *service,
+ SnHostV0 *v0)
+{
+ add_registered_item (v0, service);
+}
+
+static void
+item_unregistered_cb (SnWatcherV0Gen *watcher,
+ const gchar *service,
+ SnHostV0 *v0)
+{
+ GSList *l;
+
+ for (l = v0->items; l != NULL; l = g_slist_next (l))
+ {
+ SnItem *item;
+ gboolean found;
+ gchar *bus_name;
+ gchar *object_path;
+
+ item = SN_ITEM (l->data);
+
+ found = FALSE;
+ bus_name = NULL;
+ object_path = NULL;
+
+ get_bus_name_and_object_path (service, &bus_name, &object_path);
+
+ if (g_strcmp0 (sn_item_get_bus_name (item), bus_name) == 0 &&
+ g_strcmp0 (sn_item_get_object_path (item), object_path) == 0)
+ {
+ v0->items = g_slist_remove (v0->items, item);
+ na_host_emit_item_removed (NA_HOST (v0), NA_ITEM (item));
+ g_object_unref (item);
+
+ found = TRUE;
+ }
+
+ g_free (bus_name);
+ g_free (object_path);
+
+ if (found)
+ break;
+ }
+}
+
+static void
+register_host_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ SnHostV0 *v0;
+ gchar **items;
+ gint i;
+
+ error = NULL;
+ sn_watcher_v0_gen_call_register_host_finish (SN_WATCHER_V0_GEN (source_object),
+ res, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ v0 = SN_HOST_V0 (user_data);
+
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+
+ return;
+ }
+
+ g_signal_connect (v0->watcher, "item-registered",
+ G_CALLBACK (item_registered_cb), v0);
+
+ g_signal_connect (v0->watcher, "item-unregistered",
+ G_CALLBACK (item_unregistered_cb), v0);
+
+ items = sn_watcher_v0_gen_dup_registered_items (v0->watcher);
+
+ for (i = 0; items[i] != NULL; i++)
+ add_registered_item (v0, items[i]);
+
+ g_strfreev (items);
+}
+
+static void
+proxy_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ SnWatcherV0Gen *watcher;
+ SnHostV0 *v0;
+
+ error = NULL;
+ watcher = sn_watcher_v0_gen_proxy_new_finish (res, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ v0 = SN_HOST_V0 (user_data);
+ v0->watcher = watcher;
+
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+
+ return;
+ }
+
+ sn_watcher_v0_gen_call_register_host (v0->watcher, v0->object_path,
+ v0->cancellable, register_host_cb, v0);
+}
+
+static void
+name_appeared_cb (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ SnHostV0 *v0;
+
+ v0 = SN_HOST_V0 (user_data);
+
+ g_assert (v0->cancellable == NULL);
+ v0->cancellable = g_cancellable_new ();
+
+ sn_watcher_v0_gen_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE,
+ SN_WATCHER_BUS_NAME, SN_WATCHER_OBJECT_PATH,
+ v0->cancellable, proxy_ready_cb, user_data);
+}
+
+static void
+emit_item_removed_signal (gpointer data,
+ gpointer user_data)
+{
+ na_host_emit_item_removed (NA_HOST (user_data), NA_ITEM (data));
+}
+
+static void
+name_vanished_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ SnHostV0 *v0;
+
+ v0 = SN_HOST_V0 (user_data);
+
+ g_cancellable_cancel (v0->cancellable);
+ g_clear_object (&v0->cancellable);
+
+ g_clear_object (&v0->watcher);
+
+ if (v0->items)
+ {
+ g_slist_foreach (v0->items, emit_item_removed_signal, v0);
+ g_slist_free_full (v0->items, g_object_unref);
+ v0->items = NULL;
+ }
+}
+
+static void
+bus_acquired_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ SnHostV0 *v0;
+ GDBusInterfaceSkeleton *skeleton;
+ GError *error;
+
+ v0 = SN_HOST_V0 (user_data);
+ skeleton = G_DBUS_INTERFACE_SKELETON (v0);
+
+ error = NULL;
+ g_dbus_interface_skeleton_export (skeleton, connection,
+ v0->object_path, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+
+ return;
+ }
+
+ v0->watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION, SN_WATCHER_BUS_NAME,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ name_appeared_cb, name_vanished_cb,
+ v0, NULL);
+}
+
+static void
+sn_host_v0_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SnHostV0 *v0;
+
+ v0 = SN_HOST_V0 (object);
+
+ switch (property_id)
+ {
+ case PROP_ICON_PADDING:
+ g_value_set_int (value, v0->icon_padding);
+ break;
+
+ case PROP_ICON_SIZE:
+ g_value_set_int (value, v0->icon_size);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sn_host_v0_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SnHostV0 *v0;
+
+ v0 = SN_HOST_V0 (object);
+
+ switch (property_id)
+ {
+ case PROP_ICON_PADDING:
+ v0->icon_padding = g_value_get_int (value);
+ break;
+
+ case PROP_ICON_SIZE:
+ v0->icon_size = 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)
+{
+ /* NaHost properties */
+ g_object_class_override_property (object_class, PROP_ICON_PADDING, "icon-padding");
+ g_object_class_override_property (object_class, PROP_ICON_SIZE, "icon-size");
+}
+
+static void
+sn_host_v0_dispose (GObject *object)
+{
+ SnHostV0 *v0;
+
+ v0 = SN_HOST_V0 (object);
+
+ if (v0->bus_name_id > 0)
+ {
+ g_bus_unown_name (v0->bus_name_id);
+ v0->bus_name_id = 0;
+ }
+
+ if (v0->watcher_id > 0)
+ {
+ g_bus_unwatch_name (v0->watcher_id);
+ v0->watcher_id = 0;
+ }
+
+ g_cancellable_cancel (v0->cancellable);
+ g_clear_object (&v0->cancellable);
+
+ g_clear_object (&v0->watcher);
+
+ if (v0->items)
+ {
+ g_slist_foreach (v0->items, emit_item_removed_signal, v0);
+ g_slist_free_full (v0->items, g_object_unref);
+ v0->items = NULL;
+ }
+
+ G_OBJECT_CLASS (sn_host_v0_parent_class)->dispose (object);
+}
+
+static void
+sn_host_v0_finalize (GObject *object)
+{
+ SnHostV0 *v0;
+
+ v0 = SN_HOST_V0 (object);
+
+ g_clear_pointer (&v0->bus_name, g_free);
+ g_clear_pointer (&v0->object_path, g_free);
+
+ G_OBJECT_CLASS (sn_host_v0_parent_class)->finalize (object);
+}
+
+static void
+sn_host_v0_class_init (SnHostV0Class *v0_class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (v0_class);
+
+ object_class->dispose = sn_host_v0_dispose;
+ object_class->finalize = sn_host_v0_finalize;
+ object_class->get_property = sn_host_v0_get_property;
+ object_class->set_property = sn_host_v0_set_property;
+
+ install_properties (object_class);
+}
+
+static void
+sn_host_v0_init (SnHostV0 *v0)
+{
+ GBusNameOwnerFlags flags;
+ static guint id;
+
+ flags = G_BUS_NAME_OWNER_FLAGS_NONE;
+ id++;
+
+ v0->bus_name = g_strdup_printf ("%s-%d-%d", SN_HOST_BUS_NAME, getpid (), id);
+ v0->object_path = g_strdup_printf ("%s/%d", SN_HOST_OBJECT_PATH,id);
+
+ v0->bus_name_id = g_bus_own_name (G_BUS_TYPE_SESSION, v0->bus_name, flags,
+ bus_acquired_cb, NULL, NULL, v0, NULL);
+
+ v0->icon_size = 16;
+ v0->icon_padding = 0;
+}
+
+NaHost *
+sn_host_v0_new (void)
+{
+ return g_object_new (SN_TYPE_HOST_V0, NULL);
+}
diff --git a/applets/notification_area/status-notifier/sn-host-v0.h b/applets/notification_area/status-notifier/sn-host-v0.h
new file mode 100644
index 00000000..61c8b5c3
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-host-v0.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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/>.
+ */
+
+#ifndef SN_HOST_V0_H
+#define SN_HOST_V0_H
+
+#include "na-host.h"
+#include "sn-host-v0-gen.h"
+
+G_BEGIN_DECLS
+
+#define SN_TYPE_HOST_V0 sn_host_v0_get_type ()
+G_DECLARE_FINAL_TYPE (SnHostV0, sn_host_v0, SN, HOST_V0, SnHostV0GenSkeleton)
+
+NaHost *sn_host_v0_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/applets/notification_area/status-notifier/sn-image-menu-item.c b/applets/notification_area/status-notifier/sn-image-menu-item.c
new file mode 100644
index 00000000..2246b961
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-image-menu-item.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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 "sn-image-menu-item.h"
+
+#define SPACING 6
+
+struct _SnImageMenuItem
+{
+ GtkMenuItem parent;
+
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *accel_label;
+
+ gchar *label;
+};
+
+G_DEFINE_TYPE (SnImageMenuItem, sn_image_menu_item, GTK_TYPE_MENU_ITEM)
+
+static void
+sn_image_menu_item_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ GtkWidgetClass *widget_class;
+ SnImageMenuItem *item;
+ GtkRequisition image_requisition;
+
+ widget_class = GTK_WIDGET_CLASS (sn_image_menu_item_parent_class);
+ item = SN_IMAGE_MENU_ITEM (widget);
+
+ widget_class->get_preferred_width (widget, minimum, natural);
+
+ if (!gtk_widget_get_visible (item->image))
+ return;
+
+ gtk_widget_get_preferred_size (item->image, &image_requisition, NULL);
+
+ if (image_requisition.width > 0)
+ {
+ *minimum -= image_requisition.width + SPACING;
+ *natural -= image_requisition.width + SPACING;
+ }
+}
+
+static void
+sn_image_menu_item_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkWidgetClass *widget_class;
+ SnImageMenuItem *item;
+ GtkRequisition image_requisition;
+ GtkAllocation box_allocation;
+
+ widget_class = GTK_WIDGET_CLASS (sn_image_menu_item_parent_class);
+ item = SN_IMAGE_MENU_ITEM (widget);
+
+ widget_class->size_allocate (widget, allocation);
+
+ if (!gtk_widget_get_visible (item->image))
+ return;
+
+ gtk_widget_get_preferred_size (item->image, &image_requisition, NULL);
+ gtk_widget_get_allocation (item->box, &box_allocation);
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
+ {
+ if (image_requisition.width > 0)
+ box_allocation.x -= image_requisition.width + SPACING;
+ }
+ else
+ {
+ if (image_requisition.width > 0)
+ box_allocation.x += image_requisition.width + SPACING;
+ }
+
+ gtk_widget_size_allocate (item->box, &box_allocation);
+}
+
+static const gchar *
+sn_image_menu_item_get_label (GtkMenuItem *menu_item)
+{
+ SnImageMenuItem *item;
+
+ item = SN_IMAGE_MENU_ITEM (menu_item);
+
+ return item->label;
+}
+
+static void
+sn_image_menu_item_toggle_size_request (GtkMenuItem *menu_item,
+ gint *requisition)
+{
+ SnImageMenuItem *item;
+ GtkRequisition image_requisition;
+
+ item = SN_IMAGE_MENU_ITEM (menu_item);
+
+ *requisition = 0;
+
+ if (!gtk_widget_get_visible (item->image))
+ return;
+
+ gtk_widget_get_preferred_size (item->image, &image_requisition, NULL);
+
+ if (image_requisition.width > 0)
+ *requisition = image_requisition.width + SPACING;
+}
+
+static void
+sn_image_menu_item_set_label (GtkMenuItem *menu_item,
+ const gchar *label)
+{
+ SnImageMenuItem *item;
+
+ item = SN_IMAGE_MENU_ITEM (menu_item);
+
+ if (g_strcmp0 (item->label, label) != 0)
+ {
+ g_free (item->label);
+ item->label = g_strdup (label);
+
+ gtk_label_set_text_with_mnemonic (GTK_LABEL (item->accel_label), label);
+ g_object_notify (G_OBJECT (menu_item), "label");
+ }
+}
+
+static void
+sn_image_menu_item_class_init (SnImageMenuItemClass *item_class)
+{
+ GtkWidgetClass *widget_class;
+ GtkMenuItemClass *menu_item_class;
+
+ widget_class = GTK_WIDGET_CLASS (item_class);
+ menu_item_class = GTK_MENU_ITEM_CLASS (item_class);
+
+ widget_class->get_preferred_width = sn_image_menu_item_get_preferred_width;
+ widget_class->size_allocate = sn_image_menu_item_size_allocate;
+
+ menu_item_class->get_label = sn_image_menu_item_get_label;
+ menu_item_class->toggle_size_request = sn_image_menu_item_toggle_size_request;
+ menu_item_class->set_label = sn_image_menu_item_set_label;
+}
+
+static void
+sn_image_menu_item_init (SnImageMenuItem *item)
+{
+ GtkAccelLabel *accel_label;
+
+ item->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, SPACING);
+ gtk_container_add (GTK_CONTAINER (item), item->box);
+ gtk_widget_show (item->box);
+
+ item->image = gtk_image_new ();
+ gtk_box_pack_start (GTK_BOX (item->box), item->image, FALSE, FALSE, 0);
+
+ item->accel_label = gtk_accel_label_new ("");
+ gtk_box_pack_end (GTK_BOX (item->box), item->accel_label, TRUE, TRUE, 0);
+ gtk_label_set_xalign (GTK_LABEL (item->accel_label), 0.0);
+ gtk_widget_show (item->accel_label);
+
+ accel_label = GTK_ACCEL_LABEL (item->accel_label);
+ gtk_accel_label_set_accel_widget (accel_label, GTK_WIDGET (item));
+ gtk_label_set_use_underline (GTK_LABEL (accel_label), TRUE);
+}
+
+GtkWidget *
+sn_image_menu_item_new (void)
+{
+ return g_object_new (SN_TYPE_IMAGE_MENU_ITEM, NULL);
+}
+
+void
+sn_image_menu_item_set_image_from_icon_name (SnImageMenuItem *item,
+ const gchar *icon_name)
+{
+ GtkImage *image;
+
+ image = GTK_IMAGE (item->image);
+
+ gtk_image_set_from_icon_name (image, icon_name, GTK_ICON_SIZE_MENU);
+ gtk_image_set_pixel_size (image, 16);
+ gtk_widget_show (item->image);
+}
+
+void
+sn_image_menu_item_set_image_from_icon_pixbuf (SnImageMenuItem *item,
+ GdkPixbuf *pixbuf)
+{
+ gtk_image_set_from_pixbuf (GTK_IMAGE (item->image), pixbuf);
+ gtk_widget_show (item->image);
+}
+
+void
+sn_image_menu_item_unset_image (SnImageMenuItem *item)
+{
+ gtk_image_clear (GTK_IMAGE (item->image));
+ gtk_widget_hide (item->image);
+}
diff --git a/applets/notification_area/status-notifier/sn-image-menu-item.h b/applets/notification_area/status-notifier/sn-image-menu-item.h
new file mode 100644
index 00000000..b0fe8a90
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-image-menu-item.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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/>.
+ */
+
+#ifndef SN_IMAGE_MENU_ITEM_H
+#define SN_IMAGE_MENU_ITEM_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SN_TYPE_IMAGE_MENU_ITEM sn_image_menu_item_get_type ()
+G_DECLARE_FINAL_TYPE (SnImageMenuItem, sn_image_menu_item,
+ SN, IMAGE_MENU_ITEM, GtkMenuItem)
+
+GtkWidget *sn_image_menu_item_new (void);
+
+void sn_image_menu_item_set_image_from_icon_name (SnImageMenuItem *item,
+ const gchar *icon_name);
+
+void sn_image_menu_item_set_image_from_icon_pixbuf (SnImageMenuItem *item,
+ GdkPixbuf *pixbuf);
+
+void sn_image_menu_item_unset_image (SnImageMenuItem *item);
+
+G_END_DECLS
+
+#endif
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);
+ }
+}
diff --git a/applets/notification_area/status-notifier/sn-item-v0.h b/applets/notification_area/status-notifier/sn-item-v0.h
new file mode 100644
index 00000000..4b1bd492
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-item-v0.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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/>.
+ */
+
+#ifndef SN_ITEM_V0_H
+#define SN_ITEM_V0_H
+
+#include "sn-item.h"
+
+G_BEGIN_DECLS
+
+#define SN_TYPE_ITEM_V0 sn_item_v0_get_type ()
+G_DECLARE_FINAL_TYPE (SnItemV0, sn_item_v0, SN, ITEM_V0, SnItem)
+
+SnItem *sn_item_v0_new (const gchar *bus_name,
+ const gchar *object_path);
+
+gint sn_item_v0_get_icon_size (SnItemV0 *v0);
+void sn_item_v0_set_icon_size (SnItemV0 *v0,
+ gint size);
+
+G_END_DECLS
+
+#endif
diff --git a/applets/notification_area/status-notifier/sn-item.c b/applets/notification_area/status-notifier/sn-item.c
new file mode 100644
index 00000000..13ee68f3
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-item.c
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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 "sn-dbus-menu.h"
+#include "sn-item.h"
+
+#include "na-item.h"
+
+typedef struct
+{
+ gchar *bus_name;
+ gchar *object_path;
+
+ GtkOrientation orientation;
+
+ GtkMenu *menu;
+} SnItemPrivate;
+
+enum
+{
+ PROP_0,
+
+ PROP_BUS_NAME,
+ PROP_OBJECT_PATH,
+
+ PROP_ORIENTATION,
+
+ LAST_PROP
+};
+
+enum
+{
+ SIGNAL_READY,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void na_item_init (NaItemInterface *iface);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (SnItem, sn_item, GTK_TYPE_BUTTON,
+ //~ G_ADD_PRIVATE (SnItem);
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
+ NULL)
+ G_IMPLEMENT_INTERFACE (NA_TYPE_ITEM,
+ na_item_init))
+
+/* FIXME: apparently if requesting < 2.38, G_ADD_PRIVATE() or the default
+ * sn_item_get_instance_private() does NOT work, and returns some garbage
+ * (either get_instance_private() works but G_ADD_PRIVATE() didn't do nothing,
+ * or the other way around, but it leads to using incorrect memory) */
+#define sn_item_get_instance_private(i) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((i), SN_TYPE_ITEM, SnItemPrivate))
+
+static void
+sn_item_dispose (GObject *object)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+
+ item = SN_ITEM (object);
+ priv = sn_item_get_instance_private (item);
+
+ g_clear_object (&priv->menu);
+
+ G_OBJECT_CLASS (sn_item_parent_class)->dispose (object);
+}
+
+static void
+sn_item_finalize (GObject *object)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+
+ item = SN_ITEM (object);
+ priv = sn_item_get_instance_private (item);
+
+ g_clear_pointer (&priv->bus_name, g_free);
+ g_clear_pointer (&priv->object_path, g_free);
+
+ G_OBJECT_CLASS (sn_item_parent_class)->finalize (object);
+}
+
+static void
+sn_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+
+ item = SN_ITEM (object);
+ priv = sn_item_get_instance_private (item);
+
+ switch (property_id)
+ {
+ case PROP_BUS_NAME:
+ g_value_set_string (value, priv->bus_name);
+ break;
+
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, priv->orientation);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sn_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+
+ item = SN_ITEM (object);
+ priv = sn_item_get_instance_private (item);
+
+ switch (property_id)
+ {
+ case PROP_BUS_NAME:
+ priv->bus_name = g_value_dup_string (value);
+ break;
+
+ case PROP_OBJECT_PATH:
+ priv->object_path = g_value_dup_string (value);
+ break;
+
+ case PROP_ORIENTATION:
+ priv->orientation = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sn_item_get_action_coordinates (SnItem *item,
+ gint *x,
+ gint *y)
+{
+ GtkWidget *widget;
+ SnItemPrivate *priv;
+ GdkWindow *window;
+ GtkWidget *toplevel;
+ gint width;
+ gint height;
+
+ priv = sn_item_get_instance_private (item);
+ widget = GTK_WIDGET (item);
+ window = gtk_widget_get_window (widget);
+ toplevel = gtk_widget_get_toplevel (widget);
+
+ gdk_window_get_geometry (window, x, y, &width, &height);
+ gtk_widget_translate_coordinates (widget, toplevel, *x, *y, x, y);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ *y += height;
+ else
+ *x += width;
+}
+
+static gboolean
+sn_item_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+ GdkDisplay *display;
+ GdkSeat *seat;
+ gint x;
+ gint y;
+
+ if (event->button < 2 || event->button > 3)
+ return GTK_WIDGET_CLASS (sn_item_parent_class)->button_press_event (widget, event);
+
+ item = SN_ITEM (widget);
+ priv = sn_item_get_instance_private (item);
+ display = gdk_display_get_default ();
+ seat = gdk_display_get_default_seat (display);
+
+ sn_item_get_action_coordinates (item, &x, &y);
+
+ if (event->button == 2)
+ {
+ gdk_seat_ungrab (seat);
+ SN_ITEM_GET_CLASS (item)->secondary_activate (item, x, y);
+ }
+ else if (event->button == 3)
+ {
+ if (priv->menu != NULL)
+ {
+ gtk_menu_popup_at_widget (priv->menu, widget,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_NORTH_WEST,
+ (GdkEvent *) event);
+ }
+ else
+ {
+ gdk_seat_ungrab (seat);
+ SN_ITEM_GET_CLASS (item)->context_menu (item, x, y);
+ }
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ return GTK_WIDGET_CLASS (sn_item_parent_class)->button_press_event (widget, event);
+}
+
+static gboolean
+sn_item_popup_menu (GtkWidget *widget)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+
+ item = SN_ITEM (widget);
+ priv = sn_item_get_instance_private (item);
+
+ if (priv->menu != NULL)
+ {
+ gtk_menu_popup_at_widget (priv->menu, widget,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_NORTH_WEST,
+ NULL);
+ }
+ else
+ {
+ gint x;
+ gint y;
+
+ sn_item_get_action_coordinates (item, &x, &y);
+
+ SN_ITEM_GET_CLASS (item)->context_menu (item, x, y);
+ }
+
+ return TRUE;
+}
+
+static void
+sn_item_clicked (GtkButton *button)
+{
+ SnItem *item;
+ gint x;
+ gint y;
+
+ item = SN_ITEM (button);
+
+ sn_item_get_action_coordinates (item, &x, &y);
+
+ SN_ITEM_GET_CLASS (item)->activate (item, x, y);
+}
+
+static gboolean
+sn_item_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ SnItem *item;
+ GdkScrollDirection direction;
+ SnItemOrientation orientation;
+ gdouble dx;
+ gdouble dy;
+ gint delta;
+
+ item = SN_ITEM (widget);
+
+ if (!gdk_event_get_scroll_direction ((GdkEvent *) event, &direction))
+ {
+ g_assert_not_reached ();
+ }
+ else
+ {
+ switch (direction)
+ {
+ case GDK_SCROLL_UP:
+ case GDK_SCROLL_DOWN:
+ orientation = SN_ITEM_ORIENTATION_VERTICAL;
+ break;
+
+ case GDK_SCROLL_LEFT:
+ case GDK_SCROLL_RIGHT:
+ orientation = SN_ITEM_ORIENTATION_HORIZONTAL;
+ break;
+
+ case GDK_SCROLL_SMOOTH:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ if (!gdk_event_get_scroll_deltas ((GdkEvent *) event, &dx, &dy))
+ {
+ switch (direction)
+ {
+ case GDK_SCROLL_UP:
+ case GDK_SCROLL_LEFT:
+ delta = 1;
+ break;
+
+ case GDK_SCROLL_DOWN:
+ case GDK_SCROLL_RIGHT:
+ delta = -1;
+ break;
+
+ case GDK_SCROLL_SMOOTH:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ else
+ {
+ if (dy != 0)
+ delta = (gint) dy;
+ else
+ delta = (gint) dx;
+ }
+
+ SN_ITEM_GET_CLASS (item)->scroll (item, delta, orientation);
+
+ return GDK_EVENT_STOP;
+}
+
+static void
+sn_item_ready (SnItem *item)
+{
+ const gchar *menu;
+ SnItemPrivate *priv;
+
+ menu = SN_ITEM_GET_CLASS (item)->get_menu (item);
+ if (menu == NULL)
+ return;
+
+ if (menu == NULL || *menu == '\0' || g_strcmp0 (menu, "/") == 0)
+ return;
+
+ priv = sn_item_get_instance_private (item);
+ priv->menu = sn_dbus_menu_new (priv->bus_name, menu);
+ g_object_ref_sink (priv->menu);
+}
+
+static const gchar *
+sn_item_get_id (NaItem *item)
+{
+ return SN_ITEM_GET_CLASS (item)->get_id (SN_ITEM (item));
+}
+
+static NaItemCategory
+sn_item_get_category (NaItem *item)
+{
+ const gchar *string;
+ NaItemCategory category;
+
+ string = SN_ITEM_GET_CLASS (item)->get_category (SN_ITEM (item));
+
+ if (g_strcmp0 (string, "Hardware") == 0)
+ category = NA_ITEM_CATEGORY_HARDWARE;
+ else if (g_strcmp0 (string, "SystemServices") == 0)
+ category = NA_ITEM_CATEGORY_SYSTEM_SERVICES;
+ else if (g_strcmp0 (string, "Communications") == 0)
+ category = NA_ITEM_CATEGORY_COMMUNICATIONS;
+ else
+ category = NA_ITEM_CATEGORY_APPLICATION_STATUS;
+
+ return category;
+}
+
+static void
+na_item_init (NaItemInterface *iface)
+{
+ iface->get_id = sn_item_get_id;
+ iface->get_category = sn_item_get_category;
+}
+
+static void
+install_properties (GObjectClass *object_class)
+{
+ g_object_class_install_property (object_class, PROP_BUS_NAME,
+ g_param_spec_string ("bus-name", "bus-name", "bus-name", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH,
+ g_param_spec_string ("object-path", "object-path", "object-path", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_override_property (object_class, PROP_ORIENTATION,
+ "orientation");
+}
+
+static void
+install_signals (SnItemClass *item_class)
+{
+ signals[SIGNAL_READY] =
+ g_signal_new ("ready", G_TYPE_FROM_CLASS (item_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (SnItemClass, ready), NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+sn_item_class_init (SnItemClass *item_class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkButtonClass *button_class;
+
+ object_class = G_OBJECT_CLASS (item_class);
+ widget_class = GTK_WIDGET_CLASS (item_class);
+ button_class = GTK_BUTTON_CLASS (item_class);
+
+ object_class->dispose = sn_item_dispose;
+ object_class->finalize = sn_item_finalize;
+ object_class->get_property = sn_item_get_property;
+ object_class->set_property = sn_item_set_property;
+
+ widget_class->button_press_event = sn_item_button_press_event;
+ widget_class->popup_menu = sn_item_popup_menu;
+ widget_class->scroll_event = sn_item_scroll_event;
+
+ button_class->clicked = sn_item_clicked;
+
+ item_class->ready = sn_item_ready;
+
+ install_properties (object_class);
+ install_signals (item_class);
+
+ g_type_class_add_private (item_class, sizeof (SnItemPrivate));
+}
+
+static void
+sn_item_init (SnItem *item)
+{
+ gtk_widget_add_events (GTK_WIDGET (item), GDK_SCROLL_MASK);
+}
+
+const gchar *
+sn_item_get_bus_name (SnItem *item)
+{
+ SnItemPrivate *priv;
+
+ priv = sn_item_get_instance_private (item);
+
+ return priv->bus_name;
+}
+
+const gchar *
+sn_item_get_object_path (SnItem *item)
+{
+ SnItemPrivate *priv;
+
+ priv = sn_item_get_instance_private (item);
+
+ return priv->object_path;
+}
+
+void
+sn_item_emit_ready (SnItem *item)
+{
+ g_signal_emit (item, signals[SIGNAL_READY], 0);
+}
diff --git a/applets/notification_area/status-notifier/sn-item.h b/applets/notification_area/status-notifier/sn-item.h
new file mode 100644
index 00000000..c414b942
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-item.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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/>.
+ */
+
+#ifndef SN_ITEM_H
+#define SN_ITEM_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SN_TYPE_ITEM sn_item_get_type ()
+G_DECLARE_DERIVABLE_TYPE (SnItem, sn_item, SN, ITEM, GtkButton)
+
+typedef enum
+{
+ SN_ITEM_ORIENTATION_HORIZONTAL,
+ SN_ITEM_ORIENTATION_VERTICAL
+} SnItemOrientation;
+
+struct _SnItemClass
+{
+ GtkButtonClass parent_class;
+
+ void (* ready) (SnItem *item);
+
+ const gchar * (* get_id) (SnItem *item);
+
+ const gchar * (* get_category) (SnItem *item);
+
+ const gchar * (* get_menu) (SnItem *item);
+
+ void (* context_menu) (SnItem *item,
+ gint x,
+ gint y);
+
+ void (* activate) (SnItem *item,
+ gint x,
+ gint y);
+
+ void (* secondary_activate) (SnItem *item,
+ gint x,
+ gint y);
+
+ void (* scroll) (SnItem *item,
+ gint delta,
+ SnItemOrientation orientation);
+};
+
+const gchar *sn_item_get_bus_name (SnItem *item);
+const gchar *sn_item_get_object_path (SnItem *item);
+
+void sn_item_emit_ready (SnItem *item);
+
+G_END_DECLS
+
+#endif
diff --git a/applets/notification_area/system-tray/Makefile.am b/applets/notification_area/system-tray/Makefile.am
new file mode 100644
index 00000000..f3e8c360
--- /dev/null
+++ b/applets/notification_area/system-tray/Makefile.am
@@ -0,0 +1,42 @@
+
+noinst_LTLIBRARIES = libsystem-tray.la
+
+AM_CPPFLAGS = \
+ $(NOTIFICATION_AREA_CFLAGS) \
+ -I$(srcdir) \
+ -I$(srcdir)/.. \
+ -DMATELOCALEDIR=\""$(datadir)/locale"\" \
+ -DG_LOG_DOMAIN=\""notification-area-applet"\" \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+AM_CFLAGS = $(WARN_CFLAGS)
+
+libsystem_tray_la_SOURCES = \
+ fixedtip.h \
+ fixedtip.c \
+ na-marshal.c \
+ na-marshal.h \
+ na-tray.c \
+ na-tray.h \
+ na-tray-child.c \
+ na-tray-child.h \
+ na-tray-manager.c \
+ na-tray-manager.h
+
+libsystem_tray_la_LIBADD = \
+ $(X_LIBS) \
+ $(NOTIFICATION_AREA_LIBS)
+
+na-marshal.h: na-marshal.list $(GLIB_GENMARSHAL)
+ $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --header --prefix=_na_marshal > $@
+
+na-marshal.c: na-marshal.list $(GLIB_GENMARSHAL)
+ $(AM_V_GEN)echo "#include \"na-marshal.h\"" > $@ && \
+ $(GLIB_GENMARSHAL) $< --body --prefix=_na_marshal >> $@
+
+BUILT_SOURCES = na-marshal.c na-marshal.h
+
+EXTRA_DIST = \
+ na-marshal.list
+
+-include $(top_srcdir)/git.mk
diff --git a/applets/notification_area/fixedtip.c b/applets/notification_area/system-tray/fixedtip.c
index 6356de9b..6356de9b 100644
--- a/applets/notification_area/fixedtip.c
+++ b/applets/notification_area/system-tray/fixedtip.c
diff --git a/applets/notification_area/fixedtip.h b/applets/notification_area/system-tray/fixedtip.h
index c220f103..c220f103 100644
--- a/applets/notification_area/fixedtip.h
+++ b/applets/notification_area/system-tray/fixedtip.h
diff --git a/applets/notification_area/na-marshal.list b/applets/notification_area/system-tray/na-marshal.list
index e3fc3993..e3fc3993 100644
--- a/applets/notification_area/na-marshal.list
+++ b/applets/notification_area/system-tray/na-marshal.list
diff --git a/applets/notification_area/na-tray-child.c b/applets/notification_area/system-tray/na-tray-child.c
index 64824a74..2a87f1e1 100644
--- a/applets/notification_area/na-tray-child.c
+++ b/applets/notification_area/system-tray/na-tray-child.c
@@ -30,11 +30,27 @@
#include <gdk/gdkx.h>
#include <X11/Xatom.h>
-G_DEFINE_TYPE (NaTrayChild, na_tray_child, GTK_TYPE_SOCKET)
+#include "na-item.h"
+
+enum
+{
+ PROP_0,
+ PROP_ORIENTATION
+};
+
+static void na_item_init (NaItemInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (NaTrayChild, na_tray_child, GTK_TYPE_SOCKET,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
+ G_IMPLEMENT_INTERFACE (NA_TYPE_ITEM, na_item_init))
static void
na_tray_child_finalize (GObject *object)
{
+ NaTrayChild *child = NA_TRAY_CHILD (object);
+
+ g_clear_pointer (&child->id, g_free);
+
G_OBJECT_CLASS (na_tray_child_parent_class)->finalize (object);
}
@@ -223,9 +239,164 @@ na_tray_child_draw (GtkWidget *widget,
return FALSE;
}
+/* Children with alpha channels have been set to be composited by calling
+ * gdk_window_set_composited(). We need to paint these children ourselves.
+ *
+ * FIXME: is that still needed on GTK3? Seems like it could be done in draw().
+ */
+static gboolean
+na_tray_child_draw_on_parent (NaItem *item,
+ GtkWidget *parent,
+ cairo_t *parent_cr)
+{
+ if (na_tray_child_has_alpha (NA_TRAY_CHILD (item)))
+ {
+ GtkWidget *widget = GTK_WIDGET (item);
+ GtkAllocation parent_allocation = { 0 };
+ GtkAllocation allocation;
+
+ /* if the parent doesn't have a window, our allocation is not relative to
+ * the context coordinates but to the parent's allocation */
+ if (! gtk_widget_get_has_window (parent))
+ gtk_widget_get_allocation (parent, &parent_allocation);
+
+ gtk_widget_get_allocation (widget, &allocation);
+ allocation.x -= parent_allocation.x;
+ allocation.y -= parent_allocation.y;
+
+ cairo_save (parent_cr);
+ gdk_cairo_set_source_window (parent_cr,
+ gtk_widget_get_window (widget),
+ allocation.x,
+ allocation.y);
+ cairo_rectangle (parent_cr, allocation.x, allocation.y, allocation.width, allocation.height);
+ cairo_clip (parent_cr);
+ cairo_paint (parent_cr);
+ cairo_restore (parent_cr);
+ }
+
+ return TRUE;
+}
+
+static void
+na_tray_child_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ /* whatever */
+ g_value_set_enum (value, GTK_ORIENTATION_HORIZONTAL);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+na_tray_child_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ /* we so don't care */
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static const gchar *
+na_tray_child_get_id (NaItem *item)
+{
+ NaTrayChild *child = NA_TRAY_CHILD (item);
+
+ if (! child->id)
+ {
+ char *res_name = NULL;
+
+ na_tray_child_get_wm_class (child, &res_name, NULL);
+ child->id = g_strdup_printf ("%s-%lu", res_name, child->icon_window);
+ g_free (res_name);
+ }
+
+ return child->id;
+}
+
+static NaItemCategory
+na_tray_child_get_category (NaItem *item)
+{
+ const struct
+ {
+ const gchar *wm_class;
+ NaItemCategory category;
+ } wmclass_categories[] = {
+/* FIXME: get the same order as it used to be, without fake categories.
+ * from right to left:
+const char *ordered_roles[] = {
+ "keyboard",
+ "volume",
+ "bluetooth",
+ "network",
+ "battery",
+ NULL
+};
+
+const char *wmclass_roles[] = {
+ "Bluetooth-applet", "bluetooth",
+ "Mate-volume-control-applet", "volume",
+ "Nm-applet", "network",
+ "Mate-power-manager", "battery",
+ "keyboard", "keyboard",
+ NULL,
+};
+*/
+ { "Bluetooth-applet", NA_ITEM_CATEGORY_FAKE_BLUETOOTH },
+ { "Mate-volume-control-applet", NA_ITEM_CATEGORY_FAKE_VOLUME },
+ { "Nm-applet", NA_ITEM_CATEGORY_FAKE_NETWORK },
+ { "Mate-power-manager", NA_ITEM_CATEGORY_FAKE_BATTERY },
+ { "keyboard", NA_ITEM_CATEGORY_FAKE_KEYBOARD }
+ };
+ guint i;
+ NaItemCategory category = NA_ITEM_CATEGORY_APPLICATION_STATUS;
+ char *res_class = NULL;
+
+ na_tray_child_get_wm_class (NA_TRAY_CHILD (item), NULL, &res_class);
+
+ for (i = 0; i < G_N_ELEMENTS (wmclass_categories); i++)
+ {
+ if (g_strcmp0 (res_class, wmclass_categories[i].wm_class) == 0)
+ {
+ category = wmclass_categories[i].category;
+ break;
+ }
+ }
+
+ return category;
+}
+
+static void
+na_item_init (NaItemInterface *iface)
+{
+ iface->get_id = na_tray_child_get_id;
+ iface->get_category = na_tray_child_get_category;
+
+ iface->draw_on_parent = na_tray_child_draw_on_parent;
+}
+
static void
na_tray_child_init (NaTrayChild *child)
{
+ child->id = NULL;
}
static void
@@ -238,12 +409,18 @@ na_tray_child_class_init (NaTrayChildClass *klass)
widget_class = (GtkWidgetClass *)klass;
gobject_class->finalize = na_tray_child_finalize;
+ gobject_class->get_property = na_tray_child_get_property;
+ gobject_class->set_property = na_tray_child_set_property;
+
widget_class->style_set = na_tray_child_style_set;
widget_class->realize = na_tray_child_realize;
widget_class->get_preferred_width = na_tray_child_get_preferred_width;
widget_class->get_preferred_height = na_tray_child_get_preferred_height;
widget_class->size_allocate = na_tray_child_size_allocate;
widget_class->draw = na_tray_child_draw;
+
+ /* we don't really care actually */
+ g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
}
GtkWidget *
diff --git a/applets/notification_area/na-tray-child.h b/applets/notification_area/system-tray/na-tray-child.h
index 2ddfed9e..6641fe88 100644
--- a/applets/notification_area/na-tray-child.h
+++ b/applets/notification_area/system-tray/na-tray-child.h
@@ -49,6 +49,8 @@ struct _NaTrayChild
guint has_alpha : 1;
guint composited : 1;
guint parent_relative_bg : 1;
+
+ gchar *id;
};
struct _NaTrayChildClass
diff --git a/applets/notification_area/na-tray-manager.c b/applets/notification_area/system-tray/na-tray-manager.c
index 94d3003d..94d3003d 100644
--- a/applets/notification_area/na-tray-manager.c
+++ b/applets/notification_area/system-tray/na-tray-manager.c
diff --git a/applets/notification_area/na-tray-manager.h b/applets/notification_area/system-tray/na-tray-manager.h
index 811791be..811791be 100644
--- a/applets/notification_area/na-tray-manager.h
+++ b/applets/notification_area/system-tray/na-tray-manager.h
diff --git a/applets/notification_area/na-tray.c b/applets/notification_area/system-tray/na-tray.c
index 34fb435e..c2488ecf 100644
--- a/applets/notification_area/na-tray.c
+++ b/applets/notification_area/system-tray/na-tray.c
@@ -2,6 +2,7 @@
* Copyright (C) 2002 Red Hat, Inc.
* Copyright (C) 2003-2006 Vincent Untz
* Copyright (C) 2007 Christian Persch
+ * 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
@@ -22,8 +23,6 @@
#include <config.h>
#include <string.h>
-#include <mate-panel-applet.h>
-
#include <gtk/gtk.h>
#include "na-tray-manager.h"
@@ -31,9 +30,6 @@
#include "na-tray.h"
-#define ICON_SPACING 1
-#define MIN_BOX_SIZE 3
-
typedef struct
{
NaTrayManager *tray_manager;
@@ -47,11 +43,11 @@ struct _NaTrayPrivate
GdkScreen *screen;
TraysScreen *trays_screen;
- GtkWidget *box;
-
guint idle_redraw_id;
GtkOrientation orientation;
+ gint icon_padding;
+ gint icon_size;
};
typedef struct
@@ -75,6 +71,8 @@ enum
{
PROP_0,
PROP_ORIENTATION,
+ PROP_ICON_PADDING,
+ PROP_ICON_SIZE,
PROP_SCREEN
};
@@ -84,8 +82,21 @@ static TraysScreen *trays_screens = NULL;
static void icon_tip_show_next (IconTip *icontip);
/* NaTray */
+static void na_host_init (NaHostInterface *iface);
+static void na_tray_style_updated (NaHost *host,
+ GtkStyleContext *context);
+static void na_tray_force_redraw (NaHost *host);
-G_DEFINE_TYPE (NaTray, na_tray, GTK_TYPE_BIN)
+G_DEFINE_TYPE_WITH_CODE (NaTray, na_tray, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
+ G_IMPLEMENT_INTERFACE (NA_TYPE_HOST, na_host_init))
+
+static void
+na_host_init (NaHostInterface *iface)
+{
+ iface->force_redraw = na_tray_force_redraw;
+ iface->style_updated = na_tray_style_updated;
+}
static NaTray *
get_tray (TraysScreen *trays_screen)
@@ -96,113 +107,13 @@ get_tray (TraysScreen *trays_screen)
return trays_screen->all_trays->data;
}
-const char *ordered_roles[] = {
- "keyboard",
- "volume",
- "bluetooth",
- "network",
- "battery",
- NULL
-};
-
-const char *wmclass_roles[] = {
- "Bluetooth-applet", "bluetooth",
- "Mate-volume-control-applet", "volume",
- "Nm-applet", "network",
- "Mate-power-manager", "battery",
- "keyboard", "keyboard",
- NULL,
-};
-
-static const char *
-find_role (const char *wmclass)
-{
- int i;
-
- for (i = 0; wmclass_roles[i]; i += 2)
- {
- if (strcmp (wmclass, wmclass_roles[i]) == 0)
- return wmclass_roles[i + 1];
- }
-
- return NULL;
-}
-
-static int
-find_role_position (const char *role)
-{
- int i;
-
- for (i = 0; ordered_roles[i]; i++)
- {
- if (strcmp (role, ordered_roles[i]) == 0)
- break;
- }
-
- return i + 1;
-}
-
-static int
-find_icon_position (NaTray *tray,
- GtkWidget *icon)
-{
- NaTrayPrivate *priv;
- int position;
- char *class_a;
- const char *role;
- int role_position;
- GList *l, *children;
-
- /* We insert the icons with a known roles in a specific order (the one
- * defined by ordered_roles), and all other icons at the beginning of the box
- * (left in LTR). */
-
- priv = tray->priv;
- position = 0;
-
- class_a = NULL;
- na_tray_child_get_wm_class (NA_TRAY_CHILD (icon), NULL, &class_a);
- if (!class_a)
- return position;
-
- role = find_role (class_a);
- g_free (class_a);
- if (!role)
- return position;
-
- role_position = find_role_position (role);
- g_object_set_data (G_OBJECT (icon), "role-position", GINT_TO_POINTER (role_position));
-
- children = gtk_container_get_children (GTK_CONTAINER (priv->box));
- for (l = g_list_last (children); l; l = l->prev)
- {
- GtkWidget *child = l->data;
- int rp;
-
- rp = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (child), "role-position"));
- if (rp == 0 || rp < role_position)
- {
- position = g_list_index (children, child) + 1;
- break;
- }
- }
- g_list_free (children);
-
- /* should never happen, but it doesn't hurt to be on the safe side */
- if (position < 0)
- position = 0;
-
- return position;
-}
-
static void
tray_added (NaTrayManager *manager,
- GtkWidget *icon,
+ NaTrayChild *icon,
TraysScreen *trays_screen)
{
NaTray *tray;
NaTrayPrivate *priv;
- int position;
tray = get_tray (trays_screen);
if (tray == NULL)
@@ -214,30 +125,25 @@ tray_added (NaTrayManager *manager,
g_hash_table_insert (trays_screen->icon_table, icon, tray);
- position = find_icon_position (tray, icon);
- gtk_box_pack_start (GTK_BOX (priv->box), icon, FALSE, FALSE, 0);
- gtk_box_reorder_child (GTK_BOX (priv->box), icon, position);
+ na_host_emit_item_added (NA_HOST (tray), NA_ITEM (icon));
- gtk_widget_show (icon);
+ gtk_widget_show (GTK_WIDGET (icon));
}
static void
tray_removed (NaTrayManager *manager,
- GtkWidget *icon,
+ NaTrayChild *icon,
TraysScreen *trays_screen)
{
NaTray *tray;
- NaTrayPrivate *priv;
tray = g_hash_table_lookup (trays_screen->icon_table, icon);
if (tray == NULL)
return;
- priv = tray->priv;
-
g_assert (tray->priv->trays_screen == trays_screen);
- gtk_container_remove (GTK_CONTAINER (priv->box), icon);
+ na_host_emit_item_removed (NA_HOST (tray), NA_ITEM (icon));
g_hash_table_remove (trays_screen->icon_table, icon);
/* this will also destroy the tip associated to this icon */
@@ -339,7 +245,7 @@ icon_tip_show_next (IconTip *icontip)
if (icontip->fixedtip == NULL)
{
icontip->fixedtip = na_fixed_tip_new (icontip->icon,
- na_tray_get_orientation (icontip->tray));
+ gtk_orientable_get_orientation (GTK_ORIENTABLE (icontip->tray)));
g_signal_connect (icontip->fixedtip, "clicked",
G_CALLBACK (icon_tip_show_next_clicked), icontip);
@@ -482,8 +388,6 @@ update_size_and_orientation (NaTray *tray)
{
NaTrayPrivate *priv = tray->priv;
- gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->box), priv->orientation);
-
/* This only happens when setting the property during object construction */
if (!priv->trays_screen)
return;
@@ -494,52 +398,6 @@ update_size_and_orientation (NaTray *tray)
if (get_tray (priv->trays_screen) == tray)
na_tray_manager_set_orientation (priv->trays_screen->tray_manager,
priv->orientation);
-
- /* note, you want this larger if the frame has non-NONE relief by default. */
- switch (priv->orientation)
- {
- case GTK_ORIENTATION_VERTICAL:
- /* Give box a min size so the frame doesn't look dumb */
- gtk_widget_set_size_request (priv->box, MIN_BOX_SIZE, -1);
- break;
- case GTK_ORIENTATION_HORIZONTAL:
- gtk_widget_set_size_request (priv->box, -1, MIN_BOX_SIZE);
- break;
- }
-}
-
-/* Children with alpha channels have been set to be composited by calling
- * gdk_window_set_composited(). We need to paint these children ourselves.
- */
-static void
-na_tray_draw_icon (GtkWidget *widget,
- gpointer data)
-{
- cairo_t *cr = (cairo_t *) data;
-
- if (na_tray_child_has_alpha (NA_TRAY_CHILD (widget)))
- {
- GtkAllocation allocation;
-
- gtk_widget_get_allocation (widget, &allocation);
-
- cairo_save (cr);
- gdk_cairo_set_source_window (cr,
- gtk_widget_get_window (widget),
- allocation.x,
- allocation.y);
- cairo_rectangle (cr, allocation.x, allocation.y, allocation.width, allocation.height);
- cairo_clip (cr);
- cairo_paint (cr);
- cairo_restore (cr);
- }
-}
-
-static void
-na_tray_draw_box (GtkWidget *box,
- cairo_t *cr)
-{
- gtk_container_foreach (GTK_CONTAINER (box), na_tray_draw_icon, cr);
}
static void
@@ -551,12 +409,8 @@ na_tray_init (NaTray *tray)
priv->screen = NULL;
priv->orientation = GTK_ORIENTATION_HORIZONTAL;
-
- priv->box = gtk_box_new (priv->orientation, ICON_SPACING);
- g_signal_connect (priv->box, "draw",
- G_CALLBACK (na_tray_draw_box), NULL);
- gtk_container_add (GTK_CONTAINER (tray), priv->box);
- gtk_widget_show (priv->box);
+ priv->icon_padding = 0;
+ priv->icon_size = 0;
}
static GObject *
@@ -663,7 +517,7 @@ na_tray_dispose (GObject *object)
new_tray = get_tray (trays_screen);
if (new_tray != NULL)
na_tray_manager_set_orientation (trays_screen->tray_manager,
- na_tray_get_orientation (new_tray));
+ gtk_orientable_get_orientation (GTK_ORIENTABLE (new_tray)));
}
}
@@ -679,6 +533,20 @@ na_tray_dispose (GObject *object)
}
static void
+na_tray_set_orientation (NaTray *tray,
+ GtkOrientation orientation)
+{
+ NaTrayPrivate *priv = tray->priv;
+
+ if (orientation == priv->orientation)
+ return;
+
+ priv->orientation = orientation;
+
+ update_size_and_orientation (tray);
+}
+
+static void
na_tray_set_property (GObject *object,
guint prop_id,
const GValue *value,
@@ -692,6 +560,12 @@ na_tray_set_property (GObject *object,
case PROP_ORIENTATION:
na_tray_set_orientation (tray, g_value_get_enum (value));
break;
+ case PROP_ICON_PADDING:
+ na_tray_set_padding (tray, g_value_get_int (value));
+ break;
+ case PROP_ICON_SIZE:
+ na_tray_set_icon_size (tray, g_value_get_int (value));
+ break;
case PROP_SCREEN:
priv->screen = g_value_get_object (value);
break;
@@ -702,59 +576,49 @@ na_tray_set_property (GObject *object,
}
static void
-na_tray_get_preferred_width (GtkWidget *widget,
- gint *minimal_width,
- gint *natural_width)
-{
- gtk_widget_get_preferred_width (gtk_bin_get_child (GTK_BIN (widget)),
- minimal_width,
- natural_width);
-}
-
-static void
-na_tray_get_preferred_height (GtkWidget *widget,
- gint *minimal_height,
- gint *natural_height)
+na_tray_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
{
- gtk_widget_get_preferred_height (gtk_bin_get_child (GTK_BIN (widget)),
- minimal_height,
- natural_height);
-}
+ NaTray *tray = NA_TRAY (object);
+ NaTrayPrivate *priv = tray->priv;
-static void
-na_tray_size_allocate (GtkWidget *widget,
- GtkAllocation *allocation)
-{
- gtk_widget_size_allocate (gtk_bin_get_child (GTK_BIN (widget)), allocation);
- gtk_widget_set_allocation (widget, allocation);
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, tray->priv->orientation);
+ break;
+ case PROP_ICON_PADDING:
+ g_value_set_int (value, tray->priv->icon_padding);
+ break;
+ case PROP_ICON_SIZE:
+ g_value_set_int (value, tray->priv->icon_size);
+ break;
+ case PROP_SCREEN:
+ g_value_set_object (value, priv->screen);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
}
static void
na_tray_class_init (NaTrayClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gobject_class->constructor = na_tray_constructor;
gobject_class->set_property = na_tray_set_property;
+ gobject_class->get_property = na_tray_get_property;
gobject_class->dispose = na_tray_dispose;
- widget_class->get_preferred_width = na_tray_get_preferred_width;
- widget_class->get_preferred_height = na_tray_get_preferred_height;
- widget_class->size_allocate = na_tray_size_allocate;
+ g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
+
+ g_object_class_override_property (gobject_class, PROP_ICON_PADDING, "icon-padding");
+ g_object_class_override_property (gobject_class, PROP_ICON_SIZE, "icon-size");
- g_object_class_install_property
- (gobject_class,
- PROP_ORIENTATION,
- g_param_spec_enum ("orientation", "orientation", "orientation",
- GTK_TYPE_ORIENTATION,
- GTK_ORIENTATION_HORIZONTAL,
- G_PARAM_WRITABLE |
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_STATIC_NAME |
- G_PARAM_STATIC_NICK |
- G_PARAM_STATIC_BLURB));
-
g_object_class_install_property
(gobject_class,
PROP_SCREEN,
@@ -769,7 +633,7 @@ na_tray_class_init (NaTrayClass *klass)
g_type_class_add_private (gobject_class, sizeof (NaTrayPrivate));
}
-NaTray *
+NaHost *
na_tray_new_for_screen (GdkScreen *screen,
GtkOrientation orientation)
{
@@ -780,43 +644,12 @@ na_tray_new_for_screen (GdkScreen *screen,
}
void
-na_tray_set_orientation (NaTray *tray,
- GtkOrientation orientation)
-{
- NaTrayPrivate *priv = tray->priv;
-
- if (orientation == priv->orientation)
- return;
-
- priv->orientation = orientation;
-
- update_size_and_orientation (tray);
-}
-
-GtkOrientation
-na_tray_get_orientation (NaTray *tray)
-{
- return tray->priv->orientation;
-}
-
-static gboolean
-idle_redraw_cb (NaTray *tray)
-{
- NaTrayPrivate *priv = tray->priv;
-
- gtk_container_foreach (GTK_CONTAINER (priv->box), (GtkCallback)na_tray_child_force_redraw, tray);
-
- priv->idle_redraw_id = 0;
-
- return FALSE;
-}
-
-void
na_tray_set_padding (NaTray *tray,
gint padding)
{
NaTrayPrivate *priv = tray->priv;
+ priv->icon_padding = padding;
if (get_tray (priv->trays_screen) == tray)
na_tray_manager_set_padding (priv->trays_screen->tray_manager, padding);
}
@@ -827,11 +660,12 @@ na_tray_set_icon_size (NaTray *tray,
{
NaTrayPrivate *priv = tray->priv;
+ priv->icon_size = size;
if (get_tray (priv->trays_screen) == tray)
na_tray_manager_set_icon_size (priv->trays_screen->tray_manager, size);
}
-void
+static void
na_tray_set_colors (NaTray *tray,
GdkRGBA *fg,
GdkRGBA *error,
@@ -844,9 +678,49 @@ na_tray_set_colors (NaTray *tray,
na_tray_manager_set_colors (priv->trays_screen->tray_manager, fg, error, warning, success);
}
-void
-na_tray_force_redraw (NaTray *tray)
+static void
+na_tray_style_updated (NaHost *host,
+ GtkStyleContext *context)
+{
+ GdkRGBA fg;
+ GdkRGBA error;
+ GdkRGBA warning;
+ GdkRGBA success;
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
+
+ gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &fg);
+
+ if (!gtk_style_context_lookup_color (context, "error_color", &error))
+ error = fg;
+ if (!gtk_style_context_lookup_color (context, "warning_color", &warning))
+ warning = fg;
+ if (!gtk_style_context_lookup_color (context, "success_color", &success))
+ success = fg;
+
+ gtk_style_context_restore (context);
+
+ na_tray_set_colors (NA_TRAY (host), &fg, &error, &warning, &success);
+}
+
+static gboolean
+idle_redraw_cb (NaTray *tray)
+{
+ NaTrayPrivate *priv = tray->priv;
+
+ g_hash_table_foreach (priv->trays_screen->icon_table,
+ (GHFunc) na_tray_child_force_redraw, NULL);
+
+ priv->idle_redraw_id = 0;
+
+ return FALSE;
+}
+
+static void
+na_tray_force_redraw (NaHost *host)
{
+ NaTray *tray = NA_TRAY (host);
NaTrayPrivate *priv = tray->priv;
/* Force the icons to redraw their backgrounds.
diff --git a/applets/notification_area/na-tray.h b/applets/notification_area/system-tray/na-tray.h
index 8ecac1cb..8eaca36e 100644
--- a/applets/notification_area/na-tray.h
+++ b/applets/notification_area/system-tray/na-tray.h
@@ -29,6 +29,8 @@
#endif
#include <gtk/gtk.h>
+#include "na-host.h"
+
G_BEGIN_DECLS
#define NA_TYPE_TRAY (na_tray_get_type ())
@@ -44,32 +46,23 @@ typedef struct _NaTrayClass NaTrayClass;
struct _NaTray
{
- GtkBin parent_instance;
+ GObject parent_instance;
NaTrayPrivate *priv;
};
struct _NaTrayClass
{
- GtkBinClass parent_class;
+ GObjectClass parent_class;
};
GType na_tray_get_type (void);
-NaTray *na_tray_new_for_screen (GdkScreen *screen,
- GtkOrientation orientation);
-void na_tray_set_orientation (NaTray *tray,
+NaHost *na_tray_new_for_screen (GdkScreen *screen,
GtkOrientation orientation);
-GtkOrientation na_tray_get_orientation (NaTray *tray);
void na_tray_set_padding (NaTray *tray,
gint padding);
void na_tray_set_icon_size (NaTray *tray,
gint icon_size);
-void na_tray_set_colors (NaTray *tray,
- GdkRGBA *fg,
- GdkRGBA *error,
- GdkRGBA *warning,
- GdkRGBA *success);
-void na_tray_force_redraw (NaTray *tray);
G_END_DECLS
diff --git a/applets/notification_area/testtray.c b/applets/notification_area/testtray.c
index f3724025..b2ce0fa2 100644
--- a/applets/notification_area/testtray.c
+++ b/applets/notification_area/testtray.c
@@ -23,9 +23,14 @@
#include <string.h>
#include <stdio.h>
+#include <signal.h>
+#include <glib-unix.h>
#include <gtk/gtk.h>
-#include "na-tray-manager.h"
-#include "na-tray.h"
+#include "system-tray/na-tray-manager.h"
+#ifdef PROVIDE_WATCHER_SERVICE
+# include "libstatus-notifier-watcher/gf-status-notifier-watcher.h"
+#endif
+#include "na-box.h"
#define NOTIFICATION_AREA_ICON "mate-panel-notification-area"
@@ -36,8 +41,7 @@ typedef struct
GdkScreen *screen;
guint screen_num;
GtkWidget *window;
- NaTray *tray;
- GtkWidget *box;
+ GtkWidget *traybox;
GtkLabel *count_label;
} TrayData;
@@ -56,7 +60,7 @@ update_child_count (TrayData *data)
if (!gtk_widget_get_realized (data->window))
return;
- gtk_container_foreach (GTK_CONTAINER (data->box), (GtkCallback) do_add, &n_children);
+ gtk_container_foreach (GTK_CONTAINER (data->traybox), (GtkCallback) do_add, &n_children);
g_snprintf (text, sizeof (text), "%u icons", n_children);
gtk_label_set_text (data->count_label, text);
@@ -66,7 +70,7 @@ static void
tray_added_cb (GtkContainer *box, GtkWidget *icon, TrayData *data)
{
g_print ("[Screen %u tray %p] Child %p added to tray: \"%s\"\n",
- data->screen_num, data->tray, icon, "XXX");//na_tray_child_get_title (icon));
+ data->screen_num, data->traybox, icon, "XXX");//na_tray_child_get_title (icon));
update_child_count (data);
}
@@ -75,7 +79,7 @@ static void
tray_removed_cb (GtkContainer *box, GtkWidget *icon, TrayData *data)
{
g_print ("[Screen %u tray %p] Child %p removed from tray\n",
- data->screen_num, data->tray, icon);
+ data->screen_num, data->traybox, icon);
update_child_count (data);
}
@@ -85,9 +89,9 @@ static void orientation_changed_cb (GtkComboBox *combo, TrayData *data)
GtkOrientation orientation = (GtkOrientation) gtk_combo_box_get_active (combo);
g_print ("[Screen %u tray %p] Setting orientation to \"%s\"\n",
- data->screen_num, data->tray, orientation == 0 ? "horizontal" : "vertical");
+ data->screen_num, data->traybox, orientation == 0 ? "horizontal" : "vertical");
- na_tray_set_orientation (data->tray, orientation);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (data->traybox), orientation);
}
static void
@@ -184,12 +188,11 @@ create_tray_on_screen (GdkScreen *screen,
#endif
gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
- data->tray = na_tray_new_for_screen (screen, GTK_ORIENTATION_HORIZONTAL);
- gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (data->tray), TRUE, TRUE, 0);
+ data->traybox = na_box_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (data->traybox), TRUE, TRUE, 0);
- data->box = gtk_bin_get_child (GTK_BIN (gtk_bin_get_child (GTK_BIN (data->tray))));
- g_signal_connect_after (data->box, "add", G_CALLBACK (tray_added_cb), data);
- g_signal_connect_after (data->box, "remove", G_CALLBACK (tray_removed_cb), data);
+ g_signal_connect_after (data->traybox, "add", G_CALLBACK (tray_added_cb), data);
+ g_signal_connect_after (data->traybox, "remove", G_CALLBACK (tray_removed_cb), data);
gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
@@ -205,14 +208,66 @@ create_tray_on_screen (GdkScreen *screen,
return data;
}
+static gboolean
+signal_handler (gpointer data G_GNUC_UNUSED)
+{
+ gtk_main_quit ();
+
+ return FALSE;
+}
+
+#ifdef PROVIDE_WATCHER_SERVICE
+static GfStatusNotifierWatcher *
+status_notifier_watcher_maybe_new (void)
+{
+ GDBusProxy *proxy;
+ GError *error = NULL;
+ GfStatusNotifierWatcher *service = NULL;
+
+ /* check if the service already exists
+ * FIXME: is that a not-too-stupid way of doing it? bah, so long as it works.
+ * it's for testing purposes only anyway. */
+ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL,
+ "org.kde.StatusNotifierWatcher",
+ "/StatusNotifierWatcher",
+ "org.kde.StatusNotifierWatcher",
+ NULL, &error);
+ if (proxy)
+ g_object_unref (proxy);
+ else
+ {
+ g_warning ("Failed to connect to org.kde.StatusNotifierWatcher (%s), starting our own.",
+ error->message);
+ g_clear_error (&error);
+
+ service = gf_status_notifier_watcher_new ();
+ }
+
+ return service;
+}
+#endif
+
int
main (int argc, char *argv[])
{
GdkDisplay *display;
GdkScreen *screen;
+#ifdef PROVIDE_WATCHER_SERVICE
+ GfStatusNotifierWatcher *service;
+#endif
gtk_init (&argc, &argv);
+ g_unix_signal_add (SIGTERM, signal_handler, NULL);
+ g_unix_signal_add (SIGINT, signal_handler, NULL);
+
+#ifdef PROVIDE_WATCHER_SERVICE
+ service = status_notifier_watcher_maybe_new ();
+#endif
+
gtk_window_set_default_icon_name (NOTIFICATION_AREA_ICON);
display = gdk_display_get_default ();
@@ -222,5 +277,10 @@ main (int argc, char *argv[])
gtk_main ();
+#ifdef PROVIDE_WATCHER_SERVICE
+ if (service)
+ g_object_unref (service);
+#endif
+
return 0;
}
diff --git a/configure.ac b/configure.ac
index bc7dc709..8ddc6530 100644
--- a/configure.ac
+++ b/configure.ac
@@ -36,6 +36,7 @@ AC_DEFINE([GLIB_VERSION_MIN_REQUIRED], [GLIB_VERSION_2_36],
[Warn on use of APIs deprecated before GLib 2.36])
AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
+AC_PATH_PROG([GDBUS_CODEGEN], [gdbus-codegen])
MATE_COMPILE_WARNINGS(maximum)
@@ -56,6 +57,8 @@ if test "x$enable_deprecation_flags" = "xyes"; then
AC_SUBST(DISABLE_DEPRECATED_CFLAGS)
fi
+LT_LIB_M
+
LIBMATE_DESKTOP_REQUIRED=1.17.0
GDK_PIXBUF_REQUIRED=2.7.1
PANGO_REQUIRED=1.15.4
@@ -283,6 +286,9 @@ applets/clock/pixmaps/Makefile
applets/fish/Makefile
applets/fish/org.mate.panel.applet.fish.gschema.xml
applets/notification_area/Makefile
+applets/notification_area/libstatus-notifier-watcher/Makefile
+applets/notification_area/status-notifier/Makefile
+applets/notification_area/system-tray/Makefile
applets/wncklet/Makefile
applets/wncklet/org.mate.panel.applet.window-list.gschema.xml
applets/wncklet/org.mate.panel.applet.workspace-switcher.gschema.xml