client.cpp 23.8 KB
Newer Older
1
/*
2
 *  Copyright (C) 2015-2019 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *  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.
 */

20
#include "client.h"
21

22 23
// system
#include <memory>
24
#include <regex>
25 26

// GTK+ related
27 28
#include <gtk/gtk.h>
#include <glib/gi18n.h>
29 30 31
#include <clutter-gtk/clutter-gtk.h>

// Qt
32
#include <QtCore/QTranslator>
33 34 35 36
#include <QtCore/QCoreApplication>
#include <QtCore/QString>
#include <QtCore/QByteArray>
#include <QtCore/QItemSelectionModel>
37 38 39
#include <QtCore/QStandardPaths>

// LRC
Olivier Gregoire's avatar
Olivier Gregoire committed
40
#include <smartinfohub.h>
41
#include <globalinstances.h>
42

43 44 45
// Jami Client
#include "client_options.h"
#include "mainwindow.h"
46
#include "dialogs.h"
47
#include "native/pixbufmanipulator.h"
48
#include "native/dbuserrorhandler.h"
49
#include "notifier.h"
50
#include "config.h"
51
#include "utils/files.h"
52
#include "revision.h"
53

aviau's avatar
aviau committed
54 55 56
#if HAVE_AYATANAAPPINDICATOR
#include <libayatana-appindicator/app-indicator.h>
#elif HAVE_APPINDICATOR
57 58 59
#include <libappindicator/app-indicator.h>
#endif

60
struct _ClientClass
61
{
62
    GtkApplicationClass parent_class;
63 64
};

65
struct _Client
66 67 68 69
{
    GtkApplication parent;
};

70
typedef struct _ClientPrivate ClientPrivate;
71

72
struct _ClientPrivate {
73 74 75
    /* args */
    int    argc;
    char **argv;
76 77 78

    GSettings *settings;

79 80
    /* main window */
    GtkWidget        *win;
81
    /* for libRingClient */
82
    QCoreApplication *qtapp;
83 84
    /* UAM */
    QMetaObject::Connection uam_updated;
85

86 87
    std::unique_ptr<QTranslator> translator_lang;
    std::unique_ptr<QTranslator> translator_full;
88

89
    gboolean restore_window_state;
90 91 92

    gpointer systray_icon;
    GtkWidget *icon_menu;
93 94
};

