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"
30 #include "dbus-maybe.h"
32 #include "eventloop.h"
33 #include "glibcompat.h"
43 #include "whiteboard.h"
46 #include "gtkaccount.h"
51 #include "gtkdialogs.h"
52 #include "gtkdocklet.h"
53 #include "gtkeventloop.h"
58 #include "gtknotify.h"
59 #include "gtkplugin.h"
60 #include "gtkpounce.h"
62 #include "gtkprivacy.h"
63 #include "gtkrequest.h"
64 #include "gtkroomlist.h"
65 #include "gtksavedstatuses.h"
66 #include "gtksession.h"
67 #include "gtksmiley-theme.h"
70 #include "pidginstock.h"
71 #include "gtkwhiteboard.h"
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
[] = {
91 static const int ignore_sig_list
[] = {
97 dologin_named(const char *name
)
99 PurpleAccount
*account
;
103 if (name
!= NULL
) { /* list of names given */
104 names
= g_strsplit(name
, ",", 64);
105 for (i
= 0; names
[i
] != NULL
; i
++) {
106 account
= purple_accounts_find(names
[i
], NULL
);
107 if (account
!= NULL
) { /* found a user */
108 purple_account_set_enabled(account
, PIDGIN_UI
, TRUE
);
112 } else { /* no name given, use the first account */
115 accounts
= purple_accounts_get_all();
116 if (accounts
!= NULL
)
118 account
= (PurpleAccount
*)accounts
->data
;
119 purple_account_set_enabled(account
, PIDGIN_UI
, TRUE
);
124 static char *segfault_message
;
126 static int signal_sockets
[2];
128 static void sighandler(int sig
);
130 static void sighandler(int sig
)
135 * We won't do any of the heavy lifting for the signal handling here
136 * because we have no idea what was interrupted. Previously this signal
137 * handler could result in some calls to malloc/free, which can cause
138 * deadlock in libc when the signal handler was interrupting a previous
139 * malloc or free. So instead we'll do an ugly hack where we write the
140 * signal number to one end of a socket pair. The other half of the
141 * socket pair is watched by our main loop. When the main loop sees new
142 * data on the socket it reads in the signal and performs the appropriate
143 * action without fear of interrupting stuff.
145 if (sig
== SIGSEGV
) {
146 fprintf(stderr
, "%s", segfault_message
);
151 written
= write(signal_sockets
[0], &sig
, sizeof(int));
152 if (written
< 0 || written
!= sizeof(int)) {
153 /* This should never happen */
154 purple_debug_error("sighandler", "Received signal %d but only "
155 "wrote %" G_GSSIZE_FORMAT
" bytes out of %"
156 G_GSIZE_FORMAT
": %s\n",
157 sig
, written
, sizeof(int), g_strerror(errno
));
163 mainloop_sighandler(GIOChannel
*source
, GIOCondition cond
, gpointer data
)
168 GError
*error
= NULL
;
170 /* read the signal number off of the io channel */
171 stat
= g_io_channel_read_chars(source
, (gchar
*)&sig
, sizeof(int),
172 &bytes_read
, &error
);
173 if (stat
!= G_IO_STATUS_NORMAL
) {
174 purple_debug_error("sighandler", "Signal callback failed to read "
175 "from signal socket: %s", error
->message
);
182 /* Restore signal catching */
183 signal(SIGCHLD
, sighandler
);
186 purple_debug_warning("sighandler", "Caught signal %d\n", sig
);
198 GdkPixbuf
*icon
= NULL
;
203 const char *filename
;
205 {"16x16", "pidgin.png"},
206 {"24x24", "pidgin.png"},
207 {"32x32", "pidgin.png"},
208 {"48x48", "pidgin.png"},
209 {"scalable", "pidgin.svg"}
214 pidgin_blist_setup_sort_methods();
217 /* use the nice PNG icon for all the windows */
218 for(i
=0; i
<G_N_ELEMENTS(icon_sizes
); i
++) {
219 icon_path
= g_build_filename(PURPLE_DATADIR
, "icons", "hicolor",
220 icon_sizes
[i
].dir
, "apps", icon_sizes
[i
].filename
, NULL
);
221 icon
= pidgin_pixbuf_new_from_file(icon_path
);
224 icons
= g_list_append(icons
,icon
);
226 purple_debug_error("ui_main",
227 "Failed to load the default window icon (%spx version)!\n", icon_sizes
[i
].dir
);
231 purple_debug_error("ui_main", "Unable to load any size of default window icon!\n");
233 gtk_window_set_default_icon_list(icons
);
235 g_list_foreach(icons
, (GFunc
)g_object_unref
, NULL
);
246 purple_debug_set_ui_ops(pidgin_debug_get_ui_ops());
255 /* Set the UI operation structures. */
256 purple_accounts_set_ui_ops(pidgin_accounts_get_ui_ops());
257 purple_xfers_set_ui_ops(pidgin_xfers_get_ui_ops());
258 purple_blist_set_ui_ops(pidgin_blist_get_ui_ops());
259 purple_notify_set_ui_ops(pidgin_notify_get_ui_ops());
260 purple_request_set_ui_ops(pidgin_request_get_ui_ops());
261 purple_sound_set_ui_ops(pidgin_sound_get_ui_ops());
262 purple_connections_set_ui_ops(pidgin_connections_get_ui_ops());
263 purple_whiteboard_set_ui_ops(pidgin_whiteboard_get_ui_ops());
264 #if defined(USE_SCREENSAVER) || defined(HAVE_IOKIT)
265 purple_idle_set_ui_ops(pidgin_idle_get_ui_ops());
268 pidgin_accounts_init();
269 pidgin_connection_init();
270 pidgin_request_init();
272 pidgin_status_init();
273 pidgin_conversations_init();
274 pidgin_pounces_init();
275 pidgin_privacy_init();
277 pidgin_roomlist_init();
279 pidgin_docklet_init();
280 _pidgin_smiley_theme_init();
282 pidgin_medias_init();
283 pidgin_notify_init();
286 static GHashTable
*ui_info
= NULL
;
293 pidgin_session_end();
297 pidgin_utils_uninit();
298 pidgin_notify_uninit();
299 _pidgin_smiley_theme_uninit();
300 pidgin_conversations_uninit();
301 pidgin_status_uninit();
302 pidgin_docklet_uninit();
303 pidgin_blist_uninit();
304 pidgin_request_uninit();
305 pidgin_connection_uninit();
306 pidgin_accounts_uninit();
307 pidgin_xfers_uninit();
308 pidgin_debug_uninit();
311 g_hash_table_destroy(ui_info
);
313 /* and end it all... */
317 static GHashTable
*pidgin_ui_get_info(void)
319 if(NULL
== ui_info
) {
320 ui_info
= g_hash_table_new(g_str_hash
, g_str_equal
);
322 g_hash_table_insert(ui_info
, "name", (char*)PIDGIN_NAME
);
323 g_hash_table_insert(ui_info
, "version", VERSION
);
324 g_hash_table_insert(ui_info
, "website", "https://pidgin.im");
325 g_hash_table_insert(ui_info
, "dev_website", "https://developer.pidgin.im");
326 g_hash_table_insert(ui_info
, "client_type", "pc");
329 * This is the client key for "Pidgin." It is owned by the AIM
330 * account "markdoliner." Please don't use this key for other
331 * applications. You can either not specify a client key, in
332 * which case the default "libpurple" key will be used, or you
333 * can try to register your own at the AIM or ICQ web sites
334 * (although this functionality was removed at some point, it's
335 * possible it has been re-added). AOL's old key management
336 * page is http://developer.aim.com/manageKeys.jsp
338 g_hash_table_insert(ui_info
, "prpl-aim-clientkey", "ma1cSASNCKFtrdv9");
339 g_hash_table_insert(ui_info
, "prpl-icq-clientkey", "ma1cSASNCKFtrdv9");
342 * This is the distid for Pidgin, given to us by AOL. Please
343 * don't use this for other applications. You can just not
344 * specify a distid and libpurple will use a default.
346 g_hash_table_insert(ui_info
, "prpl-aim-distid", GINT_TO_POINTER(1550));
347 g_hash_table_insert(ui_info
, "prpl-icq-distid", GINT_TO_POINTER(1550));
353 static PurpleCoreUiOps core_ops
=
366 static PurpleCoreUiOps
*
367 pidgin_core_get_ui_ops(void)
373 show_usage(const char *name
, gboolean terse
)
378 text
= g_strdup_printf(_("%s %s. Try `%s -h' for more information.\n"), PIDGIN_NAME
, DISPLAY_VERSION
, name
);
380 GString
*str
= g_string_new(NULL
);
381 g_string_append_printf(str
, "%s %s\n", PIDGIN_NAME
, DISPLAY_VERSION
);
382 g_string_append_printf(str
, _("Usage: %s [OPTION]...\n\n"), name
);
383 g_string_append_printf(str
, " -c, --config=%s %s\n",
384 _("DIR"), _("use DIR for config files"));
385 g_string_append_printf(str
, " -d, --debug[=colored] %s\n",
386 _("print debugging messages to stdout"));
387 g_string_append_printf(str
, " -f, --force-online %s\n",
388 _("force online, regardless of network status"));
389 g_string_append_printf(str
, " -h, --help %s\n",
390 _("display this help and exit"));
391 g_string_append_printf(str
, " -m, --multiple %s\n",
392 _("allow multiple instances"));
393 g_string_append_printf(str
, " -n, --nologin %s\n",
394 _("don't automatically login"));
395 g_string_append_printf(str
, " -l, --login[=%s] %s\n",
397 _("enable specified account(s) (optional argument NAME\n"
399 "specifies account(s) to use, separated by commas.\n"
401 "Without this only the first account will be enabled)."));
403 g_string_append_printf(str
, " --display=DISPLAY %s\n",
404 _("X display to use"));
406 g_string_append_printf(str
, " -v, --version %s\n",
407 _("display the current version and exit"));
408 text
= g_string_free(str
, FALSE
);
411 purple_print_utf8_to_console(stdout
, text
);
415 int pidgin_start(int argc
, char *argv
[])
417 gboolean opt_force_online
= FALSE
;
418 gboolean opt_help
= FALSE
;
419 gboolean opt_login
= FALSE
;
420 gboolean opt_nologin
= FALSE
;
421 gboolean opt_version
= FALSE
;
422 gboolean opt_si
= TRUE
; /* Check for single instance? */
423 char *opt_config_dir_arg
= NULL
;
424 char *opt_login_arg
= NULL
;
425 char *opt_session_arg
= NULL
;
427 GtkCssProvider
*provider
;
430 int sig_indx
; /* for setting up signal catching */
433 GIOChannel
*signal_channel
;
434 GIOStatus signal_status
;
435 guint signal_channel_watcher
;
438 char *segfault_message_tmp
;
442 gboolean debug_enabled
, debug_colored
;
443 GList
*active_accounts
;
446 struct option long_options
[] = {
447 {"config", required_argument
, NULL
, 'c'},
448 {"debug", optional_argument
, NULL
, 'd'},
449 {"force-online", no_argument
, NULL
, 'f'},
450 {"help", no_argument
, NULL
, 'h'},
451 {"login", optional_argument
, NULL
, 'l'},
452 {"multiple", no_argument
, NULL
, 'm'},
453 {"nologin", no_argument
, NULL
, 'n'},
454 {"session", required_argument
, NULL
, 's'},
455 {"version", no_argument
, NULL
, 'v'},
456 {"display", required_argument
, NULL
, 'D'},
457 {"sync", no_argument
, NULL
, 'S'},
461 debug_colored
= FALSE
;
463 debug_enabled
= TRUE
;
465 debug_enabled
= FALSE
;
469 bindtextdomain(PACKAGE
, PURPLE_LOCALEDIR
);
470 bind_textdomain_codeset(PACKAGE
, "UTF-8");
474 /* Locale initialization is not complete here. See gtk_init_check() */
475 setlocale(LC_ALL
, "");
478 /* We translate this here in case the crash breaks gettext. */
479 segfault_message_tmp
= g_strdup_printf(_(
480 "%s %s has segfaulted and attempted to dump a core file.\n"
481 "This is a bug in the software and has happened through\n"
482 "no fault of your own.\n\n"
483 "If you can reproduce the crash, please notify the developers\n"
484 "by reporting a bug at:\n"
485 "%ssimpleticket/\n\n"
486 "Please make sure to specify what you were doing at the time\n"
487 "and post the backtrace from the core file. If you do not know\n"
488 "how to get the backtrace, please read the instructions at\n"
489 "%swiki/GetABacktrace\n"),
490 PIDGIN_NAME
, DISPLAY_VERSION
, PURPLE_DEVEL_WEBSITE
, PURPLE_DEVEL_WEBSITE
493 /* we have to convert the message (UTF-8 to console
494 charset) early because after a segmentation fault
495 it's not a good practice to allocate memory */
497 segfault_message
= g_locale_from_utf8(segfault_message_tmp
,
498 -1, NULL
, NULL
, &error
);
499 if (segfault_message
!= NULL
) {
500 g_free(segfault_message_tmp
);
503 /* use 'segfault_message_tmp' (UTF-8) as a fallback */
504 g_warning("%s\n", error
->message
);
506 segfault_message
= segfault_message_tmp
;
509 /* Don't mark this for translation. */
510 segfault_message
= g_strdup(
511 "Hi, user. We need to talk.\n"
512 "I think something's gone wrong here. It's probably my fault.\n"
513 "No, really, it's not you... it's me... no no no, I think we get along well\n"
514 "it's just that.... well, I want to see other people. I... what?!? NO! I \n"
515 "haven't been cheating on you!! How many times do you want me to tell you?! And\n"
516 "for the last time, it's just a rash!\n"
521 * Create a socket pair for receiving unix signals from a signal
524 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, signal_sockets
) < 0) {
525 perror("Failed to create sockets for GLib signal handling");
528 signal_channel
= g_io_channel_unix_new(signal_sockets
[1]);
531 * Set the channel encoding to raw binary instead of the default of
532 * UTF-8, because we'll be sending integers across instead of strings.
535 signal_status
= g_io_channel_set_encoding(signal_channel
, NULL
, &error
);
536 if (signal_status
!= G_IO_STATUS_NORMAL
) {
537 fprintf(stderr
, "Failed to set the signal channel to raw "
538 "binary: %s", error
->message
);
541 signal_channel_watcher
= g_io_add_watch(signal_channel
, G_IO_IN
, mainloop_sighandler
, NULL
);
542 g_io_channel_unref(signal_channel
);
544 /* Let's not violate any PLA's!!!! */
545 /* jseymour: whatever the fsck that means */
546 /* Robot101: for some reason things like gdm like to block *
547 * useful signals like SIGCHLD, so we unblock all the ones we *
548 * declare a handler for. thanks JSeymour and Vann. */
549 if (sigemptyset(&sigset
)) {
550 snprintf(errmsg
, sizeof(errmsg
), "Warning: couldn't initialise empty signal set");
553 for(sig_indx
= 0; catch_sig_list
[sig_indx
] != -1; ++sig_indx
) {
554 if(signal(catch_sig_list
[sig_indx
], sighandler
) == SIG_ERR
) {
555 snprintf(errmsg
, sizeof(errmsg
), "Warning: couldn't set signal %d for catching",
556 catch_sig_list
[sig_indx
]);
559 if(sigaddset(&sigset
, catch_sig_list
[sig_indx
])) {
560 snprintf(errmsg
, sizeof(errmsg
), "Warning: couldn't include signal %d for unblocking",
561 catch_sig_list
[sig_indx
]);
565 for(sig_indx
= 0; ignore_sig_list
[sig_indx
] != -1; ++sig_indx
) {
566 if(signal(ignore_sig_list
[sig_indx
], SIG_IGN
) == SIG_ERR
) {
567 snprintf(errmsg
, sizeof(errmsg
), "Warning: couldn't set signal %d to ignore",
568 ignore_sig_list
[sig_indx
]);
573 if (sigprocmask(SIG_UNBLOCK
, &sigset
, NULL
)) {
574 snprintf(errmsg
, sizeof(errmsg
), "Warning: couldn't unblock signals");
578 /* scan command-line options */
580 while ((opt
= getopt_long(argc
, argv
,
586 long_options
, NULL
)) != -1) {
588 case 'c': /* config dir */
589 g_free(opt_config_dir_arg
);
590 opt_config_dir_arg
= g_strdup(optarg
);
592 case 'd': /* debug */
593 debug_enabled
= TRUE
;
594 if (g_strcmp0(optarg
, "colored") == 0)
595 debug_colored
= TRUE
;
597 case 'f': /* force-online */
598 opt_force_online
= TRUE
;
603 case 'n': /* no autologin */
606 case 'l': /* login, option username */
608 g_free(opt_login_arg
);
610 opt_login_arg
= g_strdup(optarg
);
612 case 's': /* use existing session ID */
613 g_free(opt_session_arg
);
614 opt_session_arg
= g_strdup(optarg
);
616 case 'v': /* version */
619 case 'm': /* do not ensure single instance. */
622 case 'D': /* --display */
623 case 'S': /* --sync */
624 /* handled by gtk_init_check below */
626 case '?': /* show terse help */
628 show_usage(argv
[0], TRUE
);
629 g_free(segfault_message
);
635 /* show help message */
637 show_usage(argv
[0], FALSE
);
638 g_free(segfault_message
);
641 /* show version message */
643 printf("%s %s (libpurple %s)\n", PIDGIN_NAME
, DISPLAY_VERSION
,
644 purple_core_get_version());
645 g_free(segfault_message
);
649 /* set a user-specified config directory */
650 if (opt_config_dir_arg
!= NULL
) {
651 if (g_path_is_absolute(opt_config_dir_arg
)) {
652 purple_util_set_user_dir(opt_config_dir_arg
);
654 /* Make an absolute (if not canonical) path */
655 char *cwd
= g_get_current_dir();
656 char *path
= g_build_path(G_DIR_SEPARATOR_S
, cwd
, opt_config_dir_arg
, NULL
);
657 purple_util_set_user_dir(path
);
664 * We're done piddling around with command line arguments.
668 if (g_getenv("PIDGIN_DEBUG_COLORED") != NULL
)
669 debug_colored
= TRUE
;
670 purple_debug_set_enabled(debug_enabled
);
671 purple_debug_set_colored(debug_colored
);
673 gui_check
= gtk_init_check(&argc
, &argv
);
675 const char *display
= gdk_display_get_name(gdk_display_get_default());
677 printf("%s %s\n", PIDGIN_NAME
, DISPLAY_VERSION
);
679 g_warning("cannot open display: %s", display
? display
: "unset");
680 g_free(segfault_message
);
685 search_path
= g_build_filename(purple_user_dir(), "gtk-3.0.css", NULL
);
688 provider
= gtk_css_provider_new();
689 gui_check
= gtk_css_provider_load_from_path(provider
, search_path
, &error
);
691 if (gui_check
&& !error
) {
692 screen
= gdk_screen_get_default();
693 gtk_style_context_add_provider_for_screen(screen
,
694 GTK_STYLE_PROVIDER(provider
),
695 GTK_STYLE_PROVIDER_PRIORITY_USER
);
697 purple_debug_error("gtk", "Unable to load custom gtk-3.0.css: %s\n",
698 error
? error
->message
: "(unknown error)");
707 purple_core_set_ui_ops(pidgin_core_get_ui_ops());
708 purple_eventloop_set_ui_ops(pidgin_eventloop_get_ui_ops());
710 if (!purple_core_init(PIDGIN_UI
)) {
712 "Initialization of the libpurple core failed. Dumping core.\n"
713 "Please report this!\n");
714 g_free(segfault_message
);
718 search_path
= g_build_filename(purple_user_dir(), "plugins", NULL
);
719 if (!g_stat(search_path
, &st
))
720 g_mkdir(search_path
, S_IRUSR
| S_IWUSR
| S_IXUSR
);
721 purple_plugins_add_search_path(search_path
);
724 purple_plugins_add_search_path(PIDGIN_LIBDIR
);
725 purple_plugins_refresh();
727 if (opt_si
&& !purple_core_ensure_single_instance()) {
729 DBusConnection
*conn
= purple_dbus_get_connection();
730 DBusMessage
*message
= dbus_message_new_method_call(PURPLE_DBUS_SERVICE
, PURPLE_DBUS_PATH
,
731 PURPLE_DBUS_INTERFACE
, "PurpleBlistSetVisible");
733 dbus_message_append_args(message
, DBUS_TYPE_INT32
, &tr
, DBUS_TYPE_INVALID
);
734 dbus_connection_send_with_reply_and_block(conn
, message
, -1, NULL
);
735 dbus_message_unref(message
);
737 gdk_notify_startup_complete();
739 g_printerr(_("Exiting because another libpurple client is already running.\n"));
740 g_free(segfault_message
);
744 /* load plugins we had when we quit */
745 purple_plugins_load_saved(PIDGIN_PREFS_ROOT
"/plugins/loaded");
750 pidgin_session_init(argv
[0], opt_session_arg
, opt_config_dir_arg
);
752 g_free(opt_session_arg
);
753 opt_session_arg
= NULL
;
754 g_free(opt_config_dir_arg
);
755 opt_config_dir_arg
= NULL
;
757 /* This needs to be before purple_blist_show() so the
758 * statusbox gets the forced online status. */
759 if (opt_force_online
)
760 purple_network_force_online();
763 * We want to show the blist early in the init process so the
764 * user feels warm and fuzzy (not cold and prickley).
768 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/debug/enabled"))
769 pidgin_debug_window_show();
772 /* disable all accounts */
773 for (accounts
= purple_accounts_get_all(); accounts
!= NULL
; accounts
= accounts
->next
) {
774 PurpleAccount
*account
= accounts
->data
;
775 purple_account_set_enabled(account
, PIDGIN_UI
, FALSE
);
777 /* honor the startup status preference */
778 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
779 purple_savedstatus_activate(purple_savedstatus_get_startup());
780 /* now enable the requested ones */
781 dologin_named(opt_login_arg
);
782 g_free(opt_login_arg
);
783 opt_login_arg
= NULL
;
784 } else if (opt_nologin
) {
785 /* Set all accounts to "offline" */
786 PurpleSavedStatus
*saved_status
;
788 /* If we've used this type+message before, lookup the transient status */
789 saved_status
= purple_savedstatus_find_transient_by_type_and_message(
790 PURPLE_STATUS_OFFLINE
, NULL
);
792 /* If this type+message is unique then create a new transient saved status */
793 if (saved_status
== NULL
)
794 saved_status
= purple_savedstatus_new(NULL
, PURPLE_STATUS_OFFLINE
);
796 /* Set the status for each account */
797 purple_savedstatus_activate(saved_status
);
799 /* Everything is good to go--sign on already */
800 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
801 purple_savedstatus_activate(purple_savedstatus_get_startup());
802 purple_accounts_restore_current_statuses();
805 if ((active_accounts
= purple_accounts_get_all_active()) == NULL
)
807 pidgin_accounts_window_show();
811 g_list_free(active_accounts
);
814 /* GTK clears the notification for us when opening the first window,
815 * but we may have launched with only a status icon, so clear the it
817 gdk_notify_startup_complete();
820 winpidgin_post_init();
825 g_free(segfault_message
);
826 g_source_remove(signal_channel_watcher
);
827 close(signal_sockets
[0]);
828 close(signal_sockets
[1]);