Add new gtkwaylandsink element
authorGeorge Kiagiadakis <george.kiagiadakis@collabora.com>
Wed, 8 Dec 2021 08:30:21 +0000 (10:30 +0200)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Tue, 2 Aug 2022 16:34:13 +0000 (16:34 +0000)
This is based on gtksink, but similar to waylandsink uses Wayland APIs
directly instead of rendering with Gtk/Cairo primitives.

Note that the long term plan is to move this into the existing extension
in `-good`, which requires the Wayland library to move the as well.

For this reason several files like `gstgtkutils.*` and `gtkgstbasewidget.*`
are straight copies and should be kept in sync.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1515>

17 files changed:
subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json
subprojects/gst-plugins-bad/ext/gtk/gstgtkutils.c [new file with mode: 0644]
subprojects/gst-plugins-bad/ext/gtk/gstgtkutils.h [new file with mode: 0644]
subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.c [new file with mode: 0644]
subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.h [new file with mode: 0644]
subprojects/gst-plugins-bad/ext/gtk/gstplugin.c [new file with mode: 0644]
subprojects/gst-plugins-bad/ext/gtk/gtkgstbasewidget.c [new file with mode: 0644]
subprojects/gst-plugins-bad/ext/gtk/gtkgstbasewidget.h [new file with mode: 0644]
subprojects/gst-plugins-bad/ext/gtk/gtkgstwaylandwidget.c [new file with mode: 0644]
subprojects/gst-plugins-bad/ext/gtk/gtkgstwaylandwidget.h [new file with mode: 0644]
subprojects/gst-plugins-bad/ext/gtk/meson.build [new file with mode: 0644]
subprojects/gst-plugins-bad/ext/meson.build
subprojects/gst-plugins-bad/meson_options.txt
subprojects/gst-plugins-bad/tests/examples/gtk/gtkwaylandsink.c [new file with mode: 0644]
subprojects/gst-plugins-bad/tests/examples/gtk/meson.build [new file with mode: 0644]
subprojects/gst-plugins-bad/tests/examples/gtk/window.ui [new file with mode: 0644]
subprojects/gst-plugins-bad/tests/examples/meson.build

index 99b4cb2..1e06813 100644 (file)
         "tracers": {},
         "url": "Unknown package origin"
     },