95 96 97 98 99 100 101 102
/* 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;

103
G_DEFINE_TYPE_WITH_PRIVATE(Client, client, GTK_TYPE_APPLICATION);
104

105
#define CLIENT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLIENT_TYPE, ClientPrivate))
106

107

108
static void
109
exception_dialog(const char* msg)
110
{
111
    g_critical("%s", msg);
112 113 114
    GtkWidget *dialog = gtk_message_dialog_new(NULL,
                            (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
                            GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
115
                            _("Unable to initialize.\nMake sure the Jami daemon (dring) is running.\nError: %s"),
116 117
                            msg);

118
    gtk_window_set_title(GTK_WINDOW(dialog), _("Jami Error"));
119 120 121 122
    gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
}

123
static void
124
accelerators(Client *client)
125
{
126
#if GTK_CHECK_VERSION(3,12,0)
127
    const gchar *quit_accels[2] = {"<Ctrl>Q", NULL};
128
    gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.quit", quit_accels);
129 130
    const gchar *fullscreen_accels[2] = {"F11", NULL};
    gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.toggle_fullscreen", fullscreen_accels);
131

132
    const gchar *accounts_accels[2] = {"<Ctrl>J", NULL};
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    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);

163
#else
164
    gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>Q", "app.quit", NULL);
165 166
    gtk_application_add_accelerator(GTK_APPLICATION(client), "F11", "app.toggle_fullscreen", NULL);

167

168
    gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>J", "app.display_account_list", NULL);
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184

    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);
185 186 187 188 189 190 191 192 193 194 195 196 197
#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
198
    ClientPrivate *priv = CLIENT_GET_PRIVATE(user_data);
199 200 201 202
    gtk_widget_destroy(priv->win);
#endif
}

203 204
static void
action_about(G_GNUC_UNUSED GSimpleAction *simple,
205 206
             G_GNUC_UNUSED GVariant      *parameter,
             gpointer user_data)
207 208
{
    g_return_if_fail(G_IS_APPLICATION(user_data));
209
    ClientPrivate *priv = CLIENT_GET_PRIVATE(user_data);
210

211
    about_dialog(priv->win);
212 213
}

214 215 216 217 218 219
static void
exec_action(GSimpleAction *simple,
            G_GNUC_UNUSED GVariant      *parameter,
            gpointer user_data)
{
    g_return_if_fail(G_IS_APPLICATION(user_data));
220
    ClientPrivate *priv = CLIENT_GET_PRIVATE(user_data);
221 222 223 224 225 226 227 228

    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")
229
        main_window_display_account_list(MAIN_WINDOW(priv->win));
230
    else if (name == "search")
231
        main_window_search(MAIN_WINDOW(priv->win));
232
    else if (name == "conversations_list")
233
        main_window_conversations_list(MAIN_WINDOW(priv->win));
234
    else if (name == "requests_list")
235
        main_window_requests_list(MAIN_WINDOW(priv->win));
236
    else if (name == "audio_call")
237
        main_window_audio_call(MAIN_WINDOW(priv->win));
238
    else if (name == "clear_history")
239
        main_window_clear_history(MAIN_WINDOW(priv->win));
240
    else if (name == "remove_conversation")
241
        main_window_remove_conversation(MAIN_WINDOW(priv->win));
242
    else if (name == "block_contact")
243
        main_window_block_contact(MAIN_WINDOW(priv->win));
244
    else if (name == "unblock_contact")
245
        main_window_unblock_contact(MAIN_WINDOW(priv->win));
246
    else if (name == "copy_contact")
247
        main_window_copy_contact(MAIN_WINDOW(priv->win));
248
    else if (name == "add_contact")
249
        main_window_add_contact(MAIN_WINDOW(priv->win));
250
    else if (name == "accept_call")
251
        main_window_accept_call(MAIN_WINDOW(priv->win));
252
    else if (name == "decline_call")
253
        main_window_decline_call(MAIN_WINDOW(priv->win));
254
    else if (name == "toggle_fullscreen")
255
        main_window_toggle_fullscreen(MAIN_WINDOW(priv->win));
256 257 258 259
    else
        g_warning("Missing implementation for this action: %s", name.c_str());
}

Olivier Gregoire's avatar
Olivier Gregoire committed
260 261 262 263 264 265 266 267 268 269 270
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();
    }
}

271 272
static void
action_show_shortcuts(G_GNUC_UNUSED GSimpleAction *action, G_GNUC_UNUSED GVariant *parameter, gpointer user_data)
273
{
274
    g_return_if_fail(G_IS_APPLICATION(user_data));
275
    ClientPrivate *priv = CLIENT_GET_PRIVATE(user_data);
276 277 278 279 280 281 282 283 284 285

    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);
}

286
static const GActionEntry actions[] = {
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
    {"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}},
310
    {"toggle_fullscreen", exec_action, NULL, NULL, NULL, {0}},
311 312
};

313 314 315 316 317 318
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"));
}

319
static void
320
show_main_window_toggled(Client *client)
321
{
322
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
323

324 325 326 327 328 329
    if (g_settings_get_boolean(priv->settings, "show-main-window")) {
        gtk_window_present(GTK_WINDOW(priv->win));
    } else {
        gtk_widget_hide(priv->win);
    }
}
330

331
static void
332
window_show(Client *client)
333
{
334
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
335
    g_settings_set_boolean(priv->settings, "show-main-window", TRUE);
336 337 338
}

static void
339
window_hide(Client *client)
340
{
341
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
342
    g_settings_set_boolean(priv->settings, "show-main-window", FALSE);
343 344
}

345
static gboolean
346
on_close_window(GtkWidget *window, G_GNUC_UNUSED GdkEvent *event, Client *client)
347
{
348 349
    g_return_val_if_fail(GTK_IS_WINDOW(window) && IS_CLIENT(client), FALSE);
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
350

351
    if (g_settings_get_boolean(priv->settings, "show-status-icon")) {
352
        /* we want to simply hide the window and keep the client running */
