blob: 2d5824da1e2ce5d5e8d909826725414b67d6981d [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>
Stepan Salenikovich50c989b2015-03-21 18:32:46 -040038#include <video/devicemodel.h>
39#include <QtCore/QUrl>
40#include "../defines.h"
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -040041#include <stdlib.h>
Guillaume Roguez24834e02015-04-09 12:45:40 -040042#include <atomic>
Stepan Salenikovichaea6c042015-04-29 15:09:16 -040043#include <mutex>
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -040044#include "xrectsel.h"
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050045
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040046static constexpr int VIDEO_LOCAL_SIZE = 150;
47static constexpr int VIDEO_LOCAL_OPACITY_DEFAULT = 255; /* out of 255 */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040048
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040049/* check video frame queues at this rate;
50 * use 30 ms (about 30 fps) since we don't expect to
51 * receive video frames faster than that */
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040052static constexpr int FRAME_RATE_PERIOD = 30;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040053
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050054struct _VideoWidgetClass {
Stepan Salenikovich36ef3942015-11-05 18:26:57 -050055 GtkClutterEmbedClass parent_class;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050056};
57
58struct _VideoWidget {
Stepan Salenikovich36ef3942015-11-05 18:26:57 -050059 GtkClutterEmbed parent;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050060};
61
62typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
63
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040064typedef struct _VideoWidgetRenderer VideoWidgetRenderer;
65
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050066struct _VideoWidgetPrivate {
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050067 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;
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -040089 ClutterAction *drag_action;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040090 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 Salenikovichb94873c2015-06-02 16:53:18 -0400101 std::atomic_bool pause_rendering;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400102 QMetaObject::Connection frame_update;
103 QMetaObject::Connection render_stop;
104 QMetaObject::Connection render_start;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500105};
106
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500107G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_CLUTTER_TYPE_EMBED);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500108
109#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
110
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400111/* static prototypes */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400112static gboolean check_frame_queue (VideoWidget *);
113static void renderer_stop (VideoWidgetRenderer *);
114static void renderer_start (VideoWidgetRenderer *);
115static void on_drag_data_received (GtkWidget *, GdkDragContext *, gint, gint, GtkSelectionData *, guint, guint32, gpointer);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400116static gboolean on_button_press_in_screen_event(GtkWidget *, GdkEventButton *, gpointer);
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400117static gboolean check_renderer_queue (VideoWidget *);
118static void free_video_widget_renderer (VideoWidgetRenderer *);
119static void video_widget_add_renderer (VideoWidget *, VideoWidgetRenderer *);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400120
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500121/*
122 * video_widget_dispose()
123 *
124 * The dispose function for the video_widget class.
125 */
126static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400127video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500128{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400129 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500130 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
131
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400132 /* dispose may be called multiple times, make sure
133 * not to call g_source_remove more than once */
134 if (priv->frame_timeout_source) {
135 g_source_remove(priv->frame_timeout_source);
136 priv->frame_timeout_source = 0;
137 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400138
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400139 if (priv->renderer_timeout_source) {
140 g_source_remove(priv->renderer_timeout_source);
141 priv->renderer_timeout_source = 0;
142 }
143
144 if (priv->new_renderer_queue) {
145 g_async_queue_unref(priv->new_renderer_queue);
146 priv->new_renderer_queue = NULL;
147 }
148
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400149 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500150}
151
152
153/*
154 * video_widget_finalize()
155 *
156 * The finalize function for the video_widget class.
157 */
158static void
159video_widget_finalize(GObject *object)
160{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400161 VideoWidget *self = VIDEO_WIDGET(object);
162 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
163
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400164 free_video_widget_renderer(priv->local);
165 free_video_widget_renderer(priv->remote);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400166
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500167 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
168}
169
170
171/*
172 * video_widget_class_init()
173 *
174 * This function init the video_widget_class.
175 */
176static void
177video_widget_class_init(VideoWidgetClass *klass)
178{
179 GObjectClass *object_class = G_OBJECT_CLASS(klass);
180
181 /* override method */
182 object_class->dispose = video_widget_dispose;
183 object_class->finalize = video_widget_finalize;
184}
185
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400186static void
187on_allocation_changed(ClutterActor *video_area, G_GNUC_UNUSED GParamSpec *pspec, VideoWidget *self)
188{
189 g_return_if_fail(IS_VIDEO_WIDGET(self));
190 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
191
192 auto actor = priv->local->actor;
193 auto drag_action = priv->local->drag_action;
194
195 ClutterActorBox actor_box;
196 clutter_actor_get_allocation_box(actor, &actor_box);
197 gfloat actor_w = clutter_actor_box_get_width(&actor_box);
198 gfloat actor_h = clutter_actor_box_get_height(&actor_box);
199
200 ClutterActorBox area_box;
201 clutter_actor_get_allocation_box(video_area, &area_box);
202 gfloat area_w = clutter_actor_box_get_width(&area_box);
203 gfloat area_h = clutter_actor_box_get_height(&area_box);
204
205 /* make sure drag area stays within the bounds of the stage */
206 ClutterRect *rect = clutter_rect_init (
207 clutter_rect_alloc(),
208 0, 0,
209 area_w - actor_w,
210 area_h - actor_h);
211 clutter_drag_action_set_drag_area(CLUTTER_DRAG_ACTION(drag_action), rect);
212 clutter_rect_free(rect);
213}
214
215static void
216on_drag_begin(G_GNUC_UNUSED ClutterDragAction *action,
217 ClutterActor *actor,
218 G_GNUC_UNUSED gfloat event_x,
219 G_GNUC_UNUSED gfloat event_y,
220 G_GNUC_UNUSED ClutterModifierType modifiers,
221 G_GNUC_UNUSED gpointer user_data)
222{
223 /* clear the align constraint when starting to move the preview, otherwise
224 * it won't move; save and set its position, to what it was before the
225 * constraint was cleared, or else it might jump around */
226 gfloat actor_x, actor_y;
227 clutter_actor_get_position(actor, &actor_x, &actor_y);
228 clutter_actor_clear_constraints(actor);
229 clutter_actor_set_position(actor, actor_x, actor_y);
230}
231
232static void
233on_drag_end(G_GNUC_UNUSED ClutterDragAction *action,
234 ClutterActor *actor,
235 G_GNUC_UNUSED gfloat event_x,
236 G_GNUC_UNUSED gfloat event_y,
237 G_GNUC_UNUSED ClutterModifierType modifiers,
238 ClutterActor *video_area)
239{
240 ClutterActorBox area_box;
241 clutter_actor_get_allocation_box(video_area, &area_box);
242 gfloat area_w = clutter_actor_box_get_width(&area_box);
243 gfloat area_h = clutter_actor_box_get_height(&area_box);
244
245 gfloat actor_x, actor_y;
246 clutter_actor_get_position(actor, &actor_x, &actor_y);
247 gfloat actor_w, actor_h;
248 clutter_actor_get_size(actor, &actor_w, &actor_h);
249
250 area_w -= actor_w;
251 area_h -= actor_h;
252
253 /* add new constraints to make sure the preview stays in about the same location
254 * relative to the rest of the video when resizing */
255 ClutterConstraint *constraint_x = clutter_align_constraint_new(video_area,
256 CLUTTER_ALIGN_X_AXIS, actor_x/area_w);
257 clutter_actor_add_constraint(actor, constraint_x);
258
259 ClutterConstraint *constraint_y = clutter_align_constraint_new(video_area,
260 CLUTTER_ALIGN_Y_AXIS, actor_y/area_h);
261 clutter_actor_add_constraint(actor, constraint_y);
262}
263
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500264
265/*
266 * video_widget_init()
267 *
268 * This function init the video_widget.
269 * - init clutter
270 * - init all the widget members
271 */
272static void
273video_widget_init(VideoWidget *self)
274{
275 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
276
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500277 auto stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(self));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500278
279 /* layout manager is used to arrange children in space, here we ask clutter
280 * to align children to fill the space when resizing the window */
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500281 clutter_actor_set_layout_manager(stage,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500282 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
283
284 /* add a scene container where we can add and remove our actors */
285 priv->video_container = clutter_actor_new();
286 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500287 clutter_actor_add_child(stage, priv->video_container);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500288
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400289 /* init the remote and local structs */
290 priv->remote = g_new0(VideoWidgetRenderer, 1);
291 priv->local = g_new0(VideoWidgetRenderer, 1);
292
293 /* arrange remote actors */
294 priv->remote->actor = clutter_actor_new();
295 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
296 /* the remote camera must always fill the container size */
297 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
298 CLUTTER_BIND_SIZE, 0);
299 clutter_actor_add_constraint(priv->remote->actor, constraint);
300
301 /* arrange local actor */
302 priv->local->actor = clutter_actor_new();
303 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
304 /* set size to square, but it will stay the aspect ratio when the image is rendered */
305 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400306 /* set position constraint to right cornder;
307 * this constraint will be removed once the user tries to move the position
308 * of the action */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400309 constraint = clutter_align_constraint_new(priv->video_container,
310 CLUTTER_ALIGN_BOTH, 0.99);
311 clutter_actor_add_constraint(priv->local->actor, constraint);
312 clutter_actor_set_opacity(priv->local->actor,
313 VIDEO_LOCAL_OPACITY_DEFAULT);
314
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400315 /* add ability for actor to be moved */
316 clutter_actor_set_reactive(priv->local->actor, TRUE);
317 priv->local->drag_action = clutter_drag_action_new();
318 clutter_actor_add_action(priv->local->actor, priv->local->drag_action);
319
320 g_signal_connect(priv->local->drag_action, "drag-begin", G_CALLBACK(on_drag_begin), NULL);
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500321 g_signal_connect_after(priv->local->drag_action, "drag-end", G_CALLBACK(on_drag_end), stage);
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400322
323 /* make sure the actor stays within the bounds of the stage */
Stepan Salenikovich36ef3942015-11-05 18:26:57 -0500324 g_signal_connect(stage, "notify::allocation", G_CALLBACK(on_allocation_changed), self);
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400325
Stepan Salenikovich8934e842015-04-20 15:16:13 -0400326 /* Init the timeout source which will check the for new frames.
327 * The priority must be lower than GTK drawing events
328 * (G_PRIORITY_HIGH_IDLE + 20) so that this timeout source doesn't choke
329 * the main loop on slower machines.
330 */
331 priv->frame_timeout_source = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
332 FRAME_RATE_PERIOD,
333 (GSourceFunc)check_frame_queue,
334 self,
335 NULL);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400336
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400337 /* init new renderer queue */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400338 priv->new_renderer_queue = g_async_queue_new_full((GDestroyNotify)free_video_widget_renderer);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400339 /* check new render every 30 ms (30ms is "fast enough");
340 * we don't use an idle function so it doesn't consume cpu needlessly */
341 priv->renderer_timeout_source= g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
342 30,
343 (GSourceFunc)check_renderer_queue,
344 self,
345 NULL);
346
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400347 /* handle button event */
348 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 -0500349
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400350 /* drag & drop files as video sources */
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400351 gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
352 gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
353 g_signal_connect(GTK_WIDGET(self), "drag-data-received", G_CALLBACK(on_drag_data_received), NULL);
354}
355
356/*
357 * on_drag_data_received()
358 *
359 * Handle dragged data in the video widget window.
360 * Dropping an image causes the client to switch the video input to that image.
361 */
362static void
363on_drag_data_received(G_GNUC_UNUSED GtkWidget *self,
364 G_GNUC_UNUSED GdkDragContext *context,
365 G_GNUC_UNUSED gint x,
366 G_GNUC_UNUSED gint y,
367 GtkSelectionData *selection_data,
368 G_GNUC_UNUSED guint info,
369 G_GNUC_UNUSED guint32 time,
370 G_GNUC_UNUSED gpointer data)
371{
372 gchar **uris = gtk_selection_data_get_uris(selection_data);
373
374 /* only play the first selection */
375 if (uris && *uris)
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400376 Video::SourceModel::instance().setFile(QUrl(*uris));
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
382switch_video_input(G_GNUC_UNUSED GtkWidget *widget, Video::Device *device)
383{
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400384 Video::SourceModel::instance().switchTo(device);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400385}
386
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400387static void
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400388switch_video_input_screen(G_GNUC_UNUSED GtkWidget *item, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400389{
390 unsigned x, y;
391 unsigned width, height;
392
393 /* try to get the dispaly or default to 0 */
394 QString display_env{getenv("DISPLAY")};
395 int display = 0;
396
397 if (!display_env.isEmpty()) {
398 auto list = display_env.split(":", QString::SkipEmptyParts);
399 /* should only be one display, so get the first one */
400 if (list.size() > 0) {
401 display = list.at(0).toInt();
402 g_debug("sharing screen from DISPLAY %d", display);
403 }
404 }
405
406 x = y = width = height = 0;
407
408 xrectsel(&x, &y, &width, &height);
409
410 if (!width || !height) {
411 x = y = 0;
412 width = gdk_screen_width();
413 height = gdk_screen_height();
414 }
415
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400416 Video::SourceModel::instance().setDisplay(display, QRect(x,y,width,height));
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400417}
418
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400419static void
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400420switch_video_input_file(G_GNUC_UNUSED GtkWidget *item, GtkWidget *parent)
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400421{
422 if (parent && GTK_IS_WIDGET(parent)) {
423 /* get parent window */
424 parent = gtk_widget_get_toplevel(GTK_WIDGET(parent));
425 }
426
427 gchar *uri = NULL;
428 GtkWidget *dialog = gtk_file_chooser_dialog_new(
429 "Choose File",
430 GTK_WINDOW(parent),
431 GTK_FILE_CHOOSER_ACTION_OPEN,
432 "_Cancel", GTK_RESPONSE_CANCEL,
433 "_Open", GTK_RESPONSE_ACCEPT,
434 NULL);
435
436 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
437 uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400438 Video::SourceModel::instance().setFile(QUrl(uri));
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400439 }
440
441 gtk_widget_destroy(dialog);
442
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400443 g_free(uri);
444}
445
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400446/*
447 * on_button_press_in_screen_event()
448 *
449 * Handle button event in the video screen.
450 */
451static gboolean
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400452on_button_press_in_screen_event(GtkWidget *parent,
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400453 GdkEventButton *event,
454 G_GNUC_UNUSED gpointer data)
455{
456 /* check for right click */
457 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
458 return FALSE;
459
460 /* create menu with available video sources */
461 GtkWidget *menu = gtk_menu_new();
462
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400463 auto active = Video::SourceModel::instance().activeIndex();
Stepan Salenikovichcb1c2952015-08-13 14:28:20 -0400464
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400465 /* list available devices and check off the active device */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400466 auto device_list = Video::DeviceModel::instance().devices();
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400467
468 for( auto device: device_list) {
469 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(device->name().toLocal8Bit().constData());
470 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400471 auto device_idx = Video::SourceModel::instance().getDeviceIndex(device);
Stepan Salenikovichcb1c2952015-08-13 14:28:20 -0400472 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), device_idx == active);
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400473 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input), device);
474 }
475
Stepan Salenikovichbb382632015-03-26 12:40:46 -0400476 /* add separator */
477 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
478
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400479 /* add screen area as an input */
Julien Grossholtz58cfc152015-10-22 15:43:43 -0400480 GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(_("Share screen area"));
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400481 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
Stepan Salenikovichcb1c2952015-08-13 14:28:20 -0400482 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), Video::SourceModel::ExtendedDeviceList::SCREEN == active);
Stepan Salenikovich8bc51e52015-03-21 20:17:29 -0400483 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_screen), NULL);
484
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400485 /* add file as an input */
Julien Grossholtz58cfc152015-10-22 15:43:43 -0400486 item = gtk_check_menu_item_new_with_mnemonic(_("Share file"));
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400487 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
Stepan Salenikovichcb1c2952015-08-13 14:28:20 -0400488 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), Video::SourceModel::ExtendedDeviceList::FILE == active);
Stepan Salenikovich763c25a2015-03-26 13:51:31 -0400489 g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_file), parent);
490
Stepan Salenikovich50c989b2015-03-21 18:32:46 -0400491 /* show menu */
492 gtk_widget_show_all(menu);
493 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
494
495 return TRUE; /* event has been fully handled */
496}
497
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500498static void
Guillaume Roguez24834e02015-04-09 12:45:40 -0400499clutter_render_image(VideoWidgetRenderer* wg_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500500{
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400501 auto actor = wg_renderer->actor;
502 g_return_if_fail(CLUTTER_IS_ACTOR(actor));
503
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400504 if (wg_renderer->pause_rendering)
505 return;
506
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400507 if (wg_renderer->show_black_frame) {
508 /* render a black frame set the bool back to false, this is likely done
509 * when the renderer is stopped so we ignore whether or not it is running
510 */
Stepan Salenikovich6e7b0922015-05-05 17:51:30 -0400511 if (auto image_old = clutter_actor_get_content(actor)) {
512 gfloat width;
513 gfloat height;
514 if (clutter_content_get_preferred_size(image_old, &width, &height)) {
515 /* NOTE: this is a workaround for #72531, a crash which occurs
516 * in cogl < 1.18. We allocate a black frame of the same size
517 * as the previous image, instead of simply setting an empty or
518 * a NULL ClutterImage.
519 */
520 auto image_empty = clutter_image_new();
521 if (auto empty_data = (guint8 *)g_try_malloc0((gsize)width * height * 4)) {
522 GError* error = NULL;
523 clutter_image_set_data(
524 CLUTTER_IMAGE(image_empty),
525 empty_data,
526 COGL_PIXEL_FORMAT_BGRA_8888,
527 (guint)width,
528 (guint)height,
529 (guint)width*4,
530 &error);
531 if (error) {
532 g_warning("error rendering empty image to clutter: %s", error->message);
533 g_clear_error(&error);
534 g_object_unref(image_empty);
535 return;
536 }
537 clutter_actor_set_content(actor, image_empty);
538 g_object_unref(image_empty);
539 g_free(empty_data);
540 } else {
541 clutter_actor_set_content(actor, NULL);
542 }
543 } else {
544 clutter_actor_set_content(actor, NULL);
545 }
546 }
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400547 wg_renderer->show_black_frame = false;
548 return;
549 }
550
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400551 ClutterContent *image_new = nullptr;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500552
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400553 {
554 /* the following must be done under lock in case a 'stopped' signal is
555 * received during rendering; otherwise the mem could become invalid */
556 std::lock_guard<std::mutex> lock(wg_renderer->run_mutex);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500557
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400558 if (!wg_renderer->running)
559 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400560
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400561 auto renderer = wg_renderer->renderer;
562 if (renderer == nullptr)
563 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400564
Guillaume Rogueza8860ea2015-10-13 18:04:39 -0400565 auto frame_ptr = renderer->currentFrame();
566 auto frame_data = frame_ptr.ptr;
567 if (!frame_data or !wg_renderer->dirty)
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400568 return;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400569
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400570 wg_renderer->dirty = false;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500571
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400572 image_new = clutter_image_new();
573 g_return_if_fail(image_new);
574
575 const auto& res = renderer->size();
576 const gint BPP = 4;
577 const gint ROW_STRIDE = BPP * res.width();
578
579 GError *error = nullptr;
580 clutter_image_set_data(
581 CLUTTER_IMAGE(image_new),
Guillaume Rogueza8860ea2015-10-13 18:04:39 -0400582 frame_data,
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400583 COGL_PIXEL_FORMAT_BGRA_8888,
584 res.width(),
585 res.height(),
586 ROW_STRIDE,
587 &error);
588 if (error) {
589 g_warning("error rendering image to clutter: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400590 g_clear_error(&error);
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400591 g_object_unref (image_new);
592 return;
593 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500594 }
595
Guillaume Roguez24834e02015-04-09 12:45:40 -0400596 clutter_actor_set_content(actor, image_new);
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400597 g_object_unref (image_new);
Guillaume Roguez24834e02015-04-09 12:45:40 -0400598
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400599 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
600 * that the aspect ratio is correct
601 */
Guillaume Roguez24834e02015-04-09 12:45:40 -0400602 clutter_actor_set_content_gravity(actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400603}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500604
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400605static gboolean
606check_frame_queue(VideoWidget *self)
607{
608 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
609 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
610
Guillaume Roguez24834e02015-04-09 12:45:40 -0400611 /* display renderer's frames */
612 clutter_render_image(priv->local);
613 clutter_render_image(priv->remote);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400614
615 return TRUE; /* keep going */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500616}
617
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400618static void
619renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500620{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400621 QObject::disconnect(renderer->frame_update);
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400622 {
623 /* must do this under lock, in case the rendering is taking place when
624 * this signal is received */
625 std::lock_guard<std::mutex> lock(renderer->run_mutex);
626 renderer->running = false;
627 }
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400628 /* ask to show a black frame */
629 renderer->show_black_frame = true;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500630}
631
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400632static void
633renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500634{
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400635 QObject::disconnect(renderer->frame_update);
Stepan Salenikovichaea6c042015-04-29 15:09:16 -0400636 {
637 std::lock_guard<std::mutex> lock(renderer->run_mutex);
638 renderer->running = true;
639 }
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400640 renderer->frame_update = QObject::connect(
641 renderer->renderer,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500642 &Video::Renderer::frameUpdated,
Guillaume Roguez24834e02015-04-09 12:45:40 -0400643 [renderer]() {
644 // WARNING: this lambda is called in LRC renderer thread,
645 // but check_frame_queue() is in mainloop!
Stepan Salenikovichbf118ac2015-04-22 14:06:50 -0400646
647 /* make sure show_black_frame is false since it will take priority
648 * over a new frame from the Video::Renderer */
649 renderer->show_black_frame = false;
Guillaume Roguez24834e02015-04-09 12:45:40 -0400650 renderer->dirty = true;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500651 }
Guillaume Roguez24834e02015-04-09 12:45:40 -0400652 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500653}
654
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400655static void
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400656free_video_widget_renderer(VideoWidgetRenderer *renderer)
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400657{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400658 QObject::disconnect(renderer->frame_update);
659 QObject::disconnect(renderer->render_stop);
660 QObject::disconnect(renderer->render_start);
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400661 g_free(renderer);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400662}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500663
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400664static void
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400665video_widget_add_renderer(VideoWidget *self, VideoWidgetRenderer *new_video_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500666{
667 g_return_if_fail(IS_VIDEO_WIDGET(self));
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400668 g_return_if_fail(new_video_renderer);
669 g_return_if_fail(new_video_renderer->renderer);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500670
671 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
672
673 /* update the renderer */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400674 switch(new_video_renderer->type) {
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400675 case VIDEO_RENDERER_REMOTE:
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400676 /* swap the remote renderer */
677 new_video_renderer->actor = priv->remote->actor;
678 free_video_widget_renderer(priv->remote);
679 priv->remote = new_video_renderer;
680 /* reset the content gravity so that the aspect ratio gets properly
681 * reset if it chagnes */
682 clutter_actor_set_content_gravity(priv->remote->actor,
683 CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400684 break;
685 case VIDEO_RENDERER_LOCAL:
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400686 /* swap the remote renderer */
687 new_video_renderer->actor = priv->local->actor;
Stepan Salenikovich223b2fd2015-06-22 12:52:21 -0400688 new_video_renderer->drag_action = priv->local->drag_action;
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400689 free_video_widget_renderer(priv->local);
690 priv->local = new_video_renderer;
691 /* reset the content gravity so that the aspect ratio gets properly
692 * reset if it chagnes */
693 clutter_actor_set_content_gravity(priv->local->actor,
694 CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400695 break;
696 case VIDEO_RENDERER_COUNT:
697 break;
698 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500699}
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400700
701static gboolean
702check_renderer_queue(VideoWidget *self)
703{
704 g_return_val_if_fail(IS_VIDEO_WIDGET(self), G_SOURCE_REMOVE);
705 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
706
707 /* get all the renderers in the queue */
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400708 VideoWidgetRenderer *new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400709 while (new_video_renderer) {
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400710 video_widget_add_renderer(self, new_video_renderer);
711 new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400712 }
713
714 return G_SOURCE_CONTINUE;
715}
716
717/*
718 * video_widget_new()
719 *
720 * The function use to create a new video_widget
721 */
722GtkWidget*
723video_widget_new(void)
724{
725 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
726 return self;
727}
728
729/**
730 * video_widget_push_new_renderer()
731 *
732 * This function is used add a new Video::Renderer to the VideoWidget in a
733 * thread-safe manner.
734 */
735void
736video_widget_push_new_renderer(VideoWidget *self, Video::Renderer *renderer, VideoRendererType type)
737{
738 g_return_if_fail(IS_VIDEO_WIDGET(self));
739 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
740
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400741 /* if the renderer is nullptr, there is nothing to be done */
742 if (!renderer) return;
743
744 VideoWidgetRenderer *new_video_renderer = g_new0(VideoWidgetRenderer, 1);
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400745 new_video_renderer->renderer = renderer;
746 new_video_renderer->type = type;
747
Stepan Salenikovich3bcb0b22015-04-21 18:44:55 -0400748 if (new_video_renderer->renderer->isRendering())
749 renderer_start(new_video_renderer);
750
751 new_video_renderer->render_stop = QObject::connect(
752 new_video_renderer->renderer,
753 &Video::Renderer::stopped,
754 [=]() {
755 renderer_stop(new_video_renderer);
756 }
757 );
758
759 new_video_renderer->render_start = QObject::connect(
760 new_video_renderer->renderer,
761 &Video::Renderer::started,
762 [=]() {
763 renderer_start(new_video_renderer);
764 }
765 );
766
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400767 g_async_queue_push(priv->new_renderer_queue, new_video_renderer);
768}
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400769
770void
771video_widget_pause_rendering(VideoWidget *self, gboolean pause)
772{
773 g_return_if_fail(IS_VIDEO_WIDGET(self));
774 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
775
776 priv->local->pause_rendering = pause;
777 priv->remote->pause_rendering = pause;
778}