+    "gtkwayland": {
+        "description": "Gtk+ wayland sink",
+        "elements": {
+            "gtkwaylandsink": {
+                "author": "George Kiagiadakis <george.kiagiadakis@collabora.com>",
+                "description": "A video sink that renders to a GtkWidget using Wayland API",
+                "hierarchy": [
+                    "GstGtkWaylandSink",
+                    "GstVideoSink",
+                    "GstBaseSink",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "interfaces": [
+                    "GstNavigation"
+                ],
+                "klass": "Sink/Video",
+                "long-name": "Gtk Wayland Video Sink",
+                "pad-templates": {
+                    "sink": {
+                        "caps": "video/x-raw:\n         format: { BGRx, BGRA, RGBx, xBGR, xRGB, RGBA, ABGR, ARGB, RGB, BGR, RGB16, BGR16, YUY2, YVYU, UYVY, AYUV, NV12, NV21, NV16, NV61, YUV9, YVU9, Y41B, I420, YV12, Y42B, v308 }\n          width: [ 1, 2147483647 ]\n         height: [ 1, 2147483647 ]\n      framerate: [ 0/1, 2147483647/1 ]\n\nvideo/x-raw(memory:DMABuf):\n         format: { BGRx, BGRA, RGBx, xBGR, xRGB, RGBA, ABGR, ARGB, RGB, BGR, RGB16, BGR16, YUY2, YVYU, UYVY, AYUV, NV12, NV21, NV16, NV61, YUV9, YVU9, Y41B, I420, YV12, Y42B, v308 }\n          width: [ 1, 2147483647 ]\n         height: [ 1, 2147483647 ]\n      framerate: [ 0/1, 2147483647/1 ]\n",
+                        "direction": "sink",
+                        "presence": "always"
+                    }
+                },
+                "properties": {
+                    "rotate-method": {
+                        "blurb": "rotate method",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "identity (0)",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "GstVideoOrientationMethod",
+                        "writable": true
+                    },
+                    "widget": {
+                        "blurb": "The GtkWidget to place in the widget hierarchy (must only be get from the GTK main thread)",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "GtkWidget",
+                        "writable": false
+                    }
+                },
+                "rank": "marginal"
+            }
+        },
+        "filename": "gstgtkwayland",
+        "license": "LGPL",
+        "other-types": {},
+        "package": "GStreamer Bad Plug-ins",
+        "source": "gst-plugins-bad",
+        "tracers": {},
+        "url": "Unknown package origin"
+    },
     "hls": {
         "description": "HTTP Live Streaming (HLS)",
         "elements": {
diff --git a/subprojects/gst-plugins-bad/ext/gtk/gstgtkutils.c b/subprojects/gst-plugins-bad/ext/gtk/gstgtkutils.c
new file mode 100644 (file)
index 0000000..c730f01
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ * Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "gstgtkutils.h"
+
+struct invoke_context
+{
+  GThreadFunc func;
+  gpointer data;
+  GMutex lock;
+  GCond cond;
+  gboolean fired;
+
+  gpointer res;
+};
+
+static gboolean
+gst_gtk_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;
+}
+
+gpointer
+gst_gtk_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) gst_gtk_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;
+}
diff --git a/subprojects/gst-plugins-bad/ext/gtk/gstgtkutils.h b/subprojects/gst-plugins-bad/ext/gtk/gstgtkutils.h
new file mode 100644 (file)
index 0000000..7584ae2
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ * Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_GTK_UTILS_H__
+#define __GST_GTK_UTILS_H__
+
+#include <glib.h>
+
+gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data);
+
+#endif /* __GST_GTK_UTILS_H__ */
diff --git a/subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.c b/subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.c
new file mode 100644 (file)
index 0000000..e4dd06e
--- /dev/null
@@ -0,0 +1,1247 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ * Copyright (C) 2021 Collabora Ltd.
+ *   @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstgtkwaylandsink.h"
+
+#include <gdk/gdk.h>
+#include <gst/wayland/wayland.h>
+
+#include "gstgtkutils.h"
+#include "gtkgstwaylandwidget.h"
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#else
+#error "Wayland is not supported in GTK+"
+#endif
+
+#define GST_CAT_DEFAULT gst_debug_gtk_wayland_sink
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#ifndef GST_CAPS_FEATURE_MEMORY_DMABUF
+#define GST_CAPS_FEATURE_MEMORY_DMABUF "memory:DMABuf"
+#endif
+
+#define WL_VIDEO_FORMATS \
+  "{ BGRx, BGRA, RGBx, xBGR, xRGB, RGBA, ABGR, ARGB, RGB, BGR, " \
+  "RGB16, BGR16, YUY2, YVYU, UYVY, AYUV, NV12, NV21, NV16, NV61, " \
+  "YUV9, YVU9, Y41B, I420, YV12, Y42B, v308 }"
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (WL_VIDEO_FORMATS) ";"
+        GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_DMABUF,
+            WL_VIDEO_FORMATS))
+    );
+
+static void gst_gtk_wayland_sink_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+static void gst_gtk_wayland_sink_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_gtk_wayland_sink_finalize (GObject * object);
+
+static GstStateChangeReturn gst_gtk_wayland_sink_change_state (GstElement *
+    element, GstStateChange transition);
+
+static gboolean gst_gtk_wayland_sink_event (GstBaseSink * sink,
+    GstEvent * event);
+static GstCaps *gst_gtk_wayland_sink_get_caps (GstBaseSink * bsink,
+    GstCaps * filter);
+static gboolean gst_gtk_wayland_sink_set_caps (GstBaseSink * bsink,
+    GstCaps * caps);
+static gboolean gst_gtk_wayland_sink_propose_allocation (GstBaseSink * bsink,
+    GstQuery * query);
+static GstFlowReturn gst_gtk_wayland_sink_show_frame (GstVideoSink * bsink,
+    GstBuffer * buffer);
+static void gst_gtk_wayland_sink_set_rotate_method (GstGtkWaylandSink * self,
+    GstVideoOrientationMethod method, gboolean from_tag);
+
+static void
+gst_gtk_wayland_sink_navigation_interface_init (GstNavigationInterface * iface);
+
+static void
+calculate_adjustment (GtkWidget * start_widget, GtkAllocation * allocation);
+
+enum
+{
+  PROP_0,
+  PROP_WIDGET,
+  PROP_DISPLAY,
+  PROP_ROTATE_METHOD
+};
+
+typedef struct _GstGtkWaylandSinkPrivate
+{
+  GtkWidget *gtk_widget;
+  GtkWidget *gtk_window;
+  gulong gtk_window_destroy_id;
+
+  /* from GstWaylandSink */
+  GMutex display_lock;
+  GstWlDisplay *display;
+
+  GstWlWindow *wl_window;
+  gboolean is_wl_window_sync;
+
+  GstBufferPool *pool;
+  GstBuffer *last_buffer;
+  gboolean use_dmabuf;
+
+  gboolean video_info_changed;
+  GstVideoInfo video_info;
+
+  gboolean redraw_pending;
+  GMutex render_lock;
+
+  GstVideoOrientationMethod sink_rotate_method;
+  GstVideoOrientationMethod tag_rotate_method;
+  GstVideoOrientationMethod current_rotate_method;
+
+  struct wl_callback *callback;
+} GstGtkWaylandSinkPrivate;
+
+#define gst_gtk_wayland_sink_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGtkWaylandSink, gst_gtk_wayland_sink,
+    GST_TYPE_VIDEO_SINK, G_ADD_PRIVATE (GstGtkWaylandSink)
+    G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
+        gst_gtk_wayland_sink_navigation_interface_init)
+    GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_wayland_sink,
+        "gtkwaylandsink", 0, "Gtk Wayland Video sink");
+    );
+GST_ELEMENT_REGISTER_DEFINE (gtkwaylandsink, "gtkwaylandsink",
+    GST_RANK_MARGINAL, GST_TYPE_GTK_WAYLAND_SINK);
+
+static void
+gst_gtk_wayland_sink_class_init (GstGtkWaylandSinkClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  GstElementClass *gstelement_class = (GstElementClass *) klass;
+  GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass;
+  GstVideoSinkClass *gstvideosink_class = (GstVideoSinkClass *) klass;
+
+  gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_finalize);
+  gobject_class->get_property =
+      GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_get_property);
+  gobject_class->set_property =
+      GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_set_property);
+
+  g_object_class_install_property (gobject_class, PROP_WIDGET,
+      g_param_spec_object ("widget", "Gtk Widget",
+          "The GtkWidget to place in the widget hierarchy "
+          "(must only be get from the GTK main thread)",
+          GTK_TYPE_WIDGET,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS |
+          GST_PARAM_DOC_SHOW_DEFAULT));
+
+  g_object_class_install_property (gobject_class, PROP_ROTATE_METHOD,
+      g_param_spec_enum ("rotate-method",
+          "rotate method",
+          "rotate method",
+          GST_TYPE_VIDEO_ORIENTATION_METHOD, GST_VIDEO_ORIENTATION_IDENTITY,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gstelement_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_change_state);
+
+  gst_element_class_set_metadata (gstelement_class, "Gtk Wayland Video Sink",
+      "Sink/Video",
+      "A video sink that renders to a GtkWidget using Wayland API",
+      "George Kiagiadakis <george.kiagiadakis@collabora.com>");
+
+  gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
+
+  gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_event);
+  gstbasesink_class->get_caps =
+      GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_get_caps);
+  gstbasesink_class->set_caps =
+      GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_set_caps);
+  gstbasesink_class->propose_allocation =
+      GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_propose_allocation);
+
+  gstvideosink_class->show_frame =
+      GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_show_frame);
+}
+
+static void
+gst_gtk_wayland_sink_init (GstGtkWaylandSink * self)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+
+  g_mutex_init (&priv->display_lock);
+  g_mutex_init (&priv->render_lock);
+}
+
+static void
+gst_gtk_wayland_sink_finalize (GObject * object)
+{
+  GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+
+  g_clear_object (&priv->gtk_widget);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+widget_destroy_cb (GtkWidget * widget, GstGtkWaylandSink * self)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+
+  GST_OBJECT_LOCK (self);
+  g_clear_object (&priv->gtk_widget);
+  GST_OBJECT_UNLOCK (self);
+}
+
+static void
+window_destroy_cb (GtkWidget * widget, GstGtkWaylandSink * self)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+
+  GST_OBJECT_LOCK (self);
+  priv->gtk_window = NULL;
+  GST_OBJECT_UNLOCK (self);
+
+  GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("Window was closed"), (NULL));
+}
+
+static gboolean
+widget_size_allocate_cb (GtkWidget * widget, GtkAllocation * allocation,
+    gpointer user_data)
+{
+  GstGtkWaylandSink *self = user_data;
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  struct wl_subsurface *window_subsurface;
+
+  g_mutex_lock (&priv->render_lock);
+
+  priv->is_wl_window_sync = TRUE;
+
+  window_subsurface = gst_wl_window_get_subsurface (priv->wl_window);
+  if (window_subsurface)
+    wl_subsurface_set_sync (window_subsurface);
+
+  calculate_adjustment (priv->gtk_widget, allocation);
+
+  GST_DEBUG_OBJECT (self, "window geometry changed to (%d, %d) %d x %d",
+      allocation->x, allocation->y, allocation->width, allocation->height);
+  gst_wl_window_set_render_rectangle (priv->wl_window, allocation->x,
+      allocation->y, allocation->width, allocation->height);
+
+  g_mutex_unlock (&priv->render_lock);
+
+  return FALSE;
+}
+
+static gboolean
+window_after_after_paint_cb (GtkWidget * widget, gpointer user_data)
+{
+  GstGtkWaylandSink *self = user_data;
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+
+  g_mutex_lock (&priv->render_lock);
+
+  if (priv->is_wl_window_sync) {
+    struct wl_subsurface *window_subsurface;
+
+    priv->is_wl_window_sync = FALSE;
+
+    window_subsurface = gst_wl_window_get_subsurface (priv->wl_window);
+    if (window_subsurface)
+      wl_subsurface_set_desync (window_subsurface);
+  }
+
+  g_mutex_unlock (&priv->render_lock);
+
+  return FALSE;
+}
+
+static GtkWidget *
+gst_gtk_wayland_sink_get_widget (GstGtkWaylandSink * self)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+
+  if (priv->gtk_widget != NULL)
+    return g_object_ref (priv->gtk_widget);
+
+  /* Ensure GTK is initialized, this has no side effect if it was already
+   * initialized. Also, we do that lazily, so the application can be first */
+  if (!gtk_init_check (NULL, NULL)) {
+    GST_INFO_OBJECT (self, "Could not ensure GTK initialization.");
+    return NULL;
+  }
+
+  priv->gtk_widget = gtk_gst_wayland_widget_new ();
+  gtk_gst_base_widget_set_element (GTK_GST_BASE_WIDGET (priv->gtk_widget),
+      GST_ELEMENT (self));
+
+  /* Take the floating ref, other wise the destruction of the container will
+   * make this widget disappear possibly before we are done. */
+  g_object_ref_sink (priv->gtk_widget);
+  g_signal_connect_object (priv->gtk_widget, "destroy",
+      G_CALLBACK (widget_destroy_cb), self, 0);
+
+  return g_object_ref (priv->gtk_widget);
+}
+
+static GtkWidget *
+gst_gtk_wayland_sink_acquire_widget (GstGtkWaylandSink * self)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  gpointer widget = NULL;
+
+  GST_OBJECT_LOCK (self);
+  if (priv->gtk_widget != NULL)
+    widget = g_object_ref (priv->gtk_widget);
+  GST_OBJECT_UNLOCK (self);
+
+  if (!widget)
+    widget =
+        gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_wayland_sink_get_widget,
+        self);
+
+  return widget;
+}
+
+static gboolean
+gst_gtk_wayland_sink_event (GstBaseSink * sink, GstEvent * event)
+{
+  GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (sink);
+  GstTagList *taglist;
+  GstVideoOrientationMethod method;
+  gboolean ret;
+
+  GST_DEBUG_OBJECT (self, "handling %s event", GST_EVENT_TYPE_NAME (event));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_TAG:
+      gst_event_parse_tag (event, &taglist);
+
+      if (gst_video_orientation_from_tag (taglist, &method)) {
+        gst_gtk_wayland_sink_set_rotate_method (self, method, TRUE);
+      }
+
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
+
+  return ret;
+}
+
+static void
+gst_gtk_wayland_sink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+
+  switch (prop_id) {
+    case PROP_WIDGET:
+      g_value_take_object (value, gst_gtk_wayland_sink_acquire_widget (self));
+      break;
+    case PROP_ROTATE_METHOD:
+      g_value_set_enum (value, priv->current_rotate_method);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_gtk_wayland_sink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
+
+  switch (prop_id) {
+    case PROP_ROTATE_METHOD:
+      gst_gtk_wayland_sink_set_rotate_method (self, g_value_get_enum (value),
+          FALSE);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+calculate_adjustment (GtkWidget * widget, GtkAllocation * allocation)
+{
+  GdkWindow *window;
+  gint wx, wy;
+
+  window = gtk_widget_get_window (widget);
+  gdk_window_get_origin (window, &wx, &wy);
+
+  allocation->x = wx;
+  allocation->y = wy;
+}
+
+static gboolean
+scrollable_window_adjustment_changed_cb (GtkAdjustment * adjustment,
+    gpointer user_data)
+{
+  GstGtkWaylandSink *self = user_data;
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GtkAllocation allocation;
+
+  gtk_widget_get_allocation (priv->gtk_widget, &allocation);
+  calculate_adjustment (priv->gtk_widget, &allocation);
+  gst_wl_window_set_render_rectangle (priv->wl_window, allocation.x,
+      allocation.y, allocation.width, allocation.height);
+
+  return FALSE;
+}
+
+static void
+setup_wl_window (GstGtkWaylandSink * self)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GdkWindow *gdk_window;
+  GdkFrameClock *gdk_frame_clock;
+  GtkAllocation allocation;
+  GtkWidget *widget;
+
+  g_mutex_lock (&priv->render_lock);
+
+  gdk_window = gtk_widget_get_window (priv->gtk_widget);
+  g_assert (gdk_window);
+
+  if (!priv->wl_window) {
+    struct wl_surface *wl_surface;
+
+    wl_surface = gdk_wayland_window_get_wl_surface (gdk_window);
+
+    GST_INFO_OBJECT (self, "setting window handle");
+
+    priv->wl_window = gst_wl_window_new_in_surface (priv->display,
+        wl_surface, &priv->render_lock);
+    gst_wl_window_set_rotate_method (priv->wl_window,
+        priv->current_rotate_method);
+  }
+
+  /* In order to position the subsurface correctly within a scrollable widget,
+   * we can not rely on the allocation alone but need to take the window
+   * origin into account
+   */
+  widget = priv->gtk_widget;
+  do {
+    if (GTK_IS_SCROLLABLE (widget)) {
+      GtkAdjustment *hadjustment;
+      GtkAdjustment *vadjustment;
+
+      hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
+      vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
+
+      g_signal_connect (hadjustment, "value-changed",
+          G_CALLBACK (scrollable_window_adjustment_changed_cb), self);
+      g_signal_connect (vadjustment, "value-changed",
+          G_CALLBACK (scrollable_window_adjustment_changed_cb), self);
+    }
+  } while ((widget = gtk_widget_get_parent (widget)));
+
+  gtk_widget_get_allocation (priv->gtk_widget, &allocation);
+  calculate_adjustment (priv->gtk_widget, &allocation);
+  gst_wl_window_set_render_rectangle (priv->wl_window, allocation.x,
+      allocation.y, allocation.width, allocation.height);
+
+  /* Make subsurfaces syncronous during resizes.
+   * Unfortunately GTK/GDK does not provide easier to use signals.
+   */
+  g_signal_connect (priv->gtk_widget, "size-allocate",
+      G_CALLBACK (widget_size_allocate_cb), self);
+  gdk_frame_clock = gdk_window_get_frame_clock (gdk_window);
+  g_signal_connect_after (gdk_frame_clock, "after-paint",
+      G_CALLBACK (window_after_after_paint_cb), self);
+
+  /* Ensure the base widget is initialized */
+  gtk_gst_base_widget_set_buffer (GTK_GST_BASE_WIDGET (priv->gtk_widget), NULL);
+
+  g_mutex_unlock (&priv->render_lock);
+}
+
+static void
+window_initial_map_cb (GtkWidget * widget, gpointer user_data)
+{
+  GstGtkWaylandSink *self = user_data;
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+
+  setup_wl_window (self);
+  g_signal_handlers_disconnect_by_func (priv->gtk_widget,
+      window_initial_map_cb, self);
+}
+
+static void
+gst_gtk_wayland_sink_navigation_send_event (GstNavigation * navigation,
+    GstEvent * event)
+{
+  GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (navigation);
+  GstPad *pad;
+  gdouble x, y;
+
+  event = gst_event_make_writable (event);
+
+  if (gst_navigation_event_get_coordinates (event, &x, &y)) {
+    GtkGstBaseWidget *widget =
+        GTK_GST_BASE_WIDGET (gst_gtk_wayland_sink_get_widget (self));
+    gdouble stream_x, stream_y;
+
+    if (widget == NULL) {
+      GST_ERROR_OBJECT (self, "Could not ensure GTK initialization.");
+      return;
+    }
+
+    gtk_gst_base_widget_display_size_to_stream_size (widget,
+        x, y, &stream_x, &stream_y);
+    gst_navigation_event_set_coordinates (event, stream_x, stream_y);
+  }
+
+  pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (self));
+
+  GST_TRACE_OBJECT (self, "navigation event %" GST_PTR_FORMAT,
+      gst_event_get_structure (event));
+
+  if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
+    if (!gst_pad_send_event (pad, gst_event_ref (event))) {
+      /* If upstream didn't handle the event we'll post a message with it
+       * for the application in case it wants to do something with it */
+      gst_element_post_message (GST_ELEMENT_CAST (self),
+          gst_navigation_message_new_event (GST_OBJECT_CAST (self), event));
+    }
+    gst_event_unref (event);
+    gst_object_unref (pad);
+  }
+}
+
+static void
+gst_gtk_wayland_sink_navigation_interface_init (GstNavigationInterface * iface)
+{
+  iface->send_event_simple = gst_gtk_wayland_sink_navigation_send_event;
+}
+
+
+static gboolean
+gst_gtk_wayland_sink_start_on_main (GstGtkWaylandSink * self)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GtkWidget *toplevel;
+  GdkDisplay *gdk_display;
+  struct wl_display *wl_display;
+
+  if ((toplevel = gst_gtk_wayland_sink_get_widget (self)) == NULL) {
+    GST_ERROR_OBJECT (self, "Could not ensure GTK initialization.");
+    return FALSE;
+  }
+  g_object_unref (toplevel);
+
+  /* After this point, priv->gtk_widget will always be set */
+
+  gdk_display = gtk_widget_get_display (priv->gtk_widget);
+  if (!GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
+    GST_ERROR_OBJECT (self, "GDK is not using its wayland backend.");
+    return FALSE;
+  }
+  wl_display = gdk_wayland_display_get_wl_display (gdk_display);
+  priv->display = gst_wl_display_new_existing (wl_display, FALSE, NULL);
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (priv->gtk_widget));
+  if (!gtk_widget_is_toplevel (toplevel)) {
+    /* User did not add widget its own UI, let's popup a new GtkWindow to
+     * make gst-launch-1.0 work. */
+    priv->gtk_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_default_size (GTK_WINDOW (priv->gtk_window), 640, 480);
+    gtk_window_set_title (GTK_WINDOW (priv->gtk_window),
+        "Gst GTK Wayland Sink");
+    gtk_container_add (GTK_CONTAINER (priv->gtk_window), toplevel);
+    priv->gtk_window_destroy_id = g_signal_connect (priv->gtk_window, "destroy",
+        G_CALLBACK (window_destroy_cb), self);
+
+    g_signal_connect (priv->gtk_widget, "map",
+        G_CALLBACK (window_initial_map_cb), self);
+  } else {
+    if (gtk_widget_get_mapped (priv->gtk_widget)) {
+      setup_wl_window (self);
+    } else {
+      g_signal_connect (priv->gtk_widget, "map",
+          G_CALLBACK (window_initial_map_cb), self);
+    }
+  }
+
+  return TRUE;
+}
+
+static gboolean
+gst_gtk_wayland_sink_stop_on_main (GstGtkWaylandSink * self)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+
+  if (priv->gtk_window) {
+    if (priv->gtk_window_destroy_id)
+      g_signal_handler_disconnect (priv->gtk_window,
+          priv->gtk_window_destroy_id);
+    priv->gtk_window_destroy_id = 0;
+    gtk_widget_destroy (priv->gtk_window);
+    priv->gtk_window = NULL;
+  }
+
+  if (priv->gtk_widget) {
+    GtkWidget *widget;
+    GdkWindow *gdk_window;
+
+    g_signal_handlers_disconnect_by_func (priv->gtk_widget,
+        widget_size_allocate_cb, self);
+
+    widget = priv->gtk_widget;
+    do {
+      if (GTK_IS_SCROLLABLE (widget)) {
+        GtkAdjustment *hadjustment;
+        GtkAdjustment *vadjustment;
+
+        hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
+        vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
+
+        g_signal_handlers_disconnect_by_func (hadjustment,
+            scrollable_window_adjustment_changed_cb, self);
+        g_signal_handlers_disconnect_by_func (vadjustment,
+            scrollable_window_adjustment_changed_cb, self);
+      }
+    } while ((widget = gtk_widget_get_parent (widget)));
+
+    gdk_window = gtk_widget_get_window (priv->gtk_widget);
+    if (gdk_window) {
+      GdkFrameClock *gdk_frame_clock;
+
+      gdk_frame_clock = gdk_window_get_frame_clock (gdk_window);
+      g_signal_handlers_disconnect_by_func (gdk_frame_clock,
+          window_after_after_paint_cb, self);
+    }
+  }
+
+  return TRUE;
+}
+
+static void
+gst_gtk_widget_show_all_and_unref (GtkWidget * widget)
+{
+  gtk_widget_show_all (widget);
+  g_object_unref (widget);
+}
+
+static GstStateChangeReturn
+gst_gtk_wayland_sink_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (element);
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      if (!gst_gtk_invoke_on_main ((GThreadFunc)
+              gst_gtk_wayland_sink_start_on_main, element))
+        return GST_STATE_CHANGE_FAILURE;
+      break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+    {
+      GtkWindow *window = NULL;
+
+      GST_OBJECT_LOCK (self);
+      if (priv->gtk_window)
+        window = g_object_ref (GTK_WINDOW (priv->gtk_window));
+      GST_OBJECT_UNLOCK (self);
+
+      if (window)
+        gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_widget_show_all_and_unref,
+            window);
+
+      break;
+    }
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+  if (ret != GST_STATE_CHANGE_SUCCESS)
+    return ret;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_READY_TO_NULL:
+    case GST_STATE_CHANGE_NULL_TO_NULL:
+      gst_gtk_invoke_on_main ((GThreadFunc)
+          gst_gtk_wayland_sink_stop_on_main, element);
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_buffer_replace (&priv->last_buffer, NULL);
+      if (priv->wl_window) {
+        /* remove buffer from surface, show nothing */
+        gst_wl_window_render (priv->wl_window, NULL, NULL);
+      }
+
+      g_mutex_lock (&priv->render_lock);
+      if (priv->callback) {
+        wl_callback_destroy (priv->callback);
+        priv->callback = NULL;
+      }
+      priv->redraw_pending = FALSE;
+      g_mutex_unlock (&priv->render_lock);
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static GstCaps *
+gst_gtk_wayland_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
+{
+  GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GstCaps *caps;
+
+  caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (self));
+  caps = gst_caps_make_writable (caps);
+
+  g_mutex_lock (&priv->display_lock);
+
+  if (priv->display) {
+    GValue shm_list = G_VALUE_INIT, dmabuf_list = G_VALUE_INIT;
+    GValue value = G_VALUE_INIT;
+    GArray *formats;
+    gint i;
+    guint fmt;
+    GstVideoFormat gfmt;
+
+    g_value_init (&shm_list, GST_TYPE_LIST);
+    g_value_init (&dmabuf_list, GST_TYPE_LIST);
+
+    /* Add corresponding shm formats */
+    formats = gst_wl_display_get_shm_formats (priv->display);
+    for (i = 0; i < formats->len; i++) {
+      fmt = g_array_index (formats, uint32_t, i);
+      gfmt = gst_wl_shm_format_to_video_format (fmt);
+      if (gfmt != GST_VIDEO_FORMAT_UNKNOWN) {
+        g_value_init (&value, G_TYPE_STRING);
+        g_value_set_static_string (&value, gst_video_format_to_string (gfmt));
+        gst_value_list_append_and_take_value (&shm_list, &value);
+      }
+    }
+
+    gst_structure_take_value (gst_caps_get_structure (caps, 0), "format",
+        &shm_list);
+
+    /* Add corresponding dmabuf formats */
+    formats = gst_wl_display_get_dmabuf_formats (priv->display);
+    for (i = 0; i < formats->len; i++) {
+      fmt = g_array_index (formats, uint32_t, i);
+      gfmt = gst_wl_dmabuf_format_to_video_format (fmt);
+      if (gfmt != GST_VIDEO_FORMAT_UNKNOWN) {
+        g_value_init (&value, G_TYPE_STRING);
+        g_value_set_static_string (&value, gst_video_format_to_string (gfmt));
+        gst_value_list_append_and_take_value (&dmabuf_list, &value);
+      }
+    }
+
+    gst_structure_take_value (gst_caps_get_structure (caps, 1), "format",
+        &dmabuf_list);
+
+    GST_DEBUG_OBJECT (self, "display caps: %" GST_PTR_FORMAT, caps);
+  }
+
+  g_mutex_unlock (&priv->display_lock);
+
+  if (filter) {
+    GstCaps *intersection;
+
+    intersection =
+        gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
+    gst_caps_unref (caps);
+    caps = intersection;
+  }
+
+  return caps;
+}
+
+static GstBufferPool *
+gst_gtk_wayland_create_pool (GstGtkWaylandSink * self, GstCaps * caps)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GstBufferPool *pool = NULL;
+  GstStructure *structure;
+  gsize size = priv->video_info.size;
+  GstAllocator *alloc;
+
+  pool = gst_wl_video_buffer_pool_new ();
+
+  structure = gst_buffer_pool_get_config (pool);
+  gst_buffer_pool_config_set_params (structure, caps, size, 2, 0);
+
+  alloc = gst_wl_shm_allocator_get ();
+  gst_buffer_pool_config_set_allocator (structure, alloc, NULL);
+  if (!gst_buffer_pool_set_config (pool, structure)) {
+    g_object_unref (pool);
+    pool = NULL;
+  }
+  g_object_unref (alloc);
+
+  return pool;
+}
+
+static gboolean
+gst_gtk_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
+{
+  GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  gboolean use_dmabuf;
+  GstVideoFormat format;
+
+  GST_DEBUG_OBJECT (self, "set caps %" GST_PTR_FORMAT, caps);
+
+  /* extract info from caps */
+  if (!gst_video_info_from_caps (&priv->video_info, caps))
+    goto invalid_format;
+
+  format = GST_VIDEO_INFO_FORMAT (&priv->video_info);
+  priv->video_info_changed = TRUE;
+
+  /* create a new pool for the new caps */
+  if (priv->pool)
+    gst_object_unref (priv->pool);
+  priv->pool = gst_gtk_wayland_create_pool (self, caps);
+
+  use_dmabuf = gst_caps_features_contains (gst_caps_get_features (caps, 0),
+      GST_CAPS_FEATURE_MEMORY_DMABUF);
+
+  /* validate the format base on the memory type. */
+  if (use_dmabuf) {
+    if (!gst_wl_display_check_format_for_dmabuf (priv->display, format))
+      goto unsupported_format;
+  } else if (!gst_wl_display_check_format_for_shm (priv->display, format)) {
+    goto unsupported_format;
+  }
+
+  GST_OBJECT_LOCK (self);
+
+  if (priv->gtk_widget == NULL) {
+    GST_OBJECT_UNLOCK (self);
+    GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
+        ("Output widget was destroyed"), (NULL));
+    return FALSE;
+  }
+
+  if (!gtk_gst_base_widget_set_format (GTK_GST_BASE_WIDGET (priv->gtk_widget),
+          &priv->video_info)) {
+    GST_OBJECT_UNLOCK (self);
+    return FALSE;
+  }
+  GST_OBJECT_UNLOCK (self);
+
+  priv->use_dmabuf = use_dmabuf;
+
+  return TRUE;
+
+invalid_format:
+  {
+    GST_ERROR_OBJECT (self,
+        "Could not locate image format from caps %" GST_PTR_FORMAT, caps);
+    return FALSE;
+  }
+unsupported_format:
+  {
+    GST_ERROR_OBJECT (self, "Format %s is not available on the display",
+        gst_video_format_to_string (format));
+    return FALSE;
+  }
+}
+
+static gboolean
+gst_gtk_wayland_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
+{
+  GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GstCaps *caps;
+  GstBufferPool *pool = NULL;
+  gboolean need_pool;
+  GstAllocator *alloc;
+
+  gst_query_parse_allocation (query, &caps, &need_pool);
+
+  if (need_pool)
+    pool = gst_gtk_wayland_create_pool (self, caps);
+
+  gst_query_add_allocation_pool (query, pool, priv->video_info.size, 2, 0);
+  if (pool)
+    g_object_unref (pool);
+
+  alloc = gst_wl_shm_allocator_get ();
+  gst_query_add_allocation_param (query, alloc, NULL);
+  gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
+  g_object_unref (alloc);
+
+  return TRUE;
+}
+
+static void
+frame_redraw_callback (void *data, struct wl_callback *callback, uint32_t time)
+{
+  GstGtkWaylandSink *self = data;
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+
+  GST_LOG_OBJECT (self, "frame_redraw_cb");
+
+  g_mutex_lock (&priv->render_lock);
+  priv->redraw_pending = FALSE;
+
+  if (priv->callback) {
+    wl_callback_destroy (callback);
+    priv->callback = NULL;
+  }
+  g_mutex_unlock (&priv->render_lock);
+}
+
+static const struct wl_callback_listener frame_callback_listener = {
+  frame_redraw_callback
+};
+
+/* must be called with the render lock */
+static void
+render_last_buffer (GstGtkWaylandSink * self, gboolean redraw)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GstWlBuffer *wlbuffer;
+  const GstVideoInfo *info = NULL;
+  struct wl_surface *surface;
+  struct wl_callback *callback;
+
+  wlbuffer = gst_buffer_get_wl_buffer (priv->display, priv->last_buffer);
+  surface = gst_wl_window_get_wl_surface (priv->wl_window);
+
+  priv->redraw_pending = TRUE;
+  callback = wl_surface_frame (surface);
+  priv->callback = callback;
+  wl_callback_add_listener (callback, &frame_callback_listener, self);
+
+  if (G_UNLIKELY (priv->video_info_changed && !redraw)) {
+    info = &priv->video_info;
+    priv->video_info_changed = FALSE;
+  }
+  gst_wl_window_render (priv->wl_window, wlbuffer, info);
+}
+
+static GstFlowReturn
+gst_gtk_wayland_sink_show_frame (GstVideoSink * vsink, GstBuffer * buffer)
+{
+  GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (vsink);
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GstBuffer *to_render;
+  GstWlBuffer *wlbuffer;
+  GstVideoMeta *vmeta;
+  GstVideoFormat format;
+  GstVideoInfo old_vinfo;
+  GstMemory *mem;
+  struct wl_buffer *wbuf = NULL;
+
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  g_mutex_lock (&priv->render_lock);
+
+  GST_LOG_OBJECT (self, "render buffer %" GST_PTR_FORMAT "", buffer);
+
+  if (!priv->wl_window) {
+    GST_LOG_OBJECT (self,
+        "buffer %" GST_PTR_FORMAT " dropped (waiting for window)", buffer);
+    goto done;
+  }
+
+  /* drop buffers until we get a frame callback */
+  if (priv->redraw_pending) {
+    GST_LOG_OBJECT (self, "buffer %" GST_PTR_FORMAT " dropped (redraw pending)",
+        buffer);
+    goto done;
+  }
+
+  /* make sure that the application has called set_render_rectangle() */
+  if (G_UNLIKELY (gst_wl_window_get_render_rectangle (priv->wl_window)->w == 0))
+    goto no_window_size;
+
+  wlbuffer = gst_buffer_get_wl_buffer (priv->display, buffer);
+
+  if (G_LIKELY (wlbuffer &&
+          gst_wl_buffer_get_display (wlbuffer) == priv->display)) {
+    GST_LOG_OBJECT (self,
+        "buffer %" GST_PTR_FORMAT " has a wl_buffer from our display, "
+        "writing directly", buffer);
+    to_render = buffer;
+    goto render;
+  }
+
+  /* update video info from video meta */
+  mem = gst_buffer_peek_memory (buffer, 0);
+
+  old_vinfo = priv->video_info;
+  vmeta = gst_buffer_get_video_meta (buffer);
+  if (vmeta) {
+    gint i;
+
+    for (i = 0; i < vmeta->n_planes; i++) {
+      priv->video_info.offset[i] = vmeta->offset[i];
+      priv->video_info.stride[i] = vmeta->stride[i];
+    }
+    priv->video_info.size = gst_buffer_get_size (buffer);
+  }
+
+  GST_LOG_OBJECT (self,
+      "buffer %" GST_PTR_FORMAT " does not have a wl_buffer from our "
+      "display, creating it", buffer);
+
+  format = GST_VIDEO_INFO_FORMAT (&priv->video_info);
+  if (gst_wl_display_check_format_for_dmabuf (priv->display, format)) {
+    guint i, nb_dmabuf = 0;
+
+    for (i = 0; i < gst_buffer_n_memory (buffer); i++)
+      if (gst_is_dmabuf_memory (gst_buffer_peek_memory (buffer, i)))
+        nb_dmabuf++;
+
+    if (nb_dmabuf && (nb_dmabuf == gst_buffer_n_memory (buffer)))
+      wbuf = gst_wl_linux_dmabuf_construct_wl_buffer (buffer, priv->display,
+          &priv->video_info);
+  }
+
+  if (!wbuf && gst_wl_display_check_format_for_shm (priv->display, format)) {
+    if (gst_buffer_n_memory (buffer) == 1 && gst_is_fd_memory (mem))
+      wbuf = gst_wl_shm_memory_construct_wl_buffer (mem, priv->display,
+          &priv->video_info);
+
+    /* If nothing worked, copy into our internal pool */
+    if (!wbuf) {
+      GstVideoFrame src, dst;
+      GstVideoInfo src_info = priv->video_info;
+
+      /* rollback video info changes */
+      priv->video_info = old_vinfo;
+
+      /* we don't know how to create a wl_buffer directly from the provided
+       * memory, so we have to copy the data to shm memory that we know how
+       * to handle... */
+
+      GST_LOG_OBJECT (self,
+          "buffer %" GST_PTR_FORMAT " cannot have a wl_buffer, "
+          "copying to wl_shm memory", buffer);
+
+      /* priv->pool always exists (created in set_caps), but it may not
+       * be active if upstream is not using it */
+      if (!gst_buffer_pool_is_active (priv->pool)) {
+        GstStructure *config;
+        GstCaps *caps;
+
+        config = gst_buffer_pool_get_config (priv->pool);
+        gst_buffer_pool_config_get_params (config, &caps, NULL, NULL, NULL);
+
+        /* revert back to default strides and offsets */
+        gst_video_info_from_caps (&priv->video_info, caps);
+        gst_buffer_pool_config_set_params (config, caps, priv->video_info.size,
+            2, 0);
+
+        /* This is a video pool, it should not fail with basic settings */
+        if (!gst_buffer_pool_set_config (priv->pool, config) ||
+            !gst_buffer_pool_set_active (priv->pool, TRUE))
+          goto activate_failed;
+      }
+
+      ret = gst_buffer_pool_acquire_buffer (priv->pool, &to_render, NULL);
+      if (ret != GST_FLOW_OK)
+        goto no_buffer;
+
+      wlbuffer = gst_buffer_get_wl_buffer (priv->display, to_render);
+
+      /* attach a wl_buffer if there isn't one yet */
+      if (G_UNLIKELY (!wlbuffer)) {
+        mem = gst_buffer_peek_memory (to_render, 0);
+        wbuf = gst_wl_shm_memory_construct_wl_buffer (mem, priv->display,
+            &priv->video_info);
+
+        if (G_UNLIKELY (!wbuf))
+          goto no_wl_buffer_shm;
+
+        wlbuffer = gst_buffer_add_wl_buffer (to_render, wbuf, priv->display);
+      }
+
+      if (!gst_video_frame_map (&dst, &priv->video_info, to_render,
+              GST_MAP_WRITE))
+        goto dst_map_failed;
+
+      if (!gst_video_frame_map (&src, &src_info, buffer, GST_MAP_READ)) {
+        gst_video_frame_unmap (&dst);
+        goto src_map_failed;
+      }
+
+      gst_video_frame_copy (&dst, &src);
+
+      gst_video_frame_unmap (&src);
+      gst_video_frame_unmap (&dst);
+
+      goto render;
+    }
+  }
+
+  if (!wbuf)
+    goto no_wl_buffer;
+
+  wlbuffer = gst_buffer_add_wl_buffer (buffer, wbuf, priv->display);
+  to_render = buffer;
+
+render:
+  /* drop double rendering */
+  if (G_UNLIKELY (wlbuffer ==
+          gst_buffer_get_wl_buffer (priv->display, priv->last_buffer))) {
+    GST_LOG_OBJECT (self, "Buffer already being rendered");
+    goto done;
+  }
+
+  gst_buffer_replace (&priv->last_buffer, to_render);
+  render_last_buffer (self, FALSE);
+
+  if (buffer != to_render)
+    gst_buffer_unref (to_render);
+  goto done;
+
+no_window_size:
+  {
+    GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
+        ("Window has no size set"),
+        ("Make sure you set the size after calling set_window_handle"));
+    ret = GST_FLOW_ERROR;
+    goto done;
+  }
+no_buffer:
+  {
+    GST_WARNING_OBJECT (self, "could not create buffer");
+    goto done;
+  }
+no_wl_buffer_shm:
+  {
+    GST_ERROR_OBJECT (self, "could not create wl_buffer out of wl_shm memory");
+    ret = GST_FLOW_ERROR;
+    goto done;
+  }
+no_wl_buffer:
+  {
+    GST_ERROR_OBJECT (self,
+        "buffer %" GST_PTR_FORMAT " cannot have a wl_buffer", buffer);
+    ret = GST_FLOW_ERROR;
+    goto done;
+  }
+activate_failed:
+  {
+    GST_ERROR_OBJECT (self, "failed to activate bufferpool.");
+    ret = GST_FLOW_ERROR;
+    goto done;
+  }
+src_map_failed:
+  {
+    GST_ELEMENT_ERROR (self, RESOURCE, READ,
+        ("Video memory can not be read from userspace."), (NULL));
+    ret = GST_FLOW_ERROR;
+    goto done;
+  }
+dst_map_failed:
+  {
+    GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
+        ("Video memory can not be written from userspace."), (NULL));
+    ret = GST_FLOW_ERROR;
+    goto done;
+  }
+done:
+  {
+    g_mutex_unlock (&priv->render_lock);
+    return ret;
+  }
+}
+
+static void
+gst_gtk_wayland_sink_set_rotate_method (GstGtkWaylandSink * self,
+    GstVideoOrientationMethod method, gboolean from_tag)
+{
+  GstGtkWaylandSinkPrivate *priv =
+      gst_gtk_wayland_sink_get_instance_private (self);
+  GstVideoOrientationMethod new_method;
+
+  if (method == GST_VIDEO_ORIENTATION_CUSTOM) {
+    GST_WARNING_OBJECT (self, "unsupported custom orientation");
+    return;
+  }
+
+  GST_OBJECT_LOCK (self);
+  if (from_tag)
+    priv->tag_rotate_method = method;
+  else
+    priv->sink_rotate_method = method;
+
+  if (priv->sink_rotate_method == GST_VIDEO_ORIENTATION_AUTO)
+    new_method = priv->tag_rotate_method;
+  else
+    new_method = priv->sink_rotate_method;
+
+  if (new_method != priv->current_rotate_method) {
+    GST_DEBUG_OBJECT (priv, "Changing method from %d to %d",
+        priv->current_rotate_method, new_method);
+
+    if (priv->wl_window) {
+      g_mutex_lock (&priv->render_lock);
+      gst_wl_window_set_rotate_method (priv->wl_window, new_method);
+      g_mutex_unlock (&priv->render_lock);
+    }
+
+    priv->current_rotate_method = new_method;
+  }
+  GST_OBJECT_UNLOCK (self);
+}
diff --git a/subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.h b/subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.h
new file mode 100644 (file)
index 0000000..9927839
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * GStreamer
+ * Copyright (C) 2021 Collabora Ltd.
+ *   @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_GTK_WAYLAND_SINK gst_gtk_wayland_sink_get_type ()
+G_DECLARE_FINAL_TYPE (GstGtkWaylandSink, gst_gtk_wayland_sink, GST, GTK_WAYLAND_SINK, GstVideoSink);
+
+/**
+ * GstGtkWaylandSink:
+ *
+ * Opaque #GstGtkWaylandSink object
+ *
+ * Since: 1.22
+ */
+struct _GstGtkWaylandSink
+{
+  GstVideoSink parent;
+};
+
+GST_ELEMENT_REGISTER_DECLARE (gtkwaylandsink);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/ext/gtk/gstplugin.c b/subprojects/gst-plugins-bad/ext/gtk/gstplugin.c
new file mode 100644 (file)
index 0000000..35a2c51
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/**
+ * plugin-gtkwayland:
+ *
+ * Since: 1.22
+ */
+
+#include "gstgtkwaylandsink.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  gboolean ret = FALSE;
+
+  ret |= GST_ELEMENT_REGISTER (gtkwaylandsink, plugin);
+
+  return ret;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    gtkwayland,
+    "Gtk+ wayland sink",
+    plugin_init, PACKAGE_VERSION, GST_LICENSE, GST_PACKAGE_NAME,
+    GST_PACKAGE_ORIGIN)
diff --git a/subprojects/gst-plugins-bad/ext/gtk/gtkgstbasewidget.c b/subprojects/gst-plugins-bad/ext/gtk/gtkgstbasewidget.c
new file mode 100644 (file)
index 0000000..ed7cfa6
--- /dev/null
@@ -0,0 +1,670 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+#include "gtkgstbasewidget.h"
+
+GST_DEBUG_CATEGORY (gst_debug_gtk_base_widget);
+#define GST_CAT_DEFAULT gst_debug_gtk_base_widget
+
+#define DEFAULT_FORCE_ASPECT_RATIO  TRUE
+#define DEFAULT_DISPLAY_PAR_N       0
+#define DEFAULT_DISPLAY_PAR_D       1
+#define DEFAULT_VIDEO_PAR_N         0
+#define DEFAULT_VIDEO_PAR_D         1
+#define DEFAULT_IGNORE_ALPHA        TRUE
+
+enum
+{
+  PROP_0,
+  PROP_FORCE_ASPECT_RATIO,
+  PROP_PIXEL_ASPECT_RATIO,
+  PROP_IGNORE_ALPHA,
+  PROP_VIDEO_ASPECT_RATIO_OVERRIDE,
+};
+
+static gboolean
+_calculate_par (GtkGstBaseWidget * widget, GstVideoInfo * info)
+{
+  gboolean ok;
+  gint width, height;
+  gint par_n, par_d;
+  gint display_par_n, display_par_d;
+
+  width = GST_VIDEO_INFO_WIDTH (info);
+  height = GST_VIDEO_INFO_HEIGHT (info);
+  if (width == 0 || height == 0)
+    return FALSE;
+
+  /* get video's PAR */
+  if (widget->video_par_n != 0 && widget->video_par_d != 0) {
+    par_n = widget->video_par_n;
+    par_d = widget->video_par_d;
+  } else {
+    par_n = GST_VIDEO_INFO_PAR_N (info);
+    par_d = GST_VIDEO_INFO_PAR_D (info);
+  }
+
+  if (!par_n)
+    par_n = 1;
+
+  /* get display's PAR */
+  if (widget->par_n != 0 && widget->par_d != 0) {
+    display_par_n = widget->par_n;
+    display_par_d = widget->par_d;
+  } else {
+    display_par_n = 1;
+    display_par_d = 1;
+  }
+
+
+  ok = gst_video_calculate_display_ratio (&widget->display_ratio_num,
+      &widget->display_ratio_den, width, height, par_n, par_d, display_par_n,
+      display_par_d);
+
+  if (ok) {
+    GST_LOG ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n,
+        display_par_d);
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+static void
+_apply_par (GtkGstBaseWidget * widget)
+{
+  guint display_ratio_num, display_ratio_den;
+  gint width, height;
+
+  width = GST_VIDEO_INFO_WIDTH (&widget->v_info);
+  height = GST_VIDEO_INFO_HEIGHT (&widget->v_info);
+
+  if (!width || !height)
+    return;
+
+  display_ratio_num = widget->display_ratio_num;
+  display_ratio_den = widget->display_ratio_den;
+
+  if (height % display_ratio_den == 0) {
+    GST_DEBUG ("keeping video height");
+    widget->display_width = (guint)
+        gst_util_uint64_scale_int (height, display_ratio_num,
+        display_ratio_den);
+    widget->display_height = height;
+  } else if (width % display_ratio_num == 0) {
+    GST_DEBUG ("keeping video width");
+    widget->display_width = width;
+    widget->display_height = (guint)
+        gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
+  } else {
+    GST_DEBUG ("approximating while keeping video height");
+    widget->display_width = (guint)
+        gst_util_uint64_scale_int (height, display_ratio_num,
+        display_ratio_den);
+    widget->display_height = height;
+  }
+
+  GST_DEBUG ("scaling to %dx%d", widget->display_width, widget->display_height);
+}
+
+static gboolean
+_queue_draw (GtkGstBaseWidget * widget)
+{
+  GTK_GST_BASE_WIDGET_LOCK (widget);
+  widget->draw_id = 0;
+
+  if (widget->pending_resize) {
+    widget->pending_resize = FALSE;
+
+    widget->v_info = widget->pending_v_info;
+    widget->negotiated = TRUE;
+
+    _apply_par (widget);
+
+    gtk_widget_queue_resize (GTK_WIDGET (widget));
+  } else {
+    gtk_widget_queue_draw (GTK_WIDGET (widget));
+  }
+
+  GTK_GST_BASE_WIDGET_UNLOCK (widget);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+_update_par (GtkGstBaseWidget * widget)
+{
+  GTK_GST_BASE_WIDGET_LOCK (widget);
+  if (widget->pending_resize) {
+    GTK_GST_BASE_WIDGET_UNLOCK (widget);
+    return;
+  }
+
+  if (!_calculate_par (widget, &widget->v_info)) {
+    GTK_GST_BASE_WIDGET_UNLOCK (widget);
+    return;
+  }
+
+  widget->pending_resize = TRUE;
+  if (!widget->draw_id) {
+    widget->draw_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10,
+        (GSourceFunc) _queue_draw, widget, NULL);
+  }
+  GTK_GST_BASE_WIDGET_UNLOCK (widget);
+}
+
+static void
+gtk_gst_base_widget_get_preferred_width (GtkWidget * widget, gint * min,
+    gint * natural)
+{
+  GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
+  gint video_width = gst_widget->display_width;
+
+  if (!gst_widget->negotiated)
+    video_width = 10;
+
+  if (min)
+    *min = 1;
+  if (natural)
+    *natural = video_width;
+}
+
+static void
+gtk_gst_base_widget_get_preferred_height (GtkWidget * widget, gint * min,
+    gint * natural)
+{
+  GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
+  gint video_height = gst_widget->display_height;
+
+  if (!gst_widget->negotiated)
+    video_height = 10;
+
+  if (min)
+    *min = 1;
+  if (natural)
+    *natural = video_height;
+}
+
+static void
+gtk_gst_base_widget_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
+
+  switch (prop_id) {
+    case PROP_FORCE_ASPECT_RATIO:
+      gtk_widget->force_aspect_ratio = g_value_get_boolean (value);
+      break;
+    case PROP_PIXEL_ASPECT_RATIO:
+      gtk_widget->par_n = gst_value_get_fraction_numerator (value);
+      gtk_widget->par_d = gst_value_get_fraction_denominator (value);
+      _update_par (gtk_widget);
+      break;
+    case PROP_VIDEO_ASPECT_RATIO_OVERRIDE:
+      gtk_widget->video_par_n = gst_value_get_fraction_numerator (value);
+      gtk_widget->video_par_d = gst_value_get_fraction_denominator (value);
+      _update_par (gtk_widget);
+      break;
+    case PROP_IGNORE_ALPHA:
+      gtk_widget->ignore_alpha = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gtk_gst_base_widget_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
+
+  switch (prop_id) {
+    case PROP_FORCE_ASPECT_RATIO:
+      g_value_set_boolean (value, gtk_widget->force_aspect_ratio);
+      break;
+    case PROP_PIXEL_ASPECT_RATIO:
+      gst_value_set_fraction (value, gtk_widget->par_n, gtk_widget->par_d);
+      break;
+    case PROP_VIDEO_ASPECT_RATIO_OVERRIDE:
+      gst_value_set_fraction (value, gtk_widget->video_par_n,
+          gtk_widget->video_par_d);
+      break;
+    case PROP_IGNORE_ALPHA:
+      g_value_set_boolean (value, gtk_widget->ignore_alpha);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static const gchar *
+_gdk_key_to_navigation_string (guint keyval)
+{
+  /* TODO: expand */
+  switch (keyval) {
+#define KEY(key) case GDK_KEY_ ## key: return G_STRINGIFY(key)
+      KEY (Up);
+      KEY (Down);
+      KEY (Left);
+      KEY (Right);
+      KEY (Home);
+      KEY (End);
+#undef KEY
+    default:
+      return NULL;
+  }
+}
+
+static gboolean
+gtk_gst_base_widget_key_event (GtkWidget * widget, GdkEventKey * event)
+{
+  GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
+  GstElement *element;
+
+  if ((element = g_weak_ref_get (&base_widget->element))) {
+    if (GST_IS_NAVIGATION (element)) {
+      const gchar *str = _gdk_key_to_navigation_string (event->keyval);
+
+      if (!str)
+        str = event->string;
+
+      gst_navigation_send_event_simple (GST_NAVIGATION (element),
+          (event->type == GDK_KEY_PRESS) ?
+          gst_navigation_event_new_key_press (str, event->state) :
+          gst_navigation_event_new_key_release (str, event->state));
+    }
+    g_object_unref (element);
+  }
+
+  return FALSE;
+}
+
+static void
+_fit_stream_to_allocated_size (GtkGstBaseWidget * base_widget,
+    GtkAllocation * allocation, GstVideoRectangle * result)
+{
+  if (base_widget->force_aspect_ratio) {
+    GstVideoRectangle src, dst;
+
+    src.x = 0;
+    src.y = 0;
+    src.w = base_widget->display_width;
+    src.h = base_widget->display_height;
+
+    dst.x = 0;
+    dst.y = 0;
+    dst.w = allocation->width;
+    dst.h = allocation->height;
+
+    gst_video_sink_center_rect (src, dst, result, TRUE);
+  } else {
+    result->x = 0;
+    result->y = 0;
+    result->w = allocation->width;
+    result->h = allocation->height;
+  }
+}
+
+void
+gtk_gst_base_widget_display_size_to_stream_size (GtkGstBaseWidget * base_widget,
+    gdouble x, gdouble y, gdouble * stream_x, gdouble * stream_y)
+{
+  gdouble stream_width, stream_height;
+  GtkAllocation allocation;
+  GstVideoRectangle result;
+
+  gtk_widget_get_allocation (GTK_WIDGET (base_widget), &allocation);
+  _fit_stream_to_allocated_size (base_widget, &allocation, &result);
+
+  stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
+  stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
+
+  /* from display coordinates to stream coordinates */
+  if (result.w > 0)
+    *stream_x = (x - result.x) / result.w * stream_width;
+  else
+    *stream_x = 0.;
+
+  /* clip to stream size */
+  if (*stream_x < 0.)
+    *stream_x = 0.;
+  if (*stream_x > GST_VIDEO_INFO_WIDTH (&base_widget->v_info))
+    *stream_x = GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
+
+  /* same for y-axis */
+  if (result.h > 0)
+    *stream_y = (y - result.y) / result.h * stream_height;
+  else
+    *stream_y = 0.;
+
+  if (*stream_y < 0.)
+    *stream_y = 0.;
+  if (*stream_y > GST_VIDEO_INFO_HEIGHT (&base_widget->v_info))
+    *stream_y = GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
+
+  GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y);
+}
+
+static gboolean
+gtk_gst_base_widget_button_event (GtkWidget * widget, GdkEventButton * event)
+{
+  GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
+  GstElement *element;
+
+  if ((element = g_weak_ref_get (&base_widget->element))) {
+    if (GST_IS_NAVIGATION (element)) {
+      gst_navigation_send_event_simple (GST_NAVIGATION (element),
+          (event->type == GDK_BUTTON_PRESS) ?
+          gst_navigation_event_new_mouse_button_press (event->button,
+              event->x, event->y, event->state) :
+          gst_navigation_event_new_mouse_button_release (event->button,
+              event->x, event->y, event->state));
+    }
+    g_object_unref (element);
+  }
+
+  return FALSE;
+}
+
+static gboolean
+gtk_gst_base_widget_motion_event (GtkWidget * widget, GdkEventMotion * event)
+{
+  GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
+  GstElement *element;
+
+  if ((element = g_weak_ref_get (&base_widget->element))) {
+    if (GST_IS_NAVIGATION (element)) {
+      gst_navigation_send_event_simple (GST_NAVIGATION (element),
+          gst_navigation_event_new_mouse_move (event->x, event->y,
+              event->state));
+    }
+    g_object_unref (element);
+  }
+
+  return FALSE;
+}
+
+static gboolean
+gtk_gst_base_widget_scroll_event (GtkWidget * widget, GdkEventScroll * event)
+{
+  GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
+  GstElement *element;
+
+  if ((element = g_weak_ref_get (&base_widget->element))) {
+    if (GST_IS_NAVIGATION (element)) {
+      gdouble x, y, delta_x, delta_y;
+
+      gtk_gst_base_widget_display_size_to_stream_size (base_widget, event->x,
+          event->y, &x, &y);
+
+      if (!gdk_event_get_scroll_deltas ((GdkEvent *) event, &delta_x, &delta_y)) {
+        gdouble offset = 20;
+
+        delta_x = event->delta_x;
+        delta_y = event->delta_y;
+
+        switch (event->direction) {
+          case GDK_SCROLL_UP:
+            delta_y = offset;
+            break;
+          case GDK_SCROLL_DOWN:
+            delta_y = -offset;
+            break;
+          case GDK_SCROLL_LEFT:
+            delta_x = -offset;
+            break;
+          case GDK_SCROLL_RIGHT:
+            delta_x = offset;
+            break;
+          default:
+            break;
+        }
+      }
+      gst_navigation_send_event_simple (GST_NAVIGATION (element),
+          gst_navigation_event_new_mouse_scroll (x, y, delta_x, delta_y,
+              event->state));
+    }
+    g_object_unref (element);
+  }
+  return FALSE;
+}
+
+static gboolean
+gtk_gst_base_widget_touch_event (GtkWidget * widget, GdkEventTouch * event)
+{
+  GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
+  GstElement *element;
+
+  if ((element = g_weak_ref_get (&base_widget->element))) {
+    if (GST_IS_NAVIGATION (element)) {
+      GstEvent *nav_event;
+      gdouble x, y, p;
+      guint id, i;
+
+      id = GPOINTER_TO_UINT (event->sequence);
+      gtk_gst_base_widget_display_size_to_stream_size (base_widget, event->x,
+          event->y, &x, &y);
+
+      p = NAN;
+      for (i = 0; i < gdk_device_get_n_axes (event->device); i++) {
+        if (gdk_device_get_axis_use (event->device, i) == GDK_AXIS_PRESSURE) {
+          p = event->axes[i];
+          break;
+        }
+      }
+
+      switch (event->type) {
+        case GDK_TOUCH_BEGIN:
+          nav_event =
+              gst_navigation_event_new_touch_down (id, x, y, p, event->state);
+          break;
+        case GDK_TOUCH_UPDATE:
+          nav_event =
+              gst_navigation_event_new_touch_motion (id, x, y, p, event->state);
+          break;
+        case GDK_TOUCH_END:
+        case GDK_TOUCH_CANCEL:
+          nav_event =
+              gst_navigation_event_new_touch_up (id, x, y, event->state);
+          break;
+        default:
+          nav_event = NULL;
+          break;
+      }
+
+      if (nav_event)
+        gst_navigation_send_event_simple (GST_NAVIGATION (element), nav_event);
+    }
+    g_object_unref (element);
+  }
+
+  return FALSE;
+}
+
+
+void
+gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass)
+{
+  GObjectClass *gobject_klass = (GObjectClass *) klass;
+  GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
+
+  gobject_klass->set_property = gtk_gst_base_widget_set_property;
+  gobject_klass->get_property = gtk_gst_base_widget_get_property;
+
+  g_object_class_install_property (gobject_klass, PROP_FORCE_ASPECT_RATIO,
+      g_param_spec_boolean ("force-aspect-ratio",
+          "Force aspect ratio",
+          "When enabled, scaling will respect original aspect ratio",
+          DEFAULT_FORCE_ASPECT_RATIO,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+          GST_PARAM_MUTABLE_PLAYING));
+
+  g_object_class_install_property (gobject_klass, PROP_PIXEL_ASPECT_RATIO,
+      gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
+          "The pixel aspect ratio of the device",
+          0, 1, G_MAXINT, G_MAXINT, DEFAULT_DISPLAY_PAR_N,
+          DEFAULT_DISPLAY_PAR_D, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+          GST_PARAM_MUTABLE_PLAYING));
+
+  g_object_class_install_property (gobject_klass,
+      PROP_VIDEO_ASPECT_RATIO_OVERRIDE,
+      gst_param_spec_fraction ("video-aspect-ratio-override",
+          "Video Pixel Aspect Ratio",
+          "The pixel aspect ratio of the video (0/1 = follow stream)", 0,
+          G_MAXINT, G_MAXINT, 1, DEFAULT_VIDEO_PAR_N, DEFAULT_VIDEO_PAR_D,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+          GST_PARAM_MUTABLE_PLAYING));
+
+  g_object_class_install_property (gobject_klass, PROP_IGNORE_ALPHA,
+      g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
+          "When enabled, alpha will be ignored and converted to black",
+          DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  widget_klass->get_preferred_width = gtk_gst_base_widget_get_preferred_width;
+  widget_klass->get_preferred_height = gtk_gst_base_widget_get_preferred_height;
+  widget_klass->key_press_event = gtk_gst_base_widget_key_event;
+  widget_klass->key_release_event = gtk_gst_base_widget_key_event;
+  widget_klass->button_press_event = gtk_gst_base_widget_button_event;
+  widget_klass->button_release_event = gtk_gst_base_widget_button_event;
+  widget_klass->motion_notify_event = gtk_gst_base_widget_motion_event;
+  widget_klass->scroll_event = gtk_gst_base_widget_scroll_event;
+  widget_klass->touch_event = gtk_gst_base_widget_touch_event;
+
+  GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_widget, "gtkbasewidget", 0,
+      "Gtk Video Base Widget");
+}
+
+void
+gtk_gst_base_widget_init (GtkGstBaseWidget * widget)
+{
+  int event_mask;
+
+  widget->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
+  widget->par_n = DEFAULT_DISPLAY_PAR_N;
+  widget->par_d = DEFAULT_DISPLAY_PAR_D;
+  widget->video_par_n = DEFAULT_VIDEO_PAR_N;
+  widget->video_par_d = DEFAULT_VIDEO_PAR_D;
+  widget->ignore_alpha = DEFAULT_IGNORE_ALPHA;
+
+  gst_video_info_init (&widget->v_info);
+  gst_video_info_init (&widget->pending_v_info);
+
+  g_weak_ref_init (&widget->element, NULL);
+  g_mutex_init (&widget->lock);
+
+  gtk_widget_set_can_focus (GTK_WIDGET (widget), TRUE);
+  event_mask = gtk_widget_get_events (GTK_WIDGET (widget));
+  event_mask |= GDK_KEY_PRESS_MASK
+      | GDK_KEY_RELEASE_MASK
+      | GDK_BUTTON_PRESS_MASK
+      | GDK_BUTTON_RELEASE_MASK
+      | GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK | GDK_SCROLL_MASK
+      | GDK_TOUCH_MASK;
+  gtk_widget_set_events (GTK_WIDGET (widget), event_mask);
+}
+
+void
+gtk_gst_base_widget_finalize (GObject * object)
+{
+  GtkGstBaseWidget *widget = GTK_GST_BASE_WIDGET (object);
+
+  gst_buffer_replace (&widget->pending_buffer, NULL);
+  gst_buffer_replace (&widget->buffer, NULL);
+  g_mutex_clear (&widget->lock);
+  g_weak_ref_clear (&widget->element);
+
+  if (widget->draw_id)
+    g_source_remove (widget->draw_id);
+}
+
+void
+gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget,
+    GstElement * element)
+{
+  g_weak_ref_set (&widget->element, element);
+}
+
+gboolean
+gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget,
+    GstVideoInfo * v_info)
+{
+  GTK_GST_BASE_WIDGET_LOCK (widget);
+
+  if (gst_video_info_is_equal (&widget->pending_v_info, v_info)) {
+    GTK_GST_BASE_WIDGET_UNLOCK (widget);
+    return TRUE;
+  }
+
+  if (!_calculate_par (widget, v_info)) {
+    GTK_GST_BASE_WIDGET_UNLOCK (widget);
+    return FALSE;
+  }
+
+  widget->pending_resize = TRUE;
+  widget->pending_v_info = *v_info;
+
+  GTK_GST_BASE_WIDGET_UNLOCK (widget);
+
+  return TRUE;
+}
+
+void
+gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer)
+{
+  /* As we have no type, this is better then no check */
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  GTK_GST_BASE_WIDGET_LOCK (widget);
+
+  gst_buffer_replace (&widget->pending_buffer, buffer);
+
+  if (!widget->draw_id) {
+    widget->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
+        (GSourceFunc) _queue_draw, widget, NULL);
+  }
+
+  GTK_GST_BASE_WIDGET_UNLOCK (widget);
+}
+
+void
+gtk_gst_base_widget_queue_draw (GtkGstBaseWidget * widget)
+{
+  /* As we have no type, this is better then no check */
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  GTK_GST_BASE_WIDGET_LOCK (widget);
+
+  if (!widget->draw_id) {
+    widget->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
+        (GSourceFunc) _queue_draw, widget, NULL);
+  }
+
+  GTK_GST_BASE_WIDGET_UNLOCK (widget);
+}
diff --git a/subprojects/gst-plugins-bad/ext/gtk/gtkgstbasewidget.h b/subprojects/gst-plugins-bad/ext/gtk/gtkgstbasewidget.h
new file mode 100644 (file)
index 0000000..cc957bf
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GTK_GST_BASE_WIDGET_H__
+#define __GTK_GST_BASE_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+#define GTK_GST_BASE_WIDGET(w)         ((GtkGstBaseWidget *)(w))
+#define GTK_GST_BASE_WIDGET_CLASS(k)   ((GtkGstBaseWidgetClass *)(k))
+#define GTK_GST_BASE_WIDGET_LOCK(w)    g_mutex_lock(&((GtkGstBaseWidget*)(w))->lock)
+#define GTK_GST_BASE_WIDGET_UNLOCK(w)  g_mutex_unlock(&((GtkGstBaseWidget*)(w))->lock)
+
+G_BEGIN_DECLS
+
+typedef struct _GtkGstBaseWidget GtkGstBaseWidget;
+typedef struct _GtkGstBaseWidgetClass GtkGstBaseWidgetClass;
+
+struct _GtkGstBaseWidget
+{
+  union {
+    GtkDrawingArea drawing_area;
+#if GTK_CHECK_VERSION(3, 15, 0)
+    GtkGLArea gl_area;
+#endif
+  } parent;
+
+  /* properties */
+  gboolean force_aspect_ratio;
+  gint par_n, par_d;
+  gint video_par_n, video_par_d;
+  gboolean ignore_alpha;
+
+  gint display_width;
+  gint display_height;
+
+  gboolean negotiated;
+  GstBuffer *pending_buffer;
+  GstBuffer *buffer;
+  GstVideoInfo v_info;
+
+  /* resize */
+  gboolean pending_resize;
+  GstVideoInfo pending_v_info;
+  guint display_ratio_num;
+  guint display_ratio_den;
+
+  /*< private >*/
+  GMutex lock;
+  GWeakRef element;
+
+  /* Pending draw idles callback */
+  guint draw_id;
+};
+
+struct _GtkGstBaseWidgetClass
+{
+  union {
+    GtkDrawingAreaClass drawing_area_class;
+#if GTK_CHECK_VERSION(3, 15, 0)
+    GtkGLAreaClass gl_area_class;
+#endif
+  } parent_class;
+};
+
+/* For implementer */
+void            gtk_gst_base_widget_class_init           (GtkGstBaseWidgetClass * klass);
+void            gtk_gst_base_widget_init                 (GtkGstBaseWidget * widget);
+
+void            gtk_gst_base_widget_finalize             (GObject * object);
+
+/* API */
+gboolean        gtk_gst_base_widget_set_format           (GtkGstBaseWidget * widget, GstVideoInfo * v_info);
+void            gtk_gst_base_widget_set_buffer           (GtkGstBaseWidget * widget, GstBuffer * buffer);
+void            gtk_gst_base_widget_queue_draw           (GtkGstBaseWidget * widget);
+void            gtk_gst_base_widget_set_element          (GtkGstBaseWidget * widget, GstElement * element);
+void            gtk_gst_base_widget_display_size_to_stream_size (GtkGstBaseWidget * base_widget,
+                                                                 gdouble x, gdouble y,
+                                                                 gdouble * stream_x, gdouble * stream_y);
+
+G_END_DECLS
+
+#endif /* __GTK_GST_BASE_WIDGET_H__ */
diff --git a/subprojects/gst-plugins-bad/ext/gtk/gtkgstwaylandwidget.c b/subprojects/gst-plugins-bad/ext/gtk/gtkgstwaylandwidget.c
new file mode 100644 (file)
index 0000000..09dba2d
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gtkgstwaylandwidget.h"
+
+/**
+ * SECTION:gtkgstwidget
+ * @title: GtkGstWaylandWidget
+ * @short_description: a #GtkWidget that renders GStreamer video #GstBuffers
+ * @see_also: #GtkDrawingArea, #GstBuffer
+ *
+ * #GtkGstWaylandWidget is an #GtkWidget that renders GStreamer video buffers.
+ */
+
+G_DEFINE_TYPE (GtkGstWaylandWidget, gtk_gst_wayland_widget,
+    GTK_TYPE_DRAWING_AREA);
+
+static void
+gtk_gst_wayland_widget_finalize (GObject * object)
+{
+  gtk_gst_base_widget_finalize (object);
+
+  G_OBJECT_CLASS (gtk_gst_wayland_widget_parent_class)->finalize (object);
+}
+
+static void
+gtk_gst_wayland_widget_class_init (GtkGstWaylandWidgetClass * klass)
+{
+  GObjectClass *gobject_klass = (GObjectClass *) klass;
+
+  gtk_gst_base_widget_class_init (GTK_GST_BASE_WIDGET_CLASS (klass));
+  gobject_klass->finalize = gtk_gst_wayland_widget_finalize;
+}
+
+static void
+gtk_gst_wayland_widget_init (GtkGstWaylandWidget * widget)
+{
+  gtk_gst_base_widget_init (GTK_GST_BASE_WIDGET (widget));
+}
+
+GtkWidget *
+gtk_gst_wayland_widget_new (void)
+{
+  return (GtkWidget *) g_object_new (GTK_TYPE_GST_WAYLAND_WIDGET, NULL);
+}
diff --git a/subprojects/gst-plugins-bad/ext/gtk/gtkgstwaylandwidget.h b/subprojects/gst-plugins-bad/ext/gtk/gtkgstwaylandwidget.h
new file mode 100644 (file)
index 0000000..7f63d78
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * GStreamer
+ * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gst/gst.h>
+
+#include "gtkgstbasewidget.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_GST_WAYLAND_WIDGET gtk_gst_wayland_widget_get_type ()
+G_DECLARE_FINAL_TYPE (GtkGstWaylandWidget, gtk_gst_wayland_widget, GTK, GST_WAYLAND_WIDGET, GtkDrawingArea);
+
+/**
+ * GtkGstWaylandWidget:
+ *
+ * Opaque #GtkGstWaylandWidget object
+ */
+struct _GtkGstWaylandWidget
+{
+  /* <private> */
+  GtkGstBaseWidget parent;
+};
+
+GtkWidget * gtk_gst_wayland_widget_new (void);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/ext/gtk/meson.build b/subprojects/gst-plugins-bad/ext/gtk/meson.build
new file mode 100644 (file)
index 0000000..b32e8b3
--- /dev/null
@@ -0,0 +1,23 @@
+gtkwayland_sources = [
+  'gstplugin.c',
+  'gstgtkutils.c',
+  'gstgtkwaylandsink.c',
+  'gtkgstbasewidget.c',
+  'gtkgstwaylandwidget.c',
+]
+
+gtk_dep = dependency('gtk+-3.0', required : get_option('gtk3'))
+gtk_wayland_dep = dependency('gtk+-wayland-3.0', required : get_option('gtk3'))
+
+if gtk_dep.found() and gtk_wayland_dep.found() and use_wayland
+  gstgtkwayland = library('gstgtkwayland',
+    gtkwayland_sources,
+    c_args :  gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'],
+    include_directories : [configinc],
+    dependencies : [gtk_dep, gstvideo_dep, gstwayland_dep],
+    install : true,
+    install_dir : plugins_install_dir,
+  )
+  pkgconfig.generate(gstgtkwayland, install_dir : plugins_pkgconfig_install_dir)
+  plugins += [gstgtkwayland]
+endif
index 1e40ace..17195f8 100644 (file)
@@ -21,6 +21,7 @@ subdir('fluidsynth')
 subdir('gme')
 subdir('gs')
 subdir('gsm')
