commit 87627545565c4a199627ba8e579c42c1c07528e9
Author: Tristan Van Berkom <tristan.van.berkom@gmail.com>
Date:   Wed Oct 26 22:42:30 2011 -0400

    Allow GDBusInterfaceSkeleton to be exported on multiple connections.
    
    This patch adds the following apis:
      o g_dbus_interface_skeleton_unexport_from_connection()
      o g_dbus_interface_skeleton_get_connections
      o g_dbus_interface_skeleton_has_connection
    
    The codegen.py is updated to emit signals on all connections
    
    The test case gio/tests/gdbus-peer.c is updated to include
    a test where 2 interface proxies are connected to a single
    interface skeleton.

diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index 49ac6b1..4a68409 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -3288,7 +3288,10 @@ g_dbus_interface_skeleton_get_vtable
 g_dbus_interface_skeleton_get_properties
 g_dbus_interface_skeleton_export
 g_dbus_interface_skeleton_unexport
+g_dbus_interface_skeleton_unexport_from_connection
 g_dbus_interface_skeleton_get_connection
+g_dbus_interface_skeleton_get_connections
+g_dbus_interface_skeleton_has_connection
 g_dbus_interface_skeleton_get_object_path
 GDBusInterfaceSkeletonFlags
 g_dbus_interface_skeleton_get_flags
diff --git a/gio/gdbus-2.0/codegen/codegen.py b/gio/gdbus-2.0/codegen/codegen.py
index 8d34bdd..8810503 100644
--- a/gio/gdbus-2.0/codegen/codegen.py
+++ b/gio/gdbus-2.0/codegen/codegen.py
@@ -2244,6 +2244,7 @@ class CodeGenerator:
                      '{\n'
                      '  %sSkeleton *skeleton = %s%s_SKELETON (_skeleton);\n'
                      %(i.name_lower, i.camel_name, i.ns_upper, i.name_upper))
