blob: dca216b301d460190812e4a6e52a1f4c903b045c [file] [log] [blame]
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001/*
Stepan Salenikovichbe87d2c2016-01-25 14:14:34 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05003 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050018 */
19
20#include "video_widget.h"
21
Julien Grossholtz58cfc152015-10-22 15:43:43 -040022#include <glib/gi18n.h>
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050023#include <clutter/clutter.h>
24#include <clutter-gtk/clutter-gtk.h>
25#include <video/renderer.h>
Stepan Salenikovich033dc832015-03-23 15:56:47 -040026#include <video/sourcemodel.h>
Julien Grossholtza0d4f102015-10-22 14:24:17 -040027#include <media/video.h>
Stepan Salenikovich50c989b2015-03-21 18:32:46 -040028#include <video/devicemodel.h>
29#include <QtCore/QUrl>
30#include "../defines.h"
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -040031#include <stdlib.h>
Guillaume Roguez24834e02015-04-09 12:45:40 -040032#include <atomic>
Stepan Salenikovichaea6c042015-04-29 15:09:16 -040033#include <mutex>
Julien Grossholtza0d4f102015-10-22 14:24:17 -040034#include <call.h>
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -040035#include "xrectsel.h"
Olivier Gregoire66e4df72016-06-17 18:39:05 -040036#include <smartinfohub.h>
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050037
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040038static constexpr int VIDEO_LOCAL_SIZE = 150;
39static constexpr int VIDEO_LOCAL_OPACITY_DEFAULT = 255; /* out of 255 */
Julien Grossholtza0d4f102015-10-22 14:24:17 -040040static constexpr const char* JOIN_CALL_KEY = "call_data";
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040041
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040042/* check video frame queues at this rate;
43 * use 30 ms (about 30 fps) since we don't expect to
44 * receive video frames faster than that */
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040045static constexpr int FRAME_RATE_PERIOD = 30;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040046
Nicolas Jager4bb29542016-05-12 10:21:47 -040047enum SnapshotStatus {
48 NOTHING,
49 HAS_TO_TAKE_ONE,
50 HAS_A_NEW_ONE
51};
52
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050053struct _VideoWidgetClass {
Stepan Salenikovich36ef3942015-11-05 18:26:57 -050054 GtkClutterEmbedClass parent_class;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050055};
56
57struct _VideoWidget {
Stepan Salenikovich36ef3942015-11-05 18:26:57 -050058 GtkClutterEmbed parent;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050059};
60
61typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
62
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040063typedef struct _VideoWidgetRenderer VideoWidgetRenderer;
64
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050065struct _VideoWidgetPrivate {
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050066 ClutterActor *video_container;
67
68 /* remote peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040069 VideoWidgetRenderer *remote;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050070
71 /* local peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040072 VideoWidgetRenderer *local;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040073
74 guint frame_timeout_source;
Stepan Salenikovich0f693232015-04-22 10:45:08 -040075
76 /* new renderers should be put into the queue for processing by a g_timeout
77 * function whose id should be saved into renderer_timeout_source;
78 * this way when the VideoWidget object is destroyed, we do not try
79 * to process any new renderers by stoping the g_timeout function.
80 */
81 guint renderer_timeout_source;
82 GAsyncQueue *new_renderer_queue;
Stepan Salenikovich39df7372016-10-11 16:07:40 -040083
84 GtkWidget *popup_menu;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040085};
86
87struct _VideoWidgetRenderer {
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -040088 VideoRendererType type;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040089 ClutterActor *actor;
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -040090 ClutterAction *drag_action;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040091 Video::Renderer *renderer;
Nicolas Jager4bb29542016-05-12 10:21:47 -040092 GdkPixbuf *snapshot;
Stepan Salenikovichaea6c042015-04-29 15:09:16 -040093 std::mutex run_mutex;
94 bool running;
Nicolas Jager4bb29542016-05-12 10:21:47 -040095 SnapshotStatus snapshot_status;
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -040096
97 /* show_black_frame is used to request the actor to render a black image;
Stepan Salenikovichd6a4ef32015-11-12 10:59:02 -050098 * this will take over 'running', ie: a black frame will be rendered even if
99 * the Video::Renderer is not running;
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400100 * this will be set back to false once the black frame is rendered
101 */
102 std::atomic_bool show_black_frame;
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400103 std::atomic_bool pause_rendering;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400104 QMetaObject::Connection render_stop;
105 QMetaObject::Connection render_start;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500106};
107
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500108G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_CLUTTER_TYPE_EMBED);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500109
110#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
111
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400112/* static prototypes */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400113static gboolean check_frame_queue (VideoWidget *);
114static void renderer_stop (VideoWidgetRenderer *);
115static void renderer_start (VideoWidgetRenderer *);
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400116static gboolean check_renderer_queue (VideoWidget *);
117static void free_video_widget_renderer (VideoWidgetRenderer *);
118static void video_widget_add_renderer (VideoWidget *, VideoWidgetRenderer *);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400119
Nicolas Jager4bb29542016-05-12 10:21:47 -0400120/* signals */
121enum {
122 SNAPSHOT_SIGNAL,
123 LAST_SIGNAL
124};
125
126static guint video_widget_signals[LAST_SIGNAL] = { 0 };
127
128
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500129/*
130 * video_widget_dispose()
131 *
132 * The dispose function for the video_widget class.
133 */
134static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400135video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500136{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400137 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500138 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
139
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400140 /* dispose may be called multiple times, make sure
141 * not to call g_source_remove more than once */
142 if (priv->frame_timeout_source) {
143 g_source_remove(priv->frame_timeout_source);
144 priv->frame_timeout_source = 0;
145 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400146
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400147 if (priv->renderer_timeout_source) {
148 g_source_remove(priv->renderer_timeout_source);
149 priv->renderer_timeout_source = 0;
150 }
151
152 if (priv->new_renderer_queue) {
153 g_async_queue_unref(priv->new_renderer_queue);
154 priv->new_renderer_queue = NULL;
155 }
156
Stepan Salenikovich39df7372016-10-11 16:07:40 -0400157 gtk_widget_destroy(priv->popup_menu);
158
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400159 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500160}
161
162
163/*
164 * video_widget_finalize()
165 *
166 * The finalize function for the video_widget class.
167 */
168static void
169video_widget_finalize(GObject *object)
170{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400171 VideoWidget *self = VIDEO_WIDGET(object);
172 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
173
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400174 free_video_widget_renderer(priv->local);
175 free_video_widget_renderer(priv->remote);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400176
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500177 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
178}
179
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500180/*
181 * video_widget_class_init()
182 *
183 * This function init the video_widget_class.
184 */
185static void
186video_widget_class_init(VideoWidgetClass *klass)
187{
188 GObjectClass *object_class = G_OBJECT_CLASS(klass);
189
190 /* override method */
191 object_class->dispose = video_widget_dispose;
192 object_class->finalize = video_widget_finalize;
Nicolas Jager4bb29542016-05-12 10:21:47 -0400193
194 /* add snapshot signal */
195 video_widget_signals[SNAPSHOT_SIGNAL] = g_signal_new("snapshot-taken",
196 G_TYPE_FROM_CLASS(klass),
197 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
198 0,
199 nullptr,
200 nullptr,
201 g_cclosure_marshal_VOID__VOID,
202 G_TYPE_NONE, 0);
203
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500204}
205
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400206static void
207on_allocation_changed(ClutterActor *video_area, G_GNUC_UNUSED GParamSpec *pspec, VideoWidget *self)
208{
209 g_return_if_fail(IS_VIDEO_WIDGET(self));
210 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
211
212 auto actor = priv->local->actor;
213 auto drag_action = priv->local->drag_action;
214
215 ClutterActorBox actor_box;
216 clutter_actor_get_allocation_box(actor, &actor_box);
217 gfloat actor_w = clutter_actor_box_get_width(&actor_box);
218 gfloat actor_h = clutter_actor_box_get_height(&actor_box);
219
220 ClutterActorBox area_box;
221 clutter_actor_get_allocation_box(video_area, &area_box);
222 gfloat area_w = clutter_actor_box_get_width(&area_box);
223 gfloat area_h = clutter_actor_box_get_height(&area_box);
224
225 /* make sure drag area stays within the bounds of the stage */
226 ClutterRect *rect = clutter_rect_init (
227 clutter_rect_alloc(),
228 0, 0,
229 area_w - actor_w,
230 area_h - actor_h);
231 clutter_drag_action_set_drag_area(CLUTTER_DRAG_ACTION(drag_action), rect);
232 clutter_rect_free(rect);
233}
234
235static void
236on_drag_begin(G_GNUC_UNUSED ClutterDragAction *action,
237 ClutterActor *actor,
238 G_GNUC_UNUSED gfloat event_x,
239 G_GNUC_UNUSED gfloat event_y,
240 G_GNUC_UNUSED ClutterModifierType modifiers,
241 G_GNUC_UNUSED gpointer user_data)
242{
243 /* clear the align constraint when starting to move the preview, otherwise
244 * it won't move; save and set its position, to what it was before the
245 * constraint was cleared, or else it might jump around */
246 gfloat actor_x, actor_y;
247 clutter_actor_get_position(actor, &actor_x, &actor_y);
248 clutter_actor_clear_constraints(actor);
249 clutter_actor_set_position(actor, actor_x, actor_y);
250}
251
252static void
253on_drag_end(G_GNUC_UNUSED ClutterDragAction *action,
254 ClutterActor *actor,
255 G_GNUC_UNUSED gfloat event_x,
256 G_GNUC_UNUSED gfloat event_y,
257 G_GNUC_UNUSED ClutterModifierType modifiers,
258 ClutterActor *video_area)
259{
260 ClutterActorBox area_box;
261 clutter_actor_get_allocation_box(video_area, &area_box);
262 gfloat area_w = clutter_actor_box_get_width(&area_box);
263 gfloat area_h = clutter_actor_box_get_height(&area_box);
264
265 gfloat actor_x, actor_y;
266 clutter_actor_get_position(actor, &actor_x, &actor_y);
267 gfloat actor_w, actor_h;
268 clutter_actor_get_size(actor, &actor_w, &actor_h);
269
270 area_w -= actor_w;
271 area_h -= actor_h;
272
273 /* add new constraints to make sure the preview stays in about the same location
274 * relative to the rest of the video when resizing */
275 ClutterConstraint *constraint_x = clutter_align_constraint_new(video_area,
276 CLUTTER_ALIGN_X_AXIS, actor_x/area_w);
277 clutter_actor_add_constraint(actor, constraint_x);
278
279 ClutterConstraint *constraint_y = clutter_align_constraint_new(video_area,
280 CLUTTER_ALIGN_Y_AXIS, actor_y/area_h);
281 clutter_actor_add_constraint(actor, constraint_y);
282}
283
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500284
285/*
286 * video_widget_init()
287 *
288 * This function init the video_widget.
289 * - init clutter
290 * - init all the widget members
291 */
292static void
293video_widget_init(VideoWidget *self)
294{
295 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
296
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500297 auto stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(self));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500298
299 /* layout manager is used to arrange children in space, here we ask clutter
300 * to align children to fill the space when resizing the window */
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500301 clutter_actor_set_layout_manager(stage,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500302 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
303
304 /* add a scene container where we can add and remove our actors */
305 priv->video_container = clutter_actor_new();
306 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500307 clutter_actor_add_child(stage, priv->video_container);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500308
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400309 /* init the remote and local structs */
310 priv->remote = g_new0(VideoWidgetRenderer, 1);
311 priv->local = g_new0(VideoWidgetRenderer, 1);
312
313 /* arrange remote actors */
314 priv->remote->actor = clutter_actor_new();
315 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
316 /* the remote camera must always fill the container size */
317 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
318 CLUTTER_BIND_SIZE, 0);
319 clutter_actor_add_constraint(priv->remote->actor, constraint);
320
321 /* arrange local actor */
322 priv->local->actor = clutter_actor_new();
323 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
324 /* set size to square, but it will stay the aspect ratio when the image is rendered */
325 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400326 /* set position constraint to right cornder;
327 * this constraint will be removed once the user tries to move the position
328 * of the action */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400329 constraint = clutter_align_constraint_new(priv->video_container,
330 CLUTTER_ALIGN_BOTH, 0.99);
331 clutter_actor_add_constraint(priv->local->actor, constraint);
332 clutter_actor_set_opacity(priv->local->actor,
333 VIDEO_LOCAL_OPACITY_DEFAULT);
334
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400335 /* add ability for actor to be moved */
336 clutter_actor_set_reactive(priv->local->actor, TRUE);
337 priv->local->drag_action = clutter_drag_action_new();
338 clutter_actor_add_action(priv->local->actor, priv->local->drag_action);
339
340 g_signal_connect(priv->local->drag_action, "drag-begin", G_CALLBACK(on_drag_begin), NULL);
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500341 g_signal_connect_after(priv->local->drag_action, "drag-end", G_CALLBACK(on_drag_end), stage);
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400342
343 /* make sure the actor stays within the bounds of the stage */
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500344 g_signal_connect(stage, "notify::allocation", G_CALLBACK(on_allocation_changed), self);
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400345
Stepan Salenikovich8934e842015-04-20 15:16:13 -0400346 /* Init the timeout source which will check the for new frames.
347 * The priority must be lower than GTK drawing events
348 * (G_PRIORITY_HIGH_IDLE + 20) so that this timeout source doesn't choke
349 * the main loop on slower machines.
350 */
351 priv->frame_timeout_source = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
352 FRAME_RATE_PERIOD,
353 (GSourceFunc)check_frame_queue,
354 self,
355 NULL);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400356
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400357 /* init new renderer queue */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400358 priv->new_renderer_queue = g_async_queue_new_full((GDestroyNotify)free_video_widget_renderer);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400359 /* check new render every 30 ms (30ms is "fast enough");
360 * we don't use an idle function so it doesn't consume cpu needlessly */
361 priv->renderer_timeout_source= g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
362 30,
363 (GSourceFunc)check_renderer_queue,
364 self,
365 NULL);
366
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500367
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400368 /* drag & drop files as video sources */
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400369 gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
370 gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
Stepan Salenikovich39df7372016-10-11 16:07:40 -0400371
372 priv->popup_menu = gtk_menu_new();
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400373}
374
375/*
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400376 * video_widget_on_drag_data_received()
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400377 *
378 * Handle dragged data in the video widget window.
379 * Dropping an image causes the client to switch the video input to that image.
380 */
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400381void video_widget_on_drag_data_received(G_GNUC_UNUSED GtkWidget *self,
382 G_GNUC_UNUSED GdkDragContext *context,
383 G_GNUC_UNUSED gint x,
384 G_GNUC_UNUSED gint y,
385 GtkSelectionData *selection_data,
386 G_GNUC_UNUSED guint info,
387 G_GNUC_UNUSED guint32 time,
388 Call *call)
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400389{
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400390 g_return_if_fail(call);
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400391 gchar **uris = gtk_selection_data_get_uris(selection_data);
392
393 /* only play the first selection */
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400394 if (uris && *uris){
395 if (auto out_media = call->firstMedia<Media::Video>(Media::Media::Direction::OUT))
396 out_media->sourceModel()->setFile(QUrl(*uris));
397 }
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400398
399 g_strfreev(uris);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500400}
401
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400402static void
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400403switch_video_input(GtkWidget *widget, Video::Device *device)
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400404{
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400405 gpointer data = g_object_get_data(G_OBJECT(widget),JOIN_CALL_KEY );
406 g_return_if_fail(data);
407 Call *call = (Call*)data;
408
409 if (auto out_media = call->firstMedia<Media::Video>(Media::Media::Direction::OUT))
410 out_media->sourceModel()->switchTo(device);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400411}
412
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400413static void
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400414switch_video_input_screen(G_GNUC_UNUSED GtkWidget *item, Call* call)
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400415{
416 unsigned x, y;
417 unsigned width, height;
418
419 /* try to get the dispaly or default to 0 */
420 QString display_env{getenv("DISPLAY")};
421 int display = 0;
422
423 if (!display_env.isEmpty()) {
424 auto list = display_env.split(":", QString::SkipEmptyParts);
425 /* should only be one display, so get the first one */
426 if (list.size() > 0) {
427 display = list.at(0).toInt();
428 g_debug("sharing screen from DISPLAY %d", display);
429 }
430 }
431
432 x = y = width = height = 0;
433
434 xrectsel(&x, &y, &width, &height);
435
436 if (!width || !height) {
437 x = y = 0;
438 width = gdk_screen_width();
439 height = gdk_screen_height();
440 }
441
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400442 if (auto out_media = call->firstMedia<Media::Video>(Media::Media::Direction::OUT))
443 out_media->sourceModel()->setDisplay(display, QRect(x,y,width,height));
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400444}
445
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400446static void
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400447switch_video_input_file(GtkWidget *item, GtkWidget *parent)
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400448{
449 if (parent && GTK_IS_WIDGET(parent)) {
450 /* get parent window */
451 parent = gtk_widget_get_toplevel(GTK_WIDGET(parent));
452 }
453
454 gchar *uri = NULL;
455 GtkWidget *dialog = gtk_file_chooser_dialog_new(
456 "Choose File",
457 GTK_WINDOW(parent),
458 GTK_FILE_CHOOSER_ACTION_OPEN,
459 "_Cancel", GTK_RESPONSE_CANCEL,
460 "_Open", GTK_RESPONSE_ACCEPT,
461 NULL);
462
463 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400464 Call *call;
465 gpointer data = g_object_get_data(G_OBJECT(item),JOIN_CALL_KEY );
466 g_return_if_fail(data);
467 call = (Call*)data;
468
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400469 uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400470
471 if (auto out_media = call->firstMedia<Media::Video>(Media::Media::Direction::OUT))
472 out_media->sourceModel()->setFile(QUrl(uri));
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400473 }
474
475 gtk_widget_destroy(dialog);
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400476 g_free(uri);
477}
478
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400479/*
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400480 * video_widget_on_button_press_in_screen_event()
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400481 *
482 * Handle button event in the video screen.
483 */
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400484gboolean
Stepan Salenikovich39df7372016-10-11 16:07:40 -0400485video_widget_on_button_press_in_screen_event(VideoWidget *self, GdkEventButton *event, Call* call)
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400486{
487 /* check for right click */
488 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
489 return FALSE;
490
Stepan Salenikovich39df7372016-10-11 16:07:40 -0400491 /* update menu with available video sources */
492 auto priv = VIDEO_WIDGET_GET_PRIVATE(self);
493 auto menu = priv->popup_menu;
494
495 gtk_container_forall(GTK_CONTAINER(menu), (GtkCallback)gtk_widget_destroy, nullptr);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400496
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400497 Video::SourceModel *sourcemodel = nullptr;
Stepan Salenikovich39df7372016-10-11 16:07:40 -0400498 int active = -1;
499 if (auto out_media = call->firstMedia<Media::Video>(Media::Media::Direction::OUT)) {
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400500 sourcemodel = out_media->sourceModel();
Stepan Salenikovich39df7372016-10-11 16:07:40 -0400501 active = sourcemodel->activeIndex();
502 }
503 /* if sourcemodel is null then we have no outgoing video */
Stepan Salenikovichcb1c2952015-08-13 14:28:20 -0400504
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400505 /* list available devices and check off the active device */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400506 auto device_list = Video::DeviceModel::instance().devices();
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400507
508 for( auto device: device_list) {
509 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(device->name().toLocal8Bit().constData());
510 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
Stepan Salenikovich39df7372016-10-11 16:07:40 -0400511 if (sourcemodel) {
512 auto device_idx = sourcemodel->getDeviceIndex(device);
513 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), device_idx == active);
514 g_object_set_data(G_OBJECT(item), JOIN_CALL_KEY,call);
515 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input), device);
516 } else {
517 gtk_widget_set_sensitive(item, FALSE);
518 }
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400519 }
520
Stepan Salenikovichbb382632015-03-26 12:40:46 -0400521 /* add separator */
522 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
523
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400524 /* add screen area as an input */
Julien Grossholtz58cfc152015-10-22 15:43:43 -0400525 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(_("Share screen area"));
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400526 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
Stepan Salenikovich39df7372016-10-11 16:07:40 -0400527 if (sourcemodel) {
528 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), Video::SourceModel::ExtendedDeviceList::SCREEN == active);
529 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_screen), call);
530 } else {
531 gtk_widget_set_sensitive(item, FALSE);
532 }
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400533
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400534 /* add file as an input */
Julien Grossholtz58cfc152015-10-22 15:43:43 -0400535 item = gtk_check_menu_item_new_with_mnemonic(_("Share file"));
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400536 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
Stepan Salenikovich39df7372016-10-11 16:07:40 -0400537 if (sourcemodel) {
538 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), Video::SourceModel::ExtendedDeviceList::FILE == active);
539 g_object_set_data(G_OBJECT(item), JOIN_CALL_KEY, call);
540 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_file), self);
541 } else {
542 gtk_widget_set_sensitive(item, FALSE);
543 }
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400544
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400545 /* add separator */
546 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
547
548 /* add SmartInfo */
549 item = gtk_check_menu_item_new_with_mnemonic(_("Show advanced information"));
550 gtk_actionable_set_action_name(GTK_ACTIONABLE(item), "app.display-smartinfo");
551 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
552 gtk_widget_insert_action_group(menu, "app", G_ACTION_GROUP(g_application_get_default()));
553
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400554 /* show menu */
555 gtk_widget_show_all(menu);
556 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
557
558 return TRUE; /* event has been fully handled */
559}
560
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500561static void
Stepan Salenikovich6fa22052017-04-14 15:32:31 -0400562free_pixels(guchar *pixels, gpointer)
563{
564 g_free(pixels);
565}
566
567static void
Guillaume Roguez24834e02015-04-09 12:45:40 -0400568clutter_render_image(VideoWidgetRenderer* wg_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500569{
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400570 auto actor = wg_renderer->actor;
571 g_return_if_fail(CLUTTER_IS_ACTOR(actor));
572
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400573 if (wg_renderer->pause_rendering)
574 return;
575
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400576 if (wg_renderer->show_black_frame) {
577 /* render a black frame set the bool back to false, this is likely done
578 * when the renderer is stopped so we ignore whether or not it is running
579 */
Stepan Salenikovich6e7b0922015-05-05 17:51:30 -0400580 if (auto image_old = clutter_actor_get_content(actor)) {
581 gfloat width;
582 gfloat height;
583 if (clutter_content_get_preferred_size(image_old, &width, &height)) {
584 /* NOTE: this is a workaround for #72531, a crash which occurs
585 * in cogl < 1.18. We allocate a black frame of the same size
586 * as the previous image, instead of simply setting an empty or
587 * a NULL ClutterImage.
588 */
589 auto image_empty = clutter_image_new();
590 if (auto empty_data = (guint8 *)g_try_malloc0((gsize)width * height * 4)) {
591 GError* error = NULL;
592 clutter_image_set_data(
593 CLUTTER_IMAGE(image_empty),
594 empty_data,
595 COGL_PIXEL_FORMAT_BGRA_8888,
596 (guint)width,
597 (guint)height,
598 (guint)width*4,
599 &error);
600 if (error) {
601 g_warning("error rendering empty image to clutter: %s", error->message);
602 g_clear_error(&error);
603 g_object_unref(image_empty);
604 return;
605 }
606 clutter_actor_set_content(actor, image_empty);
607 g_object_unref(image_empty);
608 g_free(empty_data);
609 } else {
610 clutter_actor_set_content(actor, NULL);
611 }
612 } else {
613 clutter_actor_set_content(actor, NULL);
614 }
615 }
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400616 wg_renderer->show_black_frame = false;
617 return;
618 }
619
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400620 ClutterContent *image_new = nullptr;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500621
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400622 {
623 /* the following must be done under lock in case a 'stopped' signal is
624 * received during rendering; otherwise the mem could become invalid */
625 std::lock_guard<std::mutex> lock(wg_renderer->run_mutex);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500626
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400627 if (!wg_renderer->running)
628 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400629
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400630 auto renderer = wg_renderer->renderer;
631 if (renderer == nullptr)
632 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400633
Guillaume Rogueza8860ea2015-10-13 18:04:39 -0400634 auto frame_ptr = renderer->currentFrame();
635 auto frame_data = frame_ptr.ptr;
Stepan Salenikovichd6a4ef32015-11-12 10:59:02 -0500636 if (!frame_data)
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400637 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400638
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400639 image_new = clutter_image_new();
640 g_return_if_fail(image_new);
641
642 const auto& res = renderer->size();
Nicolas Jager4bb29542016-05-12 10:21:47 -0400643 gint BPP = 4; /* BGRA */
644 gint ROW_STRIDE = BPP * res.width();
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400645
646 GError *error = nullptr;
647 clutter_image_set_data(
648 CLUTTER_IMAGE(image_new),
Guillaume Rogueza8860ea2015-10-13 18:04:39 -0400649 frame_data,
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400650 COGL_PIXEL_FORMAT_BGRA_8888,
651 res.width(),
652 res.height(),
653 ROW_STRIDE,
654 &error);
655 if (error) {
656 g_warning("error rendering image to clutter: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400657 g_clear_error(&error);
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400658 g_object_unref (image_new);
659 return;
660 }
Nicolas Jager4bb29542016-05-12 10:21:47 -0400661
662 if (wg_renderer->snapshot_status == HAS_TO_TAKE_ONE) {
Stepan Salenikovich6fa22052017-04-14 15:32:31 -0400663 guchar *pixbuf_frame_data = (guchar *)g_malloc(res.width() * res.height() * 3);
Nicolas Jager4bb29542016-05-12 10:21:47 -0400664
665 BPP = 3; /* RGB */
666 gint ROW_STRIDE = BPP * res.width();
667
668 /* conversion from BGRA to RGB */
669 for(int i = 0, j = 0 ; i < res.width() * res.height() * 4 ; i += 4, j += 3 ) {
670 pixbuf_frame_data[j + 0] = frame_data[i + 2];
671 pixbuf_frame_data[j + 1] = frame_data[i + 1];
672 pixbuf_frame_data[j + 2] = frame_data[i + 0];
673 }
674
675 if (wg_renderer->snapshot) {
676 g_object_unref(wg_renderer->snapshot);
677 wg_renderer->snapshot = nullptr;
678 }
679
Stepan Salenikovich6fa22052017-04-14 15:32:31 -0400680 wg_renderer->snapshot = gdk_pixbuf_new_from_data(pixbuf_frame_data,
Nicolas Jager4bb29542016-05-12 10:21:47 -0400681 GDK_COLORSPACE_RGB, FALSE, 8,
682 res.width(), res.height(),
Stepan Salenikovich6fa22052017-04-14 15:32:31 -0400683 ROW_STRIDE, free_pixels, NULL);
Nicolas Jager4bb29542016-05-12 10:21:47 -0400684
685 wg_renderer->snapshot_status = HAS_A_NEW_ONE;
686
687 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500688 }
689
Guillaume Roguez24834e02015-04-09 12:45:40 -0400690 clutter_actor_set_content(actor, image_new);
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400691 g_object_unref (image_new);
Guillaume Roguez24834e02015-04-09 12:45:40 -0400692
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400693 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
694 * that the aspect ratio is correct
695 */
Guillaume Roguez24834e02015-04-09 12:45:40 -0400696 clutter_actor_set_content_gravity(actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400697}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500698
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400699static gboolean
700check_frame_queue(VideoWidget *self)
701{
702 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
703 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
704
Guillaume Roguez24834e02015-04-09 12:45:40 -0400705 /* display renderer's frames */
706 clutter_render_image(priv->local);
707 clutter_render_image(priv->remote);
Nicolas Jager4bb29542016-05-12 10:21:47 -0400708 if (priv->remote->snapshot_status == HAS_A_NEW_ONE) {
709 priv->remote->snapshot_status = NOTHING;
710 g_signal_emit(G_OBJECT(self), video_widget_signals[SNAPSHOT_SIGNAL], 0);
711 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400712
713 return TRUE; /* keep going */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500714}
715
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400716static void
717renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500718{
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400719 {
720 /* must do this under lock, in case the rendering is taking place when
721 * this signal is received */
722 std::lock_guard<std::mutex> lock(renderer->run_mutex);
723 renderer->running = false;
724 }
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400725 /* ask to show a black frame */
726 renderer->show_black_frame = true;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500727}
728
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400729static void
730renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500731{
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400732 {
733 std::lock_guard<std::mutex> lock(renderer->run_mutex);
734 renderer->running = true;
735 }
Stepan Salenikovichd6a4ef32015-11-12 10:59:02 -0500736 renderer->show_black_frame = false;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500737}
738
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400739static void
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400740free_video_widget_renderer(VideoWidgetRenderer *renderer)
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400741{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400742 QObject::disconnect(renderer->render_stop);
743 QObject::disconnect(renderer->render_start);
Nicolas Jager4bb29542016-05-12 10:21:47 -0400744 if (renderer->snapshot)
745 g_object_unref(renderer->snapshot);
Stepan Salenikovichf762d382016-07-15 11:20:37 -0400746 g_free(renderer);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400747}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500748
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400749static void
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400750video_widget_add_renderer(VideoWidget *self, VideoWidgetRenderer *new_video_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500751{
752 g_return_if_fail(IS_VIDEO_WIDGET(self));
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400753 g_return_if_fail(new_video_renderer);
754 g_return_if_fail(new_video_renderer->renderer);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500755
756 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
757
758 /* update the renderer */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400759 switch(new_video_renderer->type) {
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400760 case VIDEO_RENDERER_REMOTE:
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400761 /* swap the remote renderer */
762 new_video_renderer->actor = priv->remote->actor;
763 free_video_widget_renderer(priv->remote);
764 priv->remote = new_video_renderer;
765 /* reset the content gravity so that the aspect ratio gets properly
766 * reset if it chagnes */
767 clutter_actor_set_content_gravity(priv->remote->actor,
768 CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400769 break;
770 case VIDEO_RENDERER_LOCAL:
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400771 /* swap the remote renderer */
772 new_video_renderer->actor = priv->local->actor;
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400773 new_video_renderer->drag_action = priv->local->drag_action;
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400774 free_video_widget_renderer(priv->local);
775 priv->local = new_video_renderer;
776 /* reset the content gravity so that the aspect ratio gets properly
777 * reset if it chagnes */
778 clutter_actor_set_content_gravity(priv->local->actor,
779 CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400780 break;
781 case VIDEO_RENDERER_COUNT:
782 break;
783 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500784}
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400785
786static gboolean
787check_renderer_queue(VideoWidget *self)
788{
789 g_return_val_if_fail(IS_VIDEO_WIDGET(self), G_SOURCE_REMOVE);
790 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
791
792 /* get all the renderers in the queue */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400793 VideoWidgetRenderer *new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400794 while (new_video_renderer) {
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400795 video_widget_add_renderer(self, new_video_renderer);
796 new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400797 }
798
799 return G_SOURCE_CONTINUE;
800}
801
802/*
803 * video_widget_new()
804 *
805 * The function use to create a new video_widget
806 */
807GtkWidget*
808video_widget_new(void)
809{
810 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
811 return self;
812}
813
814/**
815 * video_widget_push_new_renderer()
816 *
817 * This function is used add a new Video::Renderer to the VideoWidget in a
818 * thread-safe manner.
819 */
820void
821video_widget_push_new_renderer(VideoWidget *self, Video::Renderer *renderer, VideoRendererType type)
822{
823 g_return_if_fail(IS_VIDEO_WIDGET(self));
824 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
825
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400826 /* if the renderer is nullptr, there is nothing to be done */
827 if (!renderer) return;
828
829 VideoWidgetRenderer *new_video_renderer = g_new0(VideoWidgetRenderer, 1);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400830 new_video_renderer->renderer = renderer;
831 new_video_renderer->type = type;
832
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400833 if (new_video_renderer->renderer->isRendering())
834 renderer_start(new_video_renderer);
835
836 new_video_renderer->render_stop = QObject::connect(
837 new_video_renderer->renderer,
838 &Video::Renderer::stopped,
839 [=]() {
840 renderer_stop(new_video_renderer);
841 }
842 );
843
844 new_video_renderer->render_start = QObject::connect(
845 new_video_renderer->renderer,
846 &Video::Renderer::started,
847 [=]() {
848 renderer_start(new_video_renderer);
849 }
850 );
851
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400852 g_async_queue_push(priv->new_renderer_queue, new_video_renderer);
853}
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400854
855void
856video_widget_pause_rendering(VideoWidget *self, gboolean pause)
857{
858 g_return_if_fail(IS_VIDEO_WIDGET(self));
859 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
860
861 priv->local->pause_rendering = pause;
862 priv->remote->pause_rendering = pause;
863}
Nicolas Jager4bb29542016-05-12 10:21:47 -0400864
865void
866video_widget_take_snapshot(VideoWidget *self)
867{
868 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
869
870 priv->remote->snapshot_status = HAS_TO_TAKE_ONE;
871}
872
873GdkPixbuf*
874video_widget_get_snapshot(VideoWidget *self)
875{
876 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
877
878 return priv->remote->snapshot;
879}