=== modified file 'docs/gst/gstreamer-sections.txt'
--- docs/gst/gstreamer-sections.txt	2008-01-21 17:43:47 +0000
+++ docs/gst/gstreamer-sections.txt	2008-01-21 18:54:17 +0000
@@ -1006,6 +1006,7 @@
 <TITLE>GstMessage</TITLE>
 GstMessage
 GstMessageType
+GstStructureChangeType
 GST_MESSAGE_SRC
 GST_MESSAGE_TIMESTAMP
 GST_MESSAGE_TYPE
@@ -1036,6 +1037,7 @@
 gst_message_new_async_start
 gst_message_new_async_done
 gst_message_new_latency
+gst_message_new_structure_change
 gst_message_parse_clock_lost
 gst_message_parse_clock_provide
 gst_message_parse_error
@@ -1049,6 +1051,7 @@
 gst_message_parse_warning
 gst_message_parse_duration
 gst_message_parse_async_start
+gst_message_parse_structure_change
 gst_message_ref
 gst_message_unref
 
@@ -1062,9 +1065,11 @@
 GST_IS_MESSAGE_CLASS
 GST_MESSAGE_GET_CLASS
 GST_TYPE_MESSAGE_TYPE
+GST_TYPE_STRUCTURE_CHANGE_TYPE
 <SUBSECTION Private>
 gst_message_get_type
 gst_message_type_get_type
+gst_structure_change_type_get_type
 GST_MESSAGE_COND
 GST_MESSAGE_GET_LOCK
 GST_MESSAGE_LOCK

=== modified file 'gst/gstbin.c'
--- gst/gstbin.c	2007-12-18 11:47:03 +0000
+++ gst/gstbin.c	2008-01-24 22:58:32 +0000
@@ -199,6 +199,7 @@
 struct _GstBinPrivate
 {
   gboolean asynchandling;
+  guint32 structure_cookie;
 };
 
 typedef struct
@@ -922,6 +923,7 @@
   bin->children = g_list_prepend (bin->children, element);
   bin->numchildren++;
   bin->children_cookie++;
+  bin->priv->structure_cookie++;
 
   /* distribute the bus */
   gst_element_set_bus (element, bin->child_bus);
@@ -1131,6 +1133,7 @@
    * so that others can detect a change in the children list. */
   bin->numchildren--;
   bin->children_cookie++;
+  bin->priv->structure_cookie++;
 
   if (is_sink && !othersink) {
     /* we're not a sink anymore */
@@ -1820,7 +1823,7 @@
       gst_iterator_new (sizeof (GstBinSortIterator),
       GST_TYPE_ELEMENT,
       GST_OBJECT_GET_LOCK (bin),
-      &bin->children_cookie,
+      &bin->priv->structure_cookie,
       (GstIteratorNextFunction) gst_bin_sort_iterator_next,
       (GstIteratorItemFunction) NULL,
       (GstIteratorResyncFunction) gst_bin_sort_iterator_resync,
@@ -2915,6 +2918,26 @@
         break;
       }
     }
+    case GST_MESSAGE_STRUCTURE_CHANGE:
+    {
+      GstStructureChangeType change_type;
+      gboolean done;
+
+      gst_message_parse_structure_change (message, &change_type, &done);
+
+      if (!done)
+      {
+        GST_OBJECT_LOCK (bin);
+        bin->priv->structure_cookie++;
+      }
+      else
+      {
+        GST_OBJECT_UNLOCK (bin);
+      }
+
+      gst_message_unref (message);
+      break;
+    }
     default:
       goto forward;
   }

