gnome: implement dbus error interface

DBus errors usually indicate that dring has crashed or been killed.
This handler shows a warning dialog and gives dring 2.5s to restart.
If a DBus connection is not re-established, an error dialog is show and
the client quits.

This implementation prevents the client from suddenly crashing when
dring is killed due to the exception thrown by the LRC
implementation of the interface.

Patch adds updated translation template.

Issue: #79655
Change-Id: Ie7679a11ca357d68d8b87a3925abe4598d36f02c
diff --git a/src/native/dbuserrorhandler.cpp b/src/native/dbuserrorhandler.cpp
new file mode 100644
index 0000000..e98afa0
--- /dev/null
+++ b/src/native/dbuserrorhandler.cpp
@@ -0,0 +1,180 @@
+/*
+ *  Copyright (C) 2015 Savoir-faire Linux Inc.
+ *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ */
+#include "dbuserrorhandler.h"
+
+#include <glib/gi18n.h>
+#include <callmodel.h>
+#include <globalinstances.h>
+#include "../ring_client.h"
+
+namespace Interfaces {
+
+static GtkWidget*
+dring_crash_dialog()
+{
+    GtkWidget *dialog = gtk_dialog_new();
+    gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
+    gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
+    gtk_window_set_decorated(GTK_WINDOW(dialog), FALSE);
+    gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+    gtk_window_set_title(GTK_WINDOW(dialog), C_("Name of error window (dialog)","Ring Error"));
+
+    /* get the main window */
+    if (auto app = g_application_get_default()) {
+        auto win = ring_client_get_main_window(RING_CLIENT(app));
+        if (win) {
+            gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(win));
+            gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
+        } else {
+            gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
+        }
+    } else {
+        g_warning("no default GApplication exists");
+    }
+
+    GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+    gtk_box_set_spacing(GTK_BOX(content_area), 10);
+    gtk_widget_set_size_request(content_area, 250, -1);
+    gtk_widget_set_margin_top(content_area, 25);
+
+    auto message = gtk_label_new(
+        _("Trying to reconnect to the Ring daemon (dring)...")
+    );
+
+    gtk_box_pack_start(GTK_BOX(content_area), message, FALSE, TRUE, 0);
+
+    GtkWidget *spinner = gtk_spinner_new();
+    gtk_spinner_start(GTK_SPINNER(spinner));
+
+    gtk_box_pack_start(GTK_BOX(content_area), spinner, FALSE, TRUE, 0);
+
+    gtk_widget_show_all(content_area);
+
+    return dialog;
+}
+
+static GtkWidget*
+ring_quitting_dialog()
+{
+    /* get the main window */
+    GtkWindow *win = NULL;
+    if (auto app = g_application_get_default()) {
+        win = ring_client_get_main_window(RING_CLIENT(app));
+    } else {
+        g_warning("no default GApplication exists");
+    }
+
+    GtkWidget *dialog = gtk_message_dialog_new(
+        win,
+        (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
+        GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+        _("Could not re-connect to the Ring daemon (dring).\nRing will now quit.")
+    );
+
+    if (win) {
+        gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
+    } else {
+        gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
+    }
+    gtk_window_set_title(GTK_WINDOW(dialog), C_("Name of error window (dialog)","Ring Error"));
+    gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+
+    return dialog;
+}
+
+static gboolean
+check_connection_cb(GtkWidget *warning_dialog)
+{
+    g_return_val_if_fail(GTK_IS_DIALOG(warning_dialog), G_SOURCE_REMOVE);
+
+    gtk_widget_destroy(warning_dialog);
+
+    if ((!CallModel::instance()->isConnected()) || (!CallModel::instance()->isValid())) {
+        g_warning("could not reconnect to the daemon");
+
+        auto quit_dialog = ring_quitting_dialog();
+
+        /* wait for the user to exit the dialog */
+        gtk_dialog_run(GTK_DIALOG(quit_dialog));
+        gtk_widget_destroy(quit_dialog);
+
+        /* quit */
+        if (auto app = g_application_get_default()) {
+            auto quit_action = G_ACTION(g_action_map_lookup_action(G_ACTION_MAP(app), "quit"));
+            g_action_activate(quit_action, NULL);
+        } else {
+            g_warning("no default GApplication exists");
+        }
+    } else {
+        /* we're done handling the error */
+        static_cast<DBusErrorHandler&>(GlobalInstances::dBusErrorHandler()).finishedHandlingError();
+    }
+
+    return G_SOURCE_REMOVE;
+}
+
+static gboolean
+error_cb(G_GNUC_UNUSED gpointer user_data)
+{
+    g_warning("dring has possibly crashed, or has been killed... will wait 2.5 seconds and try to reconnect");
+
+    auto warning_dialog = dring_crash_dialog();
+    gtk_window_present(GTK_WINDOW(warning_dialog));
+
+    /* allow 2.5 seconds for the daemon to restart and then see if we're re-connected */
+    g_timeout_add(2500, (GSourceFunc)check_connection_cb, warning_dialog);
+
+    return G_SOURCE_REMOVE;
+}
+
+void
+DBusErrorHandler::connectionError(const QString& error)
+{
+    g_warning("%s", error.toUtf8().constData());
+
+    if (!handlingError) {
+        handlingError = true;
+        /* the error may come from a different thread other than the main loop,
+         * we use an idle function to run events on the main loop */
+        g_idle_add((GSourceFunc)error_cb, NULL);
+    }
+}
+
+void
+DBusErrorHandler::invalidInterfaceError(const QString& error)
+{
+    g_warning("%s", error.toUtf8().constData());
+
+    if (!handlingError) {
+        handlingError = true;
+        /* the error may come from a different thread other than the main loop,
+         * we use an idle function to run events on the main loop */
+        g_idle_add((GSourceFunc)error_cb, NULL);
+    }
+}
+
+void
+DBusErrorHandler::finishedHandlingError()
+{
+    handlingError = false;
+}
+
+} // namespace Interfaces