(root)/
glib-2.79.0/
gio/
gactiongroupexporter.c
       1  /*
       2   * Copyright © 2010 Codethink Limited
       3   * Copyright © 2011 Canonical Limited
       4   *
       5   * SPDX-License-Identifier: LGPL-2.1-or-later
       6   *
       7   * This library is free software; you can redistribute it and/or
       8   * modify it under the terms of the GNU Lesser General Public
       9   * License as published by the Free Software Foundation; either
      10   * version 2.1 of the License, or (at your option) any later version.
      11   *
      12   * This library is distributed in the hope that it will be useful,
      13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      15   * Lesser General Public License for more details.
      16   *
      17   * You should have received a copy of the GNU Lesser General
      18   * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
      19   *
      20   * Authors: Ryan Lortie <desrt@desrt.ca>
      21   */
      22  
      23  #include "config.h"
      24  
      25  #include "gactiongroupexporter.h"
      26  
      27  #include "gdbusmethodinvocation.h"
      28  #include "gremoteactiongroup.h"
      29  #include "gdbusintrospection.h"
      30  #include "gdbusconnection.h"
      31  #include "gactiongroup.h"
      32  #include "gdbuserror.h"
      33  
      34  /**
      35   * GActionGroupExporter:
      36   *
      37   * These functions support exporting a [class@Gio.ActionGroup] on D-Bus.
      38   * The D-Bus interface that is used is a private implementation
      39   * detail.
      40   *
      41   * To access an exported `GActionGroup` remotely, use
      42   * [method@Gio.DBusActionGroup.get] to obtain a [class@Gio.DBusActionGroup].
      43   */
      44  
      45  static GVariant *
      46  g_action_group_describe_action (GActionGroup *action_group,
      47                                  const gchar  *name)
      48  {
      49    const GVariantType *type;
      50    GVariantBuilder builder;
      51    gboolean enabled;
      52    GVariant *state;
      53  
      54    g_variant_builder_init (&builder, G_VARIANT_TYPE ("(bgav)"));
      55  
      56    enabled = g_action_group_get_action_enabled (action_group, name);
      57    g_variant_builder_add (&builder, "b", enabled);
      58  
      59    if ((type = g_action_group_get_action_parameter_type (action_group, name)))
      60      {
      61        gchar *str = g_variant_type_dup_string (type);
      62        g_variant_builder_add (&builder, "g", str);
      63        g_free (str);
      64      }
      65    else
      66      g_variant_builder_add (&builder, "g", "");
      67  
      68    g_variant_builder_open (&builder, G_VARIANT_TYPE ("av"));
      69    if ((state = g_action_group_get_action_state (action_group, name)))
      70      {
      71        g_variant_builder_add (&builder, "v", state);
      72        g_variant_unref (state);
      73      }
      74    g_variant_builder_close (&builder);
      75  
      76    return g_variant_builder_end (&builder);
      77  }
      78  
      79  /* Using XML saves us dozens of relocations vs. using the introspection
      80   * structure types.  We only need to burn cycles and memory if we
      81   * actually use the exporter -- not in every single app using GIO.
      82   *
      83   * It's also a lot easier to read. :)
      84   *
      85   * For documentation of this interface, see
      86   * https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
      87   */
      88  const char org_gtk_Actions_xml[] =
      89    "<node>"
      90    "  <interface name='org.gtk.Actions'>"
      91    "    <method name='List'>"
      92    "      <arg type='as' name='list' direction='out'/>"
      93    "    </method>"
      94    "    <method name='Describe'>"
      95    "      <arg type='s' name='action_name' direction='in'/>"
      96    "      <arg type='(bgav)' name='description' direction='out'/>"
      97    "    </method>"
      98    "    <method name='DescribeAll'>"
      99    "      <arg type='a{s(bgav)}' name='descriptions' direction='out'/>"
     100    "    </method>"
     101    "    <method name='Activate'>"
     102    "      <arg type='s' name='action_name' direction='in'/>"
     103    "      <arg type='av' name='parameter' direction='in'/>"
     104    "      <arg type='a{sv}' name='platform_data' direction='in'/>"
     105    "    </method>"
     106    "    <method name='SetState'>"
     107    "      <arg type='s' name='action_name' direction='in'/>"
     108    "      <arg type='v' name='value' direction='in'/>"
     109    "      <arg type='a{sv}' name='platform_data' direction='in'/>"
     110    "    </method>"
     111    "    <signal name='Changed'>"
     112    "      <arg type='as' name='removals'/>"
     113    "      <arg type='a{sb}' name='enable_changes'/>"
     114    "      <arg type='a{sv}' name='state_changes'/>"
     115    "      <arg type='a{s(bgav)}' name='additions'/>"
     116    "    </signal>"
     117    "  </interface>"
     118    "</node>";
     119  
     120  static GDBusInterfaceInfo *org_gtk_Actions;
     121  
     122  typedef struct
     123  {
     124    GActionGroup    *action_group;
     125    GDBusConnection *connection;
     126    GMainContext    *context;
     127    gchar           *object_path;
     128    GHashTable      *pending_changes;
     129    GSource         *pending_source;
     130  } GActionGroupExporter;
     131  
     132  #define ACTION_ADDED_EVENT             (1u<<0)
     133  #define ACTION_REMOVED_EVENT           (1u<<1)
     134  #define ACTION_STATE_CHANGED_EVENT     (1u<<2)
     135  #define ACTION_ENABLED_CHANGED_EVENT   (1u<<3)
     136  
     137  static gboolean
     138  g_action_group_exporter_dispatch_events (gpointer user_data)
     139  {
     140    GActionGroupExporter *exporter = user_data;
     141    GVariantBuilder removes;
     142    GVariantBuilder enabled_changes;
     143    GVariantBuilder state_changes;
     144    GVariantBuilder adds;
     145    GHashTableIter iter;
     146    gpointer value;
     147    gpointer key;
     148  
     149    g_variant_builder_init (&removes, G_VARIANT_TYPE_STRING_ARRAY);
     150    g_variant_builder_init (&enabled_changes, G_VARIANT_TYPE ("a{sb}"));
     151    g_variant_builder_init (&state_changes, G_VARIANT_TYPE ("a{sv}"));
     152    g_variant_builder_init (&adds, G_VARIANT_TYPE ("a{s(bgav)}"));
     153  
     154    g_hash_table_iter_init (&iter, exporter->pending_changes);
     155    while (g_hash_table_iter_next (&iter, &key, &value))
     156      {
     157        guint events = GPOINTER_TO_INT (value);
     158        const gchar *name = key;
     159  
     160        /* Adds and removes are incompatible with enabled or state
     161         * changes, but we must report at least one event.
     162         */
     163        g_assert (((events & (ACTION_ENABLED_CHANGED_EVENT | ACTION_STATE_CHANGED_EVENT)) == 0) !=
     164                  ((events & (ACTION_REMOVED_EVENT | ACTION_ADDED_EVENT)) == 0));
     165  
     166        if (events & ACTION_REMOVED_EVENT)
     167          g_variant_builder_add (&removes, "s", name);
     168  
     169        if (events & ACTION_ENABLED_CHANGED_EVENT)
     170          {
     171            gboolean enabled;
     172  
     173            enabled = g_action_group_get_action_enabled (exporter->action_group, name);
     174            g_variant_builder_add (&enabled_changes, "{sb}", name, enabled);
     175          }
     176  
     177        if (events & ACTION_STATE_CHANGED_EVENT)
     178          {
     179            GVariant *state;
     180  
     181            state = g_action_group_get_action_state (exporter->action_group, name);
     182            g_variant_builder_add (&state_changes, "{sv}", name, state);
     183            g_variant_unref (state);
     184          }
     185  
     186        if (events & ACTION_ADDED_EVENT)
     187          {
     188            GVariant *description;
     189  
     190            description = g_action_group_describe_action (exporter->action_group, name);
     191            g_variant_builder_add (&adds, "{s@(bgav)}", name, description);
     192          }
     193      }
     194  
     195    g_hash_table_remove_all (exporter->pending_changes);
     196  
     197    g_dbus_connection_emit_signal (exporter->connection, NULL, exporter->object_path,
     198                                   "org.gtk.Actions", "Changed",
     199                                   g_variant_new ("(asa{sb}a{sv}a{s(bgav)})",
     200                                                  &removes, &enabled_changes,
     201                                                  &state_changes, &adds),
     202                                   NULL);
     203  
     204    exporter->pending_source = NULL;
     205  
     206    return FALSE;
     207  }
     208  
     209  static void
     210  g_action_group_exporter_flush_queue (GActionGroupExporter *exporter)
     211  {
     212    if (exporter->pending_source)
     213      {
     214        g_source_destroy (exporter->pending_source);
     215        g_action_group_exporter_dispatch_events (exporter);
     216        g_assert (exporter->pending_source == NULL);
     217      }
     218  }
     219  
     220  static guint
     221  g_action_group_exporter_get_events (GActionGroupExporter *exporter,
     222                                      const gchar          *name)
     223  {
     224    return (gsize) g_hash_table_lookup (exporter->pending_changes, name);
     225  }
     226  
     227  static void
     228  g_action_group_exporter_set_events (GActionGroupExporter *exporter,
     229                                      const gchar          *name,
     230                                      guint                 events)
     231  {
     232    gboolean have_events;
     233    gboolean is_queued;
     234  
     235    if (events != 0)
     236      g_hash_table_insert (exporter->pending_changes, g_strdup (name), GINT_TO_POINTER (events));
     237    else
     238      g_hash_table_remove (exporter->pending_changes, name);
     239  
     240    have_events = g_hash_table_size (exporter->pending_changes) > 0;
     241    is_queued = exporter->pending_source != NULL;
     242  
     243    if (have_events && !is_queued)
     244      {
     245        GSource *source;
     246  
     247        source = g_idle_source_new ();
     248        exporter->pending_source = source;
     249        g_source_set_callback (source, g_action_group_exporter_dispatch_events, exporter, NULL);
     250        g_source_set_static_name (source, "[gio] g_action_group_exporter_dispatch_events");
     251        g_source_attach (source, exporter->context);
     252        g_source_unref (source);
     253      }
     254  
     255    if (!have_events && is_queued)
     256      {
     257        g_source_destroy (exporter->pending_source);
     258        exporter->pending_source = NULL;
     259      }
     260  }
     261  
     262  static void
     263  g_action_group_exporter_action_added (GActionGroup *action_group,
     264                                        const gchar  *action_name,
     265                                        gpointer      user_data)
     266  {
     267    GActionGroupExporter *exporter = user_data;
     268    guint event_mask;
     269  
     270    event_mask = g_action_group_exporter_get_events (exporter, action_name);
     271  
     272    /* The action is new, so we should not have any pending
     273     * enabled-changed or state-changed signals for it.
     274     */
     275    g_assert (~event_mask & (ACTION_STATE_CHANGED_EVENT |
     276                             ACTION_ENABLED_CHANGED_EVENT));
     277  
     278    event_mask |= ACTION_ADDED_EVENT;
     279  
     280    g_action_group_exporter_set_events (exporter, action_name, event_mask);
     281  }
     282  
     283  static void
     284  g_action_group_exporter_action_removed (GActionGroup *action_group,
     285                                          const gchar  *action_name,
     286                                          gpointer      user_data)
     287  {
     288    GActionGroupExporter *exporter = user_data;
     289    guint event_mask;
     290  
     291    event_mask = g_action_group_exporter_get_events (exporter, action_name);
     292  
     293    /* If the add event for this is still queued then just cancel it since
     294     * it's gone now.
     295     *
     296     * If the event was freshly added, there should not have been any
     297     * enabled or state changed events.
     298     */
     299    if (event_mask & ACTION_ADDED_EVENT)
     300      {
     301        g_assert (~event_mask & ~(ACTION_STATE_CHANGED_EVENT | ACTION_ENABLED_CHANGED_EVENT));
     302        event_mask &= ~ACTION_ADDED_EVENT;
     303      }
     304  
     305    /* Otherwise, queue a remove event.  Drop any state or enabled changes
     306     * that were queued before the remove. */
     307    else
     308      {
     309        event_mask |= ACTION_REMOVED_EVENT;
     310        event_mask &= ~(ACTION_STATE_CHANGED_EVENT | ACTION_ENABLED_CHANGED_EVENT);
     311      }
     312  
     313    g_action_group_exporter_set_events (exporter, action_name, event_mask);
     314  }
     315  
     316  static void
     317  g_action_group_exporter_action_state_changed (GActionGroup *action_group,
     318                                                const gchar  *action_name,
     319                                                GVariant     *value,
     320                                                gpointer      user_data)
     321  {
     322    GActionGroupExporter *exporter = user_data;
     323    guint event_mask;
     324  
     325    event_mask = g_action_group_exporter_get_events (exporter, action_name);
     326  
     327    /* If it was removed, it must have been added back.  Otherwise, why
     328     * are we hearing about changes?
     329     */
     330    g_assert (~event_mask & ACTION_REMOVED_EVENT ||
     331              event_mask & ACTION_ADDED_EVENT);
     332  
     333    /* If it is freshly added, don't also bother with the state change
     334     * signal since the updated state will be sent as part of the pending
     335     * add message.
     336     */
     337    if (~event_mask & ACTION_ADDED_EVENT)
     338      event_mask |= ACTION_STATE_CHANGED_EVENT;
     339  
     340    g_action_group_exporter_set_events (exporter, action_name, event_mask);
     341  }
     342  
     343  static void
     344  g_action_group_exporter_action_enabled_changed (GActionGroup *action_group,
     345                                                  const gchar  *action_name,
     346                                                  gboolean      enabled,
     347                                                  gpointer      user_data)
     348  {
     349    GActionGroupExporter *exporter = user_data;
     350    guint event_mask;
     351  
     352    event_mask = g_action_group_exporter_get_events (exporter, action_name);
     353  
     354    /* Reasoning as above. */
     355    g_assert (~event_mask & ACTION_REMOVED_EVENT ||
     356              event_mask & ACTION_ADDED_EVENT);
     357  
     358    if (~event_mask & ACTION_ADDED_EVENT)
     359      event_mask |= ACTION_ENABLED_CHANGED_EVENT;
     360  
     361    g_action_group_exporter_set_events (exporter, action_name, event_mask);
     362  }
     363  
     364  static void
     365  org_gtk_Actions_method_call (GDBusConnection       *connection,
     366                               const gchar           *sender,
     367                               const gchar           *object_path,
     368                               const gchar           *interface_name,
     369                               const gchar           *method_name,
     370                               GVariant              *parameters,
     371                               GDBusMethodInvocation *invocation,
     372                               gpointer               user_data)
     373  {
     374    GActionGroupExporter *exporter = user_data;
     375    GVariant *result = NULL;
     376  
     377    g_action_group_exporter_flush_queue (exporter);
     378  
     379    if (g_str_equal (method_name, "List"))
     380      {
     381        gchar **list;
     382  
     383        list = g_action_group_list_actions (exporter->action_group);
     384        result = g_variant_new ("(^as)", list);
     385        g_strfreev (list);
     386      }
     387  
     388    else if (g_str_equal (method_name, "Describe"))
     389      {
     390        const gchar *name;
     391        GVariant *desc;
     392  
     393        g_variant_get (parameters, "(&s)", &name);
     394  
     395        if (!g_action_group_has_action (exporter->action_group, name))
     396          {
     397            g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
     398                                                   "The named action ('%s') does not exist.", name);
     399            return;
     400          }
     401  
     402        desc = g_action_group_describe_action (exporter->action_group, name);
     403        result = g_variant_new ("(@(bgav))", desc);
     404      }
     405  
     406    else if (g_str_equal (method_name, "DescribeAll"))
     407      {
     408        GVariantBuilder builder;
     409        gchar **list;
     410        gint i;
     411  
     412        list = g_action_group_list_actions (exporter->action_group);
     413        g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{s(bgav)}"));
     414        for (i = 0; list[i]; i++)
     415          {
     416            const gchar *name = list[i];
     417            GVariant *description;
     418  
     419            description = g_action_group_describe_action (exporter->action_group, name);
     420            g_variant_builder_add (&builder, "{s@(bgav)}", name, description);
     421          }
     422        result = g_variant_new ("(a{s(bgav)})", &builder);
     423        g_strfreev (list);
     424      }
     425  
     426    else if (g_str_equal (method_name, "Activate"))
     427      {
     428        GVariant *parameter = NULL;
     429        GVariant *platform_data;
     430        GVariantIter *iter;
     431        const gchar *name;
     432        const GVariantType *parameter_type = NULL;
     433  
     434        g_variant_get (parameters, "(&sav@a{sv})", &name, &iter, &platform_data);
     435        g_variant_iter_next (iter, "v", &parameter);
     436        g_variant_iter_free (iter);
     437  
     438        /* Check the action exists and the parameter type matches. */
     439        if (!g_action_group_query_action (exporter->action_group,
     440                                          name, NULL, &parameter_type,
     441                                          NULL, NULL, NULL))
     442          {
     443            g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
     444                                                   "Unknown action ‘%s", name);
     445            g_clear_pointer (&parameter, g_variant_unref);
     446            g_variant_unref (platform_data);
     447            return;
     448          }
     449  
     450        if (!((parameter_type == NULL && parameter == NULL) ||
     451              (parameter_type != NULL && parameter != NULL && g_variant_is_of_type (parameter, parameter_type))))
     452          {
     453            g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
     454                                                   "Invalid parameter for action ‘%s’: expected type %s but got type %s",
     455                                                   name,
     456                                                   (parameter_type != NULL) ? (const gchar *) parameter_type : "()",
     457                                                   (parameter != NULL) ? g_variant_get_type_string (parameter) : "()");
     458            g_clear_pointer (&parameter, g_variant_unref);
     459            g_variant_unref (platform_data);
     460            return;
     461          }
     462  
     463        if (G_IS_REMOTE_ACTION_GROUP (exporter->action_group))
     464          g_remote_action_group_activate_action_full (G_REMOTE_ACTION_GROUP (exporter->action_group),
     465                                                      name, parameter, platform_data);
     466        else
     467          g_action_group_activate_action (exporter->action_group, name, parameter);
     468  
     469        if (parameter)
     470          g_variant_unref (parameter);
     471  
     472        g_variant_unref (platform_data);
     473      }
     474  
     475    else if (g_str_equal (method_name, "SetState"))
     476      {
     477        GVariant *platform_data;
     478        const gchar *name;
     479        GVariant *state;
     480        const GVariantType *state_type = NULL;
     481  
     482        g_variant_get (parameters, "(&sv@a{sv})", &name, &state, &platform_data);
     483  
     484        /* Check the action exists and the state type matches. */
     485        if (!g_action_group_query_action (exporter->action_group,
     486                                          name, NULL, NULL,
     487                                          &state_type, NULL, NULL))
     488          {
     489            g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
     490                                                   "Unknown action ‘%s", name);
     491            g_variant_unref (state);
     492            g_variant_unref (platform_data);
     493            return;
     494          }
     495  
     496        if (state_type == NULL)
     497          {
     498            g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
     499                                                   "Cannot change state of action ‘%s’ as it is stateless", name);
     500            g_variant_unref (state);
     501            g_variant_unref (platform_data);
     502            return;
     503          }
     504  
     505        if (!g_variant_is_of_type (state, state_type))
     506          {
     507            g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
     508                                                   "Invalid state for action ‘%s’: expected type %s but got type %s",
     509                                                   name,
     510                                                   (const gchar *) state_type,
     511                                                   g_variant_get_type_string (state));
     512            g_variant_unref (state);
     513            g_variant_unref (platform_data);
     514            return;
     515          }
     516  
     517        if (G_IS_REMOTE_ACTION_GROUP (exporter->action_group))
     518          g_remote_action_group_change_action_state_full (G_REMOTE_ACTION_GROUP (exporter->action_group),
     519                                                          name, state, platform_data);
     520        else
     521          g_action_group_change_action_state (exporter->action_group, name, state);
     522  
     523        g_variant_unref (platform_data);
     524        g_variant_unref (state);
     525      }
     526  
     527    else
     528      g_assert_not_reached ();
     529  
     530    g_dbus_method_invocation_return_value (invocation, result);
     531  }
     532  
     533  static void
     534  g_action_group_exporter_free (gpointer user_data)
     535  {
     536    GActionGroupExporter *exporter = user_data;
     537  
     538    g_signal_handlers_disconnect_by_func (exporter->action_group,
     539                                          g_action_group_exporter_action_added, exporter);
     540    g_signal_handlers_disconnect_by_func (exporter->action_group,
     541                                          g_action_group_exporter_action_enabled_changed, exporter);
     542    g_signal_handlers_disconnect_by_func (exporter->action_group,
     543                                          g_action_group_exporter_action_state_changed, exporter);
     544    g_signal_handlers_disconnect_by_func (exporter->action_group,
     545                                          g_action_group_exporter_action_removed, exporter);
     546  
     547    g_hash_table_unref (exporter->pending_changes);
     548    if (exporter->pending_source)
     549      g_source_destroy (exporter->pending_source);
     550  
     551    g_main_context_unref (exporter->context);
     552    g_object_unref (exporter->connection);
     553    g_object_unref (exporter->action_group);
     554    g_free (exporter->object_path);
     555  
     556    g_slice_free (GActionGroupExporter, exporter);
     557  }
     558  
     559  /**
     560   * g_dbus_connection_export_action_group:
     561   * @connection: a #GDBusConnection
     562   * @object_path: a D-Bus object path
     563   * @action_group: a #GActionGroup
     564   * @error: a pointer to a %NULL #GError, or %NULL
     565   *
     566   * Exports @action_group on @connection at @object_path.
     567   *
     568   * The implemented D-Bus API should be considered private.  It is
     569   * subject to change in the future.
     570   *
     571   * A given object path can only have one action group exported on it.
     572   * If this constraint is violated, the export will fail and 0 will be
     573   * returned (with @error set accordingly).
     574   *
     575   * You can unexport the action group using
     576   * g_dbus_connection_unexport_action_group() with the return value of
     577   * this function.
     578   *
     579   * The thread default main context is taken at the time of this call.
     580   * All incoming action activations and state change requests are
     581   * reported from this context.  Any changes on the action group that
     582   * cause it to emit signals must also come from this same context.
     583   * Since incoming action activations and state change requests are
     584   * rather likely to cause changes on the action group, this effectively
     585   * limits a given action group to being exported from only one main
     586   * context.
     587   *
     588   * Returns: the ID of the export (never zero), or 0 in case of failure
     589   *
     590   * Since: 2.32
     591   **/
     592  guint
     593  g_dbus_connection_export_action_group (GDBusConnection  *connection,
     594                                         const gchar      *object_path,
     595                                         GActionGroup     *action_group,
     596                                         GError          **error)
     597  {
     598    const GDBusInterfaceVTable vtable = {
     599      org_gtk_Actions_method_call, NULL, NULL, { 0 }
     600    };
     601    GActionGroupExporter *exporter;
     602    guint id;
     603  
     604    if G_UNLIKELY (org_gtk_Actions == NULL)
     605      {
     606        GError *my_error = NULL;
     607        GDBusNodeInfo *info;
     608  
     609        info = g_dbus_node_info_new_for_xml (org_gtk_Actions_xml, &my_error);
     610        if G_UNLIKELY (info == NULL)
     611          g_error ("%s", my_error->message);
     612        org_gtk_Actions = g_dbus_node_info_lookup_interface (info, "org.gtk.Actions");
     613        g_assert (org_gtk_Actions != NULL);
     614        g_dbus_interface_info_ref (org_gtk_Actions);
     615        g_dbus_node_info_unref (info);
     616      }
     617  
     618    exporter = g_slice_new (GActionGroupExporter);
     619    id = g_dbus_connection_register_object (connection, object_path, org_gtk_Actions, &vtable,
     620                                            exporter, g_action_group_exporter_free, error);
     621  
     622    if (id == 0)
     623      {
     624        g_slice_free (GActionGroupExporter, exporter);
     625        return 0;
     626      }
     627  
     628    exporter->context = g_main_context_ref_thread_default ();
     629    exporter->pending_changes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
     630    exporter->pending_source = NULL;
     631    exporter->action_group = g_object_ref (action_group);
     632    exporter->connection = g_object_ref (connection);
     633    exporter->object_path = g_strdup (object_path);
     634  
     635    g_signal_connect (action_group, "action-added",
     636                      G_CALLBACK (g_action_group_exporter_action_added), exporter);
     637    g_signal_connect (action_group, "action-removed",
     638                      G_CALLBACK (g_action_group_exporter_action_removed), exporter);
     639    g_signal_connect (action_group, "action-state-changed",
     640                      G_CALLBACK (g_action_group_exporter_action_state_changed), exporter);
     641    g_signal_connect (action_group, "action-enabled-changed",
     642                      G_CALLBACK (g_action_group_exporter_action_enabled_changed), exporter);
     643  
     644    return id;
     645  }
     646  
     647  /**
     648   * g_dbus_connection_unexport_action_group:
     649   * @connection: a #GDBusConnection
     650   * @export_id: the ID from g_dbus_connection_export_action_group()
     651   *
     652   * Reverses the effect of a previous call to
     653   * g_dbus_connection_export_action_group().
     654   *
     655   * It is an error to call this function with an ID that wasn't returned
     656   * from g_dbus_connection_export_action_group() or to call it with the
     657   * same ID more than once.
     658   *
     659   * Since: 2.32
     660   **/
     661  void
     662  g_dbus_connection_unexport_action_group (GDBusConnection *connection,
     663                                           guint            export_id)
     664  {
     665    g_dbus_connection_unregister_object (connection, export_id);
     666  }