/* Copyright (C) <2018, 2019, 2020, 2025> Philippe Normand * Copyright (C) <2018> Žan Doberšek * * 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 #endif #include "gstwpe.h" #include "gstwpethreadedview.h" #include "gstwpedisplay.h" #include "gstwpeview.h" #include #include #include #include #include GST_DEBUG_CATEGORY_EXTERN (wpe_view_debug); #define GST_CAT_DEFAULT wpe_view_debug /* *INDENT-OFF* */ class GMutexHolder { public: GMutexHolder (GMutex & mutex) :m(mutex) { g_mutex_lock (&m); } ~GMutexHolder () { g_mutex_unlock (&m); } private: GMutex &m; }; /* *INDENT-ON* */ static GstWPEContextThread *s_view = NULL; GstWPEContextThread & GstWPEContextThread::singleton () { /* *INDENT-OFF* */ static gsize initialized = 0; /* *INDENT-ON* */ if (g_once_init_enter (&initialized)) { s_view = new GstWPEContextThread; g_once_init_leave (&initialized, 1); } return *s_view; } GstWPEContextThread::GstWPEContextThread () { g_mutex_init (&threading.mutex); g_cond_init (&threading.cond); threading.ready = FALSE; { GMutexHolder lock (threading.mutex); threading.thread = g_thread_new ("GstWPEContextThread", s_viewThread, this); while (!threading.ready) { g_cond_wait (&threading.cond, &threading.mutex); } GST_DEBUG ("thread spawned"); } } GstWPEContextThread::~GstWPEContextThread () { if (threading.thread) { g_thread_unref (threading.thread); threading.thread = nullptr; } g_mutex_clear (&threading.mutex); g_cond_clear (&threading.cond); } template < typename Function > void GstWPEContextThread::dispatch (Function func) { /* *INDENT-OFF* */ struct Job { Job (Function & f) :func (f) { g_mutex_init (&mutex); g_cond_init (&cond); dispatched = FALSE; } ~Job () { g_mutex_clear (&mutex); g_cond_clear (&cond); } void dispatch () { GMutexHolder lock (mutex); func (); dispatched = TRUE; g_cond_signal (&cond); } void waitCompletion () { GMutexHolder lock (mutex); while (!dispatched) { g_cond_wait (&cond, &mutex); } } Function & func; GMutex mutex; GCond cond; gboolean dispatched; }; /* *INDENT-ON* */ struct Job job (func); GSource *source = g_idle_source_new (); /* *INDENT-OFF* */ g_source_set_callback (source,[](gpointer data)->gboolean { auto job = static_cast(data); job->dispatch (); return G_SOURCE_REMOVE; }, &job, nullptr); /* *INDENT-ON* */ g_source_set_priority (source, G_PRIORITY_DEFAULT); g_source_attach (source, glib.context); job.waitCompletion (); g_source_unref (source); } gpointer GstWPEContextThread::s_viewThread (gpointer data) { /* *INDENT-OFF* */ auto &view = *static_cast(data); /* *INDENT-ON* */ view.glib.context = g_main_context_new (); view.glib.loop = g_main_loop_new (view.glib.context, FALSE); g_main_context_push_thread_default (view.glib.context); { GSource *source = g_idle_source_new (); /* *INDENT-OFF* */ g_source_set_callback(source, [](gpointer data) -> gboolean { auto& view = *static_cast(data); GMutexHolder lock (view.threading.mutex); view.threading.ready = TRUE; g_cond_signal(&view.threading.cond); return G_SOURCE_REMOVE; }, &view, nullptr); /* *INDENT-ON* */ g_source_attach (source, view.glib.context); g_source_unref (source); } g_main_loop_run (view.glib.loop); g_main_loop_unref (view.glib.loop); view.glib.loop = nullptr; g_main_context_pop_thread_default (view.glib.context); g_main_context_unref (view.glib.context); view.glib.context = nullptr; return nullptr; } GstWPEThreadedView * GstWPEContextThread::createWPEView (GstWpeVideoSrc2 * src, GstGLContext * context, GstGLDisplay * display, WPEDisplay * wpe_display, int width, int height) { GST_DEBUG ("context %p display %p, size (%d,%d)", context, display, width, height); GstWPEThreadedView *view = nullptr; /* *INDENT-OFF* */ dispatch([&]() mutable { if (!glib.web_context) { glib.web_context = WEBKIT_WEB_CONTEXT (g_object_new (WEBKIT_TYPE_WEB_CONTEXT, nullptr)); } view = new GstWPEThreadedView (glib.web_context, src, context, display, wpe_display, width, height); }); /* *INDENT-ON* */ if (view && view->hasUri ()) { GST_DEBUG ("waiting load to finish"); view->waitLoadCompletion (); GST_DEBUG ("done"); } return view; } static gboolean s_loadFailed (WebKitWebView *, WebKitLoadEvent, gchar * failing_uri, GError * error, gpointer data) { GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (data); if (g_error_matches (error, WEBKIT_NETWORK_ERROR, WEBKIT_NETWORK_ERROR_CANCELLED)) { GST_INFO_OBJECT (src, "Loading cancelled."); return FALSE; } GST_ELEMENT_ERROR (GST_ELEMENT_CAST (src), RESOURCE, FAILED, (NULL), ("Failed to load %s (%s)", failing_uri, error->message)); return FALSE; } static gboolean s_loadFailedWithTLSErrors (WebKitWebView *, gchar * failing_uri, GTlsCertificate *, GTlsCertificateFlags, gpointer data) { // Defer to load-failed. return FALSE; } static void s_loadProgressChanged (GObject * object, GParamSpec *, gpointer data) { GstElement *src = GST_ELEMENT_CAST (data); // The src element is locked already so we can't call // gst_element_post_message(). Instead retrieve the bus manually and use it // directly. GstBus *bus = GST_ELEMENT_BUS (src); double estimatedProgress; g_object_get (object, "estimated-load-progress", &estimatedProgress, nullptr); gst_object_ref (bus); gst_bus_post (bus, gst_message_new_element (GST_OBJECT_CAST (src), gst_structure_new ("wpe-stats", "estimated-load-progress", G_TYPE_DOUBLE, estimatedProgress * 100, nullptr))); gst_object_unref (bus); } static void s_webProcessCrashed (WebKitWebView *, WebKitWebProcessTerminationReason reason, gpointer data) { /* *INDENT-OFF* */ auto &view = *static_cast(data); /* *INDENT-ON* */ auto *src = view.src (); gchar *reason_str = g_enum_to_string (WEBKIT_TYPE_WEB_PROCESS_TERMINATION_REASON, reason); // In case the crash happened while doing the initial URL loading, unlock // the load completion waiting. view.notifyLoadFinished (); // TODO: Emit a signal here and fallback to error system if signal wasn't handled by application? GST_ELEMENT_ERROR (GST_ELEMENT_CAST (src), RESOURCE, FAILED, (NULL), ("%s", reason_str)); g_free (reason_str); } /* *INDENT-OFF* */ GstWPEThreadedView::GstWPEThreadedView( WebKitWebContext *web_context, GstWpeVideoSrc2 *src, GstGLContext *context, GstGLDisplay *display, WPEDisplay *wpe_display, int width, int height) : m_src(src) { g_mutex_init (&threading.ready_mutex); g_cond_init (&threading.ready_cond); threading.ready = FALSE; g_mutex_init (&images_mutex); if (context) gst.context = GST_GL_CONTEXT (gst_object_ref (context)); if (display) gst.display = GST_GL_DISPLAY (gst_object_ref (display)); wpe.width = width; wpe.height = height; auto *defaultWebsitePolicies = webkit_website_policies_new_with_policies( "autoplay", WEBKIT_AUTOPLAY_ALLOW, nullptr); webkit.view = WEBKIT_WEB_VIEW(g_object_new( WEBKIT_TYPE_WEB_VIEW, "web-context", web_context, "display", wpe_display, "website-policies", defaultWebsitePolicies, nullptr)); g_object_unref (wpe_display); g_object_unref(defaultWebsitePolicies); wpe.view = webkit_web_view_get_wpe_view (webkit.view); wpe_view_gstreamer_set_client (WPE_VIEW_GSTREAMER (wpe.view), this); if (auto wpeToplevel = wpe_view_get_toplevel (wpe.view)) wpe_toplevel_resize (wpeToplevel, width, height); // FIXME: unmap when appropriate and implement can_be_mapped if needed. wpe_view_map (wpe.view); g_signal_connect (webkit.view, "load-failed", G_CALLBACK (s_loadFailed), src); g_signal_connect (webkit.view, "load-failed-with-tls-errors", G_CALLBACK (s_loadFailedWithTLSErrors), src); g_signal_connect (webkit.view, "notify::estimated-load-progress", G_CALLBACK (s_loadProgressChanged), src); g_signal_connect (webkit.view, "web-process-terminated", G_CALLBACK (s_webProcessCrashed), this); auto *settings = webkit_web_view_get_settings (webkit.view); webkit_settings_set_enable_webaudio (settings, TRUE); gst_wpe_video_src_configure_web_view (src, webkit.view); gchar *location; gboolean drawBackground = TRUE; g_object_get (src, "location", &location, "draw-background", &drawBackground, nullptr); setDrawBackground (drawBackground); if (location) { loadUriUnlocked (location); g_free (location); } } /* *INDENT-ON* */ GstWPEThreadedView::~GstWPEThreadedView () { GstEGLImage *egl_pending = NULL; GstEGLImage *egl_committed = NULL; GstBuffer *shm_pending = NULL; GstBuffer *shm_committed = NULL; GST_TRACE ("%p destroying", this); g_mutex_clear (&threading.ready_mutex); g_cond_clear (&threading.ready_cond); { GMutexHolder lock (images_mutex); if (egl.pending) { egl_pending = egl.pending; egl.pending = nullptr; } if (egl.committed) { egl_committed = egl.committed; egl.committed = nullptr; } if (shm.pending) { GST_TRACE ("%p freeing shm pending %" GST_PTR_FORMAT, this, shm.pending); shm_pending = shm.pending; shm.pending = nullptr; } if (shm.committed) { GST_TRACE ("%p freeing shm commited %" GST_PTR_FORMAT, this, shm.committed); shm_committed = shm.committed; shm.committed = nullptr; } } if (egl_pending) gst_egl_image_unref (egl_pending); if (egl_committed) gst_egl_image_unref (egl_committed); if (shm_pending) gst_buffer_unref (shm_pending); if (shm_committed) gst_buffer_unref (shm_committed); /* *INDENT-OFF* */ GstWPEContextThread::singleton().dispatch([&]() { if (webkit.view) { g_object_unref (webkit.view); webkit.view = nullptr; } }); /* *INDENT-ON* */ if (gst.display_egl) { gst_object_unref (gst.display_egl); gst.display_egl = nullptr; } if (gst.display) { gst_object_unref (gst.display); gst.display = nullptr; } if (gst.context) { gst_object_unref (gst.context); gst.context = nullptr; } if (webkit.uri) { g_free (webkit.uri); webkit.uri = nullptr; } g_mutex_clear (&images_mutex); GST_TRACE ("%p destroyed", this); } void GstWPEThreadedView::notifyLoadFinished () { GMutexHolder lock (threading.ready_mutex); if (!threading.ready) { threading.ready = TRUE; g_cond_signal (&threading.ready_cond); } } void GstWPEThreadedView::waitLoadCompletion () { GMutexHolder lock (threading.ready_mutex); while (!threading.ready) g_cond_wait (&threading.ready_cond, &threading.ready_mutex); } GstEGLImage * GstWPEThreadedView::image () { GstEGLImage *ret = nullptr; bool dispatchFrameComplete = false; GstEGLImage *prev_image = NULL; { GMutexHolder lock (images_mutex); GST_TRACE ("pending %" GST_PTR_FORMAT " (%d) committed %" GST_PTR_FORMAT " (%d)", egl.pending, GST_IS_EGL_IMAGE (egl.pending) ? GST_MINI_OBJECT_REFCOUNT_VALUE (GST_MINI_OBJECT_CAST (egl.pending)) : 0, egl.committed, GST_IS_EGL_IMAGE (egl.committed) ? GST_MINI_OBJECT_REFCOUNT_VALUE (GST_MINI_OBJECT_CAST (egl.committed)) : 0); if (egl.pending) { prev_image = egl.committed; egl.committed = egl.pending; egl.pending = nullptr; dispatchFrameComplete = true; } if (egl.committed) ret = egl.committed; } if (prev_image) { gst_egl_image_unref (prev_image); } if (dispatchFrameComplete) { frameComplete (); } return ret; } GstBuffer * GstWPEThreadedView::buffer () { GstBuffer *ret = nullptr; bool dispatchFrameComplete = false; GstBuffer *prev_image = NULL; { GMutexHolder lock (images_mutex); GST_TRACE ("pending %" GST_PTR_FORMAT " (%d) committed %" GST_PTR_FORMAT " (%d)", shm.pending, GST_IS_BUFFER (shm.pending) ? GST_MINI_OBJECT_REFCOUNT_VALUE (GST_MINI_OBJECT_CAST (shm.pending)) : 0, shm.committed, GST_IS_BUFFER (shm.committed) ? GST_MINI_OBJECT_REFCOUNT_VALUE (GST_MINI_OBJECT_CAST (shm.committed)) : 0); if (shm.pending) { prev_image = shm.committed; shm.committed = shm.pending; shm.pending = nullptr; dispatchFrameComplete = true; } if (shm.committed) ret = shm.committed; } if (prev_image) gst_buffer_unref (prev_image); if (dispatchFrameComplete) { frameComplete (); } return ret; } void GstWPEThreadedView::resize (int width, int height) { GST_DEBUG ("resize to %dx%d", width, height); wpe.width = width; wpe.height = height; if (auto wpeToplevel = wpe_view_get_toplevel (wpe.view)) wpe_toplevel_resize (wpeToplevel, wpe.width, wpe.height); } void GstWPEThreadedView::clearBuffers () { bool dispatchFrameComplete = false; { GMutexHolder lock (images_mutex); if (shm.pending) { auto meta = gst_buffer_get_video_meta (shm.pending); if (static_cast < int >(meta->width) != wpe.width || static_cast < int >(meta->height) != wpe.height) { gst_clear_buffer (&shm.pending); dispatchFrameComplete = true; } } if (shm.committed) { auto meta = gst_buffer_get_video_meta (shm.committed); if (static_cast < int >(meta->width) != wpe.width || static_cast < int >(meta->height) != wpe.height) { gst_clear_buffer (&shm.committed); dispatchFrameComplete = true; } } } if (dispatchFrameComplete) { frameComplete (); // Wait until the next SHM buffer has been received. threading.ready = false; waitLoadCompletion (); } } void GstWPEThreadedView::loadUriUnlocked (const gchar * uri) { if (webkit.uri) g_free (webkit.uri); GST_DEBUG ("loading %s", uri); webkit.uri = g_strdup (uri); webkit_web_view_load_uri (webkit.view, webkit.uri); } void GstWPEThreadedView::loadUri (const gchar * uri) { s_view->dispatch ([&]() { loadUriUnlocked (uri);}); } static void s_runJavascriptFinished (GObject * object, GAsyncResult * result, gpointer user_data) { GError *error = NULL; g_autoptr (JSCValue) js_result = webkit_web_view_evaluate_javascript_finish (WEBKIT_WEB_VIEW (object), result, &error); // TODO: Pass result back to signal call site using a GstPromise? (void) js_result; if (error) { GST_WARNING ("Error running javascript: %s", error->message); g_error_free (error); } } void GstWPEThreadedView::runJavascript (const char *script) { /* *INDENT-OFF* */ s_view->dispatch([&]() { webkit_web_view_evaluate_javascript(webkit.view, script, -1, nullptr, nullptr, nullptr, s_runJavascriptFinished, nullptr); }); /* *INDENT-ON* */ } void GstWPEThreadedView::loadData (GBytes * bytes) { /* *INDENT-OFF* */ s_view->dispatch([this, bytes = g_bytes_ref(bytes)]() { webkit_web_view_load_bytes(webkit.view, bytes, nullptr, nullptr, nullptr); g_bytes_unref(bytes); }); /* *INDENT-ON* */ } void GstWPEThreadedView::setDrawBackground (gboolean drawsBackground) { GST_DEBUG ("%s background rendering", drawsBackground ? "Enabling" : "Disabling"); WebKitColor color; webkit_color_parse (&color, drawsBackground ? "white" : "transparent"); webkit_web_view_set_background_color (webkit.view, &color); } struct WPEBufferContext { GstWPEThreadedView *view; WPEBuffer *buffer; }; void GstWPEThreadedView::s_releaseBuffer (gpointer data) { /* *INDENT-OFF* */ s_view->dispatch([&]() { WPEBufferContext *context = static_cast(data); wpe_view_buffer_released(WPE_VIEW(context->view->wpe.view), context->buffer); g_object_unref(context->buffer); g_free(context); }); /* *INDENT-ON* */ } /* *INDENT-OFF* */ gboolean GstWPEThreadedView::setPendingBuffer(WPEBuffer *buffer, GError **error) { WPEBufferContext *bufferContext = g_new (WPEBufferContext, 1); bufferContext->view = this; bufferContext->buffer = g_object_ref (buffer); if (WPE_IS_BUFFER_DMA_BUF (buffer)) { auto eglImage = wpe_buffer_import_to_egl_image (buffer, error); if (*error) return FALSE; auto *gstImage = gst_egl_image_new_wrapped (gst.context, eglImage, GST_GL_RGBA, bufferContext,[](GstEGLImage *, gpointer data) { s_releaseBuffer (data); }); { GMutexHolder lock (images_mutex); GST_TRACE ("EGLImage %p wrapped in GstEGLImage %" GST_PTR_FORMAT, eglImage, gstImage); gst_clear_mini_object ((GstMiniObject **) & egl.pending); egl.pending = gstImage; m_pending_buffer = g_object_ref (buffer); notifyLoadFinished (); } return TRUE; } if (!WPE_IS_BUFFER_SHM (buffer)) { g_set_error_literal (error, WPE_VIEW_ERROR, WPE_VIEW_ERROR_RENDER_FAILED, "Unsupported WPEBuffer format"); return FALSE; } GBytes *bytes = wpe_buffer_import_to_pixels (buffer, error); if (!bytes) { return FALSE; } auto width = wpe_buffer_get_width (buffer); auto height = wpe_buffer_get_height (buffer); guint stride; g_object_get (buffer, "stride", &stride, nullptr); gsize size = g_bytes_get_size (bytes); auto *gstBuffer = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, (gpointer) g_bytes_get_data (bytes, nullptr), size, 0, size, bufferContext, s_releaseBuffer); gsize offsets[1]; gint strides[1]; offsets[0] = 0; strides[0] = stride; gst_buffer_add_video_meta_full (gstBuffer, GST_VIDEO_FRAME_FLAG_NONE, GST_VIDEO_FORMAT_BGRA, width, height, 1, offsets, strides); { GMutexHolder lock (images_mutex); GST_TRACE ("SHM buffer %p wrapped in buffer %" GST_PTR_FORMAT, buffer, gstBuffer); gst_clear_buffer (&shm.pending); shm.pending = gstBuffer; m_pending_buffer = g_object_ref (buffer); notifyLoadFinished (); } return TRUE; } /* *INDENT-ON* */ static uint32_t _pointer_modifiers_from_gst_event (GstEvent * ev) { GstNavigationModifierType modifier_state; uint32_t modifiers = 0; if (gst_navigation_event_parse_modifier_state (ev, &modifier_state)) { if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON1_MASK) modifiers |= WPE_MODIFIER_POINTER_BUTTON1; if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON2_MASK) modifiers |= WPE_MODIFIER_POINTER_BUTTON2; if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON3_MASK) modifiers |= WPE_MODIFIER_POINTER_BUTTON3; if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON4_MASK) modifiers |= WPE_MODIFIER_POINTER_BUTTON4; if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON5_MASK) modifiers |= WPE_MODIFIER_POINTER_BUTTON5; } return modifiers; } static uint32_t _keyboard_modifiers_from_gst_event (GstEvent * ev) { GstNavigationModifierType modifier_state; uint32_t modifiers = 0; if (gst_navigation_event_parse_modifier_state (ev, &modifier_state)) { if (modifier_state & GST_NAVIGATION_MODIFIER_CONTROL_MASK) modifiers |= WPE_MODIFIER_KEYBOARD_CONTROL; if (modifier_state & GST_NAVIGATION_MODIFIER_SHIFT_MASK) modifiers |= WPE_MODIFIER_KEYBOARD_SHIFT; if (modifier_state & GST_NAVIGATION_MODIFIER_MOD1_MASK) modifiers |= WPE_MODIFIER_KEYBOARD_ALT; if (modifier_state & GST_NAVIGATION_MODIFIER_META_MASK) modifiers |= WPE_MODIFIER_KEYBOARD_META; } return modifiers; } static WPEModifiers modifiers_from_gst_event (GstEvent * event) { /* *INDENT-OFF* */ return static_cast (_pointer_modifiers_from_gst_event (event) | _keyboard_modifiers_from_gst_event (event)); /* *INDENT-ON* */ } void GstWPEThreadedView::frameComplete () { GST_TRACE ("frame complete"); /* *INDENT-OFF* */ s_view->dispatch([&]() { if (m_committed_buffer) { wpe_view_buffer_released(WPE_VIEW(wpe.view), m_committed_buffer); g_object_unref(m_committed_buffer); } m_committed_buffer = m_pending_buffer; wpe_view_buffer_rendered (WPE_VIEW (wpe.view), m_committed_buffer); }); /* *INDENT-ON* */ } void GstWPEThreadedView::dispatchEvent (WPEEvent * wpe_event) { /* *INDENT-OFF* */ s_view->dispatch([&]() { wpe_view_event(WPE_VIEW(wpe.view), wpe_event); wpe_event_unref(wpe_event); }); /* *INDENT-ON* */ } /* *INDENT-OFF* */ gboolean GstWPEThreadedView::dispatchKeyboardEvent(GstEvent *event) { const gchar *key; if (!gst_navigation_event_parse_key_event (event, &key)) { return FALSE; } auto modifiers = static_cast(_keyboard_modifiers_from_gst_event (event)); auto timestamp = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event)); /* FIXME: This is wrong... The GstNavigation API should pass hardware-level information, not high-level keysym strings */ gunichar *unichar; glong items_written; uint32_t keysym; unichar = g_utf8_to_ucs4_fast (key, -1, &items_written); if (items_written == 1) keysym = (uint32_t) xkb_utf32_to_keysym (*unichar); else keysym = (uint32_t) xkb_keysym_from_name (key, XKB_KEYSYM_NO_FLAGS); WPEEventType event_type = WPE_EVENT_NONE; if (gst_navigation_event_get_type (event) == GST_NAVIGATION_EVENT_KEY_PRESS) event_type = WPE_EVENT_KEYBOARD_KEY_DOWN; else event_type = WPE_EVENT_KEYBOARD_KEY_UP; dispatchEvent (wpe_event_keyboard_new (event_type, WPE_VIEW (wpe.view), WPE_INPUT_SOURCE_KEYBOARD, timestamp, modifiers, keysym, keysym)); return TRUE; } gboolean GstWPEThreadedView::dispatchPointerEvent (GstEvent * event) { gdouble x, y; gint button; if (!gst_navigation_event_parse_mouse_button_event (event, &button, &x, &y)) { return FALSE; } GstNavigationModifierType modifier_state; guint wpe_button = 0; if (gst_navigation_event_parse_modifier_state (event, &modifier_state)) { if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON1_MASK) wpe_button = WPE_BUTTON_PRIMARY; else if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON2_MASK) wpe_button = WPE_BUTTON_MIDDLE; else if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON3_MASK) wpe_button = WPE_BUTTON_SECONDARY; } auto timestamp = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event)); guint press_count = 0; WPEEventType type; if (gst_navigation_event_get_type (event) == GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS) { press_count = wpe_view_compute_press_count (WPE_VIEW (wpe.view), x, y, wpe_button, timestamp); type = WPE_EVENT_POINTER_DOWN; } else { type = WPE_EVENT_POINTER_UP; } dispatchEvent (wpe_event_pointer_button_new (type, WPE_VIEW (wpe.view), WPE_INPUT_SOURCE_MOUSE, timestamp, modifiers_from_gst_event (event), wpe_button, x, y, press_count)); return TRUE; } gboolean GstWPEThreadedView::dispatchPointerMoveEvent (GstEvent * event) { gdouble x, y; if (!gst_navigation_event_parse_mouse_move_event (event, &x, &y)) { return FALSE; } gdouble delta_x = 0; gdouble delta_y = 0; if (m_last_pointer_position) { delta_x = x - m_last_pointer_position->first; delta_y = y - m_last_pointer_position->second; } m_last_pointer_position = { x, y }; auto timestamp = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event)); dispatchEvent (wpe_event_pointer_move_new (WPE_EVENT_POINTER_MOVE, WPE_VIEW (wpe.view), WPE_INPUT_SOURCE_MOUSE, timestamp, modifiers_from_gst_event (event), x, y, delta_x, delta_y)); return TRUE; } gboolean GstWPEThreadedView::dispatchAxisEvent (GstEvent * event) { gdouble x, y, delta_x, delta_y; if (!gst_navigation_event_parse_mouse_scroll_event (event, &x, &y, &delta_x, &delta_y)) { return FALSE; } auto timestamp = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event)); dispatchEvent (wpe_event_scroll_new (WPE_VIEW (wpe.view), WPE_INPUT_SOURCE_MOUSE, timestamp, modifiers_from_gst_event (event), delta_x, delta_y, TRUE, FALSE, x, y)); return TRUE; } gboolean GstWPEThreadedView::dispatchTouchEvent (GstEvent * event) { guint touch_id; gdouble x, y; if (!gst_navigation_event_parse_touch_event (event, &touch_id, &x, &y, NULL)) { return FALSE; } WPEEventType event_type = WPE_EVENT_NONE; switch (gst_navigation_event_get_type (event)) { case GST_NAVIGATION_EVENT_TOUCH_DOWN: event_type = WPE_EVENT_TOUCH_DOWN; break; case GST_NAVIGATION_EVENT_TOUCH_MOTION: event_type = WPE_EVENT_TOUCH_MOVE; break; case GST_NAVIGATION_EVENT_TOUCH_UP: event_type = WPE_EVENT_TOUCH_UP; break; default: break; } auto timestamp = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event)); auto modifiers = static_cast(_keyboard_modifiers_from_gst_event (event)); dispatchEvent (wpe_event_touch_new (event_type, WPE_VIEW (wpe.view), WPE_INPUT_SOURCE_TOUCHPAD, timestamp, modifiers, touch_id, x, y)); return TRUE; } /* *INDENT-ON* */