+
         self.c.write('\n'
                      '  GVariantBuilder builder;\n'
                      '  guint n;\n'
@@ -2256,7 +2257,7 @@ class CodeGenerator:
                      '      if (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE)\n'
                      '        {\n'
                      '          GVariant *value;\n'
-                     '          value = _%s_skeleton_handle_get_property (g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (skeleton)), NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), "%s", info->name, NULL, skeleton);\n'
+                     '          value = _%s_skeleton_handle_get_property (NULL, NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), "%s", info->name, NULL, skeleton);\n'
                      '          if (value != NULL)\n'
                      '            {\n'
                      '              g_variant_take_ref (value);\n'
@@ -2307,21 +2308,27 @@ class CodeGenerator:
                 self.c.write(',\n    %sarg_%s'%(a.ctype_in, a.name))
             self.c.write(')\n'
                          '{\n'
-                         '  %sSkeleton *skeleton = %s%s_SKELETON (object);\n'
-                         '  GDBusConnection *connection = g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (skeleton));\n'
+                         '  %sSkeleton *skeleton = %s%s_SKELETON (object);\n\n'
+                         '  GSList     *connections, *l;\n'
+                         '  connections = g_dbus_interface_skeleton_get_connections (G_DBUS_INTERFACE_SKELETON (skeleton));\n'
+                         '  for (l = connections; l; l = l->next)\n'
+                         '    {\n'
+                         '      GDBusConnection *connection = l->data;\n'
                          %(i.camel_name, i.ns_upper, i.name_upper))
-            self.c.write('  if (connection == NULL)\n'
-                         '    return;\n'
-                         '  g_dbus_connection_emit_signal (connection,\n'
-                         '    NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), "%s", "%s",\n'
-                         '    g_variant_new ("('
+            self.c.write('      g_dbus_connection_emit_signal (connection,\n'
+                         '        NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), "%s", "%s",\n'
+                         '        g_variant_new ("('
                          %(i.name, s.name))
+
             for a in s.args:
                 self.c.write('%s'%(a.format_in))
             self.c.write(')"')
             for a in s.args:
                 self.c.write(',\n                   arg_%s'%(a.name))
             self.c.write('), NULL);\n')
+            self.c.write('    }\n')
+            self.c.write('  g_slist_foreach (connections, (GFunc)g_object_unref, NULL);\n')
+            self.c.write('  g_slist_free (connections);\n')
             self.c.write('}\n'
                          '\n')
 
@@ -2388,6 +2395,7 @@ class CodeGenerator:
                          '  guint num_changes;\n'
                          '\n'
                          '  g_mutex_lock (&skeleton->priv->lock);\n'
+                         '\n'
                          '  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));\n'
                          '  g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));\n'
                          '  for (l = skeleton->priv->changed_properties, num_changes = 0; l != NULL; l = l->next)\n'
@@ -2401,20 +2409,32 @@ class CodeGenerator:
                          '        {\n'
                          '          variant = g_dbus_gvalue_to_gvariant (cur_value, G_VARIANT_TYPE (cp->info->parent_struct.signature));\n'
                          '          g_variant_builder_add (&builder, "{sv}", cp->info->parent_struct.name, variant);\n'
-                         '          g_variant_unref (variant);\n'
                          '          num_changes++;\n'
                          '        }\n'
                          '    }\n'
                          '  if (num_changes > 0)\n'
                          '    {\n'
-                         '      g_dbus_connection_emit_signal (g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (skeleton)),\n'
-                         '                                     NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)),\n'
-                         '                                     "org.freedesktop.DBus.Properties",\n'
-                         '                                     "PropertiesChanged",\n'
-                         '                                     g_variant_new ("(sa{sv}as)",\n'
-                         '                                                    "%s",\n'
-                         '                                                    &builder, &invalidated_builder),\n'
-                         '                                     NULL);\n'
+                         '      GSList *connections, *list;\n'
+                         '      GVariant *signal_variant;'
+                         '\n'
+                         '      signal_variant = g_variant_new ("(sa{sv}as)", "%s",\n'
+                         '                                      &builder, &invalidated_builder);\n'
+                         '      g_variant_ref_sink (signal_variant);'
+                         '      connections = g_dbus_interface_skeleton_get_connections (G_DBUS_INTERFACE_SKELETON (skeleton));\n'
+                         '      for (list = connections; list; list = list->next)\n'
+                         '        {\n'
+                         '          GDBusConnection *connection = list->data;\n'
+                         '\n'
+                         '          g_dbus_connection_emit_signal (connection,\n'
+                         '                                         NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)),\n'
+                         '                                         "org.freedesktop.DBus.Properties",\n'
+                         '                                         "PropertiesChanged",\n'
+                         '                                         signal_variant,\n'
+                         '                                         NULL);\n'
+                         '        }\n'
+                         '      g_variant_unref (signal_variant);\n'
+                         '      g_slist_foreach (connections, (GFunc)g_object_unref, NULL);\n'
+                         '      g_slist_free (connections);\n'
                          '    }\n'
                          '  else\n'
                          '    {\n'
@@ -2427,6 +2447,7 @@ class CodeGenerator:
             self.c.write('  skeleton->priv->changed_properties = NULL;\n')
             self.c.write('  skeleton->priv->changed_properties_idle_source = NULL;\n')
             self.c.write('  g_mutex_unlock (&skeleton->priv->lock);\n')
+                         
             self.c.write('  return FALSE;\n'
                          '}\n'
                          '\n')
@@ -2456,6 +2477,10 @@ class CodeGenerator:
                          '      g_value_init (&cp->orig_value, G_VALUE_TYPE (orig_value));\n'
                          '      g_value_copy (orig_value, &cp->orig_value);\n'
                          '    }\n'
+                         '  else\n'
+                         '    {\n'
+                         '      g_value_copy (orig_value, &cp->orig_value);\n'
+                         '    }\n'
                          '}\n'
                          '\n'
                          %())
@@ -2496,7 +2521,7 @@ class CodeGenerator:
                          '  g_object_freeze_notify (object);\n'
                          '  if (!_g_value_equal (value, &skeleton->priv->properties->values[prop_id - 1]))\n'
                          '    {\n'
-                         '      if (g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (skeleton)) != NULL)\n'
+                         '      if (g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (skeleton)))\n'
                          '        _%s_schedule_emit_changed (skeleton, _%s_property_info_pointers[prop_id - 1], prop_id, &skeleton->priv->properties->values[prop_id - 1]);\n'
                          '      g_value_copy (value, &skeleton->priv->properties->values[prop_id - 1]);\n'
                          '      g_object_notify_by_pspec (object, pspec);\n'
diff --git a/gio/gdbusinterfaceskeleton.c b/gio/gdbusinterfaceskeleton.c
index 0cd5d6f..5eddbe2 100644
--- a/gio/gdbusinterfaceskeleton.c
+++ b/gio/gdbusinterfaceskeleton.c
@@ -44,17 +44,21 @@
 
 struct _GDBusInterfaceSkeletonPrivate
 {
-  GMutex lock;
+  GMutex                      lock;
 
-  GDBusObject *object;
+  GDBusObject                *object;
   GDBusInterfaceSkeletonFlags flags;
-  guint registration_id;
 
-  GDBusConnection *connection;
-  gchar *object_path;
-  GDBusInterfaceVTable *hooked_vtable;
+  GSList                     *connections;   /* List of ConnectionData */
+  gchar                      *object_path;   /* The object path for this skeleton */
+  GDBusInterfaceVTable       *hooked_vtable;
 };
 
+typedef struct {
+  GDBusConnection *connection;
+  guint            registration_id;
+} ConnectionData;
+
 enum
 {
   G_AUTHORIZE_METHOD_SIGNAL,
@@ -69,7 +73,21 @@ enum
 
 static guint signals[LAST_SIGNAL] = {0};
 
-static void dbus_interface_interface_init (GDBusInterfaceIface *iface);
+static void     dbus_interface_interface_init                      (GDBusInterfaceIface    *iface);
+
+static gboolean set_object_path_locked                             (GDBusInterfaceSkeleton *interface_,
+								    const gchar            *object_path);
+static void     remove_connection_locked                           (GDBusInterfaceSkeleton *interface_,
+								    GDBusConnection        *connection);
+static void     skeleton_intercept_handle_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);
+
 
 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GDBusInterfaceSkeleton, g_dbus_interface_skeleton, G_TYPE_OBJECT,
                                   G_IMPLEMENT_INTERFACE (G_TYPE_DBUS_INTERFACE, dbus_interface_interface_init));
