Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • release/202106
  • release/202104
  • release/202101
  • release/202012
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • release/201811
  • release/201808
  • 1.0.0
  • 0.3.0
  • 0.2.1
  • 0.2.0
  • 0.1.0
25 results

client.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    client.cpp 25.32 KiB
    /*
     *  Copyright (C) 2015-2022 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.
     */
    
    #include "client.h"
    
    // system
    #include <memory>
    #include <regex>
    
    // GTK+ related
    #include <gtk/gtk.h>
    #include <glib/gi18n.h>
    #include <clutter-gtk/clutter-gtk.h>
    
    // Qt
    #include <QtCore/QTranslator>
    #include <QtCore/QCoreApplication>
    #include <QtCore/QString>
    #include <QtCore/QByteArray>
    #include <QtCore/QItemSelectionModel>
    #include <QtCore/QStandardPaths>
    
    // LRC
    #include <smartinfohub.h>
    #include <globalinstances.h>
    
    // Jami Client
    #include "client_options.h"
    #include "mainwindow.h"
    #include "dialogs.h"
    #include "native/dbuserrorhandler.h"
    #include "notifier.h"
    #include "config.h"
    #include "utils/drawing.h"
    #include "utils/files.h"
    
    #if HAVE_AYATANAAPPINDICATOR
    #include <libayatana-appindicator/app-indicator.h>
    #elif HAVE_APPINDICATOR
    #include <libappindicator/app-indicator.h>
    #endif
    
    struct _ClientClass
    {
        GtkApplicationClass parent_class;
    };
    
    struct _Client
    {
        GtkApplication parent;
    };
    
    typedef struct _ClientPrivate ClientPrivate;
    
    struct _ClientPrivate {
        /* args */
        int    argc;
        char **argv;
    
        GSettings *settings;
    
        /* main window */
        GtkWidget        *win;
        /* for libRingClient */
        QCoreApplication *qtapp;
        /* UAM */
        QMetaObject::Connection uam_updated;
    
        std::unique_ptr<QTranslator> translator_lang;
        std::unique_ptr<QTranslator> translator_full;
    
        gboolean restore_window_state;
    
        gpointer systray_icon;
        GtkWidget *icon_menu;
    };
    
    /* this union is used to pass ints as pointers and vice versa for GAction parameters*/
    typedef union _int_ptr_t
    {
        int value;
        gint64 value64;
        gpointer ptr;
    } int_ptr_t;
    
    G_DEFINE_TYPE_WITH_PRIVATE(Client, client, GTK_TYPE_APPLICATION);
    
    #define CLIENT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLIENT_TYPE, ClientPrivate))
    
    
    static void
    exception_dialog(const char* msg)
    {
        g_critical("%s", msg);
        GtkWidget *dialog = gtk_message_dialog_new(NULL,
                                (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
                                GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
                                _("Unable to initialize.\nMake sure the Jami daemon (jamid) is running.\nError: %s"),
                                msg);
    
        gtk_window_set_title(GTK_WINDOW(dialog), _("Jami Error"));
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
    }
    
    static void
    accelerators(Client *client)
    {
    #if GTK_CHECK_VERSION(3,12,0)
        const gchar *quit_accels[2] = {"<Ctrl>Q", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.quit", quit_accels);
        const gchar *fullscreen_accels[2] = {"F11", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.toggle_fullscreen", fullscreen_accels);
    
        const gchar *accounts_accels[2] = {"<Ctrl>J", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.display_account_list", accounts_accels);
    
        const gchar *search_accels[2] = {"<Ctrl>F", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.search", search_accels);
    
        const gchar *conversations_list_accels[2] = {"<Ctrl>L", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.conversations_list", conversations_list_accels);
        const gchar *requests_list_accels[2] = {"<Ctrl>R", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.requests_list", requests_list_accels);
    
        const gchar *audio_call_accels[2] = {"<Ctrl><Shift>C", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.audio_call", audio_call_accels);
        const gchar *clear_history_accels[2] = {"<Ctrl><Shift>L", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.clear_history", clear_history_accels);
        const gchar *remove_conversation_accels[2] = {"<Ctrl><Shift>Delete", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.remove_conversation", remove_conversation_accels);
        const gchar *block_contact_accels[2] = {"<Ctrl><Shift>B", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.block_contact", block_contact_accels);
        const gchar *unblock_contact_accels[2] = {"<Ctrl><Shift>U", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.unblock_contact", unblock_contact_accels);
        const gchar *copy_contact_accels[2] = {"<Ctrl><Shift>J", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.copy_contact", copy_contact_accels);
        const gchar *add_contact_accels[2] = {"<Ctrl><Shift>A", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.add_contact", add_contact_accels);
    
        const gchar *accept_call_accels[2] = {"<Ctrl>Y", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.accept_call", accept_call_accels);
        const gchar *decline_call_accels[2] = {"<Ctrl>D", NULL};
        gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.decline_call", decline_call_accels);
    
    #else
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>Q", "app.quit", NULL);
        gtk_application_add_accelerator(GTK_APPLICATION(client), "F11", "app.toggle_fullscreen", NULL);
    
    
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>J", "app.display_account_list", NULL);
    
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>F", "app.search", NULL);
    
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>L", "app.conversations_list", NULL);
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>R", "app.requests_list", NULL);
    
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>C", "app.audio_call", NULL);
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>L", "app.clear_history", NULL);
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>Delete", "app.remove_conversation", NULL);
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>B", "app.block_contact", NULL);
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>U", "app.unblock_contact", NULL);
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>J", "app.copy_contact", NULL);
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>A", "app.add_contact", NULL);
    
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>Y", "app.accept_call", NULL);
        gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>D", "app.decline_call", NULL);
    #endif
    }
    
    static void
    action_quit(G_GNUC_UNUSED GSimpleAction *simple,
                G_GNUC_UNUSED GVariant      *parameter,
                gpointer user_data)
    {
        g_return_if_fail(G_IS_APPLICATION(user_data));
    
    #if GLIB_CHECK_VERSION(2,32,0)
        g_application_quit(G_APPLICATION(user_data));
    #else
        ClientPrivate *priv = CLIENT_GET_PRIVATE(user_data);
        gtk_widget_destroy(priv->win);
    #endif
    }
    
    static void
    action_about(G_GNUC_UNUSED GSimpleAction *simple,
                 G_GNUC_UNUSED GVariant      *parameter,
                 gpointer user_data)
    {
        g_return_if_fail(G_IS_APPLICATION(user_data));
        ClientPrivate *priv = CLIENT_GET_PRIVATE(user_data);
    
        about_dialog(priv->win);
    }
    
    static void
    exec_action(GSimpleAction *simple,
                G_GNUC_UNUSED GVariant      *parameter,
                gpointer user_data)
    {
        g_return_if_fail(G_IS_APPLICATION(user_data));
        ClientPrivate *priv = CLIENT_GET_PRIVATE(user_data);
    
        GValue value = G_VALUE_INIT;
        g_value_init(&value, G_TYPE_STRING);
        g_object_get_property(G_OBJECT(simple), "name", &value);
        if (!g_value_get_string(&value)) return;
        std::string name = g_value_get_string(&value);
    
        if (name == "display_account_list")
            main_window_display_account_list(MAIN_WINDOW(priv->win));
        else if (name == "search")
            main_window_search(MAIN_WINDOW(priv->win));
        else if (name == "conversations_list")
            main_window_conversations_list(MAIN_WINDOW(priv->win));
        else if (name == "requests_list")
            main_window_requests_list(MAIN_WINDOW(priv->win));
        else if (name == "audio_call")
            main_window_audio_call(MAIN_WINDOW(priv->win));
        else if (name == "clear_history")
            main_window_clear_history(MAIN_WINDOW(priv->win));
        else if (name == "remove_conversation")
            main_window_remove_conversation(MAIN_WINDOW(priv->win));
        else if (name == "block_contact")
            main_window_block_contact(MAIN_WINDOW(priv->win));
        else if (name == "unblock_contact")
            main_window_unblock_contact(MAIN_WINDOW(priv->win));
        else if (name == "copy_contact")
            main_window_copy_contact(MAIN_WINDOW(priv->win));
        else if (name == "add_contact")
            main_window_add_contact(MAIN_WINDOW(priv->win));
        else if (name == "accept_call")
            main_window_accept_call(MAIN_WINDOW(priv->win));
        else if (name == "decline_call")
            main_window_decline_call(MAIN_WINDOW(priv->win));
        else if (name == "toggle_fullscreen")
            main_window_toggle_fullscreen(MAIN_WINDOW(priv->win));
        else
            g_warning("Missing implementation for this action: %s", name.c_str());
    }
    
    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 void
    action_show_shortcuts(G_GNUC_UNUSED GSimpleAction *action, G_GNUC_UNUSED GVariant *parameter, gpointer user_data)
    {
        g_return_if_fail(G_IS_APPLICATION(user_data));
        ClientPrivate *priv = CLIENT_GET_PRIVATE(user_data);
    
        GtkBuilder *builder = gtk_builder_new_from_resource("/net/jami/JamiGnome/help-overlay.ui");
        GtkWidget *overlay = GTK_WIDGET(gtk_builder_get_object (builder, "help_overlay"));
    
        gtk_window_set_transient_for(GTK_WINDOW(overlay), GTK_WINDOW(priv->win));
        gtk_widget_show(overlay);
    
        g_object_unref(builder);
    }
    
    static const GActionEntry 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}},
        {"display-smartinfo", NULL, NULL, "false", toggle_smartinfo, {0}},
        {"display_account_list", exec_action, NULL, NULL, NULL, {0}},
        {"search", exec_action, NULL, NULL, NULL, {0}},
        {"conversations_list", exec_action, NULL, NULL, NULL, {0}},
        {"requests_list", exec_action, NULL, NULL, NULL, {0}},
        {"audio_call", exec_action, NULL, NULL, NULL, {0}},
        {"clear_history", exec_action, NULL, NULL, NULL, {0}},
        {"remove_conversation", exec_action, NULL, NULL, NULL, {0}},
        {"block_contact", exec_action, NULL, NULL, NULL, {0}},
        {"unblock_contact", exec_action, NULL, NULL, NULL, {0}},
        {"copy_contact", exec_action, NULL, NULL, NULL, {0}},
        {"add_contact", exec_action, NULL, NULL, NULL, {0}},
        {"accept_call", exec_action, NULL, NULL, NULL, {0}},
        {"decline_call", exec_action, NULL, NULL, NULL, {0}},
        {"show_shortcuts", action_show_shortcuts, NULL, NULL, NULL, {0}},
        {"toggle_fullscreen", exec_action, NULL, NULL, NULL, {0}},
    };
    
    static void
    autostart_toggled(GSettings *settings, G_GNUC_UNUSED gchar *key, G_GNUC_UNUSED gpointer user_data)
    {
        autostart_symlink(g_settings_get_boolean(settings, "start-on-login"));
    }
    
    static void
    show_main_window_toggled(Client *client)
    {
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
    
        if (g_settings_get_boolean(priv->settings, "show-main-window")) {
            gtk_window_present(GTK_WINDOW(priv->win));
        } else {
            gtk_widget_hide(priv->win);
        }
    }
    
    static void
    window_show(Client *client)
    {
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
        g_settings_set_boolean(priv->settings, "show-main-window", TRUE);
    }
    
    static void
    window_hide(Client *client)
    {
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
        g_settings_set_boolean(priv->settings, "show-main-window", FALSE);
    }
    
    static gboolean
    on_close_window(GtkWidget *window, G_GNUC_UNUSED GdkEvent *event, Client *client)
    {
        g_return_val_if_fail(GTK_IS_WINDOW(window) && IS_CLIENT(client), FALSE);
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
    
        if (g_settings_get_boolean(priv->settings, "show-status-icon")) {
            /* we want to simply hide the window and keep the client running */
            auto closeWindow = main_window_can_close(MAIN_WINDOW(window));
            if (closeWindow) {
                window_hide(client);
                main_window_reset(MAIN_WINDOW(window));
            }
            return TRUE; /* do not propagate event */
        } else {
            /* we want to quit the application, so just propagate the event */
            return FALSE;
        }
    }
    
    void
    on_main_window_urgency_changed(GtkWidget *, gboolean urgent, Client *client)
    {
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
        g_return_if_fail(priv);
    #if HAVE_APPINDICATOR
        app_indicator_set_status((AppIndicator *) priv->systray_icon,
                                 urgent
                                 ? APP_INDICATOR_STATUS_ATTENTION
                                 : APP_INDICATOR_STATUS_ACTIVE);
    #else
        GError *error = NULL;
        GdkPixbuf* icon =
            gdk_pixbuf_new_from_resource(urgent
                                         ? "/net/jami/JamiGnome/jami-symbol-blue-new"
                                         : "/net/jami/JamiGnome/jami-symbol-blue",
                                         &error);
        if (icon) {
            // GtkStatusIcon is deprecated since 3.14, but we fallback on it
            G_GNUC_BEGIN_IGNORE_DEPRECATIONS
            gtk_status_icon_set_from_pixbuf((GtkStatusIcon *) priv->systray_icon, icon);
            gtk_status_icon_set_title((GtkStatusIcon *) priv->systray_icon,
                                      urgent
                                      ? _("Jami needs your attention")
                                      : "jami-gnome");
            G_GNUC_END_IGNORE_DEPRECATIONS
        } else {
            g_debug("Could not load icon: %s", error->message);
            g_clear_error(&error);
        }
    #endif
    }
    
    #if !HAVE_APPINDICATOR
    
    static void
    popup_menu(GtkStatusIcon *self,
               guint          button,
               guint          when,
               Client    *client)
    {
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
        G_GNUC_BEGIN_IGNORE_DEPRECATIONS // GtkStatusIcon is deprecated since 3.14, but we fallback on it
        gtk_menu_popup(GTK_MENU(priv->icon_menu), NULL, NULL, gtk_status_icon_position_menu, self, button, when);
        G_GNUC_END_IGNORE_DEPRECATIONS
    }
    
    #endif
    
    static void
    init_systray(Client *client)
    {
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
    
        // init menu
        if (!priv->icon_menu) {
    
            /* for some reason AppIndicator doesn't like the menu being built from a GMenuModel and/or
             * the GMenuModel being built from an xml resource. So we build the menu in code.
             */
            priv->icon_menu = gtk_menu_new();
            g_object_ref_sink(priv->icon_menu);
    
            auto item = gtk_check_menu_item_new_with_label(C_("In the status icon menu, toggle action to show or hide the Jami main window", "Show Jami"));
            gtk_actionable_set_action_name(GTK_ACTIONABLE(item), "app.show-main-window");
            gtk_menu_shell_append(GTK_MENU_SHELL(priv->icon_menu), item);
    
            item = gtk_menu_item_new_with_label(_("Quit"));
            gtk_actionable_set_action_name(GTK_ACTIONABLE(item), "app.quit");
            gtk_menu_shell_append(GTK_MENU_SHELL(priv->icon_menu), item);
    
            gtk_widget_insert_action_group(priv->icon_menu, "app", G_ACTION_GROUP(client));
            gtk_widget_show_all(priv->icon_menu);
        }
    
    #if HAVE_APPINDICATOR
        auto indicator = app_indicator_new("jami-gnome", "jami-gnome", APP_INDICATOR_CATEGORY_COMMUNICATIONS);
        app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE);
        app_indicator_set_title(indicator, JAMI_CLIENT_NAME);
        app_indicator_set_attention_icon_full(
            indicator, "jami-gnome-new", _("Jami needs your attention"));
        /* app indicator requires a menu */
        app_indicator_set_menu(indicator, GTK_MENU(priv->icon_menu));
        priv->systray_icon = indicator;
    #else
        GError *error = NULL;
        GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/net/jami/JamiGnome/jami-symbol-blue", &error);
        if (icon == nullptr) {
            g_debug("Could not load icon: %s", error->message);
            g_clear_error(&error);
        } else {
            G_GNUC_BEGIN_IGNORE_DEPRECATIONS // GtkStatusIcon is deprecated since 3.14, but we fallback on it
            auto status_icon = gtk_status_icon_new_from_pixbuf(icon);
            gtk_status_icon_set_title(status_icon, "jami-gnome");
            G_GNUC_END_IGNORE_DEPRECATIONS
            g_signal_connect_swapped(status_icon, "activate", G_CALLBACK(window_show), client);
            g_signal_connect(status_icon, "popup-menu", G_CALLBACK(popup_menu), client);
            priv->systray_icon = status_icon;
        }
    #endif
        g_signal_connect(
            priv->win, "main-window-urgency-changed",
            G_CALLBACK(on_main_window_urgency_changed),
            client);
        on_main_window_urgency_changed(
            priv->win,
            main_window_get_urgency(MAIN_WINDOW(priv->win)),
            client);
    }
    
    static void
    systray_toggled(GSettings *settings, const gchar *key, Client *client)
    {
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
    
        if (g_settings_get_boolean(settings, key)) {
            if (!priv->systray_icon)
                init_systray(client);
        } else {
            if (priv->systray_icon)
                g_clear_object(&priv->systray_icon);
        }
    }
    
    static void
    client_activate(GApplication *app)
    {
        Client *client = CLIENT(app);
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
    
        if (priv->win == NULL) {
            // activate being called for the first time
            priv->win = main_window_new(GTK_APPLICATION(app));
    
            /* make sure win is set to NULL when the window is destroyed */
            g_object_add_weak_pointer(G_OBJECT(priv->win), (gpointer *)&priv->win);
    
            /* check if the window should be destoryed or not on close */
            g_signal_connect(priv->win, "delete-event", G_CALLBACK(on_close_window), client);
    
            /* if we didn't launch with the '-r' (--restore-last-window-state) option then force the
             * show-main-window to true */
            if (!priv->restore_window_state)
                window_show(client);
            show_main_window_toggled(client);
            g_signal_connect_swapped(priv->settings, "changed::show-main-window", G_CALLBACK(show_main_window_toggled), client);
    
            // track sys icon state
            g_signal_connect(priv->settings, "changed::show-status-icon", G_CALLBACK(systray_toggled), client);
            systray_toggled(priv->settings, "show-status-icon", client);
        } else {
            // activate not being called for the first time, force showing of main window
            window_show(client);
        }
    }
    
    // TODO add some args!
    static void
    client_open(GApplication *app, GFile ** /*file*/, gint /*arg3*/, const gchar* /*arg4*/)
    {
        client_activate(app);
    
        // TODO migrate place call at begining
    }
    
    static void
    client_startup(GApplication *app)
    {
        Client *client = CLIENT(app);
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
    
        g_message("Jami GNOME client version: %s", VERSION);
    
        /* make sure that the system corresponds to the autostart setting */
        autostart_symlink(g_settings_get_boolean(priv->settings, "start-on-login"));
        g_signal_connect(priv->settings, "changed::start-on-login", G_CALLBACK(autostart_toggled), NULL);
    
        /* init clutter */
        int clutter_error;
        if ((clutter_error = gtk_clutter_init(&priv->argc, &priv->argv)) != CLUTTER_INIT_SUCCESS) {
            g_error("Could not init clutter : %d\n", clutter_error);
            exit(1); /* the g_error above should normally cause the application to exit */
        }
    
        /* init libRingClient and make sure its connected to the dbus */
        try {
            priv->qtapp = new QCoreApplication(priv->argc, priv->argv);
            /* the call model will try to connect to jamid via dbus */
        } catch(const char * msg) {
            exception_dialog(msg);
            exit(1);
        } catch(QString& msg) {
            exception_dialog(msg.toLocal8Bit().constData());
            exit(1);
        }
    
        /* load translations from LRC */
        const auto locale_name = QLocale::system().name();
        const auto locale_lang = locale_name.split('_')[0];
    
        if (locale_name != locale_lang) {
            /* Install language first to have lowest priority */
            priv->translator_lang.reset(new QTranslator);
            if (priv->translator_lang->load(JAMI_CLIENT_INSTALL "/share/libringclient/translations/lrc_" + locale_lang)) {
                g_debug("installed translations for %s", locale_lang.toUtf8().constData());
                priv->qtapp->installTranslator(priv->translator_lang.get());
            }
        }
    
        priv->translator_full.reset(new QTranslator);
        if (priv->translator_full->load(JAMI_CLIENT_INSTALL "/share/libringclient/translations/lrc_" + locale_name)) {
            g_debug("installed translations for %s", locale_name.toUtf8().constData());
        }
    
        if (not priv->translator_lang and not priv->translator_full) {
            g_debug("could not load LRC translations for %s, %s",
                    QLocale::languageToString(QLocale::system().language()).toUtf8().constData(),
                    QLocale::countryToString(QLocale::system().country()).toUtf8().constData()
            );
        }
    
        /* init delegates */
        GlobalInstances::setDBusErrorHandler(std::unique_ptr<Interfaces::DBusErrorHandler>(new Interfaces::DBusErrorHandler()));
    
        /* Override theme since we don't have appropriate icons for a dark them (yet) */
        GtkSettings *gtk_settings = gtk_settings_get_default();
        g_object_set(G_OBJECT(gtk_settings), "gtk-application-prefer-dark-theme",
                     FALSE, NULL);
        /* enable button icons */
        g_object_set(G_OBJECT(gtk_settings), "gtk-button-images",
                     TRUE, NULL);
    
        /* enable sound (for notification) */
        g_object_set(G_OBJECT(gtk_settings), "gtk-enable-event-sounds",
                     TRUE, NULL);
    
        /* add GActions */
        g_action_map_add_action_entries(
            G_ACTION_MAP(app), actions, G_N_ELEMENTS(actions), app);
    
        /* GActions for settings */
        auto action_window_visible = g_settings_create_action(priv->settings, "show-main-window");
        g_action_map_add_action(G_ACTION_MAP(app), action_window_visible);
    
        /* add accelerators */
        accelerators(CLIENT(app));
    
        G_APPLICATION_CLASS(client_parent_class)->startup(app);
    }
    
    static void
    client_shutdown(GApplication *app)
    {
        Client *self = CLIENT(app);
        ClientPrivate *priv = CLIENT_GET_PRIVATE(self);
    
        gtk_widget_destroy(priv->win);
    
        QObject::disconnect(priv->uam_updated);
    
        /* free the QCoreApplication, which will destroy all libRingClient models
         * and thus send the Unregister signal over dbus to jamid */
        if (priv->qtapp) {
            delete priv->qtapp;
            priv->qtapp = nullptr;
        }
    
        /* free the copied cmd line args */
        g_strfreev(priv->argv);
    
        g_clear_object(&priv->settings);
    
        /* Chain up to the parent class */
        G_APPLICATION_CLASS(client_parent_class)->shutdown(app);
    }
    
    static void
    client_init(Client *self)
    {
        ClientPrivate *priv = CLIENT_GET_PRIVATE(self);
    
        priv->win = NULL;
        priv->qtapp = NULL;
        priv->settings = g_settings_new_full(get_settings_schema(), NULL, NULL);
    
        /* add custom cmd line options */
        client_add_options(G_APPLICATION(self));
    }
    
    static void
    client_class_init(ClientClass *klass)
    {
        G_APPLICATION_CLASS(klass)->startup = client_startup;
        G_APPLICATION_CLASS(klass)->activate = client_activate;
        G_APPLICATION_CLASS(klass)->open = client_open;
        G_APPLICATION_CLASS(klass)->shutdown = client_shutdown;
    }
    
    Client*
    client_new(int argc, char *argv[])
    {
        Client *client = (Client *)g_object_new(client_get_type(),
                                                        "application-id", JAMI_CLIENT_APP_ID,
                                                        "flags", G_APPLICATION_HANDLES_OPEN ,
                                                        NULL);
    
        /* copy the cmd line args before they get processed by the GApplication*/
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
        priv->argc = argc;
        priv->argv = g_strdupv((gchar **)argv);
    
        return client;
    }
    
    GtkWindow*
    client_get_main_window(Client *client)
    {
        g_return_val_if_fail(IS_CLIENT(client), NULL);
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
    
        return (GtkWindow *)priv->win;
    }
    
    void
    client_set_restore_main_window_state(Client *client, gboolean restore)
    {
        g_return_if_fail(IS_CLIENT(client));
        ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
    
        priv->restore_window_state = restore;
    }