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
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
28 #include "conversation.h"
31 #include "eventloop.h"
42 #include "whiteboard.h"
44 #include "gtkaccount.h"
49 #include "gtkdialogs.h"
50 #include "gtkdocklet.h"
51 #include "gtkeventloop.h"
55 #include "gtknotify.h"
56 #include "gtkplugin.h"
57 #include "gtkpounce.h"
59 #include "gtkprivacy.h"
60 #include "gtkrequest.h"
61 #include "gtkroomlist.h"
62 #include "gtksavedstatuses.h"
63 #include "gtksession.h"
65 #include "gtkthemes.h"
67 #include "pidginstock.h"
68 #include "gtkwhiteboard.h"
76 #ifdef HAVE_STARTUP_NOTIFICATION
77 # define SN_API_NOT_YET_FROZEN
78 # include <libsn/sn-launchee.h>
79 # include <gdk/gdkx.h>
84 #ifdef HAVE_STARTUP_NOTIFICATION
85 static SnLauncheeContext
*sn_context
= NULL
;
86 static SnDisplay
*sn_display
= NULL
;
92 * Lists of signals we wish to catch and those we wish to ignore.
93 * Each list terminated with -1
95 static int catch_sig_list
[] = {
106 static int ignore_sig_list
[] = {
113 dologin_named(const char *name
)
115 PurpleAccount
*account
;
119 if (name
!= NULL
) { /* list of names given */
120 names
= g_strsplit(name
, ",", 64);
121 for (i
= 0; names
[i
] != NULL
; i
++) {
122 account
= purple_accounts_find(names
[i
], NULL
);
123 if (account
!= NULL
) { /* found a user */
124 purple_account_set_enabled(account
, PIDGIN_UI
, TRUE
);
128 } else { /* no name given, use the first account */
131 accounts
= purple_accounts_get_all();
132 if (accounts
!= NULL
)
134 account
= (PurpleAccount
*)accounts
->data
;
135 purple_account_set_enabled(account
, PIDGIN_UI
, TRUE
);
141 static void sighandler(int sig
);
144 * Reap all our dead children. Sometimes libpurple forks off a separate
145 * process to do some stuff. When that process exits we are
146 * informed about it so that we can call waitpid() and let it
147 * stop being a zombie.
149 * We used to do this immediately when our signal handler was
150 * called, but because of GStreamer we now wait one second before
151 * reaping anything. Why? For some reason GStreamer fork()s
152 * during their initialization process. I don't understand why...
153 * but they do it, and there's nothing we can do about it.
155 * Anyway, so then GStreamer waits for its child to die and then
156 * it continues with the initialization process. This means that
157 * we have a race condition where GStreamer is waitpid()ing for its
158 * child to die and we're catching the SIGCHLD signal. If GStreamer
159 * is awarded the zombied process then everything is ok. But if libpurple
160 * reaps the zombie process then the GStreamer initialization sequence
163 * So the ugly solution is to wait a second to give GStreamer time to
166 * GStreamer 0.10.10 and newer have a gst_register_fork_set_enabled()
167 * function that can be called by applications to disable forking
168 * during initialization. But it's not in 0.10.0, so we shouldn't
171 * All of this child process reaping stuff is currently only used for
172 * processes that were forked to play sounds. It's not needed for
173 * forked DNS child, which have their own waitpid() call. It might
174 * be wise to move this code into gtksound.c.
183 pid
= waitpid(-1, &status
, WNOHANG
);
184 } while (pid
!= 0 && pid
!= (pid_t
)-1);
186 if ((pid
== (pid_t
) - 1) && (errno
!= ECHILD
)) {
188 snprintf(errmsg
, BUFSIZ
, "Warning: waitpid() returned %d", pid
);
192 /* Restore signal catching */
193 signal(SIGALRM
, sighandler
);
196 char *segfault_message
;
203 purple_debug_warning("sighandler", "Caught signal %d\n", sig
);
204 purple_connections_disconnect_all();
207 fprintf(stderr
, "%s", segfault_message
);
211 /* Restore signal catching */
212 signal(SIGCHLD
, sighandler
);
219 purple_debug_warning("sighandler", "Caught signal %d\n", sig
);
220 purple_connections_disconnect_all();
222 purple_plugins_unload_all();
224 if (gtk_main_level())
236 GdkPixbuf
*icon
= NULL
;
241 const char *filename
;
243 {"16x16", "pidgin.png"},
244 {"24x24", "pidgin.png"},
245 {"32x32", "pidgin.png"},
246 {"48x48", "pidgin.png"},
247 {"scalable", "pidgin.svg"}
252 pidgin_themes_init();
254 pidgin_blist_setup_sort_methods();
257 /* use the nice PNG icon for all the windows */
258 for(i
=0; i
<G_N_ELEMENTS(icon_sizes
); i
++) {
259 icon_path
= g_build_filename(DATADIR
, "icons", "hicolor", icon_sizes
[i
].dir
, "apps", icon_sizes
[i
].filename
, NULL
);
260 icon
= gdk_pixbuf_new_from_file(icon_path
, NULL
);
263 icons
= g_list_append(icons
,icon
);
265 purple_debug_error("ui_main",
266 "Failed to load the default window icon (%spx version)!\n", icon_sizes
[i
]);
270 purple_debug_error("ui_main", "Unable to load any size of default window icon!\n");
272 gtk_window_set_default_icon_list(icons
);
274 g_list_foreach(icons
, (GFunc
)g_object_unref
, NULL
);
285 purple_debug_set_ui_ops(pidgin_debug_get_ui_ops());
292 /* Set the UI operation structures. */
293 purple_accounts_set_ui_ops(pidgin_accounts_get_ui_ops());
294 purple_xfers_set_ui_ops(pidgin_xfers_get_ui_ops());
295 purple_blist_set_ui_ops(pidgin_blist_get_ui_ops());
296 purple_notify_set_ui_ops(pidgin_notify_get_ui_ops());
297 purple_privacy_set_ui_ops(pidgin_privacy_get_ui_ops());
298 purple_request_set_ui_ops(pidgin_request_get_ui_ops());
299 purple_sound_set_ui_ops(pidgin_sound_get_ui_ops());
300 purple_connections_set_ui_ops(pidgin_connections_get_ui_ops());
301 purple_whiteboard_set_ui_ops(pidgin_whiteboard_get_ui_ops());
302 #ifdef USE_SCREENSAVER
303 purple_idle_set_ui_ops(pidgin_idle_get_ui_ops());
307 pidgin_account_init();
308 pidgin_connection_init();
310 pidgin_status_init();
311 pidgin_conversations_init();
312 pidgin_pounces_init();
313 pidgin_privacy_init();
315 pidgin_roomlist_init();
317 pidgin_docklet_init();
320 static GHashTable
*ui_info
= NULL
;
327 pidgin_session_end();
330 /* Save the plugins we have loaded for next time. */
331 pidgin_plugins_save();
334 pidgin_conversations_uninit();
335 pidgin_status_uninit();
336 pidgin_docklet_uninit();
337 pidgin_blist_uninit();
338 pidgin_connection_uninit();
339 pidgin_account_uninit();
340 pidgin_xfers_uninit();
341 pidgin_debug_uninit();
344 g_hash_table_destroy(ui_info
);
346 /* and end it all... */
350 static GHashTable
*pidgin_ui_get_info()
352 if(NULL
== ui_info
) {
353 ui_info
= g_hash_table_new(g_str_hash
, g_str_equal
);
355 g_hash_table_insert(ui_info
, "name", (char*)PIDGIN_NAME
);
356 g_hash_table_insert(ui_info
, "version", VERSION
);
362 static PurpleCoreUiOps core_ops
=
374 static PurpleCoreUiOps
*
375 pidgin_core_get_ui_ops(void)
381 show_usage(const char *name
, gboolean terse
)
386 text
= g_strdup_printf(_("%s %s. Try `%s -h' for more information.\n"), PIDGIN_NAME
, VERSION
, name
);
388 text
= g_strdup_printf(_("%s %s\n"
389 "Usage: %s [OPTION]...\n\n"
390 " -c, --config=DIR use DIR for config files\n"
391 " -d, --debug print debugging messages to stdout\n"
392 " -h, --help display this help and exit\n"
393 " -m, --multiple do not ensure single instance\n"
394 " -n, --nologin don't automatically login\n"
395 " -l, --login[=NAME] automatically login (optional argument NAME specifies\n"
396 " account(s) to use, separated by commas)\n"
397 " -v, --version display the current version and exit\n"), PIDGIN_NAME
, VERSION
, name
);
400 purple_print_utf8_to_console(stdout
, text
);
404 #ifdef HAVE_STARTUP_NOTIFICATION
406 sn_error_trap_push(SnDisplay
*display
, Display
*xdisplay
)
408 gdk_error_trap_push();
412 sn_error_trap_pop(SnDisplay
*display
, Display
*xdisplay
)
414 gdk_error_trap_pop();
418 startup_notification_complete(void)
422 xdisplay
= GDK_DISPLAY();
423 sn_display
= sn_display_new(xdisplay
,
427 sn_launchee_context_new_from_environment(sn_display
,
428 DefaultScreen(xdisplay
));
430 if (sn_context
!= NULL
)
432 sn_launchee_context_complete(sn_context
);
433 sn_launchee_context_unref(sn_context
);
435 sn_display_unref(sn_display
);
438 #endif /* HAVE_STARTUP_NOTIFICATION */
440 /* FUCKING GET ME A TOWEL! */
442 /* suppress gcc "no previous prototype" warning */
443 int pidgin_main(HINSTANCE hint
, int argc
, char *argv
[]);
444 int pidgin_main(HINSTANCE hint
, int argc
, char *argv
[])
446 int main(int argc
, char *argv
[])
449 gboolean opt_help
= FALSE
;
450 gboolean opt_login
= FALSE
;
451 gboolean opt_nologin
= FALSE
;
452 gboolean opt_version
= FALSE
;
453 gboolean opt_si
= TRUE
; /* Check for single instance? */
454 char *opt_config_dir_arg
= NULL
;
455 char *opt_login_arg
= NULL
;
456 char *opt_session_arg
= NULL
;
460 int sig_indx
; /* for setting up signal catching */
462 RETSIGTYPE (*prev_sig_disp
)(int);
465 char *segfault_message_tmp
;
466 GError
*error
= NULL
;
471 gboolean debug_enabled
;
472 gboolean migration_failed
= FALSE
;
473 GList
*active_accounts
;
475 struct option long_options
[] = {
476 {"config", required_argument
, NULL
, 'c'},
477 {"debug", no_argument
, NULL
, 'd'},
478 {"help", no_argument
, NULL
, 'h'},
479 {"login", optional_argument
, NULL
, 'l'},
480 {"multiple", no_argument
, NULL
, 'm'},
481 {"nologin", no_argument
, NULL
, 'n'},
482 {"session", required_argument
, NULL
, 's'},
483 {"version", no_argument
, NULL
, 'v'},
488 debug_enabled
= TRUE
;
490 debug_enabled
= FALSE
;
493 /* This is the first Glib function call. Make sure to initialize GThread bfeore then */
497 bindtextdomain(PACKAGE
, LOCALEDIR
);
498 bind_textdomain_codeset(PACKAGE
, "UTF-8");
502 #ifdef HAVE_SETLOCALE
503 /* Locale initialization is not complete here. See gtk_init_check() */
504 setlocale(LC_ALL
, "");
510 /* We translate this here in case the crash breaks gettext. */
511 segfault_message_tmp
= g_strdup_printf(_(
512 "%s has segfaulted and attempted to dump a core file.\n"
513 "This is a bug in the software and has happened through\n"
514 "no fault of your own.\n\n"
515 "If you can reproduce the crash, please notify the developers\n"
516 "by reporting a bug at:\n"
517 "%ssimpleticket/\n\n"
518 "Please make sure to specify what you were doing at the time\n"
519 "and post the backtrace from the core file. If you do not know\n"
520 "how to get the backtrace, please read the instructions at\n"
521 "%swiki/GetABacktrace\n\n"
522 "If you need further assistance, please IM either SeanEgn or \n"
523 "LSchiere (via AIM). Contact information for Sean and Luke \n"
524 "on other protocols is at\n"
525 "%swiki/DeveloperPages\n"),
526 PIDGIN_NAME
, PURPLE_DEVEL_WEBSITE
, PURPLE_DEVEL_WEBSITE
, PURPLE_DEVEL_WEBSITE
529 /* we have to convert the message (UTF-8 to console
530 charset) early because after a segmentation fault
531 it's not a good practice to allocate memory */
532 segfault_message
= g_locale_from_utf8(segfault_message_tmp
,
533 -1, NULL
, NULL
, &error
);
534 if (segfault_message
!= NULL
) {
535 g_free(segfault_message_tmp
);
538 /* use 'segfault_message_tmp' (UTF-8) as a fallback */
539 g_warning("%s\n", error
->message
);
541 segfault_message
= segfault_message_tmp
;
544 /* Don't mark this for translation. */
545 segfault_message
= g_strdup(
546 "Hi, user. We need to talk.\n"
547 "I think something's gone wrong here. It's probably my fault.\n"
548 "No, really, it's not you... it's me... no no no, I think we get along well\n"
549 "it's just that.... well, I want to see other people. I... what?!? NO! I \n"
550 "haven't been cheating on you!! How many times do you want me to tell you?! And\n"
551 "for the last time, it's just a rash!\n"
555 /* Let's not violate any PLA's!!!! */
556 /* jseymour: whatever the fsck that means */
557 /* Robot101: for some reason things like gdm like to block *
558 * useful signals like SIGCHLD, so we unblock all the ones we *
559 * declare a handler for. thanks JSeymour and Vann. */
560 if (sigemptyset(&sigset
)) {
561 snprintf(errmsg
, BUFSIZ
, "Warning: couldn't initialise empty signal set");
564 for(sig_indx
= 0; catch_sig_list
[sig_indx
] != -1; ++sig_indx
) {
565 if((prev_sig_disp
= signal(catch_sig_list
[sig_indx
], sighandler
)) == SIG_ERR
) {
566 snprintf(errmsg
, BUFSIZ
, "Warning: couldn't set signal %d for catching",
567 catch_sig_list
[sig_indx
]);
570 if(sigaddset(&sigset
, catch_sig_list
[sig_indx
])) {
571 snprintf(errmsg
, BUFSIZ
, "Warning: couldn't include signal %d for unblocking",
572 catch_sig_list
[sig_indx
]);
576 for(sig_indx
= 0; ignore_sig_list
[sig_indx
] != -1; ++sig_indx
) {
577 if((prev_sig_disp
= signal(ignore_sig_list
[sig_indx
], SIG_IGN
)) == SIG_ERR
) {
578 snprintf(errmsg
, BUFSIZ
, "Warning: couldn't set signal %d to ignore",
579 ignore_sig_list
[sig_indx
]);
584 if (sigprocmask(SIG_UNBLOCK
, &sigset
, NULL
)) {
585 snprintf(errmsg
, BUFSIZ
, "Warning: couldn't unblock signals");
590 /* scan command-line options */
592 while ((opt
= getopt_long(argc
, argv
,
598 long_options
, NULL
)) != -1) {
600 case 'c': /* config dir */
601 g_free(opt_config_dir_arg
);
602 opt_config_dir_arg
= g_strdup(optarg
);
604 case 'd': /* debug */
605 debug_enabled
= TRUE
;
610 case 'n': /* no autologin */
613 case 'l': /* login, option username */
615 g_free(opt_login_arg
);
617 opt_login_arg
= g_strdup(optarg
);
619 case 's': /* use existing session ID */
620 g_free(opt_session_arg
);
621 opt_session_arg
= g_strdup(optarg
);
623 case 'v': /* version */
626 case 'm': /* do not ensure single instance. */
629 case '?': /* show terse help */
631 show_usage(argv
[0], TRUE
);
633 g_free(segfault_message
);
640 /* show help message */
642 show_usage(argv
[0], FALSE
);
644 g_free(segfault_message
);
648 /* show version message */
650 printf("%s %s\n", PIDGIN_NAME
, VERSION
);
652 g_free(segfault_message
);
657 /* set a user-specified config directory */
658 if (opt_config_dir_arg
!= NULL
) {
659 purple_util_set_user_dir(opt_config_dir_arg
);
663 * We're done piddling around with command line arguments.
667 purple_debug_set_enabled(debug_enabled
);
669 /* If we're using a custom configuration directory, we
670 * do NOT want to migrate, or weird things will happen. */
671 if (opt_config_dir_arg
== NULL
)
673 if (!purple_core_migrate())
675 migration_failed
= TRUE
;
679 search_path
= g_build_filename(purple_user_dir(), "gtkrc-2.0", NULL
);
680 gtk_rc_add_default_file(search_path
);
683 gui_check
= gtk_init_check(&argc
, &argv
);
685 char *display
= gdk_get_display();
687 printf("%s %s\n", PIDGIN_NAME
, VERSION
);
689 g_warning("cannot open display: %s", display
? display
: "unset");
692 g_free(segfault_message
);
698 #if GLIB_CHECK_VERSION(2,2,0)
699 g_set_application_name(_("Pidgin"));
700 #endif /* glib-2.0 >= 2.2.0 */
703 winpidgin_init(hint
);
706 if (migration_failed
)
708 char *old
= g_strconcat(purple_home_dir(),
709 G_DIR_SEPARATOR_S
".gaim", NULL
);
710 const char *text
= _(
711 "%s encountered errors migrating your settings "
712 "from %s to %s. Please investigate and complete the "
713 "migration by hand. Please report this error at http://developer.pidgin.im");
716 dialog
= gtk_message_dialog_new(NULL
,
721 old
, purple_user_dir());
724 g_signal_connect_swapped(dialog
, "response",
725 G_CALLBACK(gtk_main_quit
), NULL
);
727 gtk_widget_show_all(dialog
);
732 g_free(segfault_message
);
737 purple_core_set_ui_ops(pidgin_core_get_ui_ops());
738 purple_eventloop_set_ui_ops(pidgin_eventloop_get_ui_ops());
741 * Set plugin search directories. Give priority to the plugins
742 * in user's home directory.
744 search_path
= g_build_filename(purple_user_dir(), "plugins", NULL
);
745 purple_plugins_add_search_path(search_path
);
747 purple_plugins_add_search_path(LIBDIR
);
749 if (!purple_core_init(PIDGIN_UI
)) {
751 "Initialization of the libpurple core failed. Dumping core.\n"
752 "Please report this!\n");
754 g_free(segfault_message
);
759 if (opt_si
&& !purple_core_ensure_single_instance()) {
762 g_free(segfault_message
);
767 /* TODO: Move blist loading into purple_blist_init() */
768 purple_set_blist(purple_blist_new());
771 /* load plugins we had when we quit */
772 purple_plugins_load_saved(PIDGIN_PREFS_ROOT
"/plugins/loaded");
774 /* TODO: Move pounces loading into purple_pounces_init() */
775 purple_pounces_load();
780 pidgin_session_init(argv
[0], opt_session_arg
, opt_config_dir_arg
);
782 if (opt_session_arg
!= NULL
) {
783 g_free(opt_session_arg
);
784 opt_session_arg
= NULL
;
786 if (opt_config_dir_arg
!= NULL
) {
787 g_free(opt_config_dir_arg
);
788 opt_config_dir_arg
= NULL
;
792 * We want to show the blist early in the init process so the
793 * user feels warm and fuzzy (not cold and prickley).
797 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/debug/enabled"))
798 pidgin_debug_window_show();
801 /* disable all accounts */
802 for (accounts
= purple_accounts_get_all(); accounts
!= NULL
; accounts
= accounts
->next
) {
803 PurpleAccount
*account
= accounts
->data
;
804 purple_account_set_enabled(account
, PIDGIN_UI
, FALSE
);
806 /* honor the startup status preference */
807 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
808 purple_savedstatus_activate(purple_savedstatus_get_startup());
809 /* now enable the requested ones */
810 dologin_named(opt_login_arg
);
811 if (opt_login_arg
!= NULL
) {
812 g_free(opt_login_arg
);
813 opt_login_arg
= NULL
;
815 } else if (opt_nologin
) {
816 /* Set all accounts to "offline" */
817 PurpleSavedStatus
*saved_status
;
819 /* If we've used this type+message before, lookup the transient status */
820 saved_status
= purple_savedstatus_find_transient_by_type_and_message(
821 PURPLE_STATUS_OFFLINE
, NULL
);
823 /* If this type+message is unique then create a new transient saved status */
824 if (saved_status
== NULL
)
825 saved_status
= purple_savedstatus_new(NULL
, PURPLE_STATUS_OFFLINE
);
827 /* Set the status for each account */
828 purple_savedstatus_activate(saved_status
);
830 /* Everything is good to go--sign on already */
831 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
832 purple_savedstatus_activate(purple_savedstatus_get_startup());
833 purple_accounts_restore_current_statuses();
836 if ((active_accounts
= purple_accounts_get_all_active()) == NULL
)
838 pidgin_accounts_window_show();
842 g_list_free(active_accounts
);
845 #ifdef HAVE_STARTUP_NOTIFICATION
846 startup_notification_complete();
850 winpidgin_post_init();
856 g_free(segfault_message
);