[gnome-panel] status-notifier: add SnDBusMenu



commit 8716e289d508373f6d4c2f0c8a3f5068a636d0df
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Fri Nov 4 15:45:35 2016 +0200

    status-notifier: add SnDBusMenu

 applets/status-notifier/Makefile.am    |    2 +
 applets/status-notifier/sn-dbus-menu.c |  469 ++++++++++++++++++++++++++++++++
 applets/status-notifier/sn-dbus-menu.h |   33 +++
 3 files changed, 504 insertions(+), 0 deletions(-)
---
diff --git a/applets/status-notifier/Makefile.am b/applets/status-notifier/Makefile.am
index 303f921..31dd57b 100644
--- a/applets/status-notifier/Makefile.am
+++ b/applets/status-notifier/Makefile.am
@@ -21,6 +21,8 @@ status_notifier_la_CFLAGS = \
 status_notifier_la_SOURCES = \
        sn-applet.c \
        sn-applet.h \
+       sn-dbus-menu.c \
+       sn-dbus-menu.h \
        sn-dbus-menu-item.c \
        sn-dbus-menu-item.h \
        sn-host.c \
diff --git a/applets/status-notifier/sn-dbus-menu.c b/applets/status-notifier/sn-dbus-menu.c
new file mode 100644
index 0000000..39fc63a
--- /dev/null
+++ b/applets/status-notifier/sn-dbus-menu.c
@@ -0,0 +1,469 @@
+/*
+ * 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);
+      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;
+    }
+
+  layout_parse (menu, layout, GTK_MENU (menu));
+  g_variant_unref (layout);
+}
+
+static void
+update_layout (SnDBusMenu *menu,
+               gint        parent)
+{
+  gint depth;
+
+  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)
+    {
+      g_hash_table_remove_all (menu->items);
+      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/status-notifier/sn-dbus-menu.h b/applets/status-notifier/sn-dbus-menu.h
new file mode 100644
index 0000000..39907d0
--- /dev/null
+++ b/applets/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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]