]> git.0d.be Git - empathy.git/blob - src/empathy.c
rename the chat handler to Empathy.Chat
[empathy.git] / src / empathy.c
1 /*
2  * Copyright (C) 2007-2009 Collabora Ltd.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include <config.h>
23
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <string.h>
27
28 #include <glib.h>
29 #include <glib/gstdio.h>
30 #include <glib/gi18n.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkx.h>
33
34 #ifdef HAVE_LIBCHAMPLAIN
35 #include <clutter-gtk/clutter-gtk.h>
36 #endif
37
38 #include <libnotify/notify.h>
39
40 #include <telepathy-glib/account-manager.h>
41 #include <telepathy-glib/dbus.h>
42 #include <telepathy-glib/debug-sender.h>
43 #include <telepathy-glib/util.h>
44 #include <telepathy-glib/connection-manager.h>
45 #include <telepathy-glib/interfaces.h>
46
47 #include <telepathy-logger/log-manager.h>
48
49 #include <libempathy/empathy-idle.h>
50 #include <libempathy/empathy-utils.h>
51 #include <libempathy/empathy-chatroom-manager.h>
52 #include <libempathy/empathy-account-settings.h>
53 #include <libempathy/empathy-connectivity.h>
54 #include <libempathy/empathy-connection-managers.h>
55 #include <libempathy/empathy-dispatcher.h>
56 #include <libempathy/empathy-ft-factory.h>
57 #include <libempathy/empathy-gsettings.h>
58 #include <libempathy/empathy-tp-chat.h>
59
60 #include <libempathy-gtk/empathy-ui-utils.h>
61 #include <libempathy-gtk/empathy-location-manager.h>
62
63 #include "empathy-main-window.h"
64 #include "empathy-accounts-common.h"
65 #include "empathy-accounts-dialog.h"
66 #include "empathy-chat-manager.h"
67 #include "empathy-status-icon.h"
68 #include "empathy-ft-manager.h"
69
70 #include "extensions/extensions.h"
71
72 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
73 #include <libempathy/empathy-debug.h>
74
75 #define EMPATHY_DBUS_NAME "org.gnome.Empathy"
76
77 #define EMPATHY_TYPE_APP (empathy_app_get_type ())
78 #define EMPATHY_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EMPATHY_TYPE_APP, EmpathyApp))
79 #define EMPATHY_APP_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((obj), EMPATHY_TYPE_APP, EmpathyAppClass))
80 #define EMPATHY_IS_EMPATHY_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EMPATHY_TYPE_APP))
81 #define EMPATHY_IS_EMPATHY_APP_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), EMPATHY_TYPE_APP))
82 #define EMPATHY_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_APP, EmpathyAppClass))
83
84 typedef struct _EmpathyApp EmpathyApp;
85 typedef struct _EmpathyAppClass EmpathyAppClass;
86
87 enum
88 {
89   PROP_NO_CONNECT = 1,
90   PROP_START_HIDDEN
91 };
92
93 GType empathy_app_get_type (void);
94
95 struct _EmpathyAppClass
96 {
97   GtkApplicationClass parent_class;
98 };
99
100 struct _EmpathyApp
101 {
102   GtkApplication parent;
103
104   /* Properties */
105   gboolean no_connect;
106   gboolean start_hidden;
107
108   GtkWidget *window;
109   EmpathyStatusIcon *icon;
110   EmpathyDispatcher *dispatcher;
111   TpAccountManager *account_manager;
112   TplLogManager *log_manager;
113   EmpathyChatroomManager *chatroom_manager;
114   EmpathyFTFactory  *ft_factory;
115   EmpathyIdle *idle;
116   EmpathyConnectivity *connectivity;
117   EmpathyChatManager *chat_manager;
118   GSettings *gsettings;
119 #ifdef HAVE_GEOCLUE
120   EmpathyLocationManager *location_manager;
121 #endif
122 #ifdef ENABLE_DEBUG
123   TpDebugSender *debug_sender;
124 #endif
125 };
126
127
128 G_DEFINE_TYPE(EmpathyApp, empathy_app, GTK_TYPE_APPLICATION)
129
130 static void
131 empathy_app_dispose (GObject *object)
132 {
133   EmpathyApp *self = EMPATHY_APP (object);
134   void (*dispose) (GObject *) =
135     G_OBJECT_CLASS (empathy_app_parent_class)->dispose;
136
137   if (self->idle != NULL)
138     {
139       empathy_idle_set_state (self->idle, TP_CONNECTION_PRESENCE_TYPE_OFFLINE);
140     }
141
142 #ifdef ENABLE_DEBUG
143   tp_clear_object (&self->debug_sender);
144 #endif
145
146   tp_clear_object (&self->chat_manager);
147   tp_clear_object (&self->idle);
148   tp_clear_object (&self->connectivity);
149   tp_clear_object (&self->icon);
150   tp_clear_object (&self->account_manager);
151   tp_clear_object (&self->log_manager);
152   tp_clear_object (&self->dispatcher);
153   tp_clear_object (&self->chatroom_manager);
154 #ifdef HAVE_GEOCLUE
155   tp_clear_object (&self->location_manager);
156 #endif
157   tp_clear_object (&self->ft_factory);
158   tp_clear_object (&self->gsettings);
159
160   if (dispose != NULL)
161     dispose (object);
162 }
163
164 static void
165 empathy_app_finalize (GObject *object)
166 {
167   EmpathyApp *self = EMPATHY_APP (object);
168   void (*finalize) (GObject *) =
169     G_OBJECT_CLASS (empathy_app_parent_class)->finalize;
170
171   gtk_widget_destroy (self->window);
172
173   if (finalize != NULL)
174     finalize (object);
175 }
176
177 static void account_manager_ready_cb (GObject *source_object,
178     GAsyncResult *result,
179     gpointer user_data);
180
181 static EmpathyApp *
182 empathy_app_new (guint argc,
183     const gchar * const * argv,
184     gboolean no_connect,
185     gboolean start_hidden)
186 {
187   EmpathyApp *self;
188   GError *error = NULL;
189   GVariant *argv_variant;
190
191   argv_variant = g_variant_new_bytestring_array (argv, argc);
192
193   self = g_object_new (EMPATHY_TYPE_APP,
194       "application-id", EMPATHY_DBUS_NAME,
195       "flags", G_APPLICATION_IS_SERVICE,
196       NULL);
197
198   if (self == NULL)
199     {
200       g_critical ("Failed to initiate EmpathyApp: %s", error->message);
201       g_error_free (error);
202     }
203
204   return self;
205 }
206
207 static void
208 empathy_app_set_property (GObject *object,
209     guint prop_id,
210     const GValue *value,
211     GParamSpec *pspec)
212 {
213   EmpathyApp *self = EMPATHY_APP (object);
214
215   switch (prop_id)
216     {
217       case PROP_NO_CONNECT:
218         self->no_connect = g_value_get_boolean (value);
219         break;
220       case PROP_START_HIDDEN:
221         self->start_hidden = g_value_get_boolean (value);
222         break;
223       default:
224         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
225         break;
226     }
227 }
228
229 static void
230 empathy_app_activate (GApplication *app)
231 {
232   EmpathyApp *self = (EmpathyApp *) app;
233
234   /* We're requested to show stuff again, disable the start hidden global
235    * in case the accounts wizard wants to pop up.
236    */
237   self->start_hidden = FALSE;
238
239   empathy_window_present (GTK_WINDOW (self->window));
240
241   /* Display the accounts dialog if needed */
242   tp_account_manager_prepare_async (self->account_manager, NULL,
243       account_manager_ready_cb, self);
244 }
245
246 static void empathy_app_constructed (GObject *object);
247
248 static void
249 empathy_app_class_init (EmpathyAppClass *klass)
250 {
251   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
252   GApplicationClass *g_app_class = G_APPLICATION_CLASS (klass);
253   GParamSpec *spec;
254
255   gobject_class->set_property = empathy_app_set_property;
256   gobject_class->constructed = empathy_app_constructed;
257   gobject_class->dispose = empathy_app_dispose;
258   gobject_class->finalize = empathy_app_finalize;
259
260   g_app_class->activate = empathy_app_activate;
261
262   spec = g_param_spec_boolean ("no-connect", "no connect",
263       "Don't connect on startup",
264       FALSE,
265       G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
266   g_object_class_install_property (gobject_class, PROP_NO_CONNECT, spec);
267
268   spec = g_param_spec_boolean ("start-hidden", "start hidden",
269       "Don't display the contact list or any other dialogs on startup",
270       FALSE,
271       G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
272   g_object_class_install_property (gobject_class, PROP_START_HIDDEN, spec);
273 }
274
275 static void
276 empathy_app_init (EmpathyApp *self)
277 {
278 }
279
280 static void
281 use_conn_notify_cb (GSettings *gsettings,
282     const gchar *key,
283     gpointer     user_data)
284 {
285   EmpathyConnectivity *connectivity = user_data;
286
287   empathy_connectivity_set_use_conn (connectivity,
288       g_settings_get_boolean (gsettings, key));
289 }
290
291 static void
292 migrate_config_to_xdg_dir (void)
293 {
294   gchar *xdg_dir, *old_dir, *xdg_filename, *old_filename;
295   int i;
296   GFile *xdg_file, *old_file;
297   static const gchar* filenames[] = {
298     "geometry.ini",
299     "irc-networks.xml",
300     "chatrooms.xml",
301     "contact-groups.xml",
302     "status-presets.xml",
303     "accels.txt",
304     NULL
305   };
306
307   xdg_dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
308   if (g_file_test (xdg_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
309     {
310       /* xdg config dir already exists */
311       g_free (xdg_dir);
312       return;
313     }
314
315   old_dir = g_build_filename (g_get_home_dir (), ".gnome2",
316       PACKAGE_NAME, NULL);
317   if (!g_file_test (old_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
318     {
319       /* old config dir didn't exist */
320       g_free (xdg_dir);
321       g_free (old_dir);
322       return;
323     }
324
325   if (g_mkdir_with_parents (xdg_dir, (S_IRUSR | S_IWUSR | S_IXUSR)) == -1)
326     {
327       DEBUG ("Failed to create configuration directory; aborting migration");
328       g_free (xdg_dir);
329       g_free (old_dir);
330       return;
331     }
332
333   for (i = 0; filenames[i]; i++)
334     {
335       old_filename = g_build_filename (old_dir, filenames[i], NULL);
336       if (!g_file_test (old_filename, G_FILE_TEST_EXISTS))
337         {
338           g_free (old_filename);
339           continue;
340         }
341       xdg_filename = g_build_filename (xdg_dir, filenames[i], NULL);
342       old_file = g_file_new_for_path (old_filename);
343       xdg_file = g_file_new_for_path (xdg_filename);
344
345       if (!g_file_move (old_file, xdg_file, G_FILE_COPY_NONE,
346           NULL, NULL, NULL, NULL))
347         DEBUG ("Failed to migrate %s", filenames[i]);
348
349       g_free (old_filename);
350       g_free (xdg_filename);
351       g_object_unref (old_file);
352       g_object_unref (xdg_file);
353     }
354
355   g_free (xdg_dir);
356   g_free (old_dir);
357 }
358
359 static void
360 show_accounts_ui (EmpathyApp *self,
361     GdkScreen *screen,
362     gboolean if_needed)
363 {
364   empathy_accounts_dialog_show_application (screen,
365       NULL, if_needed, self->start_hidden);
366 }
367
368 static gboolean
369 show_version_cb (const char *option_name,
370     const char *value,
371     gpointer data,
372     GError **error)
373 {
374   g_print ("%s\n", PACKAGE_STRING);
375
376   exit (EXIT_SUCCESS);
377 }
378
379 static void
380 new_incoming_transfer_cb (EmpathyFTFactory *factory,
381     EmpathyFTHandler *handler,
382     GError *error,
383     gpointer user_data)
384 {
385   if (error)
386     empathy_ft_manager_display_error (handler, error);
387   else
388     empathy_receive_file_with_file_chooser (handler);
389 }
390
391 static void
392 new_ft_handler_cb (EmpathyFTFactory *factory,
393     EmpathyFTHandler *handler,
394     GError *error,
395     gpointer user_data)
396 {
397   if (error)
398     empathy_ft_manager_display_error (handler, error);
399   else
400     empathy_ft_manager_add_handler (handler);
401
402   g_object_unref (handler);
403 }
404
405 static void
406 account_manager_ready_cb (GObject *source_object,
407     GAsyncResult *result,
408     gpointer user_data)
409 {
410   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
411   EmpathyApp *self = user_data;
412   GError *error = NULL;
413   TpConnectionPresenceType presence;
414
415   if (!tp_account_manager_prepare_finish (manager, result, &error))
416     {
417       GtkWidget *dialog;
418
419       DEBUG ("Failed to prepare account manager: %s", error->message);
420
421       dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
422           GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
423           _("Error contacting the Account Manager"));
424       gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
425           _("There was an error while trying to connect to the Telepathy "
426             "Account Manager. The error was:\n\n%s"),
427           error->message);
428
429       gtk_dialog_run (GTK_DIALOG (dialog));
430       gtk_widget_destroy (dialog);
431
432       g_error_free (error);
433       return;
434     }
435
436   /* Autoconnect */
437   presence = tp_account_manager_get_most_available_presence (manager, NULL,
438       NULL);
439
440   if (g_settings_get_boolean (self->gsettings, EMPATHY_PREFS_AUTOCONNECT) &&
441       !self->no_connect &&
442       tp_connection_presence_type_cmp_availability
443           (presence, TP_CONNECTION_PRESENCE_TYPE_OFFLINE)
444             <= 0)
445       /* if current state is Offline, then put it online */
446       empathy_idle_set_state (self->idle,
447           TP_CONNECTION_PRESENCE_TYPE_AVAILABLE);
448
449   /* Pop up the accounts dialog if we don't have any account */
450   if (!empathy_accounts_has_accounts (manager))
451     show_accounts_ui (self, gdk_screen_get_default (), TRUE);
452 }
453
454 static void
455 account_status_changed_cb (TpAccount *account,
456     guint old_status,
457     guint new_status,
458     guint reason,
459     gchar *dbus_error_name,
460     GHashTable *details,
461     EmpathyChatroom *room)
462 {
463   if (new_status != TP_CONNECTION_STATUS_CONNECTED)
464     return;
465
466   empathy_dispatcher_join_muc (account,
467       empathy_chatroom_get_room (room), TP_USER_ACTION_TIME_NOT_USER_ACTION);
468 }
469
470 static void
471 account_manager_chatroom_ready_cb (GObject *source_object,
472     GAsyncResult *result,
473     gpointer user_data)
474 {
475   TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
476   EmpathyChatroomManager *chatroom_manager = user_data;
477   GList *accounts, *l;
478   GError *error = NULL;
479
480   if (!tp_account_manager_prepare_finish (account_manager, result, &error))
481     {
482       DEBUG ("Failed to prepare account manager: %s", error->message);
483       g_error_free (error);
484       return;
485     }
486
487   accounts = tp_account_manager_get_valid_accounts (account_manager);
488
489   for (l = accounts; l != NULL; l = g_list_next (l))
490     {
491       TpAccount *account = TP_ACCOUNT (l->data);
492       TpConnection *conn;
493       GList *chatrooms, *p;
494
495       conn = tp_account_get_connection (account);
496
497       chatrooms = empathy_chatroom_manager_get_chatrooms (
498           chatroom_manager, account);
499
500       for (p = chatrooms; p != NULL; p = p->next)
501         {
502           EmpathyChatroom *room = EMPATHY_CHATROOM (p->data);
503
504           if (!empathy_chatroom_get_auto_connect (room))
505             continue;
506
507           if (conn == NULL)
508             {
509               g_signal_connect (G_OBJECT (account), "status-changed",
510                   G_CALLBACK (account_status_changed_cb), room);
511             }
512           else
513             {
514               empathy_dispatcher_join_muc (account,
515                   empathy_chatroom_get_room (room),
516                   TP_USER_ACTION_TIME_NOT_USER_ACTION);
517             }
518         }
519
520       g_list_free (chatrooms);
521     }
522
523   g_list_free (accounts);
524 }
525
526 static void
527 chatroom_manager_ready_cb (EmpathyChatroomManager *chatroom_manager,
528     GParamSpec *pspec,
529     gpointer user_data)
530 {
531   TpAccountManager *account_manager = user_data;
532
533   tp_account_manager_prepare_async (account_manager, NULL,
534       account_manager_chatroom_ready_cb, chatroom_manager);
535 }
536
537 static void
538 empathy_idle_set_auto_away_cb (GSettings *gsettings,
539                                 const gchar *key,
540                                 gpointer user_data)
541 {
542         EmpathyIdle *idle = user_data;
543
544         empathy_idle_set_auto_away (idle,
545       g_settings_get_boolean (gsettings, key));
546 }
547
548 static void
549 empathy_app_constructed (GObject *object)
550 {
551   EmpathyApp *self = (EmpathyApp *) object;
552   GError *error = NULL;
553   gboolean chatroom_manager_ready;
554   gboolean autoaway;
555
556   g_set_application_name (_(PACKAGE_NAME));
557
558   gtk_window_set_default_icon_name ("empathy");
559   textdomain (GETTEXT_PACKAGE);
560
561 #ifdef ENABLE_DEBUG
562   /* Set up debug sender */
563   self->debug_sender = tp_debug_sender_dup ();
564   g_log_set_default_handler (tp_debug_sender_log_handler, G_LOG_DOMAIN);
565 #endif
566
567   notify_init (_(PACKAGE_NAME));
568
569   /* Setting up Idle */
570   self->idle = empathy_idle_dup_singleton ();
571
572   self->gsettings = g_settings_new (EMPATHY_PREFS_SCHEMA);
573   autoaway = g_settings_get_boolean (self->gsettings, EMPATHY_PREFS_AUTOAWAY);
574
575   g_signal_connect (self->gsettings,
576       "changed::" EMPATHY_PREFS_AUTOAWAY,
577       G_CALLBACK (empathy_idle_set_auto_away_cb), self->idle);
578
579   empathy_idle_set_auto_away (self->idle, autoaway);
580
581   /* Setting up Connectivity */
582   self->connectivity = empathy_connectivity_dup_singleton ();
583   use_conn_notify_cb (self->gsettings, EMPATHY_PREFS_USE_CONN,
584       self->connectivity);
585   g_signal_connect (self->gsettings,
586       "changed::" EMPATHY_PREFS_USE_CONN,
587       G_CALLBACK (use_conn_notify_cb), self->connectivity);
588
589   /* account management */
590   self->account_manager = tp_account_manager_dup ();
591   tp_account_manager_prepare_async (self->account_manager, NULL,
592       account_manager_ready_cb, self);
593
594   /* The EmpathyDispatcher doesn't dispatch anything any more but we have to
595    * keep it around as we still use it to request channels */
596   self->dispatcher = empathy_dispatcher_dup_singleton ();
597
598   migrate_config_to_xdg_dir ();
599
600   /* Setting up UI */
601   self->window = empathy_main_window_dup ();
602   gtk_widget_show (self->window);
603   self->icon = empathy_status_icon_new (GTK_WINDOW (self->window),
604       self->start_hidden);
605
606   /* Chat manager */
607   self->chat_manager = empathy_chat_manager_dup_singleton ();
608
609   /* Logging */
610   self->log_manager = tpl_log_manager_dup_singleton ();
611
612   self->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
613
614   g_object_get (self->chatroom_manager, "ready", &chatroom_manager_ready, NULL);
615   if (!chatroom_manager_ready)
616     {
617       g_signal_connect (G_OBJECT (self->chatroom_manager), "notify::ready",
618           G_CALLBACK (chatroom_manager_ready_cb), self->account_manager);
619     }
620   else
621     {
622       chatroom_manager_ready_cb (self->chatroom_manager, NULL,
623           self->account_manager);
624     }
625
626   /* Create the FT factory */
627   self->ft_factory = empathy_ft_factory_dup_singleton ();
628   g_signal_connect (self->ft_factory, "new-ft-handler",
629       G_CALLBACK (new_ft_handler_cb), NULL);
630   g_signal_connect (self->ft_factory, "new-incoming-transfer",
631       G_CALLBACK (new_incoming_transfer_cb), NULL);
632
633   if (!empathy_ft_factory_register (self->ft_factory, &error))
634     {
635       g_warning ("Failed to register FileTransfer handler: %s", error->message);
636       g_error_free (error);
637     }
638
639   /* Location mananger */
640 #ifdef HAVE_GEOCLUE
641   self->location_manager = empathy_location_manager_dup_singleton ();
642 #endif
643 }
644
645 int
646 main (int argc, char *argv[])
647 {
648   EmpathyApp *app;
649   GError *error = NULL;
650   GOptionContext *optcontext;
651   gboolean no_connect = FALSE, start_hidden = FALSE;
652   GOptionEntry options[] = {
653       { "no-connect", 'n',
654         0, G_OPTION_ARG_NONE, &no_connect,
655         N_("Don't connect on startup"),
656         NULL },
657       { "start-hidden", 'h',
658         0, G_OPTION_ARG_NONE, &start_hidden,
659         N_("Don't display the contact list or any other dialogs on startup"),
660         NULL },
661       { "version", 'v',
662         G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_version_cb,
663         NULL, NULL },
664       { NULL }
665   };
666
667   g_thread_init (NULL);
668   g_type_init ();
669
670 #ifdef HAVE_LIBCHAMPLAIN
671   gtk_clutter_init (&argc, &argv);
672 #endif
673
674   empathy_init ();
675
676   optcontext = g_option_context_new (N_("- Empathy IM Client"));
677   g_option_context_add_group (optcontext, gtk_get_option_group (TRUE));
678   g_option_context_add_main_entries (optcontext, options, GETTEXT_PACKAGE);
679
680   if (!g_option_context_parse (optcontext, &argc, &argv, &error)) {
681     g_print ("%s\nRun '%s --help' to see a full list of available command line options.\n",
682         error->message, argv[0]);
683     g_warning ("Error in empathy init: %s", error->message);
684     return EXIT_FAILURE;
685   }
686
687   g_option_context_free (optcontext);
688
689   empathy_gtk_init ();
690
691   app = empathy_app_new (argc, (const gchar * const *) argv,
692       no_connect, start_hidden);
693
694   g_application_hold (G_APPLICATION (app));
695   g_application_run (G_APPLICATION (app), argc, argv);
696
697   notify_uninit ();
698   xmlCleanupParser ();
699
700   g_object_unref (app);
701   return EXIT_SUCCESS;
702 }