blob: f5a3e278d2a6f8e3f041df4e0067635e60987741 [file] [log] [blame]
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001/*
2 * Copyright (C) 2004-2015 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.
18 *
19 * Additional permission under GNU GPL version 3 section 7:
20 *
21 * If you modify this program, or any covered work, by linking or
22 * combining it with the OpenSSL project's OpenSSL library (or a
23 * modified version of that library), containing parts covered by the
24 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
25 * grants you additional permission to convey the resulting work.
26 * Corresponding Source for a non-source form of such a combination
27 * shall include the source code for the parts of OpenSSL used as well
28 * as that of the covered work.
29 */
30
31#include "video_widget.h"
32
33#include <clutter/clutter.h>
34#include <clutter-gtk/clutter-gtk.h>
35#include <video/renderer.h>
Stepan Salenikovich033dc832015-03-23 15:56:47 -040036#include <video/sourcemodel.h>
Stepan Salenikovich50c989b2015-03-21 18:32:46 -040037#include <video/devicemodel.h>
38#include <QtCore/QUrl>
39#include "../defines.h"
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -040040#include <stdlib.h>
Guillaume Roguez24834e02015-04-09 12:45:40 -040041#include <atomic>
Stepan Salenikovichaea6c042015-04-29 15:09:16 -040042#include <mutex>
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -040043#include "xrectsel.h"
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050044
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040045#define VIDEO_LOCAL_SIZE 150
46#define VIDEO_LOCAL_OPACITY_DEFAULT 150 /* out of 255 */
47
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040048/* check video frame queues at this rate;
49 * use 30 ms (about 30 fps) since we don't expect to
50 * receive video frames faster than that */
51#define FRAME_RATE_PERIOD 30
52
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050053struct _VideoWidgetClass {
54 GtkBinClass parent_class;
55};
56
57struct _VideoWidget {
58 GtkBin parent;
59};
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 {
66 GtkWidget *clutter_widget;
67 ClutterActor *stage;
68 ClutterActor *video_container;
69
70 /* remote peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040071 VideoWidgetRenderer *remote;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050072
73 /* local peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040074 VideoWidgetRenderer *local;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040075
76 guint frame_timeout_source;
Stepan Salenikovich0f693232015-04-22 10:45:08 -040077
78 /* new renderers should be put into the queue for processing by a g_timeout
79 * function whose id should be saved into renderer_timeout_source;
80 * this way when the VideoWidget object is destroyed, we do not try
81 * to process any new renderers by stoping the g_timeout function.
82 */
83 guint renderer_timeout_source;
84 GAsyncQueue *new_renderer_queue;
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;
90 Video::Renderer *renderer;
Stepan Salenikovichaea6c042015-04-29 15:09:16 -040091 std::mutex run_mutex;
92 bool running;
Guillaume Roguez24834e02015-04-09 12:45:40 -040093 std::atomic_bool dirty;
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -040094
95 /* show_black_frame is used to request the actor to render a black image;
96 * this will take over 'running' and 'dirty', ie: a black frame will be
97 * rendered even if the Video::Renderer is not running, or has a frame available.
98 * this will be set back to false once the black frame is rendered
99 */
100 std::atomic_bool show_black_frame;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400101 QMetaObject::Connection frame_update;
102 QMetaObject::Connection render_stop;
103 QMetaObject::Connection render_start;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500104};
105
106G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN);
107
108#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
109
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400110/* static prototypes */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400111static gboolean check_frame_queue (VideoWidget *);
112static void renderer_stop (VideoWidgetRenderer *);
113static void renderer_start (VideoWidgetRenderer *);
114static void on_drag_data_received (GtkWidget *, GdkDragContext *, gint, gint, GtkSelectionData *, guint, guint32, gpointer);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400115static gboolean on_button_press_in_screen_event(GtkWidget *, GdkEventButton *, gpointer);
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
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500120/*
121 * video_widget_dispose()
122 *
123 * The dispose function for the video_widget class.
124 */
125static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400126video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500127{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400128 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500129 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
130
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400131 /* dispose may be called multiple times, make sure
132 * not to call g_source_remove more than once */
133 if (priv->frame_timeout_source) {
134 g_source_remove(priv->frame_timeout_source);
135 priv->frame_timeout_source = 0;
136 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400137
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400138 if (priv->renderer_timeout_source) {
139 g_source_remove(priv->renderer_timeout_source);
140 priv->renderer_timeout_source = 0;
141 }
142
143 if (priv->new_renderer_queue) {
144 g_async_queue_unref(priv->new_renderer_queue);
145 priv->new_renderer_queue = NULL;
146 }
147
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400148 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500149}
150
151
152/*
153 * video_widget_finalize()
154 *
155 * The finalize function for the video_widget class.
156 */
157static void
158video_widget_finalize(GObject *object)
159{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400160 VideoWidget *self = VIDEO_WIDGET(object);
161 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
162
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400163 free_video_widget_renderer(priv->local);
164 free_video_widget_renderer(priv->remote);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400165
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500166 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
167}
168
169
170/*
171 * video_widget_class_init()
172 *
173 * This function init the video_widget_class.
174 */
175static void
176video_widget_class_init(VideoWidgetClass *klass)
177{
178 GObjectClass *object_class = G_OBJECT_CLASS(klass);
179
180 /* override method */
181 object_class->dispose = video_widget_dispose;
182 object_class->finalize = video_widget_finalize;
183}
184
185
186/*
187 * video_widget_init()
188 *
189 * This function init the video_widget.
190 * - init clutter
191 * - init all the widget members
192 */
193static void
194video_widget_init(VideoWidget *self)
195{
196 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
197
198 /* init clutter widget */
199 priv->clutter_widget = gtk_clutter_embed_new();
200 /* add it to the video_widget */
201 gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget);
202 /* get the stage */
203 priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget));
204
205 /* layout manager is used to arrange children in space, here we ask clutter
206 * to align children to fill the space when resizing the window */
207 clutter_actor_set_layout_manager(priv->stage,
208 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
209
210 /* add a scene container where we can add and remove our actors */
211 priv->video_container = clutter_actor_new();
212 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
213 clutter_actor_add_child(priv->stage, priv->video_container);
214
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400215 /* init the remote and local structs */
216 priv->remote = g_new0(VideoWidgetRenderer, 1);
217 priv->local = g_new0(VideoWidgetRenderer, 1);
218
219 /* arrange remote actors */
220 priv->remote->actor = clutter_actor_new();
221 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
222 /* the remote camera must always fill the container size */
223 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
224 CLUTTER_BIND_SIZE, 0);
225 clutter_actor_add_constraint(priv->remote->actor, constraint);
226
227 /* arrange local actor */
228 priv->local->actor = clutter_actor_new();
229 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
230 /* set size to square, but it will stay the aspect ratio when the image is rendered */
231 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
232 /* set position constraint to right cornder */
233 constraint = clutter_align_constraint_new(priv->video_container,
234 CLUTTER_ALIGN_BOTH, 0.99);
235 clutter_actor_add_constraint(priv->local->actor, constraint);
236 clutter_actor_set_opacity(priv->local->actor,
237 VIDEO_LOCAL_OPACITY_DEFAULT);
238
Stepan Salenikovich8934e842015-04-20 15:16:13 -0400239 /* Init the timeout source which will check the for new frames.
240 * The priority must be lower than GTK drawing events
241 * (G_PRIORITY_HIGH_IDLE + 20) so that this timeout source doesn't choke
242 * the main loop on slower machines.
243 */
244 priv->frame_timeout_source = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
245 FRAME_RATE_PERIOD,
246 (GSourceFunc)check_frame_queue,
247 self,
248 NULL);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400249
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400250 /* init new renderer queue */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400251 priv->new_renderer_queue = g_async_queue_new_full((GDestroyNotify)free_video_widget_renderer);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400252 /* check new render every 30 ms (30ms is "fast enough");
253 * we don't use an idle function so it doesn't consume cpu needlessly */
254 priv->renderer_timeout_source= g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
255 30,
256 (GSourceFunc)check_renderer_queue,
257 self,
258 NULL);
259
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400260 /* handle button event */
261 g_signal_connect(GTK_WIDGET(self), "button-press-event", G_CALLBACK(on_button_press_in_screen_event), NULL);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500262
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400263 /* drag & drop files as video sources */
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400264 gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
265 gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
266 g_signal_connect(GTK_WIDGET(self), "drag-data-received", G_CALLBACK(on_drag_data_received), NULL);
267}
268
269/*
270 * on_drag_data_received()
271 *
272 * Handle dragged data in the video widget window.
273 * Dropping an image causes the client to switch the video input to that image.
274 */
275static void
276on_drag_data_received(G_GNUC_UNUSED GtkWidget *self,
277 G_GNUC_UNUSED GdkDragContext *context,
278 G_GNUC_UNUSED gint x,
279 G_GNUC_UNUSED gint y,
280 GtkSelectionData *selection_data,
281 G_GNUC_UNUSED guint info,
282 G_GNUC_UNUSED guint32 time,
283 G_GNUC_UNUSED gpointer data)
284{
285 gchar **uris = gtk_selection_data_get_uris(selection_data);
286
287 /* only play the first selection */
288 if (uris && *uris)
Stepan Salenikovich033dc832015-03-23 15:56:47 -0400289 Video::SourceModel::instance()->setFile(QUrl(*uris));
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400290
291 g_strfreev(uris);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500292}
293
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400294static void
295switch_video_input(G_GNUC_UNUSED GtkWidget *widget, Video::Device *device)
296{
Stepan Salenikovichbb382632015-03-26 12:40:46 -0400297 Video::DeviceModel::instance()->setActive(device);
Stepan Salenikovich033dc832015-03-23 15:56:47 -0400298 Video::SourceModel::instance()->switchTo(device);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400299}
300
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400301static void
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400302switch_video_input_screen(G_GNUC_UNUSED GtkWidget *item, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400303{
304 unsigned x, y;
305 unsigned width, height;
306
307 /* try to get the dispaly or default to 0 */
308 QString display_env{getenv("DISPLAY")};
309 int display = 0;
310
311 if (!display_env.isEmpty()) {
312 auto list = display_env.split(":", QString::SkipEmptyParts);
313 /* should only be one display, so get the first one */
314 if (list.size() > 0) {
315 display = list.at(0).toInt();
316 g_debug("sharing screen from DISPLAY %d", display);
317 }
318 }
319
320 x = y = width = height = 0;
321
322 xrectsel(&x, &y, &width, &height);
323
324 if (!width || !height) {
325 x = y = 0;
326 width = gdk_screen_width();
327 height = gdk_screen_height();
328 }
329
Stepan Salenikovich033dc832015-03-23 15:56:47 -0400330 Video::SourceModel::instance()->setDisplay(display, QRect(x,y,width,height));
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400331}
332
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400333static void
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400334switch_video_input_file(G_GNUC_UNUSED GtkWidget *item, GtkWidget *parent)
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400335{
336 if (parent && GTK_IS_WIDGET(parent)) {
337 /* get parent window */
338 parent = gtk_widget_get_toplevel(GTK_WIDGET(parent));
339 }
340
341 gchar *uri = NULL;
342 GtkWidget *dialog = gtk_file_chooser_dialog_new(
343 "Choose File",
344 GTK_WINDOW(parent),
345 GTK_FILE_CHOOSER_ACTION_OPEN,
346 "_Cancel", GTK_RESPONSE_CANCEL,
347 "_Open", GTK_RESPONSE_ACCEPT,
348 NULL);
349
350 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
351 uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
352 }
353
354 gtk_widget_destroy(dialog);
355
356 Video::SourceModel::instance()->setFile(QUrl(uri));
357
358 g_free(uri);
359}
360
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400361/*
362 * on_button_press_in_screen_event()
363 *
364 * Handle button event in the video screen.
365 */
366static gboolean
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400367on_button_press_in_screen_event(GtkWidget *parent,
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400368 GdkEventButton *event,
369 G_GNUC_UNUSED gpointer data)
370{
371 /* check for right click */
372 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
373 return FALSE;
374
375 /* create menu with available video sources */
376 GtkWidget *menu = gtk_menu_new();
377
378 /* list available devices and check off the active device */
379 auto device_list = Video::DeviceModel::instance()->devices();
380
381 for( auto device: device_list) {
382 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(device->name().toLocal8Bit().constData());
383 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
384 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), device->isActive());
385 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input), device);
386 }
387
Stepan Salenikovichbb382632015-03-26 12:40:46 -0400388 /* add separator */
389 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
390
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400391 /* add screen area as an input */
392 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic("Share screen area");
393 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
394 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_screen), NULL);
395
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400396 /* add file as an input */
397 item = gtk_check_menu_item_new_with_mnemonic("Share file");
398 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
399 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_file), parent);
400
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400401 /* show menu */
402 gtk_widget_show_all(menu);
403 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
404
405 return TRUE; /* event has been fully handled */
406}
407
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500408static void
Guillaume Roguez24834e02015-04-09 12:45:40 -0400409clutter_render_image(VideoWidgetRenderer* wg_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500410{
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400411 auto actor = wg_renderer->actor;
412 g_return_if_fail(CLUTTER_IS_ACTOR(actor));
413
414 if (wg_renderer->show_black_frame) {
415 /* render a black frame set the bool back to false, this is likely done
416 * when the renderer is stopped so we ignore whether or not it is running
417 */
418 auto image_empty = clutter_image_new();
419 clutter_actor_set_content(actor, image_empty);
420 g_object_unref (image_empty);
421 wg_renderer->show_black_frame = false;
422 return;
423 }
424
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400425 ClutterContent *image_new = nullptr;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500426
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400427 {
428 /* the following must be done under lock in case a 'stopped' signal is
429 * received during rendering; otherwise the mem could become invalid */
430 std::lock_guard<std::mutex> lock(wg_renderer->run_mutex);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500431
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400432 if (!wg_renderer->running)
433 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400434
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400435 auto renderer = wg_renderer->renderer;
436 if (renderer == nullptr)
437 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400438
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400439 const auto frameData = (const guint8*)renderer->currentFrame().constData();
440 if (!frameData or !wg_renderer->dirty)
441 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400442
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400443 wg_renderer->dirty = false;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500444
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400445 image_new = clutter_image_new();
446 g_return_if_fail(image_new);
447
448 const auto& res = renderer->size();
449 const gint BPP = 4;
450 const gint ROW_STRIDE = BPP * res.width();
451
452 GError *error = nullptr;
453 clutter_image_set_data(
454 CLUTTER_IMAGE(image_new),
455 frameData,
456 COGL_PIXEL_FORMAT_BGRA_8888,
457 res.width(),
458 res.height(),
459 ROW_STRIDE,
460 &error);
461 if (error) {
462 g_warning("error rendering image to clutter: %s", error->message);
463 g_error_free(error);
464 g_object_unref (image_new);
465 return;
466 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500467 }
468
Guillaume Roguez24834e02015-04-09 12:45:40 -0400469 clutter_actor_set_content(actor, image_new);
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400470 g_object_unref (image_new);
Guillaume Roguez24834e02015-04-09 12:45:40 -0400471
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400472 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
473 * that the aspect ratio is correct
474 */
Guillaume Roguez24834e02015-04-09 12:45:40 -0400475 clutter_actor_set_content_gravity(actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400476}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500477
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400478static gboolean
479check_frame_queue(VideoWidget *self)
480{
481 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
482 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
483
Guillaume Roguez24834e02015-04-09 12:45:40 -0400484 /* display renderer's frames */
485 clutter_render_image(priv->local);
486 clutter_render_image(priv->remote);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400487
488 return TRUE; /* keep going */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500489}
490
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400491static void
492renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500493{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400494 QObject::disconnect(renderer->frame_update);
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400495 {
496 /* must do this under lock, in case the rendering is taking place when
497 * this signal is received */
498 std::lock_guard<std::mutex> lock(renderer->run_mutex);
499 renderer->running = false;
500 }
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400501 /* ask to show a black frame */
502 renderer->show_black_frame = true;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500503}
504
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400505static void
506renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500507{
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400508 QObject::disconnect(renderer->frame_update);
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400509 {
510 std::lock_guard<std::mutex> lock(renderer->run_mutex);
511 renderer->running = true;
512 }
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400513 renderer->frame_update = QObject::connect(
514 renderer->renderer,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500515 &Video::Renderer::frameUpdated,
Guillaume Roguez24834e02015-04-09 12:45:40 -0400516 [renderer]() {
517 // WARNING: this lambda is called in LRC renderer thread,
518 // but check_frame_queue() is in mainloop!
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400519
520 /* make sure show_black_frame is false since it will take priority
521 * over a new frame from the Video::Renderer */
522 renderer->show_black_frame = false;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400523 renderer->dirty = true;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500524 }
Guillaume Roguez24834e02015-04-09 12:45:40 -0400525 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500526}
527
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400528static void
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400529free_video_widget_renderer(VideoWidgetRenderer *renderer)
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400530{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400531 QObject::disconnect(renderer->frame_update);
532 QObject::disconnect(renderer->render_stop);
533 QObject::disconnect(renderer->render_start);
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400534 g_free(renderer);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400535}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500536
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400537static void
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400538video_widget_add_renderer(VideoWidget *self, VideoWidgetRenderer *new_video_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500539{
540 g_return_if_fail(IS_VIDEO_WIDGET(self));
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400541 g_return_if_fail(new_video_renderer);
542 g_return_if_fail(new_video_renderer->renderer);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500543
544 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
545
546 /* update the renderer */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400547 switch(new_video_renderer->type) {
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400548 case VIDEO_RENDERER_REMOTE:
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400549 /* swap the remote renderer */
550 new_video_renderer->actor = priv->remote->actor;
551 free_video_widget_renderer(priv->remote);
552 priv->remote = new_video_renderer;
553 /* reset the content gravity so that the aspect ratio gets properly
554 * reset if it chagnes */
555 clutter_actor_set_content_gravity(priv->remote->actor,
556 CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400557 break;
558 case VIDEO_RENDERER_LOCAL:
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400559 /* swap the remote renderer */
560 new_video_renderer->actor = priv->local->actor;
561 free_video_widget_renderer(priv->local);
562 priv->local = new_video_renderer;
563 /* reset the content gravity so that the aspect ratio gets properly
564 * reset if it chagnes */
565 clutter_actor_set_content_gravity(priv->local->actor,
566 CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400567 break;
568 case VIDEO_RENDERER_COUNT:
569 break;
570 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500571}
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400572
573static gboolean
574check_renderer_queue(VideoWidget *self)
575{
576 g_return_val_if_fail(IS_VIDEO_WIDGET(self), G_SOURCE_REMOVE);
577 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
578
579 /* get all the renderers in the queue */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400580 VideoWidgetRenderer *new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400581 while (new_video_renderer) {
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400582 video_widget_add_renderer(self, new_video_renderer);
583 new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400584 }
585
586 return G_SOURCE_CONTINUE;
587}
588
589/*
590 * video_widget_new()
591 *
592 * The function use to create a new video_widget
593 */
594GtkWidget*
595video_widget_new(void)
596{
597 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
598 return self;
599}
600
601/**
602 * video_widget_push_new_renderer()
603 *
604 * This function is used add a new Video::Renderer to the VideoWidget in a
605 * thread-safe manner.
606 */
607void
608video_widget_push_new_renderer(VideoWidget *self, Video::Renderer *renderer, VideoRendererType type)
609{
610 g_return_if_fail(IS_VIDEO_WIDGET(self));
611 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
612
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400613 /* if the renderer is nullptr, there is nothing to be done */
614 if (!renderer) return;
615
616 VideoWidgetRenderer *new_video_renderer = g_new0(VideoWidgetRenderer, 1);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400617 new_video_renderer->renderer = renderer;
618 new_video_renderer->type = type;
619
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400620 if (new_video_renderer->renderer->isRendering())
621 renderer_start(new_video_renderer);
622
623 new_video_renderer->render_stop = QObject::connect(
624 new_video_renderer->renderer,
625 &Video::Renderer::stopped,
626 [=]() {
627 renderer_stop(new_video_renderer);
628 }
629 );
630
631 new_video_renderer->render_start = QObject::connect(
632 new_video_renderer->renderer,
633 &Video::Renderer::started,
634 [=]() {
635 renderer_start(new_video_renderer);
636 }
637 );
638
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400639 g_async_queue_push(priv->new_renderer_queue, new_video_renderer);
640}