ring_client.cpp 24.5 KB
Newer Older
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
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 20 21
 *  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 "ring_client.h"

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>
Stepan Salenikovich's avatar
Stepan Salenikovich committed
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
// Ring client
44
#include "ring_client_options.h"
45
#include "ringmainwindow.h"
Stepan Salenikovich's avatar
Stepan Salenikovich committed
46
#include "dialogs.h"
47
#include "native/pixbufmanipulator.h"
48
#include "native/dbuserrorhandler.h"
49
#include "ringnotify.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
Stepan Salenikovich's avatar
Stepan Salenikovich committed
57 58 59
#include <libappindicator/app-indicator.h>
#endif

60 61
struct _RingClientClass
{
62
    GtkApplicationClass parent_class;
63 64 65 66 67 68 69 70
};

struct _RingClient
{
    GtkApplication parent;
};

typedef struct _RingClientPrivate RingClientPrivate;
71 72

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

    GSettings *settings;

79 80 81 82
    /* main window */
    GtkWidget        *win;
    /* for libRingclient */
    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;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
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(RingClient, ring_client, GTK_TYPE_APPLICATION);
104 105 106

#define RING_CLIENT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_CLIENT_TYPE, RingClientPrivate))

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,
Philippe Gorley's avatar
Philippe Gorley committed
115
                            _("Unable to initialize.\nMake sure the Jami daemon (dring) is running.\nError: %s"),
116 117
                            msg);

