blob: 44fef6112096b7071256a58820e62b146811840e [file] [log] [blame]
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001/*
2 * Copyright (C) 2004-2015 Savoir-Faire Linux Inc.
3 * Author: Sebastien Bourdelin <sebastien.bourdelin@savoirfairelinux.com>
4 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 *
20 * Additional permission under GNU GPL version 3 section 7:
21 *
22 * If you modify this program, or any covered work, by linking or
23 * combining it with the OpenSSL project's OpenSSL library (or a
24 * modified version of that library), containing parts covered by the
25 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
26 * grants you additional permission to convey the resulting work.
27 * Corresponding Source for a non-source form of such a combination
28 * shall include the source code for the parts of OpenSSL used as well
29 * as that of the covered work.
30 */
31
32#include "video_widget.h"
33
34#include <clutter/clutter.h>
35#include <clutter-gtk/clutter-gtk.h>
36#include <video/renderer.h>
37
38struct _VideoWidgetClass {
39 GtkBinClass parent_class;
40};
41
42struct _VideoWidget {
43 GtkBin parent;
44};
45
46typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
47
48struct _VideoWidgetPrivate {
49 GtkWidget *clutter_widget;
50 ClutterActor *stage;
51 ClutterActor *video_container;
52
53 /* remote peer data */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050054 ClutterActor *actor_remote;
55 Video::Renderer *renderer_remote;
56 QMetaObject::Connection remote_frame_update;
57 QMetaObject::Connection remote_render_stop;
58 QMetaObject::Connection remote_render_start;
59
60 /* local peer data */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050061 ClutterActor *actor_local;
62 Video::Renderer *renderer_local;
63 QMetaObject::Connection local_frame_update;
64};
65
66G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN);
67
68#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
69
70typedef struct _FrameInfo
71{
72 ClutterActor *image_actor;
73 ClutterContent *image;
74 guchar *data;
75 gint data_size;
76 gint width;
77 gint height;
78} FrameInfo;
79
80/*
81 * video_widget_dispose()
82 *
83 * The dispose function for the video_widget class.
84 */
85static void
86video_widget_dispose(GObject *gobject)
87{
88 VideoWidget *self = VIDEO_WIDGET(gobject);
89 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
90
91 QObject::disconnect(priv->remote_frame_update);
92 QObject::disconnect(priv->remote_render_stop);
93 QObject::disconnect(priv->remote_render_start);
94
95 G_OBJECT_CLASS(video_widget_parent_class)->dispose(gobject);
96}
97
98
99/*
100 * video_widget_finalize()
101 *
102 * The finalize function for the video_widget class.
103 */
104static void
105video_widget_finalize(GObject *object)
106{
107 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
108}
109
110
111/*
112 * video_widget_class_init()
113 *
114 * This function init the video_widget_class.
115 */
116static void
117video_widget_class_init(VideoWidgetClass *klass)
118{
119 GObjectClass *object_class = G_OBJECT_CLASS(klass);
120
121 /* override method */
122 object_class->dispose = video_widget_dispose;
123 object_class->finalize = video_widget_finalize;
124}
125
126
127/*
128 * video_widget_init()
129 *
130 * This function init the video_widget.
131 * - init clutter
132 * - init all the widget members
133 */
134static void
135video_widget_init(VideoWidget *self)
136{
137 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
138
139 /* init clutter widget */
140 priv->clutter_widget = gtk_clutter_embed_new();
141 /* add it to the video_widget */
142 gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget);
143 /* get the stage */
144 priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget));
145
146 /* layout manager is used to arrange children in space, here we ask clutter
147 * to align children to fill the space when resizing the window */
148 clutter_actor_set_layout_manager(priv->stage,
149 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
150
151 /* add a scene container where we can add and remove our actors */
152 priv->video_container = clutter_actor_new();
153 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
154 clutter_actor_add_child(priv->stage, priv->video_container);
155
156 /* TODO: handle button event in screen */
157 // g_signal_connect(screen, "button-press-event",
158 // G_CALLBACK(on_button_press_in_screen_event_cb),
159 // self);
160
161 /* TODO: init drag & drop images */
162 // gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
163 // gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
164 // g_signal_connect(GTK_WIDGET(self), "drag-data-received",
165 // G_CALLBACK(on_drag_data_received_cb), NULL);
166}
167
168static FrameInfo *
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400169prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500170{
171 const QByteArray& data = renderer->currentFrame();
172 QSize res = renderer->size();
173
174 /* copy frame data */
175 gpointer frame_data = g_memdup((gconstpointer)data.constData(), data.size());
176
177 FrameInfo *frame = g_new0(FrameInfo, 1);
178
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500179 frame->image_actor = image_actor;
180 frame->data = (guchar *)frame_data;
181 frame->data_size = data.size();
182 frame->width = res.width();
183 frame->height = res.height();
184
185 return frame;
186}
187
188static void
189free_framedata(gpointer data)
190{
191 FrameInfo *frame = (FrameInfo *)data;
192 g_free(frame->data);
193 g_free(frame);
194}
195
196static gboolean
197clutter_render_image(FrameInfo *frame)
198{
199 g_return_val_if_fail(CLUTTER_IS_ACTOR(frame->image_actor), FALSE);
200
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400201 ClutterContent * image_new = clutter_image_new();
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500202
203 const gint BPP = 4;
204 const gint ROW_STRIDE = BPP * frame->width;
205
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500206 GError *error = NULL;
207 clutter_image_set_data(
208 CLUTTER_IMAGE(image_new),
209 frame->data,
210 COGL_PIXEL_FORMAT_BGRA_8888,
211 frame->width,
212 frame->height,
213 ROW_STRIDE,
214 &error);
215 if (error) {
216 g_warning("error rendering image to clutter: %s", error->message);
217 g_error_free(error);
218 }
219
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400220 clutter_actor_set_content(frame->image_actor, image_new);
221 g_object_unref (image_new);
222 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
223 * that the aspect ratio is correct
224 */
225 clutter_actor_set_content_gravity(frame->image_actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500226
227 return FALSE; /* we do not want this function to be called again */
228}
229
230static gboolean
231renderer_stop(VideoWidget *self)
232{
233 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
234
235 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
236
237 QObject::disconnect(priv->remote_frame_update);
238
239 return FALSE; /* don't call again */
240}
241
242static gboolean
243renderer_start(VideoWidget *self)
244{
245 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
246
247 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
248
249 priv->remote_frame_update = QObject::connect(
250 priv->renderer_remote,
251 &Video::Renderer::frameUpdated,
252 [=]() {
253
254 /* this callback comes from another thread;
255 * rendering must be done in the main loop;
256 * copy the frame and add it as a task on the main loop
257 */
258
259 /* for now use the video container for the remote image */
260 FrameInfo *frame = prepare_framedata(priv->renderer_remote,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500261 priv->video_container);
262
263 g_idle_add_full(G_PRIORITY_HIGH_IDLE,
264 (GSourceFunc)clutter_render_image,
265 frame,
266 (GDestroyNotify)free_framedata);
267 }
268 );
269
270 return FALSE; /* don't call again */
271}
272
273
274/*
275 * video_widget_new()
276 *
277 * The function use to create a new video_widget
278 */
279GtkWidget*
280video_widget_new(void)
281{
282 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
283 return self;
284}
285
286void
287video_widget_set_remote_renderer(VideoWidget *self, Video::Renderer *renderer_remote_new)
288{
289 g_return_if_fail(IS_VIDEO_WIDGET(self));
290 if (renderer_remote_new == NULL) return;
291
292 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
293
294 /* update the renderer */
295 QObject::disconnect(priv->remote_frame_update);
296 QObject::disconnect(priv->remote_render_stop);
297 QObject::disconnect(priv->remote_render_start);
298 priv->renderer_remote = renderer_remote_new;
299
300 renderer_start(self);
301
302 priv->remote_render_stop = QObject::connect(
303 renderer_remote_new,
304 &Video::Renderer::stopped,
305 [=]() {
306 g_idle_add_full(G_PRIORITY_HIGH_IDLE,
307 (GSourceFunc)renderer_stop,
308 self,
309 NULL);
310 }
311 );
312
313 priv->remote_render_start = QObject::connect(
314 renderer_remote_new,
315 &Video::Renderer::started,
316 [=]() {
317 g_idle_add_full(G_PRIORITY_HIGH_IDLE,
318 (GSourceFunc)renderer_start,
319 self,
320 NULL);
321 }
322 );
323}