353
        auto closeWindow = main_window_can_close(MAIN_WINDOW(window));
354
        if (closeWindow) {
355 356
            window_hide(client);
            main_window_reset(MAIN_WINDOW(window));
357
        }
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
358
        return TRUE; /* do not propagate event */
359
    } else {
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
360
        /* we want to quit the application, so just propagate the event */
361 362 363 364
        return FALSE;
    }
}

365 366
#if !HAVE_APPINDICATOR

367 368 369 370
static void
popup_menu(GtkStatusIcon *self,
           guint          button,
           guint          when,
371
           Client    *client)
372
{
373
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
374 375 376 377 378
    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
}

379 380
#endif

381
static void
382
init_systray(Client *client)
383
{
384
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
385 386 387 388

    // init menu
    if (!priv->icon_menu) {

389 390 391 392
        /* 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();
393
        g_object_ref_sink(priv->icon_menu);
394

395
        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"));
396 397 398 399 400 401 402 403 404
        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);
405 406
    }

407
#if HAVE_APPINDICATOR
408 409
    auto indicator = app_indicator_new("jami", "jami", APP_INDICATOR_CATEGORY_COMMUNICATIONS);
    app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE);
410
    app_indicator_set_title(indicator, JAMI_CLIENT_NAME);
411 412 413 414 415 416 417 418 419 420 421 422 423 424
    /* 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");
        G_GNUC_END_IGNORE_DEPRECATIONS
425
        g_signal_connect_swapped(status_icon, "activate", G_CALLBACK(window_show), client);
426 427
        g_signal_connect(status_icon, "popup-menu", G_CALLBACK(popup_menu), client);
        priv->systray_icon = status_icon;
428 429
    }
#endif
430 431 432
}

static void
433
systray_toggled(GSettings *settings, const gchar *key, Client *client)
434
{
435
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
436 437 438 439 440 441 442 443 444 445

    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);
    }
}

446
static void
447
client_activate(GApplication *app)
448
{
449 450
    Client *client = CLIENT(app);
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
451 452

    if (priv->win == NULL) {
453
        // activate being called for the first time
454
        priv->win = main_window_new(GTK_APPLICATION(app));
455 456 457 458 459 460

        /* 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);
461

462 463 464
        /* 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)
465
            window_show(client);
466 467
        show_main_window_toggled(client);
        g_signal_connect_swapped(priv->settings, "changed::show-main-window", G_CALLBACK(show_main_window_toggled), client);
468

469 470 471 472 473
        // 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
474
        window_show(client);
475
    }
476 477
}

478
// TODO add some args!
479
static void
480
client_open(GApplication *app, GFile ** /*file*/, gint /*arg3*/, const gchar* /*arg4*/)
481
{
482
    client_activate(app);
483

484
    // TODO migrate place call at begining
485 486
}

487
static void
488
client_startup(GApplication *app)
489
{
490 491
    Client *client = CLIENT(app);
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
492

493 494
    g_message("Jami GNOME client version: %s", VERSION);
    g_message("git ref: %s", CLIENT_REVISION);
495

496 497 498 499
    /* 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);

500 501
    /* init clutter */
    int clutter_error;
502 503
    if ((clutter_error = gtk_clutter_init(&priv->argc, &priv->argv)) != CLUTTER_INIT_SUCCESS) {
        g_error("Could not init clutter : %d\n", clutter_error);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
504
        exit(1); /* the g_error above should normally cause the application to exit */
505
    }
506 507 508

    /* init libRingClient and make sure its connected to the dbus */
    try {
509
        priv->qtapp = new QCoreApplication(priv->argc, priv->argv);
510
        /* the call model will try to connect to dring via dbus */
511 512 513
    } catch(const char * msg) {
        exception_dialog(msg);
        exit(1);
514
    } catch(QString& msg) {
515 516
        exception_dialog(msg.toLocal8Bit().constData());
        exit(1);
517 518
    }

519
    /* load translations from LRC */
520 521 522 523 524 525
    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);
526
        if (priv->translator_lang->load(JAMI_CLIENT_INSTALL "/share/libringclient/translations/lrc_" + locale_lang)) {
527 528 529 530 531 532
            g_debug("installed translations for %s", locale_lang.toUtf8().constData());
            priv->qtapp->installTranslator(priv->translator_lang.get());
        }
    }

    priv->translator_full.reset(new QTranslator);
