gtk: Marshall state changes in the main thread
authorThibault Saunier <tsaunier@gnome.org>
Thu, 24 Sep 2015 08:51:31 +0000 (10:51 +0200)
committerThibault Saunier <tsaunier@gnome.org>
Thu, 24 Sep 2015 10:03:01 +0000 (12:03 +0200)
Gtk is not MT safe thus we need to make sure that everything is done
in the main thread when working with it.

https://bugzilla.gnome.org/show_bug.cgi?id=755251

ext/gtk/gstgtkbasesink.c

index 6321bda..41bb7f2 100644 (file)
@@ -77,6 +77,54 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstGtkBaseSink, gst_gtk_base_sink,
     GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_sink,
         "gtkbasesink", 0, "Gtk Video Sink base class"));
 
+struct invoke_context
+{
+  GThreadFunc func;
+  gpointer data;
+  GMutex lock;
+  GCond cond;
+  gboolean fired;
+
+  gpointer res;
+};
+
+static gboolean
+_invoke_func (struct invoke_context *info)
+{
+  g_mutex_lock (&info->lock);
+  info->res = info->func (info->data);
+  info->fired = TRUE;
+  g_cond_signal (&info->cond);
+  g_mutex_unlock (&info->lock);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gpointer
+_invoke_on_main (GThreadFunc func, gpointer data)
+{
+  GMainContext *main_context = g_main_context_default ();
+  struct invoke_context info;
+
+  g_mutex_init (&info.lock);
+  g_cond_init (&info.cond);
+  info.fired = FALSE;
+  info.func = func;
+  info.data = data;
+
+  g_main_context_invoke (main_context, (GSourceFunc) _invoke_func, &info);
+
+  g_mutex_lock (&info.lock);
+  while (!info.fired)
+    g_cond_wait (&info.cond, &info.lock);
+  g_mutex_unlock (&info.lock);
+
+  g_mutex_clear (&info.lock);
+  g_cond_clear (&info.cond);
+
+  return info.res;
+}
+
 static void
 gst_gtk_base_sink_class_init (GstGtkBaseSinkClass * klass)
 {
@@ -207,8 +255,21 @@ gst_gtk_base_sink_get_property (GObject * object, guint prop_id,
 
   switch (prop_id) {
     case PROP_WIDGET:
-      g_value_set_object (value, gst_gtk_base_sink_get_widget (gtk_sink));
+    {
+      GObject *widget = NULL;
+
+      GST_OBJECT_LOCK (gtk_sink);
+      if (gtk_sink->widget != NULL)
+        widget = G_OBJECT (gtk_sink->widget);
+      GST_OBJECT_UNLOCK (gtk_sink);
+
+      if (!widget)
+        widget = _invoke_on_main ((GThreadFunc) gst_gtk_base_sink_get_widget,
+            gtk_sink);
+
+      g_value_set_object (value, widget);
       break;
+    }
     case PROP_FORCE_ASPECT_RATIO:
       g_value_set_boolean (value, gtk_sink->force_aspect_ratio);
       break;
@@ -273,7 +334,7 @@ gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface)
 }
 
 static gboolean
-gst_gtk_base_sink_start (GstBaseSink * bsink)
+gst_gtk_base_sink_start_on_main (GstBaseSink * bsink)
 {
   GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
   GstGtkBaseSinkClass *klass = GST_GTK_BASE_SINK_GET_CLASS (bsink);
@@ -303,7 +364,14 @@ gst_gtk_base_sink_start (GstBaseSink * bsink)
 }
 
 static gboolean
-gst_gtk_base_sink_stop (GstBaseSink * bsink)
+gst_gtk_base_sink_start (GstBaseSink * bsink)
+{
+  return ! !_invoke_on_main ((GThreadFunc) gst_gtk_base_sink_start_on_main,
+      bsink);
+}
+
+static gboolean
+gst_gtk_base_sink_stop_on_main (GstBaseSink * bsink)
 {
   GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
 
@@ -317,11 +385,17 @@ gst_gtk_base_sink_stop (GstBaseSink * bsink)
 }
 
 static gboolean
-_show_window_cb (GstGtkBaseSink * gtk_sink)
+gst_gtk_base_sink_stop (GstBaseSink * bsink)
 {
-  gtk_widget_show_all (gtk_sink->window);
+  return ! !_invoke_on_main ((GThreadFunc) gst_gtk_base_sink_stop_on_main,
+      bsink);
+}
 
-  return FALSE;
+static void
+gst_gtk_widget_show_all_and_unref (GtkWidget * widget)
+{
+  gtk_widget_show_all (widget);
+  g_object_unref (widget);
 }
 
 static GstStateChangeReturn
@@ -340,11 +414,20 @@ gst_gtk_base_sink_change_state (GstElement * element, GstStateChange transition)
 
   switch (transition) {
     case GST_STATE_CHANGE_READY_TO_PAUSED:
+    {
+      GtkWindow *window = NULL;
+
       GST_OBJECT_LOCK (gtk_sink);
       if (gtk_sink->window)
-        g_idle_add ((GSourceFunc) _show_window_cb, gtk_sink);
+        window = g_object_ref (gtk_sink->window);
       GST_OBJECT_UNLOCK (gtk_sink);
+
+      if (window)
+        _invoke_on_main ((GThreadFunc) gst_gtk_widget_show_all_and_unref,
+            window);
+
       break;
+    }
     case GST_STATE_CHANGE_PAUSED_TO_READY:
       GST_OBJECT_LOCK (gtk_sink);
       if (gtk_sink->widget)