blob: 61492f5ffd2acfeffa3916a8d22a9c18734c00d7 [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
Julien Grossholtz58cfc152015-10-22 15:43:43 -040033#include <glib/gi18n.h>
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050034#include <clutter/clutter.h>
35#include <clutter-gtk/clutter-gtk.h>
36#include <video/renderer.h>
Stepan Salenikovich033dc832015-03-23 15:56:47 -040037#include <video/sourcemodel.h>
Julien Grossholtza0d4f102015-10-22 14:24:17 -040038#include <media/video.h>
Stepan Salenikovich50c989b2015-03-21 18:32:46 -040039#include <video/devicemodel.h>
40#include <QtCore/QUrl>
41#include "../defines.h"
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -040042#include <stdlib.h>
Guillaume Roguez24834e02015-04-09 12:45:40 -040043#include <atomic>
Stepan Salenikovichaea6c042015-04-29 15:09:16 -040044#include <mutex>
Julien Grossholtza0d4f102015-10-22 14:24:17 -040045#include <call.h>
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -040046#include "xrectsel.h"
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050047
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040048static constexpr int VIDEO_LOCAL_SIZE = 150;
49static constexpr int VIDEO_LOCAL_OPACITY_DEFAULT = 255; /* out of 255 */
Julien Grossholtza0d4f102015-10-22 14:24:17 -040050static constexpr const char* JOIN_CALL_KEY = "call_data";
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040051
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040052/* check video frame queues at this rate;
53 * use 30 ms (about 30 fps) since we don't expect to
54 * receive video frames faster than that */
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040055static constexpr int FRAME_RATE_PERIOD = 30;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040056
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050057struct _VideoWidgetClass {
Stepan Salenikovich36ef3942015-11-05 18:26:57 -050058 GtkClutterEmbedClass parent_class;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050059};
60
61struct _VideoWidget {
Stepan Salenikovich36ef3942015-11-05 18:26:57 -050062 GtkClutterEmbed parent;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050063};
64
65typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
66
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040067typedef struct _VideoWidgetRenderer VideoWidgetRenderer;
68
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050069struct _VideoWidgetPrivate {
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050070 ClutterActor *video_container;
71
72 /* remote peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040073 VideoWidgetRenderer *remote;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050074
75 /* local peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040076 VideoWidgetRenderer *local;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040077
78 guint frame_timeout_source;
Stepan Salenikovich0f693232015-04-22 10:45:08 -040079
80 /* new renderers should be put into the queue for processing by a g_timeout
81 * function whose id should be saved into renderer_timeout_source;
82 * this way when the VideoWidget object is destroyed, we do not try
83 * to process any new renderers by stoping the g_timeout function.
84 */
85 guint renderer_timeout_source;
86 GAsyncQueue *new_renderer_queue;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040087};
88
89struct _VideoWidgetRenderer {
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -040090 VideoRendererType type;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040091 ClutterActor *actor;
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -040092 ClutterAction *drag_action;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040093 Video::Renderer *renderer;
Stepan Salenikovichaea6c042015-04-29 15:09:16 -040094 std::mutex run_mutex;
95 bool running;
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 *);
116static void on_drag_data_received (GtkWidget *, GdkDragContext *, gint, gint, GtkSelectionData *, guint, guint32, gpointer);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400117static gboolean on_button_press_in_screen_event(GtkWidget *, GdkEventButton *, gpointer);
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400118static gboolean check_renderer_queue (VideoWidget *);
119static void free_video_widget_renderer (VideoWidgetRenderer *);
120static void video_widget_add_renderer (VideoWidget *, VideoWidgetRenderer *);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400121
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500122/*
123 * video_widget_dispose()
124 *
125 * The dispose function for the video_widget class.
126 */
127static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400128video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500129{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400130 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500131 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
132
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400133 /* dispose may be called multiple times, make sure
134 * not to call g_source_remove more than once */
135 if (priv->frame_timeout_source) {
136 g_source_remove(priv->frame_timeout_source);
137 priv->frame_timeout_source = 0;
138 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400139
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400140 if (priv->renderer_timeout_source) {
141 g_source_remove(priv->renderer_timeout_source);
142 priv->renderer_timeout_source = 0;
143 }
144
145 if (priv->new_renderer_queue) {
146 g_async_queue_unref(priv->new_renderer_queue);
147 priv->new_renderer_queue = NULL;
148 }
149
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400150 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500151}
152
153
154/*
155 * video_widget_finalize()
156 *
157 * The finalize function for the video_widget class.
158 */
159static void
160video_widget_finalize(GObject *object)
161{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400162 VideoWidget *self = VIDEO_WIDGET(object);
163 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
164
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400165 free_video_widget_renderer(priv->local);
166 free_video_widget_renderer(priv->remote);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400167
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500168 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
169}
170
171
172/*
173 * video_widget_class_init()
174 *
175 * This function init the video_widget_class.
176 */
177static void
178video_widget_class_init(VideoWidgetClass *klass)
179{
180 GObjectClass *object_class = G_OBJECT_CLASS(klass);
181
182 /* override method */
183 object_class->dispose = video_widget_dispose;
184 object_class->finalize = video_widget_finalize;
185}
186
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400187static void
188on_allocation_changed(ClutterActor *video_area, G_GNUC_UNUSED GParamSpec *pspec, VideoWidget *self)
189{
190 g_return_if_fail(IS_VIDEO_WIDGET(self));
191 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
192
193 auto actor = priv->local->actor;
194 auto drag_action = priv->local->drag_action;
195
196 ClutterActorBox actor_box;
197 clutter_actor_get_allocation_box(actor, &actor_box);
198 gfloat actor_w = clutter_actor_box_get_width(&actor_box);
199 gfloat actor_h = clutter_actor_box_get_height(&actor_box);
200
201 ClutterActorBox area_box;
202 clutter_actor_get_allocation_box(video_area, &area_box);
203 gfloat area_w = clutter_actor_box_get_width(&area_box);
204 gfloat area_h = clutter_actor_box_get_height(&area_box);
205
206 /* make sure drag area stays within the bounds of the stage */
207 ClutterRect *rect = clutter_rect_init (
208 clutter_rect_alloc(),
209 0, 0,
210 area_w - actor_w,
211 area_h - actor_h);
212 clutter_drag_action_set_drag_area(CLUTTER_DRAG_ACTION(drag_action), rect);
213 clutter_rect_free(rect);
214}
215
216static void
217on_drag_begin(G_GNUC_UNUSED ClutterDragAction *action,
218 ClutterActor *actor,
219 G_GNUC_UNUSED gfloat event_x,
220 G_GNUC_UNUSED gfloat event_y,
221 G_GNUC_UNUSED ClutterModifierType modifiers,
222 G_GNUC_UNUSED gpointer user_data)
223{
224 /* clear the align constraint when starting to move the preview, otherwise
225 * it won't move; save and set its position, to what it was before the
226 * constraint was cleared, or else it might jump around */
227 gfloat actor_x, actor_y;
228 clutter_actor_get_position(actor, &actor_x, &actor_y);
229 clutter_actor_clear_constraints(actor);
230 clutter_actor_set_position(actor, actor_x, actor_y);
231}
232
233static void
234on_drag_end(G_GNUC_UNUSED ClutterDragAction *action,
235 ClutterActor *actor,
236 G_GNUC_UNUSED gfloat event_x,
237 G_GNUC_UNUSED gfloat event_y,
238 G_GNUC_UNUSED ClutterModifierType modifiers,
239 ClutterActor *video_area)
240{
241 ClutterActorBox area_box;
242 clutter_actor_get_allocation_box(video_area, &area_box);
243 gfloat area_w = clutter_actor_box_get_width(&area_box);
244 gfloat area_h = clutter_actor_box_get_height(&area_box);
245
246 gfloat actor_x, actor_y;
247 clutter_actor_get_position(actor, &actor_x, &actor_y);
248 gfloat actor_w, actor_h;
249 clutter_actor_get_size(actor, &actor_w, &actor_h);
250
251 area_w -= actor_w;
252 area_h -= actor_h;
253
254 /* add new constraints to make sure the preview stays in about the same location
255 * relative to the rest of the video when resizing */
256 ClutterConstraint *constraint_x = clutter_align_constraint_new(video_area,
257 CLUTTER_ALIGN_X_AXIS, actor_x/area_w);
258 clutter_actor_add_constraint(actor, constraint_x);
259
260 ClutterConstraint *constraint_y = clutter_align_constraint_new(video_area,
261 CLUTTER_ALIGN_Y_AXIS, actor_y/area_h);
262 clutter_actor_add_constraint(actor, constraint_y);
263}
264
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500265
266/*
267 * video_widget_init()
268 *
269 * This function init the video_widget.
270 * - init clutter
271 * - init all the widget members
272 */
273static void
274video_widget_init(VideoWidget *self)
275{
276 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
277
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500278 auto stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(self));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500279
280 /* layout manager is used to arrange children in space, here we ask clutter
281 * to align children to fill the space when resizing the window */
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500282 clutter_actor_set_layout_manager(stage,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500283 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
284
285 /* add a scene container where we can add and remove our actors */
286 priv->video_container = clutter_actor_new();
287 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500288 clutter_actor_add_child(stage, priv->video_container);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500289
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400290 /* init the remote and local structs */
291 priv->remote = g_new0(VideoWidgetRenderer, 1);
292 priv->local = g_new0(VideoWidgetRenderer, 1);
293
294 /* arrange remote actors */
295 priv->remote->actor = clutter_actor_new();
296 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
297 /* the remote camera must always fill the container size */
298 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
299 CLUTTER_BIND_SIZE, 0);
300 clutter_actor_add_constraint(priv->remote->actor, constraint);
301
302 /* arrange local actor */
303 priv->local->actor = clutter_actor_new();
304 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
305 /* set size to square, but it will stay the aspect ratio when the image is rendered */
306 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400307 /* set position constraint to right cornder;
308 * this constraint will be removed once the user tries to move the position
309 * of the action */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400310 constraint = clutter_align_constraint_new(priv->video_container,
311 CLUTTER_ALIGN_BOTH, 0.99);
312 clutter_actor_add_constraint(priv->local->actor, constraint);
313 clutter_actor_set_opacity(priv->local->actor,
314 VIDEO_LOCAL_OPACITY_DEFAULT);
315
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400316 /* add ability for actor to be moved */
317 clutter_actor_set_reactive(priv->local->actor, TRUE);
318 priv->local->drag_action = clutter_drag_action_new();
319 clutter_actor_add_action(priv->local->actor, priv->local->drag_action);
320
321 g_signal_connect(priv->local->drag_action, "drag-begin", G_CALLBACK(on_drag_begin), NULL);
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500322 g_signal_connect_after(priv->local->drag_action, "drag-end", G_CALLBACK(on_drag_end), stage);
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400323
324 /* make sure the actor stays within the bounds of the stage */
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500325 g_signal_connect(stage, "notify::allocation", G_CALLBACK(on_allocation_changed), self);
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400326
Stepan Salenikovich8934e842015-04-20 15:16:13 -0400327 /* Init the timeout source which will check the for new frames.
328 * The priority must be lower than GTK drawing events
329 * (G_PRIORITY_HIGH_IDLE + 20) so that this timeout source doesn't choke
330 * the main loop on slower machines.
331 */
332 priv->frame_timeout_source = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
333 FRAME_RATE_PERIOD,
334 (GSourceFunc)check_frame_queue,
335 self,
336 NULL);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400337
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400338 /* init new renderer queue */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400339 priv->new_renderer_queue = g_async_queue_new_full((GDestroyNotify)free_video_widget_renderer);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400340 /* check new render every 30 ms (30ms is "fast enough");
341 * we don't use an idle function so it doesn't consume cpu needlessly */
342 priv->renderer_timeout_source= g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
343 30,
344 (GSourceFunc)check_renderer_queue,
345 self,
346 NULL);
347
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500348
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400349 /* drag & drop files as video sources */
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400350 gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
351 gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400352}
353
354/*
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400355 * video_widget_on_drag_data_received()
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400356 *
357 * Handle dragged data in the video widget window.
358 * Dropping an image causes the client to switch the video input to that image.
359 */
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400360void video_widget_on_drag_data_received(G_GNUC_UNUSED GtkWidget *self,
361 G_GNUC_UNUSED GdkDragContext *context,
362 G_GNUC_UNUSED gint x,
363 G_GNUC_UNUSED gint y,
364 GtkSelectionData *selection_data,
365 G_GNUC_UNUSED guint info,
366 G_GNUC_UNUSED guint32 time,
367 Call *call)
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400368{
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400369 g_return_if_fail(call);
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400370 gchar **uris = gtk_selection_data_get_uris(selection_data);
371
372 /* only play the first selection */
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400373 if (uris && *uris){
374 if (auto out_media = call->firstMedia<Media::Video>(Media::Media::Direction::OUT))
375 out_media->sourceModel()->setFile(QUrl(*uris));
376 }
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400377
378 g_strfreev(uris);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500379}
380
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400381static void
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400382switch_video_input(GtkWidget *widget, Video::Device *device)
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400383{
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400384 gpointer data = g_object_get_data(G_OBJECT(widget),JOIN_CALL_KEY );
385 g_return_if_fail(data);
386 Call *call = (Call*)data;
387
388 if (auto out_media = call->firstMedia<Media::Video>(Media::Media::Direction::OUT))
389 out_media->sourceModel()->switchTo(device);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400390}
391
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400392static void
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400393switch_video_input_screen(G_GNUC_UNUSED GtkWidget *item, Call* call)
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400394{
395 unsigned x, y;
396 unsigned width, height;
397
398 /* try to get the dispaly or default to 0 */
399 QString display_env{getenv("DISPLAY")};
400 int display = 0;
401
402 if (!display_env.isEmpty()) {
403 auto list = display_env.split(":", QString::SkipEmptyParts);
404 /* should only be one display, so get the first one */
405 if (list.size() > 0) {
406 display = list.at(0).toInt();
407 g_debug("sharing screen from DISPLAY %d", display);
408 }
409 }
410
411 x = y = width = height = 0;
412
413 xrectsel(&x, &y, &width, &height);
414
415 if (!width || !height) {
416 x = y = 0;
417 width = gdk_screen_width();
418 height = gdk_screen_height();
419 }
420
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400421 if (auto out_media = call->firstMedia<Media::Video>(Media::Media::Direction::OUT))
422 out_media->sourceModel()->setDisplay(display, QRect(x,y,width,height));
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400423}
424
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400425static void
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400426switch_video_input_file(GtkWidget *item, GtkWidget *parent)
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400427{
428 if (parent && GTK_IS_WIDGET(parent)) {
429 /* get parent window */
430 parent = gtk_widget_get_toplevel(GTK_WIDGET(parent));
431 }
432
433 gchar *uri = NULL;
434 GtkWidget *dialog = gtk_file_chooser_dialog_new(
435 "Choose File",
436 GTK_WINDOW(parent),
437 GTK_FILE_CHOOSER_ACTION_OPEN,
438 "_Cancel", GTK_RESPONSE_CANCEL,
439 "_Open", GTK_RESPONSE_ACCEPT,
440 NULL);
441
442 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400443 Call *call;
444 gpointer data = g_object_get_data(G_OBJECT(item),JOIN_CALL_KEY );
445 g_return_if_fail(data);
446 call = (Call*)data;
447
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400448 uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400449
450 if (auto out_media = call->firstMedia<Media::Video>(Media::Media::Direction::OUT))
451 out_media->sourceModel()->setFile(QUrl(uri));
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400452 }
453
454 gtk_widget_destroy(dialog);
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400455 g_free(uri);
456}
457
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400458/*
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400459 * video_widget_on_button_press_in_screen_event()
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400460 *
461 * Handle button event in the video screen.
462 */
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400463gboolean
464video_widget_on_button_press_in_screen_event(GtkWidget *parent, GdkEventButton *event, Call* call)
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400465{
466 /* check for right click */
467 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
468 return FALSE;
469
470 /* create menu with available video sources */
471 GtkWidget *menu = gtk_menu_new();
472
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400473 Video::SourceModel *sourcemodel = nullptr;
474 if (auto out_media = call->firstMedia<Media::Video>(Media::Media::Direction::OUT))
475 sourcemodel = out_media->sourceModel();
476
477 if(!sourcemodel)
478 return FALSE;
479
480 auto active = sourcemodel->activeIndex();
Stepan Salenikovichcb1c2952015-08-13 14:28:20 -0400481
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400482 /* list available devices and check off the active device */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400483 auto device_list = Video::DeviceModel::instance().devices();
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400484
485 for( auto device: device_list) {
486 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(device->name().toLocal8Bit().constData());
487 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400488 auto device_idx = sourcemodel->getDeviceIndex(device);
Stepan Salenikovichcb1c2952015-08-13 14:28:20 -0400489 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), device_idx == active);
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400490 g_object_set_data(G_OBJECT(item), JOIN_CALL_KEY,call);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400491 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input), device);
492 }
493
Stepan Salenikovichbb382632015-03-26 12:40:46 -0400494 /* add separator */
495 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
496
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400497 /* add screen area as an input */
Julien Grossholtz58cfc152015-10-22 15:43:43 -0400498 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(_("Share screen area"));
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400499 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
Stepan Salenikovichcb1c2952015-08-13 14:28:20 -0400500 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), Video::SourceModel::ExtendedDeviceList::SCREEN == active);
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400501 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_screen), call);
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400502
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400503 /* add file as an input */
Julien Grossholtz58cfc152015-10-22 15:43:43 -0400504 item = gtk_check_menu_item_new_with_mnemonic(_("Share file"));
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400505 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
Stepan Salenikovichcb1c2952015-08-13 14:28:20 -0400506 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), Video::SourceModel::ExtendedDeviceList::FILE == active);
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400507 g_object_set_data(G_OBJECT(item), JOIN_CALL_KEY, call);
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400508 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_file), parent);
509
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400510 /* show menu */
511 gtk_widget_show_all(menu);
512 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
513
514 return TRUE; /* event has been fully handled */
515}
516
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500517static void
Guillaume Roguez24834e02015-04-09 12:45:40 -0400518clutter_render_image(VideoWidgetRenderer* wg_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500519{
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400520 auto actor = wg_renderer->actor;
521 g_return_if_fail(CLUTTER_IS_ACTOR(actor));
522
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400523 if (wg_renderer->pause_rendering)
524 return;
525
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400526 if (wg_renderer->show_black_frame) {
527 /* render a black frame set the bool back to false, this is likely done
528 * when the renderer is stopped so we ignore whether or not it is running
529 */
Stepan Salenikovich6e7b0922015-05-05 17:51:30 -0400530 if (auto image_old = clutter_actor_get_content(actor)) {
531 gfloat width;
532 gfloat height;
533 if (clutter_content_get_preferred_size(image_old, &width, &height)) {
534 /* NOTE: this is a workaround for #72531, a crash which occurs
535 * in cogl < 1.18. We allocate a black frame of the same size
536 * as the previous image, instead of simply setting an empty or
537 * a NULL ClutterImage.
538 */
539 auto image_empty = clutter_image_new();
540 if (auto empty_data = (guint8 *)g_try_malloc0((gsize)width * height * 4)) {
541 GError* error = NULL;
542 clutter_image_set_data(
543 CLUTTER_IMAGE(image_empty),
544 empty_data,
545 COGL_PIXEL_FORMAT_BGRA_8888,
546 (guint)width,
547 (guint)height,
548 (guint)width*4,
549 &error);
550 if (error) {
551 g_warning("error rendering empty image to clutter: %s", error->message);
552 g_clear_error(&error);
553 g_object_unref(image_empty);
554 return;
555 }
556 clutter_actor_set_content(actor, image_empty);
557 g_object_unref(image_empty);
558 g_free(empty_data);
559 } else {
560 clutter_actor_set_content(actor, NULL);
561 }
562 } else {
563 clutter_actor_set_content(actor, NULL);
564 }
565 }
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400566 wg_renderer->show_black_frame = false;
567 return;
568 }
569
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400570 ClutterContent *image_new = nullptr;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500571
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400572 {
573 /* the following must be done under lock in case a 'stopped' signal is
574 * received during rendering; otherwise the mem could become invalid */
575 std::lock_guard<std::mutex> lock(wg_renderer->run_mutex);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500576
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400577 if (!wg_renderer->running)
578 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400579
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400580 auto renderer = wg_renderer->renderer;
581 if (renderer == nullptr)
582 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400583
Guillaume Rogueza8860ea2015-10-13 18:04:39 -0400584 auto frame_ptr = renderer->currentFrame();
585 auto frame_data = frame_ptr.ptr;
Stepan Salenikovichd6a4ef32015-11-12 10:59:02 -0500586 if (!frame_data)
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400587 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400588
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400589 image_new = clutter_image_new();
590 g_return_if_fail(image_new);
591
592 const auto& res = renderer->size();
593 const gint BPP = 4;
594 const gint ROW_STRIDE = BPP * res.width();
595
596 GError *error = nullptr;
597 clutter_image_set_data(
598 CLUTTER_IMAGE(image_new),
Guillaume Rogueza8860ea2015-10-13 18:04:39 -0400599 frame_data,
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400600 COGL_PIXEL_FORMAT_BGRA_8888,
601 res.width(),
602 res.height(),
603 ROW_STRIDE,
604 &error);
605 if (error) {
606 g_warning("error rendering image to clutter: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400607 g_clear_error(&error);
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400608 g_object_unref (image_new);
609 return;
610 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500611 }
612
Guillaume Roguez24834e02015-04-09 12:45:40 -0400613 clutter_actor_set_content(actor, image_new);
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400614 g_object_unref (image_new);
Guillaume Roguez24834e02015-04-09 12:45:40 -0400615
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400616 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
617 * that the aspect ratio is correct
618 */
Guillaume Roguez24834e02015-04-09 12:45:40 -0400619 clutter_actor_set_content_gravity(actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400620}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500621
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400622static gboolean
623check_frame_queue(VideoWidget *self)
624{
625 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
626 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
627
Guillaume Roguez24834e02015-04-09 12:45:40 -0400628 /* display renderer's frames */
629 clutter_render_image(priv->local);
630 clutter_render_image(priv->remote);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400631
632 return TRUE; /* keep going */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500633}
634
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400635static void
636renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500637{
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400638 {
639 /* must do this under lock, in case the rendering is taking place when
640 * this signal is received */
641 std::lock_guard<std::mutex> lock(renderer->run_mutex);
642 renderer->running = false;
643 }
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400644 /* ask to show a black frame */
645 renderer->show_black_frame = true;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500646}
647
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400648static void
649renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500650{
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400651 {
652 std::lock_guard<std::mutex> lock(renderer->run_mutex);
653 renderer->running = true;
654 }
Stepan Salenikovichd6a4ef32015-11-12 10:59:02 -0500655 renderer->show_black_frame = false;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500656}
657
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400658static void
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400659free_video_widget_renderer(VideoWidgetRenderer *renderer)
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400660{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400661 QObject::disconnect(renderer->render_stop);
662 QObject::disconnect(renderer->render_start);
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400663 g_free(renderer);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400664}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500665
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400666static void
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400667video_widget_add_renderer(VideoWidget *self, VideoWidgetRenderer *new_video_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500668{
669 g_return_if_fail(IS_VIDEO_WIDGET(self));
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400670 g_return_if_fail(new_video_renderer);
671 g_return_if_fail(new_video_renderer->renderer);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500672
673 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
674
675 /* update the renderer */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400676 switch(new_video_renderer->type) {
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400677 case VIDEO_RENDERER_REMOTE:
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400678 /* swap the remote renderer */
679 new_video_renderer->actor = priv->remote->actor;
680 free_video_widget_renderer(priv->remote);
681 priv->remote = new_video_renderer;
682 /* reset the content gravity so that the aspect ratio gets properly
683 * reset if it chagnes */
684 clutter_actor_set_content_gravity(priv->remote->actor,
685 CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400686 break;
687 case VIDEO_RENDERER_LOCAL:
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400688 /* swap the remote renderer */
689 new_video_renderer->actor = priv->local->actor;
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400690 new_video_renderer->drag_action = priv->local->drag_action;
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400691 free_video_widget_renderer(priv->local);
692 priv->local = new_video_renderer;
693 /* reset the content gravity so that the aspect ratio gets properly
694 * reset if it chagnes */
695 clutter_actor_set_content_gravity(priv->local->actor,
696 CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400697 break;
698 case VIDEO_RENDERER_COUNT:
699 break;
700 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500701}
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400702
703static gboolean
704check_renderer_queue(VideoWidget *self)
705{
706 g_return_val_if_fail(IS_VIDEO_WIDGET(self), G_SOURCE_REMOVE);
707 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
708
709 /* get all the renderers in the queue */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400710 VideoWidgetRenderer *new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400711 while (new_video_renderer) {
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400712 video_widget_add_renderer(self, new_video_renderer);
713 new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400714 }
715
716 return G_SOURCE_CONTINUE;
717}
718
719/*
720 * video_widget_new()
721 *
722 * The function use to create a new video_widget
723 */
724GtkWidget*
725video_widget_new(void)
726{
727 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
728 return self;
729}
730
731/**
732 * video_widget_push_new_renderer()
733 *
734 * This function is used add a new Video::Renderer to the VideoWidget in a
735 * thread-safe manner.
736 */
737void
738video_widget_push_new_renderer(VideoWidget *self, Video::Renderer *renderer, VideoRendererType type)
739{
740 g_return_if_fail(IS_VIDEO_WIDGET(self));
741 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
742
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400743 /* if the renderer is nullptr, there is nothing to be done */
744 if (!renderer) return;
745
746 VideoWidgetRenderer *new_video_renderer = g_new0(VideoWidgetRenderer, 1);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400747 new_video_renderer->renderer = renderer;
748 new_video_renderer->type = type;
749
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400750 if (new_video_renderer->renderer->isRendering())
751 renderer_start(new_video_renderer);
752
753 new_video_renderer->render_stop = QObject::connect(
754 new_video_renderer->renderer,
755 &Video::Renderer::stopped,
756 [=]() {
757 renderer_stop(new_video_renderer);
758 }
759 );
760
761 new_video_renderer->render_start = QObject::connect(
762 new_video_renderer->renderer,
763 &Video::Renderer::started,
764 [=]() {
765 renderer_start(new_video_renderer);
766 }
767 );
768
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400769 g_async_queue_push(priv->new_renderer_queue, new_video_renderer);
770}
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400771
772void
773video_widget_pause_rendering(VideoWidget *self, gboolean pause)
774{
775 g_return_if_fail(IS_VIDEO_WIDGET(self));
776 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
777
778 priv->local->pause_rendering = pause;
779 priv->remote->pause_rendering = pause;
780}