1 /*
2 * pidgin
3 *
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.
7 *
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.
12 *
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.
17 *
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
21 *
22 */
23
24 #include "internal.h"
25 #include "pidgin.h"
26
27 #include "account.h"
28 #include "conversation.h"
29 #include "core.h"
30 #include "dbus-maybe.h"
31 #include "debug.h"
32 #include "eventloop.h"
33 #include "ft.h"
34 #include "glibcompat.h"
35 #include "log.h"
36 #include "network.h"
37 #include "notify.h"
38 #include "prefs.h"
39 #include "prpl.h"
40 #include "pounce.h"
41 #include "sound.h"
42 #include "status.h"
43 #include "util.h"
44 #include "whiteboard.h"
45
46 #include "gtkaccount.h"
47 #include "gtkblist.h"
48 #include "gtkconn.h"
49 #include "gtkconv.h"
50 #include "gtkdebug.h"
51 #include "gtkdialogs.h"
52 #include "gtkdocklet.h"
53 #include "gtkeventloop.h"
54 #include "gtkft.h"
55 #include "gtkidle.h"
56 #include "gtklog.h"
57 #include "gtkmedia.h"
58 #include "gtknotify.h"
59 #include "gtkplugin.h"
60 #include "gtkpounce.h"
61 #include "gtkprefs.h"
62 #include "gtkprivacy.h"
63 #include "gtkrequest.h"
64 #include "gtkroomlist.h"
65 #include "gtksavedstatuses.h"
66 #include "gtksession.h"
67 #include "gtksmiley.h"
68 #include "gtksound.h"
69 #include "gtkthemes.h"
70 #include "gtkutils.h"
71 #include "pidginstock.h"
72 #include "gtkwhiteboard.h"
73
74 #ifdef HAVE_X11
75 #include <X11/Xlib.h>
76 #endif
77
78 #ifdef HAVE_SIGNAL_H
79 # include <signal.h>
80 #endif
81
82 #include <getopt.h>
83
84 #ifdef HAVE_SIGNAL_H
85
86 /*
87 * Lists of signals we wish to catch and those we wish to ignore.
88 * Each list terminated with -1
89 */
90 static const int catch_sig_list[] = {
91 SIGSEGV,
92 SIGINT,
93 SIGTERM,
94 SIGQUIT,
95 SIGCHLD,
96 #if defined(USE_GSTREAMER) && !defined(GST_CAN_DISABLE_FORKING)
97 SIGALRM,
98 #endif
99 -1
100 };
101
102 static const int ignore_sig_list[] = {
103 SIGPIPE,
104 -1
105 };
106 #endif
107
108 static void
dologin_named(const char * name)109 dologin_named(const char *name)
110 {
111 PurpleAccount *account;
112 char **names;
113 int i;
114
115 if (name != NULL) { /* list of names given */
116 names = g_strsplit(name, ",", 64);
117 for (i = 0; names[i] != NULL; i++) {
118 account = purple_accounts_find(names[i], NULL);
119 if (account != NULL) { /* found a user */
120 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
121 }
122 }
123 g_strfreev(names);
124 } else { /* no name given, use the first account */
125 GList *accounts;
126
127 accounts = purple_accounts_get_all();
128 if (accounts != NULL)
129 {
130 account = (PurpleAccount *)accounts->data;
131 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
132 }
133 }
134 }
135
136 #ifdef HAVE_SIGNAL_H
137 static char *segfault_message;
138
139 static int signal_sockets[2];
140
141 static void sighandler(int sig);
142
143 /*
144 * This child process reaping stuff is currently only used for processes that
145 * were forked to play sounds. It's not needed for forked DNS child, which
146 * have their own waitpid() call. It might be wise to move this code into
147 * gtksound.c.
148 */
149 static void
clean_pid(void)150 clean_pid(void)
151 {
152 int status;
153 pid_t pid;
154
155 do {
156 pid = waitpid(-1, &status, WNOHANG);
157 } while (pid != 0 && pid != (pid_t)-1);
158
159 if ((pid == (pid_t) - 1) && (errno != ECHILD)) {
160 char errmsg[BUFSIZ];
161 snprintf(errmsg, sizeof(errmsg), "Warning: waitpid() returned %d", pid);
162 perror(errmsg);
163 }
164 }
165
sighandler(int sig)166 static void sighandler(int sig)
167 {
168 ssize_t written;
169
170 /*
171 * We won't do any of the heavy lifting for the signal handling here
172 * because we have no idea what was interrupted. Previously this signal
173 * handler could result in some calls to malloc/free, which can cause
174 * deadlock in libc when the signal handler was interrupting a previous
175 * malloc or free. So instead we'll do an ugly hack where we write the
176 * signal number to one end of a socket pair. The other half of the
177 * socket pair is watched by our main loop. When the main loop sees new
178 * data on the socket it reads in the signal and performs the appropriate
179 * action without fear of interrupting stuff.
180 */
181 if (sig == SIGSEGV) {
182 fprintf(stderr, "%s", segfault_message);
183 abort();
184 return;
185 }
186
187 written = write(signal_sockets[0], &sig, sizeof(int));
188 if (written < 0 || written != sizeof(int)) {
189 /* This should never happen */
190 purple_debug_error("sighandler", "Received signal %d but only "
191 "wrote %" G_GSSIZE_FORMAT " bytes out of %"
192 G_GSIZE_FORMAT ": %s\n",
193 sig, written, sizeof(int), g_strerror(errno));
194 exit(1);
195 }
196 }
197
198 static gboolean
mainloop_sighandler(GIOChannel * source,GIOCondition cond,gpointer data)199 mainloop_sighandler(GIOChannel *source, GIOCondition cond, gpointer data)
200 {
201 GIOStatus stat;
202 int sig;
203 gsize bytes_read;
204 GError *error = NULL;
205
206 /* read the signal number off of the io channel */
207 stat = g_io_channel_read_chars(source, (gchar *)&sig, sizeof(int),
208 &bytes_read, &error);
209 if (stat != G_IO_STATUS_NORMAL) {
210 purple_debug_error("sighandler", "Signal callback failed to read "
211 "from signal socket: %s", error->message);
212 purple_core_quit();
213 return FALSE;
214 }
215
216 switch (sig) {
217 #if defined(USE_GSTREAMER) && !defined(GST_CAN_DISABLE_FORKING)
218 /* By default, gstreamer forks when you initialize it, and waitpids for the
219 * child. But if libpurple reaps the child rather than leaving it to
220 * gstreamer, gstreamer's initialization fails. So, we wait a second before
221 * reaping child processes, to give gst a chance to reap it if it wants to.
222 *
223 * This is not needed in later gstreamers, which let us disable the forking.
224 * And, it breaks the world on some Real Unices.
225 */
226 case SIGCHLD:
227 /* Restore signal catching */
228 signal(SIGCHLD, sighandler);
229 alarm(1);
230 break;
231 case SIGALRM:
232 #else
233 case SIGCHLD:
234 #endif
235 clean_pid();
236 /* Restore signal catching */
237 signal(SIGCHLD, sighandler);
238 break;
239 default:
240 purple_debug_warning("sighandler", "Caught signal %d\n", sig);
241 purple_core_quit();
242 }
243
244 return TRUE;
245 }
246 #endif
247
248 static int
ui_main(void)249 ui_main(void)
250 {
251 #ifndef _WIN32
252 GList *icons = NULL;
253 GdkPixbuf *icon = NULL;
254 char *icon_path;
255 gsize i;
256 struct {
257 const char *dir;
258 const char *filename;
259 } icon_sizes[] = {
260 {"16x16", "pidgin.png"},
261 {"24x24", "pidgin.png"},
262 {"32x32", "pidgin.png"},
263 {"48x48", "pidgin.png"},
264 {"scalable", "pidgin.svg"}
265 };
266
267 #endif
268
269 pidgin_themes_init();
270
271 pidgin_blist_setup_sort_methods();
272
273 #ifndef _WIN32
274 /* use the nice PNG icon for all the windows */
275 for(i=0; i<G_N_ELEMENTS(icon_sizes); i++) {
276 icon_path = g_build_filename(DATADIR, "icons", "hicolor", icon_sizes[i].dir, "apps", icon_sizes[i].filename, NULL);
277 icon = pidgin_pixbuf_new_from_file(icon_path);
278 g_free(icon_path);
279 if (icon) {
280 icons = g_list_append(icons,icon);
281 } else {
282 purple_debug_error("ui_main",
283 "Failed to load the default window icon (%spx version)!\n", icon_sizes[i].dir);
284 }
285 }
286 if(NULL == icons) {
287 purple_debug_error("ui_main", "Unable to load any size of default window icon!\n");
288 } else {
289 gtk_window_set_default_icon_list(icons);
290
291 g_list_free_full(icons, (GDestroyNotify)g_object_unref);
292 }
293 #endif
294
295 return 0;
296 }
297
298 static void
debug_init(void)299 debug_init(void)
300 {
301 purple_debug_set_ui_ops(pidgin_debug_get_ui_ops());
302 pidgin_debug_init();
303 }
304
305 static void
pidgin_ui_init(void)306 pidgin_ui_init(void)
307 {
308 pidgin_stock_init();
309
310 /* Set the UI operation structures. */
311 purple_accounts_set_ui_ops(pidgin_accounts_get_ui_ops());
312 purple_xfers_set_ui_ops(pidgin_xfers_get_ui_ops());
313 purple_blist_set_ui_ops(pidgin_blist_get_ui_ops());
314 purple_notify_set_ui_ops(pidgin_notify_get_ui_ops());
315 purple_privacy_set_ui_ops(pidgin_privacy_get_ui_ops());
316 purple_request_set_ui_ops(pidgin_request_get_ui_ops());
317 purple_sound_set_ui_ops(pidgin_sound_get_ui_ops());
318 purple_connections_set_ui_ops(pidgin_connections_get_ui_ops());
319 purple_whiteboard_set_ui_ops(pidgin_whiteboard_get_ui_ops());
320 #if defined(USE_SCREENSAVER) || defined(HAVE_IOKIT)
321 purple_idle_set_ui_ops(pidgin_idle_get_ui_ops());
322 #endif
323
324 pidgin_account_init();
325 pidgin_connection_init();
326 pidgin_blist_init();
327 pidgin_status_init();
328 pidgin_conversations_init();
329 pidgin_pounces_init();
330 pidgin_privacy_init();
331 pidgin_xfers_init();
332 pidgin_roomlist_init();
333 pidgin_log_init();
334 pidgin_docklet_init();
335 pidgin_smileys_init();
336 pidgin_utils_init();
337 pidgin_medias_init();
338 pidgin_notify_init();
339 }
340
341 static GHashTable *ui_info = NULL;
342
343 static void
pidgin_quit(void)344 pidgin_quit(void)
345 {
346 #ifdef USE_SM
347 /* unplug */
348 pidgin_session_end();
349 #endif
350
351 /* Uninit */
352 pidgin_utils_uninit();
353 pidgin_notify_uninit();
354 pidgin_smileys_uninit();
355 pidgin_conversations_uninit();
356 pidgin_status_uninit();
357 pidgin_docklet_uninit();
358 pidgin_blist_uninit();
359 pidgin_connection_uninit();
360 pidgin_account_uninit();
361 pidgin_xfers_uninit();
362 pidgin_debug_uninit();
363
364 if(NULL != ui_info)
365 g_hash_table_destroy(ui_info);
366
367 /* and end it all... */
368 gtk_main_quit();
369 }
370
pidgin_ui_get_info(void)371 static GHashTable *pidgin_ui_get_info(void)
372 {
373 if(NULL == ui_info) {
374 ui_info = g_hash_table_new(g_str_hash, g_str_equal);
375
376 g_hash_table_insert(ui_info, "name", (char*)PIDGIN_NAME);
377 g_hash_table_insert(ui_info, "version", VERSION);
378 g_hash_table_insert(ui_info, "website", "http://pidgin.im");
379 g_hash_table_insert(ui_info, "dev_website", "http://developer.pidgin.im");
380 g_hash_table_insert(ui_info, "client_type", "pc");
381
382 /*
383 * prpl-aim-clientkey is a DevID (or "client key") for Pidgin, given to
384 * us by AOL in September 2016. prpl-icq-clientkey is also a client key
385 * for Pidgin, owned by the AIM account "markdoliner." Please don't use
386 * either for other applications. Instead, you can either not specify a
387 * client key, in which case the default "libpurple" key will be used,
388 * or you can try to register your own at the AIM or ICQ web sites
389 * (although this functionality was removed at some point, it's possible
390 * it has been re-added).
391 */
392 g_hash_table_insert(ui_info, "prpl-aim-clientkey", "do1UCeb5gNqxB1S1");
393 g_hash_table_insert(ui_info, "prpl-icq-clientkey", "ma1cSASNCKFtrdv9");
394
395 /*
396 * prpl-aim-distid is a distID for Pidgin, given to us by AOL in
397 * September 2016. prpl-icq-distid is also a distID for Pidgin, given
398 * to us by AOL. Please don't use either for other applications.
399 * Instead, you can just not specify a distID and libpurple will use a
400 * default.
401 */
402 g_hash_table_insert(ui_info, "prpl-aim-distid", GINT_TO_POINTER(1715));
403 g_hash_table_insert(ui_info, "prpl-icq-distid", GINT_TO_POINTER(1550));
404 }
405
406 return ui_info;
407 }
408
409 static PurpleCoreUiOps core_ops =
410 {
411 pidgin_prefs_init,
412 debug_init,
413 pidgin_ui_init,
414 pidgin_quit,
415 pidgin_ui_get_info,
416 NULL,
417 NULL,
418 NULL
419 };
420
421 static PurpleCoreUiOps *
pidgin_core_get_ui_ops(void)422 pidgin_core_get_ui_ops(void)
423 {
424 return &core_ops;
425 }
426
427 static void
show_usage(const char * name,gboolean terse)428 show_usage(const char *name, gboolean terse)
429 {
430 char *text;
431
432 if (terse) {
433 text = g_strdup_printf(_("%s %s. Try `%s -h' for more information.\n"), PIDGIN_NAME, DISPLAY_VERSION, name);
434 } else {
435 GString *str = g_string_new(NULL);
436 g_string_append_printf(str, "%s %s\n", PIDGIN_NAME, DISPLAY_VERSION);
437 g_string_append_printf(str, _("Usage: %s [OPTION]...\n\n"), name);
438 g_string_append_printf(str, " -c, --config=%s %s\n",
439 _("DIR"), _("use DIR for config files"));
440 g_string_append_printf(str, " -d, --debug %s\n",
441 _("print debugging messages to stdout"));
442 g_string_append_printf(str, " -f, --force-online %s\n",
443 _("force online, regardless of network status"));
444 g_string_append_printf(str, " -h, --help %s\n",
445 _("display this help and exit"));
446 g_string_append_printf(str, " -m, --multiple %s\n",
447 _("allow multiple instances"));
448 g_string_append_printf(str, " -n, --nologin %s\n",
449 _("don't automatically login"));
450 g_string_append_printf(str, " -l, --login[=%s] %s\n",
451 _("NAME"),
452 _("enable specified account(s) (optional argument NAME\n"
453 " "
454 "specifies account(s) to use, separated by commas.\n"
455 " "
456 "Without this only the first account will be enabled)."));
457 #ifndef WIN32
458 g_string_append_printf(str, " --display=DISPLAY %s\n",
459 _("X display to use"));
460 #endif /* !WIN32 */
461 g_string_append_printf(str, " -v, --version %s\n",
462 _("display the current version and exit"));
463 text = g_string_free(str, FALSE);
464 }
465
466 purple_print_utf8_to_console(stdout, text);
467 g_free(text);
468 }
469
470 /* FUCKING GET ME A TOWEL! */
471 #ifdef _WIN32
472 /* suppress gcc "no previous prototype" warning */
473 int __cdecl pidgin_main(HINSTANCE hint, int argc, char *argv[]);
pidgin_main(HINSTANCE hint,int argc,char * argv[])474 int __cdecl pidgin_main(HINSTANCE hint, int argc, char *argv[])
475 #else
476 int main(int argc, char *argv[])
477 #endif
478 {
479 gboolean opt_force_online = FALSE;
480 gboolean opt_help = FALSE;
481 gboolean opt_login = FALSE;
482 gboolean opt_nologin = FALSE;
483 gboolean opt_version = FALSE;
484 gboolean opt_si = TRUE; /* Check for single instance? */
485 char *opt_config_dir_arg = NULL;
486 char *opt_login_arg = NULL;
487 char *opt_session_arg = NULL;
488 char *search_path;
489 GList *accounts;
490 #ifdef HAVE_SIGNAL_H
491 int sig_indx; /* for setting up signal catching */
492 sigset_t sigset;
493 char errmsg[BUFSIZ];
494 GIOChannel *signal_channel;
495 GIOStatus signal_status;
496 guint signal_channel_watcher;
497 #ifndef DEBUG
498 char *segfault_message_tmp;
499 #endif
500 GError *error;
501 #endif
502 int opt;
503 gboolean gui_check;
504 gboolean debug_enabled;
505 gboolean migration_failed = FALSE;
506 GList *active_accounts;
507
508 struct option long_options[] = {
509 {"config", required_argument, NULL, 'c'},
510 {"debug", no_argument, NULL, 'd'},
511 {"force-online", no_argument, NULL, 'f'},
512 {"help", no_argument, NULL, 'h'},
513 {"login", optional_argument, NULL, 'l'},
514 {"multiple", no_argument, NULL, 'm'},
515 {"nologin", no_argument, NULL, 'n'},
516 {"session", required_argument, NULL, 's'},
517 {"version", no_argument, NULL, 'v'},
518 {"display", required_argument, NULL, 'D'},
519 {"sync", no_argument, NULL, 'S'},
520 {0, 0, 0, 0}
521 };
522
523 #ifdef DEBUG
524 debug_enabled = TRUE;
525 #else
526 debug_enabled = FALSE;
527 #endif
528
529 #if !GLIB_CHECK_VERSION(2, 32, 0)
530 /* GLib threading system is automaticaly initialized since 2.32.
531 * For earlier versions, it have to be initialized before calling any
532 * Glib or GTK+ functions.
533 */
534 g_thread_init(NULL);
535 #endif
536
537 g_set_prgname("Pidgin");
538
539 #ifdef ENABLE_NLS
540 bindtextdomain(PACKAGE, LOCALEDIR);
541 bind_textdomain_codeset(PACKAGE, "UTF-8");
542 textdomain(PACKAGE);
543 #endif
544
545 #ifdef HAVE_SETLOCALE
546 /* Locale initialization is not complete here. See gtk_init_check() */
547 setlocale(LC_ALL, "");
548 #endif
549
550 #ifdef HAVE_SIGNAL_H
551
552 #ifndef DEBUG
553 /* We translate this here in case the crash breaks gettext. */
554 segfault_message_tmp = g_strdup_printf(_(
555 "%s %s has segfaulted and attempted to dump a core file.\n"
556 "This is a bug in the software and has happened through\n"
557 "no fault of your own.\n\n"
558 "If you can reproduce the crash, please notify the developers\n"
559 "by reporting a bug at:\n"
560 "%ssimpleticket/\n\n"
561 "Please make sure to specify what you were doing at the time\n"
562 "and post the backtrace from the core file. If you do not know\n"
563 "how to get the backtrace, please read the instructions at\n"
564 "%swiki/GetABacktrace\n"),
565 PIDGIN_NAME, DISPLAY_VERSION, PURPLE_DEVEL_WEBSITE, PURPLE_DEVEL_WEBSITE
566 );
567
568 /* we have to convert the message (UTF-8 to console
569 charset) early because after a segmentation fault
570 it's not a good practice to allocate memory */
571 error = NULL;
572 segfault_message = g_locale_from_utf8(segfault_message_tmp,
573 -1, NULL, NULL, &error);
574 if (segfault_message != NULL) {
575 g_free(segfault_message_tmp);
576 }
577 else {
578 /* use 'segfault_message_tmp' (UTF-8) as a fallback */
579 g_warning("%s\n", error->message);
580 g_error_free(error);
581 segfault_message = segfault_message_tmp;
582 }
583 #else
584 /* Don't mark this for translation. */
585 segfault_message = g_strdup(
586 "Hi, user. We need to talk.\n"
587 "I think something's gone wrong here. It's probably my fault.\n"
588 "No, really, it's not you... it's me... no no no, I think we get along well\n"
589 "it's just that.... well, I want to see other people. I... what?!? NO! I \n"
590 "haven't been cheating on you!! How many times do you want me to tell you?! And\n"
591 "for the last time, it's just a rash!\n"
592 );
593 #endif
594
595 /*
596 * Create a socket pair for receiving unix signals from a signal
597 * handler.
598 */
599 if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sockets) < 0) {
600 perror("Failed to create sockets for GLib signal handling");
601 exit(1);
602 }
603 signal_channel = g_io_channel_unix_new(signal_sockets[1]);
604
605 /*
606 * Set the channel encoding to raw binary instead of the default of
607 * UTF-8, because we'll be sending integers across instead of strings.
608 */
609 error = NULL;
610 signal_status = g_io_channel_set_encoding(signal_channel, NULL, &error);
611 if (signal_status != G_IO_STATUS_NORMAL) {
612 fprintf(stderr, "Failed to set the signal channel to raw "
613 "binary: %s", error->message);
614 exit(1);
615 }
616 signal_channel_watcher = g_io_add_watch(signal_channel, G_IO_IN, mainloop_sighandler, NULL);
617 g_io_channel_unref(signal_channel);
618
619 /* Let's not violate any PLA's!!!! */
620 /* jseymour: whatever the fsck that means */
621 /* Robot101: for some reason things like gdm like to block *
622 * useful signals like SIGCHLD, so we unblock all the ones we *
623 * declare a handler for. thanks JSeymour and Vann. */
624 if (sigemptyset(&sigset)) {
625 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't initialise empty signal set");
626 perror(errmsg);
627 }
628 for(sig_indx = 0; catch_sig_list[sig_indx] != -1; ++sig_indx) {
629 if(signal(catch_sig_list[sig_indx], sighandler) == SIG_ERR) {
630 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't set signal %d for catching",
631 catch_sig_list[sig_indx]);
632 perror(errmsg);
633 }
634 if(sigaddset(&sigset, catch_sig_list[sig_indx])) {
635 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't include signal %d for unblocking",
636 catch_sig_list[sig_indx]);
637 perror(errmsg);
638 }
639 }
640 for(sig_indx = 0; ignore_sig_list[sig_indx] != -1; ++sig_indx) {
641 if(signal(ignore_sig_list[sig_indx], SIG_IGN) == SIG_ERR) {
642 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't set signal %d to ignore",
643 ignore_sig_list[sig_indx]);
644 perror(errmsg);
645 }
646 }
647
648 if (sigprocmask(SIG_UNBLOCK, &sigset, NULL)) {
649 snprintf(errmsg, sizeof(errmsg), "Warning: couldn't unblock signals");
650 perror(errmsg);
651 }
652 #endif
653
654 /* scan command-line options */
655 opterr = 1;
656 while ((opt = getopt_long(argc, argv,
657 #ifndef _WIN32
658 "c:dfhmnl::s:v",
659 #else
660 "c:dfhmnl::v",
661 #endif
662 long_options, NULL)) != -1) {
663 switch (opt) {
664 case 'c': /* config dir */
665 g_free(opt_config_dir_arg);
666 opt_config_dir_arg = g_strdup(optarg);
667 break;
668 case 'd': /* debug */
669 debug_enabled = TRUE;
670 break;
671 case 'f': /* force-online */
672 opt_force_online = TRUE;
673 break;
674 case 'h': /* help */
675 opt_help = TRUE;
676 break;
677 case 'n': /* no autologin */
678 opt_nologin = TRUE;
679 break;
680 case 'l': /* login, option username */
681 opt_login = TRUE;
682 g_free(opt_login_arg);
683 if (optarg != NULL)
684 opt_login_arg = g_strdup(optarg);
685 break;
686 case 's': /* use existing session ID */
687 g_free(opt_session_arg);
688 opt_session_arg = g_strdup(optarg);
689 break;
690 case 'v': /* version */
691 opt_version = TRUE;
692 break;
693 case 'm': /* do not ensure single instance. */
694 opt_si = FALSE;
695 break;
696 case 'D': /* --display */
697 case 'S': /* --sync */
698 /* handled by gtk_init_check below */
699 break;
700 case '?': /* show terse help */
701 default:
702 show_usage(argv[0], TRUE);
703 #ifdef HAVE_SIGNAL_H
704 g_free(segfault_message);
705 #endif
706 return 0;
707 break;
708 }
709 }
710
711 /* show help message */
712 if (opt_help) {
713 show_usage(argv[0], FALSE);
714 #ifdef HAVE_SIGNAL_H
715 g_free(segfault_message);
716 #endif
717 return 0;
718 }
719 /* show version message */
720 if (opt_version) {
721 printf("%s %s (libpurple %s)\n", PIDGIN_NAME, DISPLAY_VERSION,
722 purple_core_get_version());
723 #ifdef HAVE_SIGNAL_H
724 g_free(segfault_message);
725 #endif
726 return 0;
727 }
728
729 /* set a user-specified config directory */
730 if (opt_config_dir_arg != NULL) {
731 purple_util_set_user_dir(opt_config_dir_arg);
732 }
733
734 /*
735 * We're done piddling around with command line arguments.
736 * Fire up this baby.
737 */
738
739 purple_debug_set_enabled(debug_enabled);
740
741 /* If we're using a custom configuration directory, we
742 * do NOT want to migrate, or weird things will happen. */
743 if (opt_config_dir_arg == NULL)
744 {
745 if (!purple_core_migrate())
746 {
747 migration_failed = TRUE;
748 }
749 }
750
751 search_path = g_build_filename(purple_user_dir(), "gtkrc-2.0", NULL);
752 gtk_rc_add_default_file(search_path);
753 g_free(search_path);
754
755 #if defined(HAVE_X11) && defined(USE_VV)
756 /* GStreamer elements such as ximagesrc may require this */
757 XInitThreads();
758 #endif
759
760 gui_check = gtk_init_check(&argc, &argv);
761 if (!gui_check) {
762 char *display = gdk_get_display();
763
764 printf("%s %s\n", PIDGIN_NAME, DISPLAY_VERSION);
765
766 g_warning("cannot open display: %s", display ? display : "unset");
767 g_free(display);
768 #ifdef HAVE_SIGNAL_H
769 g_free(segfault_message);
770 #endif
771
772 return 1;
773 }
774
775 g_set_application_name(PIDGIN_NAME);
776
777 #ifdef _WIN32
778 winpidgin_init(hint);
779 #endif
780
781 if (migration_failed)
782 {
783 char *old = g_strconcat(purple_home_dir(),
784 G_DIR_SEPARATOR_S ".gaim", NULL);
785 const char *text = _(
786 "%s encountered errors migrating your settings "
787 "from %s to %s. Please investigate and complete the "
788 "migration by hand. Please report this error at http://developer.pidgin.im");
789 GtkWidget *dialog;
790
791 dialog = gtk_message_dialog_new(NULL,
792 0,
793 GTK_MESSAGE_ERROR,
794 GTK_BUTTONS_CLOSE,
795 text, PIDGIN_NAME,
796 old, purple_user_dir());
797 g_free(old);
798
799 g_signal_connect_swapped(dialog, "response",
800 G_CALLBACK(gtk_main_quit), NULL);
801
802 gtk_widget_show_all(dialog);
803
804 gtk_main();
805
806 #ifdef HAVE_SIGNAL_H
807 g_free(segfault_message);
808 #endif
809 return 0;
810 }
811
812 purple_core_set_ui_ops(pidgin_core_get_ui_ops());
813 purple_eventloop_set_ui_ops(pidgin_eventloop_get_ui_ops());
814
815 /*
816 * Set plugin search directories. Give priority to the plugins
817 * in user's home directory.
818 */
819 search_path = g_build_filename(purple_user_dir(), "plugins", NULL);
820 if (g_mkdir(search_path, S_IRUSR | S_IWUSR | S_IXUSR) != 0 && errno != EEXIST)
821 fprintf(stderr, "Couldn't create plugins dir\n");
822 purple_plugins_add_search_path(search_path);
823 g_free(search_path);
824 purple_plugins_add_search_path(LIBDIR);
825
826 if (!purple_core_init(PIDGIN_UI)) {
827 fprintf(stderr,
828 "Initialization of the libpurple core failed. Dumping core.\n"
829 "Please report this!\n");
830 #ifdef HAVE_SIGNAL_H
831 g_free(segfault_message);
832 #endif
833 abort();
834 }
835
836 if (opt_si && !purple_core_ensure_single_instance()) {
837 #ifdef HAVE_DBUS
838 DBusConnection *conn = purple_dbus_get_connection();
839 DBusMessage *message = dbus_message_new_method_call(DBUS_SERVICE_PURPLE, DBUS_PATH_PURPLE,
840 DBUS_INTERFACE_PURPLE, "PurpleBlistSetVisible");
841 gboolean tr = TRUE;
842 dbus_message_append_args(message, DBUS_TYPE_INT32, &tr, DBUS_TYPE_INVALID);
843 dbus_connection_send_with_reply_and_block(conn, message, -1, NULL);
844 dbus_message_unref(message);
845 #endif
846 gdk_notify_startup_complete();
847 purple_core_quit();
848 g_printerr(_("Exiting because another libpurple client is already running.\n"));
849 #ifdef HAVE_SIGNAL_H
850 g_free(segfault_message);
851 #endif
852 return 0;
853 }
854
855 /* TODO: Move blist loading into purple_blist_init() */
856 purple_set_blist(purple_blist_new());
857 purple_blist_load();
858
859 /* load plugins we had when we quit */
860 purple_plugins_load_saved(PIDGIN_PREFS_ROOT "/plugins/loaded");
861
862 /* TODO: Move pounces loading into purple_pounces_init() */
863 purple_pounces_load();
864
865 ui_main();
866
867 #ifdef USE_SM
868 pidgin_session_init(argv[0], opt_session_arg, opt_config_dir_arg);
869 #endif
870 if (opt_session_arg != NULL) {
871 g_free(opt_session_arg);
872 opt_session_arg = NULL;
873 }
874 if (opt_config_dir_arg != NULL) {
875 g_free(opt_config_dir_arg);
876 opt_config_dir_arg = NULL;
877 }
878
879 /* This needs to be before purple_blist_show() so the
880 * statusbox gets the forced online status. */
881 if (opt_force_online)
882 purple_network_force_online();
883
884 /*
885 * We want to show the blist early in the init process so the
886 * user feels warm and fuzzy (not cold and prickley).
887 */
888 purple_blist_show();
889
890 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"))
891 pidgin_debug_window_show();
892
893 if (opt_login) {
894 /* disable all accounts */
895 for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
896 PurpleAccount *account = accounts->data;
897 purple_account_set_enabled(account, PIDGIN_UI, FALSE);
898 }
899 /* honor the startup status preference */
900 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
901 purple_savedstatus_activate(purple_savedstatus_get_startup());
902 /* now enable the requested ones */
903 dologin_named(opt_login_arg);
904 if (opt_login_arg != NULL) {
905 g_free(opt_login_arg);
906 opt_login_arg = NULL;
907 }
908 } else if (opt_nologin) {
909 /* Set all accounts to "offline" */
910 PurpleSavedStatus *saved_status;
911
912 /* If we've used this type+message before, lookup the transient status */
913 saved_status = purple_savedstatus_find_transient_by_type_and_message(
914 PURPLE_STATUS_OFFLINE, NULL);
915
916 /* If this type+message is unique then create a new transient saved status */
917 if (saved_status == NULL)
918 saved_status = purple_savedstatus_new(NULL, PURPLE_STATUS_OFFLINE);
919
920 /* Set the status for each account */
921 purple_savedstatus_activate(saved_status);
922 } else {
923 /* Everything is good to go--sign on already */
924 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
925 purple_savedstatus_activate(purple_savedstatus_get_startup());
926 purple_accounts_restore_current_statuses();
927 }
928
929 if ((active_accounts = purple_accounts_get_all_active()) == NULL)
930 {
931 pidgin_accounts_window_show();
932 }
933 else
934 {
935 g_list_free(active_accounts);
936 }
937
938 /* GTK clears the notification for us when opening the first window,
939 * but we may have launched with only a status icon, so clear the it
940 * just in case. */
941 gdk_notify_startup_complete();
942
943 #ifdef _WIN32
944 winpidgin_post_init();
945 #endif
946
947 gtk_main();
948
949 #ifdef HAVE_SIGNAL_H
950 g_free(segfault_message);
951 g_source_remove(signal_channel_watcher);
952 close(signal_sockets[0]);
953 close(signal_sockets[1]);
954 #endif
955
956 #ifdef _WIN32
957 winpidgin_cleanup();
958 #endif
959
960 return 0;
961 }
962