add SmartInfo

Use smartInfoHub API to display relevant advanced information
during a call.

Change-Id: Ia4e19953e69460ee7e006654cba553bfc2d06581
[stepan.salenikovich@savoirfairelinux.com:  got rid of mem leaks; disconnect
 from signal on destructions; improved menu item; perform start/stop action
 in RingClient so that it can be called from anywhere]
Signed-off-by: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
Reviewed-by: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
diff --git a/src/currentcallview.cpp b/src/currentcallview.cpp
index c4fa04a..7a61393 100644
--- a/src/currentcallview.cpp
+++ b/src/currentcallview.cpp
@@ -42,6 +42,7 @@
 #include "chatview.h"
 #include <itemdataroles.h>
 #include <numbercategory.h>
+#include <smartinfohub.h>
 
 static constexpr int CONTROLS_FADE_TIMEOUT = 3000000; /* microseconds */
 static constexpr int FADE_DURATION = 500; /* miliseconds */
@@ -62,11 +63,15 @@
 {
     GtkWidget *hbox_call_info;
     GtkWidget *hbox_call_controls;
+    GtkWidget *vbox_call_smartInfo;
     GtkWidget *image_peer;
     GtkWidget *label_name;
     GtkWidget *label_uri;
     GtkWidget *label_status;
     GtkWidget *label_duration;
+    GtkWidget *label_smartinfo_description;
+    GtkWidget *label_smartinfo_value;
+    GtkWidget *label_smartinfo_general_information;
     GtkWidget *paned_call;
     GtkWidget *frame_video;
     GtkWidget *video_widget;
@@ -95,6 +100,10 @@
     ClutterTransition *fade_controls;
     gint64 time_last_mouse_motion;
     guint timer_fade;
+
+    // smart info
+    QMetaObject::Connection smartinfo_refresh_connection;
+    guint smartinfo_action;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE(CurrentCallView, current_call_view, GTK_TYPE_BOX);
@@ -121,11 +130,14 @@
     QObject::disconnect(priv->call_details_connection);
     QObject::disconnect(priv->local_renderer_connection);
     QObject::disconnect(priv->remote_renderer_connection);
-
+    QObject::disconnect(priv->smartinfo_refresh_connection);
     g_clear_object(&priv->settings);
 
     g_source_remove(priv->timer_fade);
 
+    auto display_smartinfo = g_action_map_lookup_action(G_ACTION_MAP(g_application_get_default()), "display-smartinfo");
+    g_signal_handler_disconnect(display_smartinfo, priv->smartinfo_action);
+
     G_OBJECT_CLASS(current_call_view_parent_class)->dispose(object);
 }
 
@@ -410,6 +422,7 @@
     auto stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->video_widget));
     auto actor_info = gtk_clutter_actor_new_with_contents(priv->hbox_call_info);
     auto actor_controls = gtk_clutter_actor_new_with_contents(priv->hbox_call_controls);
+    auto actor_smartInfo = gtk_clutter_actor_new_with_contents(priv->vbox_call_smartInfo);
 
     clutter_actor_add_child(stage, actor_info);
     clutter_actor_set_x_align(actor_info, CLUTTER_ACTOR_ALIGN_FILL);
@@ -419,6 +432,14 @@
     clutter_actor_set_x_align(actor_controls, CLUTTER_ACTOR_ALIGN_CENTER);
     clutter_actor_set_y_align(actor_controls, CLUTTER_ACTOR_ALIGN_END);
 
+    clutter_actor_add_child(stage, actor_smartInfo);
+    clutter_actor_set_x_align(actor_smartInfo, CLUTTER_ACTOR_ALIGN_END);
+    clutter_actor_set_y_align(actor_smartInfo, CLUTTER_ACTOR_ALIGN_START);
+    ClutterMargin clutter_margin_smartInfo;
+    clutter_margin_smartInfo.top = 50;
+    clutter_margin_smartInfo.right = 10;
+    clutter_actor_set_margin (actor_smartInfo, &clutter_margin_smartInfo);
+
     /* add fade in and out states to the info and controls */
     priv->time_last_mouse_motion = g_get_monotonic_time();
     priv->fade_info = create_fade_out_transition();