@@ -78,16 +96,28 @@ static void
 g_dbus_interface_skeleton_finalize (GObject *object)
 {
   GDBusInterfaceSkeleton *interface = G_DBUS_INTERFACE_SKELETON (object);
-  /* unexport if already exported */
-  if (interface->priv->registration_id > 0)
-    g_dbus_interface_skeleton_unexport (interface);
 
-  g_assert (interface->priv->connection == NULL);
-  g_assert (interface->priv->object_path == NULL);
-  g_assert (interface->priv->hooked_vtable == NULL);
+  /* Do we need to hold the lock here ?? */
+  g_mutex_lock (&interface->priv->lock);
+
+  /* unexport from all connections if we're exported anywhere */
+  while (interface->priv->connections != NULL)
+    {
+      ConnectionData *data = interface->priv->connections->data;
+
+      remove_connection_locked (interface, data->connection);
+    }
+
+  set_object_path_locked (interface, NULL);
+
+  g_mutex_unlock (&interface->priv->lock);
+
+
+  g_free (interface->priv->hooked_vtable);
 
   if (interface->priv->object != NULL)
-    g_object_remove_weak_pointer (G_OBJECT (interface->priv->object), (gpointer *) &interface->priv->object);
+    g_object_remove_weak_pointer (G_OBJECT (interface->priv->object),
+				  (gpointer *) &interface->priv->object);
 
   g_mutex_clear (&interface->priv->lock);
 
@@ -232,7 +262,11 @@ g_dbus_interface_skeleton_class_init (GDBusInterfaceSkeletonClass *klass)
 static void
 g_dbus_interface_skeleton_init (GDBusInterfaceSkeleton *interface)
 {
-  interface->priv = G_TYPE_INSTANCE_GET_PRIVATE (interface, G_TYPE_DBUS_INTERFACE_SKELETON, GDBusInterfaceSkeletonPrivate);
+  interface->priv = 
+    G_TYPE_INSTANCE_GET_PRIVATE (interface,
+				 G_TYPE_DBUS_INTERFACE_SKELETON,
+				 GDBusInterfaceSkeletonPrivate);
+
   g_mutex_init (&interface->priv->lock);
 }
 
@@ -617,24 +651,215 @@ skeleton_intercept_handle_method_call (GDBusConnection       *connection,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static ConnectionData *
+new_connection (GDBusConnection *connection,
+		guint            registration_id)
+{
+  ConnectionData *data;
+
+  data = g_slice_new0 (ConnectionData);
+  data->connection      = g_object_ref (connection);
+  data->registration_id = registration_id;
+
+  return data;
+}
+
+static void
+free_connection (ConnectionData *data)
+{
+  if (data)
+    {
+      g_object_unref (data->connection);
+      g_slice_free (ConnectionData, data);
+    }
+}
+
+static gboolean
+add_connection_locked (GDBusInterfaceSkeleton *interface_,
+		       GDBusConnection        *connection,
+		       GError                **error)
+{
+  GDBusInterfaceSkeletonPrivate *priv = interface_->priv;
+  ConnectionData                *data;
+  guint                          registration_id;
+
+  if (!priv->hooked_vtable)
+    {
+      /* Hook the vtable since we need to intercept method calls for
+       * ::g-authorize-method and for dispatching in thread vs
+       * context
+       */
+      priv->hooked_vtable =
+	g_memdup (g_dbus_interface_skeleton_get_vtable (interface_), sizeof (GDBusInterfaceVTable));
+      priv->hooked_vtable->method_call = skeleton_intercept_handle_method_call;
+    }
+
+  registration_id =
+    g_dbus_connection_register_object (connection,
+				       priv->object_path,
+				       g_dbus_interface_skeleton_get_info (interface_),
+				       priv->hooked_vtable,
+				       interface_,
+				       NULL, /* user_data_free_func */
+				       error);
+
+  if (registration_id > 0)
+    {
+      data = new_connection (connection, registration_id);
+
+      priv->connections =
+	g_slist_append (priv->connections, data);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+remove_connection_locked (GDBusInterfaceSkeleton *interface_,
+			  GDBusConnection        *connection)
+{
+  GDBusInterfaceSkeletonPrivate *priv;
+  ConnectionData                *data;
+  GSList                        *list;
+
+  priv = interface_->priv;
+
+  /* Get the connection in the list and unregister ... */
+  for (list = priv->connections; list; list = list->next)
+    {
+      data = list->data;
+
+      if (data->connection == connection)
+	{
+	  g_warn_if_fail
+	    (g_dbus_connection_unregister_object (data->connection, data->registration_id));
+
+	  free_connection (data);
+
+	  priv->connections =
+	    g_slist_delete_link (priv->connections, list);
+	}
+    }
+}
+
+static gboolean
+set_object_path_locked (GDBusInterfaceSkeleton *interface_,
+			const gchar            *object_path)
+{
+  if (g_strcmp0 (interface_->priv->object_path, object_path) != 0)
+    {
+      g_free (interface_->priv->object_path);
+      interface_->priv->object_path = g_strdup (object_path);
+
+      return TRUE;
+    }
+  return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 /**
  * g_dbus_interface_skeleton_get_connection:
  * @interface_: A #GDBusInterfaceSkeleton.
  *
- * Gets the connection that @interface_ is exported on, if any.
+ * Gets the first connection in the list of connections that @interface_ 
+ * is exported on, if any.
  *
  * Returns: (transfer none): A #GDBusConnection or %NULL if @interface_ is
  * not exported anywhere. Do not free, the object belongs to @interface_.
- *
- * Since: 2.30
  */
 GDBusConnection *
 g_dbus_interface_skeleton_get_connection (GDBusInterfaceSkeleton *interface_)
 {
-  GDBusConnection *ret;
+  ConnectionData  *data;
+  GDBusConnection *connection;
+
   g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), NULL);
+
   g_mutex_lock (&interface_->priv->lock);
-  ret = interface_->priv->connection;
+
+  data = interface_->priv->connections->data;
+  if (data)
+    connection = data->connection;
+  else
+    connection = NULL;
+
+  g_mutex_unlock (&interface_->priv->lock);
+
+  return connection;
+}
+
+/**
+ * g_dbus_interface_skeleton_get_connections:
+ * @interface_: The D-Bus interface.
+ *
+ * Reports all the connections that @interface_ is exported
+ * on via g_dbus_interface_skeleton_export().
+ *
+ * Returns: (element-type GDBusConnection) (transfer full):
+ *   A newly allocated #GSList of #GDBusConnections all of wich
+ *   should eventually be released with g_object_unref() and
+ *   the list freed with g_slist_free().
+ *
+ * Since: 2.32
+ */
+GSList *
+g_dbus_interface_skeleton_get_connections (GDBusInterfaceSkeleton *interface_)
+{
+  GSList          *list, *connections;
+  ConnectionData  *data;
+
+  g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), NULL);
+
+  g_mutex_lock (&interface_->priv->lock);
+  connections = NULL;
+
+  for (list = interface_->priv->connections; list; list = list->next)
+    {
+      data        = list->data;
+      connections = g_slist_prepend (connections,
+				     /* Return a reference to each connection */
+				     g_object_ref (data->connection));
+    }
+
+  g_mutex_unlock (&interface_->priv->lock);
+
+  return g_slist_reverse (connections);
+}
+
+/**
+ * g_dbus_interface_skeleton_has_connection:
+ * @interface_: The D-Bus interface.
+ * @connection: The #GDBusConnection to check
+ *
+ * Checks if @connection is one of the connections @interface_ has
+ * been exported onto with g_dbus_interface_skeleton_export().
+ *
+ * Returns: whether @interface is exported on @connection
+ *
+ * Since: 2.32
+ */
+gboolean
+g_dbus_interface_skeleton_has_connection (GDBusInterfaceSkeleton     *interface_,
+					  GDBusConnection            *connection)
+{
+  GSList *list;
+  gboolean ret;
+  g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+  g_mutex_lock (&interface_->priv->lock);
+
+  for (ret = FALSE, list = interface_->priv->connections;
+       ret == FALSE && list != NULL;
+       list = list->next)
+    {
+      ConnectionData *data = list->data;
+
+      ret = (data->connection == connection);
+    }
+
   g_mutex_unlock (&interface_->priv->lock);
   return ret;
 }
@@ -643,7 +868,7 @@ g_dbus_interface_skeleton_get_connection (GDBusInterfaceSkeleton *interface_)
  * g_dbus_interface_skeleton_get_object_path:
  * @interface_: A #GDBusInterfaceSkeleton.
  *
- * Gets the object path that @interface_ is exported on, if any.
+ * Gets the object path that @interface_ is configured to export itself on.
  *
  * Returns: A string owned by @interface_ or %NULL if @interface_ is not exported
  * anywhere. Do not free, the string belongs to @interface_.
@@ -670,12 +895,14 @@ g_dbus_interface_skeleton_get_object_path (GDBusInterfaceSkeleton *interface_)
  *
  * Exports @interface_ at @object_path on @connection.
  *
+ * This can be called multiple times to export the same @interface_
+ * onto multiple connections however the @object_path provided must be
+ * the same for all connections.
+ *
  * Use g_dbus_interface_skeleton_unexport() to unexport the object.
  *
- * Returns: %TRUE if the interface was exported, other %FALSE with
+ * Returns: %TRUE if the interface was exported on @connection, otherwise %FALSE with
  * @error set.
- *
- * Since: 2.30
  */
 gboolean
 g_dbus_interface_skeleton_export (GDBusInterfaceSkeleton  *interface_,
@@ -685,50 +912,26 @@ g_dbus_interface_skeleton_export (GDBusInterfaceSkeleton  *interface_,
 {
   gboolean ret;
 
-  g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), 0);
-  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
-  g_return_val_if_fail (g_variant_is_object_path (object_path), 0);
-  g_return_val_if_fail (error == NULL || *error == NULL, 0);
+  g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+  g_return_val_if_fail (g_variant_is_object_path (object_path), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
-  g_mutex_lock (&interface_->priv->lock);
+  /* Assert that the object path is the same for multiple connections here */
+  g_return_val_if_fail (interface_->priv->object_path == NULL ||
+			g_strcmp0 (interface_->priv->object_path, object_path) == 0, FALSE);
 
+  g_mutex_lock (&interface_->priv->lock);
   ret = FALSE;
-  if (interface_->priv->registration_id > 0)
-    {
-      g_set_error_literal (error,
-                           G_IO_ERROR,
-                           G_IO_ERROR_FAILED, /* TODO: new error code */
-                           "The object is already exported");
-      goto out;
-    }
 
-  g_assert (interface_->priv->connection == NULL);
-  g_assert (interface_->priv->object_path == NULL);
-  g_assert (interface_->priv->hooked_vtable == NULL);
+  /* Set the object path */
+  set_object_path_locked (interface_, object_path);
+
+  /* Add the connection */
+  ret = add_connection_locked (interface_, connection, error);
 
-  /* Hook the vtable since we need to intercept method calls for
-   * ::g-authorize-method and for dispatching in thread vs
-   * context
-   */
-  interface_->priv->hooked_vtable = g_memdup (g_dbus_interface_skeleton_get_vtable (interface_), sizeof (GDBusInterfaceVTable));
-  interface_->priv->hooked_vtable->method_call = skeleton_intercept_handle_method_call;
-
-  interface_->priv->connection = g_object_ref (connection);
-  interface_->priv->object_path = g_strdup (object_path);
-  interface_->priv->registration_id = g_dbus_connection_register_object (connection,
-                                                                         object_path,
-                                                                         g_dbus_interface_skeleton_get_info (interface_),
-                                                                         interface_->priv->hooked_vtable,
-                                                                         interface_,
-                                                                         NULL, /* user_data_free_func */
-                                                                         error);
-  if (interface_->priv->registration_id == 0)
-    goto out;
-
-  ret = TRUE;
-
- out:
   g_mutex_unlock (&interface_->priv->lock);
+
   return ret;
 }
 
@@ -736,32 +939,57 @@ g_dbus_interface_skeleton_export (GDBusInterfaceSkeleton  *interface_,
  * g_dbus_interface_skeleton_unexport:
  * @interface_: A #GDBusInterfaceSkeleton.
  *
- * Stops exporting an interface previously exported with
- * g_dbus_interface_skeleton_export().
+ * Stops exporting an interface on all connections which it was
+ * previously exported on with g_dbus_interface_skeleton_export().
  *
- * Since: 2.30
+ * To unexport @interface_ from a single connection, use
+ * g_dbus_interface_skeleton_export_from_connection()
  */
 void
 g_dbus_interface_skeleton_unexport (GDBusInterfaceSkeleton *interface_)
 {
   g_return_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_));
-  g_return_if_fail (interface_->priv->registration_id > 0);
 
   g_mutex_lock (&interface_->priv->lock);
 
-  g_assert (interface_->priv->connection != NULL);
-  g_assert (interface_->priv->object_path != NULL);
-  g_assert (interface_->priv->hooked_vtable != NULL);
+  /* Remove the connection */
+  while (interface_->priv->connections != NULL)
+    {
+      ConnectionData *data = interface_->priv->connections->data;
+
+      remove_connection_locked (interface_, data->connection);
+    }
+
+  /* Unset the object path when there are no connections */
+  set_object_path_locked (interface_, NULL);
 
-  g_warn_if_fail (g_dbus_connection_unregister_object (interface_->priv->connection,
-                                                       interface_->priv->registration_id));
+  g_mutex_unlock (&interface_->priv->lock);
+}
+
+
+/**
+ * g_dbus_interface_skeleton_unexport_from_connection:
+ * @interface_: The D-Bus interface to export.
+ * @connection: The #GDBusConnection which should be removed.
+ *
+ * Removes @connection from the list of connections which
+ * @interface_ is exported on.
+ *
+ * Since: 2.32
+ */
+void
+g_dbus_interface_skeleton_unexport_from_connection (GDBusInterfaceSkeleton *interface_,
+						    GDBusConnection        *connection)
+{
+  g_return_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_));
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+  g_mutex_lock (&interface_->priv->lock);
+  remove_connection_locked (interface_, connection);
 
