blob: eac3dc3e740b21ba75a47e5ece6500bff8ba585f [file] [log] [blame]
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05001/*
2 * Copyright (C) 2015 Savoir-Faire Linux Inc.
3 * 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 "currentcallview.h"
32
33#include <gtk/gtk.h>
34#include <call.h>
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050035#include <callmodel.h>
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050036#include "utils/drawing.h"
37#include "video/video_widget.h"
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040038#include <video/previewmanager.h>
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050039
40struct _CurrentCallView
41{
42 GtkBox parent;
43};
44
45struct _CurrentCallViewClass
46{
47 GtkBoxClass parent_class;
48};
49
50typedef struct _CurrentCallViewPrivate CurrentCallViewPrivate;
51
52struct _CurrentCallViewPrivate
53{
54 GtkWidget *image_peer;
55 GtkWidget *label_identity;
56 GtkWidget *label_status;
57 GtkWidget *label_duration;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050058 GtkWidget *frame_video;
59 GtkWidget *video_widget;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040060 GtkWidget *button_hangup;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050061
Stepan Salenikovichc5f08152015-03-19 00:53:23 -040062 /* new renderers should be put into the queue for processing by a g_idle
63 * functions whose id should be saved into renderer_idle_source;
64 * this way when the CurrentCallView object is destroyed, we do not try
65 * to process any new renderers by stoping the g_idle function.
66 */
67 guint renderer_idle_source;
68 GAsyncQueue *new_renderer_queue;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050069
70 QMetaObject::Connection state_change_connection;
71 QMetaObject::Connection call_details_connection;
Stepan Salenikovichc5f08152015-03-19 00:53:23 -040072 QMetaObject::Connection local_renderer_connection;
73 QMetaObject::Connection remote_renderer_connection;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050074};
75
76G_DEFINE_TYPE_WITH_PRIVATE(CurrentCallView, current_call_view, GTK_TYPE_BOX);
77
78#define CURRENT_CALL_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CURRENT_CALL_VIEW_TYPE, CurrentCallViewPrivate))
79
80static void
81current_call_view_dispose(GObject *object)
82{
83 CurrentCallView *view;
84 CurrentCallViewPrivate *priv;
85
86 view = CURRENT_CALL_VIEW(object);
87 priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
88
89 QObject::disconnect(priv->state_change_connection);
90 QObject::disconnect(priv->call_details_connection);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -040091 QObject::disconnect(priv->local_renderer_connection);
92 QObject::disconnect(priv->remote_renderer_connection);
93
94 /* dispose may be called multiple times, make sure
95 * not to call g_source_remove more than once */
96 if (priv->renderer_idle_source) {
97 g_source_remove(priv->renderer_idle_source);
98 priv->renderer_idle_source = 0;
99 }
100
101 if (priv->new_renderer_queue) {
102 g_async_queue_unref(priv->new_renderer_queue);
103 priv->new_renderer_queue = NULL;
104 }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500105
106 G_OBJECT_CLASS(current_call_view_parent_class)->dispose(object);
107}
108
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400109static gboolean
110check_renderer_queue(CurrentCallView *self)
111{
112 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
113 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
114
115 /* get all the renderers in the queue */
116 gpointer new_video_renderer = g_async_queue_try_pop(priv->new_renderer_queue);
117 while (new_video_renderer) {
118 video_widget_add_renderer(VIDEO_WIDGET(priv->video_widget), (const VideoRenderer *)new_video_renderer);
119 g_free(new_video_renderer);
120 new_video_renderer = g_async_queue_try_pop(priv->new_renderer_queue);
121 }
122
123 return TRUE; /* keep going */
124}
125
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500126static void
127current_call_view_init(CurrentCallView *view)
128{
129 gtk_widget_init_template(GTK_WIDGET(view));
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400130
131 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
132
133 /* init new renderer queue */
134 priv->new_renderer_queue = g_async_queue_new_full((GDestroyNotify)g_free);
135 /* check new render queue when idle */
136 priv->renderer_idle_source = g_idle_add_full(
137 G_PRIORITY_DEFAULT_IDLE,
138 (GSourceFunc)check_renderer_queue,
139 view,
140 NULL);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500141}
142
143static void
144current_call_view_class_init(CurrentCallViewClass *klass)
145{
146 G_OBJECT_CLASS(klass)->dispose = current_call_view_dispose;
147
148 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
149 "/cx/ring/RingGnome/currentcallview.ui");
150
151 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, image_peer);
152 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_identity);
153 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_status);
154 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_duration);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500155 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_video);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400156 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, button_hangup);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500157}
158
159GtkWidget *
160current_call_view_new(void)
161{
162 return (GtkWidget *)g_object_new(CURRENT_CALL_VIEW_TYPE, NULL);
163}
164
165static void
166update_state(CurrentCallView *view, Call *call)
167{
168 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
169
170 /* change state label */
171 Call::State state = call->state();
172
173 switch(state) {
174 case Call::State::INCOMING:
175 gtk_label_set_text(GTK_LABEL(priv->label_status), "Incoming...");
176 break;
177 case Call::State::RINGING:
178 gtk_label_set_text(GTK_LABEL(priv->label_status), "Ringing...");
179 break;
180 case Call::State::CURRENT:
181 /* note: shouldn't be displayed, as the view should change */
182 gtk_label_set_text(GTK_LABEL(priv->label_status), "In progress.");
183 break;
184 case Call::State::DIALING:
185 gtk_label_set_text(GTK_LABEL(priv->label_status), "Dialing...");
186 break;
187 case Call::State::HOLD:
188 /* note: shouldn't be displayed, as the view should change */
189 gtk_label_set_text(GTK_LABEL(priv->label_status), "On hold.");
190 break;
191 case Call::State::FAILURE:
192 gtk_label_set_text(GTK_LABEL(priv->label_status), "Failed.");
193 break;
194 case Call::State::BUSY:
195 gtk_label_set_text(GTK_LABEL(priv->label_status), "Busy.");
196 break;
197 case Call::State::TRANSFERRED:
198 /* note: shouldn't be displayed, as the view should change */
199 gtk_label_set_text(GTK_LABEL(priv->label_status), "Transfered.");
200 break;
201 case Call::State::TRANSF_HOLD:
202 /* note: shouldn't be displayed, as the view should change */
203 gtk_label_set_text(GTK_LABEL(priv->label_status), "Transfer hold.");
204 break;
205 case Call::State::OVER:
206 /* note: shouldn't be displayed, as the view should change */
207 gtk_label_set_text(GTK_LABEL(priv->label_status), "Over.");
208 break;
209 case Call::State::ERROR:
210 gtk_label_set_text(GTK_LABEL(priv->label_status), "Error.");
211 break;
212 case Call::State::CONFERENCE:
213 /* note: shouldn't be displayed, as the view should change */
214 gtk_label_set_text(GTK_LABEL(priv->label_status), "Conference.");
215 break;
216 case Call::State::CONFERENCE_HOLD:
217 /* note: shouldn't be displayed, as the view should change */
218 gtk_label_set_text(GTK_LABEL(priv->label_status), "Conference hold.");
219 break;
220 case Call::State::INITIALIZATION:
221 gtk_label_set_text(GTK_LABEL(priv->label_status), "Initialization...");
222 break;
223 case Call::State::COUNT__:
224 break;
225 }
226}
227
228static void
229update_details(CurrentCallView *view, Call *call)
230{
231 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
232
233 /* update call duration */
234 QByteArray ba_length = call->length().toLocal8Bit();
235 gtk_label_set_text(GTK_LABEL(priv->label_duration), ba_length.constData());
236}
237
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400238static void
239push_new_renderer(CurrentCallView *self, Video::Renderer *renderer, VideoRendererType type)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500240{
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400241 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500242
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400243 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500244
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400245 VideoRenderer *new_video_renderer = g_new0(VideoRenderer, 1);
246 new_video_renderer->renderer = renderer;
247 new_video_renderer->type = type;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500248
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400249 g_async_queue_push(priv->new_renderer_queue, new_video_renderer);
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400250}
251
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400252static void
253fullscreen_destroy(CurrentCallView *view)
254{
255 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
256 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
257
258 /* check if the video widgets parent is the the fullscreen window */
259 GtkWidget *parent = gtk_widget_get_parent(priv->video_widget);
260 if (parent != NULL && parent != priv->frame_video) {
261 /* put the videw widget back in the call view */
262 g_object_ref(priv->video_widget);
263 gtk_container_remove(GTK_CONTAINER(parent), priv->video_widget);
264 gtk_container_add(GTK_CONTAINER(priv->frame_video), priv->video_widget);
265 g_object_unref(priv->video_widget);
266 /* destroy the fullscreen window */
267 gtk_widget_destroy(parent);
268 }
269}
270
271static gboolean
272fullscreen_handle_keys(GtkWidget *self, GdkEventKey *event, G_GNUC_UNUSED gpointer user_data)
273{
274 if (event->keyval == GDK_KEY_Escape)
275 gtk_widget_destroy(self);
276
277 /* the event has been fully handled */
278 return TRUE;
279}
280
281static gboolean
282on_button_press_in_video_event(GtkWidget *self, GdkEventButton *event, CurrentCallView *view)
283{
284 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
285 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), FALSE);
286 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
287
288 /* on double click */
289 if (event->type == GDK_2BUTTON_PRESS) {
290
291 /* get the parent to check if its in fullscreen window or not */
292 GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(self));
293 if (parent == priv->frame_video){
294 /* not fullscreen, so put it in a separate widget and make it so */
295 GtkWidget *fullscreen_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
296 gtk_window_set_decorated(GTK_WINDOW(fullscreen_window), FALSE);
297 gtk_window_set_transient_for(GTK_WINDOW(fullscreen_window),
298 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))));
299 g_object_ref(self);
300 gtk_container_remove(GTK_CONTAINER(priv->frame_video), self);
301 gtk_container_add(GTK_CONTAINER(fullscreen_window), self);
302 g_object_unref(self);
303 /* connect signals to make sure we can un-fullscreen */
304 g_signal_connect_swapped(fullscreen_window, "destroy", G_CALLBACK(fullscreen_destroy), view);
305 g_signal_connect(view, "destroy", G_CALLBACK(fullscreen_destroy), NULL);
306 g_signal_connect(fullscreen_window, "key_press_event", G_CALLBACK(fullscreen_handle_keys), NULL);
307 /* present the fullscreen widnow */
308 gtk_window_present(GTK_WINDOW(fullscreen_window));
309 gtk_window_fullscreen(GTK_WINDOW(fullscreen_window));
310 } else {
311 /* put it back in the call view */
312 fullscreen_destroy(view);
313 }
314 }
315
316 /* the event has been fully handled */
317 return TRUE;
318}
319
320
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500321void
322current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
323 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
324
325 /* get image and frame it */
326 GdkPixbuf *avatar = ring_draw_fallback_avatar(50);
327 GdkPixbuf *framed_avatar = ring_frame_avatar(avatar);
328 g_object_unref(avatar);
329 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_peer), framed_avatar);
330 g_object_unref(framed_avatar);
331
332 /* get name */
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400333 QVariant var = idx.model()->data(idx, static_cast<int>(Call::Role::Name));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500334 QByteArray ba_name = var.toString().toLocal8Bit();
335 gtk_label_set_text(GTK_LABEL(priv->label_identity), ba_name.constData());
336
337 /* change some things depending on call state */
338 Call *call = CallModel::instance()->getCall(idx);
339
340 update_state(view, call);
341 update_details(view, call);
342
343 priv->state_change_connection = QObject::connect(
344 call,
345 &Call::stateChanged,
346 [=]() { update_state(view, call); }
347 );
348
349 priv->call_details_connection = QObject::connect(
350 call,
351 static_cast<void (Call::*)(void)>(&Call::changed),
352 [=]() { update_details(view, call); }
353 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500354
355 /* video widget */
356 priv->video_widget = video_widget_new();
357 gtk_container_add(GTK_CONTAINER(priv->frame_video), priv->video_widget);
358 gtk_widget_show_all(priv->frame_video);
359
360 /* check if we already have a renderer */
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400361 push_new_renderer(view, call->videoRenderer(), VIDEO_RENDERER_REMOTE);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500362
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400363 /* callback for remote renderer */
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400364 priv->remote_renderer_connection = QObject::connect(
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400365 call,
366 &Call::videoStarted,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500367 [=](Video::Renderer *renderer) {
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400368 push_new_renderer(view, renderer, VIDEO_RENDERER_REMOTE);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500369 }
370 );
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400371
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400372 /* local renderer */
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400373 push_new_renderer(view, Video::PreviewManager::instance()->previewRenderer(), VIDEO_RENDERER_LOCAL);
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400374
375 /* callback for local renderer */
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400376 priv->local_renderer_connection = QObject::connect(
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400377 Video::PreviewManager::instance(),
378 &Video::PreviewManager::previewStarted,
379 [=](Video::Renderer *renderer) {
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400380 push_new_renderer(view, renderer, VIDEO_RENDERER_LOCAL);
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400381 }
382 );
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400383
384 /* catch double click to make full screen */
385 g_signal_connect(priv->video_widget, "button-press-event",
386 G_CALLBACK(on_button_press_in_video_event),
387 view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500388}