=== modified file 'gst/gstmessage.c'
--- gst/gstmessage.c	2007-11-07 12:49:53 +0000
+++ gst/gstmessage.c	2008-01-24 23:10:52 +0000
@@ -593,6 +593,37 @@
 }
 
 /**
+ * gst_message_new_structure_change:
+ * @src: The object originating the message.
+ * @type: The change type.
+ * @done: Whether the structure change is done.
+ *
+ * Create a new structure change message. This message is posted when the
+ * structure of a pipeline is in the process of being changed, for example
+ * when pads are linked or unlinked.
+ *
+ * Returns: The new structure change message.
+ *
+ * MT safe.
+ *
+ * Since: 0.10.17
+ */
+GstMessage *
+gst_message_new_structure_change (GstObject * src, GstStructureChangeType type,
+    gboolean done)
+{
+  GstMessage * message;
+
+  message = gst_message_new_custom (GST_MESSAGE_STRUCTURE_CHANGE, src,
+      gst_structure_new ("GstMessageStructureChange",
+          "type", GST_TYPE_STRUCTURE_CHANGE_TYPE, type,
+          "done", G_TYPE_BOOLEAN, done,
+          NULL));
+
+  return message;
+}
+
+/**
  * gst_message_new_segment_start:
  * @src: The object originating the message.
  * @format: The format of the position being played
@@ -967,6 +998,31 @@
 }
 
 /**
+ * gst_message_parse_structure_change:
+ * @message: A valid #GstMessage of type GST_MESSAGE_STRUCTURE_CHANGE.
+ * @type: A pointer to hold the change type
+ * @done: A pointer to hold whether the change has been completed
+ *
+ * Extracts the change type and completion status from the GstMessage.
+ *
+ * MT safe.
+ *
+ * Since: 0.10.17
+ */
+void
+gst_message_parse_structure_change (GstMessage * message, GstStructureChangeType * type, gboolean * done)
+{
+  g_return_if_fail (GST_IS_MESSAGE (message));
+  g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STRUCTURE_CHANGE);
+  g_return_if_fail (type != NULL);
+
+  g_return_if_fail (gst_structure_get_enum (message->structure,
+      "type", GST_TYPE_STRUCTURE_CHANGE_TYPE, (gint *) type));
+  g_return_if_fail (gst_structure_get_boolean (message->structure,
+      "done", done));
+}
+
+/**
  * gst_message_parse_error:
  * @message: A valid #GstMessage of type GST_MESSAGE_ERROR.
  * @gerror: Location for the GError

=== modified file 'gst/gstmessage.h'
--- gst/gstmessage.h	2007-11-07 12:49:53 +0000
+++ gst/gstmessage.h	2008-01-24 22:58:32 +0000
@@ -58,8 +58,8 @@
  *                          unusable. The pipeline will select a new clock on
  *                          the next PLAYING state change.
  * @GST_MESSAGE_NEW_CLOCK: a new clock was selected in the pipeline.
- * @GST_MESSAGE_STRUCTURE_CHANGE: the structure of the pipeline changed. Not
- * implemented yet.
+ * @GST_MESSAGE_STRUCTURE_CHANGE: the structure of the pipeline changed. This
+ * message is used internally and never forwarded to the application.
  * @GST_MESSAGE_STREAM_STATUS: status about a stream, emitted when it starts,
  *                             stops, errors, etc.. Not implemented yet.
  * @GST_MESSAGE_APPLICATION: message posted by the application, possibly
@@ -180,6 +180,20 @@
 #define GST_MESSAGE_SRC(message)	(GST_MESSAGE(message)->src)
 
 /**
+ * GstStructureChangeType:
+ * @GST_STRUCTURE_CHANGE_TYPE_PAD_LINK: Pad linking is starting or done.
+ * @GST_STRUCTURE_CHANGE_TYPE_PAD_UNLINK: Pad unlinking is starting or done.
+ *
+ * The type of a #GstMessageStructureChange.
+ *
+ * Since: 0.10.17
+ */
+typedef enum {
+  GST_STRUCTURE_CHANGE_TYPE_PAD_LINK   = 0,
+  GST_STRUCTURE_CHANGE_TYPE_PAD_UNLINK = 1
+} GstStructureChangeType;
+
+/**
  * GstMessage:
  * @mini_object: the parent structure
  * @type: the #GstMessageType of the message
@@ -290,6 +304,7 @@
 GstMessage *	gst_message_new_async_start	(GstObject * src, gboolean new_base_time);
 GstMessage *	gst_message_new_async_done	(GstObject * src);
 GstMessage *	gst_message_new_latency         (GstObject * src);
+GstMessage *	gst_message_new_structure_change (GstObject * src, GstStructureChangeType type, gboolean done);
 GstMessage *	gst_message_new_custom		(GstMessageType type,
 						 GstObject    * src,
 						 GstStructure * structure);
@@ -308,6 +323,7 @@
 void		gst_message_parse_segment_done	(GstMessage *message, GstFormat *format, gint64 *position);
 void		gst_message_parse_duration	(GstMessage *message, GstFormat *format, gint64 *duration);
 void		gst_message_parse_async_start	(GstMessage *message, gboolean *new_base_time);
+void		gst_message_parse_structure_change	(GstMessage *message, GstStructureChangeType *type, gboolean *done);
 
 const GstStructure *  gst_message_get_structure	(GstMessage *message);
 

=== modified file 'gst/gstpad.c'
--- gst/gstpad.c	2008-01-21 17:43:47 +0000
+++ gst/gstpad.c	2008-01-24 23:06:32 +0000
@@ -1540,6 +1540,9 @@
 gboolean
 gst_pad_unlink (GstPad * srcpad, GstPad * sinkpad)
 {
+  gboolean result = FALSE;
+  GstElement * parent = NULL;
+
   g_return_val_if_fail (GST_IS_PAD (srcpad), FALSE);
   g_return_val_if_fail (GST_IS_PAD (sinkpad), FALSE);
 
@@ -1547,6 +1550,23 @@
       GST_DEBUG_PAD_NAME (srcpad), srcpad,
       GST_DEBUG_PAD_NAME (sinkpad), sinkpad);
 
+  /* We need to notify the parent before taking any pad locks as the bin in
+   * question might be waiting for a lock on the pad while holding its lock
+   * that our message will try to take. */
+  parent = GST_PAD_PARENT (srcpad);
+  if (parent != NULL && GST_IS_ELEMENT (parent))
+  {
+    /* ref the parent so it doesn't go away if it's unreffed before we post
+     * the last message */
+    gst_object_ref (parent);
+
+    gst_element_post_message (parent,
+        gst_message_new_structure_change (GST_OBJECT (srcpad),
+            GST_STRUCTURE_CHANGE_TYPE_PAD_UNLINK, FALSE));
+  }
+  else
+    parent = NULL;
+
   GST_OBJECT_LOCK (srcpad);
 
   if (G_UNLIKELY (GST_PAD_DIRECTION (srcpad) != GST_PAD_SRC))
