From 5f1b290fe87aa72be0c55e7cdd2e2933584b1cd8 Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Wed, 15 Apr 2020 10:38:04 +0100 Subject: [PATCH] qt: Add navigation events support Currently handles only mouse events. Part-of: --- ext/qt/gstqtsink.cc | 45 ++++++++++++- ext/qt/qtitem.cc | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++- ext/qt/qtitem.h | 13 ++++ 3 files changed, 239 insertions(+), 6 deletions(-) diff --git a/ext/qt/gstqtsink.cc b/ext/qt/gstqtsink.cc index b2de37d..8f55b72 100644 --- a/ext/qt/gstqtsink.cc +++ b/ext/qt/gstqtsink.cc @@ -81,6 +81,7 @@ #define GST_CAT_DEFAULT gst_debug_qt_gl_sink GST_DEBUG_CATEGORY (GST_CAT_DEFAULT); +static void gst_qt_sink_navigation_interface_init (GstNavigationInterface * iface); static void gst_qt_sink_finalize (GObject * object); static void gst_qt_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * param_spec); @@ -134,7 +135,9 @@ enum #define gst_qt_sink_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstQtSink, gst_qt_sink, GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, - "qtsink", 0, "Qt Video Sink")); + "qtsink", 0, "Qt Video Sink"); + G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, + gst_qt_sink_navigation_interface_init)); GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (qmlglsink, "qmlglsink", GST_RANK_NONE, GST_TYPE_QT_SINK, qt5_element_init (plugin)); @@ -194,6 +197,8 @@ static void gst_qt_sink_init (GstQtSink * qt_sink) { qt_sink->widget = QSharedPointer(); + if (qt_sink->widget) + qt_sink->widget->setSink (GST_ELEMENT_CAST (qt_sink)); } static void @@ -205,10 +210,14 @@ gst_qt_sink_set_property (GObject * object, guint prop_id, switch (prop_id) { case PROP_WIDGET: { QtGLVideoItem *qt_item = static_cast (g_value_get_pointer (value)); - if (qt_item) + if (qt_item) { qt_sink->widget = qt_item->getInterface(); - else + if (qt_sink->widget) { + qt_sink->widget->setSink (GST_ELEMENT_CAST (qt_sink)); + } + } else { qt_sink->widget.clear(); + } break; } case PROP_FORCE_ASPECT_RATIO: @@ -546,3 +555,33 @@ config_failed: return FALSE; } } + +static void +gst_qt_sink_navigation_send_event (GstNavigation * navigation, + GstStructure * structure) +{ + GstQtSink *qt_sink = GST_QT_SINK (navigation); + GstEvent *event; + GstPad *pad; + + event = gst_event_new_navigation (structure); + pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (qt_sink)); + + GST_TRACE_OBJECT (qt_sink, "navigation event %" GST_PTR_FORMAT, structure); + + if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) { + if (!gst_pad_send_event (pad, gst_event_ref (event))) { + /* If upstream didn't handle the event we'll post a message with it + * for the application in case it wants to do something with it */ + gst_element_post_message (GST_ELEMENT_CAST (qt_sink), + gst_navigation_message_new_event (GST_OBJECT_CAST (qt_sink), event)); + } + gst_event_unref (event); + gst_object_unref (pad); + } +} + +static void gst_qt_sink_navigation_interface_init (GstNavigationInterface * iface) +{ + iface->send_event = gst_qt_sink_navigation_send_event; +} diff --git a/ext/qt/qtitem.cc b/ext/qt/qtitem.cc index 1b0ba5a..e16533f 100644 --- a/ext/qt/qtitem.cc +++ b/ext/qt/qtitem.cc @@ -37,9 +37,8 @@ #include /** - * SECTION:gtkgstglwidget - * @short_description: a #GtkGLArea that renders GStreamer video #GstBuffers - * @see_also: #GtkGLArea, #GstBuffer + * SECTION:QtGLVideoItem + * @short_description: a Qt5 QtQuick item that renders GStreamer video #GstBuffers * * #QtGLVideoItem is an #QQuickItem that renders GStreamer video buffers. */ @@ -66,6 +65,8 @@ struct _QtGLVideoItemPrivate gboolean force_aspect_ratio; gint par_n, par_d; + GWeakRef sink; + gint display_width; gint display_height; @@ -129,6 +130,8 @@ QtGLVideoItem::QtGLVideoItem() g_mutex_init (&this->priv->lock); + g_weak_ref_init (&priv->sink, NULL); + this->priv->display = gst_qt_get_gl_display(TRUE); connect(this, SIGNAL(windowChanged(QQuickWindow*)), this, @@ -136,6 +139,10 @@ QtGLVideoItem::QtGLVideoItem() this->proxy = QSharedPointer(new QtGLVideoItemInterface(this)); + setFlag(ItemHasContents, true); + setAcceptedMouseButtons(Qt::AllButtons); + setAcceptHoverEvents(true); + GST_DEBUG ("%p init Qt Video Item", this); } @@ -171,6 +178,9 @@ QtGLVideoItem::~QtGLVideoItem() gst_buffer_replace (&this->priv->buffer, NULL); gst_caps_replace (&this->priv->caps, NULL); + + g_weak_ref_clear (&this->priv->sink); + g_free (this->priv); this->priv = NULL; } @@ -305,6 +315,165 @@ QtGLVideoItem::updatePaintNode(QSGNode * oldNode, return texNode; } +/* This method has to be invoked with the the priv->lock taken */ +void +QtGLVideoItem::fitStreamToAllocatedSize(GstVideoRectangle * result) +{ + if (this->priv->force_aspect_ratio) { + GstVideoRectangle src, dst; + + src.x = 0; + src.y = 0; + src.w = this->priv->display_width; + src.h = this->priv->display_height; + + dst.x = 0; + dst.y = 0; + dst.w = size().width(); + dst.h = size().height(); + + gst_video_sink_center_rect (src, dst, result, TRUE); + } else { + result->x = 0; + result->y = 0; + result->w = size().width(); + result->h = size().height(); + } +} + +/* This method has to be invoked with the the priv->lock taken */ +QPointF +QtGLVideoItem::mapPointToStreamSize(QPointF pos) +{ + gdouble stream_width, stream_height; + GstVideoRectangle result; + double stream_x, stream_y; + double x, y; + + fitStreamToAllocatedSize(&result); + + stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&this->priv->v_info); + stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&this->priv->v_info); + x = pos.x(); + y = pos.y(); + + /* 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 */ + stream_x = CLAMP(stream_x, 0., stream_width); + + /* same for y-axis */ + if (result.h > 0) + stream_y = (y - result.y) / result.h * stream_height; + else + stream_y = 0.; + + stream_y = CLAMP(stream_y, 0., stream_height); + GST_TRACE ("transform %fx%f into %fx%f", x, y, stream_x, stream_y); + return QPointF(stream_x, stream_y); +} + +void +QtGLVideoItem::wheelEvent(QWheelEvent * event) +{ + g_mutex_lock (&this->priv->lock); + QPoint delta = event->angleDelta(); + GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink)); + + if (element != NULL) { +#if (QT_VERSION >= QT_VERSION_CHECK (5, 14, 0)) + auto position = event->position(); +#else + auto position = *event; +#endif + gst_navigation_send_mouse_scroll_event (GST_NAVIGATION (element), + position.x(), position.y(), delta.x(), delta.y()); + g_object_unref (element); + } + g_mutex_unlock (&this->priv->lock); +} + +void +QtGLVideoItem::hoverEnterEvent(QHoverEvent *) +{ + m_hovering = true; +} + +void +QtGLVideoItem::hoverLeaveEvent(QHoverEvent *) +{ + m_hovering = false; +} + +void +QtGLVideoItem::hoverMoveEvent(QHoverEvent * event) +{ + if (!m_hovering) + return; + + int button = !!m_mousePressedButton; + g_mutex_lock (&this->priv->lock); + if (event->pos() != event->oldPos()) { + QPointF pos = mapPointToStreamSize(event->pos()); + GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink)); + + if (element != NULL) { + gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move", + button, pos.x(), pos.y()); + g_object_unref (element); + } + } + g_mutex_unlock (&this->priv->lock); +} + +void +QtGLVideoItem::sendMouseEvent(QMouseEvent * event, const gchar * type) +{ + int button = 0; + switch (event->button()) { + case Qt::LeftButton: + button = 1; + break; + case Qt::RightButton: + button = 2; + break; + default: + break; + } + m_mousePressedButton = button; + g_mutex_lock (&this->priv->lock); + + QPointF pos = mapPointToStreamSize(event->pos()); + gchar* event_type = g_strconcat ("mouse-button-", type, NULL); + GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink)); + + if (element != NULL) { + gst_navigation_send_mouse_event (GST_NAVIGATION (element), event_type, + button, pos.x(), pos.y()); + g_object_unref (element); + } + + g_free (event_type); + g_mutex_unlock (&this->priv->lock); +} + +void +QtGLVideoItem::mousePressEvent(QMouseEvent * event) +{ + forceActiveFocus(); + sendMouseEvent(event, "press"); +} + +void +QtGLVideoItem::mouseReleaseEvent(QMouseEvent * event) +{ + sendMouseEvent(event, "release"); +} + static void _reset (QtGLVideoItem * qt_item) { @@ -328,6 +497,18 @@ _reset (QtGLVideoItem * qt_item) } void +QtGLVideoItemInterface::setSink (GstElement * sink) +{ + QMutexLocker locker(&lock); + if (qt_item == NULL) + return; + + g_mutex_lock (&qt_item->priv->lock); + g_weak_ref_set (&qt_item->priv->sink, sink); + g_mutex_unlock (&qt_item->priv->lock); +} + +void QtGLVideoItemInterface::setBuffer (GstBuffer * buffer) { QMutexLocker locker(&lock); diff --git a/ext/qt/qtitem.h b/ext/qt/qtitem.h index f2f67f7..b04f41a 100644 --- a/ext/qt/qtitem.h +++ b/ext/qt/qtitem.h @@ -42,6 +42,7 @@ public: void invalidateRef(); + void setSink (GstElement * sink); void setBuffer (GstBuffer * buffer); gboolean setCaps (GstCaps *caps); gboolean initWinSys (); @@ -98,6 +99,12 @@ private Q_SLOTS: protected: QSGNode * updatePaintNode (QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData); + void wheelEvent(QWheelEvent *) override; + void hoverEnterEvent(QHoverEvent *) override; + void hoverLeaveEvent (QHoverEvent *) override; + void hoverMoveEvent (QHoverEvent *) override; + void mousePressEvent(QMouseEvent*) override; + void mouseReleaseEvent(QMouseEvent*) override; private: @@ -105,8 +112,14 @@ private: void setViewportSize(const QSize &size); void shareContext(); + void fitStreamToAllocatedSize(GstVideoRectangle * result); + QPointF mapPointToStreamSize(QPointF); + + void sendMouseEvent(QMouseEvent * event, const gchar * type); QSize m_viewportSize; bool m_openGlContextInitialized; + bool m_hovering; + uint32_t m_mousePressedButton; QSharedPointer proxy; }; -- 2.7.4