[glib] Add GMenuModel D-Bus exporter



commit 66e089f086c0243eb43847137081bf99f2fc89dc
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat Nov 26 21:02:15 2011 -0500

    Add GMenuModel D-Bus exporter

 docs/reference/gio/gio-docs.xml     |    1 +
 docs/reference/gio/gio-sections.txt |    7 +
 gio/Makefile.am                     |    2 +
 gio/gio.h                           |    1 +
 gio/gio.symbols                     |    3 +
 gio/gmenuexporter.c                 |  884 +++++++++++++++++++++++++++++++++++
 gio/gmenuexporter.h                 |   45 ++
 7 files changed, 943 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml
index a1e6ad2..bb2bcb0 100644
--- a/docs/reference/gio/gio-docs.xml
+++ b/docs/reference/gio/gio-docs.xml
@@ -203,6 +203,7 @@
         <xi:include href="xml/gmenumodel.xml"/>
         <xi:include href="xml/gmenu.xml"/>
         <xi:include href="xml/gmenumarkup.xml"/>
+        <xi:include href="xml/gmenuexporter.xml"/>
     </chapter>
     <chapter id="extending">
         <title>Extending GIO</title>
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index 88bc4e9..b034148 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -3542,6 +3542,13 @@ G_NETWORK_MONITOR_GET_INTERFACE
 </SECTION>
 
 <SECTION>
+<FILE>gmenuexporter</FILE>
+g_menu_exporter_export
+g_menu_exporter_stop
+g_menu_exporter_query
+</SECTION>
+
+<SECTION>
 <FILE>gmenu</FILE>
 GMenu
 g_menu_new
diff --git a/gio/Makefile.am b/gio/Makefile.am
index d80a97b..0f0515e 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -136,6 +136,7 @@ application_headers = \
 	gmenumodel.h			\
 	gmenu.h				\
 	gmenumarkup.h			\
+	gmenuexporter.h			\
 	$(NULL)
 
 application_sources = \
@@ -152,6 +153,7 @@ application_sources = \
 	gmenumodel.c				\
 	gmenu.c					\
 	gmenumarkup.c				\
+	gmenuexporter.c				\
 	$(NULL)
 
 local_sources = \
diff --git a/gio/gio.h b/gio/gio.h
index f379ecc..296355c 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -148,6 +148,7 @@
 #include <gio/gdbusactiongroup.h>
 #include <gio/gmenumodel.h>
 #include <gio/gmenu.h>
+#include <gio/gmenuexporter.h>
 #include <gio/gmenumarkup.h>
 
 #undef __GIO_GIO_H_INSIDE__
diff --git a/gio/gio.symbols b/gio/gio.symbols
index 1091455..0129067 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1613,6 +1613,9 @@ g_menu_attribute_iter_get_next
 g_menu_attribute_iter_get_type
 g_menu_attribute_iter_get_value
 g_menu_attribute_iter_next
+g_menu_exporter_export
+g_menu_exporter_query
+g_menu_exporter_stop
 g_menu_freeze
 g_menu_get_type
 g_menu_insert