@@ -1582,20 +1602,21 @@
   GST_CAT_INFO (GST_CAT_ELEMENT_PADS, "unlinked %s:%s and %s:%s",
       GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad));
 
-  return TRUE;
+  result = TRUE;
+  goto beach;
 
 not_srcpad:
   {
     g_critical ("pad %s is not a source pad", GST_PAD_NAME (srcpad));
     GST_OBJECT_UNLOCK (srcpad);
-    return FALSE;
+    goto beach;
   }
 not_sinkpad:
   {
     g_critical ("pad %s is not a sink pad", GST_PAD_NAME (sinkpad));
     GST_OBJECT_UNLOCK (sinkpad);
     GST_OBJECT_UNLOCK (srcpad);
-    return FALSE;
+    goto beach;
   }
 not_linked_together:
   {
@@ -1603,8 +1624,17 @@
      * be made MT safe.*/
     GST_OBJECT_UNLOCK (sinkpad);
     GST_OBJECT_UNLOCK (srcpad);
-    return FALSE;
-  }
+    goto beach;
+  }
+beach:
+  if (parent != NULL) {
+    gst_element_post_message (parent,
+        gst_message_new_structure_change (GST_OBJECT (srcpad),
+            GST_STRUCTURE_CHANGE_TYPE_PAD_UNLINK, TRUE));
+    gst_object_unref (parent);
+  }
+
+  return result;
 }
 
 /**
@@ -1863,12 +1893,28 @@
 gst_pad_link (GstPad * srcpad, GstPad * sinkpad)
 {
   GstPadLinkReturn result;
+  GstElement * parent;
+
+  /* Notify the parent early. See gst_pad_unlink for details. */
+  parent = GST_PAD_PARENT (srcpad);
+  if (parent && GST_IS_ELEMENT (parent))
+  {
+    /* ref the parent so it doesn't go away if it's unreffed before we post
+     * the last message */
+    gst_object_ref (parent);
+
+    gst_element_post_message (parent,
+        gst_message_new_structure_change (GST_OBJECT (srcpad),
+            GST_STRUCTURE_CHANGE_TYPE_PAD_LINK, FALSE));
+  }
+  else
+    parent = NULL;
 
   /* prepare will also lock the two pads */
   result = gst_pad_link_prepare (srcpad, sinkpad);
 
   if (result != GST_PAD_LINK_OK)
