diff -r 3560f0bc6eaf -r e77b9f184125 pidgin/gtkpidgin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkpidgin.c Sat Oct 12 03:08:49 2013 +0530 @@ -0,0 +1,891 @@ +/* + * pidgin + * + * Pidgin 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ + +#include "internal.h" +#include "pidgin.h" + +#include "account.h" +#include "conversation.h" +#include "core.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "eventloop.h" +#include "xfer.h" +#include "log.h" +#include "network.h" +#include "notify.h" +#include "prefs.h" +#include "protocol.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 "gtkxfer.h" +#include "gtkidle.h" +#include "gtklog.h" +#include "gtkmedia.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 "gtksmiley.h" +#include "gtksound.h" +#include "gtkthemes.h" +#include "gtkutils.h" +#include "pidginstock.h" +#include "gtkwhiteboard.h" + +#ifdef HAVE_SIGNAL_H +# include +#endif + +#ifdef ENABLE_INTROSPECTION +# include +#endif + +#include + + +#ifdef HAVE_SIGNAL_H + +/* + * Lists of signals we wish to catch and those we wish to ignore. + * Each list terminated with -1 + */ +static const int catch_sig_list[] = { + SIGSEGV, + SIGINT, + SIGTERM, + SIGQUIT, + SIGCHLD, + -1 +}; + +static const int ignore_sig_list[] = { + SIGPIPE, + -1 +}; +#endif + +static void +dologin_named(const char *name) +{ + PurpleAccount *account; + char **names; + int i; + + if (name != NULL) { /* list of names given */ + names = g_strsplit(name, ",", 64); + for (i = 0; names[i] != NULL; i++) { + account = purple_accounts_find(names[i], NULL); + if (account != NULL) { /* found a user */ + purple_account_set_enabled(account, PIDGIN_UI, TRUE); + } + } + g_strfreev(names); + } else { /* no name given, use the first account */ + GList *accounts; + + accounts = purple_accounts_get_all(); + if (accounts != NULL) + { + account = (PurpleAccount *)accounts->data; + purple_account_set_enabled(account, PIDGIN_UI, TRUE); + } + } +} + +#ifdef HAVE_SIGNAL_H +static char *segfault_message; + +static int signal_sockets[2]; + +static void sighandler(int sig); + +static void sighandler(int sig) +{ + ssize_t written; + + /* + * We won't do any of the heavy lifting for the signal handling here + * because we have no idea what was interrupted. Previously this signal + * handler could result in some calls to malloc/free, which can cause + * deadlock in libc when the signal handler was interrupting a previous + * malloc or free. So instead we'll do an ugly hack where we write the + * signal number to one end of a socket pair. The other half of the + * socket pair is watched by our main loop. When the main loop sees new + * data on the socket it reads in the signal and performs the appropriate + * action without fear of interrupting stuff. + */ + if (sig == SIGSEGV) { + fprintf(stderr, "%s", segfault_message); + abort(); + return; + } + + written = write(signal_sockets[0], &sig, sizeof(int)); + if (written < 0 || written != sizeof(int)) { + /* This should never happen */ + purple_debug_error("sighandler", "Received signal %d but only " + "wrote %" G_GSSIZE_FORMAT " bytes out of %" + G_GSIZE_FORMAT ": %s\n", + sig, written, sizeof(int), g_strerror(errno)); + exit(1); + } +} + +static gboolean +mainloop_sighandler(GIOChannel *source, GIOCondition cond, gpointer data) +{ + GIOStatus stat; + int sig; + gsize bytes_read; + GError *error = NULL; + + /* read the signal number off of the io channel */ + stat = g_io_channel_read_chars(source, (gchar *)&sig, sizeof(int), + &bytes_read, &error); + if (stat != G_IO_STATUS_NORMAL) { + purple_debug_error("sighandler", "Signal callback failed to read " + "from signal socket: %s", error->message); + purple_core_quit(); + return FALSE; + } + + switch (sig) { + case SIGCHLD: + /* Restore signal catching */ + signal(SIGCHLD, sighandler); + break; + default: + purple_debug_warning("sighandler", "Caught signal %d\n", sig); + purple_core_quit(); + } + + return TRUE; +} +#endif + +static int +ui_main(void) +{ +#ifndef _WIN32 + GList *icons = NULL; + GdkPixbuf *icon = NULL; + char *icon_path; + gsize i; + struct { + const char *dir; + const char *filename; + } icon_sizes[] = { + {"16x16", "pidgin.png"}, + {"24x24", "pidgin.png"}, + {"32x32", "pidgin.png"}, + {"48x48", "pidgin.png"}, + {"scalable", "pidgin.svg"} + }; + +#endif + + pidgin_themes_init(); + + pidgin_blist_setup_sort_methods(); + +#ifndef _WIN32 + /* use the nice PNG icon for all the windows */ + for(i=0; imessage); + 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 + + /* + * Create a socket pair for receiving unix signals from a signal + * handler. + */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sockets) < 0) { + perror("Failed to create sockets for GLib signal handling"); + exit(1); + } + signal_channel = g_io_channel_unix_new(signal_sockets[1]); + + /* + * Set the channel encoding to raw binary instead of the default of + * UTF-8, because we'll be sending integers across instead of strings. + */ + error = NULL; + signal_status = g_io_channel_set_encoding(signal_channel, NULL, &error); + if (signal_status != G_IO_STATUS_NORMAL) { + fprintf(stderr, "Failed to set the signal channel to raw " + "binary: %s", error->message); + exit(1); + } + signal_channel_watcher = g_io_add_watch(signal_channel, G_IO_IN, mainloop_sighandler, NULL); + g_io_channel_unref(signal_channel); + + /* 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, sizeof(errmsg), "Warning: couldn't initialise empty signal set"); + perror(errmsg); + } + for(sig_indx = 0; catch_sig_list[sig_indx] != -1; ++sig_indx) { + if(signal(catch_sig_list[sig_indx], sighandler) == SIG_ERR) { + snprintf(errmsg, sizeof(errmsg), "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, sizeof(errmsg), "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(signal(ignore_sig_list[sig_indx], SIG_IGN) == SIG_ERR) { + snprintf(errmsg, sizeof(errmsg), "Warning: couldn't set signal %d to ignore", + ignore_sig_list[sig_indx]); + perror(errmsg); + } + } + + if (sigprocmask(SIG_UNBLOCK, &sigset, NULL)) { + snprintf(errmsg, sizeof(errmsg), "Warning: couldn't unblock signals"); + perror(errmsg); + } +#endif + + /* scan command-line options */ + opterr = 1; + while ((opt = getopt_long(argc, argv, +#ifndef _WIN32 + "c:dfhmnl::s:vi:", +#else + "c:dfhmnl::vi:", +#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; + if (g_strcmp0(optarg, "colored") == 0) + debug_colored = TRUE; + break; + case 'f': /* force-online */ + opt_force_online = 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 'm': /* do not ensure single instance. */ + opt_si = FALSE; + break; + case 'D': /* --display */ + case 'S': /* --sync */ + /* handled by gtk_init_check below */ + break; +#ifdef ENABLE_INTROSPECTION + case 'i': /* introspection */ + g_irepository_dump(optarg, NULL); + return 0; + break; +#endif + case '?': /* show terse help */ + default: + show_usage(argv[0], TRUE); +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); +#endif + return 0; + break; + } + } + + /* show help message */ + if (opt_help) { + show_usage(argv[0], FALSE); +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); +#endif + return 0; + } + /* show version message */ + if (opt_version) { + printf("%s %s (libpurple %s)\n", PIDGIN_NAME, DISPLAY_VERSION, + purple_core_get_version()); +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); +#endif + return 0; + } + + /* set a user-specified config directory */ + if (opt_config_dir_arg != NULL) { + if (g_path_is_absolute(opt_config_dir_arg)) { + purple_util_set_user_dir(opt_config_dir_arg); + } else { + /* Make an absolute (if not canonical) path */ + char *cwd = g_get_current_dir(); + char *path = g_build_path(G_DIR_SEPARATOR_S, cwd, opt_config_dir_arg, NULL); + purple_util_set_user_dir(path); + g_free(path); + g_free(cwd); + } + } + + /* + * We're done piddling around with command line arguments. + * Fire up this baby. + */ + + purple_debug_set_enabled(debug_enabled); + purple_debug_set_colored(debug_colored); + +#if !GTK_CHECK_VERSION(3,0,0) + search_path = g_build_filename(purple_user_dir(), "gtkrc-2.0", NULL); + gtk_rc_add_default_file(search_path); + g_free(search_path); +#endif + + gui_check = gtk_init_check(&argc, &argv); + if (!gui_check) { + char *display = gdk_get_display(); + + printf("%s %s\n", PIDGIN_NAME, DISPLAY_VERSION); + + g_warning("cannot open display: %s", display ? display : "unset"); + g_free(display); +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); +#endif + + return 1; + } + +#if GTK_CHECK_VERSION(3,0,0) + search_path = g_build_filename(purple_user_dir(), "gtk-3.0.css", NULL); + + error = NULL; + provider = gtk_css_provider_new(); + gui_check = gtk_css_provider_load_from_path(provider, search_path, &error); + + if (gui_check && !error) { + screen = gdk_screen_get_default(); + gtk_style_context_add_provider_for_screen(screen, + GTK_STYLE_PROVIDER(provider), + GTK_STYLE_PROVIDER_PRIORITY_USER); + } else { + purple_debug_error("gtk", "Unable to load custom gtk-3.0.css: %s\n", + error ? error->message : "(unknown error)"); + } + + g_free(search_path); +#endif + +#ifdef _WIN32 + winpidgin_init(hint); +#endif + + purple_core_set_ui_ops(pidgin_core_get_ui_ops()); + purple_eventloop_set_ui_ops(pidgin_eventloop_get_ui_ops()); + + if (!purple_core_init(PIDGIN_UI)) { + fprintf(stderr, + "Initialization of the libpurple core failed. Dumping core.\n" + "Please report this!\n"); +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); +#endif + abort(); + } + + search_path = g_build_filename(purple_user_dir(), "plugins", NULL); + if (!g_stat(search_path, &st)) + g_mkdir(search_path, S_IRUSR | S_IWUSR | S_IXUSR); + purple_plugins_add_search_path(search_path); + g_free(search_path); + + purple_plugins_add_search_path(LIBDIR); + purple_plugins_refresh(); + + if (opt_si && !purple_core_ensure_single_instance()) { +#ifdef HAVE_DBUS + DBusConnection *conn = purple_dbus_get_connection(); + DBusMessage *message = dbus_message_new_method_call(DBUS_SERVICE_PURPLE, DBUS_PATH_PURPLE, + DBUS_INTERFACE_PURPLE, "PurpleBlistSetVisible"); + gboolean tr = TRUE; + dbus_message_append_args(message, DBUS_TYPE_INT32, &tr, DBUS_TYPE_INVALID); + dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); + dbus_message_unref(message); +#endif + gdk_notify_startup_complete(); + purple_core_quit(); + g_printerr(_("Exiting because another libpurple client is already running.\n")); +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); +#endif + return 0; + } + + /* load plugins we had when we quit */ + purple_plugins_load_saved(PIDGIN_PREFS_ROOT "/plugins/loaded"); + + ui_main(); + +#ifdef USE_SM + pidgin_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; + } + + /* This needs to be before purple_blist_show() so the + * statusbox gets the forced online status. */ + if (opt_force_online) + purple_network_force_online(); + + /* + * We want to show the blist early in the init process so the + * user feels warm and fuzzy (not cold and prickley). + */ + purple_blist_show(); + + if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled")) + pidgin_debug_window_show(); + + if (opt_login) { + /* disable all accounts */ + for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) { + PurpleAccount *account = accounts->data; + purple_account_set_enabled(account, PIDGIN_UI, FALSE); + } + /* honor the startup status preference */ + if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status")) + purple_savedstatus_activate(purple_savedstatus_get_startup()); + /* now enable the requested ones */ + dologin_named(opt_login_arg); + if (opt_login_arg != NULL) { + g_free(opt_login_arg); + opt_login_arg = NULL; + } + } else if (opt_nologin) { + /* Set all accounts to "offline" */ + PurpleSavedStatus *saved_status; + + /* If we've used this type+message before, lookup the transient status */ + saved_status = purple_savedstatus_find_transient_by_type_and_message( + PURPLE_STATUS_OFFLINE, NULL); + + /* If this type+message is unique then create a new transient saved status */ + if (saved_status == NULL) + saved_status = purple_savedstatus_new(NULL, PURPLE_STATUS_OFFLINE); + + /* Set the status for each account */ + purple_savedstatus_activate(saved_status); + } else { + /* Everything is good to go--sign on already */ + if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status")) + purple_savedstatus_activate(purple_savedstatus_get_startup()); + purple_accounts_restore_current_statuses(); + } + + if ((active_accounts = purple_accounts_get_all_active()) == NULL) + { + pidgin_accounts_window_show(); + } + else + { + g_list_free(active_accounts); + } + + /* GTK clears the notification for us when opening the first window, + * but we may have launched with only a status icon, so clear the it + * just in case. */ + gdk_notify_startup_complete(); + +#ifdef _WIN32 + winpidgin_post_init(); +#endif + + gtk_main(); + +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); + g_source_remove(signal_channel_watcher); + close(signal_sockets[0]); + close(signal_sockets[1]); +#endif + +#ifdef _WIN32 + winpidgin_cleanup(); +#endif + + return 0; +}