Blame gio/gmenuexporter.c

Packit ae235b
/*
Packit ae235b
 * Copyright © 2011 Canonical Ltd.
Packit ae235b
 *
Packit ae235b
 *  This library is free software; you can redistribute it and/or
Packit ae235b
 *  modify it under the terms of the GNU Lesser General Public
Packit ae235b
 *  License as published by the Free Software Foundation; either
Packit ae235b
 *  version 2.1 of the License, or (at your option) any later version.
Packit ae235b
 *
Packit ae235b
 *  This library is distributed in the hope that it will be useful, but
Packit ae235b
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
Packit ae235b
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit ae235b
 *  Lesser General Public License for more details.
Packit ae235b
 *
Packit ae235b
 *  You should have received a copy of the GNU Lesser General Public
Packit ae235b
 *  License along with this library; if not, see <http://www.gnu.org/licenses/>.
Packit ae235b
 *
Packit ae235b
 * Author: Ryan Lortie <desrt@desrt.ca>
Packit ae235b
 */
Packit ae235b
Packit ae235b
#include "config.h"
Packit ae235b
Packit ae235b
#include "gmenuexporter.h"
Packit ae235b
Packit ae235b
#include "gdbusmethodinvocation.h"
Packit ae235b
#include "gdbusintrospection.h"
Packit ae235b
#include "gdbusnamewatching.h"
Packit ae235b
#include "gdbuserror.h"
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * SECTION:gmenuexporter
Packit ae235b
 * @title: GMenuModel exporter
Packit ae235b
 * @short_description: Export GMenuModels on D-Bus
Packit ae235b
 * @include: gio/gio.h
Packit ae235b
 * @see_also: #GMenuModel, #GDBusMenuModel
Packit ae235b
 *
Packit ae235b
 * These functions support exporting a #GMenuModel on D-Bus.
Packit ae235b
 * The D-Bus interface that is used is a private implementation
Packit ae235b
 * detail.
Packit ae235b
 *
Packit ae235b
 * To access an exported #GMenuModel remotely, use
Packit ae235b
 * g_dbus_menu_model_get() to obtain a #GDBusMenuModel.
Packit ae235b
 */
Packit ae235b
Packit ae235b
/* {{{1 D-Bus Interface description */
Packit ae235b
Packit ae235b
/* For documentation of this interface, see
Packit ae235b
 * https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
Packit ae235b
 */
