Mercurial > pidgin.yaz
diff pidgin/gtkmain.c @ 15374:5fe8042783c1
Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sat, 20 Jan 2007 02:32:10 +0000 |
parents | |
children | 42961709cb30 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkmain.c Sat Jan 20 02:32:10 2007 +0000 @@ -0,0 +1,828 @@ +/* + * gaim + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "internal.h" +#include "gtkgaim.h" + +#include "account.h" +#include "conversation.h" +#include "core.h" +#include "debug.h" +#include "eventloop.h" +#include "ft.h" +#include "log.h" +#include "notify.h" +#include "prefs.h" +#include "prpl.h" +#include "pounce.h" +#include "sound.h" +#include "status.h" +#include "util.h" +#include "whiteboard.h" + +#include "gtkaccount.h" +#include "gtkblist.h" +#include "gtkconn.h" +#include "gtkconv.h" +#include "gtkdebug.h" +#include "gtkdialogs.h" +#include "gtkdocklet.h" +#include "gtkeventloop.h" +#include "gtkft.h" +#include "gtkidle.h" +#include "gtklog.h" +#include "gtknotify.h" +#include "gtkplugin.h" +#include "gtkpounce.h" +#include "gtkprefs.h" +#include "gtkprivacy.h" +#include "gtkrequest.h" +#include "gtkroomlist.h" +#include "gtksavedstatuses.h" +#include "gtksession.h" +#include "gtksound.h" +#include "gtkthemes.h" +#include "gtkutils.h" +#include "gaimstock.h" +#include "gtkwhiteboard.h" + +#ifdef HAVE_SIGNAL_H +# include <signal.h> +#endif + +#include <getopt.h> + +#ifdef HAVE_STARTUP_NOTIFICATION +# define SN_API_NOT_YET_FROZEN +# include <libsn/sn-launchee.h> +# include <gdk/gdkx.h> +#endif + + + +#ifdef HAVE_STARTUP_NOTIFICATION +static SnLauncheeContext *sn_context = NULL; +static SnDisplay *sn_display = NULL; +#endif + +#ifdef HAVE_SIGNAL_H + +/* + * Lists of signals we wish to catch and those we wish to ignore. + * Each list terminated with -1 + */ +static int catch_sig_list[] = { + SIGSEGV, + SIGHUP, + SIGINT, + SIGTERM, + SIGQUIT, + SIGCHLD, + SIGALRM, + -1 +}; + +static int ignore_sig_list[] = { + SIGPIPE, + -1 +}; +#endif + +static int +dologin_named(const char *name) +{ + GaimAccount *account; + char **names; + int i; + int ret = -1; + + if (name != NULL) { /* list of names given */ + names = g_strsplit(name, ",", 64); + for (i = 0; names[i] != NULL; i++) { + account = gaim_accounts_find(names[i], NULL); + if (account != NULL) { /* found a user */ + ret = 0; + gaim_account_connect(account); + } + } + g_strfreev(names); + } else { /* no name given, use the first account */ + GList *accounts; + + accounts = gaim_accounts_get_all(); + if (accounts != NULL) + { + account = (GaimAccount *)accounts->data; + ret = 0; + gaim_account_connect(account); + } + } + + return ret; +} + +#ifdef HAVE_SIGNAL_H +static void sighandler(int sig); + +/** + * Reap all our dead children. Sometimes Gaim forks off a separate + * process to do some stuff. When that process exits we are + * informed about it so that we can call waitpid() and let it + * stop being a zombie. + * + * We used to do this immediately when our signal handler was + * called, but because of GStreamer we now wait one second before + * reaping anything. Why? For some reason GStreamer fork()s + * during their initialization process. I don't understand why... + * but they do it, and there's nothing we can do about it. + * + * Anyway, so then GStreamer waits for its child to die and then + * it continues with the initialization process. This means that + * we have a race condition where GStreamer is waitpid()ing for its + * child to die and we're catching the SIGCHLD signal. If GStreamer + * is awarded the zombied process then everything is ok. But if Gaim + * reaps the zombie process then the GStreamer initialization sequence + * fails. + * + * So the ugly solution is to wait a second to give GStreamer time to + * reap that bad boy. + * + * GStreamer 0.10.10 and newer have a gst_register_fork_set_enabled() + * function that can be called by applications to disable forking + * during initialization. But it's not in 0.10.0, so we shouldn't + * use it. + */ +static void +clean_pid() +{ + int status; + pid_t pid; + + do { + pid = waitpid(-1, &status, WNOHANG); + } while (pid != 0 && pid != (pid_t)-1); + + if ((pid == (pid_t) - 1) && (errno != ECHILD)) { + char errmsg[BUFSIZ]; + snprintf(errmsg, BUFSIZ, "Warning: waitpid() returned %d", pid); + perror(errmsg); + } + + /* Restore signal catching */ + signal(SIGALRM, sighandler); +} + +char *segfault_message; + +static void +sighandler(int sig) +{ + switch (sig) { + case SIGHUP: + gaim_debug_warning("sighandler", "Caught signal %d\n", sig); + gaim_connections_disconnect_all(); + break; + case SIGSEGV: + fprintf(stderr, "%s", segfault_message); + abort(); + break; + case SIGCHLD: + /* Restore signal catching */ + signal(SIGCHLD, sighandler); + alarm(1); + break; + case SIGALRM: + clean_pid(); + break; + default: + gaim_debug_warning("sighandler", "Caught signal %d\n", sig); + gaim_connections_disconnect_all(); + + gaim_plugins_unload_all(); + + if (gtk_main_level()) + gtk_main_quit(); + exit(0); + } +} +#endif + +static int +ui_main() +{ +#ifndef _WIN32 + GList *icons = NULL; + GdkPixbuf *icon = NULL; + char *icon_path; +#endif + + gaim_gtkthemes_init(); + + gaim_gtk_blist_setup_sort_methods(); + +#ifndef _WIN32 + /* use the nice PNG icon for all the windows */ + icon_path = g_build_filename(DATADIR, "pixmaps", "gaim", "icons", "online.png", NULL); + icon = gdk_pixbuf_new_from_file(icon_path, NULL); + g_free(icon_path); + if (icon) { + icons = g_list_append(icons,icon); + gtk_window_set_default_icon_list(icons); + g_object_unref(G_OBJECT(icon)); + g_list_free(icons); + } else { + gaim_debug_error("ui_main", + "Failed to load the default window icon!\n"); + } +#endif + + return 0; +} + +static void +debug_init(void) +{ + gaim_debug_set_ui_ops(gaim_gtk_debug_get_ui_ops()); + gaim_gtk_debug_init(); +} + +static void +gaim_gtk_ui_init(void) +{ + /* Set the UI operation structures. */ + gaim_accounts_set_ui_ops(gaim_gtk_accounts_get_ui_ops()); + gaim_xfers_set_ui_ops(gaim_gtk_xfers_get_ui_ops()); + gaim_blist_set_ui_ops(gaim_gtk_blist_get_ui_ops()); + gaim_notify_set_ui_ops(gaim_gtk_notify_get_ui_ops()); + gaim_privacy_set_ui_ops(gaim_gtk_privacy_get_ui_ops()); + gaim_request_set_ui_ops(gaim_gtk_request_get_ui_ops()); + gaim_sound_set_ui_ops(gaim_gtk_sound_get_ui_ops()); + gaim_connections_set_ui_ops(gaim_gtk_connections_get_ui_ops()); + gaim_whiteboard_set_ui_ops(gaim_gtk_whiteboard_get_ui_ops()); +#ifdef USE_SCREENSAVER + gaim_idle_set_ui_ops(gaim_gtk_idle_get_ui_ops()); +#endif + + gaim_gtk_stock_init(); + gaim_gtk_account_init(); + gaim_gtk_connection_init(); + gaim_gtk_blist_init(); + gaim_gtk_status_init(); + gaim_gtk_conversations_init(); + gaim_gtk_pounces_init(); + gaim_gtk_privacy_init(); + gaim_gtk_xfers_init(); + gaim_gtk_roomlist_init(); + gaim_gtk_log_init(); +} + +static void +gaim_gtk_quit(void) +{ +#ifdef USE_SM + /* unplug */ + gaim_gtk_session_end(); +#endif + + /* Save the plugins we have loaded for next time. */ + gaim_gtk_plugins_save(); + + /* Uninit */ + gaim_gtk_conversations_uninit(); + gaim_gtk_status_uninit(); + gaim_gtk_docklet_uninit(); + gaim_gtk_blist_uninit(); + gaim_gtk_connection_uninit(); + gaim_gtk_account_uninit(); + gaim_gtk_xfers_uninit(); + gaim_gtk_debug_uninit(); + + /* and end it all... */ + gtk_main_quit(); +} + +static GaimCoreUiOps core_ops = +{ + gaim_gtk_prefs_init, + debug_init, + gaim_gtk_ui_init, + gaim_gtk_quit +}; + +static GaimCoreUiOps * +gaim_gtk_core_get_ui_ops(void) +{ + return &core_ops; +} + +static void +show_usage(const char *name, gboolean terse) +{ + char *text; + + if (terse) { + text = g_strdup_printf(_(PIDGIN_NAME " %s. Try `%s -h' for more information.\n"), VERSION, name); + } else { + text = g_strdup_printf(_(PIDGIN_NAME " %s\n" + "Usage: %s [OPTION]...\n\n" + " -c, --config=DIR use DIR for config files\n" + " -d, --debug print debugging messages to stdout\n" + " -h, --help display this help and exit\n" + " -n, --nologin don't automatically login\n" + " -l, --login[=NAME] automatically login (optional argument NAME specifies\n" + " account(s) to use, separated by commas)\n" + " -v, --version display the current version and exit\n"), VERSION, name); + } + + gaim_print_utf8_to_console(stdout, text); + g_free(text); +} + +#ifdef HAVE_STARTUP_NOTIFICATION +static void +sn_error_trap_push(SnDisplay *display, Display *xdisplay) +{ + gdk_error_trap_push(); +} + +static void +sn_error_trap_pop(SnDisplay *display, Display *xdisplay) +{ + gdk_error_trap_pop(); +} + +static void +startup_notification_complete(void) +{ + Display *xdisplay; + + xdisplay = GDK_DISPLAY(); + sn_display = sn_display_new(xdisplay, + sn_error_trap_push, + sn_error_trap_pop); + sn_context = + sn_launchee_context_new_from_environment(sn_display, + DefaultScreen(xdisplay)); + + if (sn_context != NULL) + { + sn_launchee_context_complete(sn_context); + sn_launchee_context_unref(sn_context); + + sn_display_unref(sn_display); + } +} +#endif /* HAVE_STARTUP_NOTIFICATION */ + +#ifndef _WIN32 +static char *gaim_find_binary_location(void *symbol, void *data) +{ + static char *fullname = NULL; + static gboolean first = TRUE; + + char *argv0 = data; + struct stat st; + char *basebuf, *linkbuf, *fullbuf; + + if (!first) + /* We've already been through this. */ + return strdup(fullname); + + first = FALSE; + + if (!argv0) + return NULL; + + + basebuf = g_find_program_in_path(argv0); + + /* But we still need to deal with symbolic links */ + g_lstat(basebuf, &st); + while ((st.st_mode & S_IFLNK) == S_IFLNK) { + int written; + linkbuf = g_malloc(1024); + written = readlink(basebuf, linkbuf, 1024 - 1); + if (written == -1) + { + /* This really shouldn't happen, but do we + * need something better here? */ + g_free(linkbuf); + continue; + } + linkbuf[written] = '\0'; + if (linkbuf[0] == G_DIR_SEPARATOR) { + /* an absolute path */ + fullbuf = g_strdup(linkbuf); + } else { + char *dirbuf = g_path_get_dirname(basebuf); + /* a relative path */ + fullbuf = g_strdup_printf("%s%s%s", + dirbuf, G_DIR_SEPARATOR_S, + linkbuf); + g_free(dirbuf); + } + /* There's no memory leak here. Really! */ + g_free(linkbuf); + g_free(basebuf); + basebuf = fullbuf; + g_lstat(basebuf, &st); + } + + fullname = basebuf; + return strdup(fullname); +} +#endif /* #ifndef _WIN32 */ + +/* FUCKING GET ME A TOWEL! */ +#ifdef _WIN32 +int gaim_main(HINSTANCE hint, int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + gboolean opt_help = FALSE; + gboolean opt_login = FALSE; + gboolean opt_nologin = FALSE; + gboolean opt_version = FALSE; + char *opt_config_dir_arg = NULL; + char *opt_login_arg = NULL; + char *opt_session_arg = NULL; + int dologin_ret = -1; + char *search_path; + GList *accounts; +#ifdef HAVE_SIGNAL_H + int sig_indx; /* for setting up signal catching */ + sigset_t sigset; + RETSIGTYPE (*prev_sig_disp)(int); + char errmsg[BUFSIZ]; +#ifndef DEBUG + char *segfault_message_tmp; + GError *error = NULL; +#endif +#endif + int opt; + gboolean gui_check; + gboolean debug_enabled; + + struct option long_options[] = { + {"config", required_argument, NULL, 'c'}, + {"debug", no_argument, NULL, 'd'}, + {"help", no_argument, NULL, 'h'}, + {"login", optional_argument, NULL, 'l'}, + {"nologin", no_argument, NULL, 'n'}, + {"session", required_argument, NULL, 's'}, + {"version", no_argument, NULL, 'v'}, + {0, 0, 0, 0} + }; + +#ifdef DEBUG + debug_enabled = TRUE; +#else + debug_enabled = FALSE; +#endif + +#ifdef GAIM_FATAL_ASSERTS + /* Make g_return_... functions fatal. */ + g_log_set_always_fatal(G_LOG_LEVEL_CRITICAL); +#endif + +#ifndef _WIN32 + br_set_locate_fallback_func(gaim_find_binary_location, argv[0]); +#endif +#ifdef ENABLE_NLS + bindtextdomain(PACKAGE, LOCALEDIR); + bind_textdomain_codeset(PACKAGE, "UTF-8"); + textdomain(PACKAGE); +#endif + +#ifdef HAVE_SETLOCALE + /* Locale initialization is not complete here. See gtk_init_check() */ + setlocale(LC_ALL, ""); +#endif + +#ifdef HAVE_SIGNAL_H + +#ifndef DEBUG + /* We translate this here in case the crash breaks gettext. */ + segfault_message_tmp = g_strdup_printf(_( + PIDGIN_NAME " has segfaulted and attempted to dump a core file.\n" + "This is a bug in the software and has happened through\n" + "no fault of your own.\n\n" + "If you can reproduce the crash, please notify the gaim\n" + "developers by reporting a bug at\n" + "%sbug.php\n\n" + "Please make sure to specify what you were doing at the time\n" + "and post the backtrace from the core file. If you do not know\n" + "how to get the backtrace, please read the instructions at\n" + "%sgdb.php\n\n" + "If you need further assistance, please IM either SeanEgn or \n" + "LSchiere (via AIM). Contact information for Sean and Luke \n" + "on other protocols is at\n" + "%scontactinfo.php\n"), + GAIM_WEBSITE, GAIM_WEBSITE, GAIM_WEBSITE + ); + + /* we have to convert the message (UTF-8 to console + charset) early because after a segmentation fault + it's not a good practice to allocate memory */ + segfault_message = g_locale_from_utf8(segfault_message_tmp, + -1, NULL, NULL, &error); + if (segfault_message != NULL) { + g_free(segfault_message_tmp); + } + else { + /* use 'segfault_message_tmp' (UTF-8) as a fallback */ + g_warning("%s\n", error->message); + g_error_free(error); + segfault_message = segfault_message_tmp; + } +#else + /* Don't mark this for translation. */ + segfault_message = g_strdup( + "Hi, user. We need to talk.\n" + "I think something's gone wrong here. It's probably my fault.\n" + "No, really, it's not you... it's me... no no no, I think we get along well\n" + "it's just that.... well, I want to see other people. I... what?!? NO! I \n" + "haven't been cheating on you!! How many times do you want me to tell you?! And\n" + "for the last time, it's just a rash!\n" + ); +#endif + + /* Let's not violate any PLA's!!!! */ + /* jseymour: whatever the fsck that means */ + /* Robot101: for some reason things like gdm like to block * + * useful signals like SIGCHLD, so we unblock all the ones we * + * declare a handler for. thanks JSeymour and Vann. */ + if (sigemptyset(&sigset)) { + snprintf(errmsg, BUFSIZ, "Warning: couldn't initialise empty signal set"); + perror(errmsg); + } + for(sig_indx = 0; catch_sig_list[sig_indx] != -1; ++sig_indx) { + if((prev_sig_disp = signal(catch_sig_list[sig_indx], sighandler)) == SIG_ERR) { + snprintf(errmsg, BUFSIZ, "Warning: couldn't set signal %d for catching", + catch_sig_list[sig_indx]); + perror(errmsg); + } + if(sigaddset(&sigset, catch_sig_list[sig_indx])) { + snprintf(errmsg, BUFSIZ, "Warning: couldn't include signal %d for unblocking", + catch_sig_list[sig_indx]); + perror(errmsg); + } + } + for(sig_indx = 0; ignore_sig_list[sig_indx] != -1; ++sig_indx) { + if((prev_sig_disp = signal(ignore_sig_list[sig_indx], SIG_IGN)) == SIG_ERR) { + snprintf(errmsg, BUFSIZ, "Warning: couldn't set signal %d to ignore", + ignore_sig_list[sig_indx]); + perror(errmsg); + } + } + + if (sigprocmask(SIG_UNBLOCK, &sigset, NULL)) { + snprintf(errmsg, BUFSIZ, "Warning: couldn't unblock signals"); + perror(errmsg); + } +#endif + + /* scan command-line options */ + opterr = 1; + while ((opt = getopt_long(argc, argv, +#ifndef _WIN32 + "c:dhnl::s:v", +#else + "c:dhnl::v", +#endif + long_options, NULL)) != -1) { + switch (opt) { + case 'c': /* config dir */ + g_free(opt_config_dir_arg); + opt_config_dir_arg = g_strdup(optarg); + break; + case 'd': /* debug */ + debug_enabled = TRUE; + break; + case 'h': /* help */ + opt_help = TRUE; + break; + case 'n': /* no autologin */ + opt_nologin = TRUE; + break; + case 'l': /* login, option username */ + opt_login = TRUE; + g_free(opt_login_arg); + if (optarg != NULL) + opt_login_arg = g_strdup(optarg); + break; + case 's': /* use existing session ID */ + g_free(opt_session_arg); + opt_session_arg = g_strdup(optarg); + break; + case 'v': /* version */ + opt_version = TRUE; + break; + case '?': /* show terse help */ + default: + show_usage(argv[0], TRUE); + return 0; + break; + } + } + + /* show help message */ + if (opt_help) { + show_usage(argv[0], FALSE); + return 0; + } + /* show version message */ + if (opt_version) { + printf(PIDGIN_NAME " %s\n", VERSION); + return 0; + } + + /* set a user-specified config directory */ + if (opt_config_dir_arg != NULL) { + gaim_util_set_user_dir(opt_config_dir_arg); + } + + /* + * We're done piddling around with command line arguments. + * Fire up this baby. + */ + + gaim_debug_set_enabled(debug_enabled); + + + search_path = g_build_filename(gaim_user_dir(), "gtkrc-2.0", NULL); + gtk_rc_add_default_file(search_path); + g_free(search_path); + + gui_check = gtk_init_check(&argc, &argv); + if (!gui_check) { + char *display = gdk_get_display(); + + printf(PIDGIN_NAME " %s\n", VERSION); + + g_warning("cannot open display: %s", display ? display : "unset"); + g_free(display); + + return 1; + } + +#ifdef _WIN32 + gtkwgaim_init(hint); +#endif + + gaim_core_set_ui_ops(gaim_gtk_core_get_ui_ops()); + gaim_eventloop_set_ui_ops(gaim_gtk_eventloop_get_ui_ops()); + + /* + * Set plugin search directories. Give priority to the plugins + * in user's home directory. + */ + search_path = g_build_filename(gaim_user_dir(), "plugins", NULL); + gaim_plugins_add_search_path(search_path); + g_free(search_path); + gaim_plugins_add_search_path(LIBDIR); + + if (!gaim_core_init(GAIM_GTK_UI)) { + fprintf(stderr, + "Initialization of the " PIDGIN_NAME " core failed. Dumping core.\n" + "Please report this!\n"); + abort(); + } + + /* TODO: Move blist loading into gaim_blist_init() */ + gaim_set_blist(gaim_blist_new()); + gaim_blist_load(); + + /* TODO: Move prefs loading into gaim_prefs_init() */ + gaim_prefs_load(); + gaim_prefs_update_old(); + gaim_gtk_prefs_update_old(); + + /* load plugins we had when we quit */ + gaim_plugins_load_saved("/gaim/gtk/plugins/loaded"); + gaim_gtk_docklet_init(); + + /* TODO: Move pounces loading into gaim_pounces_init() */ + gaim_pounces_load(); + + + /* HACK BY SEANEGAN: + * We've renamed prpl-oscar to prpl-aim and prpl-icq, accordingly. + * Let's do that change right here... after everything's loaded, but + * before anything has happened + */ + for (accounts = gaim_accounts_get_all(); accounts != NULL; accounts = accounts->next) { + GaimAccount *account = accounts->data; + if (!strcmp(gaim_account_get_protocol_id(account), "prpl-oscar")) { + if (isdigit(*gaim_account_get_username(account))) + gaim_account_set_protocol_id(account, "prpl-icq"); + else + gaim_account_set_protocol_id(account, "prpl-aim"); + } + } + + ui_main(); + +#ifdef USE_SM + gaim_gtk_session_init(argv[0], opt_session_arg, opt_config_dir_arg); +#endif + if (opt_session_arg != NULL) { + g_free(opt_session_arg); + opt_session_arg = NULL; + } + if (opt_config_dir_arg != NULL) { + g_free(opt_config_dir_arg); + opt_config_dir_arg = NULL; + } + + /* + * We want to show the blist early in the init process so the + * user feels warm and fuzzy (not cold and prickley). + */ + gaim_blist_show(); + + if (gaim_prefs_get_bool("/gaim/gtk/debug/enabled")) + gaim_gtk_debug_window_show(); + + if (opt_login) { + dologin_ret = dologin_named(opt_login_arg); + if (opt_login_arg != NULL) { + g_free(opt_login_arg); + opt_login_arg = NULL; + } + } + + if (opt_nologin) + { + /* Set all accounts to "offline" */ + GaimSavedStatus *saved_status; + + /* If we've used this type+message before, lookup the transient status */ + saved_status = gaim_savedstatus_find_transient_by_type_and_message( + GAIM_STATUS_OFFLINE, NULL); + + /* If this type+message is unique then create a new transient saved status */ + if (saved_status == NULL) + saved_status = gaim_savedstatus_new(NULL, GAIM_STATUS_OFFLINE); + + /* Set the status for each account */ + gaim_savedstatus_activate(saved_status); + } + else + { + /* Everything is good to go--sign on already */ + if (!gaim_prefs_get_bool("/core/savedstatus/startup_current_status")) + gaim_savedstatus_activate(gaim_savedstatus_get_startup()); + gaim_accounts_restore_current_statuses(); + } + + if ((accounts = gaim_accounts_get_all_active()) == NULL) + { + gaim_gtk_accounts_window_show(); + } + else + { + g_list_free(accounts); + } + +#ifdef HAVE_STARTUP_NOTIFICATION + startup_notification_complete(); +#endif + +#ifdef _WIN32 + gtkwgaim_post_init(); +#endif + + gtk_main(); + +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); +#endif + +#ifdef _WIN32 + gtkwgaim_cleanup(); +#endif + + return 0; +}