debugqroverlay: fix string leak
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / ext / wpe / gstwpevideosrc.cpp
1 /* Copyright (C) <2018> Philippe Normand <philn@igalia.com>
2  * Copyright (C) <2018> Žan Doberšek <zdobersek@igalia.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /**
21  * SECTION:element-wpevideosrc
22  * @title: wpevideosrc
23  *
24  * The wpevideosrc element is used to produce a video texture representing a web page
25  * rendered off-screen by WPE.
26  *
27  * Starting from WPEBackend-FDO 1.6.x, software rendering support is available. This
28  * features allows wpevideosrc to be used on machines without GPU, and/or for testing
29  * purpose. To enable it, set the `LIBGL_ALWAYS_SOFTWARE=true` environment
30  * variable and make sure `video/x-raw, format=BGRA` caps are negotiated by the
31  * wpevideosrc element.
32  *
33  * As the webview loading is usually not instantaneous, the wpevideosrc element emits
34  * messages indicating the load progress, in percent. The value is an estimate
35  * based on the total number of bytes expected to be received for a document,
36  * including all its possible subresources and child documents. The application
37  * can handle these `element` messages synchronously for instance, in order to
38  * display a progress bar or other visual load indicator. The load percent value
39  * is stored in the message structure as a double value named
40  * `estimated-load-progress` and the structure name is `wpe-stats`.
41  *
42  * ## Example launch lines
43  *
44  * ```shell
45  * gst-launch-1.0 -v wpevideosrc location="https://gstreamer.freedesktop.org" ! queue ! glimagesink
46  * ```
47  * Shows the GStreamer website homepage
48  *
49  * ```shell
50  * LIBGL_ALWAYS_SOFTWARE=true gst-launch-1.0 -v wpevideosrc num-buffers=50 location="https://gstreamer.freedesktop.org" \
51  *   videoconvert ! pngenc ! multifilesink location=/tmp/snapshot-%05d.png
52  * ```
53  * Saves the first 50 video frames generated for the GStreamer website as PNG files in /tmp.
54  *
55  * ```shell
56  * gst-play-1.0 --videosink gtkglsink web+https://gstreamer.freedesktop.org
57  * ```
58  * Shows the GStreamer website homepage as played with GstPlayer in a GTK+ window.
59  *
60  * ```shell
61  * gst-launch-1.0  glvideomixer name=m sink_1::zorder=0 ! glimagesink wpevideosrc location="file:///tmp/asset.html" draw-background=0 \
62  *   ! m. videotestsrc ! queue ! glupload ! glcolorconvert ! m.
63  * ```
64  * Composite WPE with a video stream in a single OpenGL scene.
65  *
66  * ```shell
67  * gst-launch-1.0 glvideomixer name=m sink_1::zorder=0 sink_0::height=818 sink_0::width=1920 ! gtkglsink \
68  *    wpevideosrc location="file:///tmp/asset.html" draw-background=0 ! m.
69  *    uridecodebin uri="http://example.com/Sintel.2010.1080p.mkv" name=d d. ! queue ! glupload ! glcolorconvert ! m.
70  * ```
71  * Composite WPE with a video stream, sink_0 pad properties have to match the video dimensions.
72  *
73  * Since: 1.16
74  */
75
76 /*
77  * TODO:
78  * - DMABuf support (requires changes in WPEBackend-fdo to expose DMABuf planes and fds)
79  * - Custom EGLMemory allocator
80  * - Better navigation events handling (would require a new GstNavigation API)
81  */
82
83 #ifdef HAVE_CONFIG_H
84 #include <config.h>
85 #endif
86
87 #include "gstwpe.h"
88 #include "gstwpevideosrc.h"
89 #include <gst/gl/gl.h>
90 #include <gst/gl/egl/gstglmemoryegl.h>
91 #include <gst/gl/wayland/gstgldisplay_wayland.h>
92 #include <gst/video/video.h>
93 #include <xkbcommon/xkbcommon.h>
94
95 #include "WPEThreadedView.h"
96
97 #define DEFAULT_WIDTH 1920
98 #define DEFAULT_HEIGHT 1080
99 #define DEFAULT_FPS_N 30
100 #define DEFAULT_FPS_D 1
101 #define DEFAULT_DRAW_BACKGROUND TRUE
102
103 enum
104 {
105   PROP_0,
106   PROP_LOCATION,
107   PROP_DRAW_BACKGROUND
108 };
109
110 enum
111 {
112   SIGNAL_CONFIGURE_WEB_VIEW,
113   SIGNAL_LOAD_BYTES,
114   SIGNAL_RUN_JAVASCRIPT,
115   LAST_SIGNAL
116 };
117 static guint gst_wpe_video_src_signals[LAST_SIGNAL] = { 0 };
118
119 struct _GstWpeVideoSrc
120 {
121   GstGLBaseSrc parent;
122
123   /* properties */
124   gchar *location;
125   gboolean draw_background;
126
127   GBytes *bytes;
128   gboolean gl_enabled;
129
130   gint64 n_frames;              /* total frames sent */
131
132   WPEView *view;
133
134   GArray *touch_points;
135   struct wpe_input_touch_event_raw *last_touch;
136
137   GMutex lock;
138 };
139
140 #define WPE_LOCK(o) g_mutex_lock(&(o)->lock)
141 #define WPE_UNLOCK(o) g_mutex_unlock(&(o)->lock)
142
143 GST_DEBUG_CATEGORY_EXTERN (wpe_video_src_debug);
144 #define GST_CAT_DEFAULT wpe_video_src_debug
145
146 #define gst_wpe_video_src_parent_class parent_class
147 G_DEFINE_TYPE (GstWpeVideoSrc, gst_wpe_video_src, GST_TYPE_GL_BASE_SRC);
148
149 #define WPE_RAW_CAPS "video/x-raw, "            \
150   "format = (string) BGRA, "                    \
151   "width = " GST_VIDEO_SIZE_RANGE ", "          \
152   "height = " GST_VIDEO_SIZE_RANGE ", "         \
153   "framerate = " GST_VIDEO_FPS_RANGE ", "       \
154   "pixel-aspect-ratio = (fraction)1/1"
155
156 #define WPE_GL_CAPS "video/x-raw(memory:GLMemory), "    \
157   "format = (string) RGBA, "                            \
158   "width = " GST_VIDEO_SIZE_RANGE ", "                  \
159   "height = " GST_VIDEO_SIZE_RANGE ", "                 \
160   "framerate = " GST_VIDEO_FPS_RANGE ", "               \
161   "pixel-aspect-ratio = (fraction)1/1, texture-target = (string)2D"
162
163 #define WPE_VIDEO_SRC_CAPS WPE_GL_CAPS "; " WPE_RAW_CAPS
164 #define WPE_VIDEO_SRC_DOC_CAPS WPE_GL_CAPS "; video/x-raw, format = (string) BGRA"
165
166 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
167     GST_PAD_SRC,
168     GST_PAD_ALWAYS,
169     GST_STATIC_CAPS (WPE_VIDEO_SRC_CAPS));
170
171 static GstFlowReturn
172 gst_wpe_video_src_create (GstBaseSrc * bsrc, guint64 offset, guint length,
173     GstBuffer ** buf)
174 {
175   GstGLBaseSrc *gl_src = GST_GL_BASE_SRC (bsrc);
176   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (bsrc);
177   GstFlowReturn ret = GST_FLOW_ERROR;
178   GstBuffer *locked_buffer;
179   GstClockTime next_time;
180   gint64 ts_offset = 0;
181
182   WPE_LOCK (src);
183   if (src->gl_enabled) {
184     WPE_UNLOCK (src);
185     return GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SRC_CLASS, create, (bsrc,
186             offset, length, buf), ret);
187   }
188
189   locked_buffer = src->view->buffer ();
190   if (locked_buffer == NULL) {
191     WPE_UNLOCK (src);
192     GST_ELEMENT_ERROR (src, RESOURCE, FAILED,
193         ("WPE View did not render a buffer"), (NULL));
194     return ret;
195   }
196   *buf = gst_buffer_copy_deep (locked_buffer);
197
198   g_object_get (gl_src, "timestamp-offset", &ts_offset, NULL);
199
200   /* The following code mimics the behaviour of GLBaseSrc::fill */
201   GST_BUFFER_TIMESTAMP (*buf) = ts_offset + gl_src->running_time;
202   GST_BUFFER_OFFSET (*buf) = src->n_frames;
203   src->n_frames++;
204   GST_BUFFER_OFFSET_END (*buf) = src->n_frames;
205   if (gl_src->out_info.fps_n) {
206     next_time = gst_util_uint64_scale_int (src->n_frames * GST_SECOND,
207         gl_src->out_info.fps_d, gl_src->out_info.fps_n);
208     GST_BUFFER_DURATION (*buf) = next_time - gl_src->running_time;
209   } else {
210     next_time = ts_offset;
211     GST_BUFFER_DURATION (*buf) = GST_CLOCK_TIME_NONE;
212   }
213
214   GST_LOG_OBJECT (src, "Created buffer from SHM %" GST_PTR_FORMAT, *buf);
215
216   gl_src->running_time = next_time;
217
218   ret = GST_FLOW_OK;
219   WPE_UNLOCK (src);
220   return ret;
221 }
222
223 static GQuark
224 _egl_image_quark (void)
225 {
226   static GQuark quark = 0;
227
228   if (!quark)
229     quark = g_quark_from_static_string ("GstWPEEGLImage");
230   return quark;
231 }
232
233 static gboolean
234 gst_wpe_video_src_fill_memory (GstGLBaseSrc * bsrc, GstGLMemory * memory)
235 {
236   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (bsrc);
237   const GstGLFuncs *gl;
238   guint tex_id;
239   GstEGLImage *locked_image;
240
241   if (!gst_gl_context_check_feature (GST_GL_CONTEXT (bsrc->context),
242           "EGL_KHR_image_base")) {
243     GST_ERROR_OBJECT (src, "EGL_KHR_image_base is not supported");
244     return FALSE;
245   }
246
247   WPE_LOCK (src);
248
249   gl = bsrc->context->gl_vtable;
250   tex_id = gst_gl_memory_get_texture_id (memory);
251   locked_image = src->view->image ();
252
253   if (!locked_image) {
254     WPE_UNLOCK (src);
255     return TRUE;
256   }
257
258   // The EGLImage is implicitely associated with the memory we're filling, so we
259   // need to ensure their life cycles are tied.
260   gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (memory), _egl_image_quark (),
261       gst_egl_image_ref (locked_image), (GDestroyNotify) gst_egl_image_unref);
262
263   gl->ActiveTexture (GL_TEXTURE0 + memory->plane);
264   gl->BindTexture (GL_TEXTURE_2D, tex_id);
265   gl->EGLImageTargetTexture2D (GL_TEXTURE_2D,
266       gst_egl_image_get_image (locked_image));
267   gl->Flush ();
268   WPE_UNLOCK (src);
269   return TRUE;
270 }
271
272 static gboolean
273 gst_wpe_video_src_start (GstWpeVideoSrc * src)
274 {
275   GstGLContext *context = NULL;
276   GstGLDisplay *display = NULL;
277   GstGLBaseSrc *base_src = GST_GL_BASE_SRC (src);
278   gboolean created_view = FALSE;
279   GBytes *bytes;
280
281   GST_INFO_OBJECT (src, "Starting up");
282   WPE_LOCK (src);
283
284   if (src->gl_enabled) {
285     context = base_src->context;
286     display = base_src->display;
287   }
288
289   GST_DEBUG_OBJECT (src, "Will %sfill GLMemories",
290       src->gl_enabled ? "" : "NOT ");
291
292   auto & thread = WPEContextThread::singleton ();
293
294   if (!src->view) {
295     src->view = thread.createWPEView (src, context, display,
296         GST_VIDEO_INFO_WIDTH (&base_src->out_info),
297         GST_VIDEO_INFO_HEIGHT (&base_src->out_info));
298     created_view = TRUE;
299     GST_DEBUG_OBJECT (src, "created view %p", src->view);
300   }
301
302   if (!src->view) {
303     WPE_UNLOCK (src);
304     GST_ELEMENT_ERROR (src, RESOURCE, FAILED,
305         ("WPEBackend-FDO EGL display initialisation failed"), (NULL));
306     return FALSE;
307   }
308
309   GST_OBJECT_LOCK (src);
310   bytes = src->bytes;
311   src->bytes = NULL;
312   GST_OBJECT_UNLOCK (src);
313
314   if (bytes != NULL) {
315     src->view->loadData (bytes);
316     g_bytes_unref (bytes);
317   }
318
319   if (created_view) {
320     src->n_frames = 0;
321   }
322   WPE_UNLOCK (src);
323   return TRUE;
324 }
325
326 static gboolean
327 gst_wpe_video_src_decide_allocation (GstBaseSrc * base_src, GstQuery * query)
328 {
329   GstGLBaseSrc *gl_src = GST_GL_BASE_SRC (base_src);
330   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (base_src);
331   GstCapsFeatures *caps_features;
332
333   WPE_LOCK (src);
334   caps_features = gst_caps_get_features (gl_src->out_caps, 0);
335   if (caps_features != NULL
336       && gst_caps_features_contains (caps_features,
337           GST_CAPS_FEATURE_MEMORY_GL_MEMORY)) {
338     src->gl_enabled = TRUE;
339   } else {
340     src->gl_enabled = FALSE;
341   }
342
343   if (src->gl_enabled) {
344     WPE_UNLOCK (src);
345     return GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SRC_CLASS, decide_allocation,
346         (base_src, query), FALSE);
347   }
348   WPE_UNLOCK (src);
349   return gst_wpe_video_src_start (src);
350 }
351
352 static gboolean
353 gst_wpe_video_src_gl_start (GstGLBaseSrc * base_src)
354 {
355   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (base_src);
356   return gst_wpe_video_src_start (src);
357 }
358
359 static void
360 gst_wpe_video_src_stop_unlocked (GstWpeVideoSrc * src)
361 {
362   if (src->view) {
363     GST_DEBUG_OBJECT (src, "deleting view %p", src->view);
364     delete src->view;
365     src->view = NULL;
366   }
367 }
368
369 static void
370 gst_wpe_video_src_gl_stop (GstGLBaseSrc * base_src)
371 {
372   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (base_src);
373
374   WPE_LOCK (src);
375   gst_wpe_video_src_stop_unlocked (src);
376   WPE_UNLOCK (src);
377 }
378
379 static gboolean
380 gst_wpe_video_src_stop (GstBaseSrc * base_src)
381 {
382   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (base_src);
383
384   /* we can call this always, GstGLBaseSrc is smart enough to not crash if
385    * gst_gl_base_src_gl_start() has not been called from chaining up
386    * gst_wpe_video_src_decide_allocation() */
387   if (!GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SRC_CLASS, stop, (base_src),
388           FALSE))
389     return FALSE;
390
391   WPE_LOCK (src);
392
393   /* if gl-enabled, gst_wpe_video_src_stop_unlocked() would have already been called
394    * inside gst_wpe_video_src_gl_stop() from the base class stopping the OpenGL
395    * context */
396   if (!src->gl_enabled)
397     gst_wpe_video_src_stop_unlocked (src);
398
399   WPE_UNLOCK (src);
400   return TRUE;
401 }
402
403 static GstCaps *
404 gst_wpe_video_src_fixate (GstBaseSrc * base_src, GstCaps * combined_caps)
405 {
406   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (base_src);
407   GstStructure *structure;
408   gint width, height;
409   GstCaps *caps;
410
411   /* In situation where software GL support is explicitly requested, select raw
412    * caps, otherwise perform default caps negotiation. Unfortunately at this
413    * point we don't know yet if a GL context will be usable or not, so we can't
414    * check the element GstContext.
415    */
416   if (!g_strcmp0 (g_getenv ("LIBGL_ALWAYS_SOFTWARE"), "true")) {
417     caps = gst_caps_from_string (WPE_RAW_CAPS);
418   } else {
419     caps = gst_caps_make_writable (combined_caps);
420   }
421
422   structure = gst_caps_get_structure (caps, 0);
423
424   gst_structure_fixate_field_nearest_int (structure, "width", DEFAULT_WIDTH);
425   gst_structure_fixate_field_nearest_int (structure, "height", DEFAULT_HEIGHT);
426
427   if (gst_structure_has_field (structure, "framerate"))
428     gst_structure_fixate_field_nearest_fraction (structure, "framerate",
429         DEFAULT_FPS_N, DEFAULT_FPS_D);
430   else
431     gst_structure_set (structure, "framerate", GST_TYPE_FRACTION, DEFAULT_FPS_N,
432         DEFAULT_FPS_D, NULL);
433
434   caps = GST_BASE_SRC_CLASS (parent_class)->fixate (base_src, caps);
435   GST_INFO_OBJECT (base_src, "Fixated caps to %" GST_PTR_FORMAT, caps);
436
437   if (src->view) {
438     gst_structure_get (structure, "width", G_TYPE_INT, &width, "height",
439         G_TYPE_INT, &height, NULL);
440     src->view->resize (width, height);
441   }
442   return caps;
443 }
444
445 void
446 gst_wpe_video_src_configure_web_view (GstWpeVideoSrc * src,
447     WebKitWebView * webview)
448 {
449   GValue args[2] = { {0}, {0} };
450
451   g_value_init (&args[0], GST_TYPE_ELEMENT);
452   g_value_set_object (&args[0], src);
453   g_value_init (&args[1], G_TYPE_OBJECT);
454   g_value_set_object (&args[1], webview);
455
456   g_signal_emitv (args, gst_wpe_video_src_signals[SIGNAL_CONFIGURE_WEB_VIEW], 0,
457       NULL);
458
459   g_value_unset (&args[0]);
460   g_value_unset (&args[1]);
461 }
462
463 static void
464 gst_wpe_video_src_run_javascript (GstWpeVideoSrc * src, const gchar * script)
465 {
466   if (src->view && GST_STATE (GST_ELEMENT_CAST (src)) > GST_STATE_NULL) {
467     GST_INFO_OBJECT (src, "running javascript");
468     src->view->runJavascript (script);
469   }
470 }
471
472 static void
473 gst_wpe_video_src_load_bytes (GstWpeVideoSrc * src, GBytes * bytes)
474 {
475   if (src->view && GST_STATE (GST_ELEMENT_CAST (src)) > GST_STATE_NULL) {
476     src->view->loadData (bytes);
477   } else {
478     GST_OBJECT_LOCK (src);
479     if (src->bytes)
480       g_bytes_unref (src->bytes);
481     src->bytes = g_bytes_ref (bytes);
482     GST_OBJECT_UNLOCK (src);
483   }
484 }
485
486 static gboolean
487 gst_wpe_video_src_set_location (GstWpeVideoSrc * src, const gchar * location,
488     GError ** error)
489 {
490   GST_OBJECT_LOCK (src);
491   g_free (src->location);
492   src->location = g_strdup (location);
493   GST_OBJECT_UNLOCK (src);
494
495   if (src->view)
496     src->view->loadUri (location);
497
498   return TRUE;
499 }
500
501 static void
502 gst_wpe_video_src_set_draw_background (GstWpeVideoSrc * src,
503     gboolean draw_background)
504 {
505   GST_OBJECT_LOCK (src);
506   src->draw_background = draw_background;
507   GST_OBJECT_UNLOCK (src);
508
509   if (src->view)
510     src->view->setDrawBackground (draw_background);
511 }
512
513 static void
514 gst_wpe_video_src_set_property (GObject * object, guint prop_id,
515     const GValue * value, GParamSpec * pspec)
516 {
517   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (object);
518
519   switch (prop_id) {
520     case PROP_LOCATION:
521     {
522       const gchar *location;
523
524       location = g_value_get_string (value);
525       if (location == NULL) {
526         GST_WARNING_OBJECT (src, "location property cannot be NULL");
527         return;
528       }
529
530       if (!gst_wpe_video_src_set_location (src, location, NULL)) {
531         GST_WARNING_OBJECT (src, "badly formatted location");
532         return;
533       }
534       break;
535     }
536     case PROP_DRAW_BACKGROUND:
537       gst_wpe_video_src_set_draw_background (src, g_value_get_boolean (value));
538       break;
539     default:
540       break;
541   }
542 }
543
544 static void
545 gst_wpe_video_src_get_property (GObject * object, guint prop_id, GValue * value,
546     GParamSpec * pspec)
547 {
548   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (object);
549
550   switch (prop_id) {
551     case PROP_LOCATION:
552       GST_OBJECT_LOCK (src);
553       g_value_set_string (value, src->location);
554       GST_OBJECT_UNLOCK (src);
555       break;
556     case PROP_DRAW_BACKGROUND:
557       GST_OBJECT_LOCK (src);
558       g_value_set_boolean (value, src->draw_background);
559       GST_OBJECT_UNLOCK (src);
560       break;
561     default:
562       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
563       break;
564   }
565 }
566
567 static void
568 _set_touch_point (struct wpe_input_touch_event_raw * point,
569     GstEvent * event, enum wpe_input_touch_event_type type, guint id, gdouble x,
570     gdouble y)
571 {
572
573   point->time = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event));
574   point->type = type;
575   point->id = (int) id;
576   point->x = (int32_t) x;
577   point->y = (int32_t) y;
578 }
579
580 static uint32_t
581 _gst_modifiers_to_wpe (GstEvent * ev)
582 {
583   GstNavigationModifierType modifier_state;
584   uint32_t modifiers = 0;
585
586   if (gst_navigation_event_parse_modifier_state (ev, &modifier_state)) {
587     if (modifier_state & GST_NAVIGATION_MODIFIER_CONTROL_MASK)
588       modifiers |= wpe_input_keyboard_modifier_control;
589     if (modifier_state & GST_NAVIGATION_MODIFIER_SHIFT_MASK)
590       modifiers |= wpe_input_keyboard_modifier_shift;
591     if (modifier_state & GST_NAVIGATION_MODIFIER_MOD1_MASK)
592       modifiers |= wpe_input_keyboard_modifier_alt;
593     if (modifier_state & GST_NAVIGATION_MODIFIER_META_MASK)
594       modifiers |= wpe_input_keyboard_modifier_meta;
595     if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON1_MASK)
596       modifiers |= wpe_input_pointer_modifier_button1;
597     if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON2_MASK)
598       modifiers |= wpe_input_pointer_modifier_button2;
599     if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON3_MASK)
600       modifiers |= wpe_input_pointer_modifier_button3;
601     if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON4_MASK)
602       modifiers |= wpe_input_pointer_modifier_button4;
603     if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON5_MASK)
604       modifiers |= wpe_input_pointer_modifier_button5;
605   }
606
607   return modifiers;
608 }
609
610 static gboolean
611 gst_wpe_video_src_event (GstBaseSrc * base_src, GstEvent * event)
612 {
613   gboolean ret = FALSE;
614   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (base_src);
615
616   if (src->view && GST_EVENT_TYPE (event) == GST_EVENT_NAVIGATION) {
617     const gchar *key;
618     gint button;
619     guint touch_id;
620     gdouble x, y, delta_x, delta_y;
621
622     GST_DEBUG_OBJECT (src, "Processing event %" GST_PTR_FORMAT, event);
623     switch (gst_navigation_event_get_type (event)) {
624       case GST_NAVIGATION_EVENT_KEY_PRESS:
625       case GST_NAVIGATION_EVENT_KEY_RELEASE:
626         if (gst_navigation_event_parse_key_event (event, &key)) {
627           /* FIXME: This is wrong... The GstNavigation API should pass
628              hardware-level information, not high-level keysym strings */
629           gunichar *unichar;
630           glong items_written;
631           uint32_t keysym;
632           struct wpe_input_keyboard_event wpe_event = { 0 };
633
634           unichar = g_utf8_to_ucs4_fast (key, -1, &items_written);
635           if (items_written == 1)
636             keysym = (uint32_t) xkb_utf32_to_keysym (*unichar);
637           else
638             keysym =
639               (uint32_t) xkb_keysym_from_name (key, XKB_KEYSYM_NO_FLAGS);
640
641           wpe_event.key_code = keysym;
642           wpe_event.pressed =
643               gst_navigation_event_get_type (event) ==
644               GST_NAVIGATION_EVENT_KEY_PRESS;
645           wpe_event.modifiers = _gst_modifiers_to_wpe (event);
646           src->view->dispatchKeyboardEvent (wpe_event);
647           ret = TRUE;
648         }
649         break;
650       case GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS:
651       case GST_NAVIGATION_EVENT_MOUSE_BUTTON_RELEASE:
652         if (gst_navigation_event_parse_mouse_button_event (event, &button, &x,
653                 &y)) {
654           struct wpe_input_pointer_event wpe_event;
655           wpe_event.time = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event));
656           wpe_event.type = wpe_input_pointer_event_type_button;
657           wpe_event.x = (int) x;
658           wpe_event.y = (int) y;
659           wpe_event.modifiers = _gst_modifiers_to_wpe (event);
660           wpe_event.button = button;
661           wpe_event.state =
662               gst_navigation_event_get_type (event) ==
663               GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS;
664           src->view->dispatchPointerEvent (wpe_event);
665           ret = TRUE;
666         }
667         break;
668       case GST_NAVIGATION_EVENT_MOUSE_MOVE:
669         if (gst_navigation_event_parse_mouse_move_event (event, &x, &y)) {
670           struct wpe_input_pointer_event wpe_event;
671           wpe_event.time = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event));
672           wpe_event.type = wpe_input_pointer_event_type_motion;
673           wpe_event.x = (int) x;
674           wpe_event.y = (int) y;
675           wpe_event.modifiers = _gst_modifiers_to_wpe (event);
676           src->view->dispatchPointerEvent (wpe_event);
677           ret = TRUE;
678         }
679         break;
680       case GST_NAVIGATION_EVENT_MOUSE_SCROLL:
681         if (gst_navigation_event_parse_mouse_scroll_event (event, &x, &y,
682                 &delta_x, &delta_y)) {
683           struct wpe_input_axis_event wpe_event;
684           if (delta_x) {
685             wpe_event.axis = 1;
686             wpe_event.value = delta_x;
687           } else {
688             wpe_event.axis = 0;
689             wpe_event.value = delta_y;
690           }
691           wpe_event.time = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event));
692           wpe_event.type = wpe_input_axis_event_type_motion;
693           wpe_event.x = (int) x;
694           wpe_event.y = (int) y;
695           src->view->dispatchAxisEvent (wpe_event);
696           ret = TRUE;
697         }
698         break;
699       case GST_NAVIGATION_EVENT_TOUCH_DOWN:
700         if (gst_navigation_event_parse_touch_event (event, &touch_id, &x, &y,
701               NULL)) {
702           struct wpe_input_touch_event_raw *point = g_new
703               (struct wpe_input_touch_event_raw, 1);
704
705           _set_touch_point (point, event, wpe_input_touch_event_type_down,
706               touch_id, x, y);
707           src->touch_points = g_array_append_vals(src->touch_points, point, 1);
708           src->last_touch = point;
709           ret = TRUE;
710         }
711         break;
712       case GST_NAVIGATION_EVENT_TOUCH_MOTION:
713         if (gst_navigation_event_parse_touch_event (event, &touch_id, &x, &y,
714               NULL)) {
715           struct wpe_input_touch_event_raw *touch_points;
716           guint idx;
717
718           touch_points = (struct wpe_input_touch_event_raw *)
719               src->touch_points->data;
720           for (idx = 0; idx < src->touch_points->len; idx++) {
721             if (touch_points[idx].id == (int32_t) touch_id) {
722               _set_touch_point (&touch_points[idx], event,
723                   wpe_input_touch_event_type_motion, touch_id, x, y);
724               src->last_touch = &touch_points[idx];
725               break;
726             }
727           }
728           ret = TRUE;
729         }
730         break;
731       case GST_NAVIGATION_EVENT_TOUCH_UP:
732         if (gst_navigation_event_parse_touch_up_event (event, &touch_id, &x,
733               &y)) {
734           struct wpe_input_touch_event_raw *touch_points;
735           guint idx;
736
737           touch_points = (struct wpe_input_touch_event_raw *)
738               src->touch_points->data;
739           for (idx = 0; idx < src->touch_points->len; idx++) {
740             if (touch_points[idx].id == (int32_t) touch_id) {
741               _set_touch_point (&touch_points[idx], event,
742                   wpe_input_touch_event_type_up, touch_id, x, y);
743               src->last_touch = &touch_points[idx];
744               break;
745             }
746           }
747           ret = TRUE;
748         }
749         break;
750       case GST_NAVIGATION_EVENT_TOUCH_FRAME:
751         if (src->last_touch && src->touch_points->len > 0)
752         {
753           struct wpe_input_touch_event_raw *touch_points;
754           struct wpe_input_touch_event wpe_event;
755           guint idx;
756
757           wpe_event.touchpoints =
758               (struct wpe_input_touch_event_raw *) src->touch_points->data;
759           wpe_event.touchpoints_length = src->touch_points->len;
760           wpe_event.type = src->last_touch->type;
761           wpe_event.id = src->last_touch->id;
762           wpe_event.modifiers = _gst_modifiers_to_wpe (event);
763           wpe_event.time = src->last_touch->time;
764           src->view->dispatchTouchEvent (wpe_event);
765
766           touch_points = (struct wpe_input_touch_event_raw *)
767               src->touch_points->data;
768           for (idx = 0; idx < src->touch_points->len; idx++) {
769             if (touch_points[idx].type == wpe_input_touch_event_type_up ||
770                 touch_points[idx].type == wpe_input_touch_event_type_null) {
771               g_array_remove_index_fast(src->touch_points, idx);
772               idx--;
773             }
774           }
775           src->last_touch = NULL;
776           ret = TRUE;
777         }
778         break;
779       case GST_NAVIGATION_EVENT_TOUCH_CANCEL:
780         break;
781       default:
782         break;
783     }
784   }
785
786   if (!ret) {
787     ret =
788         GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SRC_CLASS, event, (base_src,
789             event), FALSE);
790   }
791   return ret;
792 }
793
794 static void
795 gst_wpe_video_src_init (GstWpeVideoSrc * src)
796 {
797   src->draw_background = DEFAULT_DRAW_BACKGROUND;
798   src->location = g_strdup (DEFAULT_LOCATION);
799
800   gst_base_src_set_live (GST_BASE_SRC_CAST (src), TRUE);
801
802   src->touch_points = g_array_sized_new (FALSE, FALSE, sizeof (void *), 10);
803   g_mutex_init (&src->lock);
804 }
805
806 static void
807 gst_wpe_video_src_finalize (GObject * object)
808 {
809   GstWpeVideoSrc *src = GST_WPE_VIDEO_SRC (object);
810
811   g_free (src->location);
812   g_clear_pointer (&src->bytes, g_bytes_unref);
813   g_array_unref (src->touch_points);
814   g_mutex_clear (&src->lock);
815
816   G_OBJECT_CLASS (parent_class)->finalize (object);
817 }
818
819 static void
820 gst_wpe_video_src_class_init (GstWpeVideoSrcClass * klass)
821 {
822   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
823   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
824   GstGLBaseSrcClass *gl_base_src_class = GST_GL_BASE_SRC_CLASS (klass);
825   GstBaseSrcClass *base_src_class = GST_BASE_SRC_CLASS (klass);
826   GstPadTemplate *tmpl;
827   GstCaps *doc_caps;
828
829   gobject_class->set_property = gst_wpe_video_src_set_property;
830   gobject_class->get_property = gst_wpe_video_src_get_property;
831   gobject_class->finalize = gst_wpe_video_src_finalize;
832
833   g_object_class_install_property (gobject_class, PROP_LOCATION,
834       g_param_spec_string ("location", "location",
835           "The URL to display",
836           DEFAULT_LOCATION, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
837   g_object_class_install_property (gobject_class, PROP_DRAW_BACKGROUND,
838       g_param_spec_boolean ("draw-background", "Draws the background",
839           "Whether to draw the WebView background", DEFAULT_DRAW_BACKGROUND,
840           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
841
842   gst_element_class_set_static_metadata (gstelement_class,
843       "WPE source", "Source/Video",
844       "Creates a video stream from a WPE browser",
845       "Philippe Normand <philn@igalia.com>, Žan Doberšek <zdobersek@igalia.com>");
846
847   tmpl = gst_static_pad_template_get (&src_factory);
848   gst_element_class_add_pad_template (gstelement_class, tmpl);
849
850   base_src_class->fixate = GST_DEBUG_FUNCPTR (gst_wpe_video_src_fixate);
851   base_src_class->create = GST_DEBUG_FUNCPTR (gst_wpe_video_src_create);
852   base_src_class->decide_allocation =
853       GST_DEBUG_FUNCPTR (gst_wpe_video_src_decide_allocation);
854   base_src_class->stop = GST_DEBUG_FUNCPTR (gst_wpe_video_src_stop);
855   base_src_class->event = GST_DEBUG_FUNCPTR (gst_wpe_video_src_event);
856
857   gl_base_src_class->supported_gl_api =
858       static_cast < GstGLAPI >
859       (GST_GL_API_OPENGL | GST_GL_API_OPENGL3 | GST_GL_API_GLES2);
860   gl_base_src_class->gl_start = GST_DEBUG_FUNCPTR (gst_wpe_video_src_gl_start);
861   gl_base_src_class->gl_stop = GST_DEBUG_FUNCPTR (gst_wpe_video_src_gl_stop);
862   gl_base_src_class->fill_gl_memory =
863       GST_DEBUG_FUNCPTR (gst_wpe_video_src_fill_memory);
864
865   doc_caps = gst_caps_from_string (WPE_VIDEO_SRC_DOC_CAPS);
866   gst_pad_template_set_documentation_caps (tmpl, doc_caps);
867   gst_clear_caps (&doc_caps);
868
869   /**
870    * GstWpeVideoSrc::configure-web-view:
871    * @src: the object which received the signal
872    * @webview: the webView
873    *
874    * Allow application to configure the webView settings.
875    */
876   gst_wpe_video_src_signals[SIGNAL_CONFIGURE_WEB_VIEW] =
877       g_signal_new ("configure-web-view", G_TYPE_FROM_CLASS (klass),
878       G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT);
879
880   /**
881    * GstWpeVideoSrc::load-bytes:
882    * @src: the object which received the signal
883    * @bytes: the GBytes data to load
884    *
885    * Load the specified bytes into the internal webView.
886    */
887   gst_wpe_video_src_signals[SIGNAL_LOAD_BYTES] =
888       g_signal_new_class_handler ("load-bytes", G_TYPE_FROM_CLASS (klass),
889       static_cast < GSignalFlags > (G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
890       G_CALLBACK (gst_wpe_video_src_load_bytes), NULL, NULL, NULL,
891       G_TYPE_NONE, 1, G_TYPE_BYTES);
892
893   /**
894    * GstWpeSrc::run-javascript:
895    * @src: the object which received the signal
896    * @script: the script to run
897    *
898    * Asynchronously run script in the context of the current page on the
899    * internal webView.
900    *
901    * Since: 1.22
902    */
903     gst_wpe_video_src_signals[SIGNAL_RUN_JAVASCRIPT] =
904       g_signal_new_class_handler ("run-javascript", G_TYPE_FROM_CLASS (klass),
905       static_cast < GSignalFlags > (G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
906       G_CALLBACK (gst_wpe_video_src_run_javascript), NULL, NULL, NULL,
907       G_TYPE_NONE, 1, G_TYPE_STRING);
908 }