44f93db0ed073fac16954d7db1fef3a72ef62d92
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / ext / gtk / gstgtkwaylandsink.c
1 /*
2  * GStreamer
3  * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
4  * Copyright (C) 2021 Collabora Ltd.
5  *   @author George Kiagiadakis <george.kiagiadakis@collabora.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 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  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include "gstgtkwaylandsink.h"
28
29 #include <gdk/gdk.h>
30 #include <gst/wayland/wayland.h>
31
32 #include "gstgtkutils.h"
33 #include "gtkgstwaylandwidget.h"
34
35 #ifdef GDK_WINDOWING_WAYLAND
36 #include <gdk/gdkwayland.h>
37 #else
38 #error "Wayland is not supported in GTK+"
39 #endif
40
41 #define GST_CAT_DEFAULT gst_debug_gtk_wayland_sink
42 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
43
44 #ifndef GST_CAPS_FEATURE_MEMORY_DMABUF
45 #define GST_CAPS_FEATURE_MEMORY_DMABUF "memory:DMABuf"
46 #endif
47
48 #define WL_VIDEO_FORMATS \
49   "{ BGRx, BGRA, RGBx, xBGR, xRGB, RGBA, ABGR, ARGB, RGB, BGR, " \
50   "RGB16, BGR16, YUY2, YVYU, UYVY, AYUV, NV12, NV21, NV16, NV61, " \
51   "YUV9, YVU9, Y41B, I420, YV12, Y42B, v308 }"
52
53 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
54     GST_PAD_SINK,
55     GST_PAD_ALWAYS,
56     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (WL_VIDEO_FORMATS) ";"
57         GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_DMABUF,
58             WL_VIDEO_FORMATS))
59     );
60
61 static void gst_gtk_wayland_sink_get_property (GObject * object,
62     guint prop_id, GValue * value, GParamSpec * pspec);
63 static void gst_gtk_wayland_sink_set_property (GObject * object,
64     guint prop_id, const GValue * value, GParamSpec * pspec);
65 static void gst_gtk_wayland_sink_finalize (GObject * object);
66
67 static GstStateChangeReturn gst_gtk_wayland_sink_change_state (GstElement *
68     element, GstStateChange transition);
69
70 static gboolean gst_gtk_wayland_sink_event (GstBaseSink * sink,
71     GstEvent * event);
72 static GstCaps *gst_gtk_wayland_sink_get_caps (GstBaseSink * bsink,
73     GstCaps * filter);
74 static gboolean gst_gtk_wayland_sink_set_caps (GstBaseSink * bsink,
75     GstCaps * caps);
76 static gboolean gst_gtk_wayland_sink_propose_allocation (GstBaseSink * bsink,
77     GstQuery * query);
78 static GstFlowReturn gst_gtk_wayland_sink_show_frame (GstVideoSink * bsink,
79     GstBuffer * buffer);
80 static void gst_gtk_wayland_sink_set_rotate_method (GstGtkWaylandSink * self,
81     GstVideoOrientationMethod method, gboolean from_tag);
82
83 static void
84 gst_gtk_wayland_sink_navigation_interface_init (GstNavigationInterface * iface);
85
86 static void
87 calculate_adjustment (GtkWidget * start_widget, GtkAllocation * allocation);
88
89 enum
90 {
91   PROP_0,
92   PROP_WIDGET,
93   PROP_DISPLAY,
94   PROP_ROTATE_METHOD
95 };
96
97 typedef struct _GstGtkWaylandSinkPrivate
98 {
99   GtkWidget *gtk_widget;
100   GtkWidget *gtk_window;
101   gulong gtk_window_destroy_id;
102
103   /* from GstWaylandSink */
104   GMutex display_lock;
105   GstWlDisplay *display;
106
107   GstWlWindow *wl_window;
108   gboolean is_wl_window_sync;
109
110   GstBufferPool *pool;
111   GstBuffer *last_buffer;
112   gboolean use_dmabuf;
113
114   gboolean video_info_changed;
115   GstVideoInfo video_info;
116
117   gboolean redraw_pending;
118   GMutex render_lock;
119
120   GstVideoOrientationMethod sink_rotate_method;
121   GstVideoOrientationMethod tag_rotate_method;
122   GstVideoOrientationMethod current_rotate_method;
123
124   struct wl_callback *callback;
125 } GstGtkWaylandSinkPrivate;
126
127 #define gst_gtk_wayland_sink_parent_class parent_class
128 G_DEFINE_TYPE_WITH_CODE (GstGtkWaylandSink, gst_gtk_wayland_sink,
129     GST_TYPE_VIDEO_SINK, G_ADD_PRIVATE (GstGtkWaylandSink)
130     G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
131         gst_gtk_wayland_sink_navigation_interface_init)
132     GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_wayland_sink,
133         "gtkwaylandsink", 0, "Gtk Wayland Video sink");
134     );
135 GST_ELEMENT_REGISTER_DEFINE (gtkwaylandsink, "gtkwaylandsink",
136     GST_RANK_MARGINAL, GST_TYPE_GTK_WAYLAND_SINK);
137
138 static void
139 gst_gtk_wayland_sink_class_init (GstGtkWaylandSinkClass * klass)
140 {
141   GObjectClass *gobject_class = (GObjectClass *) klass;
142   GstElementClass *gstelement_class = (GstElementClass *) klass;
143   GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass;
144   GstVideoSinkClass *gstvideosink_class = (GstVideoSinkClass *) klass;
145
146   gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_finalize);
147   gobject_class->get_property =
148       GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_get_property);
149   gobject_class->set_property =
150       GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_set_property);
151
152   g_object_class_install_property (gobject_class, PROP_WIDGET,
153       g_param_spec_object ("widget", "Gtk Widget",
154           "The GtkWidget to place in the widget hierarchy "
155           "(must only be get from the GTK main thread)",
156           GTK_TYPE_WIDGET,
157           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS |
158           GST_PARAM_DOC_SHOW_DEFAULT));
159
160   g_object_class_install_property (gobject_class, PROP_ROTATE_METHOD,
161       g_param_spec_enum ("rotate-method",
162           "rotate method",
163           "rotate method",
164           GST_TYPE_VIDEO_ORIENTATION_METHOD, GST_VIDEO_ORIENTATION_IDENTITY,
165           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
166
167   gstelement_class->change_state =
168       GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_change_state);
169
170   gst_element_class_set_metadata (gstelement_class, "Gtk Wayland Video Sink",
171       "Sink/Video",
172       "A video sink that renders to a GtkWidget using Wayland API",
173       "George Kiagiadakis <george.kiagiadakis@collabora.com>");
174
175   gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
176
177   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_event);
178   gstbasesink_class->get_caps =
179       GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_get_caps);
180   gstbasesink_class->set_caps =
181       GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_set_caps);
182   gstbasesink_class->propose_allocation =
183       GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_propose_allocation);
184
185   gstvideosink_class->show_frame =
186       GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_show_frame);
187 }
188
189 static void
190 gst_gtk_wayland_sink_init (GstGtkWaylandSink * self)
191 {
192   GstGtkWaylandSinkPrivate *priv =
193       gst_gtk_wayland_sink_get_instance_private (self);
194
195   g_mutex_init (&priv->display_lock);
196   g_mutex_init (&priv->render_lock);
197 }
198
199 static void
200 gst_gtk_wayland_sink_finalize (GObject * object)
201 {
202   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
203   GstGtkWaylandSinkPrivate *priv =
204       gst_gtk_wayland_sink_get_instance_private (self);
205
206   g_clear_object (&priv->gtk_widget);
207
208   G_OBJECT_CLASS (parent_class)->finalize (object);
209 }
210
211 static void
212 widget_destroy_cb (GtkWidget * widget, GstGtkWaylandSink * self)
213 {
214   GstGtkWaylandSinkPrivate *priv =
215       gst_gtk_wayland_sink_get_instance_private (self);
216
217   GST_OBJECT_LOCK (self);
218   g_clear_object (&priv->gtk_widget);
219   GST_OBJECT_UNLOCK (self);
220 }
221
222 static void
223 window_destroy_cb (GtkWidget * widget, GstGtkWaylandSink * self)
224 {
225   GstGtkWaylandSinkPrivate *priv =
226       gst_gtk_wayland_sink_get_instance_private (self);
227
228   GST_OBJECT_LOCK (self);
229   priv->gtk_window = NULL;
230   GST_OBJECT_UNLOCK (self);
231
232   GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("Window was closed"), (NULL));
233 }
234
235 static gboolean
236 widget_size_allocate_cb (GtkWidget * widget, GtkAllocation * allocation,
237     gpointer user_data)
238 {
239   GstGtkWaylandSink *self = user_data;
240   GstGtkWaylandSinkPrivate *priv =
241       gst_gtk_wayland_sink_get_instance_private (self);
242   struct wl_subsurface *window_subsurface;
243
244   g_mutex_lock (&priv->render_lock);
245
246   priv->is_wl_window_sync = TRUE;
247
248   window_subsurface = gst_wl_window_get_subsurface (priv->wl_window);
249   if (window_subsurface)
250     wl_subsurface_set_sync (window_subsurface);
251
252   calculate_adjustment (priv->gtk_widget, allocation);
253
254   GST_DEBUG_OBJECT (self, "window geometry changed to (%d, %d) %d x %d",
255       allocation->x, allocation->y, allocation->width, allocation->height);
256   gst_wl_window_set_render_rectangle (priv->wl_window, allocation->x,
257       allocation->y, allocation->width, allocation->height);
258
259   g_mutex_unlock (&priv->render_lock);
260
261   return FALSE;
262 }
263
264 static gboolean
265 window_after_after_paint_cb (GtkWidget * widget, gpointer user_data)
266 {
267   GstGtkWaylandSink *self = user_data;
268   GstGtkWaylandSinkPrivate *priv =
269       gst_gtk_wayland_sink_get_instance_private (self);
270
271   g_mutex_lock (&priv->render_lock);
272
273   if (priv->is_wl_window_sync) {
274     struct wl_subsurface *window_subsurface;
275
276     priv->is_wl_window_sync = FALSE;
277
278     window_subsurface = gst_wl_window_get_subsurface (priv->wl_window);
279     if (window_subsurface)
280       wl_subsurface_set_desync (window_subsurface);
281   }
282
283   g_mutex_unlock (&priv->render_lock);
284
285   return FALSE;
286 }
287
288 static GtkWidget *
289 gst_gtk_wayland_sink_get_widget (GstGtkWaylandSink * self)
290 {
291   GstGtkWaylandSinkPrivate *priv =
292       gst_gtk_wayland_sink_get_instance_private (self);
293
294   if (priv->gtk_widget != NULL)
295     return g_object_ref (priv->gtk_widget);
296
297   /* Ensure GTK is initialized, this has no side effect if it was already
298    * initialized. Also, we do that lazily, so the application can be first */
299   if (!gtk_init_check (NULL, NULL)) {
300     GST_INFO_OBJECT (self, "Could not ensure GTK initialization.");
301     return NULL;
302   }
303
304   priv->gtk_widget = gtk_gst_wayland_widget_new ();
305   gtk_gst_base_widget_set_element (GTK_GST_BASE_WIDGET (priv->gtk_widget),
306       GST_ELEMENT (self));
307
308   /* Take the floating ref, other wise the destruction of the container will
309    * make this widget disappear possibly before we are done. */
310   g_object_ref_sink (priv->gtk_widget);
311   g_signal_connect_object (priv->gtk_widget, "destroy",
312       G_CALLBACK (widget_destroy_cb), self, 0);
313
314   return g_object_ref (priv->gtk_widget);
315 }
316
317 static GtkWidget *
318 gst_gtk_wayland_sink_acquire_widget (GstGtkWaylandSink * self)
319 {
320   GstGtkWaylandSinkPrivate *priv =
321       gst_gtk_wayland_sink_get_instance_private (self);
322   gpointer widget = NULL;
323
324   GST_OBJECT_LOCK (self);
325   if (priv->gtk_widget != NULL)
326     widget = g_object_ref (priv->gtk_widget);
327   GST_OBJECT_UNLOCK (self);
328
329   if (!widget)
330     widget =
331         gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_wayland_sink_get_widget,
332         self);
333
334   return widget;
335 }
336
337 static gboolean
338 gst_gtk_wayland_sink_event (GstBaseSink * sink, GstEvent * event)
339 {
340   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (sink);
341   GstTagList *taglist;
342   GstVideoOrientationMethod method;
343   gboolean ret;
344
345   GST_DEBUG_OBJECT (self, "handling %s event", GST_EVENT_TYPE_NAME (event));
346
347   switch (GST_EVENT_TYPE (event)) {
348     case GST_EVENT_TAG:
349       gst_event_parse_tag (event, &taglist);
350
351       if (gst_video_orientation_from_tag (taglist, &method)) {
352         gst_gtk_wayland_sink_set_rotate_method (self, method, TRUE);
353       }
354
355       break;
356     default:
357       break;
358   }
359
360   ret = GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
361
362   return ret;
363 }
364
365 static void
366 gst_gtk_wayland_sink_get_property (GObject * object, guint prop_id,
367     GValue * value, GParamSpec * pspec)
368 {
369   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
370   GstGtkWaylandSinkPrivate *priv =
371       gst_gtk_wayland_sink_get_instance_private (self);
372
373   switch (prop_id) {
374     case PROP_WIDGET:
375       g_value_take_object (value, gst_gtk_wayland_sink_acquire_widget (self));
376       break;
377     case PROP_ROTATE_METHOD:
378       g_value_set_enum (value, priv->current_rotate_method);
379       break;
380     default:
381       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
382       break;
383   }
384 }
385
386 static void
387 gst_gtk_wayland_sink_set_property (GObject * object, guint prop_id,
388     const GValue * value, GParamSpec * pspec)
389 {
390   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
391
392   switch (prop_id) {
393     case PROP_ROTATE_METHOD:
394       gst_gtk_wayland_sink_set_rotate_method (self, g_value_get_enum (value),
395           FALSE);
396       break;
397     default:
398       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
399       break;
400   }
401 }
402
403 static void
404 calculate_adjustment (GtkWidget * widget, GtkAllocation * allocation)
405 {
406   GdkWindow *window;
407   gint wx, wy;
408
409   window = gtk_widget_get_window (widget);
410   gdk_window_get_origin (window, &wx, &wy);
411
412   allocation->x = wx;
413   allocation->y = wy;
414 }
415
416 static gboolean
417 scrollable_window_adjustment_changed_cb (GtkAdjustment * adjustment,
418     gpointer user_data)
419 {
420   GstGtkWaylandSink *self = user_data;
421   GstGtkWaylandSinkPrivate *priv =
422       gst_gtk_wayland_sink_get_instance_private (self);
423   GtkAllocation allocation;
424
425   gtk_widget_get_allocation (priv->gtk_widget, &allocation);
426   calculate_adjustment (priv->gtk_widget, &allocation);
427   gst_wl_window_set_render_rectangle (priv->wl_window, allocation.x,
428       allocation.y, allocation.width, allocation.height);
429
430   return FALSE;
431 }
432
433 static void
434 setup_wl_window (GstGtkWaylandSink * self)
435 {
436   GstGtkWaylandSinkPrivate *priv =
437       gst_gtk_wayland_sink_get_instance_private (self);
438   GdkWindow *gdk_window;
439   GdkFrameClock *gdk_frame_clock;
440   GtkAllocation allocation;
441   GtkWidget *widget;
442
443   g_mutex_lock (&priv->render_lock);
444
445   gdk_window = gtk_widget_get_window (priv->gtk_widget);
446   g_assert (gdk_window);
447
448   if (!priv->wl_window) {
449     struct wl_surface *wl_surface;
450
451     wl_surface = gdk_wayland_window_get_wl_surface (gdk_window);
452
453     GST_INFO_OBJECT (self, "setting window handle");
454
455     priv->wl_window = gst_wl_window_new_in_surface (priv->display,
456         wl_surface, &priv->render_lock);
457     gst_wl_window_set_rotate_method (priv->wl_window,
458         priv->current_rotate_method);
459   }
460
461   /* In order to position the subsurface correctly within a scrollable widget,
462    * we can not rely on the allocation alone but need to take the window
463    * origin into account
464    */
465   widget = priv->gtk_widget;
466   do {
467     if (GTK_IS_SCROLLABLE (widget)) {
468       GtkAdjustment *hadjustment;
469       GtkAdjustment *vadjustment;
470
471       hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
472       vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
473
474       g_signal_connect (hadjustment, "value-changed",
475           G_CALLBACK (scrollable_window_adjustment_changed_cb), self);
476       g_signal_connect (vadjustment, "value-changed",
477           G_CALLBACK (scrollable_window_adjustment_changed_cb), self);
478     }
479   } while ((widget = gtk_widget_get_parent (widget)));
480
481   gtk_widget_get_allocation (priv->gtk_widget, &allocation);
482   calculate_adjustment (priv->gtk_widget, &allocation);
483   gst_wl_window_set_render_rectangle (priv->wl_window, allocation.x,
484       allocation.y, allocation.width, allocation.height);
485
486   /* Make subsurfaces syncronous during resizes.
487    * Unfortunately GTK/GDK does not provide easier to use signals.
488    */
489   g_signal_connect (priv->gtk_widget, "size-allocate",
490       G_CALLBACK (widget_size_allocate_cb), self);
491   gdk_frame_clock = gdk_window_get_frame_clock (gdk_window);
492   g_signal_connect_after (gdk_frame_clock, "after-paint",
493       G_CALLBACK (window_after_after_paint_cb), self);
494
495   /* Ensure the base widget is initialized */
496   gtk_gst_base_widget_set_buffer (GTK_GST_BASE_WIDGET (priv->gtk_widget), NULL);
497
498   g_mutex_unlock (&priv->render_lock);
499 }
500
501 static void
502 window_initial_map_cb (GtkWidget * widget, gpointer user_data)
503 {
504   GstGtkWaylandSink *self = user_data;
505   GstGtkWaylandSinkPrivate *priv =
506       gst_gtk_wayland_sink_get_instance_private (self);
507
508   setup_wl_window (self);
509   g_signal_handlers_disconnect_by_func (priv->gtk_widget,
510       window_initial_map_cb, self);
511 }
512
513 static void
514 gst_gtk_wayland_sink_navigation_send_event (GstNavigation * navigation,
515     GstEvent * event)
516 {
517   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (navigation);
518   GstPad *pad;
519   gdouble x, y;
520
521   event = gst_event_make_writable (event);
522
523   if (gst_navigation_event_get_coordinates (event, &x, &y)) {
524     GtkGstBaseWidget *widget =
525         GTK_GST_BASE_WIDGET (gst_gtk_wayland_sink_get_widget (self));
526     gdouble stream_x, stream_y;
527
528     if (widget == NULL) {
529       GST_ERROR_OBJECT (self, "Could not ensure GTK initialization.");
530       return;
531     }
532
533     gtk_gst_base_widget_display_size_to_stream_size (widget,
534         x, y, &stream_x, &stream_y);
535     gst_navigation_event_set_coordinates (event, stream_x, stream_y);
536   }
537
538   pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (self));
539
540   GST_TRACE_OBJECT (self, "navigation event %" GST_PTR_FORMAT,
541       gst_event_get_structure (event));
542
543   if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
544     if (!gst_pad_send_event (pad, gst_event_ref (event))) {
545       /* If upstream didn't handle the event we'll post a message with it
546        * for the application in case it wants to do something with it */
547       gst_element_post_message (GST_ELEMENT_CAST (self),
548           gst_navigation_message_new_event (GST_OBJECT_CAST (self), event));
549     }
550     gst_event_unref (event);
551     gst_object_unref (pad);
552   }
553 }
554
555 static void
556 gst_gtk_wayland_sink_navigation_interface_init (GstNavigationInterface * iface)
557 {
558   iface->send_event_simple = gst_gtk_wayland_sink_navigation_send_event;
559 }
560
561
562 static gboolean
563 gst_gtk_wayland_sink_start_on_main (GstGtkWaylandSink * self)
564 {
565   GstGtkWaylandSinkPrivate *priv =
566       gst_gtk_wayland_sink_get_instance_private (self);
567   GtkWidget *toplevel;
568   GdkDisplay *gdk_display;
569   struct wl_display *wl_display;
570
571   if ((toplevel = gst_gtk_wayland_sink_get_widget (self)) == NULL) {
572     GST_ERROR_OBJECT (self, "Could not ensure GTK initialization.");
573     return FALSE;
574   }
575   g_object_unref (toplevel);
576
577   /* After this point, priv->gtk_widget will always be set */
578
579   gdk_display = gtk_widget_get_display (priv->gtk_widget);
580   if (!GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
581     GST_ERROR_OBJECT (self, "GDK is not using its wayland backend.");
582     return FALSE;
583   }
584   wl_display = gdk_wayland_display_get_wl_display (gdk_display);
585   priv->display = gst_wl_display_new_existing (wl_display, FALSE, NULL);
586
587   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (priv->gtk_widget));
588   if (!gtk_widget_is_toplevel (toplevel)) {
589     /* User did not add widget its own UI, let's popup a new GtkWindow to
590      * make gst-launch-1.0 work. */
591     priv->gtk_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
592     gtk_window_set_default_size (GTK_WINDOW (priv->gtk_window), 640, 480);
593     gtk_window_set_title (GTK_WINDOW (priv->gtk_window),
594         "Gst GTK Wayland Sink");
595     gtk_container_add (GTK_CONTAINER (priv->gtk_window), toplevel);
596     priv->gtk_window_destroy_id = g_signal_connect (priv->gtk_window, "destroy",
597         G_CALLBACK (window_destroy_cb), self);
598
599     g_signal_connect (priv->gtk_widget, "map",
600         G_CALLBACK (window_initial_map_cb), self);
601   } else {
602     if (gtk_widget_get_mapped (priv->gtk_widget)) {
603       setup_wl_window (self);
604     } else {
605       g_signal_connect (priv->gtk_widget, "map",
606           G_CALLBACK (window_initial_map_cb), self);
607     }
608   }
609
610   return TRUE;
611 }
612
613 static gboolean
614 gst_gtk_wayland_sink_stop_on_main (GstGtkWaylandSink * self)
615 {
616   GstGtkWaylandSinkPrivate *priv =
617       gst_gtk_wayland_sink_get_instance_private (self);
618
619   if (priv->gtk_window) {
620     if (priv->gtk_window_destroy_id)
621       g_signal_handler_disconnect (priv->gtk_window,
622           priv->gtk_window_destroy_id);
623     priv->gtk_window_destroy_id = 0;
624     gtk_widget_destroy (priv->gtk_window);
625     priv->gtk_window = NULL;
626   }
627
628   if (priv->gtk_widget) {
629     GtkWidget *widget;
630     GdkWindow *gdk_window;
631
632     g_signal_handlers_disconnect_by_func (priv->gtk_widget,
633         widget_size_allocate_cb, self);
634
635     widget = priv->gtk_widget;
636     do {
637       if (GTK_IS_SCROLLABLE (widget)) {
638         GtkAdjustment *hadjustment;
639         GtkAdjustment *vadjustment;
640
641         hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
642         vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
643
644         g_signal_handlers_disconnect_by_func (hadjustment,
645             scrollable_window_adjustment_changed_cb, self);
646         g_signal_handlers_disconnect_by_func (vadjustment,
647             scrollable_window_adjustment_changed_cb, self);
648       }
649     } while ((widget = gtk_widget_get_parent (widget)));
650
651     gdk_window = gtk_widget_get_window (priv->gtk_widget);
652     if (gdk_window) {
653       GdkFrameClock *gdk_frame_clock;
654
655       gdk_frame_clock = gdk_window_get_frame_clock (gdk_window);
656       g_signal_handlers_disconnect_by_func (gdk_frame_clock,
657           window_after_after_paint_cb, self);
658     }
659   }
660
661   return TRUE;
662 }
663
664 static void
665 gst_gtk_widget_show_all_and_unref (GtkWidget * widget)
666 {
667   gtk_widget_show_all (widget);
668   g_object_unref (widget);
669 }
670
671 static GstStateChangeReturn
672 gst_gtk_wayland_sink_change_state (GstElement * element,
673     GstStateChange transition)
674 {
675   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (element);
676   GstGtkWaylandSinkPrivate *priv =
677       gst_gtk_wayland_sink_get_instance_private (self);
678   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
679
680   switch (transition) {
681     case GST_STATE_CHANGE_NULL_TO_READY:
682       if (!gst_gtk_invoke_on_main ((GThreadFunc)
683               gst_gtk_wayland_sink_start_on_main, element))
684         return GST_STATE_CHANGE_FAILURE;
685       break;
686     case GST_STATE_CHANGE_READY_TO_PAUSED:
687     {
688       GtkWindow *window = NULL;
689
690       GST_OBJECT_LOCK (self);
691       if (priv->gtk_window)
692         window = g_object_ref (GTK_WINDOW (priv->gtk_window));
693       GST_OBJECT_UNLOCK (self);
694
695       if (window)
696         gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_widget_show_all_and_unref,
697             window);
698
699       break;
700     }
701     default:
702       break;
703   }
704
705   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
706   if (ret != GST_STATE_CHANGE_SUCCESS)
707     return ret;
708
709   switch (transition) {
710     case GST_STATE_CHANGE_READY_TO_NULL:
711     case GST_STATE_CHANGE_NULL_TO_NULL:
712       gst_gtk_invoke_on_main ((GThreadFunc)
713           gst_gtk_wayland_sink_stop_on_main, element);
714       break;
715     case GST_STATE_CHANGE_PAUSED_TO_READY:
716       gst_buffer_replace (&priv->last_buffer, NULL);
717       if (priv->wl_window) {
718         /* remove buffer from surface, show nothing */
719         gst_wl_window_render (priv->wl_window, NULL, NULL);
720       }
721
722       g_mutex_lock (&priv->render_lock);
723       if (priv->callback) {
724         wl_callback_destroy (priv->callback);
725         priv->callback = NULL;
726       }
727       priv->redraw_pending = FALSE;
728       g_mutex_unlock (&priv->render_lock);
729       break;
730     default:
731       break;
732   }
733
734   return ret;
735 }
736
737 static GstCaps *
738 gst_gtk_wayland_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
739 {
740   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
741   GstGtkWaylandSinkPrivate *priv =
742       gst_gtk_wayland_sink_get_instance_private (self);
743   GstCaps *caps;
744
745   caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (self));
746   caps = gst_caps_make_writable (caps);
747
748   g_mutex_lock (&priv->display_lock);
749
750   if (priv->display) {
751     GValue shm_list = G_VALUE_INIT, dmabuf_list = G_VALUE_INIT;
752     GValue value = G_VALUE_INIT;
753     GArray *formats;
754     gint i;
755     guint fmt;
756     GstVideoFormat gfmt;
757
758     g_value_init (&shm_list, GST_TYPE_LIST);
759     g_value_init (&dmabuf_list, GST_TYPE_LIST);
760
761     /* Add corresponding shm formats */
762     formats = gst_wl_display_get_shm_formats (priv->display);
763     for (i = 0; i < formats->len; i++) {
764       fmt = g_array_index (formats, uint32_t, i);
765       gfmt = gst_wl_shm_format_to_video_format (fmt);
766       if (gfmt != GST_VIDEO_FORMAT_UNKNOWN) {
767         g_value_init (&value, G_TYPE_STRING);
768         g_value_set_static_string (&value, gst_video_format_to_string (gfmt));
769         gst_value_list_append_and_take_value (&shm_list, &value);
770       }
771     }
772
773     gst_structure_take_value (gst_caps_get_structure (caps, 0), "format",
774         &shm_list);
775
776     /* Add corresponding dmabuf formats */
777     formats = gst_wl_display_get_dmabuf_formats (priv->display);
778     for (i = 0; i < formats->len; i++) {
779       fmt = g_array_index (formats, uint32_t, i);
780       gfmt = gst_wl_dmabuf_format_to_video_format (fmt);
781       if (gfmt != GST_VIDEO_FORMAT_UNKNOWN) {
782         g_value_init (&value, G_TYPE_STRING);
783         g_value_set_static_string (&value, gst_video_format_to_string (gfmt));
784         gst_value_list_append_and_take_value (&dmabuf_list, &value);
785       }
786     }
787
788     gst_structure_take_value (gst_caps_get_structure (caps, 1), "format",
789         &dmabuf_list);
790
791     GST_DEBUG_OBJECT (self, "display caps: %" GST_PTR_FORMAT, caps);
792   }
793
794   g_mutex_unlock (&priv->display_lock);
795
796   if (filter) {
797     GstCaps *intersection;
798
799     intersection =
800         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
801     gst_caps_unref (caps);
802     caps = intersection;
803   }
804
805   return caps;
806 }
807
808 static GstBufferPool *
809 gst_gtk_wayland_create_pool (GstGtkWaylandSink * self, GstCaps * caps)
810 {
811   GstGtkWaylandSinkPrivate *priv =
812       gst_gtk_wayland_sink_get_instance_private (self);
813   GstBufferPool *pool = NULL;
814   GstStructure *structure;
815   gsize size = priv->video_info.size;
816   GstAllocator *alloc;
817
818   pool = gst_wl_video_buffer_pool_new ();
819
820   structure = gst_buffer_pool_get_config (pool);
821   gst_buffer_pool_config_set_params (structure, caps, size, 2, 0);
822
823   alloc = gst_wl_shm_allocator_get ();
824   gst_buffer_pool_config_set_allocator (structure, alloc, NULL);
825   if (!gst_buffer_pool_set_config (pool, structure)) {
826     g_object_unref (pool);
827     pool = NULL;
828   }
829   g_object_unref (alloc);
830
831   return pool;
832 }
833
834 static gboolean
835 gst_gtk_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
836 {
837   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
838   GstGtkWaylandSinkPrivate *priv =
839       gst_gtk_wayland_sink_get_instance_private (self);
840   gboolean use_dmabuf;
841   GstVideoFormat format;
842
843   GST_DEBUG_OBJECT (self, "set caps %" GST_PTR_FORMAT, caps);
844
845   /* extract info from caps */
846   if (!gst_video_info_from_caps (&priv->video_info, caps))
847     goto invalid_format;
848
849   format = GST_VIDEO_INFO_FORMAT (&priv->video_info);
850   priv->video_info_changed = TRUE;
851
852   /* create a new pool for the new caps */
853   if (priv->pool)
854     gst_object_unref (priv->pool);
855   priv->pool = gst_gtk_wayland_create_pool (self, caps);
856
857   use_dmabuf = gst_caps_features_contains (gst_caps_get_features (caps, 0),
858       GST_CAPS_FEATURE_MEMORY_DMABUF);
859
860   /* validate the format base on the memory type. */
861   if (use_dmabuf) {
862     if (!gst_wl_display_check_format_for_dmabuf (priv->display, format))
863       goto unsupported_format;
864   } else if (!gst_wl_display_check_format_for_shm (priv->display, format)) {
865     goto unsupported_format;
866   }
867
868   GST_OBJECT_LOCK (self);
869
870   if (priv->gtk_widget == NULL) {
871     GST_OBJECT_UNLOCK (self);
872     GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
873         ("Output widget was destroyed"), (NULL));
874     return FALSE;
875   }
876
877   if (!gtk_gst_base_widget_set_format (GTK_GST_BASE_WIDGET (priv->gtk_widget),
878           &priv->video_info)) {
879     GST_OBJECT_UNLOCK (self);
880     return FALSE;
881   }
882   GST_OBJECT_UNLOCK (self);
883
884   priv->use_dmabuf = use_dmabuf;
885
886   return TRUE;
887
888 invalid_format:
889   {
890     GST_ERROR_OBJECT (self,
891         "Could not locate image format from caps %" GST_PTR_FORMAT, caps);
892     return FALSE;
893   }
894 unsupported_format:
895   {
896     GST_ERROR_OBJECT (self, "Format %s is not available on the display",
897         gst_video_format_to_string (format));
898     return FALSE;
899   }
900 }
901
902 static gboolean
903 gst_gtk_wayland_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
904 {
905   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
906   GstGtkWaylandSinkPrivate *priv =
907       gst_gtk_wayland_sink_get_instance_private (self);
908   GstCaps *caps;
909   GstBufferPool *pool = NULL;
910   gboolean need_pool;
911   GstAllocator *alloc;
912
913   gst_query_parse_allocation (query, &caps, &need_pool);
914
915   if (need_pool)
916     pool = gst_gtk_wayland_create_pool (self, caps);
917
918   gst_query_add_allocation_pool (query, pool, priv->video_info.size, 2, 0);
919   if (pool)
920     g_object_unref (pool);
921
922   alloc = gst_wl_shm_allocator_get ();
923   gst_query_add_allocation_param (query, alloc, NULL);
924   gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
925   g_object_unref (alloc);
926
927   return TRUE;
928 }
929
930 static void
931 frame_redraw_callback (void *data, struct wl_callback *callback, uint32_t time)
932 {
933   GstGtkWaylandSink *self = data;
934   GstGtkWaylandSinkPrivate *priv =
935       gst_gtk_wayland_sink_get_instance_private (self);
936
937   GST_LOG_OBJECT (self, "frame_redraw_cb");
938
939   g_mutex_lock (&priv->render_lock);
940   priv->redraw_pending = FALSE;
941
942   if (priv->callback) {
943     wl_callback_destroy (callback);
944     priv->callback = NULL;
945   }
946   g_mutex_unlock (&priv->render_lock);
947 }
948
949 static const struct wl_callback_listener frame_callback_listener = {
950   frame_redraw_callback
951 };
952
953 /* must be called with the render lock */
954 static void
955 render_last_buffer (GstGtkWaylandSink * self, gboolean redraw)
956 {
957   GstGtkWaylandSinkPrivate *priv =
958       gst_gtk_wayland_sink_get_instance_private (self);
959   GstWlBuffer *wlbuffer;
960   const GstVideoInfo *info = NULL;
961   struct wl_surface *surface;
962   struct wl_callback *callback;
963
964   if (!priv->wl_window)
965     return;
966
967   wlbuffer = gst_buffer_get_wl_buffer (priv->display, priv->last_buffer);
968   surface = gst_wl_window_get_wl_surface (priv->wl_window);
969
970   priv->redraw_pending = TRUE;
971   callback = wl_surface_frame (surface);
972   priv->callback = callback;
973   wl_callback_add_listener (callback, &frame_callback_listener, self);
974
975   if (G_UNLIKELY (priv->video_info_changed && !redraw)) {
976     info = &priv->video_info;
977     priv->video_info_changed = FALSE;
978   }
979   gst_wl_window_render (priv->wl_window, wlbuffer, info);
980 }
981
982 static GstFlowReturn
983 gst_gtk_wayland_sink_show_frame (GstVideoSink * vsink, GstBuffer * buffer)
984 {
985   GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (vsink);
986   GstGtkWaylandSinkPrivate *priv =
987       gst_gtk_wayland_sink_get_instance_private (self);
988   GstBuffer *to_render;
989   GstWlBuffer *wlbuffer;
990   GstVideoMeta *vmeta;
991   GstVideoFormat format;
992   GstVideoInfo old_vinfo;
993   GstMemory *mem;
994   struct wl_buffer *wbuf = NULL;
995
996   GstFlowReturn ret = GST_FLOW_OK;
997
998   g_mutex_lock (&priv->render_lock);
999
1000   GST_LOG_OBJECT (self, "render buffer %" GST_PTR_FORMAT "", buffer);
1001
1002   if (!priv->wl_window) {
1003     GST_LOG_OBJECT (self,
1004         "buffer %" GST_PTR_FORMAT " dropped (waiting for window)", buffer);
1005     goto done;
1006   }
1007
1008   /* drop buffers until we get a frame callback */
1009   if (priv->redraw_pending) {
1010     GST_LOG_OBJECT (self, "buffer %" GST_PTR_FORMAT " dropped (redraw pending)",
1011         buffer);
1012     goto done;
1013   }
1014
1015   /* make sure that the application has called set_render_rectangle() */
1016   if (G_UNLIKELY (gst_wl_window_get_render_rectangle (priv->wl_window)->w == 0))
1017     goto no_window_size;
1018
1019   wlbuffer = gst_buffer_get_wl_buffer (priv->display, buffer);
1020
1021   if (G_LIKELY (wlbuffer &&
1022           gst_wl_buffer_get_display (wlbuffer) == priv->display)) {
1023     GST_LOG_OBJECT (self,
1024         "buffer %" GST_PTR_FORMAT " has a wl_buffer from our display, "
1025         "writing directly", buffer);
1026     to_render = buffer;
1027     goto render;
1028   }
1029
1030   /* update video info from video meta */
1031   mem = gst_buffer_peek_memory (buffer, 0);
1032
1033   old_vinfo = priv->video_info;
1034   vmeta = gst_buffer_get_video_meta (buffer);
1035   if (vmeta) {
1036     gint i;
1037
1038     for (i = 0; i < vmeta->n_planes; i++) {
1039       priv->video_info.offset[i] = vmeta->offset[i];
1040       priv->video_info.stride[i] = vmeta->stride[i];
1041     }
1042     priv->video_info.size = gst_buffer_get_size (buffer);
1043   }
1044
1045   GST_LOG_OBJECT (self,
1046       "buffer %" GST_PTR_FORMAT " does not have a wl_buffer from our "
1047       "display, creating it", buffer);
1048
1049   format = GST_VIDEO_INFO_FORMAT (&priv->video_info);
1050   if (gst_wl_display_check_format_for_dmabuf (priv->display, format)) {
1051     guint i, nb_dmabuf = 0;
1052
1053     for (i = 0; i < gst_buffer_n_memory (buffer); i++)
1054       if (gst_is_dmabuf_memory (gst_buffer_peek_memory (buffer, i)))
1055         nb_dmabuf++;
1056
1057     if (nb_dmabuf && (nb_dmabuf == gst_buffer_n_memory (buffer)))
1058       wbuf = gst_wl_linux_dmabuf_construct_wl_buffer (buffer, priv->display,
1059           &priv->video_info);
1060   }
1061
1062   if (!wbuf && gst_wl_display_check_format_for_shm (priv->display, format)) {
1063     if (gst_buffer_n_memory (buffer) == 1 && gst_is_fd_memory (mem))
1064       wbuf = gst_wl_shm_memory_construct_wl_buffer (mem, priv->display,
1065           &priv->video_info);
1066
1067     /* If nothing worked, copy into our internal pool */
1068     if (!wbuf) {
1069       GstVideoFrame src, dst;
1070       GstVideoInfo src_info = priv->video_info;
1071
1072       /* rollback video info changes */
1073       priv->video_info = old_vinfo;
1074
1075       /* we don't know how to create a wl_buffer directly from the provided
1076        * memory, so we have to copy the data to shm memory that we know how
1077        * to handle... */
1078
1079       GST_LOG_OBJECT (self,
1080           "buffer %" GST_PTR_FORMAT " cannot have a wl_buffer, "
1081           "copying to wl_shm memory", buffer);
1082
1083       /* priv->pool always exists (created in set_caps), but it may not
1084        * be active if upstream is not using it */
1085       if (!gst_buffer_pool_is_active (priv->pool)) {
1086         GstStructure *config;
1087         GstCaps *caps;
1088
1089         config = gst_buffer_pool_get_config (priv->pool);
1090         gst_buffer_pool_config_get_params (config, &caps, NULL, NULL, NULL);
1091
1092         /* revert back to default strides and offsets */
1093         gst_video_info_from_caps (&priv->video_info, caps);
1094         gst_buffer_pool_config_set_params (config, caps, priv->video_info.size,
1095             2, 0);
1096
1097         /* This is a video pool, it should not fail with basic settings */
1098         if (!gst_buffer_pool_set_config (priv->pool, config) ||
1099             !gst_buffer_pool_set_active (priv->pool, TRUE))
1100           goto activate_failed;
1101       }
1102
1103       ret = gst_buffer_pool_acquire_buffer (priv->pool, &to_render, NULL);
1104       if (ret != GST_FLOW_OK)
1105         goto no_buffer;
1106
1107       wlbuffer = gst_buffer_get_wl_buffer (priv->display, to_render);
1108
1109       /* attach a wl_buffer if there isn't one yet */
1110       if (G_UNLIKELY (!wlbuffer)) {
1111         mem = gst_buffer_peek_memory (to_render, 0);
1112         wbuf = gst_wl_shm_memory_construct_wl_buffer (mem, priv->display,
1113             &priv->video_info);
1114
1115         if (G_UNLIKELY (!wbuf))
1116           goto no_wl_buffer_shm;
1117
1118         wlbuffer = gst_buffer_add_wl_buffer (to_render, wbuf, priv->display);
1119       }
1120
1121       if (!gst_video_frame_map (&dst, &priv->video_info, to_render,
1122               GST_MAP_WRITE))
1123         goto dst_map_failed;
1124
1125       if (!gst_video_frame_map (&src, &src_info, buffer, GST_MAP_READ)) {
1126         gst_video_frame_unmap (&dst);
1127         goto src_map_failed;
1128       }
1129
1130       gst_video_frame_copy (&dst, &src);
1131
1132       gst_video_frame_unmap (&src);
1133       gst_video_frame_unmap (&dst);
1134
1135       goto render;
1136     }
1137   }
1138
1139   if (!wbuf)
1140     goto no_wl_buffer;
1141
1142   wlbuffer = gst_buffer_add_wl_buffer (buffer, wbuf, priv->display);
1143   to_render = buffer;
1144
1145 render:
1146   /* drop double rendering */
1147   if (G_UNLIKELY (wlbuffer ==
1148           gst_buffer_get_wl_buffer (priv->display, priv->last_buffer))) {
1149     GST_LOG_OBJECT (self, "Buffer already being rendered");
1150     goto done;
1151   }
1152
1153   gst_buffer_replace (&priv->last_buffer, to_render);
1154   render_last_buffer (self, FALSE);
1155
1156   if (buffer != to_render)
1157     gst_buffer_unref (to_render);
1158   goto done;
1159
1160 no_window_size:
1161   {
1162     GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
1163         ("Window has no size set"),
1164         ("Make sure you set the size after calling set_window_handle"));
1165     ret = GST_FLOW_ERROR;
1166     goto done;
1167   }
1168 no_buffer:
1169   {
1170     GST_WARNING_OBJECT (self, "could not create buffer");
1171     goto done;
1172   }
1173 no_wl_buffer_shm:
1174   {
1175     GST_ERROR_OBJECT (self, "could not create wl_buffer out of wl_shm memory");
1176     ret = GST_FLOW_ERROR;
1177     goto done;
1178   }
1179 no_wl_buffer:
1180   {
1181     GST_ERROR_OBJECT (self,
1182         "buffer %" GST_PTR_FORMAT " cannot have a wl_buffer", buffer);
1183     ret = GST_FLOW_ERROR;
1184     goto done;
1185   }
1186 activate_failed:
1187   {
1188     GST_ERROR_OBJECT (self, "failed to activate bufferpool.");
1189     ret = GST_FLOW_ERROR;
1190     goto done;
1191   }
1192 src_map_failed:
1193   {
1194     GST_ELEMENT_ERROR (self, RESOURCE, READ,
1195         ("Video memory can not be read from userspace."), (NULL));
1196     ret = GST_FLOW_ERROR;
1197     goto done;
1198   }
1199 dst_map_failed:
1200   {
1201     GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
1202         ("Video memory can not be written from userspace."), (NULL));
1203     ret = GST_FLOW_ERROR;
1204     goto done;
1205   }
1206 done:
1207   {
1208     g_mutex_unlock (&priv->render_lock);
1209     return ret;
1210   }
1211 }
1212
1213 static void
1214 gst_gtk_wayland_sink_set_rotate_method (GstGtkWaylandSink * self,
1215     GstVideoOrientationMethod method, gboolean from_tag)
1216 {
1217   GstGtkWaylandSinkPrivate *priv =
1218       gst_gtk_wayland_sink_get_instance_private (self);
1219   GstVideoOrientationMethod new_method;
1220
1221   if (method == GST_VIDEO_ORIENTATION_CUSTOM) {
1222     GST_WARNING_OBJECT (self, "unsupported custom orientation");
1223     return;
1224   }
1225
1226   GST_OBJECT_LOCK (self);
1227   if (from_tag)
1228     priv->tag_rotate_method = method;
1229   else
1230     priv->sink_rotate_method = method;
1231
1232   if (priv->sink_rotate_method == GST_VIDEO_ORIENTATION_AUTO)
1233     new_method = priv->tag_rotate_method;
1234   else
1235     new_method = priv->sink_rotate_method;
1236
1237   if (new_method != priv->current_rotate_method) {
1238     GST_DEBUG_OBJECT (priv, "Changing method from %d to %d",
1239         priv->current_rotate_method, new_method);
1240
1241     if (priv->wl_window) {
1242       g_mutex_lock (&priv->render_lock);
1243       gst_wl_window_set_rotate_method (priv->wl_window, new_method);
1244       g_mutex_unlock (&priv->render_lock);
1245     }
1246
1247     priv->current_rotate_method = new_method;
1248   }
1249   GST_OBJECT_UNLOCK (self);
1250 }