-  g_object_unref (interface_->priv->connection);
-  g_free (interface_->priv->object_path);
-  interface_->priv->connection = NULL;
-  interface_->priv->object_path = NULL;
-  interface_->priv->hooked_vtable = NULL;
-  interface_->priv->registration_id = 0;
+  /* Reset the object path when removing the last connection */
+  if (interface_->priv->connections == NULL)
+    set_object_path_locked (interface_, NULL);
 
   g_mutex_unlock (&interface_->priv->lock);
 }
diff --git a/gio/gdbusinterfaceskeleton.h b/gio/gdbusinterfaceskeleton.h
index 69dcc2f..4d621bf 100644
--- a/gio/gdbusinterfaceskeleton.h
+++ b/gio/gdbusinterfaceskeleton.h
@@ -101,7 +101,13 @@ gboolean                     g_dbus_interface_skeleton_export          (GDBusInt
                                                                         const gchar                 *object_path,
                                                                         GError                     **error);
 void                         g_dbus_interface_skeleton_unexport        (GDBusInterfaceSkeleton      *interface_);
+void                g_dbus_interface_skeleton_unexport_from_connection (GDBusInterfaceSkeleton      *interface_,
+									GDBusConnection             *connection);
+
 GDBusConnection             *g_dbus_interface_skeleton_get_connection  (GDBusInterfaceSkeleton      *interface_);