Philippe Gorley's avatar
Philippe Gorley committed
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 124 125
static void
ring_accelerators(RingClient *client)
{
Stepan Salenikovich's avatar
Stepan Salenikovich committed
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 198 199 200 201 202
#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
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(user_data);
    gtk_widget_destroy(priv->win);
#endif
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
203 204
static void
action_about(G_GNUC_UNUSED GSimpleAction *simple,
Stepan Salenikovich's avatar
Stepan Salenikovich committed
205 206
             G_GNUC_UNUSED GVariant      *parameter,
             gpointer user_data)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
207 208 209 210 211 212 213
{
    g_return_if_fail(G_IS_APPLICATION(user_data));
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(user_data);

    ring_about_dialog(priv->win);
}

214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
static void
exec_action(GSimpleAction *simple,
            G_GNUC_UNUSED GVariant      *parameter,
            gpointer user_data)
{
    g_return_if_fail(G_IS_APPLICATION(user_data));
    RingClientPrivate *priv = RING_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")
        ring_main_window_display_account_list(RING_MAIN_WINDOW(priv->win));
    else if (name == "search")
        ring_main_window_search(RING_MAIN_WINDOW(priv->win));
    else if (name == "conversations_list")
        ring_main_window_conversations_list(RING_MAIN_WINDOW(priv->win));
    else if (name == "requests_list")
        ring_main_window_requests_list(RING_MAIN_WINDOW(priv->win));
    else if (name == "audio_call")
        ring_main_window_audio_call(RING_MAIN_WINDOW(priv->win));
    else if (name == "clear_history")
        ring_main_window_clear_history(RING_MAIN_WINDOW(priv->win));
    else if (name == "remove_conversation")
        ring_main_window_remove_conversation(RING_MAIN_WINDOW(priv->win));
    else if (name == "block_contact")
        ring_main_window_block_contact(RING_MAIN_WINDOW(priv->win));
    else if (name == "unblock_contact")
        ring_main_window_unblock_contact(RING_MAIN_WINDOW(priv->win));
    else if (name == "copy_contact")
        ring_main_window_copy_contact(RING_MAIN_WINDOW(priv->win));
    else if (name == "add_contact")
        ring_main_window_add_contact(RING_MAIN_WINDOW(priv->win));
    else if (name == "accept_call")
        ring_main_window_accept_call(RING_MAIN_WINDOW(priv->win));
    else if (name == "decline_call")
        ring_main_window_decline_call(RING_MAIN_WINDOW(priv->win));
254 255
    else if (name == "toggle_fullscreen")
        ring_main_window_toggle_fullscreen(RING_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 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
    g_return_if_fail(G_IS_APPLICATION(user_data));
    RingClientPrivate *priv = RING_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 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}},
    {"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
Stepan Salenikovich's avatar
Stepan Salenikovich committed
320
show_main_window_toggled(RingClient *client)
321 322 323
{
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);

Stepan Salenikovich's avatar
Stepan Salenikovich committed
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

Stepan Salenikovich's avatar
Stepan Salenikovich committed
331 332 333 334 335
static void
ring_window_show(RingClient *client)
{
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
    g_settings_set_boolean(priv->settings, "show-main-window", TRUE);
336 337 338 339 340 341
}

static void
ring_window_hide(RingClient *client)
{
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
342
    g_settings_set_boolean(priv->settings, "show-main-window", FALSE);
343 344
}

345 346 347 348 349 350
static gboolean
on_close_window(GtkWidget *window, G_GNUC_UNUSED GdkEvent *event, RingClient *client)
{
    g_return_val_if_fail(GTK_IS_WINDOW(window) && IS_RING_CLIENT(client), FALSE);
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);

Stepan Salenikovich's avatar
Stepan Salenikovich committed
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 354 355 356 357
        auto closeWindow = ring_main_window_can_close(RING_MAIN_WINDOW(window));
        if (closeWindow) {
            ring_window_hide(client);
            ring_main_window_reset(RING_MAIN_WINDOW(window));
        }
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

Stepan Salenikovich's avatar
Stepan Salenikovich committed
367 368 369 370 371 372 373 374 375 376 377 378
static void
popup_menu(GtkStatusIcon *self,
           guint          button,
           guint          when,
           RingClient    *client)
{
    RingClientPrivate *priv = RING_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
}

379 380
#endif

Stepan Salenikovich's avatar
Stepan Salenikovich committed
381 382 383 384 385 386 387 388
static void
init_systray(RingClient *client)
{
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);

    // 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();
Stepan Salenikovich's avatar
Stepan Salenikovich committed
393
        g_object_ref_sink(priv->icon_menu);
394

Philippe Gorley's avatar
Philippe Gorley committed
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);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
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 425 426 427
    /* 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
        g_signal_connect_swapped(status_icon, "activate", G_CALLBACK(ring_window_show), client);
        g_signal_connect(status_icon, "popup-menu", G_CALLBACK(popup_menu), client);
        priv->systray_icon = status_icon;
428 429
    }
#endif
Stepan Salenikovich's avatar
Stepan Salenikovich committed
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
}

static void
systray_toggled(GSettings *settings, const gchar *key, RingClient *client)
{
    RingClientPrivate *priv = RING_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);
    }
}

446 447 448 449 450 451 452
static void
ring_client_activate(GApplication *app)
{
    RingClient *client = RING_CLIENT(app);
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);

    if (priv->win == NULL) {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
453
        // activate being called for the first time
454 455 456 457 458 459 460
        priv->win = ring_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);
461

Stepan Salenikovich's avatar
Stepan Salenikovich committed
462 463 464 465
        /* 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)
            ring_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

Stepan Salenikovich's avatar
Stepan Salenikovich committed
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
        ring_window_show(client);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
475
    }
476 477
}

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

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

487 488
static void
ring_client_startup(GApplication *app)
489 490 491 492
{
    RingClient *client = RING_CLIENT(app);
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);

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

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 563
        G_ACTION_MAP(app), ring_actions, G_N_ELEMENTS(ring_actions), app);

Stepan Salenikovich's avatar
Stepan Salenikovich committed
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 569
    /* add accelerators */
    ring_accelerators(RING_CLIENT(app));
Stepan Salenikovich's avatar
Stepan Salenikovich committed
570

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

574 575 576 577 578 579
static void
ring_client_shutdown(GApplication *app)
{
    RingClient *self = RING_CLIENT(app);
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(self);

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 */
Nicolas Jager's avatar
Nicolas Jager committed
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 597 598 599 600 601 602 603 604 605 606
    /* Chain up to the parent class */
    G_APPLICATION_CLASS(ring_client_parent_class)->shutdown(app);
}

static void
ring_client_init(RingClient *self)
{
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(self);

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

    /* add custom cmd line options */
    ring_client_add_options(G_APPLICATION(self));
611 612 613 614 615
}

static void
ring_client_class_init(RingClientClass *klass)
{
616 617
    G_APPLICATION_CLASS(klass)->startup = ring_client_startup;
    G_APPLICATION_CLASS(klass)->activate = ring_client_activate;
618
    G_APPLICATION_CLASS(klass)->open = ring_client_open;
619 620 621
    G_APPLICATION_CLASS(klass)->shutdown = ring_client_shutdown;
}

622
RingClient*
623
ring_client_new(int argc, char *argv[])
624
{
625
    RingClient *client = (RingClient *)g_object_new(ring_client_get_type(),
626
                                                    "application-id", JAMI_CLIENT_APP_ID,
627
                                                    "flags", G_APPLICATION_HANDLES_OPEN ,
628 629 630 631 632 633 634 635
                                                    NULL);

    /* copy the cmd line args before they get processed by the GApplication*/
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
    priv->argc = argc;
    priv->argv = g_strdupv((gchar **)argv);

    return client;
636
}
637

638
GtkWindow*
639
ring_client_get_main_window(RingClient *client)
640 641 642 643 644 645
{
    g_return_val_if_fail(IS_RING_CLIENT(client), NULL);
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);

    return (GtkWindow *)priv->win;
}
646 647 648 649 650 651 652 653 654

void
ring_client_set_restore_main_window_state(RingClient *client, gboolean restore)
{
    g_return_if_fail(IS_RING_CLIENT(client));
    RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);

    priv->restore_window_state = restore;
}