Packit ae235b
Packit ae235b
static GDBusInterfaceInfo *
Packit ae235b
org_gtk_Menus_get_interface (void)
Packit ae235b
{
Packit ae235b
  static GDBusInterfaceInfo *interface_info;
Packit ae235b
Packit ae235b
  if (interface_info == NULL)
Packit ae235b
    {
Packit ae235b
      GError *error = NULL;
Packit ae235b
      GDBusNodeInfo *info;
Packit ae235b
Packit ae235b
      info = g_dbus_node_info_new_for_xml ("<node>"
Packit ae235b
                                           "  <interface name='org.gtk.Menus'>"
Packit ae235b
                                           "    <method name='Start'>"
Packit ae235b
                                           "      <arg type='au' name='groups' direction='in'/>"
Packit ae235b
                                           "      <arg type='a(uuaa{sv})' name='content' direction='out'/>"
Packit ae235b
                                           "    </method>"
Packit ae235b
                                           "    <method name='End'>"
Packit ae235b
                                           "      <arg type='au' name='groups' direction='in'/>"
Packit ae235b
                                           "    </method>"
Packit ae235b
                                           "    <signal name='Changed'>"
Packit ae235b
                                           "      arg type='a(uuuuaa{sv})' name='changes'/>"
Packit ae235b
                                           "    </signal>"
Packit ae235b
                                           "  </interface>"
Packit ae235b
                                           "</node>", &error);
Packit ae235b
      if (info == NULL)
Packit ae235b
        g_error ("%s\n", error->message);
Packit ae235b
      interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
Packit ae235b
      g_assert (interface_info != NULL);
Packit ae235b
      g_dbus_interface_info_ref (interface_info);
Packit ae235b
      g_dbus_node_info_unref (info);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return interface_info;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* {{{1 Forward declarations */
Packit ae235b
typedef struct _GMenuExporterMenu                           GMenuExporterMenu;
Packit ae235b
typedef struct _GMenuExporterLink                           GMenuExporterLink;
Packit ae235b
typedef struct _GMenuExporterGroup                          GMenuExporterGroup;
Packit ae235b
typedef struct _GMenuExporterRemote                         GMenuExporterRemote;
Packit ae235b
typedef struct _GMenuExporterWatch                          GMenuExporterWatch;
Packit ae235b
typedef struct _GMenuExporter                               GMenuExporter;
Packit ae235b
Packit ae235b
static gboolean                 g_menu_exporter_group_is_subscribed    (GMenuExporterGroup *group);
Packit ae235b
static guint                    g_menu_exporter_group_get_id           (GMenuExporterGroup *group);
Packit ae235b
static GMenuExporter *          g_menu_exporter_group_get_exporter     (GMenuExporterGroup *group);
Packit ae235b
static GMenuExporterMenu *      g_menu_exporter_group_add_menu         (GMenuExporterGroup *group,
Packit ae235b
                                                                        GMenuModel         *model);
Packit ae235b
static void                     g_menu_exporter_group_remove_menu      (GMenuExporterGroup *group,
Packit ae235b
                                                                        guint               id);
Packit ae235b
Packit ae235b
static GMenuExporterGroup *     g_menu_exporter_create_group           (GMenuExporter      *exporter);
Packit ae235b
static GMenuExporterGroup *     g_menu_exporter_lookup_group           (GMenuExporter      *exporter,
Packit ae235b
                                                                        guint               group_id);
Packit ae235b
static void                     g_menu_exporter_report                 (GMenuExporter      *exporter,
Packit ae235b
                                                                        GVariant           *report);
Packit ae235b
static void                     g_menu_exporter_remove_group           (GMenuExporter      *exporter,
Packit ae235b
                                                                        guint               id);
Packit ae235b
Packit ae235b
/* {{{1 GMenuExporterLink, GMenuExporterMenu */
Packit ae235b
Packit ae235b
struct _GMenuExporterMenu
Packit ae235b
{
Packit ae235b
  GMenuExporterGroup *group;
Packit ae235b
  guint               id;
Packit ae235b
Packit ae235b
  GMenuModel *model;
Packit ae235b
  gulong      handler_id;
Packit ae235b
  GSequence  *item_links;
Packit ae235b
};
Packit ae235b
Packit ae235b
struct _GMenuExporterLink
Packit ae235b
{
Packit ae235b
  gchar             *name;
Packit ae235b
  GMenuExporterMenu *menu;
Packit ae235b
  GMenuExporterLink *next;
Packit ae235b
};
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_menu_free (GMenuExporterMenu *menu)
Packit ae235b
{
Packit ae235b
  g_menu_exporter_group_remove_menu (menu->group, menu->id);
Packit ae235b
Packit ae235b
  if (menu->handler_id != 0)
Packit ae235b
    g_signal_handler_disconnect (menu->model, menu->handler_id);
Packit ae235b
Packit ae235b
  if (menu->item_links != NULL)
Packit ae235b
    g_sequence_free (menu->item_links);
Packit ae235b
Packit ae235b
  g_object_unref (menu->model);
Packit ae235b
Packit ae235b
  g_slice_free (GMenuExporterMenu, menu);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_link_free (gpointer data)
Packit ae235b
{
Packit ae235b
  GMenuExporterLink *link = data;
Packit ae235b
Packit ae235b
  while (link != NULL)
Packit ae235b
    {
Packit ae235b
      GMenuExporterLink *tmp = link;
Packit ae235b
      link = tmp->next;
Packit ae235b
Packit ae235b
      g_menu_exporter_menu_free (tmp->menu);
Packit ae235b
      g_free (tmp->name);
Packit ae235b
Packit ae235b
      g_slice_free (GMenuExporterLink, tmp);
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
static GMenuExporterLink *
Packit ae235b
g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
Packit ae235b
                                   gint               position)
Packit ae235b
{
Packit ae235b
  GMenuExporterLink *list = NULL;
Packit ae235b
  GMenuLinkIter *iter;
Packit ae235b
  const char *name;
Packit ae235b
  GMenuModel *model;
Packit ae235b
Packit ae235b
  iter = g_menu_model_iterate_item_links (menu->model, position);
Packit ae235b
Packit ae235b
  while (g_menu_link_iter_get_next (iter, &name, &model))
Packit ae235b
    {
Packit ae235b
      GMenuExporterGroup *group;
Packit ae235b
      GMenuExporterLink *tmp;
Packit ae235b
Packit ae235b
      /* keep sections in the same group, but create new groups
Packit ae235b
       * otherwise
Packit ae235b
       */
Packit ae235b
      if (!g_str_equal (name, "section"))
Packit ae235b
        group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
Packit ae235b
      else
Packit ae235b
        group = menu->group;
Packit ae235b
Packit ae235b
      tmp = g_slice_new (GMenuExporterLink);
Packit ae235b
      tmp->name = g_strconcat (":", name, NULL);
Packit ae235b
      tmp->menu = g_menu_exporter_group_add_menu (group, model);
Packit ae235b
      tmp->next = list;
Packit ae235b
      list = tmp;
Packit ae235b
Packit ae235b
      g_object_unref (model);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_object_unref (iter);
Packit ae235b
Packit ae235b
  return list;
Packit ae235b
}
Packit ae235b
Packit ae235b
static GVariant *
Packit ae235b
g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
Packit ae235b
                                    gint               position)
Packit ae235b
{
Packit ae235b
  GMenuAttributeIter *attr_iter;
Packit ae235b
  GVariantBuilder builder;
Packit ae235b
  GSequenceIter *iter;
Packit ae235b
  GMenuExporterLink *link;
Packit ae235b
  const char *name;
Packit ae235b
  GVariant *value;
Packit ae235b
Packit ae235b
  g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
Packit ae235b
Packit ae235b
  attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
Packit ae235b
  while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
Packit ae235b
    {
Packit ae235b
      g_variant_builder_add (&builder, "{sv}", name, value);
Packit ae235b
      g_variant_unref (value);
Packit ae235b
    }
Packit ae235b
  g_object_unref (attr_iter);
Packit ae235b
Packit ae235b
  iter = g_sequence_get_iter_at_pos (menu->item_links, position);
Packit ae235b
  for (link = g_sequence_get (iter); link; link = link->next)
Packit ae235b
    g_variant_builder_add (&builder, "{sv}", link->name,
Packit ae235b
                           g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id));
Packit ae235b
Packit ae235b
  return g_variant_builder_end (&builder);
Packit ae235b
}
Packit ae235b
Packit ae235b
static GVariant *
Packit ae235b
g_menu_exporter_menu_list (GMenuExporterMenu *menu)
Packit ae235b
{
Packit ae235b
  GVariantBuilder builder;
Packit ae235b
  gint i, n;
Packit ae235b
Packit ae235b
  g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
Packit ae235b
Packit ae235b
  n = g_sequence_get_length (menu->item_links);
Packit ae235b
  for (i = 0; i < n; i++)
Packit ae235b
    g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
Packit ae235b
Packit ae235b
  return g_variant_builder_end (&builder);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_menu_items_changed (GMenuModel *model,
Packit ae235b
                                    gint        position,
Packit ae235b
                                    gint        removed,
Packit ae235b
                                    gint        added,
Packit ae235b
                                    gpointer    user_data)
Packit ae235b
{
Packit ae235b
  GMenuExporterMenu *menu = user_data;
Packit ae235b
  GSequenceIter *point;
Packit ae235b
  gint i;
Packit ae235b
Packit ae235b
  g_assert (menu->model == model);
Packit ae235b
  g_assert (menu->item_links != NULL);
Packit ae235b
  g_assert (position + removed <= g_sequence_get_length (menu->item_links));
Packit ae235b
Packit ae235b
  point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
Packit ae235b
  g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
Packit ae235b
Packit ae235b
  for (i = position; i < position + added; i++)
Packit ae235b
    g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i));
Packit ae235b
Packit ae235b
  if (g_menu_exporter_group_is_subscribed (menu->group))
Packit ae235b
    {
Packit ae235b
      GVariantBuilder builder;
Packit ae235b
Packit ae235b
      g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuuuaa{sv})"));
Packit ae235b
      g_variant_builder_add (&builder, "u", g_menu_exporter_group_get_id (menu->group));
Packit ae235b
      g_variant_builder_add (&builder, "u", menu->id);
Packit ae235b
      g_variant_builder_add (&builder, "u", position);
Packit ae235b
      g_variant_builder_add (&builder, "u", removed);
Packit ae235b
Packit ae235b
      g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}"));
Packit ae235b
      for (i = position; i < position + added; i++)
Packit ae235b
        g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
Packit ae235b
      g_variant_builder_close (&builder);
Packit ae235b
Packit ae235b
      g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder));
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_menu_prepare (GMenuExporterMenu *menu)
Packit ae235b
{
Packit ae235b
  gint n_items;
Packit ae235b
Packit ae235b
  g_assert (menu->item_links == NULL);
Packit ae235b
Packit ae235b
  if (g_menu_model_is_mutable (menu->model))
Packit ae235b
    menu->handler_id = g_signal_connect (menu->model, "items-changed",
Packit ae235b
                                         G_CALLBACK (g_menu_exporter_menu_items_changed), menu);
Packit ae235b
Packit ae235b
  menu->item_links = g_sequence_new (g_menu_exporter_link_free);
Packit ae235b
Packit ae235b
  n_items = g_menu_model_get_n_items (menu->model);
Packit ae235b
  if (n_items)
Packit ae235b
    g_menu_exporter_menu_items_changed (menu->model, 0, 0, n_items, menu);
Packit ae235b
}
Packit ae235b
Packit ae235b
static GMenuExporterMenu *
Packit ae235b
g_menu_exporter_menu_new (GMenuExporterGroup *group,
Packit ae235b
                          guint               id,
Packit ae235b
                          GMenuModel         *model)
Packit ae235b
{
Packit ae235b
  GMenuExporterMenu *menu;
Packit ae235b
Packit ae235b
  menu = g_slice_new0 (GMenuExporterMenu);
Packit ae235b
  menu->group = group;
Packit ae235b
  menu->id = id;
Packit ae235b
  menu->model = g_object_ref (model);
Packit ae235b
Packit ae235b
  return menu;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* {{{1 GMenuExporterGroup */
Packit ae235b
Packit ae235b
struct _GMenuExporterGroup
Packit ae235b
{
Packit ae235b
  GMenuExporter *exporter;
Packit ae235b
  guint          id;
Packit ae235b
Packit ae235b
  GHashTable *menus;
Packit ae235b
  guint       next_menu_id;
Packit ae235b
  gboolean    prepared;
Packit ae235b
Packit ae235b
  gint subscribed;
Packit ae235b
};
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
Packit ae235b
{
Packit ae235b
  if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0)
Packit ae235b
    {
Packit ae235b
      g_menu_exporter_remove_group (group->exporter, group->id);
Packit ae235b
Packit ae235b
      g_hash_table_unref (group->menus);
Packit ae235b
Packit ae235b
      g_slice_free (GMenuExporterGroup, group);
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
Packit ae235b
                                 GVariantBuilder    *builder)
Packit ae235b
{
Packit ae235b
  GHashTableIter iter;
Packit ae235b
  gpointer key, val;
Packit ae235b
Packit ae235b
  if (!group->prepared)
Packit ae235b
    {
Packit ae235b
      GMenuExporterMenu *menu;
Packit ae235b
Packit ae235b
      /* set this first, so that any menus created during the
Packit ae235b
       * preparation of the first menu also end up in the prepared
Packit ae235b
       * state.
Packit ae235b
       * */
Packit ae235b
      group->prepared = TRUE;
Packit ae235b
Packit ae235b
      menu = g_hash_table_lookup (group->menus, 0);
Packit ae235b
Packit ae235b
      /* If the group was created by a subscription and does not yet
Packit ae235b
       * exist, it won't have a root menu...
Packit ae235b
       *
Packit ae235b
       * That menu will be prepared if it is ever added (due to
Packit ae235b
       * group->prepared == TRUE).
Packit ae235b
       */
Packit ae235b
      if (menu)
Packit ae235b
        g_menu_exporter_menu_prepare (menu);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  group->subscribed++;
Packit ae235b
Packit ae235b
  g_hash_table_iter_init (&iter, group->menus);
Packit ae235b
  while (g_hash_table_iter_next (&iter, &key, &val))
Packit ae235b
    {
Packit ae235b
      guint id = GPOINTER_TO_INT (key);
Packit ae235b
      GMenuExporterMenu *menu = val;
Packit ae235b
Packit ae235b
      if (!g_sequence_is_empty (menu->item_links))
Packit ae235b
        {
Packit ae235b
          g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})"));
Packit ae235b
          g_variant_builder_add (builder, "u", group->id);
Packit ae235b
          g_variant_builder_add (builder, "u", id);
Packit ae235b
          g_variant_builder_add_value (builder, g_menu_exporter_menu_list (menu));
Packit ae235b
          g_variant_builder_close (builder);
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_group_unsubscribe (GMenuExporterGroup *group,
Packit ae235b
                                   gint                count)
Packit ae235b
{
Packit ae235b
  g_assert (group->subscribed >= count);
Packit ae235b
Packit ae235b
  group->subscribed -= count;
Packit ae235b
Packit ae235b
  g_menu_exporter_group_check_if_useless (group);
Packit ae235b
}
Packit ae235b
Packit ae235b
static GMenuExporter *
Packit ae235b
g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
Packit ae235b
{
Packit ae235b
  return group->exporter;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
Packit ae235b
{
Packit ae235b
  return group->subscribed > 0;
Packit ae235b
}
Packit ae235b
Packit ae235b
static guint
Packit ae235b
g_menu_exporter_group_get_id (GMenuExporterGroup *group)
Packit ae235b
{
Packit ae235b
  return group->id;
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
Packit ae235b
                                   guint               id)
Packit ae235b
{
Packit ae235b
  g_hash_table_remove (group->menus, GINT_TO_POINTER (id));
Packit ae235b
Packit ae235b
  g_menu_exporter_group_check_if_useless (group);
Packit ae235b
}
Packit ae235b
Packit ae235b
static GMenuExporterMenu *
Packit ae235b
g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
Packit ae235b
                                GMenuModel         *model)
Packit ae235b
{
Packit ae235b
  GMenuExporterMenu *menu;
Packit ae235b
  guint id;
Packit ae235b
Packit ae235b
  id = group->next_menu_id++;
Packit ae235b
  menu = g_menu_exporter_menu_new (group, id, model);
Packit ae235b
  g_hash_table_insert (group->menus, GINT_TO_POINTER (id), menu);
Packit ae235b
Packit ae235b
  if (group->prepared)
Packit ae235b
    g_menu_exporter_menu_prepare (menu);
Packit ae235b
Packit ae235b
  return menu;
Packit ae235b
}
Packit ae235b
Packit ae235b
static GMenuExporterGroup *
Packit ae235b
g_menu_exporter_group_new (GMenuExporter *exporter,
Packit ae235b
                           guint          id)
Packit ae235b
{
Packit ae235b
  GMenuExporterGroup *group;
Packit ae235b
Packit ae235b
  group = g_slice_new0 (GMenuExporterGroup);
Packit ae235b
  group->menus = g_hash_table_new (NULL, NULL);
Packit ae235b
  group->exporter = exporter;
Packit ae235b
  group->id = id;
Packit ae235b
Packit ae235b
  return group;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* {{{1 GMenuExporterRemote */
Packit ae235b
Packit ae235b
struct _GMenuExporterRemote
Packit ae235b
{
Packit ae235b
  GMenuExporter *exporter;
Packit ae235b
  GHashTable    *watches;
Packit ae235b
  guint          watch_id;
Packit ae235b
};
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
Packit ae235b
                                  guint                group_id,
Packit ae235b
                                  GVariantBuilder     *builder)
Packit ae235b
{
Packit ae235b
  GMenuExporterGroup *group;
Packit ae235b
  guint count;
Packit ae235b
Packit ae235b
  count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
Packit ae235b
  g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count + 1));
Packit ae235b
Packit ae235b
  /* Group will be created (as empty/unsubscribed if it does not exist) */
Packit ae235b
  group = g_menu_exporter_lookup_group (remote->exporter, group_id);
Packit ae235b
  g_menu_exporter_group_subscribe (group, builder);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote,
Packit ae235b
                                    guint                group_id)
Packit ae235b
{
Packit ae235b
  GMenuExporterGroup *group;
Packit ae235b
  guint count;
Packit ae235b
Packit ae235b
  count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
Packit ae235b
Packit ae235b
  if (count == 0)
Packit ae235b
    return;
Packit ae235b
Packit ae235b
  if (count != 1)
Packit ae235b
    g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count - 1));
Packit ae235b
  else
Packit ae235b
    g_hash_table_remove (remote->watches, GINT_TO_POINTER (group_id));
Packit ae235b
Packit ae235b
  group = g_menu_exporter_lookup_group (remote->exporter, group_id);
Packit ae235b
  g_menu_exporter_group_unsubscribe (group, 1);
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
Packit ae235b
{
Packit ae235b
  return g_hash_table_size (remote->watches) != 0;
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_remote_free (gpointer data)
Packit ae235b
{
Packit ae235b
  GMenuExporterRemote *remote = data;
Packit ae235b
  GHashTableIter iter;
Packit ae235b
  gpointer key, val;
Packit ae235b
Packit ae235b
  g_hash_table_iter_init (&iter, remote->watches);
Packit ae235b
  while (g_hash_table_iter_next (&iter, &key, &val))
Packit ae235b
    {
Packit ae235b
      GMenuExporterGroup *group;
Packit ae235b
Packit ae235b
      group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key));
Packit ae235b
      g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val));
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (remote->watch_id > 0)
Packit ae235b
    g_bus_unwatch_name (remote->watch_id);
Packit ae235b
Packit ae235b
  g_hash_table_unref (remote->watches);
Packit ae235b
Packit ae235b
  g_slice_free (GMenuExporterRemote, remote);
Packit ae235b
}
Packit ae235b
Packit ae235b
static GMenuExporterRemote *
Packit ae235b
g_menu_exporter_remote_new (GMenuExporter *exporter,
Packit ae235b
                            guint          watch_id)
Packit ae235b
{
Packit ae235b
  GMenuExporterRemote *remote;
Packit ae235b
Packit ae235b
  remote = g_slice_new0 (GMenuExporterRemote);
Packit ae235b
  remote->exporter = exporter;
Packit ae235b
  remote->watches = g_hash_table_new (NULL, NULL);
Packit ae235b
  remote->watch_id = watch_id;
Packit ae235b
Packit ae235b
  return remote;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* {{{1 GMenuExporter */
Packit ae235b
Packit ae235b
struct _GMenuExporter
Packit ae235b
{
Packit ae235b
  GDBusConnection *connection;
Packit ae235b
  gchar *object_path;
Packit ae235b
  guint registration_id;
Packit ae235b
  GHashTable *groups;
Packit ae235b
  guint next_group_id;
Packit ae235b
Packit ae235b
  GMenuExporterMenu *root;
Packit ae235b
  GMenuExporterRemote *peer_remote;
Packit ae235b
  GHashTable *remotes;
Packit ae235b
};
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_name_vanished (GDBusConnection *connection,
Packit ae235b
                               const gchar     *name,
Packit ae235b
                               gpointer         user_data)
Packit ae235b
{
Packit ae235b
  GMenuExporter *exporter = user_data;
Packit ae235b
Packit ae235b
  /* connection == NULL when we get called because the connection closed */
Packit ae235b
  g_assert (exporter->connection == connection || connection == NULL);
Packit ae235b
Packit ae235b
  g_hash_table_remove (exporter->remotes, name);
Packit ae235b
}
Packit ae235b
Packit ae235b
static GVariant *
Packit ae235b
g_menu_exporter_subscribe (GMenuExporter *exporter,
Packit ae235b
                           const gchar   *sender,
Packit ae235b
                           GVariant      *group_ids)
Packit ae235b
{
Packit ae235b
  GMenuExporterRemote *remote;
Packit ae235b
  GVariantBuilder builder;
Packit ae235b
  GVariantIter iter;
Packit ae235b
  guint32 id;
Packit ae235b
Packit ae235b
  if (sender != NULL)
Packit ae235b
    remote = g_hash_table_lookup (exporter->remotes, sender);
Packit ae235b
  else
Packit ae235b
    remote = exporter->peer_remote;
Packit ae235b
Packit ae235b
  if (remote == NULL)
Packit ae235b
    {
Packit ae235b
      if (sender != NULL)
Packit ae235b
        {
Packit ae235b
          guint watch_id;
Packit ae235b
Packit ae235b
          watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
Packit ae235b
                                                     NULL, g_menu_exporter_name_vanished, exporter, NULL);
Packit ae235b
          remote = g_menu_exporter_remote_new (exporter, watch_id);
Packit ae235b
          g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
Packit ae235b
        }
Packit ae235b
      else
Packit ae235b
        remote = exporter->peer_remote =
Packit ae235b
          g_menu_exporter_remote_new (exporter, 0);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
Packit ae235b
Packit ae235b
  g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
Packit ae235b
Packit ae235b
  g_variant_iter_init (&iter, group_ids);
Packit ae235b
  while (g_variant_iter_next (&iter, "u", &id))
Packit ae235b
    g_menu_exporter_remote_subscribe (remote, id, &builder);
Packit ae235b
Packit ae235b
  g_variant_builder_close (&builder);
Packit ae235b
Packit ae235b
  return g_variant_builder_end (&builder);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_unsubscribe (GMenuExporter *exporter,
Packit ae235b
                             const gchar   *sender,
Packit ae235b
                             GVariant      *group_ids)
Packit ae235b
{
Packit ae235b
  GMenuExporterRemote *remote;
Packit ae235b
  GVariantIter iter;
Packit ae235b
  guint32 id;
Packit ae235b
Packit ae235b
  if (sender != NULL)
Packit ae235b
    remote = g_hash_table_lookup (exporter->remotes, sender);
Packit ae235b
  else
Packit ae235b
    remote = exporter->peer_remote;
Packit ae235b
Packit ae235b
  if (remote == NULL)
Packit ae235b
    return;
Packit ae235b
Packit ae235b
  g_variant_iter_init (&iter, group_ids);
Packit ae235b
  while (g_variant_iter_next (&iter, "u", &id))
Packit ae235b
    g_menu_exporter_remote_unsubscribe (remote, id);
Packit ae235b
Packit ae235b
  if (!g_menu_exporter_remote_has_subscriptions (remote))
Packit ae235b
    {
Packit ae235b
      if (sender != NULL)
Packit ae235b
        g_hash_table_remove (exporter->remotes, sender);
Packit ae235b
      else
Packit ae235b
        g_clear_pointer (&exporter->peer_remote, g_menu_exporter_remote_free);
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_report (GMenuExporter *exporter,
Packit ae235b
                        GVariant      *report)
Packit ae235b
{
Packit ae235b
  GVariantBuilder builder;
Packit ae235b
Packit ae235b
  g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
Packit ae235b
  g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
Packit ae235b
  g_variant_builder_add_value (&builder, report);
Packit ae235b
  g_variant_builder_close (&builder);
Packit ae235b
Packit ae235b
  g_dbus_connection_emit_signal (exporter->connection,
Packit ae235b
                                 NULL,
Packit ae235b
                                 exporter->object_path,
Packit ae235b
                                 "org.gtk.Menus", "Changed",
Packit ae235b
                                 g_variant_builder_end (&builder),
Packit ae235b
                                 NULL);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_remove_group (GMenuExporter *exporter,
Packit ae235b
                              guint          id)
Packit ae235b
{
Packit ae235b
  g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
Packit ae235b
}
Packit ae235b
Packit ae235b
static GMenuExporterGroup *
Packit ae235b
g_menu_exporter_lookup_group (GMenuExporter *exporter,
Packit ae235b
                              guint          group_id)
Packit ae235b
{
Packit ae235b
  GMenuExporterGroup *group;
Packit ae235b
Packit ae235b
  group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
Packit ae235b
Packit ae235b
  if (group == NULL)
Packit ae235b
    {
Packit ae235b
      group = g_menu_exporter_group_new (exporter, group_id);
Packit ae235b
      g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return group;
Packit ae235b
}
Packit ae235b
Packit ae235b
static GMenuExporterGroup *
Packit ae235b
g_menu_exporter_create_group (GMenuExporter *exporter)
Packit ae235b
{
Packit ae235b
  GMenuExporterGroup *group;
Packit ae235b
  guint id;
Packit ae235b
Packit ae235b
  id = exporter->next_group_id++;
Packit ae235b
  group = g_menu_exporter_group_new (exporter, id);
Packit ae235b
  g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
Packit ae235b
Packit ae235b
  return group;
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_free (gpointer user_data)
Packit ae235b
{
Packit ae235b
  GMenuExporter *exporter = user_data;
Packit ae235b
Packit ae235b
  g_menu_exporter_menu_free (exporter->root);
Packit ae235b
  g_clear_pointer (&exporter->peer_remote, g_menu_exporter_remote_free);
Packit ae235b
  g_hash_table_unref (exporter->remotes);
Packit ae235b
  g_hash_table_unref (exporter->groups);
Packit ae235b
  g_object_unref (exporter->connection);
Packit ae235b
  g_free (exporter->object_path);
Packit ae235b
Packit ae235b
  g_slice_free (GMenuExporter, exporter);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_menu_exporter_method_call (GDBusConnection       *connection,
Packit ae235b
                             const gchar           *sender,
Packit ae235b
                             const gchar           *object_path,
Packit ae235b
                             const gchar           *interface_name,
Packit ae235b
                             const gchar           *method_name,
Packit ae235b
                             GVariant              *parameters,
Packit ae235b
                             GDBusMethodInvocation *invocation,
Packit ae235b
                             gpointer               user_data)
Packit ae235b
{
Packit ae235b
  GMenuExporter *exporter = user_data;
Packit ae235b
  GVariant *group_ids;
Packit ae235b
Packit ae235b
  group_ids = g_variant_get_child_value (parameters, 0);
Packit ae235b
Packit ae235b
  if (g_str_equal (method_name, "Start"))
Packit ae235b
    g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
Packit ae235b
Packit ae235b
  else if (g_str_equal (method_name, "End"))
Packit ae235b
    {
Packit ae235b
      g_menu_exporter_unsubscribe (exporter, sender, group_ids);
Packit ae235b
      g_dbus_method_invocation_return_value (invocation, NULL);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  else
Packit ae235b
    g_assert_not_reached ();
Packit ae235b
Packit ae235b
  g_variant_unref (group_ids);
Packit ae235b
}
Packit ae235b
Packit ae235b
/* {{{1 Public API */
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_dbus_connection_export_menu_model:
Packit ae235b
 * @connection: a #GDBusConnection
Packit ae235b
 * @object_path: a D-Bus object path
Packit ae235b
 * @menu: a #GMenuModel
Packit ae235b
 * @error: return location for an error, or %NULL
Packit ae235b
 *
Packit ae235b
 * Exports @menu on @connection at @object_path.
Packit ae235b
 *
Packit ae235b
 * The implemented D-Bus API should be considered private.
Packit ae235b
 * It is subject to change in the future.
Packit ae235b
 *
Packit ae235b
 * An object path can only have one menu model exported on it. If this
Packit ae235b
 * constraint is violated, the export will fail and 0 will be
Packit ae235b
 * returned (with @error set accordingly).
Packit ae235b
 *
Packit ae235b
 * You can unexport the menu model using
Packit ae235b
 * g_dbus_connection_unexport_menu_model() with the return value of
Packit ae235b
 * this function.
Packit ae235b
 *
Packit ae235b
 * Returns: the ID of the export (never zero), or 0 in case of failure
Packit ae235b
 *
Packit ae235b
 * Since: 2.32
Packit ae235b
 */
Packit ae235b
guint
Packit ae235b
g_dbus_connection_export_menu_model (GDBusConnection  *connection,
Packit ae235b
                                     const gchar      *object_path,
Packit ae235b
                                     GMenuModel       *menu,
Packit ae235b
                                     GError          **error)
Packit ae235b
{
Packit ae235b
  const GDBusInterfaceVTable vtable = {
Packit ae235b
    g_menu_exporter_method_call,
Packit ae235b
  };
Packit ae235b
  GMenuExporter *exporter;
Packit ae235b
  guint id;
Packit ae235b
Packit ae235b
  exporter = g_slice_new0 (GMenuExporter);
Packit ae235b
Packit ae235b
  id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
Packit ae235b
                                          &vtable, exporter, g_menu_exporter_free, error);
Packit ae235b
Packit ae235b
  if (id == 0)
Packit ae235b
    {
Packit ae235b
      g_slice_free (GMenuExporter, exporter);
Packit ae235b
      return 0;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  exporter->connection = g_object_ref (connection);
Packit ae235b
  exporter->object_path = g_strdup (object_path);
Packit ae235b
  exporter->groups = g_hash_table_new (NULL, NULL);
Packit ae235b
  exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
Packit ae235b
  exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), menu);
Packit ae235b
Packit ae235b
  return id;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_dbus_connection_unexport_menu_model:
Packit ae235b
 * @connection: a #GDBusConnection
Packit ae235b
 * @export_id: the ID from g_dbus_connection_export_menu_model()
Packit ae235b
 *
Packit ae235b
 * Reverses the effect of a previous call to
Packit ae235b
 * g_dbus_connection_export_menu_model().
Packit ae235b
 *
Packit ae235b
 * It is an error to call this function with an ID that wasn't returned
Packit ae235b
 * from g_dbus_connection_export_menu_model() or to call it with the
Packit ae235b
 * same ID more than once.
Packit ae235b
 *
Packit ae235b
 * Since: 2.32
Packit ae235b
 */
Packit ae235b
void
Packit ae235b
g_dbus_connection_unexport_menu_model (GDBusConnection *connection,
Packit ae235b
                                       guint            export_id)
Packit ae235b
{
Packit ae235b
  g_dbus_connection_unregister_object (connection, export_id);
Packit ae235b
}
Packit ae235b
Packit ae235b
/* {{{1 Epilogue */
Packit ae235b
/* vim:set foldmethod=marker: */