+GSList                      *g_dbus_interface_skeleton_get_connections (GDBusInterfaceSkeleton      *interface_);
+gboolean                     g_dbus_interface_skeleton_has_connection  (GDBusInterfaceSkeleton      *interface_,
+									GDBusConnection             *connection);
 const gchar                 *g_dbus_interface_skeleton_get_object_path (GDBusInterfaceSkeleton      *interface_);
 
 G_END_DECLS
diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am
index 2091366..cfa66f9 100644
--- a/gio/tests/Makefile.am
+++ b/gio/tests/Makefile.am
@@ -332,9 +332,6 @@ gdbus_export_LDADD = $(progs_ldadd)
 gdbus_error_SOURCES = gdbus-error.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
 gdbus_error_LDADD = $(progs_ldadd)
 
-gdbus_peer_SOURCES = gdbus-peer.c gdbus-tests.h gdbus-tests.c
-gdbus_peer_LDADD = $(progs_ldadd)
-
 gdbus_non_socket_SOURCES = gdbus-non-socket.c gdbus-tests.h gdbus-tests.c
 gdbus_non_socket_LDADD = $(progs_ldadd)
 
@@ -502,6 +499,11 @@ gdbus_example_objectmanager_server_LDADD    = $(top_builddir)/gio/tests/gdbus-ob
 gdbus_example_objectmanager_client_SOURCES  = gdbus-example-objectmanager-client.c
 gdbus_example_objectmanager_client_CFLAGS   = -I$(top_builddir)/gio/tests/gdbus-object-manager-example
 gdbus_example_objectmanager_client_LDADD    = $(top_builddir)/gio/tests/gdbus-object-manager-example/libgdbus-example-objectmanager.la $(progs_ldadd)