+subdir('gtk')
 subdir('hls')
 subdir('iqa')
 subdir('isac')
index 9d29349..2ab3097 100644 (file)
@@ -114,6 +114,7 @@ option('gl', type : 'feature', value : 'auto', description : 'GStreamer OpenGL i
 option('gme', type : 'feature', value : 'auto', description : 'libgme gaming console music file decoder plugin')
 option('gs', type : 'feature', value : 'auto', description : 'Google Cloud Storage source and sink plugin')
 option('gsm', type : 'feature', value : 'auto', description : 'GSM encoder/decoder plugin')
+option('gtk3', type : 'feature', value : 'auto', description : 'GTK+ video sink plugin')
 option('ipcpipeline', type : 'feature', value : 'auto', description : 'Inter-process communication plugin')
 option('iqa', type : 'feature', value : 'auto', description : 'Image quality assessment plugin (AGPL - only built if gpl option is also enabled!)')
 option('kate', type : 'feature', value : 'auto', description : 'Kate subtitle parser, tagger, and codec plugin')
diff --git a/subprojects/gst-plugins-bad/tests/examples/gtk/gtkwaylandsink.c b/subprojects/gst-plugins-bad/tests/examples/gtk/gtkwaylandsink.c
new file mode 100644 (file)
index 0000000..8cada2a
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2014-2021 Collabora Ltd.
+ *   @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <gst/gst.h>
+#include <gtk/gtk.h>
+
+static gboolean live = FALSE;
+static gboolean scrollable = FALSE;
+
+static GOptionEntry entries[] = {
+  {"live", 'l', 0, G_OPTION_ARG_NONE, &live, "Use a live source", NULL},
+  {"scrollable", 's', 0, G_OPTION_ARG_NONE, &scrollable,
+      "Put the GtkWaylandSink into a GtkScrolledWindow ", NULL},
+  {NULL}
+};
+
+typedef struct
+{
+  GtkWidget *app_widget;
+
+  GstElement *pipeline;
+
+  gchar **argv;
+  gint current_uri;             /* index for argv */
+
+  gboolean is_fullscreen;
+} DemoApp;
+
+static void
+on_about_to_finish (GstElement * playbin, DemoApp * d)
+{
+  if (d->argv[++d->current_uri] == NULL)
+    d->current_uri = 1;
+
+  g_print ("Now playing %s\n", d->argv[d->current_uri]);
+  g_object_set (playbin, "uri", d->argv[d->current_uri], NULL);
+}
+
+static void
+error_cb (GstBus * bus, GstMessage * msg, gpointer user_data)
+{
+  DemoApp *d = user_data;
+  gchar *debug = NULL;
+  GError *err = NULL;
+
+  gst_message_parse_error (msg, &err, &debug);
+
+  g_print ("Error: %s\n", err->message);
+  g_error_free (err);
+
+  if (debug) {
+    g_print ("Debug details: %s\n", debug);
+    g_free (debug);
+  }
+
+  gst_element_set_state (d->pipeline, GST_STATE_NULL);
+}
+
+static gboolean
+video_widget_button_pressed_cb (GtkWidget * widget,
+    GdkEventButton * eventButton, DemoApp * d)
+{
+  if (eventButton->type == GDK_2BUTTON_PRESS) {
+    if (d->is_fullscreen) {
+      gtk_window_unfullscreen (GTK_WINDOW (d->app_widget));
+      d->is_fullscreen = FALSE;
+    } else {
+      gtk_window_fullscreen (GTK_WINDOW (d->app_widget));
+      d->is_fullscreen = TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
+static void
+playing_clicked_cb (GtkButton * button, DemoApp * d)
+{
+  gst_element_set_state (d->pipeline, GST_STATE_PLAYING);
+}
+
+static void
+paused_clicked_cb (GtkButton * button, DemoApp * d)
+{
+  gst_element_set_state (d->pipeline, GST_STATE_PAUSED);
+}
+
+static void
+ready_clicked_cb (GtkButton * button, DemoApp * d)
+{
+  gst_element_set_state (d->pipeline, GST_STATE_READY);
+}
+
+static void
+null_clicked_cb (GtkButton * button, DemoApp * d)
+{
+  gst_element_set_state (d->pipeline, GST_STATE_NULL);
+}
+
+static void
+build_window (DemoApp * d)
+{
+  GtkBuilder *builder;
+  GstElement *sink;
+  GtkWidget *box;
+  GtkWidget *widget;
+  GError *error = NULL;
+
+  builder = gtk_builder_new ();
+  if (!gtk_builder_add_from_file (builder, "window.ui", &error)) {
+    g_error ("Failed to load window.ui: %s", error->message);
+    g_error_free (error);
+    goto exit;
+  }
+
+  d->app_widget = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
+  g_object_ref (d->app_widget);
+  g_signal_connect (d->app_widget, "destroy", G_CALLBACK (gtk_main_quit), NULL);
+
+  box = GTK_WIDGET (gtk_builder_get_object (builder, "box"));
+  sink = gst_bin_get_by_name (GST_BIN (d->pipeline), "vsink");
+  if (!sink && !g_strcmp0 (G_OBJECT_TYPE_NAME (d->pipeline), "GstPlayBin")) {
+    g_object_get (d->pipeline, "video-sink", &sink, NULL);
+    if (sink && g_strcmp0 (G_OBJECT_TYPE_NAME (sink), "GstGtkWaylandSink") != 0
+        && GST_IS_BIN (sink)) {
+      GstBin *sinkbin = GST_BIN (sink);
+      sink = gst_bin_get_by_name (sinkbin, "vsink");
+      gst_object_unref (sinkbin);
+    }
+  }
+  g_assert (sink);
+
+  g_object_get (sink, "widget", &widget, NULL);
+  if (scrollable) {
+    GtkWidget *scrollable;
+    scrollable = gtk_scrolled_window_new (NULL, NULL);
+
+    gtk_container_add (GTK_CONTAINER (scrollable), widget);
+    g_object_unref (widget);
+    widget = scrollable;
+  }
+
+  gtk_box_pack_start (GTK_BOX (box), widget, TRUE, TRUE, 0);
+  gtk_box_reorder_child (GTK_BOX (box), widget, 0);
+
+  g_signal_connect (widget, "button-press-event",
+      G_CALLBACK (video_widget_button_pressed_cb), d);
+  if (!scrollable)
+    g_object_unref (widget);
+  g_object_unref (sink);
+
+  widget = GTK_WIDGET (gtk_builder_get_object (builder, "button_playing"));
+  g_signal_connect (widget, "clicked", G_CALLBACK (playing_clicked_cb), d);
+
+  widget = GTK_WIDGET (gtk_builder_get_object (builder, "button_paused"));
+  g_signal_connect (widget, "clicked", G_CALLBACK (paused_clicked_cb), d);
+
+  widget = GTK_WIDGET (gtk_builder_get_object (builder, "button_ready"));
+  g_signal_connect (widget, "clicked", G_CALLBACK (ready_clicked_cb), d);
+
+  widget = GTK_WIDGET (gtk_builder_get_object (builder, "button_null"));
+  g_signal_connect (widget, "clicked", G_CALLBACK (null_clicked_cb), d);
+
+  gtk_widget_show_all (d->app_widget);
+
+exit:
+  g_object_unref (builder);
+}
+
+int
+main (int argc, char **argv)
+{
+  DemoApp *d;
+  GOptionContext *context;
+  GstBus *bus;
+  GError *error = NULL;
+
+  gtk_init (&argc, &argv);
+  gst_init (&argc, &argv);
+
+  context = g_option_context_new ("- gtkwaylandsink demo");
+  g_option_context_add_main_entries (context, entries, NULL);
+  if (!g_option_context_parse (context, &argc, &argv, &error)) {
+    g_printerr ("option parsing failed: %s\n", error->message);
+    return 1;
+  }
+
+  d = g_slice_new0 (DemoApp);
+
+  if (argc > 1) {
+    d->argv = argv;
+    d->current_uri = 1;
+
+    d->pipeline =
+        gst_parse_launch ("playbin video-sink=\"gtkwaylandsink name=vsink\"",
+        NULL);
+    g_object_set (d->pipeline, "uri", argv[d->current_uri], NULL);
+
+    /* enable looping */
+    g_signal_connect (d->pipeline, "about-to-finish",
+        G_CALLBACK (on_about_to_finish), d);
+  } else {
+    if (live) {
+      d->pipeline = gst_parse_launch ("videotestsrc pattern=18 "
+          "background-color=0xFF0062FF is-live=true ! "
+          "gtkwaylandsink name=vsink", NULL);
+    } else {
+      d->pipeline = gst_parse_launch ("videotestsrc pattern=18 "
+          "background-color=0xFF0062FF ! gtkwaylandsink name=vsink", NULL);
+    }
+  }
+
+  build_window (d);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (d->pipeline));
+  gst_bus_add_signal_watch (bus);
+  g_signal_connect (bus, "message::error", G_CALLBACK (error_cb), d);
+  gst_object_unref (bus);
+
+  gst_element_set_state (d->pipeline, GST_STATE_PLAYING);
+
+  gtk_main ();
+
+  gst_element_set_state (d->pipeline, GST_STATE_NULL);
+  gst_object_unref (d->pipeline);
+  g_object_unref (d->app_widget);
+  g_slice_free (DemoApp, d);
+
+  return 0;
+}
diff --git a/subprojects/gst-plugins-bad/tests/examples/gtk/meson.build b/subprojects/gst-plugins-bad/tests/examples/gtk/meson.build
new file mode 100644 (file)
index 0000000..1ed0dee
--- /dev/null
@@ -0,0 +1,10 @@
+if gtk_dep.found() and gtk_wayland_dep.found() and use_wayland
+  executable('gtkwaylandsink',
+    'gtkwaylandsink.c',
+    extra_files: ['window.ui'],
+    install: false,
+    include_directories : [configinc],
+    dependencies : [gtk_dep, gst_dep],
+    c_args : gst_plugins_bad_args,
+  )
+endif
diff --git a/subprojects/gst-plugins-bad/tests/examples/gtk/window.ui b/subprojects/gst-plugins-bad/tests/examples/gtk/window.ui
new file mode 100644 (file)
index 0000000..76ae6b8
--- /dev/null
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <object class="GtkWindow" id="window">
+    <property name="can-focus">False</property>
+    <property name="title" translatable="yes">GStreamer Wayland GTK Demo</property>
+    <child>
+      <object class="GtkBox" id="box">
+        <property name="height-request">300</property>
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <object class="GtkButtonBox" id="buttonbox">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="layout-style">center</property>
+            <child>
+              <object class="GtkButton" id="button_playing">
+                <property name="label" translatable="yes">PLAYING</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button_paused">
+                <property name="label" translatable="yes">PAUSED</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button_ready">
+                <property name="label" translatable="yes">READY</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button_null">
+                <property name="label" translatable="yes">NULL</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
index 0f3695f..9fb4015 100644 (file)
@@ -4,6 +4,7 @@ subdir('camerabin2')
 subdir('codecparsers')
 subdir('d3d11')
 subdir('directfb')
+subdir('gtk')
 subdir('ipcpipeline')
 subdir('mediafoundation')
 subdir('mpegts')