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 "glibcompat.h"
42 #include "whiteboard.h"
45 #include "gtkaccount.h"
50 #include "gtkdialogs.h"
51 #include "gtkdocklet.h"
56 #include "gtknotify.h"
57 #include "gtkplugin.h"
58 #include "gtkpounce.h"
60 #include "gtkprivacy.h"
61 #include "gtkrequest.h"
62 #include "gtkroomlist.h"
63 #include "gtksavedstatuses.h"
64 #include "gtksmiley-theme.h"
67 #include "pidginstock.h"
68 #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
[] = {
98 dologin_named(const char *name
)
100 PurpleAccount
*account
;
104 if (name
!= NULL
) { /* list of names given */
105 names
= g_strsplit(name
, ",", 64);
106 for (i
= 0; names
[i
] != NULL
; i
++) {
107 account
= purple_accounts_find(names
[i
], NULL
);
108 if (account
!= NULL
) { /* found a user */
109 purple_account_set_enabled(account
, PIDGIN_UI
, TRUE
);
113 } else { /* no name given, use the first account */
116 accounts
= purple_accounts_get_all();
117 if (accounts
!= NULL
)
119 account
= (PurpleAccount
*)accounts
->data
;
120 purple_account_set_enabled(account
, PIDGIN_UI
, TRUE
);
126 static char *segfault_message
;
128 static int signal_sockets
[2];
130 static void sighandler(int sig
);
132 static void sighandler(int sig
)
137 * We won't do any of the heavy lifting for the signal handling here
138 * because we have no idea what was interrupted. Previously this signal
139 * handler could result in some calls to malloc/free, which can cause
140 * deadlock in libc when the signal handler was interrupting a previous
141 * malloc or free. So instead we'll do an ugly hack where we write the
142 * signal number to one end of a socket pair. The other half of the
143 * socket pair is watched by our main loop. When the main loop sees new
144 * data on the socket it reads in the signal and performs the appropriate
145 * action without fear of interrupting stuff.
147 if (sig
== SIGSEGV
) {
148 fprintf(stderr
, "%s", segfault_message
);
153 written
= write(signal_sockets
[0], &sig
, sizeof(int));
154 if (written
< 0 || written
!= sizeof(int)) {
155 /* This should never happen */
156 purple_debug_error("sighandler", "Received signal %d but only "
157 "wrote %" G_GSSIZE_FORMAT
" bytes out of %"
158 G_GSIZE_FORMAT
": %s\n",
159 sig
, written
, sizeof(int), g_strerror(errno
));
165 mainloop_sighandler(GIOChannel
*source
, GIOCondition cond
, gpointer data
)
170 GError
*error
= NULL
;
172 /* read the signal number off of the io channel */
173 stat
= g_io_channel_read_chars(source
, (gchar
*)&sig
, sizeof(int),
174 &bytes_read
, &error
);
175 if (stat
!= G_IO_STATUS_NORMAL
) {
176 purple_debug_error("sighandler", "Signal callback failed to read "
177 "from signal socket: %s", error
->message
);
184 /* Restore signal catching */
185 signal(SIGCHLD
, sighandler
);
188 purple_debug_warning("sighandler", "Caught signal %d\n", sig
);
201 GdkPixbuf
*icon
= NULL
;
206 const char *filename
;
208 {"16x16", "pidgin.png"},
209 {"24x24", "pidgin.png"},
210 {"32x32", "pidgin.png"},
211 {"48x48", "pidgin.png"},
212 {"scalable", "pidgin.svg"}
217 pidgin_blist_setup_sort_methods();
220 /* use the nice PNG icon for all the windows */
221 for(i
=0; i
<G_N_ELEMENTS(icon_sizes
); i
++) {
222 icon_path
= g_build_filename(PURPLE_DATADIR
, "icons", "hicolor",
223 icon_sizes
[i
].dir
, "apps", icon_sizes
[i
].filename
, NULL
);
224 icon
= pidgin_pixbuf_new_from_file(icon_path
);
227 icons
= g_list_append(icons
,icon
);
229 purple_debug_error("ui_main",
230 "Failed to load the default window icon (%spx version)!\n", icon_sizes
[i
].dir
);
234 purple_debug_error("ui_main", "Unable to load any size of default window icon!\n");
236 gtk_window_set_default_icon_list(icons
);
238 g_list_foreach(icons
, (GFunc
)g_object_unref
, NULL
);
249 purple_debug_set_ui_ops(pidgin_debug_get_ui_ops());
258 /* Set the UI operation structures. */
259 purple_accounts_set_ui_ops(pidgin_accounts_get_ui_ops());
260 purple_xfers_set_ui_ops(pidgin_xfers_get_ui_ops());
261 purple_blist_set_ui_ops(pidgin_blist_get_ui_ops());
262 purple_notify_set_ui_ops(pidgin_notify_get_ui_ops());
263 purple_request_set_ui_ops(pidgin_request_get_ui_ops());
264 purple_sound_set_ui_ops(pidgin_sound_get_ui_ops());
265 purple_connections_set_ui_ops(pidgin_connections_get_ui_ops());
266 purple_whiteboard_set_ui_ops(pidgin_whiteboard_get_ui_ops());
267 #if defined(USE_SCREENSAVER) || defined(HAVE_IOKIT)
268 purple_idle_set_ui_ops(pidgin_idle_get_ui_ops());
271 pidgin_accounts_init();
272 pidgin_connection_init();
273 pidgin_request_init();
275 pidgin_status_init();
276 pidgin_conversations_init();
277 pidgin_pounces_init();
278 pidgin_privacy_init();
280 pidgin_roomlist_init();
282 pidgin_docklet_init();
283 _pidgin_smiley_theme_init();
285 pidgin_medias_init();
286 pidgin_notify_init();
289 static GHashTable
*ui_info
= NULL
;
295 pidgin_utils_uninit();
296 pidgin_notify_uninit();
297 _pidgin_smiley_theme_uninit();
298 pidgin_conversations_uninit();
299 pidgin_status_uninit();
300 pidgin_docklet_uninit();
301 pidgin_blist_uninit();
302 pidgin_request_uninit();
303 pidgin_connection_uninit();
304 pidgin_accounts_uninit();
305 pidgin_xfers_uninit();
306 pidgin_debug_uninit();
309 g_hash_table_destroy(ui_info
);
311 /* and end it all... */
312 g_application_quit(g_application_get_default());
315 static GHashTable
*pidgin_ui_get_info(void)
317 if(NULL
== ui_info
) {
318 ui_info
= g_hash_table_new(g_str_hash
, g_str_equal
);
320 g_hash_table_insert(ui_info
, "name", (char*)PIDGIN_NAME
);
321 g_hash_table_insert(ui_info
, "version", VERSION
);
322 g_hash_table_insert(ui_info
, "website", "https://pidgin.im");
323 g_hash_table_insert(ui_info
, "dev_website", "https://developer.pidgin.im");
324 g_hash_table_insert(ui_info
, "client_type", "pc");
327 * prpl-aim-clientkey is a DevID (or "client key") for Pidgin, given to
328 * us by AOL in September 2016. prpl-icq-clientkey is also a client key
329 * for Pidgin, owned by the AIM account "markdoliner." Please don't use
330 * either for other applications. Instead, you can either not specify a
331 * client key, in which case the default "libpurple" key will be used,
332 * or you can try to register your own at the AIM or ICQ web sites
333 * (although this functionality was removed at some point, it's possible
334 * it has been re-added).
336 g_hash_table_insert(ui_info
, "prpl-aim-clientkey", "do1UCeb5gNqxB1S1");
337 g_hash_table_insert(ui_info
, "prpl-icq-clientkey", "ma1cSASNCKFtrdv9");
340 * prpl-aim-distid is a distID for Pidgin, given to us by AOL in
341 * September 2016. prpl-icq-distid is also a distID for Pidgin, given
342 * to us by AOL. Please don't use either for other applications.
343 * Instead, you can just not specify a distID and libpurple will use a
346 g_hash_table_insert(ui_info
, "prpl-aim-distid", GINT_TO_POINTER(1715));
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 pidgin_activate_cb(GApplication
*application
, gpointer user_data
)
375 purple_blist_set_visible(TRUE
);
379 show_usage(const char *name
, gboolean terse
)
384 text
= g_strdup_printf(_("%s %s. Try `%s -h' for more information.\n"), PIDGIN_NAME
, DISPLAY_VERSION
, name
);
386 GString
*str
= g_string_new(NULL
);
387 g_string_append_printf(str
, "%s %s\n", PIDGIN_NAME
, DISPLAY_VERSION
);
388 g_string_append_printf(str
, _("Usage: %s [OPTION]...\n\n"), name
);
389 g_string_append_printf(str
, " -c, --config=%s %s\n",
390 _("DIR"), _("use DIR for config files"));
391 g_string_append_printf(str
, " -d, --debug[=colored] %s\n",
392 _("print debugging messages to stdout"));
393 g_string_append_printf(str
, " -f, --force-online %s\n",
394 _("force online, regardless of network status"));
395 g_string_append_printf(str
, " -h, --help %s\n",
396 _("display this help and exit"));
397 g_string_append_printf(str
, " -m, --multiple %s\n",
398 _("allow multiple instances"));
399 g_string_append_printf(str
, " -n, --nologin %s\n",
400 _("don't automatically login"));
401 g_string_append_printf(str
, " -l, --login[=%s] %s\n",
403 _("enable specified account(s) (optional argument NAME\n"
405 "specifies account(s) to use, separated by commas.\n"
407 "Without this only the first account will be enabled)."));
409 g_string_append_printf(str
, " --display=DISPLAY %s\n",
410 _("X display to use"));
412 g_string_append_printf(str
, " -v, --version %s\n",
413 _("display the current version and exit"));
414 text
= g_string_free(str
, FALSE
);
417 purple_print_utf8_to_console(stdout
, text
);
421 int pidgin_start(int argc
, char *argv
[])
424 gboolean opt_force_online
= FALSE
;
425 gboolean opt_help
= FALSE
;
426 gboolean opt_login
= FALSE
;
427 gboolean opt_nologin
= FALSE
;
428 gboolean opt_version
= FALSE
;
429 gboolean opt_si
= TRUE
; /* Check for single instance? */
430 char *opt_config_dir_arg
= NULL
;
431 char *opt_login_arg
= NULL
;
432 char *opt_session_arg
= NULL
;
434 GtkCssProvider
*provider
;
438 int sig_indx
; /* for setting up signal catching */
441 GIOChannel
*signal_channel
;
442 GIOStatus signal_status
;
443 guint signal_channel_watcher
;
445 char *segfault_message_tmp
;
450 gboolean debug_enabled
, debug_colored
;
451 GList
*active_accounts
;
456 struct option long_options
[] = {
457 {"config", required_argument
, NULL
, 'c'},
458 {"debug", optional_argument
, NULL
, 'd'},
459 {"force-online", no_argument
, NULL
, 'f'},
460 {"help", no_argument
, NULL
, 'h'},
461 {"login", optional_argument
, NULL
, 'l'},
462 {"multiple", no_argument
, NULL
, 'm'},
463 {"nologin", no_argument
, NULL
, 'n'},
464 {"session", required_argument
, NULL
, 's'},
465 {"version", no_argument
, NULL
, 'v'},
466 {"display", required_argument
, NULL
, 'D'},
467 {"sync", no_argument
, NULL
, 'S'},
471 debug_colored
= FALSE
;
473 debug_enabled
= TRUE
;
475 debug_enabled
= FALSE
;
479 bindtextdomain(PACKAGE
, PURPLE_LOCALEDIR
);
480 bind_textdomain_codeset(PACKAGE
, "UTF-8");
484 /* Locale initialization is not complete here. See gtk_init_check() */
485 setlocale(LC_ALL
, "");
490 /* We translate this here in case the crash breaks gettext. */
491 segfault_message_tmp
= g_strdup_printf(_(
492 "%s %s has segfaulted and attempted to dump a core file.\n"
493 "This is a bug in the software and has happened through\n"
494 "no fault of your own.\n\n"
495 "If you can reproduce the crash, please notify the developers\n"
496 "by reporting a bug at:\n"
497 "%ssimpleticket/\n\n"
498 "Please make sure to specify what you were doing at the time\n"
499 "and post the backtrace from the core file. If you do not know\n"
500 "how to get the backtrace, please read the instructions at\n"
501 "%swiki/GetABacktrace\n"),
502 PIDGIN_NAME
, DISPLAY_VERSION
, PURPLE_DEVEL_WEBSITE
, PURPLE_DEVEL_WEBSITE
505 /* we have to convert the message (UTF-8 to console
506 charset) early because after a segmentation fault
507 it's not a good practice to allocate memory */
509 segfault_message
= g_locale_from_utf8(segfault_message_tmp
,
510 -1, NULL
, NULL
, &error
);
511 if (segfault_message
!= NULL
) {
512 g_free(segfault_message_tmp
);
515 /* use 'segfault_message_tmp' (UTF-8) as a fallback */
516 g_warning("%s\n", error
->message
);
518 segfault_message
= segfault_message_tmp
;
521 /* Don't mark this for translation. */
522 segfault_message
= g_strdup(
523 "Hi, user. We need to talk.\n"
524 "I think something's gone wrong here. It's probably my fault.\n"
525 "No, really, it's not you... it's me... no no no, I think we get along well\n"
526 "it's just that.... well, I want to see other people. I... what?!? NO! I \n"
527 "haven't been cheating on you!! How many times do you want me to tell you?! And\n"
528 "for the last time, it's just a rash!\n"
533 * Create a socket pair for receiving unix signals from a signal
536 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, signal_sockets
) < 0) {
537 perror("Failed to create sockets for GLib signal handling");
540 signal_channel
= g_io_channel_unix_new(signal_sockets
[1]);
543 * Set the channel encoding to raw binary instead of the default of
544 * UTF-8, because we'll be sending integers across instead of strings.
547 signal_status
= g_io_channel_set_encoding(signal_channel
, NULL
, &error
);
548 if (signal_status
!= G_IO_STATUS_NORMAL
) {
549 fprintf(stderr
, "Failed to set the signal channel to raw "
550 "binary: %s", error
->message
);
553 signal_channel_watcher
= g_io_add_watch(signal_channel
, G_IO_IN
, mainloop_sighandler
, NULL
);
554 g_io_channel_unref(signal_channel
);
556 /* Let's not violate any PLA's!!!! */
557 /* jseymour: whatever the fsck that means */
558 /* Robot101: for some reason things like gdm like to block *
559 * useful signals like SIGCHLD, so we unblock all the ones we *
560 * declare a handler for. thanks JSeymour and Vann. */
561 if (sigemptyset(&sigset
)) {
562 snprintf(errmsg
, sizeof(errmsg
), "Warning: couldn't initialise empty signal set");
565 for(sig_indx
= 0; catch_sig_list
[sig_indx
] != -1; ++sig_indx
) {
566 if(signal(catch_sig_list
[sig_indx
], sighandler
) == SIG_ERR
) {
567 snprintf(errmsg
, sizeof(errmsg
), "Warning: couldn't set signal %d for catching",
568 catch_sig_list
[sig_indx
]);
571 if(sigaddset(&sigset
, catch_sig_list
[sig_indx
])) {
572 snprintf(errmsg
, sizeof(errmsg
), "Warning: couldn't include signal %d for unblocking",
573 catch_sig_list
[sig_indx
]);
577 for(sig_indx
= 0; ignore_sig_list
[sig_indx
] != -1; ++sig_indx
) {
578 if(signal(ignore_sig_list
[sig_indx
], SIG_IGN
) == SIG_ERR
) {
579 snprintf(errmsg
, sizeof(errmsg
), "Warning: couldn't set signal %d to ignore",
580 ignore_sig_list
[sig_indx
]);
585 if (sigprocmask(SIG_UNBLOCK
, &sigset
, NULL
)) {
586 snprintf(errmsg
, sizeof(errmsg
), "Warning: couldn't unblock signals");
591 /* scan command-line options */
593 while ((opt
= getopt_long(argc
, argv
,
599 long_options
, NULL
)) != -1) {
601 case 'c': /* config dir */
602 g_free(opt_config_dir_arg
);
603 opt_config_dir_arg
= g_strdup(optarg
);
605 case 'd': /* debug */
606 debug_enabled
= TRUE
;
607 if (g_strcmp0(optarg
, "colored") == 0)
608 debug_colored
= TRUE
;
610 case 'f': /* force-online */
611 opt_force_online
= TRUE
;
616 case 'n': /* no autologin */
619 case 'l': /* login, option username */
621 g_free(opt_login_arg
);
623 opt_login_arg
= g_strdup(optarg
);
625 case 's': /* use existing session ID */
626 g_free(opt_session_arg
);
627 opt_session_arg
= g_strdup(optarg
);
629 case 'v': /* version */
632 case 'm': /* do not ensure single instance. */
635 case 'D': /* --display */
636 case 'S': /* --sync */
637 /* handled by gtk_init_check below */
639 case '?': /* show terse help */
641 show_usage(argv
[0], TRUE
);
643 g_free(segfault_message
);
650 /* show help message */
652 show_usage(argv
[0], FALSE
);
654 g_free(segfault_message
);
658 /* show version message */
660 printf("%s %s (libpurple %s)\n", PIDGIN_NAME
, DISPLAY_VERSION
,
661 purple_core_get_version());
663 g_free(segfault_message
);
668 /* set a user-specified config directory */
669 if (opt_config_dir_arg
!= NULL
) {
670 if (g_path_is_absolute(opt_config_dir_arg
)) {
671 purple_util_set_user_dir(opt_config_dir_arg
);
673 /* Make an absolute (if not canonical) path */
674 char *cwd
= g_get_current_dir();
675 char *path
= g_build_path(G_DIR_SEPARATOR_S
, cwd
, opt_config_dir_arg
, NULL
);
676 purple_util_set_user_dir(path
);
683 * We're done piddling around with command line arguments.
687 if (g_getenv("PIDGIN_DEBUG_COLORED") != NULL
)
688 debug_colored
= TRUE
;
689 purple_debug_set_enabled(debug_enabled
);
690 purple_debug_set_colored(debug_colored
);
692 /* Call this here as GtkApplication calls gtk_init() in
693 * g_application_register() and we don't necessarily want to exit().
695 gui_check
= gtk_init_check(&argc
, &argv
);
697 const char *display
= gdk_display_get_name(gdk_display_get_default());
699 printf("%s %s\n", PIDGIN_NAME
, DISPLAY_VERSION
);
701 g_warning("cannot open display: %s", display
? display
: "unset");
703 g_free(segfault_message
);
709 app
= G_APPLICATION(gtk_application_new("im.pidgin.Pidgin",
710 G_APPLICATION_NON_UNIQUE
));
712 g_object_set(app
, "register-session", TRUE
, NULL
);
714 g_signal_connect(app
, "activate",
715 G_CALLBACK(pidgin_activate_cb
), NULL
);
717 if (!g_application_register(app
, NULL
, &error
)) {
718 purple_debug_error("gtk",
719 "Unable to register GApplication: %s\n",
721 g_clear_error(&error
);
724 g_free(segfault_message
);
729 search_path
= g_build_filename(purple_user_dir(), "gtk-3.0.css", NULL
);
732 provider
= gtk_css_provider_new();
733 gui_check
= gtk_css_provider_load_from_path(provider
, search_path
, &error
);
735 if (gui_check
&& !error
) {
736 screen
= gdk_screen_get_default();
737 gtk_style_context_add_provider_for_screen(screen
,
738 GTK_STYLE_PROVIDER(provider
),
739 GTK_STYLE_PROVIDER_PRIORITY_USER
);
741 purple_debug_error("gtk", "Unable to load custom gtk-3.0.css: %s\n",
742 error
? error
->message
: "(unknown error)");
751 purple_core_set_ui_ops(pidgin_core_get_ui_ops());
753 if (!purple_core_init(PIDGIN_UI
)) {
755 "Initialization of the libpurple core failed. Dumping core.\n"
756 "Please report this!\n");
758 g_free(segfault_message
);
763 search_path
= g_build_filename(purple_user_dir(), "plugins", NULL
);
764 if (!g_stat(search_path
, &st
))
765 g_mkdir(search_path
, S_IRUSR
| S_IWUSR
| S_IXUSR
);
766 purple_plugins_add_search_path(search_path
);
769 purple_plugins_add_search_path(PIDGIN_LIBDIR
);
770 purple_plugins_refresh();
772 if (opt_si
&& !purple_core_ensure_single_instance()) {
774 DBusConnection
*conn
= purple_dbus_get_connection();
775 DBusMessage
*message
= dbus_message_new_method_call(PURPLE_DBUS_SERVICE
, PURPLE_DBUS_PATH
,
776 PURPLE_DBUS_INTERFACE
, "PurpleBlistSetVisible");
778 dbus_message_append_args(message
, DBUS_TYPE_INT32
, &tr
, DBUS_TYPE_INVALID
);
779 dbus_connection_send_with_reply_and_block(conn
, message
, -1, NULL
);
780 dbus_message_unref(message
);
782 gdk_notify_startup_complete();
784 g_printerr(_("Exiting because another libpurple client is already running.\n"));
786 g_free(segfault_message
);
791 /* load plugins we had when we quit */
792 purple_plugins_load_saved(PIDGIN_PREFS_ROOT
"/plugins/loaded");
796 g_free(opt_session_arg
);
797 opt_session_arg
= NULL
;
798 g_free(opt_config_dir_arg
);
799 opt_config_dir_arg
= NULL
;
801 /* This needs to be before purple_blist_show() so the
802 * statusbox gets the forced online status. */
803 if (opt_force_online
)
804 purple_network_force_online();
807 * We want to show the blist early in the init process so the
808 * user feels warm and fuzzy (not cold and prickley).
812 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/debug/enabled"))
813 pidgin_debug_window_show();
816 /* disable all accounts */
817 for (accounts
= purple_accounts_get_all(); accounts
!= NULL
; accounts
= accounts
->next
) {
818 PurpleAccount
*account
= accounts
->data
;
819 purple_account_set_enabled(account
, PIDGIN_UI
, FALSE
);
821 /* honor the startup status preference */
822 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
823 purple_savedstatus_activate(purple_savedstatus_get_startup());
824 /* now enable the requested ones */
825 dologin_named(opt_login_arg
);
826 g_free(opt_login_arg
);
827 opt_login_arg
= NULL
;
828 } else if (opt_nologin
) {
829 /* Set all accounts to "offline" */
830 PurpleSavedStatus
*saved_status
;
832 /* If we've used this type+message before, lookup the transient status */
833 saved_status
= purple_savedstatus_find_transient_by_type_and_message(
834 PURPLE_STATUS_OFFLINE
, NULL
);
836 /* If this type+message is unique then create a new transient saved status */
837 if (saved_status
== NULL
)
838 saved_status
= purple_savedstatus_new(NULL
, PURPLE_STATUS_OFFLINE
);
840 /* Set the status for each account */
841 purple_savedstatus_activate(saved_status
);
843 /* Everything is good to go--sign on already */
844 if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
845 purple_savedstatus_activate(purple_savedstatus_get_startup());
846 purple_accounts_restore_current_statuses();
849 if ((active_accounts
= purple_accounts_get_all_active()) == NULL
)
851 pidgin_accounts_window_show();
855 g_list_free(active_accounts
);
858 /* GTK clears the notification for us when opening the first window,
859 * but we may have launched with only a status icon, so clear the it
861 gdk_notify_startup_complete();
864 winpidgin_post_init();
867 /* TODO: Use GtkApplicationWindow or add a window instead */
868 g_application_hold(app
);
870 ret
= g_application_run(app
, 0, NULL
);
872 /* Make sure purple has quit in case something in GApplication
873 * has caused g_application_run() to finish on its own. This can
874 * happen, for example, if the desktop session is ending.
876 if (purple_get_core() != NULL
) {
880 /* Now that we're sure purple_core_quit() has been called,
886 g_free(segfault_message
);
887 g_source_remove(signal_channel_watcher
);
888 close(signal_sockets
[0]);
889 close(signal_sockets
[1]);