Merged in CMaiku/pidgin (pull request #226)
[pidgin-git.git] / pidgin / libpidgin.c
blobef4bbf92288bca16a85c58c6615954fe63590ad4
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 "internal.h"
25 #include "pidgin.h"
27 #include "account.h"
28 #include "conversation.h"
29 #include "core.h"
30 #include "dbus-maybe.h"
31 #include "debug.h"
32 #include "glibcompat.h"
33 #include "log.h"
34 #include "network.h"
35 #include "notify.h"
36 #include "prefs.h"
37 #include "protocol.h"
38 #include "pounce.h"
39 #include "sound.h"
40 #include "status.h"
41 #include "util.h"
42 #include "whiteboard.h"
43 #include "xfer.h"
45 #include "gtkaccount.h"
46 #include "gtkblist.h"
47 #include "gtkconn.h"
48 #include "gtkconv.h"
49 #include "gtkdebug.h"
50 #include "gtkdialogs.h"
51 #include "gtkdocklet.h"
52 #include "gtkxfer.h"
53 #include "gtkidle.h"
54 #include "gtklog.h"
55 #include "gtkmedia.h"
56 #include "gtknotify.h"
57 #include "gtkplugin.h"
58 #include "gtkpounce.h"
59 #include "gtkprefs.h"
60 #include "gtkprivacy.h"
61 #include "gtkrequest.h"
62 #include "gtkroomlist.h"
63 #include "gtksavedstatuses.h"
64 #include "gtksmiley-theme.h"
65 #include "gtksound.h"
66 #include "gtkutils.h"
67 #include "pidginstock.h"
68 #include "gtkwhiteboard.h"
70 #ifndef _WIN32
71 #include <signal.h>
72 #endif
74 #include <getopt.h>
76 #ifndef _WIN32
79 * Lists of signals we wish to catch and those we wish to ignore.
80 * Each list terminated with -1
82 static const int catch_sig_list[] = {
83 SIGSEGV,
84 SIGINT,
85 SIGTERM,
86 SIGQUIT,
87 SIGCHLD,
91 static const int ignore_sig_list[] = {
92 SIGPIPE,
95 #endif /* !_WIN32 */
97 static void
98 dologin_named(const char *name)
100 PurpleAccount *account;
101 char **names;
102 int i;
104 if (name != NULL) { /* list of names given */
105 names = g_strsplit(name, ",", 64);
106 for (i = 0; names[i] != NULL; i++) {
107 account = purple_accounts_find(names[i], NULL);
108 if (account != NULL) { /* found a user */
109 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
112 g_strfreev(names);
113 } else { /* no name given, use the first account */
114 GList *accounts;
116 accounts = purple_accounts_get_all();
117 if (accounts != NULL)
119 account = (PurpleAccount *)accounts->data;
120 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
125 #ifndef _WIN32
126 static char *segfault_message;
128 static int signal_sockets[2];
130 static void sighandler(int sig);
132 static void sighandler(int sig)
134 ssize_t written;
137 * We won't do any of the heavy lifting for the signal handling here
138 * because we have no idea what was interrupted. Previously this signal
139 * handler could result in some calls to malloc/free, which can cause
140 * deadlock in libc when the signal handler was interrupting a previous
141 * malloc or free. So instead we'll do an ugly hack where we write the
142 * signal number to one end of a socket pair. The other half of the
143 * socket pair is watched by our main loop. When the main loop sees new
144 * data on the socket it reads in the signal and performs the appropriate
145 * action without fear of interrupting stuff.
147 if (sig == SIGSEGV) {
148 fprintf(stderr, "%s", segfault_message);
149 abort();
150 return;
153 written = write(signal_sockets[0], &sig, sizeof(int));
154 if (written < 0 || written != sizeof(int)) {
155 /* This should never happen */
156 purple_debug_error("sighandler", "Received signal %d but only "
157 "wrote %" G_GSSIZE_FORMAT " bytes out of %"
158 G_GSIZE_FORMAT ": %s\n",
159 sig, written, sizeof(int), g_strerror(errno));
160 exit(1);
164 static gboolean
165 mainloop_sighandler(GIOChannel *source, GIOCondition cond, gpointer data)
167 GIOStatus stat;
168 int sig;
169 gsize bytes_read;
170 GError *error = NULL;
172 /* read the signal number off of the io channel */
173 stat = g_io_channel_read_chars(source, (gchar *)&sig, sizeof(int),
174 &bytes_read, &error);
175 if (stat != G_IO_STATUS_NORMAL) {
176 purple_debug_error("sighandler", "Signal callback failed to read "
177 "from signal socket: %s", error->message);
178 purple_core_quit();
179 return FALSE;
182 switch (sig) {
183 case SIGCHLD:
184 /* Restore signal catching */
185 signal(SIGCHLD, sighandler);
186 break;
187 default:
188 purple_debug_warning("sighandler", "Caught signal %d\n", sig);
189 purple_core_quit();
192 return TRUE;
194 #endif /* !_WIN32 */
196 static int
197 ui_main(void)
199 #ifndef _WIN32
200 GList *icons = NULL;
201 GdkPixbuf *icon = NULL;
202 char *icon_path;
203 gsize i;
204 struct {
205 const char *dir;
206 const char *filename;
207 } icon_sizes[] = {
208 {"16x16", "pidgin.png"},
209 {"24x24", "pidgin.png"},
210 {"32x32", "pidgin.png"},
211 {"48x48", "pidgin.png"},
212 {"scalable", "pidgin.svg"}
215 #endif
217 pidgin_blist_setup_sort_methods();
219 #ifndef _WIN32
220 /* use the nice PNG icon for all the windows */
221 for(i=0; i<G_N_ELEMENTS(icon_sizes); i++) {
222 icon_path = g_build_filename(PURPLE_DATADIR, "icons", "hicolor",
223 icon_sizes[i].dir, "apps", icon_sizes[i].filename, NULL);
224 icon = pidgin_pixbuf_new_from_file(icon_path);
225 g_free(icon_path);
226 if (icon) {
227 icons = g_list_append(icons,icon);
228 } else {
229 purple_debug_error("ui_main",
230 "Failed to load the default window icon (%spx version)!\n", icon_sizes[i].dir);
233 if(NULL == icons) {
234 purple_debug_error("ui_main", "Unable to load any size of default window icon!\n");
235 } else {
236 gtk_window_set_default_icon_list(icons);
238 g_list_foreach(icons, (GFunc)g_object_unref, NULL);
239 g_list_free(icons);
241 #endif
243 return 0;
246 static void
247 debug_init(void)
249 purple_debug_set_ui_ops(pidgin_debug_get_ui_ops());
250 pidgin_debug_init();
253 static void
254 pidgin_ui_init(void)
256 pidgin_stock_init();
258 /* Set the UI operation structures. */
259 purple_accounts_set_ui_ops(pidgin_accounts_get_ui_ops());
260 purple_xfers_set_ui_ops(pidgin_xfers_get_ui_ops());
261 purple_blist_set_ui_ops(pidgin_blist_get_ui_ops());
262 purple_notify_set_ui_ops(pidgin_notify_get_ui_ops());
263 purple_request_set_ui_ops(pidgin_request_get_ui_ops());
264 purple_sound_set_ui_ops(pidgin_sound_get_ui_ops());
265 purple_connections_set_ui_ops(pidgin_connections_get_ui_ops());
266 purple_whiteboard_set_ui_ops(pidgin_whiteboard_get_ui_ops());
267 #if defined(USE_SCREENSAVER) || defined(HAVE_IOKIT)
268 purple_idle_set_ui_ops(pidgin_idle_get_ui_ops());
269 #endif
271 pidgin_accounts_init();
272 pidgin_connection_init();
273 pidgin_request_init();
274 pidgin_blist_init();
275 pidgin_status_init();
276 pidgin_conversations_init();
277 pidgin_pounces_init();
278 pidgin_privacy_init();
279 pidgin_xfers_init();
280 pidgin_roomlist_init();
281 pidgin_log_init();
282 pidgin_docklet_init();
283 _pidgin_smiley_theme_init();
284 pidgin_utils_init();
285 pidgin_medias_init();
286 pidgin_notify_init();
289 static GHashTable *ui_info = NULL;
291 static void
292 pidgin_quit(void)
294 /* Uninit */
295 pidgin_utils_uninit();
296 pidgin_notify_uninit();
297 _pidgin_smiley_theme_uninit();
298 pidgin_conversations_uninit();
299 pidgin_status_uninit();
300 pidgin_docklet_uninit();
301 pidgin_blist_uninit();
302 pidgin_request_uninit();
303 pidgin_connection_uninit();
304 pidgin_accounts_uninit();
305 pidgin_xfers_uninit();
306 pidgin_debug_uninit();
308 if(NULL != ui_info)
309 g_hash_table_destroy(ui_info);
311 /* and end it all... */
312 g_application_quit(g_application_get_default());
315 static GHashTable *pidgin_ui_get_info(void)
317 if(NULL == ui_info) {
318 ui_info = g_hash_table_new(g_str_hash, g_str_equal);
320 g_hash_table_insert(ui_info, "name", (char*)PIDGIN_NAME);
321 g_hash_table_insert(ui_info, "version", VERSION);
322 g_hash_table_insert(ui_info, "website", "https://pidgin.im");
323 g_hash_table_insert(ui_info, "dev_website", "https://developer.pidgin.im");
324 g_hash_table_insert(ui_info, "client_type", "pc");
327 * prpl-aim-clientkey is a DevID (or "client key") for Pidgin, given to
328 * us by AOL in September 2016. prpl-icq-clientkey is also a client key
329 * for Pidgin, owned by the AIM account "markdoliner." Please don't use
330 * either for other applications. Instead, you can either not specify a
331 * client key, in which case the default "libpurple" key will be used,
332 * or you can try to register your own at the AIM or ICQ web sites
333 * (although this functionality was removed at some point, it's possible
334 * it has been re-added).
336 g_hash_table_insert(ui_info, "prpl-aim-clientkey", "do1UCeb5gNqxB1S1");
337 g_hash_table_insert(ui_info, "prpl-icq-clientkey", "ma1cSASNCKFtrdv9");
340 * prpl-aim-distid is a distID for Pidgin, given to us by AOL in
341 * September 2016. prpl-icq-distid is also a distID for Pidgin, given
342 * to us by AOL. Please don't use either for other applications.
343 * Instead, you can just not specify a distID and libpurple will use a
344 * default.
346 g_hash_table_insert(ui_info, "prpl-aim-distid", GINT_TO_POINTER(1715));
347 g_hash_table_insert(ui_info, "prpl-icq-distid", GINT_TO_POINTER(1550));
350 return ui_info;
353 static PurpleCoreUiOps core_ops =
355 pidgin_prefs_init,
356 debug_init,
357 pidgin_ui_init,
358 pidgin_quit,
359 pidgin_ui_get_info,
360 NULL,
361 NULL,
362 NULL,
363 NULL
366 static PurpleCoreUiOps *
367 pidgin_core_get_ui_ops(void)
369 return &core_ops;
372 static void
373 pidgin_activate_cb(GApplication *application, gpointer user_data)
375 purple_blist_set_visible(TRUE);
378 static void
379 show_usage(const char *name, gboolean terse)
381 char *text;
383 if (terse) {
384 text = g_strdup_printf(_("%s %s. Try `%s -h' for more information.\n"), PIDGIN_NAME, DISPLAY_VERSION, name);
385 } else {
386 GString *str = g_string_new(NULL);
387 g_string_append_printf(str, "%s %s\n", PIDGIN_NAME, DISPLAY_VERSION);
388 g_string_append_printf(str, _("Usage: %s [OPTION]...\n\n"), name);
389 g_string_append_printf(str, " -c, --config=%s %s\n",
390 _("DIR"), _("use DIR for config files"));
391 g_string_append_printf(str, " -d, --debug[=colored] %s\n",
392 _("print debugging messages to stdout"));
393 g_string_append_printf(str, " -f, --force-online %s\n",
394 _("force online, regardless of network status"));
395 g_string_append_printf(str, " -h, --help %s\n",
396 _("display this help and exit"));
397 g_string_append_printf(str, " -m, --multiple %s\n",
398 _("allow multiple instances"));
399 g_string_append_printf(str, " -n, --nologin %s\n",
400 _("don't automatically login"));
401 g_string_append_printf(str, " -l, --login[=%s] %s\n",
402 _("NAME"),
403 _("enable specified account(s) (optional argument NAME\n"
405 "specifies account(s) to use, separated by commas.\n"
407 "Without this only the first account will be enabled)."));
408 #ifndef WIN32
409 g_string_append_printf(str, " --display=DISPLAY %s\n",
410 _("X display to use"));
411 #endif /* !WIN32 */
412 g_string_append_printf(str, " -v, --version %s\n",
413 _("display the current version and exit"));
414 text = g_string_free(str, FALSE);
417 purple_print_utf8_to_console(stdout, text);
418 g_free(text);
421 int pidgin_start(int argc, char *argv[])
423 GApplication *app;
424 gboolean opt_force_online = FALSE;
425 gboolean opt_help = FALSE;
426 gboolean opt_login = FALSE;
427 gboolean opt_nologin = FALSE;
428 gboolean opt_version = FALSE;
429 gboolean opt_si = TRUE; /* Check for single instance? */
430 char *opt_config_dir_arg = NULL;
431 char *opt_login_arg = NULL;
432 char *opt_session_arg = NULL;
433 char *search_path;
434 GtkCssProvider *provider;
435 GdkScreen *screen;
436 GList *accounts;
437 #ifndef _WIN32
438 int sig_indx; /* for setting up signal catching */
439 sigset_t sigset;
440 char errmsg[BUFSIZ];
441 GIOChannel *signal_channel;
442 GIOStatus signal_status;
443 guint signal_channel_watcher;
444 #ifndef DEBUG
445 char *segfault_message_tmp;
446 #endif /* DEBUG */
447 #endif /* !_WIN32 */
448 int opt;
449 gboolean gui_check;
450 gboolean debug_enabled, debug_colored;
451 GList *active_accounts;
452 GStatBuf st;
453 GError *error;
454 int ret;
456 struct option long_options[] = {
457 {"config", required_argument, NULL, 'c'},
458 {"debug", optional_argument, NULL, 'd'},
459 {"force-online", no_argument, NULL, 'f'},
460 {"help", no_argument, NULL, 'h'},
461 {"login", optional_argument, NULL, 'l'},
462 {"multiple", no_argument, NULL, 'm'},
463 {"nologin", no_argument, NULL, 'n'},
464 {"session", required_argument, NULL, 's'},
465 {"version", no_argument, NULL, 'v'},
466 {"display", required_argument, NULL, 'D'},
467 {"sync", no_argument, NULL, 'S'},
468 {0, 0, 0, 0}
471 debug_colored = FALSE;
472 #ifdef DEBUG
473 debug_enabled = TRUE;
474 #else
475 debug_enabled = FALSE;
476 #endif
478 #ifdef ENABLE_NLS
479 bindtextdomain(PACKAGE, PURPLE_LOCALEDIR);
480 bind_textdomain_codeset(PACKAGE, "UTF-8");
481 textdomain(PACKAGE);
482 #endif
484 /* Locale initialization is not complete here. See gtk_init_check() */
485 setlocale(LC_ALL, "");
487 #ifndef _WIN32
489 #ifndef DEBUG
490 /* We translate this here in case the crash breaks gettext. */
491 segfault_message_tmp = g_strdup_printf(_(
492 "%s %s has segfaulted and attempted to dump a core file.\n"
493 "This is a bug in the software and has happened through\n"
494 "no fault of your own.\n\n"
495 "If you can reproduce the crash, please notify the developers\n"
496 "by reporting a bug at:\n"
497 "%ssimpleticket/\n\n"
498 "Please make sure to specify what you were doing at the time\n"
499 "and post the backtrace from the core file. If you do not know\n"
500 "how to get the backtrace, please read the instructions at\n"
501 "%swiki/GetABacktrace\n"),
502 PIDGIN_NAME, DISPLAY_VERSION, PURPLE_DEVEL_WEBSITE, PURPLE_DEVEL_WEBSITE
505 /* we have to convert the message (UTF-8 to console
506 charset) early because after a segmentation fault
507 it's not a good practice to allocate memory */
508 error = NULL;
509 segfault_message = g_locale_from_utf8(segfault_message_tmp,
510 -1, NULL, NULL, &error);
511 if (segfault_message != NULL) {
512 g_free(segfault_message_tmp);
514 else {
515 /* use 'segfault_message_tmp' (UTF-8) as a fallback */
516 g_warning("%s\n", error->message);
517 g_error_free(error);
518 segfault_message = segfault_message_tmp;
520 #else
521 /* Don't mark this for translation. */
522 segfault_message = g_strdup(
523 "Hi, user. We need to talk.\n"
524 "I think something's gone wrong here. It's probably my fault.\n"
525 "No, really, it's not you... it's me... no no no, I think we get along well\n"
526 "it's just that.... well, I want to see other people. I... what?!? NO! I \n"
527 "haven't been cheating on you!! How many times do you want me to tell you?! And\n"
528 "for the last time, it's just a rash!\n"
530 #endif
533 * Create a socket pair for receiving unix signals from a signal
534 * handler.
536 if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sockets) < 0) {
537 perror("Failed to create sockets for GLib signal handling");
538 exit(1);
540 signal_channel = g_io_channel_unix_new(signal_sockets[1]);
543 * Set the channel encoding to raw binary instead of the default of
544 * UTF-8, because we'll be sending integers across instead of strings.
546 error = NULL;
547 signal_status = g_io_channel_set_encoding(signal_channel, NULL, &error);
548 if (signal_status != G_IO_STATUS_NORMAL) {
549 fprintf(stderr, "Failed to set the signal channel to raw "
550 "binary: %s", error->message);
551 exit(1);
553 signal_channel_watcher = g_io_add_watch(signal_channel, G_IO_IN, mainloop_sighandler, NULL);
554 g_io_channel_unref(signal_channel);
556 /* Let's not violate any PLA's!!!! */
557 /* jseymour: whatever the fsck that means */
558 /* Robot101: for some reason things like gdm like to block *
559 * useful signals like SIGCHLD, so we unblock all the ones we *
560 * declare a handler for. thanks JSeymour and Vann. */
561 if (sigemptyset(&sigset)) {
562 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't initialise empty signal set");
563 perror(errmsg);
565 for(sig_indx = 0; catch_sig_list[sig_indx] != -1; ++sig_indx) {
566 if(signal(catch_sig_list[sig_indx], sighandler) == SIG_ERR) {
567 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't set signal %d for catching",
568 catch_sig_list[sig_indx]);
569 perror(errmsg);
571 if(sigaddset(&sigset, catch_sig_list[sig_indx])) {
572 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't include signal %d for unblocking",
573 catch_sig_list[sig_indx]);
574 perror(errmsg);
577 for(sig_indx = 0; ignore_sig_list[sig_indx] != -1; ++sig_indx) {
578 if(signal(ignore_sig_list[sig_indx], SIG_IGN) == SIG_ERR) {
579 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't set signal %d to ignore",
580 ignore_sig_list[sig_indx]);
581 perror(errmsg);
585 if (sigprocmask(SIG_UNBLOCK, &sigset, NULL)) {
586 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't unblock signals");
587 perror(errmsg);
589 #endif /* !_WIN32 */
591 /* scan command-line options */
592 opterr = 1;
593 while ((opt = getopt_long(argc, argv,
594 #ifndef _WIN32
595 "c:dfhmnl::s:v",
596 #else
597 "c:dfhmnl::v",
598 #endif
599 long_options, NULL)) != -1) {
600 switch (opt) {
601 case 'c': /* config dir */
602 g_free(opt_config_dir_arg);
603 opt_config_dir_arg = g_strdup(optarg);
604 break;
605 case 'd': /* debug */
606 debug_enabled = TRUE;
607 if (g_strcmp0(optarg, "colored") == 0)
608 debug_colored = TRUE;
609 break;
610 case 'f': /* force-online */
611 opt_force_online = TRUE;
612 break;
613 case 'h': /* help */
614 opt_help = TRUE;
615 break;
616 case 'n': /* no autologin */
617 opt_nologin = TRUE;
618 break;
619 case 'l': /* login, option username */
620 opt_login = TRUE;
621 g_free(opt_login_arg);
622 if (optarg != NULL)
623 opt_login_arg = g_strdup(optarg);
624 break;
625 case 's': /* use existing session ID */
626 g_free(opt_session_arg);
627 opt_session_arg = g_strdup(optarg);
628 break;
629 case 'v': /* version */
630 opt_version = TRUE;
631 break;
632 case 'm': /* do not ensure single instance. */
633 opt_si = FALSE;
634 break;
635 case 'D': /* --display */
636 case 'S': /* --sync */
637 /* handled by gtk_init_check below */
638 break;
639 case '?': /* show terse help */
640 default:
641 show_usage(argv[0], TRUE);
642 #ifndef _WIN32
643 g_free(segfault_message);
644 #endif
645 return 0;
646 break;
650 /* show help message */
651 if (opt_help) {
652 show_usage(argv[0], FALSE);
653 #ifndef _WIN32
654 g_free(segfault_message);
655 #endif
656 return 0;
658 /* show version message */
659 if (opt_version) {
660 printf("%s %s (libpurple %s)\n", PIDGIN_NAME, DISPLAY_VERSION,
661 purple_core_get_version());
662 #ifndef _WIN32
663 g_free(segfault_message);
664 #endif
665 return 0;
668 /* set a user-specified config directory */
669 if (opt_config_dir_arg != NULL) {
670 if (g_path_is_absolute(opt_config_dir_arg)) {
671 purple_util_set_user_dir(opt_config_dir_arg);
672 } else {
673 /* Make an absolute (if not canonical) path */
674 char *cwd = g_get_current_dir();
675 char *path = g_build_path(G_DIR_SEPARATOR_S, cwd, opt_config_dir_arg, NULL);
676 purple_util_set_user_dir(path);
677 g_free(path);
678 g_free(cwd);
683 * We're done piddling around with command line arguments.
684 * Fire up this baby.
687 if (g_getenv("PIDGIN_DEBUG_COLORED") != NULL)
688 debug_colored = TRUE;
689 purple_debug_set_enabled(debug_enabled);
690 purple_debug_set_colored(debug_colored);
692 /* Call this here as GtkApplication calls gtk_init() in
693 * g_application_register() and we don't necessarily want to exit().
695 gui_check = gtk_init_check(&argc, &argv);
696 if (!gui_check) {
697 const char *display = gdk_display_get_name(gdk_display_get_default());
699 printf("%s %s\n", PIDGIN_NAME, DISPLAY_VERSION);
701 g_warning("cannot open display: %s", display ? display : "unset");
702 #ifndef _WIN32
703 g_free(segfault_message);
704 #endif
706 return 1;
709 app = G_APPLICATION(gtk_application_new("im.pidgin.Pidgin",
710 G_APPLICATION_NON_UNIQUE));
712 g_object_set(app, "register-session", TRUE, NULL);
714 g_signal_connect(app, "activate",
715 G_CALLBACK(pidgin_activate_cb), NULL);
717 if (!g_application_register(app, NULL, &error)) {
718 purple_debug_error("gtk",
719 "Unable to register GApplication: %s\n",
720 error->message);
721 g_clear_error(&error);
722 g_object_unref(app);
723 #ifndef _WIN32
724 g_free(segfault_message);
725 #endif
726 return 1;
729 search_path = g_build_filename(purple_user_dir(), "gtk-3.0.css", NULL);
731 error = NULL;
732 provider = gtk_css_provider_new();
733 gui_check = gtk_css_provider_load_from_path(provider, search_path, &error);
735 if (gui_check && !error) {
736 screen = gdk_screen_get_default();
737 gtk_style_context_add_provider_for_screen(screen,
738 GTK_STYLE_PROVIDER(provider),
739 GTK_STYLE_PROVIDER_PRIORITY_USER);
740 } else {
741 purple_debug_error("gtk", "Unable to load custom gtk-3.0.css: %s\n",
742 error ? error->message : "(unknown error)");
745 g_free(search_path);
747 #ifdef _WIN32
748 winpidgin_init();
749 #endif
751 purple_core_set_ui_ops(pidgin_core_get_ui_ops());
753 if (!purple_core_init(PIDGIN_UI)) {
754 fprintf(stderr,
755 "Initialization of the libpurple core failed. Dumping core.\n"
756 "Please report this!\n");
757 #ifndef _WIN32
758 g_free(segfault_message);
759 #endif
760 abort();
763 search_path = g_build_filename(purple_user_dir(), "plugins", NULL);
764 if (!g_stat(search_path, &st))
765 g_mkdir(search_path, S_IRUSR | S_IWUSR | S_IXUSR);
766 purple_plugins_add_search_path(search_path);
767 g_free(search_path);
769 purple_plugins_add_search_path(PIDGIN_LIBDIR);
770 purple_plugins_refresh();
772 if (opt_si && !purple_core_ensure_single_instance()) {
773 #ifdef HAVE_DBUS
774 DBusConnection *conn = purple_dbus_get_connection();
775 DBusMessage *message = dbus_message_new_method_call(PURPLE_DBUS_SERVICE, PURPLE_DBUS_PATH,
776 PURPLE_DBUS_INTERFACE, "PurpleBlistSetVisible");
777 gboolean tr = TRUE;
778 dbus_message_append_args(message, DBUS_TYPE_INT32, &tr, DBUS_TYPE_INVALID);
779 dbus_connection_send_with_reply_and_block(conn, message, -1, NULL);
780 dbus_message_unref(message);
781 #endif
782 gdk_notify_startup_complete();
783 purple_core_quit();
784 g_printerr(_("Exiting because another libpurple client is already running.\n"));
785 #ifndef _WIN32
786 g_free(segfault_message);
787 #endif
788 return 0;
791 /* load plugins we had when we quit */
792 purple_plugins_load_saved(PIDGIN_PREFS_ROOT "/plugins/loaded");
794 ui_main();
796 g_free(opt_session_arg);
797 opt_session_arg = NULL;
798 g_free(opt_config_dir_arg);
799 opt_config_dir_arg = NULL;
801 /* This needs to be before purple_blist_show() so the
802 * statusbox gets the forced online status. */
803 if (opt_force_online)
804 purple_network_force_online();
807 * We want to show the blist early in the init process so the
808 * user feels warm and fuzzy (not cold and prickley).
810 purple_blist_show();
812 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"))
813 pidgin_debug_window_show();
815 if (opt_login) {
816 /* disable all accounts */
817 for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
818 PurpleAccount *account = accounts->data;
819 purple_account_set_enabled(account, PIDGIN_UI, FALSE);
821 /* honor the startup status preference */
822 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
823 purple_savedstatus_activate(purple_savedstatus_get_startup());
824 /* now enable the requested ones */
825 dologin_named(opt_login_arg);
826 g_free(opt_login_arg);
827 opt_login_arg = NULL;
828 } else if (opt_nologin) {
829 /* Set all accounts to "offline" */
830 PurpleSavedStatus *saved_status;
832 /* If we've used this type+message before, lookup the transient status */
833 saved_status = purple_savedstatus_find_transient_by_type_and_message(
834 PURPLE_STATUS_OFFLINE, NULL);
836 /* If this type+message is unique then create a new transient saved status */
837 if (saved_status == NULL)
838 saved_status = purple_savedstatus_new(NULL, PURPLE_STATUS_OFFLINE);
840 /* Set the status for each account */
841 purple_savedstatus_activate(saved_status);
842 } else {
843 /* Everything is good to go--sign on already */
844 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
845 purple_savedstatus_activate(purple_savedstatus_get_startup());
846 purple_accounts_restore_current_statuses();
849 if ((active_accounts = purple_accounts_get_all_active()) == NULL)
851 pidgin_accounts_window_show();
853 else
855 g_list_free(active_accounts);
858 /* GTK clears the notification for us when opening the first window,
859 * but we may have launched with only a status icon, so clear the it
860 * just in case. */
861 gdk_notify_startup_complete();
863 #ifdef _WIN32
864 winpidgin_post_init();
865 #endif
867 /* TODO: Use GtkApplicationWindow or add a window instead */
868 g_application_hold(app);
870 ret = g_application_run(app, 0, NULL);
872 /* Make sure purple has quit in case something in GApplication
873 * has caused g_application_run() to finish on its own. This can
874 * happen, for example, if the desktop session is ending.
876 if (purple_get_core() != NULL) {
877 purple_core_quit();
880 /* Now that we're sure purple_core_quit() has been called,
881 * this can be freed.
883 g_object_unref(app);
885 #ifndef _WIN32
886 g_free(segfault_message);
887 g_source_remove(signal_channel_watcher);
888 close(signal_sockets[0]);
889 close(signal_sockets[1]);
890 #endif
892 #ifdef _WIN32
893 winpidgin_cleanup();
894 #endif
896 return ret;