diff --git a/gio/gmenuexporter.c b/gio/gmenuexporter.c
new file mode 100644
index 0000000..e0fb4ca
--- /dev/null
+++ b/gio/gmenuexporter.c
@@ -0,0 +1,884 @@
+/*
+ * Copyright  2011 Canonical Ltd.
+ *
+ *  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
+ *  licence, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "gmenuexporter.h"
+
+#include "gdbusmethodinvocation.h"
+#include "gdbusintrospection.h"
+#include "gdbusnamewatching.h"
+#include "gdbuserror.h"
+
+/**
+ * SECTION:gmenuexporter
+ * @title: GMenuModel exporter
+ * @short_description: Export GMenuModels on D-Bus
+ * @see_also: #GMenuModel, #GMenuProxy
+ *
+ * These functions support exporting a #GMenuModel on D-Bus.
+ * The D-Bus interface that is used is a private implementation
+ * detail.
+ *
+ * To access an exported #GMenuModel remotely, use
+ * g_menu_proxy_get() to obtain a #GMenuProxy.
+ */
+
+/* {{{1 D-Bus Interface description */
+static GDBusInterfaceInfo *
+org_gtk_Menus_get_interface (void)
+{
+  static GDBusInterfaceInfo *interface_info;
+
+  if (interface_info == NULL)
+    {
+      GError *error = NULL;
+      GDBusNodeInfo *info;
+
+      info = g_dbus_node_info_new_for_xml ("<node>"
+                                           "  <interface name='org.gtk.Menus'>"
+                                           "    <method name='Start'>"
+                                           "      <arg type='au' name='groups' direction='in'/>"
+                                           "      <arg type='a(uuaa{sv})' name='content' direction='out'/>"
+                                           "    </method>"
+                                           "    <method name='End'>"
+                                           "      <arg type='au' name='groups' direction='in'/>"
+                                           "    </method>"
+                                           "    <signal name='Changed'>"
+                                           "      arg type='a(uuuuaa{sv})' name='changes'/>"
+                                           "    </signal>"
+                                           "  </interface>"
+                                           "</node>", &error);
+      if (info == NULL)
+        g_error ("%s\n", error->message);
+      interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
+      g_assert (interface_info != NULL);
+      g_dbus_interface_info_ref (interface_info);
+      g_dbus_node_info_unref (info);
+    }
+
+  return interface_info;
+}
+
+/* {{{1 Forward declarations */
+typedef struct _GMenuExporterMenu                           GMenuExporterMenu;
+typedef struct _GMenuExporterLink                           GMenuExporterLink;
+typedef struct _GMenuExporterGroup                          GMenuExporterGroup;
+typedef struct _GMenuExporterRemote                         GMenuExporterRemote;
+typedef struct _GMenuExporterWatch                          GMenuExporterWatch;
+typedef struct _GMenuExporter                               GMenuExporter;
+
+static gboolean                 g_menu_exporter_group_is_subscribed    (GMenuExporterGroup *group);
+static guint                    g_menu_exporter_group_get_id           (GMenuExporterGroup *group);
+static GMenuExporter *          g_menu_exporter_group_get_exporter     (GMenuExporterGroup *group);
+static GMenuExporterMenu *      g_menu_exporter_group_add_menu         (GMenuExporterGroup *group,
+                                                                        GMenuModel         *model);
+static void                     g_menu_exporter_group_remove_menu      (GMenuExporterGroup *group,
+                                                                        guint               id);
+
+static GMenuExporterGroup *     g_menu_exporter_create_group           (GMenuExporter      *exporter);
+static GMenuExporterGroup *     g_menu_exporter_lookup_group           (GMenuExporter      *exporter,
+                                                                        guint               group_id);
+static void                     g_menu_exporter_report                 (GMenuExporter      *exporter,
+                                                                        GVariant           *report);
+static void                     g_menu_exporter_remove_group           (GMenuExporter      *exporter,
+                                                                        guint               id);
+
+/* {{{1 GMenuExporterLink, GMenuExporterMenu */
+
+struct _GMenuExporterMenu
+{
+  GMenuExporterGroup *group;
+  guint               id;
+
+  GMenuModel *model;
+  gulong      handler_id;
+  GSequence  *item_links;
+};
+
+struct _GMenuExporterLink
+{
+  gchar             *name;
+  GMenuExporterMenu *menu;
+  GMenuExporterLink *next;
+};
+
+static void
+g_menu_exporter_menu_free (GMenuExporterMenu *menu)
+{
+  g_menu_exporter_group_remove_menu (menu->group, menu->id);
+
+  if (menu->handler_id != 0)
+    g_signal_handler_disconnect (menu->model, menu->handler_id);
+
+  if (menu->item_links != NULL)
+    g_sequence_free (menu->item_links);
+
+  g_object_unref (menu->model);
+
+  g_slice_free (GMenuExporterMenu, menu);
+}
+
+static void
+g_menu_exporter_link_free (gpointer data)
+{
+  GMenuExporterLink *link = data;
+
+  while (link != NULL)
+    {
+      GMenuExporterLink *tmp = link;
+      link = tmp->next;
+
+      g_menu_exporter_menu_free (tmp->menu);
+      g_free (tmp->name);
+
+      g_slice_free (GMenuExporterLink, tmp);
+    }
+}
+
+static GMenuExporterLink *
+g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
+                                   gint               position)
+{
+  GMenuExporterLink *list = NULL;
+  GMenuLinkIter *iter;
+  const char *name;
+  GMenuModel *model;
+
+  iter = g_menu_model_iterate_item_links (menu->model, position);
+
+  while (g_menu_link_iter_get_next (iter, &name, &model))
+    {
+      GMenuExporterGroup *group;
+      GMenuExporterLink *tmp;
+
+      if (0) /* [magic] */
+        group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
+      else
+        group = menu->group;
+
+      tmp = g_slice_new (GMenuExporterLink);
+      tmp->name = g_strconcat (":", name, NULL);
+      tmp->menu = g_menu_exporter_group_add_menu (group, model);
+      tmp->next = list;
+      list = tmp;
+
+      g_object_unref (model);
+    }
+
+  g_object_unref (iter);
+
+  return list;
+}
+
+static GVariant *
+g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
+                                    gint               position)
+{
+  GMenuAttributeIter *attr_iter;
+  GVariantBuilder builder;
+  GSequenceIter *iter;
+  GMenuExporterLink *link;
+  const char *name;
+  GVariant *value;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+  attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
+  while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
+    {
+      g_variant_builder_add (&builder, "{sv}", name, value);
+      g_variant_unref (value);
+    }
+  g_object_unref (attr_iter);
+
+  iter = g_sequence_get_iter_at_pos (menu->item_links, position);
+  for (link = g_sequence_get (iter); link; link = link->next)
+    g_variant_builder_add (&builder, "{sv}", link->name,
+                           g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id));
+
+  return g_variant_builder_end (&builder);
+}
+
+static GVariant *
+g_menu_exporter_menu_list (GMenuExporterMenu *menu)
+{
+  GVariantBuilder builder;
+  gint i, n;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+  n = g_sequence_get_length (menu->item_links);
+  for (i = 0; i < n; i++)
+    g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
+
+  return g_variant_builder_end (&builder);
+}
+
+static void
+g_menu_exporter_menu_items_changed (GMenuModel *model,
+                                    gint        position,
+                                    gint        removed,
+                                    gint        added,
+                                    gpointer    user_data)
+{
+  GMenuExporterMenu *menu = user_data;
+  GSequenceIter *point;
+  gint i;
+
+  g_assert (menu->model == model);
+  g_assert (menu->item_links != NULL);
+  g_assert (position + removed <= g_sequence_get_length (menu->item_links));
+
+  point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
+  g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
+
+  for (i = position; i < position + added; i++)
+    g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i));
+
+  if (g_menu_exporter_group_is_subscribed (menu->group))
+    {
+      GVariantBuilder builder;
+
+      g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuuuaa{sv})"));
+      g_variant_builder_add (&builder, "u", g_menu_exporter_group_get_id (menu->group));
+      g_variant_builder_add (&builder, "u", menu->id);
+      g_variant_builder_add (&builder, "u", position);
+      g_variant_builder_add (&builder, "u", removed);
+
+      g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}"));
+      for (i = position; i < position + added; i++)
+        g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
+      g_variant_builder_close (&builder);
+
+      g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder));
+    }
+}
+
+static void
+g_menu_exporter_menu_prepare (GMenuExporterMenu *menu)
+{
+  gint n_items;
+
+  g_assert (menu->item_links == NULL);
+
+  if (g_menu_model_is_mutable (menu->model))
+    menu->handler_id = g_signal_connect (menu->model, "items-changed",
+                                         G_CALLBACK (g_menu_exporter_menu_items_changed), menu);
+
+  menu->item_links = g_sequence_new (g_menu_exporter_link_free);
+
+  n_items = g_menu_model_get_n_items (menu->model);
+  if (n_items)
+    g_menu_exporter_menu_items_changed (menu->model, 0, 0, n_items, menu);
+}
+
+static GMenuExporterMenu *
+g_menu_exporter_menu_new (GMenuExporterGroup *group,
+                          guint               id,
+                          GMenuModel         *model)
+{
+  GMenuExporterMenu *menu;
+
+  menu = g_slice_new0 (GMenuExporterMenu);
+  menu->group = group;
+  menu->id = id;
+  menu->model = g_object_ref (model);
+
+  return menu;
+}
+
+/* {{{1 GMenuExporterGroup */
+
+struct _GMenuExporterGroup
+{
+  GMenuExporter *exporter;
+  guint          id;
+
+  GHashTable *menus;
+  guint       next_menu_id;
+  gboolean    prepared;
+
+  gint subscribed;
+};
+
+static void
+g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
+{
+  if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0)
+    {
+      g_menu_exporter_remove_group (group->exporter, group->id);
+
+      g_hash_table_unref (group->menus);
+
+      g_slice_free (GMenuExporterGroup, group);
+    }
+}
+
+static void
+g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
+                                 GVariantBuilder    *builder)
+{
+  GHashTableIter iter;
+  gpointer key, val;
+
+  if (!group->prepared)
+    {
+      GMenuExporterMenu *menu;
+
+      /* set this first, so that any menus created during the
+       * preparation of the first menu also end up in the prepared
+       * state.
+       * */
+      group->prepared = TRUE;
+
+      menu = g_hash_table_lookup (group->menus, 0);
+      g_menu_exporter_menu_prepare (menu);
+    }
+
+  group->subscribed++;
+
+  g_hash_table_iter_init (&iter, group->menus);
+  while (g_hash_table_iter_next (&iter, &key, &val))
+    {
+      guint id = GPOINTER_TO_INT (key);
+      GMenuExporterMenu *menu = val;
+
+      if (g_sequence_get_length (menu->item_links))
+        {
+          g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})"));
+          g_variant_builder_add (builder, "u", group->id);
+          g_variant_builder_add (builder, "u", id);
+          g_variant_builder_add_value (builder, g_menu_exporter_menu_list (menu));
+          g_variant_builder_close (builder);
+        }
+    }
+}
+
+static void
+g_menu_exporter_group_unsubscribe (GMenuExporterGroup *group,
+                                   gint                count)
+{
+  g_assert (group->subscribed >= count);
+
+  group->subscribed -= count;
+
+  g_menu_exporter_group_check_if_useless (group);
+}
+
+static GMenuExporter *
+g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
+{
+  return group->exporter;
+}
+
+static gboolean
+g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
+{
+  return group->subscribed > 0;
+}
+
+static guint
+g_menu_exporter_group_get_id (GMenuExporterGroup *group)
+{
+  return group->id;
+}
+
+static void
+g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
+                                   guint               id)
+{
+  g_hash_table_remove (group->menus, GINT_TO_POINTER (id));
+
+  g_menu_exporter_group_check_if_useless (group);
+}
+
+static GMenuExporterMenu *
+g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
+                                GMenuModel         *model)
+{
+  GMenuExporterMenu *menu;
+  guint id;
+
+  id = group->next_menu_id++;
+  menu = g_menu_exporter_menu_new (group, id, model);
+  g_hash_table_insert (group->menus, GINT_TO_POINTER (id), menu);
+
+  if (group->prepared)
+    g_menu_exporter_menu_prepare (menu);
+
+  return menu;
+}
+
+static GMenuExporterGroup *
+g_menu_exporter_group_new (GMenuExporter *exporter,
+                           guint          id)
+{
+  GMenuExporterGroup *group;
+
+  group = g_slice_new0 (GMenuExporterGroup);
+  group->menus = g_hash_table_new (NULL, NULL);
+  group->exporter = exporter;
+  group->id = id;
+
+  return group;
+}
+
+/* {{{1 GMenuExporterRemote */
+
+struct _GMenuExporterRemote
+{
+  GMenuExporter *exporter;
+  GHashTable    *watches;
+  guint          watch_id;
+};
+
+static void
+g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
+                                  guint                group_id,
+                                  GVariantBuilder     *builder)
+{
+  GMenuExporterGroup *group;
+  guint count;
+
+  count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
+  g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count + 1));
+
+  group = g_menu_exporter_lookup_group (remote->exporter, group_id);
+  g_menu_exporter_group_subscribe (group, builder);
+}
+
+static void
+g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote,
+                                    guint                group_id)
+{
+  GMenuExporterGroup *group;
+  guint count;
+
+  count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
+
+  if (count == 0)
+    return;
+
+  if (count != 1)
+    g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count - 1));
+  else
+    g_hash_table_remove (remote->watches, GINT_TO_POINTER (group_id));
+
+  group = g_menu_exporter_lookup_group (remote->exporter, group_id);
+  g_menu_exporter_group_unsubscribe (group, 1);
+}
+
+static gboolean
+g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
+{
+  return g_hash_table_size (remote->watches) != 0;
+}
+
+static void
+g_menu_exporter_remote_free (gpointer data)
+{
+  GMenuExporterRemote *remote = data;
+  GHashTableIter iter;
+  gpointer key, val;
+
+  g_hash_table_iter_init (&iter, remote->watches);
+  while (g_hash_table_iter_next (&iter, &key, &val))
+    {
+      GMenuExporterGroup *group;
+
+      group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key));
+      g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val));
+    }
+
+  g_bus_unwatch_name (remote->watch_id);
+  g_hash_table_unref (remote->watches);
+
+  g_slice_free (GMenuExporterRemote, remote);
+}
+
+static GMenuExporterRemote *
+g_menu_exporter_remote_new (GMenuExporter *exporter,
+                            guint          watch_id)
+{
+  GMenuExporterRemote *remote;
+
+  remote = g_slice_new0 (GMenuExporterRemote);
+  remote->exporter = exporter;
+  remote->watches = g_hash_table_new (NULL, NULL);
+  remote->watch_id = watch_id;
+
+  return remote;
+}
+
+/* {{{1 GMenuExporter */
+
+struct _GMenuExporter
+{
+  GDBusConnection *connection;
+  gchar *object_path;
+  guint registration_id;
+  GHashTable *groups;
+  guint next_group_id;
+
+  GMenuExporterMenu *root;
+  GHashTable *remotes;
+};
+
+static void
+g_menu_exporter_name_vanished (GDBusConnection *connection,
+                               const gchar     *name,
+                               gpointer         user_data)
+{
+  GMenuExporter *exporter = user_data;
+
+  g_assert (exporter->connection == connection);
+
+  g_hash_table_remove (exporter->remotes, name);
+}
+
+static GVariant *
+g_menu_exporter_subscribe (GMenuExporter *exporter,
+                           const gchar   *sender,
+                           GVariant      *group_ids)
+{
+  GMenuExporterRemote *remote;
+  GVariantBuilder builder;
+  GVariantIter iter;
+  guint32 id;
+
+  remote = g_hash_table_lookup (exporter->remotes, sender);
+
+  if (remote == NULL)
+    {
+      guint watch_id;
+
+      watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                                 NULL, g_menu_exporter_name_vanished, exporter, NULL);
+      remote = g_menu_exporter_remote_new (exporter, watch_id);
+      g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
+    }
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
+
+  g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
+
+  g_variant_iter_init (&iter, group_ids);
+  while (g_variant_iter_next (&iter, "u", &id))
+    g_menu_exporter_remote_subscribe (remote, id, &builder);
+
+  g_variant_builder_close (&builder);
+
+  return g_variant_builder_end (&builder);
+}
+
+static void
+g_menu_exporter_unsubscribe (GMenuExporter *exporter,
+                             const gchar   *sender,
+                             GVariant      *group_ids)
+{
+  GMenuExporterRemote *remote;
+  GVariantIter iter;
+  guint32 id;
+
+  remote = g_hash_table_lookup (exporter->remotes, sender);
+
+  if (remote == NULL)
+    return;
+
+  g_variant_iter_init (&iter, group_ids);
+  while (g_variant_iter_next (&iter, "u", &id))
+    g_menu_exporter_remote_unsubscribe (remote, id);
+
+  if (!g_menu_exporter_remote_has_subscriptions (remote))
+    g_hash_table_remove (exporter->remotes, sender);
+}
+
+static void
+g_menu_exporter_report (GMenuExporter *exporter,
+                        GVariant      *report)
+{
+  GVariantBuilder builder;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
+  g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
+  g_variant_builder_add_value (&builder, report);
+  g_variant_builder_close (&builder);
+
+  g_dbus_connection_emit_signal (exporter->connection,
+                                 NULL,
+                                 exporter->object_path,
+                                 "org.gtk.Menus", "Changed",
+                                 g_variant_builder_end (&builder),
+                                 NULL);
+}
+
+static void
+g_menu_exporter_remove_group (GMenuExporter *exporter,
+                              guint          id)
+{
+  g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
+}
+
+static GMenuExporterGroup *
+g_menu_exporter_lookup_group (GMenuExporter *exporter,
+                              guint          group_id)
+{
+  GMenuExporterGroup *group;
+
+  group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
+
+  if (group == NULL)
+    {
+      group = g_menu_exporter_group_new (exporter, group_id);
+      g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
+    }
+
+  return group;
+}
+
+static GMenuExporterGroup *
+g_menu_exporter_create_group (GMenuExporter *exporter)
+{
+  GMenuExporterGroup *group;
+  guint id;
+
+  id = exporter->next_group_id++;
+  group = g_menu_exporter_group_new (exporter, id);
+  g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
+
+  return group;
+}
+
+static void
+g_menu_exporter_free (GMenuExporter *exporter)
+{
+  g_dbus_connection_unregister_object (exporter->connection, exporter->registration_id);
+  g_menu_exporter_menu_free (exporter->root);
+  g_hash_table_unref (exporter->remotes);
+  g_hash_table_unref (exporter->groups);
+  g_object_unref (exporter->connection);
+  g_free (exporter->object_path);
+
+  g_slice_free (GMenuExporter, exporter);
+}
+
+static void
+g_menu_exporter_method_call (GDBusConnection       *connection,
+                             const gchar           *sender,
+                             const gchar           *object_path,
+                             const gchar           *interface_name,
+                             const gchar           *method_name,
+                             GVariant              *parameters,
+                             GDBusMethodInvocation *invocation,
+                             gpointer               user_data)
+{
+  GMenuExporter *exporter = user_data;
+  GVariant *group_ids;
+
+  group_ids = g_variant_get_child_value (parameters, 0);
+
+  if (g_str_equal (method_name, "Start"))
+    g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
+
+  else if (g_str_equal (method_name, "End"))
+    {
+      g_menu_exporter_unsubscribe (exporter, sender, group_ids);
+      g_dbus_method_invocation_return_value (invocation, NULL);
+    }
+
+  else
+    g_assert_not_reached ();
+
+  g_variant_unref (group_ids);
+}
+
+static GDBusConnection *
+g_menu_exporter_get_connection (GMenuExporter *exporter)
+{
+  return exporter->connection;
+}
+
+static const gchar *
+g_menu_exporter_get_object_path (GMenuExporter *exporter)
+{
+  return exporter->object_path;
+}
+
+static GMenuExporter *
+g_menu_exporter_new (GDBusConnection  *connection,
+                     const gchar      *object_path,
+                     GMenuModel       *model,
+                     GError          **error)
+{
+  const GDBusInterfaceVTable vtable = {
+    g_menu_exporter_method_call,
+  };
+  GMenuExporter *exporter;
+  guint id;
+
+  exporter = g_slice_new0 (GMenuExporter);
+
+  id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
+                                          &vtable, exporter, NULL, error);
+
+  if (id == 0)
+    {
+      g_slice_free (GMenuExporter, exporter);
+      return NULL;
+    }
+
+  exporter->connection = g_object_ref (connection);
+  exporter->object_path = g_strdup (object_path);
+  exporter->registration_id = id;
+  exporter->groups = g_hash_table_new (NULL, NULL);
+  exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
+  exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), model);
+
+  return exporter;
+}
+
+/* {{{1 Public API */
+
+static GHashTable *g_menu_exporter_exported_menus;
+
+/**
+ * g_menu_exporter_export:
+ * @connection: a #GDBusConnection
+ * @object_path: a D-Bus object path
+ * @menu: a #GMenuModel
+ * @error: return location for an error, or %NULL
+ *
+ * Exports @menu on @connection at @object_path.
+ *
+ * The implemented D-Bus API should be considered private.
+ * It is subject to change in the future.
+ *
+ * A given menu model can only be exported on one object path
+ * and an object_path can only have one action group exported
+ * on it.  If either constraint is violated, the export will
+ * fail and %FALSE will be returned (with @error set accordingly).
+ *
+ * Use g_menu_exporter_stop() to stop exporting @menu
+ * or g_menu_exporter_query() to find out if and where a given
+ * menu model is exported.
+ *
+ * Returns: %TRUE if the export is successful, or %FALSE (with
+ *     @error set) in the event of a failure.
+ */
+gboolean
+g_menu_exporter_export (GDBusConnection  *connection,
+                        const gchar      *object_path,
+                        GMenuModel       *menu,
+                        GError          **error)
+{
+  GMenuExporter *exporter;
+
+  if G_UNLIKELY (g_menu_exporter_exported_menus == NULL)
+    g_menu_exporter_exported_menus = g_hash_table_new (NULL, NULL);
+
+  if G_UNLIKELY (g_hash_table_lookup (g_menu_exporter_exported_menus, menu))
+    {
+      g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FILE_EXISTS, "The given GMenuModel has already been exported");
+      return FALSE;
+    }
+
+  exporter = g_menu_exporter_new (connection, object_path, menu, error);
+
+  if (exporter == NULL)
+    return FALSE;
+
+  g_hash_table_insert (g_menu_exporter_exported_menus, menu, exporter);
+
+  return TRUE;
+}
+
+/**
+ * g_menu_exporter_stop:
+ * @menu: a #GMenuModel
+ *
+ * Stops the export of @menu.
+ *
+ * This reverses the effect of a previous call to
+ * g_menu_exporter_export() for @menu.
+ *
+ * Returns: %TRUE if an export was stopped or %FALSE
+ *     if @menu was not exported in the first place
+ */
+gboolean
+g_menu_exporter_stop (GMenuModel *menu)
+{
+  GMenuExporter *exporter;
+
+  if G_UNLIKELY (g_menu_exporter_exported_menus == NULL)
+    return FALSE;
+
+  exporter = g_hash_table_lookup (g_menu_exporter_exported_menus, menu);
+  if G_UNLIKELY (exporter == NULL)
+    return FALSE;
+
+  g_hash_table_remove (g_menu_exporter_exported_menus, menu);
+  g_menu_exporter_free (exporter);
+
+  return TRUE;
+}
+
+/**
+ * g_menu_exporter_query:
+ * @menu: a #GMenuModel
+ * @connection: (out): the #GDBusConnection used for exporting
+ * @object_path: (out): the object path used for exporting
+ *
+ * Queries if and where @menu is exported.
+ *
+ * If @menu is exported, %TRUE is returned. If @connection is
+ * non-%NULL then it is set to the #GDBusConnection used for
+ * the export. If @object_path is non-%NULL then it is set to
+ * the object path.
+ *
+ * If the @menu is not exported, %FALSE is returned and
+ * @connection and @object_path remain unmodified.
+ *
+ * Returns: %TRUE if @menu was exported, else %FALSE
+ */
+gboolean
+g_menu_exporter_query (GMenuModel       *menu,
+                       GDBusConnection **connection,
+                       const gchar     **object_path)
+{
+  GMenuExporter *exporter;
+
+  if (g_menu_exporter_exported_menus == NULL)
+    return FALSE;
+
+  exporter = g_hash_table_lookup (g_menu_exporter_exported_menus, menu);
+  if (exporter == NULL)
+    return FALSE;
+
+  if (connection)
+    *connection = g_menu_exporter_get_connection (exporter);
+
+  if (object_path)
+    *object_path = g_menu_exporter_get_object_path (exporter);
+
+  return TRUE;
+}
+
+/* {{{1 Epilogue */
+/* vim:set foldmethod=marker: */
diff --git a/gio/gmenuexporter.h b/gio/gmenuexporter.h
new file mode 100644
index 0000000..602bf31
--- /dev/null
+++ b/gio/gmenuexporter.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright  2011 Canonical Ltd.
+ *
+ *  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
+ *  licence, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __G_MENU_EXPORTER_H__
+#define __G_MENU_EXPORTER_H__
+
+#include <gio/gdbusconnection.h>
+#include <gio/gmenumodel.h>
+
+G_BEGIN_DECLS
+
+gboolean        g_menu_exporter_export          (GDBusConnection  *connection,
+                                                 const gchar      *object_path,
+                                                 GMenuModel       *menu,
+                                                 GError          **error);
+
+gboolean        g_menu_exporter_stop            (GMenuModel       *menu);
+
+
+
+gboolean        g_menu_exporter_query           (GMenuModel       *menu,
+                                                 GDBusConnection **connection,
+                                                 const gchar     **object_path);
+
+G_END_DECLS
+
+#endif /* __G_MENU_EXPORTER_H__ */



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