533
    if (priv->translator_full->load(JAMI_CLIENT_INSTALL "/share/libringclient/translations/lrc_" + locale_name)) {
534 535 536 537
        g_debug("installed translations for %s", locale_name.toUtf8().constData());
    }

    if (not priv->translator_lang and not priv->translator_full) {
538
        g_debug("could not load LRC translations for %s, %s",
539 540
                QLocale::languageToString(QLocale::system().language()).toUtf8().constData(),
                QLocale::countryToString(QLocale::system().country()).toUtf8().constData()
541 542 543
        );
    }

544
    /* init delegates */
545
    GlobalInstances::setPixmapManipulator(std::unique_ptr<Interfaces::PixbufManipulator>(new Interfaces::PixbufManipulator()));
546
    GlobalInstances::setDBusErrorHandler(std::unique_ptr<Interfaces::DBusErrorHandler>(new Interfaces::DBusErrorHandler()));
547

548 549 550 551 552 553 554 555
    /* 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);

556 557 558 559
    /* enable sound (for notification) */
    g_object_set(G_OBJECT(gtk_settings), "gtk-enable-event-sounds",
                 TRUE, NULL);

560
    /* add GActions */
561
    g_action_map_add_action_entries(
562
        G_ACTION_MAP(app), actions, G_N_ELEMENTS(actions), app);
563

564 565 566 567
    /* 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);

568
    /* add accelerators */
569
    accelerators(CLIENT(app));
570

571
    G_APPLICATION_CLASS(client_parent_class)->startup(app);
572 573
}

574
static void
575
client_shutdown(GApplication *app)
576
{
577 578
    Client *self = CLIENT(app);
    ClientPrivate *priv = CLIENT_GET_PRIVATE(self);
579

580
    gtk_widget_destroy(priv->win);
581

582 583
    QObject::disconnect(priv->uam_updated);

584 585
    /* free the QCoreApplication, which will destroy all libRingClient models
     * and thus send the Unregister signal over dbus to dring */
586 587 588 589
    if (priv->qtapp) {
        delete priv->qtapp;
        priv->qtapp = nullptr;
    }
590

591 592 593
    /* free the copied cmd line args */
    g_strfreev(priv->argv);

594 595
    g_clear_object(&priv->settings);

596
    /* Chain up to the parent class */
597
    G_APPLICATION_CLASS(client_parent_class)->shutdown(app);
598 599 600
}

static void
601
client_init(Client *self)
602
{
603
    ClientPrivate *priv = CLIENT_GET_PRIVATE(self);
604 605 606

    priv->win = NULL;
    priv->qtapp = NULL;
607
    priv->settings = g_settings_new_full(get_settings_schema(), NULL, NULL);
608 609

    /* add custom cmd line options */
610
    client_add_options(G_APPLICATION(self));
611 612 613
}

static void
614
client_class_init(ClientClass *klass)
615
{
616 617 618 619
    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;
620 621
}

622 623
Client*
client_new(int argc, char *argv[])
624
{
625
    Client *client = (Client *)g_object_new(client_get_type(),
626
                                                    "application-id", JAMI_CLIENT_APP_ID,
627
                                                    "flags", G_APPLICATION_HANDLES_OPEN ,
628 629 630
                                                    NULL);

    /* copy the cmd line args before they get processed by the GApplication*/
631
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
632 633 634 635
    priv->argc = argc;
    priv->argv = g_strdupv((gchar **)argv);

    return client;
636
}
637

638
GtkWindow*
639
client_get_main_window(Client *client)
640
{
641 642
    g_return_val_if_fail(IS_CLIENT(client), NULL);
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
643 644 645

    return (GtkWindow *)priv->win;
}
646 647

void
648
client_set_restore_main_window_state(Client *client, gboolean restore)
649
{
650 651
    g_return_if_fail(IS_CLIENT(client));
    ClientPrivate *priv = CLIENT_GET_PRIVATE(client);
652 653 654

    priv->restore_window_state = restore;
}