-    goto prepare_failed;
+    goto beach;
 
   /* must set peers before calling the link function */
   GST_PAD_PEER (srcpad) = sinkpad;
@@ -1914,12 +1960,18 @@
     GST_OBJECT_UNLOCK (sinkpad);
     GST_OBJECT_UNLOCK (srcpad);
   }
+
+  goto beach;
+
+beach:
+  if (parent) {
+    gst_element_post_message (parent,
+        gst_message_new_structure_change (GST_OBJECT (srcpad),
+            GST_STRUCTURE_CHANGE_TYPE_PAD_LINK, TRUE));
+    gst_object_unref (parent);
+  }
+
   return result;
-
-prepare_failed:
-  {
-    return result;
-  }
 }
 
 static void

=== modified file 'tests/check/gst/gstbin.c'
--- tests/check/gst/gstbin.c	2007-12-18 11:47:03 +0000
+++ tests/check/gst/gstbin.c	2008-01-24 23:10:52 +0000
@@ -891,6 +891,81 @@
 
 GST_END_TEST;
 
+static void
+test_link_structure_change_state_changed_sync_cb (GstBus *bus,
+    GstMessage *message, gpointer data)
+{
+  GstPipeline *pipeline = GST_PIPELINE (data);
+  GstElement *src, *identity, *sink;
+  GstState old, snew, pending;
+
+  sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink");
+  fail_unless (sink != NULL, "Could not get sink");
+
+  gst_message_parse_state_changed (message, &old, &snew, &pending);
+  if (message->src != GST_OBJECT (sink) || snew != GST_STATE_READY) {
+    gst_object_unref (sink);
+    return;
+  }
+
+  src = gst_bin_get_by_name (GST_BIN (pipeline), "src");
+  fail_unless (src != NULL, "Could not get src");
+
+  identity = gst_bin_get_by_name (GST_BIN (pipeline), "identity");
+  fail_unless (identity != NULL, "Could not get identity");
+
+  /* link src to identity, the pipeline should detect the new link and
+   * resync the state change */
+  fail_unless (gst_element_link (src, identity) == TRUE);
+
+  gst_object_unref (src);
+  gst_object_unref (identity);
+  gst_object_unref (sink);
+}
+
+GST_START_TEST (test_link_structure_change)
+{
+  GstElement *src, *identity, *sink, *pipeline;
+  GstBus *bus;
+  GstState state;
+
+  pipeline = gst_pipeline_new (NULL);
+  fail_unless (pipeline != NULL, "Could not create pipeline");
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+  fail_unless (bus != NULL, "Could not get bus");
+
+  /* use the sync signal handler to link elements while the pipeline is still
+   * doing the state change */
+  gst_bus_set_sync_handler (bus, gst_bus_sync_signal_handler, pipeline);
+  g_object_connect (bus, "signal::sync-message::state-changed",
+      G_CALLBACK (test_link_structure_change_state_changed_sync_cb), pipeline, NULL);
+
+  src = gst_element_factory_make ("fakesrc", "src");
+  fail_if (src == NULL, "Could not create fakesrc");
+
+  identity = gst_element_factory_make ("identity", "identity");
+  fail_if (identity == NULL, "Could not create identity");
+
+  sink = gst_element_factory_make ("fakesink", "sink");
+  fail_if (sink == NULL, "Could not create fakesink1");
+
+  gst_bin_add_many (GST_BIN (pipeline), src, identity, sink, NULL);
+
+  gst_element_set_state (pipeline, GST_STATE_READY);
+  gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
+
+  /* the state change will be done on src only if the pipeline correctly resyncs
+   * after that filesrc has been linked to identity */
+  gst_element_get_state (src, &state, NULL, 0);
+  fail_unless_equals_int (state, GST_STATE_READY);
+
+  gst_object_unref (bus);
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
 Suite *
 gst_bin_suite (void)
 {
@@ -911,6 +986,7 @@
   tcase_add_test (tc_chain, test_add_linked);
   tcase_add_test (tc_chain, test_add_self);
   tcase_add_test (tc_chain, test_iterate_sorted);
+  tcase_add_test (tc_chain, test_link_structure_change);
 
   return s;
 }

