blob: 06c3bf2e6b3b2f4a0b0edc4f31256815ee9a897e [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>
41#include "xrectsel.h"
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050042
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040043#define VIDEO_LOCAL_SIZE 150
44#define VIDEO_LOCAL_OPACITY_DEFAULT 150 /* out of 255 */
45
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040046/* check video frame queues at this rate;
47 * use 30 ms (about 30 fps) since we don't expect to
48 * receive video frames faster than that */
49#define FRAME_RATE_PERIOD 30
50
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050051struct _VideoWidgetClass {
52 GtkBinClass parent_class;
53};
54
55struct _VideoWidget {
56 GtkBin parent;
57};
58
59typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
60
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040061typedef struct _VideoWidgetRenderer VideoWidgetRenderer;
62
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050063struct _VideoWidgetPrivate {
64 GtkWidget *clutter_widget;
65 ClutterActor *stage;
66 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 Salenikovich4ac89f12015-03-10 16:48:47 -040075};
76
77struct _VideoWidgetRenderer {
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040078 GAsyncQueue *frame_queue;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040079 ClutterActor *actor;
80 Video::Renderer *renderer;
81 QMetaObject::Connection frame_update;
82 QMetaObject::Connection render_stop;
83 QMetaObject::Connection render_start;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050084};
85
86G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN);
87
88#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
89
90typedef struct _FrameInfo
91{
92 ClutterActor *image_actor;
93 ClutterContent *image;
94 guchar *data;
95 gint data_size;
96 gint width;
97 gint height;
98} FrameInfo;
99
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400100/* static prototypes */
101static FrameInfo *prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor);
102static void free_framedata(gpointer data);
103static void clutter_render_image(FrameInfo *frame);
104static gboolean check_frame_queue(VideoWidget *self);
105static void renderer_stop(VideoWidgetRenderer *renderer);
106static void renderer_start(VideoWidgetRenderer *renderer);
107static void video_widget_set_renderer(VideoWidgetRenderer *renderer);
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400108static void on_drag_data_received(GtkWidget *, GdkDragContext *, gint, gint, GtkSelectionData *, guint, guint32, gpointer);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400109static gboolean on_button_press_in_screen_event(GtkWidget *, GdkEventButton *, gpointer);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400110
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500111/*
112 * video_widget_dispose()
113 *
114 * The dispose function for the video_widget class.
115 */
116static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400117video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500118{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400119 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500120 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
121
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400122 QObject::disconnect(priv->remote->frame_update);
123 QObject::disconnect(priv->remote->render_stop);
124 QObject::disconnect(priv->remote->render_start);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500125
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400126 QObject::disconnect(priv->local->frame_update);
127 QObject::disconnect(priv->local->render_stop);
128 QObject::disconnect(priv->local->render_start);
129
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400130 /* dispose may be called multiple times, make sure
131 * not to call g_source_remove more than once */
132 if (priv->frame_timeout_source) {
133 g_source_remove(priv->frame_timeout_source);
134 priv->frame_timeout_source = 0;
135 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400136
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400137 if (priv->remote->frame_queue) {
138 g_async_queue_unref(priv->remote->frame_queue);
139 priv->remote->frame_queue = NULL;
140 }
141
142 if (priv->local->frame_queue) {
143 g_async_queue_unref(priv->local->frame_queue);
144 priv->local->frame_queue = NULL;
145 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400146
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400147 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500148}
149
150
151/*
152 * video_widget_finalize()
153 *
154 * The finalize function for the video_widget class.
155 */
156static void
157video_widget_finalize(GObject *object)
158{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400159 VideoWidget *self = VIDEO_WIDGET(object);
160 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
161
162 g_free(priv->remote);
163 g_free(priv->local);
164
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500165 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
166}
167
168
169/*
170 * video_widget_class_init()
171 *
172 * This function init the video_widget_class.
173 */
174static void
175video_widget_class_init(VideoWidgetClass *klass)
176{
177 GObjectClass *object_class = G_OBJECT_CLASS(klass);
178
179 /* override method */
180 object_class->dispose = video_widget_dispose;
181 object_class->finalize = video_widget_finalize;
182}
183
184
185/*
186 * video_widget_init()
187 *
188 * This function init the video_widget.
189 * - init clutter
190 * - init all the widget members
191 */
192static void
193video_widget_init(VideoWidget *self)
194{
195 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
196
197 /* init clutter widget */
198 priv->clutter_widget = gtk_clutter_embed_new();
199 /* add it to the video_widget */
200 gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget);
201 /* get the stage */
202 priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget));
203
204 /* layout manager is used to arrange children in space, here we ask clutter
205 * to align children to fill the space when resizing the window */
206 clutter_actor_set_layout_manager(priv->stage,
207 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
208
209 /* add a scene container where we can add and remove our actors */
210 priv->video_container = clutter_actor_new();
211 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
212 clutter_actor_add_child(priv->stage, priv->video_container);
213
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400214 /* init the remote and local structs */
215 priv->remote = g_new0(VideoWidgetRenderer, 1);
216 priv->local = g_new0(VideoWidgetRenderer, 1);
217
218 /* arrange remote actors */
219 priv->remote->actor = clutter_actor_new();
220 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
221 /* the remote camera must always fill the container size */
222 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
223 CLUTTER_BIND_SIZE, 0);
224 clutter_actor_add_constraint(priv->remote->actor, constraint);
225
226 /* arrange local actor */
227 priv->local->actor = clutter_actor_new();
228 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
229 /* set size to square, but it will stay the aspect ratio when the image is rendered */
230 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
231 /* set position constraint to right cornder */
232 constraint = clutter_align_constraint_new(priv->video_container,
233 CLUTTER_ALIGN_BOTH, 0.99);
234 clutter_actor_add_constraint(priv->local->actor, constraint);
235 clutter_actor_set_opacity(priv->local->actor,
236 VIDEO_LOCAL_OPACITY_DEFAULT);
237
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400238 /* init frame queues and the timeout sources to check them */
239 priv->remote->frame_queue = g_async_queue_new_full((GDestroyNotify)free_framedata);
240 priv->local->frame_queue = g_async_queue_new_full((GDestroyNotify)free_framedata);
241 priv->frame_timeout_source = g_timeout_add(FRAME_RATE_PERIOD, (GSourceFunc)check_frame_queue, self);
242
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400243 /* handle button event */
244 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 -0500245
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400246 /* drag & drop files as video sources */
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400247 gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
248 gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
249 g_signal_connect(GTK_WIDGET(self), "drag-data-received", G_CALLBACK(on_drag_data_received), NULL);
250}
251
252/*
253 * on_drag_data_received()
254 *
255 * Handle dragged data in the video widget window.
256 * Dropping an image causes the client to switch the video input to that image.
257 */
258static void
259on_drag_data_received(G_GNUC_UNUSED GtkWidget *self,
260 G_GNUC_UNUSED GdkDragContext *context,
261 G_GNUC_UNUSED gint x,
262 G_GNUC_UNUSED gint y,
263 GtkSelectionData *selection_data,
264 G_GNUC_UNUSED guint info,
265 G_GNUC_UNUSED guint32 time,
266 G_GNUC_UNUSED gpointer data)
267{
268 gchar **uris = gtk_selection_data_get_uris(selection_data);
269
270 /* only play the first selection */
271 if (uris && *uris)
Stepan Salenikovich033dc832015-03-23 15:56:47 -0400272 Video::SourceModel::instance()->setFile(QUrl(*uris));
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400273
274 g_strfreev(uris);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500275}
276
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400277static void
278switch_video_input(G_GNUC_UNUSED GtkWidget *widget, Video::Device *device)
279{
Stepan Salenikovichbb382632015-03-26 12:40:46 -0400280 Video::DeviceModel::instance()->setActive(device);
Stepan Salenikovich033dc832015-03-23 15:56:47 -0400281 Video::SourceModel::instance()->switchTo(device);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400282}
283
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400284static void
285switch_video_input_screen(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer user_data)
286{
287 unsigned x, y;
288 unsigned width, height;
289
290 /* try to get the dispaly or default to 0 */
291 QString display_env{getenv("DISPLAY")};
292 int display = 0;
293
294 if (!display_env.isEmpty()) {
295 auto list = display_env.split(":", QString::SkipEmptyParts);
296 /* should only be one display, so get the first one */
297 if (list.size() > 0) {
298 display = list.at(0).toInt();
299 g_debug("sharing screen from DISPLAY %d", display);
300 }
301 }
302
303 x = y = width = height = 0;
304
305 xrectsel(&x, &y, &width, &height);
306
307 if (!width || !height) {
308 x = y = 0;
309 width = gdk_screen_width();
310 height = gdk_screen_height();
311 }
312
Stepan Salenikovich033dc832015-03-23 15:56:47 -0400313 Video::SourceModel::instance()->setDisplay(display, QRect(x,y,width,height));
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400314}
315
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400316static void
317switch_video_input_file(GtkWidget *parent)
318{
319 if (parent && GTK_IS_WIDGET(parent)) {
320 /* get parent window */
321 parent = gtk_widget_get_toplevel(GTK_WIDGET(parent));
322 }
323
324 gchar *uri = NULL;
325 GtkWidget *dialog = gtk_file_chooser_dialog_new(
326 "Choose File",
327 GTK_WINDOW(parent),
328 GTK_FILE_CHOOSER_ACTION_OPEN,
329 "_Cancel", GTK_RESPONSE_CANCEL,
330 "_Open", GTK_RESPONSE_ACCEPT,
331 NULL);
332
333 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
334 uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
335 }
336
337 gtk_widget_destroy(dialog);
338
339 Video::SourceModel::instance()->setFile(QUrl(uri));
340
341 g_free(uri);
342}
343
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400344/*
345 * on_button_press_in_screen_event()
346 *
347 * Handle button event in the video screen.
348 */
349static gboolean
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400350on_button_press_in_screen_event(GtkWidget *parent,
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400351 GdkEventButton *event,
352 G_GNUC_UNUSED gpointer data)
353{
354 /* check for right click */
355 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
356 return FALSE;
357
358 /* create menu with available video sources */
359 GtkWidget *menu = gtk_menu_new();
360
361 /* list available devices and check off the active device */
362 auto device_list = Video::DeviceModel::instance()->devices();
363
364 for( auto device: device_list) {
365 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(device->name().toLocal8Bit().constData());
366 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
367 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), device->isActive());
368 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input), device);
369 }
370
Stepan Salenikovichbb382632015-03-26 12:40:46 -0400371 /* add separator */
372 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
373
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400374 /* add screen area as an input */
375 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic("Share screen area");
376 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
377 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_screen), NULL);
378
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400379 /* add file as an input */
380 item = gtk_check_menu_item_new_with_mnemonic("Share file");
381 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
382 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_file), parent);
383
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400384 /* show menu */
385 gtk_widget_show_all(menu);
386 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
387
388 return TRUE; /* event has been fully handled */
389}
390
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500391static FrameInfo *
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400392prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500393{
394 const QByteArray& data = renderer->currentFrame();
395 QSize res = renderer->size();
396
397 /* copy frame data */
398 gpointer frame_data = g_memdup((gconstpointer)data.constData(), data.size());
399
400 FrameInfo *frame = g_new0(FrameInfo, 1);
401
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500402 frame->image_actor = image_actor;
403 frame->data = (guchar *)frame_data;
404 frame->data_size = data.size();
405 frame->width = res.width();
406 frame->height = res.height();
407
408 return frame;
409}
410
411static void
412free_framedata(gpointer data)
413{
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400414 if (data == NULL) return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500415 FrameInfo *frame = (FrameInfo *)data;
416 g_free(frame->data);
417 g_free(frame);
418}
419
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400420static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500421clutter_render_image(FrameInfo *frame)
422{
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400423 if (frame == NULL) return;
424 g_return_if_fail(CLUTTER_IS_ACTOR(frame->image_actor));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500425
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400426 ClutterContent * image_new = clutter_image_new();
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500427
428 const gint BPP = 4;
429 const gint ROW_STRIDE = BPP * frame->width;
430
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500431 GError *error = NULL;
432 clutter_image_set_data(
433 CLUTTER_IMAGE(image_new),
434 frame->data,
435 COGL_PIXEL_FORMAT_BGRA_8888,
436 frame->width,
437 frame->height,
438 ROW_STRIDE,
439 &error);
440 if (error) {
441 g_warning("error rendering image to clutter: %s", error->message);
442 g_error_free(error);
443 }
444
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400445 clutter_actor_set_content(frame->image_actor, image_new);
446 g_object_unref (image_new);
447 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
448 * that the aspect ratio is correct
449 */
450 clutter_actor_set_content_gravity(frame->image_actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400451}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500452
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400453static gboolean
454check_frame_queue(VideoWidget *self)
455{
456 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
457 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
458
459 /* get the latest frame in the queue */
460 gpointer local_data_last = NULL;
461 gpointer local_data_next = g_async_queue_try_pop(priv->local->frame_queue);
462 while(local_data_next != NULL) {
463 // if (local_data_last != NULL) g_debug("skipping local frame");
464 /* make sure to free the frame we're skipping */
465 free_framedata(local_data_last);
466 local_data_last = local_data_next;
467 local_data_next = g_async_queue_try_pop(priv->local->frame_queue);
468 }
469
470 /* display the frame */
471 clutter_render_image((FrameInfo *)local_data_last);
472 free_framedata(local_data_last);
473
474 /* get the latest frame in the queue */
475 gpointer remote_data_last = NULL;
476 gpointer remote_data_next = g_async_queue_try_pop(priv->remote->frame_queue);
477 while(remote_data_next != NULL) {
478 // if (remote_data_last != NULL) g_debug("skipping remote frame");
479 /* make sure to free the frame we're skipping */
480 free_framedata(remote_data_last);
481 remote_data_last = remote_data_next;
482 remote_data_next = g_async_queue_try_pop(priv->remote->frame_queue);
483 }
484
485 /* display the frame */
486 clutter_render_image((FrameInfo *)remote_data_last);
487 free_framedata(remote_data_last);
488
489 return TRUE; /* keep going */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500490}
491
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400492static void
493renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500494{
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400495 g_return_if_fail(CLUTTER_IS_ACTOR(renderer->actor));
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400496 QObject::disconnect(renderer->frame_update);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500497}
498
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400499static void
500renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500501{
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400502 g_return_if_fail(CLUTTER_IS_ACTOR(renderer->actor));
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400503 QObject::disconnect(renderer->frame_update);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400504 renderer->frame_update = QObject::connect(
505 renderer->renderer,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500506 &Video::Renderer::frameUpdated,
507 [=]() {
Stepan Salenikovich57058802015-03-25 14:16:13 -0400508 if (!renderer->renderer->isRendering()) {
509 g_debug("got frame but not rendering");
510 return;
511 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500512
513 /* this callback comes from another thread;
514 * rendering must be done in the main loop;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400515 * copy the frame and add it to the frame queue
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500516 */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400517 FrameInfo *frame = prepare_framedata(renderer->renderer,
518 renderer->actor);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400519 g_async_queue_push(renderer->frame_queue, frame);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500520 }
521 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500522}
523
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400524static void
525video_widget_set_renderer(VideoWidgetRenderer *renderer)
526{
527 if (renderer == NULL) return;
528
Stepan Salenikovich57058802015-03-25 14:16:13 -0400529 /* reset the content gravity so that the aspect ratio gets properly set if it chagnes */
530 clutter_actor_set_content_gravity(renderer->actor, CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
531
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400532 /* update the renderer */
533 QObject::disconnect(renderer->frame_update);
534 QObject::disconnect(renderer->render_stop);
535 QObject::disconnect(renderer->render_start);
536
Stepan Salenikovich57058802015-03-25 14:16:13 -0400537 if (renderer->renderer->isRendering())
538 renderer_start(renderer);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400539
540 renderer->render_stop = QObject::connect(
541 renderer->renderer,
542 &Video::Renderer::stopped,
543 [=]() {
544 renderer_stop(renderer);
545 }
546 );
547
548 renderer->render_start = QObject::connect(
549 renderer->renderer,
550 &Video::Renderer::started,
551 [=]() {
552 renderer_start(renderer);
553 }
554 );
555}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500556
557/*
558 * video_widget_new()
559 *
560 * The function use to create a new video_widget
561 */
562GtkWidget*
563video_widget_new(void)
564{
565 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
566 return self;
567}
568
569void
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400570video_widget_add_renderer(VideoWidget *self, const VideoRenderer *new_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500571{
572 g_return_if_fail(IS_VIDEO_WIDGET(self));
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400573 if (new_renderer == NULL || new_renderer->renderer == NULL)
574 return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500575
576 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
577
578 /* update the renderer */
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400579 switch(new_renderer->type) {
580 case VIDEO_RENDERER_REMOTE:
581 priv->remote->renderer = new_renderer->renderer;
582 video_widget_set_renderer(priv->remote);
583 break;
584 case VIDEO_RENDERER_LOCAL:
585 priv->local->renderer = new_renderer->renderer;
586 video_widget_set_renderer(priv->local);
587 break;
588 case VIDEO_RENDERER_COUNT:
589 break;
590 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500591}