blob: a658dd44d4ecc2ea9ba00c9a697b926e2d50870c [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
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -050047namespace { namespace details
48{
49class CppImpl;
50}}
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -050051
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050052struct _CurrentCallView
53{
54 GtkBox parent;
55};
56
57struct _CurrentCallViewClass
58{
59 GtkBoxClass parent_class;
60};
61
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -050062struct CurrentCallViewPrivate
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050063{
Stepan Salenikoviche178e632015-11-06 13:31:19 -050064 GtkWidget *hbox_call_info;
65 GtkWidget *hbox_call_controls;
Olivier Gregoire66e4df72016-06-17 18:39:05 -040066 GtkWidget *vbox_call_smartInfo;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050067 GtkWidget *image_peer;
Stepan Salenikovich07107e92016-05-06 10:35:17 -040068 GtkWidget *label_name;
Nicolas Jager2e467c32017-01-18 08:52:23 -050069 GtkWidget *label_bestId;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050070 GtkWidget *label_status;
71 GtkWidget *label_duration;
Olivier Gregoire66e4df72016-06-17 18:39:05 -040072 GtkWidget *label_smartinfo_description;
73 GtkWidget *label_smartinfo_value;
74 GtkWidget *label_smartinfo_general_information;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050075 GtkWidget *paned_call;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050076 GtkWidget *frame_video;
77 GtkWidget *video_widget;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050078 GtkWidget *frame_chat;
Stepan Salenikovicha448f602015-05-29 13:33:06 -040079 GtkWidget *togglebutton_chat;
AmarOke7c02972017-07-17 15:21:20 -040080 GtkWidget *togglebutton_muteaudio;
81 GtkWidget *togglebutton_mutevideo;
82 GtkWidget *togglebutton_hold;
Sébastien Blin55bff9d2017-10-03 15:15:23 -040083 GtkWidget *togglebutton_record;
Stepan Salenikovich77baa522015-07-07 15:29:14 -040084 GtkWidget *button_hangup;
Stepan Salenikovich7e283552015-12-21 16:17:52 -050085 GtkWidget *scalebutton_quality;
86 GtkWidget *checkbutton_autoquality;
Sébastien Blin366243f2017-11-03 14:14:54 -040087 GtkWidget *chat_view;
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -050088 GtkWidget *webkit_chat_container; // The webkit_chat_container is created once, then reused for all chat views
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -050089
90 GSettings *settings;
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -050091
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -050092 details::CppImpl* cpp; ///< Non-UI and C++ only code
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050093};
94
95G_DEFINE_TYPE_WITH_PRIVATE(CurrentCallView, current_call_view, GTK_TYPE_BOX);
96
97#define CURRENT_CALL_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CURRENT_CALL_VIEW_TYPE, CurrentCallViewPrivate))
98
Stepan Salenikoviche1b54892015-12-13 22:18:44 -050099enum {
100 VIDEO_DOUBLE_CLICKED,
101 LAST_SIGNAL
102};
103
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500104//==============================================================================
105
106namespace { namespace details
107{
108
109static constexpr int CONTROLS_FADE_TIMEOUT = 3000000; /* microseconds */
110static constexpr int FADE_DURATION = 500; /* miliseconds */
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500111static guint current_call_view_signals[LAST_SIGNAL] = { 0 };
112
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500113namespace // Helpers
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500114{
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400115
Stepan Salenikovichdaf3cb32016-10-12 16:39:42 -0400116static gboolean
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500117map_boolean_to_orientation(GValue* value, GVariant* variant, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500118{
119 if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
120 if (g_variant_get_boolean(variant)) {
121 // true, chat should be horizontal (to the right)
122 g_value_set_enum(value, GTK_ORIENTATION_HORIZONTAL);
123 } else {
124 // false, chat should be vertical (at the bottom)
125 g_value_set_enum(value, GTK_ORIENTATION_VERTICAL);
126 }
127 return TRUE;
128 }
129 return FALSE;
130}
131
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500132static ClutterTransition*
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500133create_fade_out_transition()
134{
135 auto transition = clutter_property_transition_new("opacity");
136 clutter_transition_set_from(transition, G_TYPE_UINT, 255);
137 clutter_transition_set_to(transition, G_TYPE_UINT, 0);
138 clutter_timeline_set_duration(CLUTTER_TIMELINE(transition), FADE_DURATION);
139 clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(transition), 0);
140 clutter_timeline_set_progress_mode(CLUTTER_TIMELINE(transition), CLUTTER_EASE_IN_OUT_CUBIC);
141 return transition;
142}
143
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500144static GtkBox *
145gtk_scale_button_get_box(GtkScaleButton *button)
146{
147 GtkWidget *box = NULL;
148 if (auto dock = gtk_scale_button_get_popup(button)) {
149 // the dock is a popover which contains the box
150 box = gtk_bin_get_child(GTK_BIN(dock));
151 if (box) {
152 if (GTK_IS_FRAME(box)) {
153 // support older versions of gtk; the box used to be in a frame
154 box = gtk_bin_get_child(GTK_BIN(box));
155 }
156 }
157 }
158
159 return GTK_BOX(box);
160}
161
162/**
163 * This gets the GtkScaleButtonScale widget (which is a GtkScale) from the
164 * given GtkScaleButton in order to be able to modify its properties and connect
165 * to its signals
166 */
167static GtkScale *
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500168gtk_scale_button_get_scale(GtkScaleButton* button)
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500169{
170 GtkScale *scale = NULL;
171
172 if (auto box = gtk_scale_button_get_box(button)) {
173 GList *children = gtk_container_get_children(GTK_CONTAINER(box));
174 for (GList *c = children; c && !scale; c = c->next) {
175 if (GTK_IS_SCALE(c->data))
176 scale = GTK_SCALE(c->data);
177 }
178 g_list_free(children);
179 }
180
181 return scale;
182}
183
184static void
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500185set_call_quality(Call& call, bool auto_quality_on, double desired_quality)
Sébastien Blin3667fa62017-11-23 09:11:53 -0500186{
187 /* set auto quality true or false, also set the bitrate and quality values;
188 * the slider is from 0 to 100, use the min and max vals to scale each value accordingly */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500189 if (const auto& codecModel = call.account()->codecModel()) {
Sébastien Blin3667fa62017-11-23 09:11:53 -0500190 const auto& videoCodecs = codecModel->videoCodecs();
191
192 for (int i=0; i < videoCodecs->rowCount();i++) {
193 const auto& idx = videoCodecs->index(i,0);
194
195 if (auto_quality_on) {
196 // g_debug("enable auto quality");
197 videoCodecs->setData(idx, "true", CodecModel::Role::AUTO_QUALITY_ENABLED);
198 } else {
199 auto min_bitrate = idx.data(static_cast<int>(CodecModel::Role::MIN_BITRATE)).toInt();
200 auto max_bitrate = idx.data(static_cast<int>(CodecModel::Role::MAX_BITRATE)).toInt();
201 auto min_quality = idx.data(static_cast<int>(CodecModel::Role::MIN_QUALITY)).toInt();
202 auto max_quality = idx.data(static_cast<int>(CodecModel::Role::MAX_QUALITY)).toInt();
203
204 // g_debug("bitrate min: %d, max: %d, quality min: %d, max: %d", min_bitrate, max_bitrate, min_quality, max_quality);
205
206 double bitrate;
207 bitrate = min_bitrate + (double)(max_bitrate - min_bitrate)*(desired_quality/100.0);
208 if (bitrate < 0) bitrate = 0;
209
210 double quality;
211 // note: a lower value means higher quality
212 quality = (double)min_quality - (min_quality - max_quality)*(desired_quality/100.0);
213 if (quality < 0) quality = 0;
214
215 // g_debug("disable auto quality; %% quality: %d; bitrate: %d; quality: %d", (int)desired_quality, (int)bitrate, (int)quality);
216 videoCodecs->setData(idx, "false", CodecModel::Role::AUTO_QUALITY_ENABLED);
217 videoCodecs->setData(idx, QString::number((int)bitrate), CodecModel::Role::BITRATE);
218 videoCodecs->setData(idx, QString::number((int)quality), CodecModel::Role::QUALITY);
219 }
220 }
221 codecModel << CodecModel::EditAction::SAVE;
222 }
223}
224
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500225} // namespace
226
227class CppImpl
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500228{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500229public:
230 explicit CppImpl(CurrentCallView& widget);
231 ~CppImpl();
232
233 void init();
234 void setup(WebKitChatContainer* chat_widget,
235 AccountContainer* accountContainer,
236 lrc::api::conversation::Info* conversation);
237
238 void insertControls();
239 void checkControlsFading();
240
241 CurrentCallView* self = nullptr; // The GTK widget itself
242 CurrentCallViewPrivate* widgets = nullptr;
243
244 lrc::api::conversation::Info* conversation = nullptr;
245 AccountContainer* accountContainer = nullptr;
246
247 QMetaObject::Connection state_change_connection;
248 QMetaObject::Connection local_renderer_connection;
249 QMetaObject::Connection remote_renderer_connection;
250 QMetaObject::Connection new_message_connection;
251 QMetaObject::Connection smartinfo_refresh_connection;
252
253 // for clutter animations and to know when to fade in/out the overlays
254 ClutterTransition* fade_info = nullptr;
255 ClutterTransition* fade_controls = nullptr;
256 gint64 time_last_mouse_motion = 0;
257 guint timer_fade = 0;
258
259 /* flag used to keep track of the video quality scale pressed state;
260 * we do not want to update the codec bitrate until the user releases the
261 * scale button */
262 gboolean quality_scale_pressed = FALSE;
263 gulong insert_controls_id = 0;
264 guint smartinfo_action = 0;
265
266private:
267 CppImpl() = delete;
268 CppImpl(const CppImpl&) = delete;
269 CppImpl& operator=(const CppImpl&) = delete;
270
271 void setCallInfo();
272 void updateDetails();
273 void updateState();
274 void updateNameAndPhoto();
275 void updateSmartInfo();
276};
277
278inline namespace gtk_callbacks
279{
280
281static void
282on_new_chat_interactions(CurrentCallView* view)
283{
284 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
285 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
286
287 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_chat), TRUE);
288}
289
290static void
291on_togglebutton_chat_toggled(GtkToggleButton* widget, CurrentCallView* view)
292{
293 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
294 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
295
296 if (gtk_toggle_button_get_active(widget)) {
297 gtk_widget_show_all(priv->frame_chat);
298 gtk_widget_grab_focus(priv->frame_chat);
299 } else {
300 gtk_widget_hide(priv->frame_chat);
301 }
302}
303
304static gboolean
305on_timer_fade_timeout(CurrentCallView* view)
306{
307 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), G_SOURCE_REMOVE);
308 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
309 priv->cpp->checkControlsFading();
310 return G_SOURCE_CONTINUE;
311}
312
313static void
314on_size_allocate(CurrentCallView* view)
315{
316 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
317 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
318
319 priv->cpp->insertControls();
320}
321
322static void
323on_button_hangup_clicked(CurrentCallView* view)
324{
325 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
326 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
327
328 auto callToHangUp = priv->cpp->conversation->callId;
329 if (!priv->cpp->conversation->confId.empty())
330 callToHangUp = priv->cpp->conversation->confId;
331 priv->cpp->accountContainer->info.callModel->hangUp(callToHangUp);
332}
333
334static void
335on_togglebutton_hold_clicked(CurrentCallView* view)
336{
337 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
338 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
339
340 auto callToHold = priv->cpp->conversation->callId;
341 if (!priv->cpp->conversation->confId.empty())
342 callToHold = priv->cpp->conversation->confId;
343 priv->cpp->accountContainer->info.callModel->togglePause(callToHold);
344}
345
346static void
347on_togglebutton_record_clicked(CurrentCallView* view)
348{
349 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
350 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
351
352 auto callToRecord = priv->cpp->conversation->callId;
353 if (!priv->cpp->conversation->confId.empty())
354 callToRecord = priv->cpp->conversation->confId;
355 priv->cpp->accountContainer->info.callModel->toggleAudioRecord(callToRecord);
356}
357
358static void
359on_togglebutton_muteaudio_clicked(CurrentCallView* view)
360{
361 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
362 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
363
364 auto callToMute = priv->cpp->conversation->callId;
365 if (!priv->cpp->conversation->confId.empty())
366 callToMute = priv->cpp->conversation->confId;
367 //auto muteAudioBtn = GTK_TOGGLE_BUTTON(priv->togglebutton_muteaudio);
368 priv->cpp->accountContainer->info.callModel->toggleMedia(callToMute,
369 lrc::api::NewCallModel::Media::AUDIO);
370
371 auto togglebutton = GTK_TOGGLE_BUTTON(priv->togglebutton_muteaudio);
372 auto image = gtk_image_new_from_resource ("/cx/ring/RingGnome/mute_audio");
373 if (gtk_toggle_button_get_active(togglebutton))
374 image = gtk_image_new_from_resource ("/cx/ring/RingGnome/unmute_audio");
375 gtk_button_set_image(GTK_BUTTON(togglebutton), image);
376}
377
378static void
379on_togglebutton_mutevideo_clicked(CurrentCallView* view)
380{
381 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
382 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
383
384 auto callToMute = priv->cpp->conversation->callId;
385 if (!priv->cpp->conversation->confId.empty())
386 callToMute = priv->cpp->conversation->confId;
387 //auto muteVideoBtn = GTK_TOGGLE_BUTTON(priv->togglebutton_mutevideo);
388 priv->cpp->accountContainer->info.callModel->toggleMedia(callToMute,
389 lrc::api::NewCallModel::Media::VIDEO);
390
391 auto togglebutton = GTK_TOGGLE_BUTTON(priv->togglebutton_mutevideo);
392 auto image = gtk_image_new_from_resource ("/cx/ring/RingGnome/mute_video");
393 if (gtk_toggle_button_get_active(togglebutton))
394 image = gtk_image_new_from_resource ("/cx/ring/RingGnome/unmute_video");
395 gtk_button_set_image(GTK_BUTTON(togglebutton), image);
396}
397
398static gboolean
399on_mouse_moved(CurrentCallView* view)
400{
401 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), FALSE);
402 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
403
404 priv->cpp->time_last_mouse_motion = g_get_monotonic_time();
405
406 // since the mouse moved, make sure the controls are shown
407 if (clutter_timeline_get_direction(CLUTTER_TIMELINE(priv->cpp->fade_info)) == CLUTTER_TIMELINE_FORWARD) {
408 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->cpp->fade_info), CLUTTER_TIMELINE_BACKWARD);
409 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->cpp->fade_controls), CLUTTER_TIMELINE_BACKWARD);
410 if (!clutter_timeline_is_playing(CLUTTER_TIMELINE(priv->cpp->fade_info))) {
411 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->cpp->fade_info));
412 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->cpp->fade_controls));
413 clutter_timeline_start(CLUTTER_TIMELINE(priv->cpp->fade_info));
414 clutter_timeline_start(CLUTTER_TIMELINE(priv->cpp->fade_controls));
415 }
416 }
417
418 return FALSE; // propogate event
419}
420
421static void
422on_autoquality_toggled(GtkToggleButton* button, CurrentCallView* view)
423{
424 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
425 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500426
427 gboolean auto_quality_on = gtk_toggle_button_get_active(button);
428
429 auto scale = gtk_scale_button_get_scale(GTK_SCALE_BUTTON(priv->scalebutton_quality));
430 auto plus_button = gtk_scale_button_get_plus_button(GTK_SCALE_BUTTON(priv->scalebutton_quality));
431 auto minus_button = gtk_scale_button_get_minus_button(GTK_SCALE_BUTTON(priv->scalebutton_quality));
432
433 gtk_widget_set_sensitive(GTK_WIDGET(scale), !auto_quality_on);
434 gtk_widget_set_sensitive(plus_button, !auto_quality_on);
435 gtk_widget_set_sensitive(minus_button, !auto_quality_on);
Sébastien Blin3667fa62017-11-23 09:11:53 -0500436
437 double desired_quality = gtk_scale_button_get_value(GTK_SCALE_BUTTON(priv->scalebutton_quality));
438
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500439 auto callToRender = priv->cpp->conversation->callId;
440 if (!priv->cpp->conversation->confId.empty())
441 callToRender = priv->cpp->conversation->confId;
442 auto renderer = priv->cpp->accountContainer->info.callModel->getRenderer(callToRender);
443 for (auto* activeCall: CallModel::instance().getActiveCalls()) {
444 if (activeCall and activeCall->videoRenderer() == renderer)
445 set_call_quality(*activeCall, auto_quality_on, desired_quality);
446 }
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500447}
448
449static void
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500450on_quality_changed(G_GNUC_UNUSED GtkScaleButton *button, G_GNUC_UNUSED gdouble value,
451 CurrentCallView* view)
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500452{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500453 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
454 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500455
456 /* no need to upate quality if auto quality is enabled */
457 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->checkbutton_autoquality))) return;
458
459 /* only update if the scale button is released, to reduce the number of updates */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500460 if (priv->cpp->quality_scale_pressed) return;
Sébastien Blin3667fa62017-11-23 09:11:53 -0500461
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500462 auto callToRender = priv->cpp->conversation->callId;
463 if (!priv->cpp->conversation->confId.empty())
464 callToRender = priv->cpp->conversation->confId;
465 auto renderer = priv->cpp->accountContainer->info.callModel->getRenderer(callToRender);
466 for (auto* activeCall: CallModel::instance().getActiveCalls())
467 if (activeCall and activeCall->videoRenderer() == renderer)
468 set_call_quality(*activeCall, false, gtk_scale_button_get_value(button));
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500469}
470
471static gboolean
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500472on_quality_button_pressed(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event,
473 CurrentCallView* view)
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500474{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500475 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), FALSE);
476 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
477
478 priv->cpp->quality_scale_pressed = TRUE;
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500479
480 return GDK_EVENT_PROPAGATE;
481}
482
483static gboolean
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500484on_quality_button_released(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event,
485 CurrentCallView* view)
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500486{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500487 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), FALSE);
488 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
489
490 priv->cpp->quality_scale_pressed = FALSE;
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500491
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400492 // now make sure the quality gets updated
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500493 on_quality_changed(GTK_SCALE_BUTTON(priv->scalebutton_quality), 0, view);
494
495 return GDK_EVENT_PROPAGATE;
496}
497
498static gboolean
499on_video_widget_focus(GtkWidget* widget, GtkDirectionType direction, CurrentCallView* view)
500{
501 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), FALSE);
502 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
503
504 // if this widget already has focus, we want the focus to move to the next widget, otherwise we
505 // will get stuck in a focus loop on the buttons
506 if (gtk_widget_has_focus(widget))
507 return FALSE;
508
509 // otherwise we want the focus to go to and change between the call control buttons
510 if (gtk_widget_child_focus(GTK_WIDGET(priv->hbox_call_controls), direction)) {
511 // selected a child, make sure call controls are shown
512 on_mouse_moved(view);
513 return TRUE;
514 }
515
516 // did not select the next child, propogate the event
517 return FALSE;
518}
519
520static gboolean
521on_button_press_in_video_event(GtkWidget* widget, GdkEventButton *event, CurrentCallView* view)
522{
523 g_return_val_if_fail(IS_VIDEO_WIDGET(widget), FALSE);
524 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), FALSE);
525
526 // on double click
527 if (event->type == GDK_2BUTTON_PRESS) {
528 g_debug("double click in video");
529 g_signal_emit(G_OBJECT(view), current_call_view_signals[VIDEO_DOUBLE_CLICKED], 0);
530 }
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500531
532 return GDK_EVENT_PROPAGATE;
533}
534
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400535static void
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500536on_toggle_smartinfo(GSimpleAction* action, G_GNUC_UNUSED GVariant* state, GtkWidget* vbox_call_smartInfo)
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400537{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500538 if (g_variant_get_boolean(g_action_get_state(G_ACTION(action)))) {
539 gtk_widget_show(vbox_call_smartInfo);
540 } else {
541 gtk_widget_hide(vbox_call_smartInfo);
542 }
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400543}
544
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500545} // namespace gtk_callbacks
546
547CppImpl::CppImpl(CurrentCallView& widget)
548 : self {&widget}
549 , widgets {CURRENT_CALL_VIEW_GET_PRIVATE(&widget)}
550{}
551
552CppImpl::~CppImpl()
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400553{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500554 QObject::disconnect(state_change_connection);
555 QObject::disconnect(local_renderer_connection);
556 QObject::disconnect(remote_renderer_connection);
557 QObject::disconnect(smartinfo_refresh_connection);
558 QObject::disconnect(new_message_connection);
559 g_clear_object(&widgets->settings);
560
561 g_source_remove(timer_fade);
562
563 auto* display_smartinfo = g_action_map_lookup_action(G_ACTION_MAP(g_application_get_default()),
564 "display-smartinfo");
565 g_signal_handler_disconnect(display_smartinfo, smartinfo_action);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400566}
567
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500568void
569CppImpl::init()
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400570{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500571 // CSS styles
572 auto provider = gtk_css_provider_new();
573 gtk_css_provider_load_from_data(provider,
574 ".smartinfo-block-style { color: #8ae234; background-color: rgba(1, 1, 1, 0.33); } \
575 @keyframes blink { 0% {opacity: 1;} 49% {opacity: 1;} 50% {opacity: 0;} 100% {opacity: 0;} } \
576 .record-button { background: rgba(0, 0, 0, 1); border-radius: 50%; border: 0; transition: all 0.3s ease; } \
577 .record-button:checked { animation: blink 1s; animation-iteration-count: infinite; } \
578 .call-button { background: rgba(0, 0, 0, 0.35); border-radius: 50%; border: 0; transition: all 0.3s ease; } \
579 .call-button:hover { background: rgba(0, 0, 0, 0.2); } \
580 .call-button:disabled { opacity: 0.2; } \
581 .can-be-disabled:checked { background: rgba(219, 58, 55, 1); } \
582 .hangup-button-style { background: rgba(219, 58, 55, 1); border-radius: 50%; border: 0; transition: all 0.3s ease; } \
583 .hangup-button-style:hover { background: rgba(219, 39, 25, 1); }",
584 -1, nullptr
585 );
586 gtk_style_context_add_provider_for_screen(gdk_display_get_default_screen(gdk_display_get_default()),
587 GTK_STYLE_PROVIDER(provider),
588 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
589
590 widgets->video_widget = video_widget_new();
591 gtk_container_add(GTK_CONTAINER(widgets->frame_video), widgets->video_widget);
592 gtk_widget_show_all(widgets->frame_video);
593
594 // add the overlay controls only once the view has been allocated a size to prevent size
595 // allocation warnings in the log
596 insert_controls_id = g_signal_connect(self, "size-allocate", G_CALLBACK(on_size_allocate), nullptr);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400597}
598
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500599void
600CppImpl::setup(WebKitChatContainer* chat_widget,
601 AccountContainer* account_container,
602 lrc::api::conversation::Info* conv_info)
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400603{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500604 widgets->webkit_chat_container = GTK_WIDGET(chat_widget);
605 conversation = conv_info;
606 accountContainer = account_container;
607 setCallInfo();
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400608}
609
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500610void
611CppImpl::setCallInfo()
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400612{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500613 // change some things depending on call state
614 updateState();
615 updateDetails();
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400616
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500617 // NOTE/TODO we need to rewrite the video_widget file to use the new LRC.
618 g_signal_connect(widgets->video_widget, "button-press-event",
619 G_CALLBACK(video_widget_on_button_press_in_screen_event), nullptr);
620
621 // check if we already have a renderer
622 auto callToRender = conversation->callId;
623 if (!conversation->confId.empty())
624 callToRender = conversation->confId;
625 video_widget_push_new_renderer(VIDEO_WIDGET(widgets->video_widget),
626 accountContainer->info.callModel->getRenderer(callToRender),
627 VIDEO_RENDERER_REMOTE);
628
629 // local renderer
630 if (Video::PreviewManager::instance().isPreviewing())
631 video_widget_push_new_renderer(VIDEO_WIDGET(widgets->video_widget),
632 Video::PreviewManager::instance().previewRenderer(),
633 VIDEO_RENDERER_LOCAL);
634
635 // callback for local renderer
636 local_renderer_connection = QObject::connect(
637 &Video::PreviewManager::instance(),
638 &Video::PreviewManager::previewStarted,
639 [this] (Video::Renderer* renderer) {
640 video_widget_push_new_renderer(VIDEO_WIDGET(widgets->video_widget),
641 renderer,
642 VIDEO_RENDERER_LOCAL);
643 }
644 );
645
646 smartinfo_refresh_connection = QObject::connect(
647 &SmartInfoHub::instance(),
648 &SmartInfoHub::changed,
649 [this] { updateSmartInfo(); }
650 );
651
652 remote_renderer_connection = QObject::connect(
653 &*accountContainer->info.callModel,
654 &lrc::api::NewCallModel::remotePreviewStarted,
655 [this] (const std::string& callId, Video::Renderer* renderer) {
656 if (conversation->callId == callId) {
657 video_widget_push_new_renderer(VIDEO_WIDGET(widgets->video_widget),
658 renderer,
659 VIDEO_RENDERER_REMOTE);
660 }
661 });
662
663 state_change_connection = QObject::connect(
664 &*accountContainer->info.callModel,
665 &lrc::api::NewCallModel::callStatusChanged,
666 [this] (const std::string& callId) {
667 if (callId == conversation->callId) {
668 updateState();
669 updateNameAndPhoto();
670 }
671 });
672
673 new_message_connection = QObject::connect(
674 &*accountContainer->info.conversationModel,
675 &lrc::api::ConversationModel::newUnreadMessage,
676 [this] (const std::string& uid, uint64_t msgId, lrc::api::interaction::Info msg) {
677 Q_UNUSED(uid)
678 Q_UNUSED(msgId)
679 Q_UNUSED(msg)
680 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets->togglebutton_chat), TRUE);
681 });
682
683 // catch double click to make full screen
684 g_signal_connect(widgets->video_widget, "button-press-event",
685 G_CALLBACK(on_button_press_in_video_event), self);
686
687 // handle smartinfo in right click menu
688 auto display_smartinfo = g_action_map_lookup_action(G_ACTION_MAP(g_application_get_default()),
689 "display-smartinfo");
690 smartinfo_action = g_signal_connect(display_smartinfo,
691 "notify::state",
692 G_CALLBACK(on_toggle_smartinfo),
693 widgets->vbox_call_smartInfo);
694
695 // init chat view
696 widgets->chat_view = chat_view_new(WEBKIT_CHAT_CONTAINER(widgets->webkit_chat_container),
697 accountContainer, conversation);
698 gtk_container_add(GTK_CONTAINER(widgets->frame_chat), widgets->chat_view);
699
700 g_signal_connect_swapped(widgets->chat_view, "new-interactions-displayed",
701 G_CALLBACK(on_new_chat_interactions), self);
702 chat_view_set_header_visible(CHAT_VIEW(widgets->chat_view), FALSE);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400703}
704
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500705void
706CppImpl::insertControls()
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500707{
Stepan Salenikovich88092932017-05-15 18:19:00 -0400708 /* only add the controls once */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500709 g_signal_handler_disconnect(self, insert_controls_id);
710 insert_controls_id = 0;
Stepan Salenikoviche178e632015-11-06 13:31:19 -0500711
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500712 auto stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(widgets->video_widget));
713 auto actor_info = gtk_clutter_actor_new_with_contents(widgets->hbox_call_info);
714 auto actor_controls = gtk_clutter_actor_new_with_contents(widgets->hbox_call_controls);
715 auto actor_smartInfo = gtk_clutter_actor_new_with_contents(widgets->vbox_call_smartInfo);
Stepan Salenikoviche178e632015-11-06 13:31:19 -0500716
717 clutter_actor_add_child(stage, actor_info);
718 clutter_actor_set_x_align(actor_info, CLUTTER_ACTOR_ALIGN_FILL);
719 clutter_actor_set_y_align(actor_info, CLUTTER_ACTOR_ALIGN_START);
720
721 clutter_actor_add_child(stage, actor_controls);
722 clutter_actor_set_x_align(actor_controls, CLUTTER_ACTOR_ALIGN_CENTER);
723 clutter_actor_set_y_align(actor_controls, CLUTTER_ACTOR_ALIGN_END);
724
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400725 clutter_actor_add_child(stage, actor_smartInfo);
726 clutter_actor_set_x_align(actor_smartInfo, CLUTTER_ACTOR_ALIGN_END);
727 clutter_actor_set_y_align(actor_smartInfo, CLUTTER_ACTOR_ALIGN_START);
728 ClutterMargin clutter_margin_smartInfo;
729 clutter_margin_smartInfo.top = 50;
730 clutter_margin_smartInfo.right = 10;
philippegorleya7337942017-07-04 15:29:42 -0400731 clutter_margin_smartInfo.left = 10;
732 clutter_margin_smartInfo.bottom = 10;
Olivier Gregoire66e4df72016-06-17 18:39:05 -0400733 clutter_actor_set_margin (actor_smartInfo, &clutter_margin_smartInfo);
734
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500735 /* add fade in and out states to the info and controls */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500736 time_last_mouse_motion = g_get_monotonic_time();
737 fade_info = create_fade_out_transition();
738 fade_controls = create_fade_out_transition();
739 clutter_actor_add_transition(actor_info, "fade_info", fade_info);
740 clutter_actor_add_transition(actor_controls, "fade_controls", fade_controls);
741 clutter_timeline_set_direction(CLUTTER_TIMELINE(fade_info), CLUTTER_TIMELINE_BACKWARD);
742 clutter_timeline_set_direction(CLUTTER_TIMELINE(fade_controls), CLUTTER_TIMELINE_BACKWARD);
743 clutter_timeline_stop(CLUTTER_TIMELINE(fade_info));
744 clutter_timeline_stop(CLUTTER_TIMELINE(fade_controls));
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500745
746 /* have a timer check every 1 second if the controls should fade out */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500747 timer_fade = g_timeout_add(1000, (GSourceFunc)on_timer_fade_timeout, self);
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500748
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400749 /* connect the controllers (new model) */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500750 g_signal_connect_swapped(widgets->button_hangup, "clicked", G_CALLBACK(on_button_hangup_clicked), self);
751 g_signal_connect_swapped(widgets->togglebutton_hold, "clicked", G_CALLBACK(on_togglebutton_hold_clicked), self);
752 g_signal_connect_swapped(widgets->togglebutton_muteaudio, "clicked", G_CALLBACK(on_togglebutton_muteaudio_clicked), self);
753 g_signal_connect_swapped(widgets->togglebutton_record, "clicked", G_CALLBACK(on_togglebutton_record_clicked), self);
754 g_signal_connect_swapped(widgets->togglebutton_mutevideo, "clicked", G_CALLBACK(on_togglebutton_mutevideo_clicked), self);
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400755
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500756 /* connect to the mouse motion event to reset the last moved time */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500757 g_signal_connect_swapped(widgets->video_widget, "motion-notify-event", G_CALLBACK(on_mouse_moved), self);
758 g_signal_connect_swapped(widgets->video_widget, "button-press-event", G_CALLBACK(on_mouse_moved), self);
759 g_signal_connect_swapped(widgets->video_widget, "button-release-event", G_CALLBACK(on_mouse_moved), self);
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500760
Stepan Salenikovich5ed1b492015-11-13 14:03:31 -0500761 /* manually handle the focus of the video widget to be able to focus on the call controls */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500762 g_signal_connect(widgets->video_widget, "focus", G_CALLBACK(on_video_widget_focus), self);
Stepan Salenikovich5ed1b492015-11-13 14:03:31 -0500763
Sébastien Blin55bff9d2017-10-03 15:15:23 -0400764
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500765 /* toggle whether or not the chat is displayed */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500766 g_signal_connect(widgets->togglebutton_chat, "toggled", G_CALLBACK(on_togglebutton_chat_toggled), self);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400767
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500768 /* bind the chat orientation to the gsetting */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500769 widgets->settings = g_settings_new_full(get_ring_schema(), nullptr, nullptr);
770 g_settings_bind_with_mapping(widgets->settings, "chat-pane-horizontal",
771 widgets->paned_call, "orientation",
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500772 G_SETTINGS_BIND_GET,
773 map_boolean_to_orientation,
774 nullptr, nullptr, nullptr);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500775
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500776 g_signal_connect(widgets->scalebutton_quality, "value-changed", G_CALLBACK(on_quality_changed), self);
777
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500778 /* customize the quality button scale */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500779 if (auto scale_box = gtk_scale_button_get_box(GTK_SCALE_BUTTON(widgets->scalebutton_quality))) {
780 widgets->checkbutton_autoquality = gtk_check_button_new_with_label(C_("Enable automatic video quality",
781 "Auto"));
782 gtk_widget_show(widgets->checkbutton_autoquality);
783 gtk_box_pack_start(GTK_BOX(scale_box), widgets->checkbutton_autoquality, FALSE, TRUE, 0);
784 g_signal_connect(widgets->checkbutton_autoquality, "toggled", G_CALLBACK(on_autoquality_toggled), self);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500785 }
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500786 if (auto scale = gtk_scale_button_get_scale(GTK_SCALE_BUTTON(widgets->scalebutton_quality))) {
787 g_signal_connect(scale, "button-press-event", G_CALLBACK(on_quality_button_pressed), self);
788 g_signal_connect(scale, "button-release-event", G_CALLBACK(on_quality_button_released), self);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500789 }
Stepan Salenikovich88092932017-05-15 18:19:00 -0400790
Sébastien Blin3667fa62017-11-23 09:11:53 -0500791 /* by this time we should have the call already set, but we check to make sure */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500792 auto callToRender = conversation->callId;
793 if (!conversation->confId.empty())
794 callToRender = conversation->confId;
795 auto renderer = accountContainer->info.callModel->getRenderer(callToRender);
796 for (auto* activeCall: CallModel::instance().getActiveCalls())
797 if (activeCall and activeCall->videoRenderer() == renderer) {
798 g_signal_connect(widgets->video_widget, "drag-data-received",
799 G_CALLBACK(video_widget_on_drag_data_received), activeCall);
Sébastien Blin3667fa62017-11-23 09:11:53 -0500800 /* check if auto quality is enabled or not */
801 if (const auto& codecModel = activeCall->account()->codecModel()) {
802 const auto& videoCodecs = codecModel->videoCodecs();
803 if (videoCodecs->rowCount() > 0) {
804 /* we only need to check the first codec since by default it is ON for all, and the
805 * gnome client sets its ON or OFF for all codecs as well */
806 const auto& idx = videoCodecs->index(0,0);
807 auto auto_quality_enabled = idx.data(static_cast<int>(CodecModel::Role::AUTO_QUALITY_ENABLED)).toString() == "true";
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500808 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets->checkbutton_autoquality),
809 auto_quality_enabled);
Sébastien Blin3667fa62017-11-23 09:11:53 -0500810
811 // TODO: save the manual quality setting in the client and set the slider to that value here;
812 // the daemon resets the bitrate/quality between each call, and the default may be
813 // different for each codec, so there is no reason to check it here
814 }
815 }
816 } else {
817 /* Auto-quality is off by default */
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500818 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets->checkbutton_autoquality), FALSE);
Sébastien Blin3667fa62017-11-23 09:11:53 -0500819 }
820
Sébastien Blin4514eeb2017-07-25 14:17:01 -0400821 // Get if the user wants to show the smartInfo box
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500822 auto display_smartinfo = g_action_map_lookup_action(G_ACTION_MAP(g_application_get_default()),
823 "display-smartinfo");
Sébastien Blin4514eeb2017-07-25 14:17:01 -0400824 if (g_variant_get_boolean(g_action_get_state(G_ACTION(display_smartinfo)))) {
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500825 gtk_widget_show(widgets->vbox_call_smartInfo);
Sébastien Blin4514eeb2017-07-25 14:17:01 -0400826 } else {
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500827 gtk_widget_hide(widgets->vbox_call_smartInfo);
Sébastien Blin4514eeb2017-07-25 14:17:01 -0400828 }
Stepan Salenikovich88092932017-05-15 18:19:00 -0400829}
830
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -0500831void
832CppImpl::updateDetails()
833{
834 auto callRendered = conversation->callId;
835
836 if (!conversation->confId.empty())
837 callRendered = conversation->confId;
838
839 gtk_label_set_text(GTK_LABEL(widgets->label_duration),
840 accountContainer->info.callModel->getFormattedCallDuration(callRendered).c_str());
841
842 auto call = accountContainer->info.callModel->getCall(callRendered);
843 gtk_widget_set_sensitive(GTK_WIDGET(widgets->togglebutton_muteaudio),
844 (call.type != lrc::api::call::Type::CONFERENCE));
845 gtk_widget_set_sensitive(GTK_WIDGET(widgets->togglebutton_mutevideo),
846 (call.type != lrc::api::call::Type::CONFERENCE));
847}
848
849void
850CppImpl::updateState()
851{
852 if (conversation) return;
853
854 auto callId = conversation->callId;
855
856 try {
857 auto call = accountContainer->info.callModel->getCall(callId);
858
859 auto pauseBtn = GTK_TOGGLE_BUTTON(widgets->togglebutton_hold);
860 auto image = gtk_image_new_from_resource ("/cx/ring/RingGnome/pause");
861 if (call.status == lrc::api::call::Status::PAUSED)
862 image = gtk_image_new_from_resource ("/cx/ring/RingGnome/play");
863 gtk_button_set_image(GTK_BUTTON(pauseBtn), image);
864
865 auto audioButton = GTK_TOGGLE_BUTTON(widgets->togglebutton_muteaudio);
866 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets->togglebutton_muteaudio), call.audioMuted);
867 auto imageMuteAudio = gtk_image_new_from_resource ("/cx/ring/RingGnome/mute_audio");
868 if (call.audioMuted)
869 imageMuteAudio = gtk_image_new_from_resource ("/cx/ring/RingGnome/unmute_audio");
870 gtk_button_set_image(GTK_BUTTON(audioButton), imageMuteAudio);
871
872 auto videoButton = GTK_TOGGLE_BUTTON(widgets->togglebutton_mutevideo);
873 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets->togglebutton_mutevideo), call.videoMuted);
874 auto imageMuteVideo = gtk_image_new_from_resource ("/cx/ring/RingGnome/mute_video");
875 if (call.videoMuted)
876 imageMuteVideo = gtk_image_new_from_resource ("/cx/ring/RingGnome/unmute_video");
877 gtk_button_set_image(GTK_BUTTON(videoButton), imageMuteVideo);
878
879 gchar *status = g_strdup_printf("%s", lrc::api::call::to_string(call.status).c_str());
880 gtk_label_set_text(GTK_LABEL(widgets->label_status), status);
881 g_free(status);
882 } catch (std::out_of_range& e) {
883 g_warning("Can't update state for callId=%s", callId.c_str());
884 }
885}
886
887void
888CppImpl::updateNameAndPhoto()
889{
890 QVariant var_i = GlobalInstances::pixmapManipulator().conversationPhoto(
891 *conversation,
892 accountContainer->info,
893 QSize(60, 60),
894 false
895 );
896 std::shared_ptr<GdkPixbuf> image = var_i.value<std::shared_ptr<GdkPixbuf>>();
897 gtk_image_set_from_pixbuf(GTK_IMAGE(widgets->image_peer), image.get());
898
899 try {
900 auto contactInfo = accountContainer->info.contactModel->getContact(conversation->participants.front());
901 auto name = contactInfo.profileInfo.alias;
902 gtk_label_set_text(GTK_LABEL(widgets->label_name), name.c_str());
903
904 auto bestId = contactInfo.registeredName;
905 if (name != bestId) {
906 gtk_label_set_text(GTK_LABEL(widgets->label_bestId), bestId.c_str());
907 gtk_widget_show(widgets->label_bestId);
908 }
909 } catch (const std::out_of_range&) {
910 // ContactModel::getContact() exception
911 }
912}
913
914void
915CppImpl::updateSmartInfo()
916{
917 if (!SmartInfoHub::instance().isConference()) {
918 gchar* general_information = g_strdup_printf(
919 "Call ID: %s", SmartInfoHub::instance().callID().toStdString().c_str());
920 gtk_label_set_text(GTK_LABEL(widgets->label_smartinfo_general_information), general_information);
921 g_free(general_information);
922
923 gchar* description = g_strdup_printf("You\n"
924 "Framerate:\n"
925 "Video codec:\n"
926 "Audio codec:\n"
927 "Resolution:\n\n"
928 "Peer\n"
929 "Framerate:\n"
930 "Video codec:\n"
931 "Audio codec:\n"
932 "Resolution:");
933 gtk_label_set_text(GTK_LABEL(widgets->label_smartinfo_description),description);
934 g_free(description);
935
936 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",
937 (double)SmartInfoHub::instance().localFps(),
938 SmartInfoHub::instance().localVideoCodec().toStdString().c_str(),
939 SmartInfoHub::instance().localAudioCodec().toStdString().c_str(),
940 SmartInfoHub::instance().localWidth(),
941 SmartInfoHub::instance().localHeight(),
942 (double)SmartInfoHub::instance().remoteFps(),
943 SmartInfoHub::instance().remoteVideoCodec().toStdString().c_str(),
944 SmartInfoHub::instance().remoteAudioCodec().toStdString().c_str(),
945 SmartInfoHub::instance().remoteWidth(),
946 SmartInfoHub::instance().remoteHeight());
947 gtk_label_set_text(GTK_LABEL(widgets->label_smartinfo_value),value);
948 g_free(value);
949 } else {
950 gchar* general_information = g_strdup_printf(
951 "Conference ID: %s", SmartInfoHub::instance().callID().toStdString().c_str());
952 gtk_label_set_text(GTK_LABEL(widgets->label_smartinfo_general_information), general_information);
953 g_free(general_information);
954
955 gchar* description = g_strdup_printf("You\n"
956 "Framerate:\n"
957 "Video codec:\n"
958 "Audio codec:\n"
959 "Resolution:");
960 gtk_label_set_text(GTK_LABEL(widgets->label_smartinfo_description),description);
961 g_free(description);
962
963 gchar* value = g_strdup_printf("\n%f\n%s\n%s\n%dx%d",
964 (double)SmartInfoHub::instance().localFps(),
965 SmartInfoHub::instance().localVideoCodec().toStdString().c_str(),
966 SmartInfoHub::instance().localAudioCodec().toStdString().c_str(),
967 SmartInfoHub::instance().localWidth(),
968 SmartInfoHub::instance().localHeight());
969 gtk_label_set_text(GTK_LABEL(widgets->label_smartinfo_value),value);
970 g_free(value);
971 }
972}
973
974void
975CppImpl::checkControlsFading()
976{
977 auto current_time = g_get_monotonic_time();
978 if (current_time - time_last_mouse_motion >= CONTROLS_FADE_TIMEOUT) {
979 // timeout has passed, hide the controls
980 if (clutter_timeline_get_direction(CLUTTER_TIMELINE(fade_info)) == CLUTTER_TIMELINE_BACKWARD) {
981 clutter_timeline_set_direction(CLUTTER_TIMELINE(fade_info), CLUTTER_TIMELINE_FORWARD);
982 clutter_timeline_set_direction(CLUTTER_TIMELINE(fade_controls), CLUTTER_TIMELINE_FORWARD);
983 if (!clutter_timeline_is_playing(CLUTTER_TIMELINE(fade_info))) {
984 clutter_timeline_rewind(CLUTTER_TIMELINE(fade_info));
985 clutter_timeline_rewind(CLUTTER_TIMELINE(fade_controls));
986 clutter_timeline_start(CLUTTER_TIMELINE(fade_info));
987 clutter_timeline_start(CLUTTER_TIMELINE(fade_controls));
988 }
989 }
990 }
991
992 updateDetails();
993}
994
995}} // namespace <anonymous>::details
996
997//==============================================================================
998
999lrc::api::conversation::Info
1000current_call_view_get_conversation(CurrentCallView *self)
1001{
1002 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), lrc::api::conversation::Info());
1003 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
1004 return *priv->cpp->conversation;
1005}
1006
1007GtkWidget *
1008current_call_view_get_chat_view(CurrentCallView *self)
1009{
1010 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), nullptr);
1011 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
1012 return priv->chat_view;
1013}
1014
1015//==============================================================================
1016
Stepan Salenikovich88092932017-05-15 18:19:00 -04001017static void
1018current_call_view_init(CurrentCallView *view)
1019{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -05001020 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
Stepan Salenikovich88092932017-05-15 18:19:00 -04001021 gtk_widget_init_template(GTK_WIDGET(view));
1022
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -05001023 // CppImpl ctor
1024 priv->cpp = new details::CppImpl {*view};
1025 priv->cpp->init();
1026}
Stepan Salenikovich88092932017-05-15 18:19:00 -04001027
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -05001028static void
1029current_call_view_dispose(GObject *object)
1030{
1031 auto* view = CURRENT_CALL_VIEW(object);
1032 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
Stepan Salenikovich88092932017-05-15 18:19:00 -04001033
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -05001034 delete priv->cpp;
1035 priv->cpp = nullptr;
Stepan Salenikovich88092932017-05-15 18:19:00 -04001036
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -05001037 G_OBJECT_CLASS(current_call_view_parent_class)->dispose(object);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05001038}
1039
1040static void
1041current_call_view_class_init(CurrentCallViewClass *klass)
1042{
1043 G_OBJECT_CLASS(klass)->dispose = current_call_view_dispose;
1044
1045 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1046 "/cx/ring/RingGnome/currentcallview.ui");
1047
Stepan Salenikoviche178e632015-11-06 13:31:19 -05001048 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_info);
1049 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_controls);
Olivier Gregoire66e4df72016-06-17 18:39:05 -04001050 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, vbox_call_smartInfo);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05001051 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, image_peer);
Stepan Salenikovich07107e92016-05-06 10:35:17 -04001052 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_name);
Nicolas Jager2e467c32017-01-18 08:52:23 -05001053 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_bestId);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05001054 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_status);
1055 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_duration);
Olivier Gregoire66e4df72016-06-17 18:39:05 -04001056 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_smartinfo_description);
1057 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_smartinfo_value);
1058 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_smartinfo_general_information);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -05001059 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, paned_call);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001060 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_video);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -05001061 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_chat);
Stepan Salenikovicha448f602015-05-29 13:33:06 -04001062 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_chat);
AmarOke7c02972017-07-17 15:21:20 -04001063 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_hold);
1064 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_muteaudio);
Sébastien Blin55bff9d2017-10-03 15:15:23 -04001065 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_record);
AmarOke7c02972017-07-17 15:21:20 -04001066 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_mutevideo);
Stepan Salenikovich77baa522015-07-07 15:29:14 -04001067 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, button_hangup);
Stepan Salenikovich7e283552015-12-21 16:17:52 -05001068 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, scalebutton_quality);
Stepan Salenikoviche1b54892015-12-13 22:18:44 -05001069
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -05001070 details::current_call_view_signals[VIDEO_DOUBLE_CLICKED] = g_signal_new (
Stepan Salenikoviche1b54892015-12-13 22:18:44 -05001071 "video-double-clicked",
1072 G_TYPE_FROM_CLASS(klass),
1073 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
1074 0,
1075 nullptr,
1076 nullptr,
1077 g_cclosure_marshal_VOID__VOID,
1078 G_TYPE_NONE, 0);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05001079}
1080
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001081GtkWidget *
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -05001082current_call_view_new(WebKitChatContainer* chat_widget,
1083 AccountContainer* accountContainer,
1084 lrc::api::conversation::Info* conversation)
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001085{
Guillaume Roguezaaf8b0f2018-01-24 14:38:43 -05001086 auto* self = g_object_new(CURRENT_CALL_VIEW_TYPE, NULL);
1087 auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
1088
1089 priv->cpp->setup(chat_widget, accountContainer, conversation);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001090
1091 return GTK_WIDGET(self);
1092}