]> git.0d.be Git - empathy.git/blob - src/empathy.c
empathy: stop handling media channels
[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 #include <unique/unique.h>
34
35 #if HAVE_LIBCHAMPLAIN
36 #include <clutter-gtk/clutter-gtk.h>
37 #endif
38
39 #include <libnotify/notify.h>
40
41 #include <telepathy-glib/account-manager.h>
42 #include <telepathy-glib/dbus.h>
43 #include <telepathy-glib/debug-sender.h>
44 #include <telepathy-glib/util.h>
45 #include <telepathy-glib/connection-manager.h>
46 #include <telepathy-glib/interfaces.h>
47
48 #include <telepathy-logger/log-manager.h>
49
50 #include <libempathy/empathy-idle.h>
51 #include <libempathy/empathy-utils.h>
52 #include <libempathy/empathy-chatroom-manager.h>
53 #include <libempathy/empathy-account-settings.h>
54 #include <libempathy/empathy-connectivity.h>
55 #include <libempathy/empathy-connection-managers.h>
56 #include <libempathy/empathy-dispatcher.h>
57 #include <libempathy/empathy-dispatch-operation.h>
58 #include <libempathy/empathy-ft-factory.h>
59 #include <libempathy/empathy-gsettings.h>
60 #include <libempathy/empathy-tp-chat.h>
61
62 #include <libempathy-gtk/empathy-ui-utils.h>
63 #include <libempathy-gtk/empathy-location-manager.h>
64
65 #include "empathy-main-window.h"
66 #include "empathy-accounts-common.h"
67 #include "empathy-accounts-dialog.h"
68 #include "empathy-chat-manager.h"
69 #include "empathy-status-icon.h"
70 #include "empathy-chat-window.h"
71 #include "empathy-ft-manager.h"
72
73 #include "extensions/extensions.h"
74
75 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
76 #include <libempathy/empathy-debug.h>
77
78 static gboolean start_hidden = FALSE;
79 static gboolean no_connect = FALSE;
80
81 static void account_manager_ready_cb (GObject *source_object,
82     GAsyncResult *result,
83     gpointer user_data);
84
85 static void
86 dispatch_cb (EmpathyDispatcher *dispatcher,
87     EmpathyDispatchOperation *operation,
88     gpointer user_data)
89 {
90   GQuark channel_type;
91
92   channel_type = empathy_dispatch_operation_get_channel_type_id (operation);
93
94   if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_TEXT)
95     {
96       EmpathyTpChat *tp_chat;
97       EmpathyChat   *chat = NULL;
98       const gchar   *id;
99
100       tp_chat = EMPATHY_TP_CHAT
101         (empathy_dispatch_operation_get_channel_wrapper (operation));
102
103       id = empathy_tp_chat_get_id (tp_chat);
104       if (!EMP_STR_EMPTY (id))
105         {
106           TpConnection *connection;
107           TpAccount *account;
108
109           connection = empathy_tp_chat_get_connection (tp_chat);
110           account = empathy_get_account_for_connection (connection);
111           chat = empathy_chat_window_find_chat (account, id);
112         }
113
114       if (chat)
115         {
116           empathy_chat_set_tp_chat (chat, tp_chat);
117         }
118       else
119         {
120           chat = empathy_chat_new (tp_chat);
121           /* empathy_chat_new returns a floating reference as EmpathyChat is
122            * a GtkWidget. This reference will be taken by a container
123            * (a GtkNotebook) when we'll call empathy_chat_window_present_chat */
124         }
125
126       empathy_chat_window_present_chat (chat,
127           empathy_dispatch_operation_get_user_action_time (operation));
128
129       if (empathy_tp_chat_is_invited (tp_chat, NULL))
130         {
131           /* We have been invited to the room. Add ourself as member as this
132            * channel has been approved. */
133           empathy_tp_chat_join (tp_chat);
134         }
135
136       empathy_dispatch_operation_claim (operation);
137     }
138   else if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_FILE_TRANSFER)
139     {
140       EmpathyFTFactory *factory;
141
142       factory = empathy_ft_factory_dup_singleton ();
143
144       /* if the operation is not incoming, don't claim it,
145        * as it might have been triggered by another client, and
146        * we are observing it.
147        */
148       if (empathy_dispatch_operation_is_incoming (operation))
149         empathy_ft_factory_claim_channel (factory, operation);
150     }
151 }
152
153 static void
154 use_conn_notify_cb (GSettings *gsettings,
155     const gchar *key,
156     gpointer     user_data)
157 {
158   EmpathyConnectivity *connectivity = user_data;
159
160   empathy_connectivity_set_use_conn (connectivity,
161       g_settings_get_boolean (gsettings, key));
162 }
163
164 static void
165 migrate_config_to_xdg_dir (void)
166 {
167   gchar *xdg_dir, *old_dir, *xdg_filename, *old_filename;
168   int i;
169   GFile *xdg_file, *old_file;
170   static const gchar* filenames[] = {
171     "geometry.ini",
172     "irc-networks.xml",
173     "chatrooms.xml",
174     "contact-groups.xml",
175     "status-presets.xml",
176     "accels.txt",
177     NULL
178   };
179
180   xdg_dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
181   if (g_file_test (xdg_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
182     {
183       /* xdg config dir already exists */
184       g_free (xdg_dir);
185       return;
186     }
187
188   old_dir = g_build_filename (g_get_home_dir (), ".gnome2",
189       PACKAGE_NAME, NULL);
190   if (!g_file_test (old_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
191     {
192       /* old config dir didn't exist */
193       g_free (xdg_dir);
194       g_free (old_dir);
195       return;
196     }
197
198   if (g_mkdir_with_parents (xdg_dir, (S_IRUSR | S_IWUSR | S_IXUSR)) == -1)
199     {
200       DEBUG ("Failed to create configuration directory; aborting migration");
201       g_free (xdg_dir);
202       g_free (old_dir);
203       return;
204     }
205
206   for (i = 0; filenames[i]; i++)
207     {
208       old_filename = g_build_filename (old_dir, filenames[i], NULL);
209       if (!g_file_test (old_filename, G_FILE_TEST_EXISTS))
210         {
211           g_free (old_filename);
212           continue;
213         }
214       xdg_filename = g_build_filename (xdg_dir, filenames[i], NULL);
215       old_file = g_file_new_for_path (old_filename);
216       xdg_file = g_file_new_for_path (xdg_filename);
217
218       if (!g_file_move (old_file, xdg_file, G_FILE_COPY_NONE,
219           NULL, NULL, NULL, NULL))
220         DEBUG ("Failed to migrate %s", filenames[i]);
221
222       g_free (old_filename);
223       g_free (xdg_filename);
224       g_object_unref (old_file);
225       g_object_unref (xdg_file);
226     }
227
228   g_free (xdg_dir);
229   g_free (old_dir);
230 }
231
232 static void
233 show_accounts_ui (GdkScreen *screen,
234     gboolean if_needed)
235 {
236   empathy_accounts_dialog_show_application (screen,
237       NULL, if_needed, start_hidden);
238 }
239
240 static UniqueResponse
241 unique_app_message_cb (UniqueApp *unique_app,
242     gint command,
243     UniqueMessageData *data,
244     guint timestamp,
245     gpointer user_data)
246 {
247   GtkWindow *window = user_data;
248   TpAccountManager *account_manager;
249
250   DEBUG ("Other instance launched, presenting the main window. "
251       "Command=%d, timestamp %u", command, timestamp);
252
253   /* XXX: the standalone app somewhat breaks this case, since
254    * communicating it would be a pain */
255
256   /* We're requested to show stuff again, disable the start hidden global
257    * in case the accounts wizard wants to pop up.
258    */
259   start_hidden = FALSE;
260
261   gtk_window_set_screen (GTK_WINDOW (window),
262       unique_message_data_get_screen (data));
263   gtk_window_set_startup_id (GTK_WINDOW (window),
264       unique_message_data_get_startup_id (data));
265   gtk_window_present_with_time (GTK_WINDOW (window), timestamp);
266   gtk_window_set_skip_taskbar_hint (window, FALSE);
267
268   account_manager = tp_account_manager_dup ();
269   tp_account_manager_prepare_async (account_manager, NULL,
270       account_manager_ready_cb, NULL);
271   g_object_unref (account_manager);
272
273   return UNIQUE_RESPONSE_OK;
274 }
275
276 static gboolean
277 show_version_cb (const char *option_name,
278     const char *value,
279     gpointer data,
280     GError **error)
281 {
282   g_print ("%s\n", PACKAGE_STRING);
283
284   exit (EXIT_SUCCESS);
285
286   return FALSE;
287 }
288
289 static void
290 new_incoming_transfer_cb (EmpathyFTFactory *factory,
291     EmpathyFTHandler *handler,
292     GError *error,
293     gpointer user_data)
294 {
295   if (error)
296     empathy_ft_manager_display_error (handler, error);
297   else
298     empathy_receive_file_with_file_chooser (handler);
299 }
300
301 static void
302 new_ft_handler_cb (EmpathyFTFactory *factory,
303     EmpathyFTHandler *handler,
304     GError *error,
305     gpointer user_data)
306 {
307   if (error)
308     empathy_ft_manager_display_error (handler, error);
309   else
310     empathy_ft_manager_add_handler (handler);
311
312   g_object_unref (handler);
313 }
314
315 static void
316 account_manager_ready_cb (GObject *source_object,
317     GAsyncResult *result,
318     gpointer user_data)
319 {
320   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
321   GError *error = NULL;
322   EmpathyIdle *idle;
323   EmpathyConnectivity *connectivity;
324   TpConnectionPresenceType presence;
325   GSettings *gsettings = g_settings_new (EMPATHY_PREFS_SCHEMA);
326
327   if (!tp_account_manager_prepare_finish (manager, result, &error))
328     {
329       DEBUG ("Failed to prepare account manager: %s", error->message);
330       g_error_free (error);
331       return;
332     }
333
334   /* Autoconnect */
335   idle = empathy_idle_dup_singleton ();
336   connectivity = empathy_connectivity_dup_singleton ();
337
338   presence = tp_account_manager_get_most_available_presence (manager, NULL,
339       NULL);
340
341   if (g_settings_get_boolean (gsettings, EMPATHY_PREFS_AUTOCONNECT) &&
342       !no_connect &&
343       tp_connection_presence_type_cmp_availability
344           (presence, TP_CONNECTION_PRESENCE_TYPE_OFFLINE)
345             <= 0)
346       /* if current state is Offline, then put it online */
347       empathy_idle_set_state (idle, TP_CONNECTION_PRESENCE_TYPE_AVAILABLE);
348
349   /* Pop up the accounts dialog if we don't have any account */
350   if (!empathy_accounts_has_accounts (manager))
351     show_accounts_ui (gdk_screen_get_default (), TRUE);
352
353   g_object_unref (idle);
354   g_object_unref (connectivity);
355   g_object_unref (gsettings);
356 }
357
358 static EmpathyDispatcher *
359 setup_dispatcher (void)
360 {
361   EmpathyDispatcher *d;
362   GPtrArray *filters;
363   struct {
364     const gchar *channeltype;
365     TpHandleType handletype;
366   } types[] = {
367     /* Text channels with handle types none, contact and room */
368     { TP_IFACE_CHANNEL_TYPE_TEXT, TP_HANDLE_TYPE_NONE  },
369     { TP_IFACE_CHANNEL_TYPE_TEXT, TP_HANDLE_TYPE_CONTACT  },
370     { TP_IFACE_CHANNEL_TYPE_TEXT, TP_HANDLE_TYPE_ROOM  },
371     /* file transfer to contacts */
372     { TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, TP_HANDLE_TYPE_CONTACT  },
373     /* roomlists */
374     { TP_IFACE_CHANNEL_TYPE_ROOM_LIST, TP_HANDLE_TYPE_NONE },
375   };
376   GHashTable *asv;
377   guint i;
378
379   /* Setup the basic Client.Handler that matches our client filter */
380   filters = g_ptr_array_new ();
381   asv = tp_asv_new (
382         TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING,
383            TP_IFACE_CHANNEL_TYPE_TEXT,
384         TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_INT,
385             TP_HANDLE_TYPE_CONTACT,
386         NULL);
387   g_ptr_array_add (filters, asv);
388
389   d = empathy_dispatcher_new (PACKAGE_NAME, filters, NULL);
390
391   g_ptr_array_foreach (filters, (GFunc) g_hash_table_destroy, NULL);
392   g_ptr_array_free (filters, TRUE);
393
394   /* Setup the an extended Client.Handler that matches everything we can do */
395   filters = g_ptr_array_new ();
396   for (i = 0 ; i < G_N_ELEMENTS (types); i++)
397     {
398       asv = tp_asv_new (
399         TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING, types[i].channeltype,
400         TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_INT, types[i].handletype,
401         NULL);
402
403       g_ptr_array_add (filters, asv);
404     }
405
406   empathy_dispatcher_add_handler (d, PACKAGE_NAME"MoreThanMeetsTheEye",
407     filters, NULL);
408
409   g_ptr_array_foreach (filters, (GFunc) g_hash_table_destroy, NULL);
410   g_ptr_array_free (filters, TRUE);
411
412   return d;
413 }
414
415 static void
416 account_status_changed_cb (TpAccount *account,
417     guint old_status,
418     guint new_status,
419     guint reason,
420     gchar *dbus_error_name,
421     GHashTable *details,
422     EmpathyChatroom *room)
423 {
424   TpConnection *conn;
425
426   conn = tp_account_get_connection (account);
427   if (conn == NULL)
428     return;
429
430   empathy_dispatcher_join_muc (conn,
431       empathy_chatroom_get_room (room), EMPATHY_DISPATCHER_NON_USER_ACTION,
432       NULL, NULL);
433 }
434
435 static void
436 account_manager_chatroom_ready_cb (GObject *source_object,
437     GAsyncResult *result,
438     gpointer user_data)
439 {
440   TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
441   EmpathyChatroomManager *chatroom_manager = user_data;
442   GList *accounts, *l;
443   GError *error = NULL;
444
445   if (!tp_account_manager_prepare_finish (account_manager, result, &error))
446     {
447       DEBUG ("Failed to prepare account manager: %s", error->message);
448       g_error_free (error);
449       return;
450     }
451
452   accounts = tp_account_manager_get_valid_accounts (account_manager);
453
454   for (l = accounts; l != NULL; l = g_list_next (l))
455     {
456       TpAccount *account = TP_ACCOUNT (l->data);
457       TpConnection *conn;
458       GList *chatrooms, *p;
459
460       conn = tp_account_get_connection (account);
461
462       chatrooms = empathy_chatroom_manager_get_chatrooms (
463           chatroom_manager, account);
464
465       for (p = chatrooms; p != NULL; p = p->next)
466         {
467           EmpathyChatroom *room = EMPATHY_CHATROOM (p->data);
468
469           if (!empathy_chatroom_get_auto_connect (room))
470             continue;
471
472           if (conn == NULL)
473             {
474               g_signal_connect (G_OBJECT (account), "status-changed",
475                   G_CALLBACK (account_status_changed_cb), room);
476             }
477           else
478             {
479               empathy_dispatcher_join_muc (conn,
480                   empathy_chatroom_get_room (room),
481                   EMPATHY_DISPATCHER_NON_USER_ACTION, NULL, NULL);
482             }
483         }
484
485       g_list_free (chatrooms);
486     }
487
488   g_list_free (accounts);
489 }
490
491 static void
492 chatroom_manager_ready_cb (EmpathyChatroomManager *chatroom_manager,
493     GParamSpec *pspec,
494     gpointer user_data)
495 {
496   TpAccountManager *account_manager = user_data;
497
498   tp_account_manager_prepare_async (account_manager, NULL,
499       account_manager_chatroom_ready_cb, chatroom_manager);
500 }
501
502 static void
503 empathy_idle_set_auto_away_cb (GSettings *gsettings,
504                                 const gchar *key,
505                                 gpointer user_data)
506 {
507         EmpathyIdle *idle = user_data;
508
509         empathy_idle_set_auto_away (idle,
510       g_settings_get_boolean (gsettings, key));
511 }
512
513 int
514 main (int argc, char *argv[])
515 {
516 #if HAVE_GEOCLUE
517   EmpathyLocationManager *location_manager = NULL;
518 #endif
519   EmpathyStatusIcon *icon;
520   EmpathyDispatcher *dispatcher;
521   TpAccountManager *account_manager;
522   TplLogManager *log_manager;
523   EmpathyChatroomManager *chatroom_manager;
524   EmpathyFTFactory  *ft_factory;
525   GtkWidget *window;
526   EmpathyIdle *idle;
527   EmpathyConnectivity *connectivity;
528   EmpathyChatManager *chat_manager;
529   GError *error = NULL;
530   UniqueApp *unique_app;
531   gboolean chatroom_manager_ready;
532   gboolean autoaway = TRUE;
533 #ifdef ENABLE_DEBUG
534   TpDebugSender *debug_sender;
535 #endif
536   GSettings *gsettings;
537
538   GOptionContext *optcontext;
539   GOptionEntry options[] = {
540       { "no-connect", 'n',
541         0, G_OPTION_ARG_NONE, &no_connect,
542         N_("Don't connect on startup"),
543         NULL },
544       { "start-hidden", 'h',
545         0, G_OPTION_ARG_NONE, &start_hidden,
546         N_("Don't display the contact list or any other dialogs on startup"),
547         NULL },
548       { "version", 'v',
549         G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_version_cb,
550         NULL, NULL },
551       { NULL }
552   };
553
554   /* Init */
555   g_thread_init (NULL);
556   empathy_init ();
557
558   optcontext = g_option_context_new (N_("- Empathy IM Client"));
559   g_option_context_add_group (optcontext, gtk_get_option_group (TRUE));
560 #if HAVE_LIBCHAMPLAIN
561   g_option_context_add_group (optcontext, clutter_get_option_group ());
562 #endif
563   g_option_context_add_main_entries (optcontext, options, GETTEXT_PACKAGE);
564
565   if (!g_option_context_parse (optcontext, &argc, &argv, &error)) {
566     g_print ("%s\nRun '%s --help' to see a full list of available command line options.\n",
567         error->message, argv[0]);
568     g_warning ("Error in empathy init: %s", error->message);
569     return EXIT_FAILURE;
570   }
571
572   g_option_context_free (optcontext);
573
574   empathy_gtk_init ();
575   g_set_application_name (_(PACKAGE_NAME));
576
577   gtk_window_set_default_icon_name ("empathy");
578   textdomain (GETTEXT_PACKAGE);
579
580 #ifdef ENABLE_DEBUG
581   /* Set up debug sender */
582   debug_sender = tp_debug_sender_dup ();
583   g_log_set_default_handler (tp_debug_sender_log_handler, G_LOG_DOMAIN);
584 #endif
585
586   unique_app = unique_app_new ("org.gnome."PACKAGE_NAME, NULL);
587
588   if (unique_app_is_running (unique_app))
589     {
590       unique_app_send_message (unique_app, UNIQUE_ACTIVATE, NULL);
591
592       g_object_unref (unique_app);
593       return EXIT_SUCCESS;
594     }
595
596   notify_init (_(PACKAGE_NAME));
597
598   /* Setting up Idle */
599   idle = empathy_idle_dup_singleton ();
600
601   gsettings = g_settings_new (EMPATHY_PREFS_SCHEMA);
602   autoaway = g_settings_get_boolean (gsettings, EMPATHY_PREFS_AUTOAWAY);
603
604   g_signal_connect (gsettings,
605       "changed::" EMPATHY_PREFS_AUTOAWAY,
606       G_CALLBACK (empathy_idle_set_auto_away_cb), idle);
607
608   empathy_idle_set_auto_away (idle, autoaway);
609
610   /* Setting up Connectivity */
611   connectivity = empathy_connectivity_dup_singleton ();
612   use_conn_notify_cb (gsettings, EMPATHY_PREFS_USE_CONN,
613       connectivity);
614   g_signal_connect (gsettings,
615       "changed::" EMPATHY_PREFS_USE_CONN,
616       G_CALLBACK (use_conn_notify_cb), connectivity);
617
618   /* account management */
619   account_manager = tp_account_manager_dup ();
620   tp_account_manager_prepare_async (account_manager, NULL,
621       account_manager_ready_cb, NULL);
622
623   /* Handle channels */
624   dispatcher = setup_dispatcher ();
625   g_signal_connect (dispatcher, "dispatch", G_CALLBACK (dispatch_cb), NULL);
626
627   migrate_config_to_xdg_dir ();
628
629   /* Setting up UI */
630   window = empathy_main_window_dup ();
631   gtk_widget_show (window);
632   icon = empathy_status_icon_new (GTK_WINDOW (window), start_hidden);
633
634   /* Chat manager */
635   chat_manager = empathy_chat_manager_dup_singleton ();
636
637   g_signal_connect (unique_app, "message-received",
638       G_CALLBACK (unique_app_message_cb), window);
639
640   /* Logging */
641   log_manager = tpl_log_manager_dup_singleton ();
642
643   chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
644   empathy_chatroom_manager_observe (chatroom_manager, dispatcher);
645
646   g_object_get (chatroom_manager, "ready", &chatroom_manager_ready, NULL);
647   if (!chatroom_manager_ready)
648     {
649       g_signal_connect (G_OBJECT (chatroom_manager), "notify::ready",
650           G_CALLBACK (chatroom_manager_ready_cb), account_manager);
651     }
652   else
653     {
654       chatroom_manager_ready_cb (chatroom_manager, NULL, account_manager);
655     }
656
657   /* Create the FT factory */
658   ft_factory = empathy_ft_factory_dup_singleton ();
659   g_signal_connect (ft_factory, "new-ft-handler",
660       G_CALLBACK (new_ft_handler_cb), NULL);
661   g_signal_connect (ft_factory, "new-incoming-transfer",
662       G_CALLBACK (new_incoming_transfer_cb), NULL);
663
664   /* Location mananger */
665 #if HAVE_GEOCLUE
666   location_manager = empathy_location_manager_dup_singleton ();
667 #endif
668
669   gtk_main ();
670
671   empathy_idle_set_state (idle, TP_CONNECTION_PRESENCE_TYPE_OFFLINE);
672
673 #ifdef ENABLE_DEBUG
674   g_object_unref (debug_sender);
675 #endif
676
677   g_object_unref (chat_manager);
678   g_object_unref (idle);
679   g_object_unref (connectivity);
680   g_object_unref (icon);
681   g_object_unref (account_manager);
682   g_object_unref (log_manager);
683   g_object_unref (dispatcher);
684   g_object_unref (chatroom_manager);
685 #if HAVE_GEOCLUE
686   g_object_unref (location_manager);
687 #endif
688   g_object_unref (ft_factory);
689   g_object_unref (unique_app);
690   g_object_unref (gsettings);
691
692   notify_uninit ();
693   xmlCleanupParser ();
694
695   return EXIT_SUCCESS;
696 }