+
+gdbus_peer_SOURCES = gdbus-peer.c gdbus-tests.h gdbus-tests.c
+gdbus_peer_CFLAGS  = -I$(top_builddir)/gio/tests/gdbus-object-manager-example
+gdbus_peer_LDADD   = $(top_builddir)/gio/tests/gdbus-object-manager-example/libgdbus-example-objectmanager.la $(progs_ldadd)
+
 endif OS_UNIX
 
 # -----------------------------------------------------------------------------
diff --git a/gio/tests/gdbus-peer.c b/gio/tests/gdbus-peer.c
index ee2ceb6..60cf449 100644
--- a/gio/tests/gdbus-peer.c
+++ b/gio/tests/gdbus-peer.c
@@ -601,6 +601,7 @@ test_peer (void)
   g_assert (c == NULL);
 
   /* bring up a server - we run the server in a different thread to avoid deadlocks */
+  service_loop = NULL;
   service_thread = g_thread_new ("test_peer",
                                  service_thread_func,
                                  &data);
@@ -1534,6 +1535,193 @@ test_tcp_anonymous (void)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+#include "gdbus-example-objectmanager-generated.h"
+
+static GDBusServer *codegen_server = NULL;
+
+static gboolean
+codegen_on_animal_poke (ExampleAnimal          *animal,
+			GDBusMethodInvocation  *invocation,
+			gboolean                make_sad,
+			gboolean                make_happy,
+			gpointer                user_data)
+{
+  if ((make_sad && make_happy) || (!make_sad && !make_happy))
+    {
+      g_dbus_method_invocation_return_dbus_error (invocation,
+                                                  "org.gtk.GDBus.Examples.ObjectManager.Error.Failed",
+                                                  "Exactly one of make_sad or make_happy must be TRUE");
+      goto out;
+    }
+
+  if (make_sad)
+    {
+      if (g_strcmp0 (example_animal_get_mood (animal), "Sad") == 0)
+        {
+          g_dbus_method_invocation_return_dbus_error (invocation,
+                                                      "org.gtk.GDBus.Examples.ObjectManager.Error.SadAnimalIsSad",
+                                                      "Sad animal is already sad");
+          goto out;
+        }
+
+      example_animal_set_mood (animal, "Sad");
+      example_animal_complete_poke (animal, invocation);
+      goto out;
+    }
+
+  if (make_happy)
+    {
+      g_main_loop_quit (service_loop);
+
+      if (g_strcmp0 (example_animal_get_mood (animal), "Happy") == 0)
+        {
+          g_dbus_method_invocation_return_dbus_error (invocation,
+                                                      "org.gtk.GDBus.Examples.ObjectManager.Error.HappyAnimalIsHappy",
+                                                      "Happy animal is already happy");
+          goto out;
+        }
+
+      example_animal_set_mood (animal, "Happy");
+      example_animal_complete_poke (animal, invocation);
+      goto out;
+    }
+
+  g_assert_not_reached ();
+
+ out:
+  return TRUE; /* to indicate that the method was handled */
+}
+
+/* Runs in thread we created GDBusServer in (since we didn't pass G_DBUS_SERVER_FLAGS_RUN_IN_THREAD) */
+static gboolean
+codegen_on_new_connection (GDBusServer *server,
+			   GDBusConnection *connection,
+			   gpointer user_data)
+{
+  ExampleAnimal *animal = user_data;
+  GError        *error = NULL;
+
+  g_print ("Client connected.\n"
+           "Negotiated capabilities: unix-fd-passing=%d\n",
+           g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
+
+  g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (animal), connection,
+				    "/Example/Animals/000", &error);
+  g_assert_no_error (error);
+
+  return TRUE;
+}
+
+static gpointer
+codegen_service_thread_func (gpointer user_data)
+{
+  GMainContext   *service_context;
+  ExampleAnimal  *animal;
+  GError         *error = NULL;
+
+  animal = example_animal_skeleton_new ();
+
+  /* Handle Poke() D-Bus method invocations on the .Animal interface */
+  g_signal_connect (animal, "handle-poke",
+		    G_CALLBACK (codegen_on_animal_poke),
+		    NULL); /* user_data */
+
+  service_context = g_main_context_new ();
+  g_main_context_push_thread_default (service_context);
+
+  codegen_server = g_dbus_server_new_sync (tmp_address,
+					   G_DBUS_SERVER_FLAGS_NONE,
+					   test_guid,
+					   NULL, /* observer */
+					   NULL, /* cancellable */
+					   &error);
+  g_assert_no_error (error);
+  g_dbus_server_start (codegen_server);
+
+  g_signal_connect (codegen_server, "new-connection",
+                    G_CALLBACK (codegen_on_new_connection),
+		    animal);
+
+  service_loop = g_main_loop_new (service_context, FALSE);
+  g_main_loop_run (service_loop);
+
+  g_object_unref (animal);
+
+  g_main_context_pop_thread_default (service_context);
+
+  g_main_loop_unref (service_loop);
+  g_main_context_unref (service_context);
+
+  g_dbus_server_stop (codegen_server);
+  g_object_unref (codegen_server);
+  codegen_server = NULL;
+
+  return NULL;
+}
+
+static void
+codegen_test_peer (void)
+{
+  GDBusConnection     *connection;
+  ExampleAnimal       *animal1, *animal2;
+  GThread             *service_thread;
+  GError              *error = NULL;
+
+  /* bring up a server - we run the server in a different thread to avoid deadlocks */
+  service_thread = g_thread_new ("codegen_test_peer", 
+				 codegen_service_thread_func,
+				 NULL);
+  while (service_loop == NULL)
+    g_thread_yield ();
+  g_assert (codegen_server != NULL);
+
+  /* Get an animal ...  */
+  connection = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (codegen_server),
+						       G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+						       NULL, /* GDBusAuthObserver */
+						       NULL, /* cancellable */
+						       &error);
+  g_assert_no_error (error);
+  g_assert (connection != NULL);
+
+  animal1 = example_animal_proxy_new_sync (connection, 0, NULL,
+					   "/Example/Animals/000", NULL, &error);
+  g_assert_no_error (error);
+  g_assert (animal1 != NULL);
+  g_object_unref (connection);
+
+  /* Get another animal ...  */
+  connection = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (codegen_server),
+						       G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+						       NULL, /* GDBusAuthObserver */
+						       NULL, /* cancellable */
+						       &error);
+  g_assert_no_error (error);
+  g_assert (connection != NULL);
+
+  animal2 = example_animal_proxy_new_sync (connection, 0, NULL,
+					   "/Example/Animals/000", NULL, &error);
+  g_assert_no_error (error);
+  g_assert (animal2 != NULL);
+  g_object_unref (connection);
+
+  example_animal_call_poke_sync (animal1, TRUE, FALSE, NULL, &error);
+  g_assert_no_error (error);
+
+  example_animal_call_poke_sync (animal2, FALSE, TRUE, NULL, &error);
+  g_assert_no_error (error);
+
+  g_object_unref (animal1);
+  g_object_unref (animal2);
+  g_thread_join (service_thread);
+}
+
+
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
 int
 main (int   argc,
       char *argv[])
@@ -1575,6 +1763,7 @@ main (int   argc,
   g_test_add_func ("/gdbus/tcp-anonymous", test_tcp_anonymous);
   g_test_add_func ("/gdbus/credentials", test_credentials);
   g_test_add_func ("/gdbus/overflow", test_overflow);
+  g_test_add_func ("/gdbus/codegen-peer-to-peer", codegen_test_peer);
 
   ret = g_test_run();
 
