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