@@ -477,11 +498,15 @@
 
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_info);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_controls);
+    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, vbox_call_smartInfo);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, image_peer);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_name);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_uri);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_status);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_duration);
+    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_smartinfo_description);
+    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_smartinfo_value);
+    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_smartinfo_general_information);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, paned_call);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_video);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_chat);
@@ -528,6 +553,67 @@
     gtk_label_set_text(GTK_LABEL(priv->label_duration), ba_length.constData());
 }
 
+static void
+update_smartInfo(CurrentCallView *view)
+{
+    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
+
+    if (!SmartInfoHub::instance().isConference()) {
+        gchar* general_information = g_strdup_printf("Call ID: %s", SmartInfoHub::instance().callID().toStdString().c_str());
+        gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_general_information), general_information);
+        g_free(general_information);
+
+        gchar* description = g_strdup_printf("You\n"
+                                             "Framerate:\n"
+                                             "Video codec:\n"
+                                             "Audio codec:\n"
+                                             "Resolution:\n\n"
+                                             "Peer\n"
+                                             "Framerate:\n"
+                                             "Video codec:\n"
+                                             "Audio codec:\n"
+                                             "Resolution:");
+        gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_description),description);
+        g_free(description);
+
+        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",
+                                       (double)SmartInfoHub::instance().localFps(),
+                                       SmartInfoHub::instance().localVideoCodec().toStdString().c_str(),
+                                       SmartInfoHub::instance().localAudioCodec().toStdString().c_str(),
+                                       SmartInfoHub::instance().localWidth(),
+                                       SmartInfoHub::instance().localHeight(),
+                                       (double)SmartInfoHub::instance().remoteFps(),
+                                       SmartInfoHub::instance().remoteVideoCodec().toStdString().c_str(),
+                                       SmartInfoHub::instance().remoteAudioCodec().toStdString().c_str(),
+                                       SmartInfoHub::instance().remoteWidth(),
+                                       SmartInfoHub::instance().remoteHeight());
+        gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_value),value);
+        g_free(value);
+    } else {
+        gchar* general_information = g_strdup_printf("Conference ID: %s", SmartInfoHub::instance().callID().toStdString().c_str());
+        gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_general_information), general_information);
+        g_free(general_information);
+
+        gchar* description = g_strdup_printf("You\n"
+                                             "Framerate:\n"
+                                             "Video codec:\n"
+                                             "Audio codec:\n"
+                                             "Resolution:");
+        gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_description),description);
+        g_free(description);
+
+        gchar* value = g_strdup_printf("\n%f\n%s\n%s\n%dx%d",
+                                       (double)SmartInfoHub::instance().localFps(),
+                                       SmartInfoHub::instance().localVideoCodec().toStdString().c_str(),
+                                       SmartInfoHub::instance().localAudioCodec().toStdString().c_str(),
+                                       SmartInfoHub::instance().localWidth(),
+                                       SmartInfoHub::instance().localHeight());
+        gtk_label_set_text(GTK_LABEL(priv->label_smartinfo_value),value);
+        g_free(value);
+    }
+}
+
+
 static gboolean
 on_button_press_in_video_event(GtkWidget *self, GdkEventButton *event, CurrentCallView *view)
 {
@@ -543,6 +629,16 @@
     return GDK_EVENT_PROPAGATE;
 }
 
+static void
+toggle_smartinfo(GSimpleAction* action, G_GNUC_UNUSED GVariant* state, GtkWidget* vbox_call_smartInfo)
+{
+    if (g_variant_get_boolean(g_action_get_state(G_ACTION(action)))) {
+        gtk_widget_show(vbox_call_smartInfo);
+    } else {
+        gtk_widget_hide(vbox_call_smartInfo);
+    }
+}
+
 void
 current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
     CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
@@ -573,6 +669,12 @@
     update_state(view, priv->call);
     update_details(view, priv->call);
 
+    priv->smartinfo_refresh_connection = QObject::connect(
+        &SmartInfoHub::instance(),
+        &SmartInfoHub::changed,
+        [view, priv]() { update_smartInfo(view); }
+    );
+
     priv->state_change_connection = QObject::connect(
         priv->call,
         &Call::stateChanged,
@@ -629,6 +731,13 @@
                      G_CALLBACK(on_button_press_in_video_event),
                      view);
 
