From 7e18fc1b1f25501a6077fe681ef3ece1c7a1417c Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Wed, 8 Dec 2021 10:30:21 +0200 Subject: [PATCH] Add new gtkwaylandsink element 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: --- .../docs/plugins/gst_plugins_cache.json | 63 + .../gst-plugins-bad/ext/gtk/gstgtkutils.c | 71 + .../gst-plugins-bad/ext/gtk/gstgtkutils.h | 29 + .../ext/gtk/gstgtkwaylandsink.c | 1247 +++++++++++++++++ .../ext/gtk/gstgtkwaylandsink.h | 47 + .../gst-plugins-bad/ext/gtk/gstplugin.c | 48 + .../ext/gtk/gtkgstbasewidget.c | 670 +++++++++ .../ext/gtk/gtkgstbasewidget.h | 102 ++ .../ext/gtk/gtkgstwaylandwidget.c | 66 + .../ext/gtk/gtkgstwaylandwidget.h | 46 + .../gst-plugins-bad/ext/gtk/meson.build | 23 + subprojects/gst-plugins-bad/ext/meson.build | 1 + subprojects/gst-plugins-bad/meson_options.txt | 1 + .../tests/examples/gtk/gtkwaylandsink.c | 247 ++++ .../tests/examples/gtk/meson.build | 10 + .../tests/examples/gtk/window.ui | 84 ++ .../tests/examples/meson.build | 1 + 17 files changed, 2756 insertions(+) create mode 100644 subprojects/gst-plugins-bad/ext/gtk/gstgtkutils.c create mode 100644 subprojects/gst-plugins-bad/ext/gtk/gstgtkutils.h create mode 100644 subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.c create mode 100644 subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.h create mode 100644 subprojects/gst-plugins-bad/ext/gtk/gstplugin.c create mode 100644 subprojects/gst-plugins-bad/ext/gtk/gtkgstbasewidget.c create mode 100644 subprojects/gst-plugins-bad/ext/gtk/gtkgstbasewidget.h create mode 100644 subprojects/gst-plugins-bad/ext/gtk/gtkgstwaylandwidget.c create mode 100644 subprojects/gst-plugins-bad/ext/gtk/gtkgstwaylandwidget.h create mode 100644 subprojects/gst-plugins-bad/ext/gtk/meson.build create mode 100644 subprojects/gst-plugins-bad/tests/examples/gtk/gtkwaylandsink.c create mode 100644 subprojects/gst-plugins-bad/tests/examples/gtk/meson.build create mode 100644 subprojects/gst-plugins-bad/tests/examples/gtk/window.ui diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index 99b4cb2bb0..1e06813a33 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -28438,6 +28438,69 @@ "tracers": {}, "url": "Unknown package origin" }, + "gtkwayland": { + "description": "Gtk+ wayland sink", + "elements": { + "gtkwaylandsink": { + "author": "George Kiagiadakis ", + "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 index 0000000000..c730f0188f --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/gtk/gstgtkutils.c @@ -0,0 +1,71 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2015 Thibault Saunier + * + * 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 index 0000000000..7584ae2c66 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/gtk/gstgtkutils.h @@ -0,0 +1,29 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2015 Thibault Saunier + * + * 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 + +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 index 0000000000..e4dd06e9f1 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.c @@ -0,0 +1,1247 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2021 Collabora Ltd. + * @author George Kiagiadakis + * + * 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 +#include + +#include "gstgtkutils.h" +#include "gtkgstwaylandwidget.h" + +#ifdef GDK_WINDOWING_WAYLAND +#include +#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 "); + + 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 index 0000000000..9927839c52 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.h @@ -0,0 +1,47 @@ +/* + * GStreamer + * Copyright (C) 2021 Collabora Ltd. + * @author George Kiagiadakis + * + * 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 +#include +#include + +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 index 0000000000..35a2c510fa --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/gtk/gstplugin.c @@ -0,0 +1,48 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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 index 0000000000..ed7cfa6609 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/gtk/gtkgstbasewidget.c @@ -0,0 +1,670 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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 +#include + +#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 index 0000000000..cc957bf13b --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/gtk/gtkgstbasewidget.h @@ -0,0 +1,102 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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 +#include +#include + +#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 index 0000000000..09dba2db12 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/gtk/gtkgstwaylandwidget.c @@ -0,0 +1,66 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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 index 0000000000..7f63d78b35 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/gtk/gtkgstwaylandwidget.h @@ -0,0 +1,46 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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 +#include + +#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 +{ + /* */ + 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 index 0000000000..b32e8b3b93 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/gtk/meson.build @@ -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 diff --git a/subprojects/gst-plugins-bad/ext/meson.build b/subprojects/gst-plugins-bad/ext/meson.build index 1e40ace7f3..17195f83d8 100644 --- a/subprojects/gst-plugins-bad/ext/meson.build +++ b/subprojects/gst-plugins-bad/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/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt index 9d29349e7a..2ab309761a 100644 --- a/subprojects/gst-plugins-bad/meson_options.txt +++ b/subprojects/gst-plugins-bad/meson_options.txt @@ -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 index 0000000000..8cada2aa8c --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/gtk/gtkwaylandsink.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2014-2021 Collabora Ltd. + * @author George Kiagiadakis + * + * 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 +#include + +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 index 0000000000..1ed0dee5f1 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/gtk/meson.build @@ -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 index 0000000000..76ae6b8c16 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/gtk/window.ui @@ -0,0 +1,84 @@ + + + + + + False + GStreamer Wayland GTK Demo + + + 300 + True + False + vertical + + + + + + True + False + center + + + PLAYING + True + True + True + + + False + True + 0 + + + + + PAUSED + True + True + True + + + False + True + 1 + + + + + READY + True + True + True + + + False + True + 2 + + + + + NULL + True + True + True + + + False + True + 3 + + + + + False + True + 1 + + + + + + diff --git a/subprojects/gst-plugins-bad/tests/examples/meson.build b/subprojects/gst-plugins-bad/tests/examples/meson.build index 0f3695f198..9fb4015d52 100644 --- a/subprojects/gst-plugins-bad/tests/examples/meson.build +++ b/subprojects/gst-plugins-bad/tests/examples/meson.build @@ -4,6 +4,7 @@ subdir('camerabin2') subdir('codecparsers') subdir('d3d11') subdir('directfb') +subdir('gtk') subdir('ipcpipeline') subdir('mediafoundation') subdir('mpegts')