Philippe Normand 28d97212c5 wpe2: New WPE plugin making use of the "WPE Platform API"
Currently only a wpevideosrc2 element is exposed. GL and SHM buffer rendering
are supported, navigation events too (touch is un-tested). Audio pads handling
is not supported yet (that requires new WPE API).

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8789>
2025-04-29 08:10:01 +01:00

947 lines
26 KiB
C++

/* Copyright (C) <2018, 2019, 2020, 2025> Philippe Normand <philn@igalia.com>
* Copyright (C) <2018> Žan Doberšek <zdobersek@igalia.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 "gstwpe.h"
#include "gstwpethreadedview.h"
#include "gstwpedisplay.h"
#include "gstwpeview.h"
#include <gst/gl/gl.h>
#include <gst/gl/egl/gsteglimage.h>
#include <gst/gl/egl/gstgldisplay_egl.h>
#include <cstdio>
#include <mutex>
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<struct Job *>(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<GstWPEContextThread *>(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<GstWPEContextThread*>(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<GstWPEThreadedView *>(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<WPEBufferContext *>(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<WPEModifiers>
(_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<WPEModifiers>(_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<WPEModifiers>(_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* */