Fix broken `priv != NULL` checks in Pidgin.
[pidgin-git.git] / pidgin / libpidgin.c
blobe465d308d529ff85efe6cfa240d05ac805b00a6f
1 /*
2 * pidgin
4 * Pidgin is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include <locale.h>
25 #include "internal.h"
26 #include "pidgin.h"
28 #include "account.h"
29 #include "conversation.h"
30 #include "core.h"
31 #include "debug.h"
32 #include "glibcompat.h"
33 #include "log.h"
34 #include "network.h"
35 #include "notify.h"
36 #include "options.h"
37 #include "prefs.h"
38 #include "protocol.h"
39 #include "pounce.h"
40 #include "sound.h"
41 #include "status.h"
42 #include "util.h"
43 #include "whiteboard.h"
44 #include "xfer.h"
46 #include "gtkaccount.h"
47 #include "gtkblist.h"
48 #include "gtkconn.h"
49 #include "gtkconv.h"
50 #include "gtkdialogs.h"
51 #include "gtkdocklet.h"
52 #include "gtkxfer.h"
53 #include "gtkidle.h"
54 #include "gtkmedia.h"
55 #include "gtknotify.h"
56 #include "gtkplugin.h"
57 #include "gtkpounce.h"
58 #include "gtkprefs.h"
59 #include "gtkprivacy.h"
60 #include "gtkrequest.h"
61 #include "gtkroomlist.h"
62 #include "gtksavedstatuses.h"
63 #include "gtksmiley-theme.h"
64 #include "gtksound.h"
65 #include "gtkutils.h"
66 #include "pidginstock.h"
67 #include "gtkwhiteboard.h"
68 #include "pidgindebug.h"
69 #include "pidginlog.h"
71 #ifndef _WIN32
72 #include <signal.h>
73 #endif
75 #ifndef _WIN32
78 * Lists of signals we wish to catch and those we wish to ignore.
79 * Each list terminated with -1
81 static const int catch_sig_list[] = {
82 SIGSEGV,
83 SIGINT,
84 SIGTERM,
85 SIGQUIT,
86 SIGCHLD,
90 static const int ignore_sig_list[] = {
91 SIGPIPE,
94 #endif /* !_WIN32 */
96 static void
97 dologin_named(const char *name)
99 PurpleAccount *account;
100 char **names;
101 int i;
103 if (name != NULL) { /* list of names given */
104 names = g_strsplit(name, ",", 64);
105 for (i = 0; names[i] != NULL; i++) {
106 account = purple_accounts_find(names[i], NULL);
107 if (account != NULL) { /* found a user */
108 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
111 g_strfreev(names);
112 } else { /* no name given, use the first account */
113 GList *accounts;
115 accounts = purple_accounts_get_all();
116 if (accounts != NULL)
118 account = (PurpleAccount *)accounts->data;
119 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
124 #ifndef _WIN32
125 static char *segfault_message;
127 static guint signal_channel_watcher;
129 static int signal_sockets[2];
131 static void sighandler(int sig);
133 static void sighandler(int sig)
135 ssize_t written;
138 * We won't do any of the heavy lifting for the signal handling here
139 * because we have no idea what was interrupted. Previously this signal
140 * handler could result in some calls to malloc/free, which can cause
141 * deadlock in libc when the signal handler was interrupting a previous
142 * malloc or free. So instead we'll do an ugly hack where we write the
143 * signal number to one end of a socket pair. The other half of the
144 * socket pair is watched by our main loop. When the main loop sees new
145 * data on the socket it reads in the signal and performs the appropriate
146 * action without fear of interrupting stuff.
148 if (sig == SIGSEGV) {
149 fprintf(stderr, "%s", segfault_message);
150 abort();
151 return;
154 written = write(signal_sockets[0], &sig, sizeof(int));
155 if (written < 0 || written != sizeof(int)) {
156 /* This should never happen */
157 purple_debug_error("sighandler", "Received signal %d but only "
158 "wrote %" G_GSSIZE_FORMAT " bytes out of %"
159 G_GSIZE_FORMAT ": %s\n",
160 sig, written, sizeof(int), g_strerror(errno));
161 exit(1);
165 static gboolean
166 mainloop_sighandler(GIOChannel *source, GIOCondition cond, gpointer data)
168 GIOStatus stat;
169 int sig;
170 gsize bytes_read;
171 GError *error = NULL;
173 /* read the signal number off of the io channel */
174 stat = g_io_channel_read_chars(source, (gchar *)&sig, sizeof(int),
175 &bytes_read, &error);
176 if (stat != G_IO_STATUS_NORMAL) {
177 purple_debug_error("sighandler", "Signal callback failed to read "
178 "from signal socket: %s", error->message);
179 purple_core_quit();
180 return FALSE;
183 switch (sig) {
184 case SIGCHLD:
185 /* Restore signal catching */
186 signal(SIGCHLD, sighandler);
187 break;
188 default:
189 purple_debug_warning("sighandler", "Caught signal %d\n", sig);
190 purple_core_quit();
193 return TRUE;
195 #endif /* !_WIN32 */
197 static int
198 ui_main(void)
200 pidgin_blist_setup_sort_methods();
202 gtk_window_set_default_icon_name("pidgin");
204 return 0;
207 static void
208 debug_init(void)
210 PidginDebugUi *ui = pidgin_debug_ui_new();
211 purple_debug_set_ui(PURPLE_DEBUG_UI(ui));
214 static void
215 pidgin_ui_init(void)
217 gchar *path;
219 path = g_build_filename(PURPLE_DATADIR, "pidgin", "icons", NULL);
220 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), path);
221 g_free(path);
223 pidgin_stock_init();
225 /* Set the UI operation structures. */
226 purple_accounts_set_ui_ops(pidgin_accounts_get_ui_ops());
227 purple_xfers_set_ui_ops(pidgin_xfers_get_ui_ops());
228 purple_blist_set_ui(PIDGIN_TYPE_BUDDY_LIST);
229 purple_notify_set_ui_ops(pidgin_notify_get_ui_ops());
230 purple_request_set_ui_ops(pidgin_request_get_ui_ops());
231 purple_sound_set_ui_ops(pidgin_sound_get_ui_ops());
232 purple_connections_set_ui_ops(pidgin_connections_get_ui_ops());
233 purple_whiteboard_set_ui_ops(pidgin_whiteboard_get_ui_ops());
234 purple_idle_set_ui_ops(pidgin_idle_get_ui_ops());
236 pidgin_accounts_init();
237 pidgin_connection_init();
238 pidgin_request_init();
239 pidgin_blist_init();
240 pidgin_status_init();
241 pidgin_conversations_init();
242 pidgin_pounces_init();
243 pidgin_privacy_init();
244 pidgin_xfers_init();
245 pidgin_roomlist_init();
246 pidgin_log_init();
247 pidgin_docklet_init();
248 _pidgin_smiley_theme_init();
249 pidgin_medias_init();
250 pidgin_notify_init();
253 static GHashTable *ui_info = NULL;
255 static void
256 pidgin_quit(void)
258 /* Uninit */
259 PurpleDebugUi *ui;
261 pidgin_notify_uninit();
262 _pidgin_smiley_theme_uninit();
263 pidgin_conversations_uninit();
264 pidgin_status_uninit();
265 pidgin_docklet_uninit();
266 pidgin_blist_uninit();
267 pidgin_request_uninit();
268 pidgin_connection_uninit();
269 pidgin_accounts_uninit();
270 pidgin_xfers_uninit();
271 ui = purple_debug_get_ui();
272 purple_debug_set_ui(NULL);
273 g_object_unref(ui);
275 if(NULL != ui_info)
276 g_hash_table_destroy(ui_info);
278 /* and end it all... */
279 g_application_quit(g_application_get_default());
282 static GHashTable *pidgin_ui_get_info(void)
284 if(NULL == ui_info) {
285 ui_info = g_hash_table_new(g_str_hash, g_str_equal);
287 g_hash_table_insert(ui_info, "name", (char*)PIDGIN_NAME);
288 g_hash_table_insert(ui_info, "version", VERSION);
289 g_hash_table_insert(ui_info, "website", "https://pidgin.im");
290 g_hash_table_insert(ui_info, "dev_website", "https://developer.pidgin.im");
291 g_hash_table_insert(ui_info, "client_type", "pc");
294 * prpl-aim-clientkey is a DevID (or "client key") for Pidgin, given to
295 * us by AOL in September 2016. prpl-icq-clientkey is also a client key
296 * for Pidgin, owned by the AIM account "markdoliner." Please don't use
297 * either for other applications. Instead, you can either not specify a
298 * client key, in which case the default "libpurple" key will be used,
299 * or you can try to register your own at the AIM or ICQ web sites
300 * (although this functionality was removed at some point, it's possible
301 * it has been re-added).
303 g_hash_table_insert(ui_info, "prpl-aim-clientkey", "do1UCeb5gNqxB1S1");
304 g_hash_table_insert(ui_info, "prpl-icq-clientkey", "ma1cSASNCKFtrdv9");
307 * prpl-aim-distid is a distID for Pidgin, given to us by AOL in
308 * September 2016. prpl-icq-distid is also a distID for Pidgin, given
309 * to us by AOL. Please don't use either for other applications.
310 * Instead, you can just not specify a distID and libpurple will use a
311 * default.
313 g_hash_table_insert(ui_info, "prpl-aim-distid", GINT_TO_POINTER(1715));
314 g_hash_table_insert(ui_info, "prpl-icq-distid", GINT_TO_POINTER(1550));
317 return ui_info;
320 static PurpleCoreUiOps core_ops =
322 pidgin_prefs_init,
323 debug_init,
324 pidgin_ui_init,
325 pidgin_quit,
326 pidgin_ui_get_info,
327 NULL,
328 NULL,
329 NULL,
330 NULL
333 static PurpleCoreUiOps *
334 pidgin_core_get_ui_ops(void)
336 return &core_ops;
339 static gint
340 pidgin_handle_local_options_cb(GApplication *app, GVariantDict *options,
341 gpointer user_data)
343 #if !GLIB_CHECK_VERSION(2, 48, 0)
344 gchar *app_id = NULL;
345 #endif
347 if (g_variant_dict_contains(options, "version")) {
348 printf("%s %s (libpurple %s)\n", PIDGIN_NAME, DISPLAY_VERSION,
349 purple_core_get_version());
350 return 0;
353 #if !GLIB_CHECK_VERSION(2, 48, 0)
354 if (g_variant_dict_lookup(options, "gapplication-app-id",
355 "s", &app_id)) {
356 g_variant_dict_remove(options, "gapplication-app-id");
357 g_application_set_application_id(app, app_id);
358 g_free(app_id);
360 #endif
362 return -1;
365 static void
366 pidgin_activate_cb(GApplication *application, gpointer user_data)
368 PidginBuddyList *blist = pidgin_blist_get_default_gtk_blist();
370 if (blist != NULL && blist->window != NULL) {
371 gtk_window_present(GTK_WINDOW(blist->window));
375 static gint
376 pidgin_command_line_cb(GApplication *application,
377 GApplicationCommandLine *cmdline, gpointer user_data)
379 gchar **argv;
380 int argc;
381 int i;
383 argv = g_application_command_line_get_arguments(cmdline, &argc);
385 if (argc == 1) {
386 /* No arguments, just activate */
387 g_application_activate(application);
390 /* Start at 1 to skip the executable name */
391 for (i = 1; i < argc; ++i) {
392 purple_got_protocol_handler_uri(argv[i]);
395 g_strfreev(argv);
397 return 0;
400 static gchar *opt_config_dir_arg = NULL;
401 static gboolean opt_nologin = FALSE;
402 static gboolean opt_login = FALSE;
403 static gchar *opt_login_arg = NULL;
405 static gboolean
406 login_opt_arg_func(const gchar *option_name, const gchar *value,
407 gpointer data, GError **error)
409 opt_login = TRUE;
411 g_free(opt_login_arg);
412 opt_login_arg = g_strdup(value);
414 return TRUE;
417 static GOptionEntry option_entries[] = {
418 #if !GLIB_CHECK_VERSION(2, 48, 0)
419 /* Support G_APPLICATION_CAN_OVERRIDE_APP_ID functionality
420 * even though we don't depend on version 2.48 yet
422 {"gapplication-app-id", '\0', 0, G_OPTION_ARG_STRING, NULL,
423 N_("Override the application's ID") },
424 #endif
425 {"config", 'c', 0,
426 G_OPTION_ARG_FILENAME, &opt_config_dir_arg,
427 N_("use DIR for config files"), N_("DIR")},
428 {"login", 'l', G_OPTION_FLAG_OPTIONAL_ARG,
429 G_OPTION_ARG_CALLBACK, &login_opt_arg_func,
430 N_("enable specified account(s) (optional argument NAME\n"
432 "specifies account(s) to use, separated by commas.\n"
434 "Without this only the first account will be enabled)"),
435 N_("[NAME]")},
436 {"nologin", 'n', 0,
437 G_OPTION_ARG_NONE, &opt_nologin,
438 N_("don't automatically login"), NULL},
439 {"version", 'v', 0,
440 G_OPTION_ARG_NONE, NULL,
441 N_("display the current version and exit"), NULL},
442 {NULL}
445 #ifndef _WIN32
446 static void
447 pidgin_setup_error_handler(void)
449 int sig_indx; /* for setting up signal catching */
450 sigset_t sigset;
451 char errmsg[BUFSIZ];
452 GIOChannel *signal_channel;
453 GIOStatus signal_status;
454 GError *error = NULL;
455 #ifndef DEBUG
456 char *segfault_message_tmp;
458 /* We translate this here in case the crash breaks gettext. */
459 segfault_message_tmp = g_strdup_printf(_(
460 "%s %s has segfaulted and attempted to dump a core file.\n"
461 "This is a bug in the software and has happened through\n"
462 "no fault of your own.\n\n"
463 "If you can reproduce the crash, please notify the developers\n"
464 "by reporting a bug at:\n"
465 "%ssimpleticket/\n\n"
466 "Please make sure to specify what you were doing at the time\n"
467 "and post the backtrace from the core file. If you do not know\n"
468 "how to get the backtrace, please read the instructions at\n"
469 "%swiki/GetABacktrace\n"),
470 PIDGIN_NAME, DISPLAY_VERSION, PURPLE_DEVEL_WEBSITE, PURPLE_DEVEL_WEBSITE
473 /* we have to convert the message (UTF-8 to console
474 charset) early because after a segmentation fault
475 it's not a good practice to allocate memory */
476 segfault_message = g_locale_from_utf8(segfault_message_tmp,
477 -1, NULL, NULL, &error);
478 if (segfault_message != NULL) {
479 g_free(segfault_message_tmp);
481 else {
482 /* use 'segfault_message_tmp' (UTF-8) as a fallback */
483 g_warning("%s\n", error->message);
484 g_clear_error(&error);
485 segfault_message = segfault_message_tmp;
487 #else
488 /* Don't mark this for translation. */
489 segfault_message = g_strdup(
490 "Hi, user. We need to talk.\n"
491 "I think something's gone wrong here. It's probably my fault.\n"
492 "No, really, it's not you... it's me... no no no, I think we get along well\n"
493 "it's just that.... well, I want to see other people. I... what?!? NO! I \n"
494 "haven't been cheating on you!! How many times do you want me to tell you?! And\n"
495 "for the last time, it's just a rash!\n"
497 #endif
500 * Create a socket pair for receiving unix signals from a signal
501 * handler.
503 if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sockets) < 0) {
504 perror("Failed to create sockets for GLib signal handling");
505 exit(1);
507 signal_channel = g_io_channel_unix_new(signal_sockets[1]);
510 * Set the channel encoding to raw binary instead of the default of
511 * UTF-8, because we'll be sending integers across instead of strings.
513 signal_status = g_io_channel_set_encoding(signal_channel, NULL, &error);
514 if (signal_status != G_IO_STATUS_NORMAL) {
515 fprintf(stderr, "Failed to set the signal channel to raw "
516 "binary: %s", error->message);
517 g_clear_error(&error);
518 exit(1);
520 signal_channel_watcher = g_io_add_watch(signal_channel, G_IO_IN, mainloop_sighandler, NULL);
521 g_io_channel_unref(signal_channel);
523 /* Let's not violate any PLA's!!!! */
524 /* jseymour: whatever the fsck that means */
525 /* Robot101: for some reason things like gdm like to block *
526 * useful signals like SIGCHLD, so we unblock all the ones we *
527 * declare a handler for. thanks JSeymour and Vann. */
528 if (sigemptyset(&sigset)) {
529 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't initialise empty signal set");
530 perror(errmsg);
532 for(sig_indx = 0; catch_sig_list[sig_indx] != -1; ++sig_indx) {
533 if(signal(catch_sig_list[sig_indx], sighandler) == SIG_ERR) {
534 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't set signal %d for catching",
535 catch_sig_list[sig_indx]);
536 perror(errmsg);
538 if(sigaddset(&sigset, catch_sig_list[sig_indx])) {
539 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't include signal %d for unblocking",
540 catch_sig_list[sig_indx]);
541 perror(errmsg);
544 for(sig_indx = 0; ignore_sig_list[sig_indx] != -1; ++sig_indx) {
545 if(signal(ignore_sig_list[sig_indx], SIG_IGN) == SIG_ERR) {
546 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't set signal %d to ignore",
547 ignore_sig_list[sig_indx]);
548 perror(errmsg);
552 if (sigprocmask(SIG_UNBLOCK, &sigset, NULL)) {
553 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't unblock signals");
554 perror(errmsg);
557 #endif /* !_WIN32 */
559 static void
560 pidgin_startup_cb(GApplication *app, gpointer user_data)
562 char *search_path;
563 GtkCssProvider *provider;
564 GdkScreen *screen;
565 GList *accounts;
566 gboolean gui_check;
567 GList *active_accounts;
568 GStatBuf st;
569 GError *error = NULL;
571 /* set a user-specified config directory */
572 if (opt_config_dir_arg != NULL) {
573 if (g_path_is_absolute(opt_config_dir_arg)) {
574 purple_util_set_user_dir(opt_config_dir_arg);
575 } else {
576 /* Make an absolute (if not canonical) path */
577 char *cwd = g_get_current_dir();
578 char *path = g_build_path(G_DIR_SEPARATOR_S, cwd, opt_config_dir_arg, NULL);
579 purple_util_set_user_dir(path);
580 g_free(path);
581 g_free(cwd);
585 search_path = g_build_filename(purple_user_dir(), "gtk-3.0.css", NULL);
587 provider = gtk_css_provider_new();
588 gui_check = gtk_css_provider_load_from_path(provider, search_path, &error);
590 if (gui_check && !error) {
591 screen = gdk_screen_get_default();
592 gtk_style_context_add_provider_for_screen(screen,
593 GTK_STYLE_PROVIDER(provider),
594 GTK_STYLE_PROVIDER_PRIORITY_USER);
595 } else {
596 purple_debug_error("gtk", "Unable to load custom gtk-3.0.css: %s\n",
597 error ? error->message : "(unknown error)");
598 g_clear_error(&error);
601 g_free(search_path);
603 #ifdef _WIN32
604 winpidgin_init();
605 #endif
607 purple_core_set_ui_ops(pidgin_core_get_ui_ops());
609 if (!purple_core_init(PIDGIN_UI)) {
610 fprintf(stderr,
611 "Initialization of the libpurple core failed. Dumping core.\n"
612 "Please report this!\n");
613 #ifndef _WIN32
614 g_free(segfault_message);
615 #endif
616 abort();
619 if (!g_getenv("PURPLE_PLUGINS_SKIP")) {
620 search_path = g_build_filename(purple_user_dir(),
621 "plugins", NULL);
622 if (!g_stat(search_path, &st))
623 g_mkdir(search_path, S_IRUSR | S_IWUSR | S_IXUSR);
624 purple_plugins_add_search_path(search_path);
625 g_free(search_path);
627 purple_plugins_add_search_path(PIDGIN_LIBDIR);
628 } else {
629 purple_debug_info("gtk",
630 "PURPLE_PLUGINS_SKIP environment variable "
631 "set, skipping normal Pidgin plugin paths");
634 purple_plugins_refresh();
636 /* load plugins we had when we quit */
637 purple_plugins_load_saved(PIDGIN_PREFS_ROOT "/plugins/loaded");
639 ui_main();
641 g_free(opt_config_dir_arg);
642 opt_config_dir_arg = NULL;
645 * We want to show the blist early in the init process so the
646 * user feels warm and fuzzy (not cold and prickley).
648 purple_blist_show();
650 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"))
651 pidgin_debug_window_show();
653 if (opt_login) {
654 /* disable all accounts */
655 for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
656 PurpleAccount *account = accounts->data;
657 purple_account_set_enabled(account, PIDGIN_UI, FALSE);
659 /* honor the startup status preference */
660 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
661 purple_savedstatus_activate(purple_savedstatus_get_startup());
662 /* now enable the requested ones */
663 dologin_named(opt_login_arg);
664 g_free(opt_login_arg);
665 opt_login_arg = NULL;
666 } else if (opt_nologin) {
667 /* Set all accounts to "offline" */
668 PurpleSavedStatus *saved_status;
670 /* If we've used this type+message before, lookup the transient status */
671 saved_status = purple_savedstatus_find_transient_by_type_and_message(
672 PURPLE_STATUS_OFFLINE, NULL);
674 /* If this type+message is unique then create a new transient saved status */
675 if (saved_status == NULL)
676 saved_status = purple_savedstatus_new(NULL, PURPLE_STATUS_OFFLINE);
678 /* Set the status for each account */
679 purple_savedstatus_activate(saved_status);
680 } else {
681 /* Everything is good to go--sign on already */
682 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
683 purple_savedstatus_activate(purple_savedstatus_get_startup());
684 purple_accounts_restore_current_statuses();
687 if ((active_accounts = purple_accounts_get_all_active()) == NULL)
689 pidgin_accounts_window_show();
691 else
693 g_list_free(active_accounts);
696 /* GTK clears the notification for us when opening the first window,
697 * but we may have launched with only a status icon, so clear the it
698 * just in case. */
699 gdk_notify_startup_complete();
701 #ifdef _WIN32
702 winpidgin_post_init();
703 #endif
705 /* TODO: Use GtkApplicationWindow or add a window instead */
706 g_application_hold(app);
709 int pidgin_start(int argc, char *argv[])
711 GApplication *app;
712 int ret;
714 #ifdef DEBUG
715 purple_debug_set_enabled(TRUE);
716 #endif
718 bindtextdomain(PACKAGE, PURPLE_LOCALEDIR);
719 bind_textdomain_codeset(PACKAGE, "UTF-8");
720 textdomain(PACKAGE);
722 /* Locale initialization is not complete here. See gtk_init_check() */
723 setlocale(LC_ALL, "");
725 #ifndef _WIN32
726 pidgin_setup_error_handler();
727 #endif
729 app = G_APPLICATION(gtk_application_new("im.pidgin.Pidgin",
730 #if GLIB_CHECK_VERSION(2, 48, 0)
731 G_APPLICATION_CAN_OVERRIDE_APP_ID |
732 #endif
733 G_APPLICATION_HANDLES_COMMAND_LINE));
735 g_application_add_main_option_entries(app, option_entries);
736 g_application_add_option_group(app, purple_get_option_group());
737 g_application_add_option_group(app, gplugin_get_option_group());
739 g_object_set(app, "register-session", TRUE, NULL);
741 g_signal_connect(app, "handle-local-options",
742 G_CALLBACK(pidgin_handle_local_options_cb), NULL);
743 g_signal_connect(app, "startup",
744 G_CALLBACK(pidgin_startup_cb), NULL);
745 g_signal_connect(app, "activate",
746 G_CALLBACK(pidgin_activate_cb), NULL);
747 g_signal_connect(app, "command-line",
748 G_CALLBACK(pidgin_command_line_cb), NULL);
750 ret = g_application_run(app, argc, argv);
752 /* Make sure purple has quit in case something in GApplication
753 * has caused g_application_run() to finish on its own. This can
754 * happen, for example, if the desktop session is ending.
756 if (purple_get_core() != NULL) {
757 purple_core_quit();
760 if (g_application_get_is_registered(app) &&
761 g_application_get_is_remote(app)) {
762 g_printerr(_("Exiting because another libpurple client is "
763 "already running.\n"));
766 /* Now that we're sure purple_core_quit() has been called,
767 * this can be freed.
769 g_object_unref(app);
771 #ifndef _WIN32
772 g_free(segfault_message);
773 g_source_remove(signal_channel_watcher);
774 close(signal_sockets[0]);
775 close(signal_sockets[1]);
776 #endif
778 #ifdef _WIN32
779 winpidgin_cleanup();
780 #endif
782 return ret;