/* * 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; }