+    /* handle smartinfo in right click menu */
+    auto display_smartinfo = g_action_map_lookup_action(G_ACTION_MAP(g_application_get_default()), "display-smartinfo");
+    priv->smartinfo_action = g_signal_connect(display_smartinfo,
+                                              "notify::state",
+                                              G_CALLBACK(toggle_smartinfo),
+                                              priv->vbox_call_smartInfo);
+
     /* check if auto quality is enabled or not; */
     if (const auto& codecModel = priv->call->account()->codecModel()) {
         const auto& videoCodecs = codecModel->videoCodecs();
diff --git a/src/ring_client.cpp b/src/ring_client.cpp
index 4fbaee5..5906e99 100644
--- a/src/ring_client.cpp
+++ b/src/ring_client.cpp
@@ -50,6 +50,7 @@
 #include <peerprofilecollection.h>
 #include <localprofilecollection.h>
 #include <accountmodel.h>
+#include <smartinfohub.h>
 
 // Ring client
 #include "ring_client_options.h"
@@ -177,16 +178,28 @@
     ring_about_dialog(priv->win);
 }
 
+static void
+toggle_smartinfo(GSimpleAction *action, GVariant *parameter, gpointer)
+{
+    g_simple_action_set_state(action, parameter);
+    if (g_variant_get_boolean(parameter)) {
+        SmartInfoHub::instance().start();
+    } else {
+        SmartInfoHub::instance().stop();
+    }
+}
+
 static const GActionEntry ring_actions[] =
 {
-    { "accept", NULL,         NULL, NULL,    NULL, {0} },
-    { "hangup", NULL,         NULL, NULL,    NULL, {0} },
-    { "hold",   NULL,         NULL, "false", NULL, {0} },
-    { "quit",   action_quit,  NULL, NULL,    NULL, {0} },
-    { "about",  action_about, NULL, NULL,    NULL, {0} },
-    { "mute_audio", NULL,     NULL, "false", NULL, {0} },
-    { "mute_video", NULL,     NULL, "false", NULL, {0} },
-    { "record",     NULL,     NULL, "false", NULL, {0} },
+    { "accept",             NULL,         NULL, NULL,    NULL, {0} },
+    { "hangup",             NULL,         NULL, NULL,    NULL, {0} },
+    { "hold",               NULL,         NULL, "false", NULL, {0} },
+    { "quit",               action_quit,  NULL, NULL,    NULL, {0} },
+    { "about",              action_about, NULL, NULL,    NULL, {0} },
+    { "mute_audio",         NULL,         NULL, "false", NULL, {0} },
+    { "mute_video",         NULL,         NULL, "false", NULL, {0} },
+    { "record",             NULL,         NULL, "false", NULL, {0} },
+    { "display-smartinfo",  NULL,         NULL, "false", toggle_smartinfo, {0} },
     /* TODO implement the other actions */
     // { "transfer",   NULL,        NULL, "flase", NULL, {0} },
 };
diff --git a/src/video/video_widget.cpp b/src/video/video_widget.cpp
index d5f12bd..1f7b62a 100644
--- a/src/video/video_widget.cpp
+++ b/src/video/video_widget.cpp
@@ -33,6 +33,7 @@
 #include <mutex>
 #include <call.h>
 #include "xrectsel.h"
+#include <smartinfohub.h>
 
 static constexpr int VIDEO_LOCAL_SIZE            = 150;
 static constexpr int VIDEO_LOCAL_OPACITY_DEFAULT = 255; /* out of 255 */
@@ -172,7 +173,6 @@
     G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
 }
 
-
 /*
  * video_widget_class_init()
  *
@@ -522,6 +522,15 @@
     g_object_set_data(G_OBJECT(item), JOIN_CALL_KEY, call);
     g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_file), parent);
 
+    /* add separator */
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
+
+    /* add SmartInfo */
+    item = gtk_check_menu_item_new_with_mnemonic(_("Show advanced information"));
+    gtk_actionable_set_action_name(GTK_ACTIONABLE(item), "app.display-smartinfo");
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+    gtk_widget_insert_action_group(menu, "app", G_ACTION_GROUP(g_application_get_default()));
+
     /* show menu */
     gtk_widget_show_all(menu);
     gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);