blob: 932efe4c51c4823497dd82bc4f3fc40b916fbd68 [file] [log] [blame]
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05001/*
Guillaume Roguez2a6150d2017-07-19 18:24:47 -04002 * Copyright (C) 2015-2017 Savoir-faire Linux Inc.
Stepan Salenikovichc64523b2015-02-27 16:31:00 -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.
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050018 */
19
20#include "currentcallview.h"
21
Sébastien Blin55bff9d2017-10-03 15:15:23 -040022// Gtk
23#include <clutter-gtk/clutter-gtk.h>
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050024#include <gtk/gtk.h>
Stepan Salenikovich7e283552015-12-21 16:17:52 -050025#include <glib/gi18n.h>
Sébastien Blin55bff9d2017-10-03 15:15:23 -040026
27// Lrc
Stepan Salenikovichf6f42652015-07-15 12:46:14 -040028#include <account.h>
Sébastien Blin55bff9d2017-10-03 15:15:23 -040029#include <api/conversationmodel.h>
30#include <api/contact.h>
31#include <api/contactmodel.h>
32#include <api/newcallmodel.h>
Sébastien Blin3667fa62017-11-23 09:11:53 -050033#include <callmodel.h>
Sébastien Blin55bff9d2017-10-03 15:15:23 -040034#include <codecmodel.h>
35#include <globalinstances.h>
Olivier Gregoire66e4df72016-06-17 18:39:05 -040036#include <smartinfohub.h>
Sébastien Blin55bff9d2017-10-03 15:15:23 -040037#include <video/previewmanager.h>
38
39// Client
40#include "chatview.h"
41#include "native/pixbufmanipulator.h"
42#include "ringnotify.h"
43#include "utils/drawing.h"
44#include "utils/files.h"
45#include "video/video_widget.h"
46
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050047
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -050048static constexpr int CONTROLS_FADE_TIMEOUT = 3000000; /* microseconds */
49static constexpr int FADE_DURATION = 500; /* miliseconds */
50
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050051struct _CurrentCallView
52{
53 GtkBox parent;
54};
55
56struct _CurrentCallViewClass
57{
58 GtkBoxClass parent_class;
59};
60
61typedef struct _CurrentCallViewPrivate CurrentCallViewPrivate;
62
63struct _CurrentCallViewPrivate
64{
Stepan Salenikoviche178e632015-11-06 13:31:19 -050065 GtkWidget *hbox_call_info;
66 GtkWidget *hbox_call_controls;
Olivier Gregoire66e4df72016-06-17 18:39:05 -040067 GtkWidget *vbox_call_smartInfo;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050068 GtkWidget *image_peer;
Stepan Salenikovich07107e92016-05-06 10:35:17 -040069 GtkWidget *label_name;
Nicolas Jager2e467c32017-01-18 08:52:23 -050070 GtkWidget *label_bestId;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050071 GtkWidget *label_status;
72 GtkWidget *label_duration;
Olivier Gregoire66e4df72016-06-17 18:39:05 -040073 GtkWidget *label_smartinfo_description;
74 GtkWidget *label_smartinfo_value;
75 GtkWidget *label_smartinfo_general_information;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050076 GtkWidget *paned_call;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050077 GtkWidget *frame_video;
78 GtkWidget *video_widget;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050079 GtkWidget *frame_chat;
Stepan Salenikovicha448f602015-05-29 13:33:06 -040080 GtkWidget *togglebutton_chat;
AmarOke7c02972017-07-17 15:21:20 -040081 GtkWidget *togglebutton_muteaudio;
82 GtkWidget *togglebutton_mutevideo;
83 GtkWidget *togglebutton_hold;
Sébastien Blin55bff9d2017-10-03 15:15:23 -040084 GtkWidget *togglebutton_record;
Stepan Salenikovich77baa522015-07-07 15:29:14 -040085 GtkWidget *button_hangup;
Stepan Salenikovich7e283552015-12-21 16:17:52 -050086 GtkWidget *scalebutton_quality;
87 GtkWidget *checkbutton_autoquality;
Sébastien Blin366243f2017-11-03 14:14:54 -040088 GtkWidget *chat_view;
Stepan Salenikovich7e283552015-12-21 16:17:52 -050089
aviau039001d2016-09-29 16:39:05 -040090 /* The webkit_chat_container is created once, then reused for all chat
91 * views */
92 GtkWidget *webkit_chat_container;
93
Stepan Salenikovich7e283552015-12-21 16:17:52 -050094 /* flag used to keep track of the video quality scale pressed state;
95 * we do not want to update the codec bitrate until the user releases the
96 * scale button */
97 gboolean quality_scale_pressed;
Stepan Salenikovicha448f602015-05-29 13:33:06 -040098
Sébastien Blin55bff9d2017-10-03 15:15:23 -040099 lrc::api::conversation::Info* conversation_;
100 AccountContainer* accountContainer_;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500101
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500102 QMetaObject::Connection state_change_connection;
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400103 QMetaObject::Connection local_renderer_connection;
104 QMetaObject::Connection remote_renderer_connection;
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400105 QMetaObject::Connection new_message_connection;
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500106
107 GSettings *settings;
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500108
109 // for clutter animations and to know when to fade in/out the overlays
110 ClutterTransition *fade_info;
111 ClutterTransition *fade_controls;
112 gint64 time_last_mouse_motion;
113 guint timer_fade;
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400114
Stepan Salenikovich88092932017-05-15 18:19:00 -0400115 gulong insert_controls_id;
116
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400117 // smart info
118 QMetaObject::Connection smartinfo_refresh_connection;
119 guint smartinfo_action;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500120};
121
122G_DEFINE_TYPE_WITH_PRIVATE(CurrentCallView, current_call_view, GTK_TYPE_BOX);
123
124#define CURRENT_CALL_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CURRENT_CALL_VIEW_TYPE, CurrentCallViewPrivate))
125
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500126enum {
127 VIDEO_DOUBLE_CLICKED,
128 LAST_SIGNAL
129};
130
131static guint current_call_view_signals[LAST_SIGNAL] = { 0 };
132
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500133static void
134current_call_view_dispose(GObject *object)
135{
136 CurrentCallView *view;
137 CurrentCallViewPrivate *priv;
138
139 view = CURRENT_CALL_VIEW(object);
140 priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
141
142 QObject::disconnect(priv->state_change_connection);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400143 QObject::disconnect(priv->local_renderer_connection);
144 QObject::disconnect(priv->remote_renderer_connection);
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400145 QObject::disconnect(priv->smartinfo_refresh_connection);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400146 QObject::disconnect(priv->new_message_connection);
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500147 g_clear_object(&priv->settings);
148
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500149 g_source_remove(priv->timer_fade);
150
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400151 auto display_smartinfo = g_action_map_lookup_action(G_ACTION_MAP(g_application_get_default()), "display-smartinfo");
152 g_signal_handler_disconnect(display_smartinfo, priv->smartinfo_action);
153
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500154 G_OBJECT_CLASS(current_call_view_parent_class)->dispose(object);
155}
156
157static void
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500158show_chat_view(CurrentCallView *self)
159{
160 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
161 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
162
163 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_chat), TRUE);
164}
165
166static void
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400167chat_toggled(GtkToggleButton *togglebutton, CurrentCallView *self)
168{
169 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
170 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
171
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400172 if (gtk_toggle_button_get_active(togglebutton)) {
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500173 gtk_widget_show_all(priv->frame_chat);
174 gtk_widget_grab_focus(priv->frame_chat);
Stepan Salenikovicha5129f62015-11-05 15:10:59 -0500175 } else {
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500176 gtk_widget_hide(priv->frame_chat);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400177 }
178}
179
Stepan Salenikovichdaf3cb32016-10-12 16:39:42 -0400180static gboolean
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500181map_boolean_to_orientation(GValue *value, GVariant *variant, G_GNUC_UNUSED gpointer user_data)
182{
183 if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
184 if (g_variant_get_boolean(variant)) {
185 // true, chat should be horizontal (to the right)
186 g_value_set_enum(value, GTK_ORIENTATION_HORIZONTAL);
187 } else {
188 // false, chat should be vertical (at the bottom)
189 g_value_set_enum(value, GTK_ORIENTATION_VERTICAL);
190 }
191 return TRUE;
192 }
193 return FALSE;
194}
195
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400196static void
197update_details(CurrentCallView *view)
198{
199 auto *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
200 auto callRendered = priv->conversation_->callId;
201
202 if (!priv->conversation_->confId.empty())
203 callRendered = priv->conversation_->confId;
204
205 gtk_label_set_text(GTK_LABEL(priv->label_duration),
206 priv->accountContainer_->info.callModel->getFormattedCallDuration(callRendered).c_str());
207
208 auto call = priv->accountContainer_->info.callModel->getCall(callRendered);
209 gtk_widget_set_sensitive(GTK_WIDGET(priv->togglebutton_muteaudio), (call.type != lrc::api::call::Type::CONFERENCE));
210 gtk_widget_set_sensitive(GTK_WIDGET(priv->togglebutton_mutevideo), (call.type != lrc::api::call::Type::CONFERENCE));
211}
212
213
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500214static gboolean
215timeout_check_last_motion_event(CurrentCallView *self)
216{
217 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), G_SOURCE_REMOVE);
218 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
219
220 auto current_time = g_get_monotonic_time();
221 if (current_time - priv->time_last_mouse_motion >= CONTROLS_FADE_TIMEOUT) {
222 // timeout has passed, hide the controls
223 if (clutter_timeline_get_direction(CLUTTER_TIMELINE(priv->fade_info)) == CLUTTER_TIMELINE_BACKWARD) {
224 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_FORWARD);
225 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_FORWARD);
226 if (!clutter_timeline_is_playing(CLUTTER_TIMELINE(priv->fade_info))) {
227 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_info));
228 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_controls));
229 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_info));
230 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_controls));
231 }
232 }
233 }
234
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400235 update_details(self);
236
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500237 return G_SOURCE_CONTINUE;
238}
239
240static gboolean
241mouse_moved(CurrentCallView *self)
242{
243 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
244 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
245
246 priv->time_last_mouse_motion = g_get_monotonic_time();
247
248 // since the mouse moved, make sure the controls are shown
249 if (clutter_timeline_get_direction(CLUTTER_TIMELINE(priv->fade_info)) == CLUTTER_TIMELINE_FORWARD) {
250 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_BACKWARD);
251 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_BACKWARD);
252 if (!clutter_timeline_is_playing(CLUTTER_TIMELINE(priv->fade_info))) {
253 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_info));
254 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_controls));
255 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_info));
256 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_controls));
257 }
258 }
259
260 return FALSE; // propogate event
261}
262
263static ClutterTransition *
264create_fade_out_transition()
265{
266 auto transition = clutter_property_transition_new("opacity");
267 clutter_transition_set_from(transition, G_TYPE_UINT, 255);
268 clutter_transition_set_to(transition, G_TYPE_UINT, 0);
269 clutter_timeline_set_duration(CLUTTER_TIMELINE(transition), FADE_DURATION);
270 clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(transition), 0);
271 clutter_timeline_set_progress_mode(CLUTTER_TIMELINE(transition), CLUTTER_EASE_IN_OUT_CUBIC);
272 return transition;
273}
274
Stepan Salenikovich5ed1b492015-11-13 14:03:31 -0500275static gboolean
276video_widget_focus(GtkWidget *widget, GtkDirectionType direction, CurrentCallView *self)
277{
278 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
279 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
280
281 // if this widget already has focus, we want the focus to move to the next widget, otherwise we
282 // will get stuck in a focus loop on the buttons
283 if (gtk_widget_has_focus(widget))
284 return FALSE;
285
286 // otherwise we want the focus to go to and change between the call control buttons
287 if (gtk_widget_child_focus(GTK_WIDGET(priv->hbox_call_controls), direction)) {
288 // selected a child, make sure call controls are shown
289 mouse_moved(self);
290 return TRUE;
291 }
292
293 // did not select the next child, propogate the event
294 return FALSE;
295}
296
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500297static GtkBox *
298gtk_scale_button_get_box(GtkScaleButton *button)
299{
300 GtkWidget *box = NULL;
301 if (auto dock = gtk_scale_button_get_popup(button)) {
302 // the dock is a popover which contains the box
303 box = gtk_bin_get_child(GTK_BIN(dock));
304 if (box) {
305 if (GTK_IS_FRAME(box)) {
306 // support older versions of gtk; the box used to be in a frame
307 box = gtk_bin_get_child(GTK_BIN(box));
308 }
309 }
310 }
311
312 return GTK_BOX(box);
313}
314
315/**
316 * This gets the GtkScaleButtonScale widget (which is a GtkScale) from the
317 * given GtkScaleButton in order to be able to modify its properties and connect
318 * to its signals
319 */
320static GtkScale *
321gtk_scale_button_get_scale(GtkScaleButton *button)
322{
323 GtkScale *scale = NULL;
324
325 if (auto box = gtk_scale_button_get_box(button)) {
326 GList *children = gtk_container_get_children(GTK_CONTAINER(box));
327 for (GList *c = children; c && !scale; c = c->next) {
328 if (GTK_IS_SCALE(c->data))
329 scale = GTK_SCALE(c->data);
330 }
331 g_list_free(children);
332 }
333
334 return scale;
335}
336
337static void
Sébastien Blin3667fa62017-11-23 09:11:53 -0500338set_quality(Call *call, gboolean auto_quality_on, double desired_quality)
339{
340 /* set auto quality true or false, also set the bitrate and quality values;
341 * the slider is from 0 to 100, use the min and max vals to scale each value accordingly */
342 if (const auto& codecModel = call->account()->codecModel()) {
343 const auto& videoCodecs = codecModel->videoCodecs();
344
345 for (int i=0; i < videoCodecs->rowCount();i++) {
346 const auto& idx = videoCodecs->index(i,0);
347
348 if (auto_quality_on) {
349 // g_debug("enable auto quality");
350 videoCodecs->setData(idx, "true", CodecModel::Role::AUTO_QUALITY_ENABLED);
351 } else {
352 auto min_bitrate = idx.data(static_cast<int>(CodecModel::Role::MIN_BITRATE)).toInt();
353 auto max_bitrate = idx.data(static_cast<int>(CodecModel::Role::MAX_BITRATE)).toInt();
354 auto min_quality = idx.data(static_cast<int>(CodecModel::Role::MIN_QUALITY)).toInt();
355 auto max_quality = idx.data(static_cast<int>(CodecModel::Role::MAX_QUALITY)).toInt();
356
357 // g_debug("bitrate min: %d, max: %d, quality min: %d, max: %d", min_bitrate, max_bitrate, min_quality, max_quality);
358
359 double bitrate;
360 bitrate = min_bitrate + (double)(max_bitrate - min_bitrate)*(desired_quality/100.0);
361 if (bitrate < 0) bitrate = 0;
362
363 double quality;
364 // note: a lower value means higher quality
365 quality = (double)min_quality - (min_quality - max_quality)*(desired_quality/100.0);
366 if (quality < 0) quality = 0;
367
368 // g_debug("disable auto quality; %% quality: %d; bitrate: %d; quality: %d", (int)desired_quality, (int)bitrate, (int)quality);
369 videoCodecs->setData(idx, "false", CodecModel::Role::AUTO_QUALITY_ENABLED);
370 videoCodecs->setData(idx, QString::number((int)bitrate), CodecModel::Role::BITRATE);
371 videoCodecs->setData(idx, QString::number((int)quality), CodecModel::Role::QUALITY);
372 }
373 }
374 codecModel << CodecModel::EditAction::SAVE;
375 }
376}
377
378static void
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500379autoquality_toggled(GtkToggleButton *button, CurrentCallView *self)
380{
381 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
382 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
383
384 gboolean auto_quality_on = gtk_toggle_button_get_active(button);
385
386 auto scale = gtk_scale_button_get_scale(GTK_SCALE_BUTTON(priv->scalebutton_quality));
387 auto plus_button = gtk_scale_button_get_plus_button(GTK_SCALE_BUTTON(priv->scalebutton_quality));
388 auto minus_button = gtk_scale_button_get_minus_button(GTK_SCALE_BUTTON(priv->scalebutton_quality));
389
390 gtk_widget_set_sensitive(GTK_WIDGET(scale), !auto_quality_on);
391 gtk_widget_set_sensitive(plus_button, !auto_quality_on);
392 gtk_widget_set_sensitive(minus_button, !auto_quality_on);
Sébastien Blin3667fa62017-11-23 09:11:53 -0500393
394 double desired_quality = gtk_scale_button_get_value(GTK_SCALE_BUTTON(priv->scalebutton_quality));
395
396 auto callToRender = priv->conversation_->callId;
397 if (!priv->conversation_->confId.empty())
398 callToRender = priv->conversation_->confId;
399 auto renderer = priv->accountContainer_->info.callModel->getRenderer(callToRender);
400 for (const auto& activeCall: CallModel::instance().getActiveCalls())
401 if (activeCall->videoRenderer() == renderer)
402 set_quality(activeCall, auto_quality_on, desired_quality);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500403}
404
405static void
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400406quality_changed(G_GNUC_UNUSED GtkScaleButton *button, G_GNUC_UNUSED gdouble value, CurrentCallView *self)
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500407{
408 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
409 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
410
411 /* no need to upate quality if auto quality is enabled */
412 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->checkbutton_autoquality))) return;
413
414 /* only update if the scale button is released, to reduce the number of updates */
415 if (priv->quality_scale_pressed) return;
Sébastien Blin3667fa62017-11-23 09:11:53 -0500416
417 auto callToRender = priv->conversation_->callId;
418 if (!priv->conversation_->confId.empty())
419 callToRender = priv->conversation_->confId;
420 auto renderer = priv->accountContainer_->info.callModel->getRenderer(callToRender);
421 for (const auto& activeCall: CallModel::instance().getActiveCalls())
422 if (activeCall->videoRenderer() == renderer)
423 set_quality(activeCall, FALSE, gtk_scale_button_get_value(button));
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500424}
425
426static gboolean
427quality_button_pressed(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, CurrentCallView *self)
428{
429 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
430 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500431 priv->quality_scale_pressed = TRUE;
432
433 return GDK_EVENT_PROPAGATE;
434}
435
436static gboolean
437quality_button_released(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, CurrentCallView *self)
438{
439 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
440 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500441 priv->quality_scale_pressed = FALSE;
442
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400443 // now make sure the quality gets updated
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500444 quality_changed(GTK_SCALE_BUTTON(priv->scalebutton_quality), 0, self);
445
446 return GDK_EVENT_PROPAGATE;
447}
448
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400449static void
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400450button_hangup_clicked(CurrentCallView *view)
451{
452 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
453 auto callToHangUp = priv->conversation_->callId;
454 if (!priv->conversation_->confId.empty())
455 callToHangUp = priv->conversation_->confId;
456 priv->accountContainer_->info.callModel->hangUp(callToHangUp);
457}
458
459static void
460togglebutton_hold_clicked(CurrentCallView *view)
461{
462 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
463 auto callToHold = priv->conversation_->callId;
464 if (!priv->conversation_->confId.empty())
465 callToHold = priv->conversation_->confId;
466 priv->accountContainer_->info.callModel->togglePause(callToHold);
467}
468
469static void
470togglebutton_record_clicked(CurrentCallView *view)
471{
472 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
473 auto callToRecord = priv->conversation_->callId;
474 if (!priv->conversation_->confId.empty())
475 callToRecord = priv->conversation_->confId;
476 priv->accountContainer_->info.callModel->toggleAudioRecord(callToRecord);
477}
478
479static void
480togglebutton_muteaudio_clicked(CurrentCallView *view)
481{
482 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
483 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
484 auto callToMute = priv->conversation_->callId;
485 if (!priv->conversation_->confId.empty())
486 callToMute = priv->conversation_->confId;
487 auto muteAudioBtn = GTK_TOGGLE_BUTTON(priv->togglebutton_muteaudio);
488 priv->accountContainer_->info.callModel->toggleMedia(callToMute,
Sébastien Blin1ffd4722017-11-06 15:38:08 -0500489 lrc::api::NewCallModel::Media::AUDIO);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400490
491 auto togglebutton = GTK_TOGGLE_BUTTON(priv->togglebutton_muteaudio);
492 auto image = gtk_image_new_from_resource ("/cx/ring/RingGnome/mute_audio");
493 if (gtk_toggle_button_get_active(togglebutton))
494 image = gtk_image_new_from_resource ("/cx/ring/RingGnome/unmute_audio");
495 gtk_button_set_image(GTK_BUTTON(togglebutton), image);
496}
497
498static void
499togglebutton_mutevideo_clicked(CurrentCallView *view)
500{
501 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
502 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
503 auto callToMute = priv->conversation_->callId;
504 if (!priv->conversation_->confId.empty())
505 callToMute = priv->conversation_->confId;
506 auto muteVideoBtn = GTK_TOGGLE_BUTTON(priv->togglebutton_mutevideo);
507 priv->accountContainer_->info.callModel->toggleMedia(callToMute,
Sébastien Blin1ffd4722017-11-06 15:38:08 -0500508 lrc::api::NewCallModel::Media::VIDEO);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400509
510 auto togglebutton = GTK_TOGGLE_BUTTON(priv->togglebutton_mutevideo);
511 auto image = gtk_image_new_from_resource ("/cx/ring/RingGnome/mute_video");
512 if (gtk_toggle_button_get_active(togglebutton))
513 image = gtk_image_new_from_resource ("/cx/ring/RingGnome/unmute_video");
514 gtk_button_set_image(GTK_BUTTON(togglebutton), image);
515}
516
517static void
Stepan Salenikovich88092932017-05-15 18:19:00 -0400518insert_controls(CurrentCallView *view)
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500519{
Stepan Salenikovich88092932017-05-15 18:19:00 -0400520 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400521
Stepan Salenikovich88092932017-05-15 18:19:00 -0400522 /* only add the controls once */
523 g_signal_handler_disconnect(view, priv->insert_controls_id);
524 priv->insert_controls_id = 0;
Stepan Salenikoviche178e632015-11-06 13:31:19 -0500525
526 auto stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->video_widget));
527 auto actor_info = gtk_clutter_actor_new_with_contents(priv->hbox_call_info);
528 auto actor_controls = gtk_clutter_actor_new_with_contents(priv->hbox_call_controls);
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400529 auto actor_smartInfo = gtk_clutter_actor_new_with_contents(priv->vbox_call_smartInfo);
Stepan Salenikoviche178e632015-11-06 13:31:19 -0500530
531 clutter_actor_add_child(stage, actor_info);
532 clutter_actor_set_x_align(actor_info, CLUTTER_ACTOR_ALIGN_FILL);
533 clutter_actor_set_y_align(actor_info, CLUTTER_ACTOR_ALIGN_START);
534
535 clutter_actor_add_child(stage, actor_controls);
536 clutter_actor_set_x_align(actor_controls, CLUTTER_ACTOR_ALIGN_CENTER);
537 clutter_actor_set_y_align(actor_controls, CLUTTER_ACTOR_ALIGN_END);
538
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400539 clutter_actor_add_child(stage, actor_smartInfo);
540 clutter_actor_set_x_align(actor_smartInfo, CLUTTER_ACTOR_ALIGN_END);
541 clutter_actor_set_y_align(actor_smartInfo, CLUTTER_ACTOR_ALIGN_START);
542 ClutterMargin clutter_margin_smartInfo;
543 clutter_margin_smartInfo.top = 50;
544 clutter_margin_smartInfo.right = 10;
philippegorleya7337942017-07-04 15:29:42 -0400545 clutter_margin_smartInfo.left = 10;
546 clutter_margin_smartInfo.bottom = 10;
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400547 clutter_actor_set_margin (actor_smartInfo, &clutter_margin_smartInfo);
548
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500549 /* add fade in and out states to the info and controls */
550 priv->time_last_mouse_motion = g_get_monotonic_time();
551 priv->fade_info = create_fade_out_transition();
552 priv->fade_controls = create_fade_out_transition();
553 clutter_actor_add_transition(actor_info, "fade_info", priv->fade_info);
554 clutter_actor_add_transition(actor_controls, "fade_controls", priv->fade_controls);
555 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_BACKWARD);
556 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_BACKWARD);
557 clutter_timeline_stop(CLUTTER_TIMELINE(priv->fade_info));
558 clutter_timeline_stop(CLUTTER_TIMELINE(priv->fade_controls));
559
560 /* have a timer check every 1 second if the controls should fade out */
561 priv->timer_fade = g_timeout_add(1000, (GSourceFunc)timeout_check_last_motion_event, view);
562
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400563 /* connect the controllers (new model) */
564 g_signal_connect_swapped(priv->button_hangup, "clicked", G_CALLBACK(button_hangup_clicked), view);
565 g_signal_connect_swapped(priv->togglebutton_hold, "clicked", G_CALLBACK(togglebutton_hold_clicked), view);
566 g_signal_connect_swapped(priv->togglebutton_muteaudio, "clicked", G_CALLBACK(togglebutton_muteaudio_clicked), view);
567 g_signal_connect_swapped(priv->togglebutton_record, "clicked", G_CALLBACK(togglebutton_record_clicked), view);
568 g_signal_connect_swapped(priv->togglebutton_mutevideo, "clicked", G_CALLBACK(togglebutton_mutevideo_clicked), view);
569
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500570 /* connect to the mouse motion event to reset the last moved time */
571 g_signal_connect_swapped(priv->video_widget, "motion-notify-event", G_CALLBACK(mouse_moved), view);
572 g_signal_connect_swapped(priv->video_widget, "button-press-event", G_CALLBACK(mouse_moved), view);
573 g_signal_connect_swapped(priv->video_widget, "button-release-event", G_CALLBACK(mouse_moved), view);
574
Stepan Salenikovich5ed1b492015-11-13 14:03:31 -0500575 /* manually handle the focus of the video widget to be able to focus on the call controls */
576 g_signal_connect(priv->video_widget, "focus", G_CALLBACK(video_widget_focus), view);
577
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400578
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500579 /* toggle whether or not the chat is displayed */
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400580 g_signal_connect(priv->togglebutton_chat, "toggled", G_CALLBACK(chat_toggled), view);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400581
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500582 /* bind the chat orientation to the gsetting */
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500583 priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
584 g_settings_bind_with_mapping(priv->settings, "chat-pane-horizontal",
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500585 priv->paned_call, "orientation",
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500586 G_SETTINGS_BIND_GET,
587 map_boolean_to_orientation,
588 nullptr, nullptr, nullptr);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500589
590 g_signal_connect(priv->scalebutton_quality, "value-changed", G_CALLBACK(quality_changed), view);
591 /* customize the quality button scale */
592 if (auto scale_box = gtk_scale_button_get_box(GTK_SCALE_BUTTON(priv->scalebutton_quality))) {
593 priv->checkbutton_autoquality = gtk_check_button_new_with_label(C_("Enable automatic video quality", "Auto"));
594 gtk_widget_show(priv->checkbutton_autoquality);
595 gtk_box_pack_start(GTK_BOX(scale_box), priv->checkbutton_autoquality, FALSE, TRUE, 0);
596 g_signal_connect(priv->checkbutton_autoquality, "toggled", G_CALLBACK(autoquality_toggled), view);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500597 }
598 if (auto scale = gtk_scale_button_get_scale(GTK_SCALE_BUTTON(priv->scalebutton_quality))) {
599 g_signal_connect(scale, "button-press-event", G_CALLBACK(quality_button_pressed), view);
600 g_signal_connect(scale, "button-release-event", G_CALLBACK(quality_button_released), view);
601 }
Stepan Salenikovich88092932017-05-15 18:19:00 -0400602
Sébastien Blin3667fa62017-11-23 09:11:53 -0500603 /* by this time we should have the call already set, but we check to make sure */
604 auto callToRender = priv->conversation_->callId;
605 if (!priv->conversation_->confId.empty())
606 callToRender = priv->conversation_->confId;
607 auto renderer = priv->accountContainer_->info.callModel->getRenderer(callToRender);
608 for (const auto& activeCall: CallModel::instance().getActiveCalls())
609 if (activeCall->videoRenderer() == renderer) {
Sébastien Blina5c756e2017-11-27 10:10:07 -0500610 g_signal_connect(priv->video_widget, "drag-data-received", G_CALLBACK(video_widget_on_drag_data_received), activeCall);
Sébastien Blin3667fa62017-11-23 09:11:53 -0500611 /* check if auto quality is enabled or not */
612 if (const auto& codecModel = activeCall->account()->codecModel()) {
613 const auto& videoCodecs = codecModel->videoCodecs();
614 if (videoCodecs->rowCount() > 0) {
615 /* we only need to check the first codec since by default it is ON for all, and the
616 * gnome client sets its ON or OFF for all codecs as well */
617 const auto& idx = videoCodecs->index(0,0);
618 auto auto_quality_enabled = idx.data(static_cast<int>(CodecModel::Role::AUTO_QUALITY_ENABLED)).toString() == "true";
619 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->checkbutton_autoquality), auto_quality_enabled);
620
621 // TODO: save the manual quality setting in the client and set the slider to that value here;
622 // the daemon resets the bitrate/quality between each call, and the default may be
623 // different for each codec, so there is no reason to check it here
624 }
625 }
626 } else {
627 /* Auto-quality is off by default */
628 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->checkbutton_autoquality), FALSE);
629 }
630
Sébastien Blin4514eeb2017-07-25 14:17:01 -0400631 // Get if the user wants to show the smartInfo box
632 auto display_smartinfo = g_action_map_lookup_action(G_ACTION_MAP(g_application_get_default()), "display-smartinfo");
633 if (g_variant_get_boolean(g_action_get_state(G_ACTION(display_smartinfo)))) {
634 gtk_widget_show(priv->vbox_call_smartInfo);
635 } else {
636 gtk_widget_hide(priv->vbox_call_smartInfo);
637 }
Stepan Salenikovich88092932017-05-15 18:19:00 -0400638}
639
640static void
641current_call_view_init(CurrentCallView *view)
642{
643 gtk_widget_init_template(GTK_WIDGET(view));
644
645 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
646
647 // CSS styles
648 auto provider = gtk_css_provider_new();
649 gtk_css_provider_load_from_data(provider,
AmarOke7c02972017-07-17 15:21:20 -0400650 ".smartinfo-block-style { color: #8ae234; background-color: rgba(1, 1, 1, 0.33); } \
651 @keyframes blink { 0% {opacity: 1;} 49% {opacity: 1;} 50% {opacity: 0;} 100% {opacity: 0;} } \
652 .record-button { background: rgba(0, 0, 0, 1); border-radius: 50%; border: 0; transition: all 0.3s ease; } \
653 .record-button:checked { animation: blink 1s; animation-iteration-count: infinite; } \
Sébastien Blinac743682017-11-30 17:04:38 -0500654 .call-button { background: rgba(0, 0, 0, 0.35); border-radius: 50%; border: 0; transition: all 0.3s ease; } \
655 .call-button:hover { background: rgba(0, 0, 0, 0.2); } \
AmarOke7c02972017-07-17 15:21:20 -0400656 .call-button:disabled { opacity: 0.2; } \
657 .can-be-disabled:checked { background: rgba(219, 58, 55, 1); } \
658 .hangup-button-style { background: rgba(219, 58, 55, 1); border-radius: 50%; border: 0; transition: all 0.3s ease; } \
659 .hangup-button-style:hover { background: rgba(219, 39, 25, 1); }",
Stepan Salenikovich88092932017-05-15 18:19:00 -0400660 -1, nullptr
661 );
662 gtk_style_context_add_provider_for_screen(gdk_display_get_default_screen(gdk_display_get_default()),
663 GTK_STYLE_PROVIDER(provider),
664 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
665
666 priv->video_widget = video_widget_new();
667 gtk_container_add(GTK_CONTAINER(priv->frame_video), priv->video_widget);
668 gtk_widget_show_all(priv->frame_video);
669
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400670 // add the overlay controls only once the view has been allocated a size to prevent size
671 // allocation warnings in the log
Stepan Salenikovich88092932017-05-15 18:19:00 -0400672 priv->insert_controls_id = g_signal_connect(view, "size-allocate", G_CALLBACK(insert_controls), nullptr);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500673}
674
675static void
676current_call_view_class_init(CurrentCallViewClass *klass)
677{
678 G_OBJECT_CLASS(klass)->dispose = current_call_view_dispose;
679
680 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
681 "/cx/ring/RingGnome/currentcallview.ui");
682
Stepan Salenikoviche178e632015-11-06 13:31:19 -0500683 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_info);
684 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_controls);
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400685 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, vbox_call_smartInfo);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500686 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, image_peer);
Stepan Salenikovich07107e92016-05-06 10:35:17 -0400687 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_name);
Nicolas Jager2e467c32017-01-18 08:52:23 -0500688 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_bestId);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500689 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_status);
690 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_duration);
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400691 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_smartinfo_description);
692 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_smartinfo_value);
693 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_smartinfo_general_information);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500694 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, paned_call);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500695 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_video);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500696 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_chat);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400697 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_chat);
AmarOke7c02972017-07-17 15:21:20 -0400698 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_hold);
699 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_muteaudio);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400700 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_record);
AmarOke7c02972017-07-17 15:21:20 -0400701 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_mutevideo);
Stepan Salenikovich77baa522015-07-07 15:29:14 -0400702 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, button_hangup);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500703 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, scalebutton_quality);
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500704
705 current_call_view_signals[VIDEO_DOUBLE_CLICKED] = g_signal_new (
706 "video-double-clicked",
707 G_TYPE_FROM_CLASS(klass),
708 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
709 0,
710 nullptr,
711 nullptr,
712 g_cclosure_marshal_VOID__VOID,
713 G_TYPE_NONE, 0);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500714}
715
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500716static void
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400717update_state(CurrentCallView *view)
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500718{
719 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400720 if (!priv || !priv->conversation_) return;
721 auto callId = priv->conversation_->callId;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500722
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400723 try {
724 auto call = priv->accountContainer_->info.callModel->getCall(callId);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500725
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400726 auto pauseBtn = GTK_TOGGLE_BUTTON(priv->togglebutton_hold);
727 auto image = gtk_image_new_from_resource ("/cx/ring/RingGnome/pause");
728 if (call.status == lrc::api::call::Status::PAUSED)
729 image = gtk_image_new_from_resource ("/cx/ring/RingGnome/play");
730 gtk_button_set_image(GTK_BUTTON(pauseBtn), image);
Stepan Salenikovich7ec8fe82015-06-02 18:26:39 -0400731
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400732 auto audioButton = GTK_TOGGLE_BUTTON(priv->togglebutton_muteaudio);
733 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_muteaudio), call.audioMuted);
734 auto imageMuteAudio = gtk_image_new_from_resource ("/cx/ring/RingGnome/mute_audio");
735 if (call.audioMuted)
736 imageMuteAudio = gtk_image_new_from_resource ("/cx/ring/RingGnome/unmute_audio");
737 gtk_button_set_image(GTK_BUTTON(audioButton), imageMuteAudio);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500738
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400739 auto videoButton = GTK_TOGGLE_BUTTON(priv->togglebutton_mutevideo);
740 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_mutevideo), call.videoMuted);
741 auto imageMuteVideo = gtk_image_new_from_resource ("/cx/ring/RingGnome/mute_video");
742 if (call.videoMuted)
743 imageMuteVideo = gtk_image_new_from_resource ("/cx/ring/RingGnome/unmute_video");
744 gtk_button_set_image(GTK_BUTTON(videoButton), imageMuteVideo);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500745
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400746 gchar *status = g_strdup_printf("%s", lrc::api::call::to_string(call.status).c_str());
747 gtk_label_set_text(GTK_LABEL(priv->label_status), status);
748 g_free(status);
749 } catch (std::out_of_range& e) {
750 g_warning("Can't update state for callId=%s", callId.c_str());
751 }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500752}
753
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400754static void
Stepan Salenikovich46bb1572017-05-19 16:09:43 -0400755update_name_and_photo(CurrentCallView *view)
756{
757 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
758
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400759 QVariant var_i = GlobalInstances::pixmapManipulator().conversationPhoto(
760 *priv->conversation_,
761 priv->accountContainer_->info,
762 QSize(60, 60),
763 false
764 );
Stepan Salenikovich46bb1572017-05-19 16:09:43 -0400765 std::shared_ptr<GdkPixbuf> image = var_i.value<std::shared_ptr<GdkPixbuf>>();
766 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_peer), image.get());
767
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400768 auto contactInfo = priv->accountContainer_->info.contactModel->getContact(priv->conversation_->participants.front());
Stepan Salenikovich46bb1572017-05-19 16:09:43 -0400769
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400770 auto name = contactInfo.profileInfo.alias;
771 gtk_label_set_text(GTK_LABEL(priv->label_name), name.c_str());
772
773 auto bestId = contactInfo.registeredName;
774 if (name != bestId) {
775 gtk_label_set_text(GTK_LABEL(priv->label_bestId), bestId.c_str());
Stepan Salenikovich46bb1572017-05-19 16:09:43 -0400776 gtk_widget_show(priv->label_bestId);
777 }
778}
779
780static void
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400781update_smartInfo(CurrentCallView *view)
782{
783 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
784
785 if (!SmartInfoHub::instance().isConference()) {
786 gchar* general_information = g_strdup_printf("Call ID: %s", SmartInfoHub::instance().callID().toStdString().c_str());
787 gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_general_information), general_information);
788 g_free(general_information);
789
790 gchar* description = g_strdup_printf("You\n"
791 "Framerate:\n"
792 "Video codec:\n"
793 "Audio codec:\n"
794 "Resolution:\n\n"
795 "Peer\n"
796 "Framerate:\n"
797 "Video codec:\n"
798 "Audio codec:\n"
799 "Resolution:");
800 gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_description),description);
801 g_free(description);
802
803 gchar* value = g_strdup_printf("\n%f\n%s\n%s\n%dx%d\n\n\n%f\n%s\n%s\n%dx%d",
804 (double)SmartInfoHub::instance().localFps(),
805 SmartInfoHub::instance().localVideoCodec().toStdString().c_str(),
806 SmartInfoHub::instance().localAudioCodec().toStdString().c_str(),
807 SmartInfoHub::instance().localWidth(),
808 SmartInfoHub::instance().localHeight(),
809 (double)SmartInfoHub::instance().remoteFps(),
810 SmartInfoHub::instance().remoteVideoCodec().toStdString().c_str(),
811 SmartInfoHub::instance().remoteAudioCodec().toStdString().c_str(),
812 SmartInfoHub::instance().remoteWidth(),
813 SmartInfoHub::instance().remoteHeight());
814 gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_value),value);
815 g_free(value);
816 } else {
817 gchar* general_information = g_strdup_printf("Conference ID: %s", SmartInfoHub::instance().callID().toStdString().c_str());
818 gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_general_information), general_information);
819 g_free(general_information);
820
821 gchar* description = g_strdup_printf("You\n"
822 "Framerate:\n"
823 "Video codec:\n"
824 "Audio codec:\n"
825 "Resolution:");
826 gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_description),description);
827 g_free(description);
828
829 gchar* value = g_strdup_printf("\n%f\n%s\n%s\n%dx%d",
830 (double)SmartInfoHub::instance().localFps(),
831 SmartInfoHub::instance().localVideoCodec().toStdString().c_str(),
832 SmartInfoHub::instance().localAudioCodec().toStdString().c_str(),
833 SmartInfoHub::instance().localWidth(),
834 SmartInfoHub::instance().localHeight());
835 gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_value),value);
836 g_free(value);
837 }
838}
839
840
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500841static gboolean
842on_button_press_in_video_event(GtkWidget *self, GdkEventButton *event, CurrentCallView *view)
843{
844 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
845 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), FALSE);
846
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400847 // on double click
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500848 if (event->type == GDK_2BUTTON_PRESS) {
849 g_debug("double click in video");
850 g_signal_emit(G_OBJECT(view), current_call_view_signals[VIDEO_DOUBLE_CLICKED], 0);
851 }
852
853 return GDK_EVENT_PROPAGATE;
854}
855
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400856static void
857toggle_smartinfo(GSimpleAction* action, G_GNUC_UNUSED GVariant* state, GtkWidget* vbox_call_smartInfo)
858{
859 if (g_variant_get_boolean(g_action_get_state(G_ACTION(action)))) {
860 gtk_widget_show(vbox_call_smartInfo);
861 } else {
862 gtk_widget_hide(vbox_call_smartInfo);
863 }
864}
865
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400866
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400867static void
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400868set_call_info(CurrentCallView *view) {
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500869 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
870
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400871 // change some things depending on call state
872 update_state(view);
873 update_details(view);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400874
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400875 // NOTE/TODO we need to rewrite the video_widget file to use the new LRC.
876 g_signal_connect(priv->video_widget, "button-press-event", G_CALLBACK(video_widget_on_button_press_in_screen_event), nullptr);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500877
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400878 // check if we already have a renderer
879 auto callToRender = priv->conversation_->callId;
880 if (!priv->conversation_->confId.empty())
881 callToRender = priv->conversation_->confId;
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400882 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400883 priv->accountContainer_->info.callModel->getRenderer(callToRender),
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400884 VIDEO_RENDERER_REMOTE);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500885
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400886 // local renderer
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400887 if (Video::PreviewManager::instance().isPreviewing())
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400888 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400889 Video::PreviewManager::instance().previewRenderer(),
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400890 VIDEO_RENDERER_LOCAL);
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400891
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400892 // callback for local renderer
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400893 priv->local_renderer_connection = QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400894 &Video::PreviewManager::instance(),
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400895 &Video::PreviewManager::previewStarted,
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400896 [priv](Video::Renderer *renderer) {
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400897 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
898 renderer,
899 VIDEO_RENDERER_LOCAL);
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400900 }
901 );
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400902
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400903 priv->smartinfo_refresh_connection = QObject::connect(
904 &SmartInfoHub::instance(),
905 &SmartInfoHub::changed,
906 [view, priv]() { update_smartInfo(view); }
907 );
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400908
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400909 priv->remote_renderer_connection = QObject::connect(
910 &*priv->accountContainer_->info.callModel,
911 &lrc::api::NewCallModel::remotePreviewStarted,
912 [priv](const std::string& callId, Video::Renderer *renderer) {
913 if (priv->conversation_->callId == callId) {
914 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
915 renderer,
916 VIDEO_RENDERER_REMOTE);
917 }
918 }
919 );
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400920
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400921 priv->state_change_connection = QObject::connect(
922 &*priv->accountContainer_->info.callModel,
923 &lrc::api::NewCallModel::callStatusChanged,
924 [view, priv] (const std::string& callId) {
925 if (callId == priv->conversation_->callId) {
926 update_state(view);
927 update_name_and_photo(view);
928 }
929 });
930
931 priv->new_message_connection = QObject::connect(
932 &*priv->accountContainer_->info.conversationModel, &lrc::api::ConversationModel::newUnreadMessage,
933 [priv](const std::string& uid, uint64_t msgId, lrc::api::interaction::Info msg) {
934 Q_UNUSED(uid)
935 Q_UNUSED(msgId)
936 Q_UNUSED(msg)
937 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_chat), TRUE);
938 });
939
940
941 // catch double click to make full screen
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400942 g_signal_connect(priv->video_widget, "button-press-event",
943 G_CALLBACK(on_button_press_in_video_event),
944 view);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400945
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400946 // handle smartinfo in right click menu
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400947 auto display_smartinfo = g_action_map_lookup_action(G_ACTION_MAP(g_application_get_default()), "display-smartinfo");
948 priv->smartinfo_action = g_signal_connect(display_smartinfo,
949 "notify::state",
950 G_CALLBACK(toggle_smartinfo),
951 priv->vbox_call_smartInfo);
952
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400953 // init chat view
Sébastien Blin366243f2017-11-03 14:14:54 -0400954 priv->chat_view = chat_view_new(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), priv->accountContainer_, priv->conversation_);
955 gtk_container_add(GTK_CONTAINER(priv->frame_chat), priv->chat_view);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500956
Sébastien Blin366243f2017-11-03 14:14:54 -0400957 g_signal_connect_swapped(priv->chat_view, "new-interactions-displayed", G_CALLBACK(show_chat_view), view);
958 chat_view_set_header_visible(CHAT_VIEW(priv->chat_view), FALSE);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400959
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500960}
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400961
962GtkWidget *
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400963current_call_view_new(WebKitChatContainer* view, AccountContainer* accountContainer, lrc::api::conversation::Info* conversation)
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400964{
965 auto self = g_object_new(CURRENT_CALL_VIEW_TYPE, NULL);
aviau039001d2016-09-29 16:39:05 -0400966 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400967 priv->webkit_chat_container = GTK_WIDGET(view);
968 priv->conversation_ = conversation;
969 priv->accountContainer_ = accountContainer;
970 set_call_info(CURRENT_CALL_VIEW(self));
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400971
972 return GTK_WIDGET(self);
973}
974
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400975lrc::api::conversation::Info
976current_call_view_get_conversation(CurrentCallView *self)
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400977{
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400978 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), lrc::api::conversation::Info());
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400979 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
980
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400981 return *priv->conversation_;
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400982}
Sébastien Blin366243f2017-11-03 14:14:54 -0400983
984GtkWidget *
985current_call_view_get_chat_view(CurrentCallView *self)
986{
987 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), nullptr);
988 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
989 return priv->chat_view;
990}