meta-digi/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad/0016-Add-new-gtkwaylandsink...

1572 lines
48 KiB
Diff

From 33fa3db5ac3c5e3c82df9573dec334ef7bf77ee3 Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Fri, 17 Dec 2021 14:02:51 +0200
Subject: [PATCH 16/16] Add new gtkwaylandsink element
Upstream-Status: Inappropriate [DEY specific]
---
ext/gtk/gstgtkutils.c | 71 +++++
ext/gtk/gstgtkutils.h | 29 ++
ext/gtk/gstgtkwaylandsink.c | 460 +++++++++++++++++++++++++++++
ext/gtk/gstgtkwaylandsink.h | 51 ++++
ext/gtk/gstplugin.c | 43 +++
ext/gtk/gtkgstbasewidget.c | 542 ++++++++++++++++++++++++++++++++++
ext/gtk/gtkgstbasewidget.h | 100 +++++++
ext/gtk/gtkgstwaylandwidget.c | 67 +++++
ext/gtk/gtkgstwaylandwidget.h | 68 +++++
ext/gtk/meson.build | 22 ++
ext/meson.build | 1 +
meson_options.txt | 1 +
12 files changed, 1455 insertions(+)
create mode 100644 ext/gtk/gstgtkutils.c
create mode 100644 ext/gtk/gstgtkutils.h
create mode 100644 ext/gtk/gstgtkwaylandsink.c
create mode 100644 ext/gtk/gstgtkwaylandsink.h
create mode 100644 ext/gtk/gstplugin.c
create mode 100644 ext/gtk/gtkgstbasewidget.c
create mode 100644 ext/gtk/gtkgstbasewidget.h
create mode 100644 ext/gtk/gtkgstwaylandwidget.c
create mode 100644 ext/gtk/gtkgstwaylandwidget.h
create mode 100644 ext/gtk/meson.build
diff --git a/ext/gtk/gstgtkutils.c b/ext/gtk/gstgtkutils.c
new file mode 100644
index 0000000..c730f01
--- /dev/null
+++ b/ext/gtk/gstgtkutils.c
@@ -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/ext/gtk/gstgtkutils.h b/ext/gtk/gstgtkutils.h
new file mode 100644
index 0000000..7584ae2
--- /dev/null
+++ b/ext/gtk/gstgtkutils.h
@@ -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/ext/gtk/gstgtkwaylandsink.c b/ext/gtk/gstgtkwaylandsink.c
new file mode 100644
index 0000000..c38ea51
--- /dev/null
+++ b/ext/gtk/gstgtkwaylandsink.c
@@ -0,0 +1,460 @@
+/*
+ * 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 "gtkgstwaylandwidget.h"
+#include "gstgtkutils.h"
+
+#include <gst/video/video.h>
+#include <gst/wayland/wayland.h>
+
+#include <gdk/gdk.h>
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#else
+#error "Wayland is not supported in GTK+"
+#endif
+
+GST_DEBUG_CATEGORY (gst_debug_gtk_wayland_sink);
+#define GST_CAT_DEFAULT gst_debug_gtk_wayland_sink
+
+#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_navigation_interface_init (GstNavigationInterface * iface);
+
+static GstPadProbeReturn sink_pad_probe_cb (GstPad * pad,
+ GstPadProbeInfo * info, gpointer user_data);
+
+enum
+{
+ PROP_0,
+ PROP_WIDGET,
+};
+
+#define gst_gtk_wayland_sink_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstGtkWaylandSink, gst_gtk_wayland_sink, GST_TYPE_BIN,
+ 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"));
+
+static void
+gst_gtk_wayland_sink_init (GstGtkWaylandSink * self)
+{
+ self->waylandsink = gst_element_factory_make ("waylandsink", NULL);
+ if (self->waylandsink) {
+ GstPadTemplate *tmpl;
+ GstPad *pad;
+
+ gst_bin_add (GST_BIN (self), self->waylandsink);
+
+ tmpl = gst_static_pad_template_get (&sink_template);
+ pad = gst_element_get_static_pad (self->waylandsink, "sink");
+ self->ghostpad = gst_ghost_pad_new_from_template ("sink", pad, tmpl);
+
+ gst_pad_add_probe (self->ghostpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ sink_pad_probe_cb, self, NULL);
+ gst_element_add_pad (GST_ELEMENT (self), self->ghostpad);
+
+ gst_object_unref (pad);
+ gst_object_unref (tmpl);
+ }
+}
+
+static void
+gst_gtk_wayland_sink_finalize (GObject * object)
+{
+ GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
+
+ g_clear_object (&self->widget);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+widget_destroy_cb (GtkWidget * widget, GstGtkWaylandSink * self)
+{
+ GST_OBJECT_LOCK (self);
+ g_clear_object (&self->widget);
+ GST_OBJECT_UNLOCK (self);
+}
+
+static void
+window_destroy_cb (GtkWidget * widget, GstGtkWaylandSink * self)
+{
+ GST_OBJECT_LOCK (self);
+ self->window = NULL;
+ GST_OBJECT_UNLOCK (self);
+
+ GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("Window was closed"), (NULL));
+}
+
+/* We use the "draw" callback to change the size of the sink
+ * because the "configure-event" is only sent to top-level widgets. */
+static gboolean
+widget_draw_cb (GtkWidget * widget, cairo_t * cr, gpointer user_data)
+{
+ GstGtkWaylandSink *self = user_data;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (self->waylandsink),
+ allocation.x, allocation.y, allocation.width, allocation.height);
+
+ return FALSE;
+}
+
+static GtkWidget *
+gst_gtk_wayland_sink_get_widget (GstGtkWaylandSink * self)
+{
+ if (self->widget != NULL)
+ return g_object_ref (self->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;
+ }
+
+ self->widget = gtk_gst_wayland_widget_new ();
+ g_signal_connect_object (self->widget, "draw",
+ G_CALLBACK (widget_draw_cb), self, 0);
+ gtk_gst_base_widget_set_element (GTK_GST_BASE_WIDGET (self->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 (self->widget);
+ g_signal_connect_object (self->widget, "destroy",
+ G_CALLBACK (widget_destroy_cb), self, 0);
+
+ return g_object_ref (self->widget);
+}
+
+static GtkWidget *
+gst_gtk_wayland_sink_acquire_widget (GstGtkWaylandSink * self)
+{
+ gpointer widget = NULL;
+
+ GST_OBJECT_LOCK (self);
+ if (self->widget != NULL)
+ widget = g_object_ref (self->widget);
+ GST_OBJECT_UNLOCK (self);
+
+ if (!widget)
+ widget =
+ gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_wayland_sink_get_widget,
+ self);
+
+ return widget;
+}
+
+static void
+gst_gtk_wayland_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
+
+ switch (prop_id) {
+ case PROP_WIDGET:
+ {
+ g_value_take_object (value, gst_gtk_wayland_sink_acquire_widget (self));
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_gtk_wayland_sink_start_on_main (GstGtkWaylandSink * self)
+{
+ GtkWidget *toplevel;
+ GstContext *context;
+ GdkDisplay *display;
+ struct wl_display *display_handle;
+
+ 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, self->widget will always be set */
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self->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. */
+ self->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (self->window), 640, 480);
+ gtk_window_set_title (GTK_WINDOW (self->window), "Gst GTK Wayland Sink");
+ gtk_container_add (GTK_CONTAINER (self->window), toplevel);
+ self->window_destroy_id = g_signal_connect (self->window, "destroy",
+ G_CALLBACK (window_destroy_cb), self);
+ }
+
+ display = gtk_widget_get_display (self->widget);
+ if (!GDK_IS_WAYLAND_DISPLAY (display)) {
+ GST_ERROR_OBJECT (self, "GDK is not using its wayland backend.");
+ return FALSE;
+ }
+ display_handle = gdk_wayland_display_get_wl_display (display);
+ context = gst_wayland_display_handle_context_new (display_handle);
+ gst_element_set_context (self->waylandsink, context);
+ gst_context_unref (context);
+
+ return TRUE;
+}
+
+static gboolean
+gst_gtk_wayland_sink_stop_on_main (GstGtkWaylandSink * self)
+{
+ if (self->window) {
+ if (self->window_destroy_id)
+ g_signal_handler_disconnect (self->window, self->window_destroy_id);
+ self->window_destroy_id = 0;
+ gtk_widget_destroy (self->window);
+ self->window = NULL;
+ }
+
+ 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);
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!self->waylandsink) {
+ GST_ERROR_OBJECT (self, "Failed to load waylandsink");
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ 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 (self->window)
+ window = g_object_ref (GTK_WINDOW (self->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;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static GstPadProbeReturn
+sink_pad_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (user_data);
+ if (GST_IS_EVENT (info->data) &&
+ GST_EVENT_TYPE (info->data) == GST_EVENT_CAPS) {
+ GstVideoInfo v_info;
+ GstCaps *caps;
+ GtkWidget *widget;
+
+ widget = gst_gtk_wayland_sink_acquire_widget (self);
+ if (widget) {
+ gst_event_parse_caps (GST_EVENT (info->data), &caps);
+ gst_video_info_from_caps (&v_info, caps);
+ gtk_gst_base_widget_set_format (GTK_GST_BASE_WIDGET (widget), &v_info);
+ g_object_unref (widget);
+ }
+ }
+
+ return GST_PAD_PROBE_OK;
+}
+
+static void
+gst_gtk_wayland_sink_set_window_handle_on_main (GstGtkWaylandSink * self)
+{
+ GtkAllocation allocation;
+ GdkWindow *window;
+ struct wl_surface *window_handle;
+
+ gtk_widget_get_allocation (self->widget, &allocation);
+ window = gtk_widget_get_window (self->widget);
+ window_handle = gdk_wayland_window_get_wl_surface (window);
+
+ GST_INFO_OBJECT (self, "setting window handle and size (%d x %d)",
+ allocation.width, allocation.height);
+
+ gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (self->waylandsink),
+ (guintptr) window_handle);
+ gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (self->waylandsink),
+ allocation.x, allocation.y, allocation.width, allocation.height);
+}
+
+static void
+gst_gtk_wayland_sink_handle_message (GstBin * bin, GstMessage * message)
+{
+ GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bin);
+
+ if (gst_is_video_overlay_prepare_window_handle_message (message)) {
+ gst_gtk_invoke_on_main ((GThreadFunc)
+ gst_gtk_wayland_sink_set_window_handle_on_main, self);
+ gst_message_unref (message);
+ return;
+ }
+
+ GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+}
+
+static void
+gst_gtk_wayland_sink_class_init (GstGtkWaylandSinkClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *gstelement_class = (GstElementClass *) klass;
+ GstBinClass *gstbin_class = (GstBinClass *) klass;
+
+ gobject_class->finalize = gst_gtk_wayland_sink_finalize;
+ gobject_class->get_property = gst_gtk_wayland_sink_get_property;
+ gstelement_class->change_state = gst_gtk_wayland_sink_change_state;
+ gstbin_class->handle_message = gst_gtk_wayland_sink_handle_message;
+
+ 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_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);
+}
+
+static void
+gst_gtk_wayland_sink_navigation_send_event (GstNavigation * navigation,
+ GstStructure * structure)
+{
+ GstGtkWaylandSink *sink = GST_GTK_WAYLAND_SINK (navigation);
+ GstEvent *event;
+ GstPad *pad;
+ gdouble x, y;
+
+ if (gst_structure_get_double (structure, "pointer_x", &x) &&
+ gst_structure_get_double (structure, "pointer_y", &y)) {
+ GtkGstBaseWidget *widget =
+ (GtkGstBaseWidget *) gst_gtk_wayland_sink_acquire_widget (sink);
+ gdouble stream_x, stream_y;
+
+ if (widget == NULL) {
+ GST_ERROR_OBJECT (sink, "Could not ensure GTK initialization.");
+ return;
+ }
+
+ gtk_gst_base_widget_display_size_to_stream_size (widget,
+ x, y, &stream_x, &stream_y);
+ gst_structure_set (structure,
+ "pointer_x", G_TYPE_DOUBLE, (gdouble) stream_x,
+ "pointer_y", G_TYPE_DOUBLE, (gdouble) stream_y, NULL);
+
+ g_object_unref (widget);
+ }
+
+ event = gst_event_new_navigation (structure);
+ pad = gst_pad_get_peer (sink->ghostpad);
+
+ GST_TRACE_OBJECT (sink, "navigation event %" GST_PTR_FORMAT, structure);
+
+ 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 (sink),
+ gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event));
+ }
+ gst_event_unref (event);
+ gst_object_unref (pad);
+ }
+}
+
+static void
+gst_gtk_wayland_sink_navigation_interface_init (GstNavigationInterface * iface)
+{
+ iface->send_event = gst_gtk_wayland_sink_navigation_send_event;
+}
diff --git a/ext/gtk/gstgtkwaylandsink.h b/ext/gtk/gstgtkwaylandsink.h
new file mode 100644
index 0000000..33fc96f
--- /dev/null
+++ b/ext/gtk/gstgtkwaylandsink.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#ifndef __GST_GTK_WAYLAND_SINK_H__
+#define __GST_GTK_WAYLAND_SINK_H__
+
+#include <gtk/gtk.h>
+#include <gst/gst.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, GstBin)
+
+/**
+ * GstGtkWaylandSink:
+ *
+ * Opaque #GstGtkWaylandSink object
+ */
+struct _GstGtkWaylandSink
+{
+ /* <private> */
+ GstBin parent;
+ GstElement *waylandsink;
+ GstPad *ghostpad;
+ GtkWidget *widget;
+ GtkWidget *window;
+ gulong window_destroy_id;
+};
+
+G_END_DECLS
+
+#endif /* __GST_GTK_WAYLAND_SINK_H__ */
diff --git a/ext/gtk/gstplugin.c b/ext/gtk/gstplugin.c
new file mode 100644
index 0000000..f154b08
--- /dev/null
+++ b/ext/gtk/gstplugin.c
@@ -0,0 +1,43 @@
+/*
+ * 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 "gstgtkwaylandsink.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ gboolean ret = FALSE;
+
+ ret |= gst_element_register (plugin, "gtkwaylandsink", GST_RANK_NONE,
+ GST_TYPE_GTK_WAYLAND_SINK);
+
+ 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/ext/gtk/gtkgstbasewidget.c b/ext/gtk/gtkgstbasewidget.c
new file mode 100644
index 0000000..053640b
--- /dev/null
+++ b/ext/gtk/gtkgstbasewidget.c
@@ -0,0 +1,542 @@
+/*
+ * 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 "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_PAR_N 0
+#define DEFAULT_PAR_D 1
+#define DEFAULT_IGNORE_ALPHA TRUE
+
+enum
+{
+ PROP_0,
+ PROP_FORCE_ASPECT_RATIO,
+ PROP_PIXEL_ASPECT_RATIO,
+ PROP_IGNORE_ALPHA,
+};
+
+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);
+ 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_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 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);
+
+ 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);
+
+ 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 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);
+ const gchar *key_type =
+ event->type == GDK_KEY_PRESS ? "key-press" : "key-release";
+
+ if (!str)
+ str = event->string;
+
+ gst_navigation_send_key_event (GST_NAVIGATION (element), key_type, str);
+ }
+ 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)) {
+ const gchar *key_type =
+ event->type ==
+ GDK_BUTTON_PRESS ? "mouse-button-press" : "mouse-button-release";
+ gst_navigation_send_mouse_event (GST_NAVIGATION (element), key_type,
+ event->button, event->x, event->y);
+ }
+ 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_mouse_event (GST_NAVIGATION (element), "mouse-move",
+ 0, event->x, event->y);
+ }
+ g_object_unref (element);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtk_gst_base_widget_scroll_event (GtkWidget * widget, GdkEventScroll * event)
+{
+#if GST_CHECK_VERSION(1, 18, 0)
+ 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_mouse_scroll_event (GST_NAVIGATION (element),
+ x, y, delta_x, delta_y);
+ }
+ g_object_unref (element);
+ }
+#endif
+ 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));
+
+ 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", DEFAULT_PAR_N, DEFAULT_PAR_D,
+ G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ 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;
+
+ 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_PAR_N;
+ widget->par_d = DEFAULT_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;
+ 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;
+
+ 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);
+
+ 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);
+}
diff --git a/ext/gtk/gtkgstbasewidget.h b/ext/gtk/gtkgstbasewidget.h
new file mode 100644
index 0000000..c10ed38
--- /dev/null
+++ b/ext/gtk/gtkgstbasewidget.h
@@ -0,0 +1,100 @@
+/*
+ * 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;
+ 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_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/ext/gtk/gtkgstwaylandwidget.c b/ext/gtk/gtkgstwaylandwidget.c
new file mode 100644
index 0000000..df3d1d5
--- /dev/null
+++ b/ext/gtk/gtkgstwaylandwidget.c
@@ -0,0 +1,67 @@
+/*
+ * 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"
+#include <gst/video/video.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/ext/gtk/gtkgstwaylandwidget.h b/ext/gtk/gtkgstwaylandwidget.h
new file mode 100644
index 0000000..48cfe9e
--- /dev/null
+++ b/ext/gtk/gtkgstwaylandwidget.h
@@ -0,0 +1,68 @@
+/*
+ * 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_WAYLAND_WIDGET_H__
+#define __GTK_GST_WAYLAND_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include <gst/gst.h>
+
+#include "gtkgstbasewidget.h"
+
+G_BEGIN_DECLS
+
+GType gtk_gst_wayland_widget_get_type (void);
+#define GTK_TYPE_GST_WAYLAND_WIDGET (gtk_gst_wayland_widget_get_type())
+#define GTK_GST_WAYLAND_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_GST_WAYLAND_WIDGET,GtkGstWaylandWidget))
+#define GTK_GST_WAYLAND_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_GST_WAYLAND_WIDGET,GtkGstWaylandWidgetClass))
+#define GTK_IS_GST_WAYLAND_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_GST_WAYLAND_WIDGET))
+#define GTK_IS_GST_WAYLAND_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_GST_WAYLAND_WIDGET))
+#define GTK_GST_WAYLAND_WIDGET_CAST(obj) ((GtkGstWaylandWidget*)(obj))
+
+typedef struct _GtkGstWaylandWidget GtkGstWaylandWidget;
+typedef struct _GtkGstWaylandWidgetClass GtkGstWaylandWidgetClass;
+
+/**
+ * GtkGstWaylandWidget:
+ *
+ * Opaque #GtkGstWaylandWidget object
+ */
+struct _GtkGstWaylandWidget
+{
+ /* <private> */
+ GtkGstBaseWidget base;
+};
+
+/**
+ * GtkGstWaylandWidgetClass:
+ *
+ * The #GtkGstWaylandWidgetClass struct only contains private data
+ */
+struct _GtkGstWaylandWidgetClass
+{
+ /* <private> */
+ GtkGstBaseWidgetClass base_class;
+};
+
+GtkWidget * gtk_gst_wayland_widget_new (void);
+
+G_END_DECLS
+
+#endif /* __GTK_GST_WAYLAND_WIDGET_H__ */
diff --git a/ext/gtk/meson.build b/ext/gtk/meson.build
new file mode 100644
index 0000000..199188d
--- /dev/null
+++ b/ext/gtk/meson.build
@@ -0,0 +1,22 @@
+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)
+endif
diff --git a/ext/meson.build b/ext/meson.build
index 1e40ace..17195f8 100644
--- a/ext/meson.build
+++ b/ext/meson.build
@@ -21,6 +21,7 @@ subdir('fluidsynth')
subdir('gme')
subdir('gs')
subdir('gsm')
+subdir('gtk')
subdir('hls')
subdir('iqa')
subdir('isac')
diff --git a/meson_options.txt b/meson_options.txt
index b347dcb..c64f292 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -29,6 +29,7 @@ option('frei0r', type : 'feature', value : 'auto')
option('gaudieffects', type : 'feature', value : 'auto')
option('gdp', type : 'feature', value : 'auto')
option('geometrictransform', type : 'feature', value : 'auto')
+option('gtk3', type : 'feature', value : 'auto')
option('id3tag', type : 'feature', value : 'auto')
option('inter', type : 'feature', value : 'auto')
option('interlace', type : 'feature', value : 'auto')
--
2.34.1