blob: 034afc816f85c56bd902fe95063a227349ad1432 [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 Salenikovich8bc51e52015-03-21 20:17:29 -040042#include "xrectsel.h"
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050043
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040044#define VIDEO_LOCAL_SIZE 150
45#define VIDEO_LOCAL_OPACITY_DEFAULT 150 /* out of 255 */
46
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040047/* check video frame queues at this rate;
48 * use 30 ms (about 30 fps) since we don't expect to
49 * receive video frames faster than that */
50#define FRAME_RATE_PERIOD 30
51
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050052struct _VideoWidgetClass {
53 GtkBinClass parent_class;
54};
55
56struct _VideoWidget {
57 GtkBin parent;
58};
59
60typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
61
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040062typedef struct _VideoWidgetRenderer VideoWidgetRenderer;
63
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050064struct _VideoWidgetPrivate {
65 GtkWidget *clutter_widget;
66 ClutterActor *stage;
67 ClutterActor *video_container;
68
69 /* remote peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040070 VideoWidgetRenderer *remote;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050071
72 /* local peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040073 VideoWidgetRenderer *local;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040074
75 guint frame_timeout_source;
Stepan Salenikovich0f693232015-04-22 10:45:08 -040076
77 /* new renderers should be put into the queue for processing by a g_timeout
78 * function whose id should be saved into renderer_timeout_source;
79 * this way when the VideoWidget object is destroyed, we do not try
80 * to process any new renderers by stoping the g_timeout function.
81 */
82 guint renderer_timeout_source;
83 GAsyncQueue *new_renderer_queue;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040084};
85
86struct _VideoWidgetRenderer {
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -040087 VideoRendererType type;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040088 ClutterActor *actor;
89 Video::Renderer *renderer;
Guillaume Roguez24834e02015-04-09 12:45:40 -040090 std::atomic_bool running;
91 std::atomic_bool dirty;
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -040092
93 /* show_black_frame is used to request the actor to render a black image;
94 * this will take over 'running' and 'dirty', ie: a black frame will be
95 * rendered even if the Video::Renderer is not running, or has a frame available.
96 * this will be set back to false once the black frame is rendered
97 */
98 std::atomic_bool show_black_frame;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040099 QMetaObject::Connection frame_update;
100 QMetaObject::Connection render_stop;
101 QMetaObject::Connection render_start;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500102};
103
104G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN);
105
106#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
107
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400108/* static prototypes */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400109static gboolean check_frame_queue (VideoWidget *);
110static void renderer_stop (VideoWidgetRenderer *);
111static void renderer_start (VideoWidgetRenderer *);
112static void on_drag_data_received (GtkWidget *, GdkDragContext *, gint, gint, GtkSelectionData *, guint, guint32, gpointer);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400113static gboolean on_button_press_in_screen_event(GtkWidget *, GdkEventButton *, gpointer);
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400114static gboolean check_renderer_queue (VideoWidget *);
115static void free_video_widget_renderer (VideoWidgetRenderer *);
116static void video_widget_add_renderer (VideoWidget *, VideoWidgetRenderer *);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400117
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500118/*
119 * video_widget_dispose()
120 *
121 * The dispose function for the video_widget class.
122 */
123static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400124video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500125{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400126 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500127 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
128
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400129 /* dispose may be called multiple times, make sure
130 * not to call g_source_remove more than once */
131 if (priv->frame_timeout_source) {
132 g_source_remove(priv->frame_timeout_source);
133 priv->frame_timeout_source = 0;
134 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400135
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400136 if (priv->renderer_timeout_source) {
137 g_source_remove(priv->renderer_timeout_source);
138 priv->renderer_timeout_source = 0;
139 }
140
141 if (priv->new_renderer_queue) {
142 g_async_queue_unref(priv->new_renderer_queue);
143 priv->new_renderer_queue = NULL;
144 }
145
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400146 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500147}
148
149
150/*
151 * video_widget_finalize()
152 *
153 * The finalize function for the video_widget class.
154 */
155static void
156video_widget_finalize(GObject *object)
157{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400158 VideoWidget *self = VIDEO_WIDGET(object);
159 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
160
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400161 free_video_widget_renderer(priv->local);
162 free_video_widget_renderer(priv->remote);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400163
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500164 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
165}
166
167
168/*
169 * video_widget_class_init()
170 *
171 * This function init the video_widget_class.
172 */
173static void
174video_widget_class_init(VideoWidgetClass *klass)
175{
176 GObjectClass *object_class = G_OBJECT_CLASS(klass);
177
178 /* override method */
179 object_class->dispose = video_widget_dispose;
180 object_class->finalize = video_widget_finalize;
181}
182
183
184/*
185 * video_widget_init()
186 *
187 * This function init the video_widget.
188 * - init clutter
189 * - init all the widget members
190 */
191static void
192video_widget_init(VideoWidget *self)
193{
194 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
195
196 /* init clutter widget */
197 priv->clutter_widget = gtk_clutter_embed_new();
198 /* add it to the video_widget */
199 gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget);
200 /* get the stage */
201 priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget));
202
203 /* layout manager is used to arrange children in space, here we ask clutter
204 * to align children to fill the space when resizing the window */
205 clutter_actor_set_layout_manager(priv->stage,
206 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
207
208 /* add a scene container where we can add and remove our actors */
209 priv->video_container = clutter_actor_new();
210 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
211 clutter_actor_add_child(priv->stage, priv->video_container);
212
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400213 /* init the remote and local structs */
214 priv->remote = g_new0(VideoWidgetRenderer, 1);
215 priv->local = g_new0(VideoWidgetRenderer, 1);
216
217 /* arrange remote actors */
218 priv->remote->actor = clutter_actor_new();
219 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
220 /* the remote camera must always fill the container size */
221 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
222 CLUTTER_BIND_SIZE, 0);
223 clutter_actor_add_constraint(priv->remote->actor, constraint);
224
225 /* arrange local actor */
226 priv->local->actor = clutter_actor_new();
227 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
228 /* set size to square, but it will stay the aspect ratio when the image is rendered */
229 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
230 /* set position constraint to right cornder */
231 constraint = clutter_align_constraint_new(priv->video_container,
232 CLUTTER_ALIGN_BOTH, 0.99);
233 clutter_actor_add_constraint(priv->local->actor, constraint);
234 clutter_actor_set_opacity(priv->local->actor,
235 VIDEO_LOCAL_OPACITY_DEFAULT);
236
Stepan Salenikovich8934e842015-04-20 15:16:13 -0400237 /* Init the timeout source which will check the for new frames.
238 * The priority must be lower than GTK drawing events
239 * (G_PRIORITY_HIGH_IDLE + 20) so that this timeout source doesn't choke
240 * the main loop on slower machines.
241 */
242 priv->frame_timeout_source = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
243 FRAME_RATE_PERIOD,
244 (GSourceFunc)check_frame_queue,
245 self,
246 NULL);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400247
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400248 /* init new renderer queue */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400249 priv->new_renderer_queue = g_async_queue_new_full((GDestroyNotify)free_video_widget_renderer);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400250 /* check new render every 30 ms (30ms is "fast enough");
251 * we don't use an idle function so it doesn't consume cpu needlessly */
252 priv->renderer_timeout_source= g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
253 30,
254 (GSourceFunc)check_renderer_queue,
255 self,
256 NULL);
257
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400258 /* handle button event */
259 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 -0500260
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400261 /* drag & drop files as video sources */
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400262 gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
263 gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
264 g_signal_connect(GTK_WIDGET(self), "drag-data-received", G_CALLBACK(on_drag_data_received), NULL);
265}
266
267/*
268 * on_drag_data_received()
269 *
270 * Handle dragged data in the video widget window.
271 * Dropping an image causes the client to switch the video input to that image.
272 */
273static void
274on_drag_data_received(G_GNUC_UNUSED GtkWidget *self,
275 G_GNUC_UNUSED GdkDragContext *context,
276 G_GNUC_UNUSED gint x,
277 G_GNUC_UNUSED gint y,
278 GtkSelectionData *selection_data,
279 G_GNUC_UNUSED guint info,
280 G_GNUC_UNUSED guint32 time,
281 G_GNUC_UNUSED gpointer data)
282{
283 gchar **uris = gtk_selection_data_get_uris(selection_data);
284
285 /* only play the first selection */
286 if (uris && *uris)
Stepan Salenikovich033dc832015-03-23 15:56:47 -0400287 Video::SourceModel::instance()->setFile(QUrl(*uris));
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400288
289 g_strfreev(uris);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500290}
291
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400292static void
293switch_video_input(G_GNUC_UNUSED GtkWidget *widget, Video::Device *device)
294{
Stepan Salenikovichbb382632015-03-26 12:40:46 -0400295 Video::DeviceModel::instance()->setActive(device);
Stepan Salenikovich033dc832015-03-23 15:56:47 -0400296 Video::SourceModel::instance()->switchTo(device);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400297}
298
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400299static void
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400300switch_video_input_screen(G_GNUC_UNUSED GtkWidget *item, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400301{
302 unsigned x, y;
303 unsigned width, height;
304
305 /* try to get the dispaly or default to 0 */
306 QString display_env{getenv("DISPLAY")};
307 int display = 0;
308
309 if (!display_env.isEmpty()) {
310 auto list = display_env.split(":", QString::SkipEmptyParts);
311 /* should only be one display, so get the first one */
312 if (list.size() > 0) {
313 display = list.at(0).toInt();
314 g_debug("sharing screen from DISPLAY %d", display);
315 }
316 }
317
318 x = y = width = height = 0;
319
320 xrectsel(&x, &y, &width, &height);
321
322 if (!width || !height) {
323 x = y = 0;
324 width = gdk_screen_width();
325 height = gdk_screen_height();
326 }
327
Stepan Salenikovich033dc832015-03-23 15:56:47 -0400328 Video::SourceModel::instance()->setDisplay(display, QRect(x,y,width,height));
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400329}
330
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400331static void
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400332switch_video_input_file(G_GNUC_UNUSED GtkWidget *item, GtkWidget *parent)
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400333{
334 if (parent && GTK_IS_WIDGET(parent)) {
335 /* get parent window */
336 parent = gtk_widget_get_toplevel(GTK_WIDGET(parent));
337 }
338
339 gchar *uri = NULL;
340 GtkWidget *dialog = gtk_file_chooser_dialog_new(
341 "Choose File",
342 GTK_WINDOW(parent),
343 GTK_FILE_CHOOSER_ACTION_OPEN,
344 "_Cancel", GTK_RESPONSE_CANCEL,
345 "_Open", GTK_RESPONSE_ACCEPT,
346 NULL);
347
348 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
349 uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
350 }
351
352 gtk_widget_destroy(dialog);
353
354 Video::SourceModel::instance()->setFile(QUrl(uri));
355
356 g_free(uri);
357}
358
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400359/*
360 * on_button_press_in_screen_event()
361 *
362 * Handle button event in the video screen.
363 */
364static gboolean
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400365on_button_press_in_screen_event(GtkWidget *parent,
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400366 GdkEventButton *event,
367 G_GNUC_UNUSED gpointer data)
368{
369 /* check for right click */
370 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
371 return FALSE;
372
373 /* create menu with available video sources */
374 GtkWidget *menu = gtk_menu_new();
375
376 /* list available devices and check off the active device */
377 auto device_list = Video::DeviceModel::instance()->devices();
378
379 for( auto device: device_list) {
380 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(device->name().toLocal8Bit().constData());
381 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
382 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), device->isActive());
383 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input), device);
384 }
385
Stepan Salenikovichbb382632015-03-26 12:40:46 -0400386 /* add separator */
387 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
388
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400389 /* add screen area as an input */
390 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic("Share screen area");
391 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
392 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_screen), NULL);
393
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400394 /* add file as an input */
395 item = gtk_check_menu_item_new_with_mnemonic("Share file");
396 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
397 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_file), parent);
398
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400399 /* show menu */
400 gtk_widget_show_all(menu);
401 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
402
403 return TRUE; /* event has been fully handled */
404}
405
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500406static void
Guillaume Roguez24834e02015-04-09 12:45:40 -0400407clutter_render_image(VideoWidgetRenderer* wg_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500408{
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400409 auto actor = wg_renderer->actor;
410 g_return_if_fail(CLUTTER_IS_ACTOR(actor));
411
412 if (wg_renderer->show_black_frame) {
413 /* render a black frame set the bool back to false, this is likely done
414 * when the renderer is stopped so we ignore whether or not it is running
415 */
416 auto image_empty = clutter_image_new();
417 clutter_actor_set_content(actor, image_empty);
418 g_object_unref (image_empty);
419 wg_renderer->show_black_frame = false;
420 return;
421 }
422
Guillaume Roguez24834e02015-04-09 12:45:40 -0400423 if (!wg_renderer->running)
424 return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500425
Guillaume Roguez24834e02015-04-09 12:45:40 -0400426 auto renderer = wg_renderer->renderer;
427 if (renderer == nullptr)
428 return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500429
Guillaume Roguez24834e02015-04-09 12:45:40 -0400430 const auto frameData = (const guint8*)renderer->currentFrame().constData();
431 if (!frameData or !wg_renderer->dirty)
432 return;
433
434 wg_renderer->dirty = false;
435
436 auto image_new = clutter_image_new();
437 g_return_if_fail(image_new);
438
439 const auto& res = renderer->size();
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500440 const gint BPP = 4;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400441 const gint ROW_STRIDE = BPP * res.width();
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500442
Guillaume Roguez24834e02015-04-09 12:45:40 -0400443 GError *error = nullptr;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500444 clutter_image_set_data(
Guillaume Roguez24834e02015-04-09 12:45:40 -0400445 CLUTTER_IMAGE(image_new),
446 frameData,
447 COGL_PIXEL_FORMAT_BGRA_8888,
448 res.width(),
449 res.height(),
450 ROW_STRIDE,
451 &error);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500452 if (error) {
453 g_warning("error rendering image to clutter: %s", error->message);
454 g_error_free(error);
Guillaume Roguez24834e02015-04-09 12:45:40 -0400455 g_object_unref (image_new);
456 return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500457 }
458
Guillaume Roguez24834e02015-04-09 12:45:40 -0400459 clutter_actor_set_content(actor, image_new);
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400460 g_object_unref (image_new);
Guillaume Roguez24834e02015-04-09 12:45:40 -0400461
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400462 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
463 * that the aspect ratio is correct
464 */
Guillaume Roguez24834e02015-04-09 12:45:40 -0400465 clutter_actor_set_content_gravity(actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400466}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500467
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400468static gboolean
469check_frame_queue(VideoWidget *self)
470{
471 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
472 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
473
Guillaume Roguez24834e02015-04-09 12:45:40 -0400474 /* display renderer's frames */
475 clutter_render_image(priv->local);
476 clutter_render_image(priv->remote);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400477
478 return TRUE; /* keep going */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500479}
480
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400481static void
482renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500483{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400484 QObject::disconnect(renderer->frame_update);
Guillaume Roguez24834e02015-04-09 12:45:40 -0400485 renderer->running = false;
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400486 /* ask to show a black frame */
487 renderer->show_black_frame = true;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500488}
489
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400490static void
491renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500492{
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400493 QObject::disconnect(renderer->frame_update);
Guillaume Roguez24834e02015-04-09 12:45:40 -0400494
495 renderer->running = true;
496
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400497 renderer->frame_update = QObject::connect(
498 renderer->renderer,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500499 &Video::Renderer::frameUpdated,
Guillaume Roguez24834e02015-04-09 12:45:40 -0400500 [renderer]() {
501 // WARNING: this lambda is called in LRC renderer thread,
502 // but check_frame_queue() is in mainloop!
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400503
504 /* make sure show_black_frame is false since it will take priority
505 * over a new frame from the Video::Renderer */
506 renderer->show_black_frame = false;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400507 renderer->dirty = true;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500508 }
Guillaume Roguez24834e02015-04-09 12:45:40 -0400509 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500510}
511
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400512static void
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400513free_video_widget_renderer(VideoWidgetRenderer *renderer)
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400514{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400515 QObject::disconnect(renderer->frame_update);
516 QObject::disconnect(renderer->render_stop);
517 QObject::disconnect(renderer->render_start);
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400518 g_free(renderer);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400519}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500520
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400521static void
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400522video_widget_add_renderer(VideoWidget *self, VideoWidgetRenderer *new_video_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500523{
524 g_return_if_fail(IS_VIDEO_WIDGET(self));
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400525 g_return_if_fail(new_video_renderer);
526 g_return_if_fail(new_video_renderer->renderer);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500527
528 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
529
530 /* update the renderer */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400531 switch(new_video_renderer->type) {
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400532 case VIDEO_RENDERER_REMOTE:
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400533 /* swap the remote renderer */
534 new_video_renderer->actor = priv->remote->actor;
535 free_video_widget_renderer(priv->remote);
536 priv->remote = new_video_renderer;
537 /* reset the content gravity so that the aspect ratio gets properly
538 * reset if it chagnes */
539 clutter_actor_set_content_gravity(priv->remote->actor,
540 CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400541 break;
542 case VIDEO_RENDERER_LOCAL:
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400543 /* swap the remote renderer */
544 new_video_renderer->actor = priv->local->actor;
545 free_video_widget_renderer(priv->local);
546 priv->local = new_video_renderer;
547 /* reset the content gravity so that the aspect ratio gets properly
548 * reset if it chagnes */
549 clutter_actor_set_content_gravity(priv->local->actor,
550 CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400551 break;
552 case VIDEO_RENDERER_COUNT:
553 break;
554 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500555}
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400556
557static gboolean
558check_renderer_queue(VideoWidget *self)
559{
560 g_return_val_if_fail(IS_VIDEO_WIDGET(self), G_SOURCE_REMOVE);
561 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
562
563 /* get all the renderers in the queue */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400564 VideoWidgetRenderer *new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400565 while (new_video_renderer) {
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400566 video_widget_add_renderer(self, new_video_renderer);
567 new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400568 }
569
570 return G_SOURCE_CONTINUE;
571}
572
573/*
574 * video_widget_new()
575 *
576 * The function use to create a new video_widget
577 */
578GtkWidget*
579video_widget_new(void)
580{
581 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
582 return self;
583}
584
585/**
586 * video_widget_push_new_renderer()
587 *
588 * This function is used add a new Video::Renderer to the VideoWidget in a
589 * thread-safe manner.
590 */
591void
592video_widget_push_new_renderer(VideoWidget *self, Video::Renderer *renderer, VideoRendererType type)
593{
594 g_return_if_fail(IS_VIDEO_WIDGET(self));
595 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
596
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400597 /* if the renderer is nullptr, there is nothing to be done */
598 if (!renderer) return;
599
600 VideoWidgetRenderer *new_video_renderer = g_new0(VideoWidgetRenderer, 1);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400601 new_video_renderer->renderer = renderer;
602 new_video_renderer->type = type;
603
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400604 if (new_video_renderer->renderer->isRendering())
605 renderer_start(new_video_renderer);
606
607 new_video_renderer->render_stop = QObject::connect(
608 new_video_renderer->renderer,
609 &Video::Renderer::stopped,
610 [=]() {
611 renderer_stop(new_video_renderer);
612 }
613 );
614
615 new_video_renderer->render_start = QObject::connect(
616 new_video_renderer->renderer,
617 &Video::Renderer::started,
618 [=]() {
619 renderer_start(new_video_renderer);
620 }
621 );
622
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400623 g_async_queue_push(priv->new_renderer_queue, new_video_renderer);
624}