src/mn-main.c (15141B) - raw
1 /*
2 * Mail Notification
3 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <string.h>
21 #include <stdlib.h>
22 #include <signal.h>
23 #include <glib/gi18n.h>
24 #include <gnome.h>
25 #include <libgnomevfs/gnome-vfs.h>
26 #include <libnotify/notify.h>
27 #include <dbus/dbus-glib-lowlevel.h>
28 #if WITH_GMIME
29 #include <gmime/gmime.h>
30 #include "mn-gmime-stream-vfs.h"
31 #endif
32 #if WITH_MBOX || WITH_MOZILLA || WITH_MH || WITH_MAILDIR || WITH_SYLPHEED
33 #include "mn-vfs-mailbox.h"
34 #endif
35 #if WITH_MAILDIR
36 #include "mn-maildir-message.h"
37 #endif
38 #if WITH_SYLPHEED
39 #include "mn-sylpheed-message.h"
40 #endif
41 #if WITH_EVOLUTION
42 #include "mn-evolution-message.h"
43 #endif
44 #include "mn-conf.h"
45 #include "mn-util.h"
46 #include "mn-stock.h"
47 #include "mn-shell.h"
48 #include "mn-message.h"
49 #include "mn-client-dbus.h"
50 #include "mn-server.h"
51
52 #define CALL_SERVER(call) \
53 { \
54 if (! (call)) \
55 handle_server_call_error(err); \
56 }
57
58 typedef struct
59 {
60 const char *name;
61 gboolean enabled;
62 } Component;
63
64 static gboolean arg_enable_info = FALSE;
65
66 static unsigned int bus_disconnected_timeout_id = 0;
67
68 static gboolean bus_disconnected_cb (gpointer data) G_GNUC_NORETURN;
69
70 static void
71 print_components (const Component *components, int n_components)
72 {
73 int i;
74
75 for (i = 0; i < n_components; i++)
76 g_print(" %-30s %s\n", components[i].name, components[i].enabled ? _("yes") : _("no"));
77 }
78
79 static void
80 print_version (void)
81 {
82 /*
83 * Here and everywhere else, backends and features are sorted
84 * alphabetically.
85 */
86
87 static const Component mailbox_backends[] = {
88 { "Evolution", WITH_EVOLUTION },
89 { "Gmail", WITH_GMAIL },
90 { "IMAP", WITH_IMAP },
91 { "Maildir", WITH_MAILDIR },
92 { "mbox", WITH_MBOX },
93 { "MH", WITH_MH },
94 { "Mozilla products", WITH_MOZILLA },
95 { "POP3", WITH_POP3 },
96 { "Sylpheed", WITH_SYLPHEED },
97 { "Windows Live Hotmail", WITH_HOTMAIL },
98 { "Yahoo! Mail", WITH_YAHOO }
99 };
100
101 static const Component pi_features[] = {
102 { "IPv6", WITH_IPV6 },
103 { "SASL", WITH_SASL },
104 { "SSL/TLS", WITH_SSL }
105 };
106
107 g_print(_("%s version %s\n"), _("Mail Notification"), VERSION);
108 g_print("Copyright (C) 2003-2008 Jean-Yves Lefort.\n");
109
110 g_print("\n");
111
112 g_print(_("Mailbox backends:\n"));
113 print_components(mailbox_backends, G_N_ELEMENTS(mailbox_backends));
114
115 g_print("\n");
116
117 g_print(_("IMAP and POP3 features:\n"));
118 print_components(pi_features, G_N_ELEMENTS(pi_features));
119 }
120
121 static void
122 info_log_cb (const char *log_domain,
123 GLogLevelFlags log_level,
124 const char *message,
125 gpointer user_data)
126 {
127 if (arg_enable_info)
128 g_log_default_handler(log_domain, log_level, message, user_data);
129 }
130
131 static void
132 init_classes (void)
133 {
134 int i;
135
136 #if WITH_GMIME
137 g_type_class_ref(MN_TYPE_GMIME_STREAM_VFS);
138 g_type_class_ref(GMIME_TYPE_PARSER);
139 g_type_class_ref(GMIME_TYPE_STREAM_MEM);
140 g_type_class_ref(GMIME_TYPE_MESSAGE);
141 #endif /* WITH_GMIME */
142 #if WITH_MBOX || WITH_MOZILLA || WITH_MH || WITH_MAILDIR || WITH_SYLPHEED
143 for (i = 0; mn_vfs_mailbox_backend_types[i]; i++)
144 g_type_class_ref(mn_vfs_mailbox_backend_types[i]);
145 #endif
146 #if WITH_MAILDIR
147 g_type_class_ref(MN_TYPE_MAILDIR_MESSAGE);
148 #endif
149 #if WITH_SYLPHEED
150 g_type_class_ref(MN_TYPE_SYLPHEED_MESSAGE);
151 #endif
152 g_type_class_ref(MN_TYPE_MESSAGE);
153 }
154
155 #if WITH_GMIME
156 static void
157 init_gmime (void)
158 {
159 GPtrArray *array;
160 GSList *fallback_charsets;
161 GSList *l;
162
163 g_mime_init(0);
164
165 array = g_ptr_array_new();
166
167 fallback_charsets = mn_conf_get_string_list(MN_CONF_FALLBACK_CHARSETS);
168
169 MN_LIST_FOREACH(l, fallback_charsets)
170 {
171 const char *charset = l->data;
172
173 if (! strcmp(charset, "user"))
174 {
175 const char *user_charset;
176
177 g_get_charset(&user_charset);
178 g_ptr_array_add(array, g_strdup(user_charset));
179 }
180 else
181 g_ptr_array_add(array, g_strdup(charset));
182 }
183
184 mn_g_slist_free_deep(fallback_charsets);
185
186 g_ptr_array_add(array, NULL); /* canary */
187
188 g_mime_set_user_charsets((const char **) array->pdata);
189
190 /*
191 * Note that because of
192 * http://bugzilla.gnome.org/show_bug.cgi?id=509434, we do not free
193 * the strings of the array (since GMime does not copy them).
194 */
195 g_ptr_array_free(array, TRUE);
196 }
197 #endif /* WITH_GMIME */
198
199 static void
200 handle_server_call_error (GError *err)
201 {
202 g_return_if_fail(err != NULL);
203
204 mn_show_fatal_error_dialog(NULL, _("Unable to contact the running Mail Notification instance: %s."), err->message);
205 }
206
207 static gboolean
208 has_icon_path (const char *path)
209 {
210 char **paths;
211 int i;
212 gboolean has = FALSE;
213
214 gtk_icon_theme_get_search_path(gtk_icon_theme_get_default(), &paths, NULL);
215 for (i = 0; paths[i]; i++)
216 if (! strcmp(paths[i], path))
217 {
218 has = TRUE;
219 break;
220 }
221 g_strfreev(paths);
222
223 return has;
224 }
225
226 /*
227 * This is needed when MN is not installed in the standard prefix (as
228 * is the case for my test builds).
229 */
230 static void
231 ensure_icon_path (void)
232 {
233 static const char *icon_path = DATADIR G_DIR_SEPARATOR_S "icons";
234
235 if (! has_icon_path(icon_path))
236 gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), icon_path);
237 }
238
239 static void
240 report_option_ignored (const char *option_name)
241 {
242 g_return_if_fail(option_name != NULL);
243
244 g_message(_("%s option ignored since Mail Notification is not already running"), option_name);
245 }
246
247 static gboolean
248 bus_disconnected_cb (gpointer data)
249 {
250 mn_show_fatal_error_dialog(NULL, _("The connection to the D-Bus session bus was lost."));
251 }
252
253 static DBusHandlerResult
254 session_bus_filter_cb (DBusConnection *conn, DBusMessage *message, void *user_data)
255 {
256 if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected"))
257 {
258 /*
259 * If the user is logging out there is no reason to display the
260 * error dialog, so wait a bit, hoping that we will be killed
261 * before the timeout triggers.
262 */
263
264 if (bus_disconnected_timeout_id == 0)
265 bus_disconnected_timeout_id = gdk_threads_add_timeout(5000, bus_disconnected_cb, NULL);
266
267 return DBUS_HANDLER_RESULT_HANDLED;
268 }
269
270 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
271 }
272
273 static DBusGConnection *
274 connect_to_session_bus (void)
275 {
276 GError *err = NULL;
277 DBusGConnection *bus;
278 DBusConnection *raw_bus;
279
280 bus = dbus_g_bus_get(DBUS_BUS_SESSION, &err);
281 if (! bus)
282 {
283 mn_show_fatal_error_dialog(NULL, _("Unable to connect to the D-Bus session bus: %s."), err->message);
284 g_error_free(err);
285 }
286
287 raw_bus = dbus_g_connection_get_connection(bus);
288
289 dbus_connection_set_exit_on_disconnect(raw_bus, FALSE);
290
291 if (! dbus_connection_add_filter(raw_bus, session_bus_filter_cb, NULL, NULL))
292 /* too unlikely to be worth a translation */
293 mn_show_fatal_error_dialog(NULL, "Unable to add a D-Bus filter: not enough memory.");
294
295 return bus;
296 }
297
298 /*
299 * A memory management bug in DBusGProxy
300 * (https://bugs.freedesktop.org/show_bug.cgi?id=14030) prevents us
301 * from unreferencing the proxy, so provide an eternal singleton
302 * proxy.
303 */
304 static DBusGProxy *
305 get_bus_proxy (DBusGConnection *bus)
306 {
307 DBusGProxy *proxy;
308
309 g_return_val_if_fail(bus != NULL, NULL);
310
311 proxy = dbus_g_proxy_new_for_name(bus,
312 DBUS_SERVICE_DBUS,
313 DBUS_PATH_DBUS,
314 DBUS_INTERFACE_DBUS);
315
316 #if WITH_EVOLUTION
317 /* needed by MNEvolutionClient */
318 dbus_g_proxy_add_signal(proxy,
319 "NameOwnerChanged",
320 G_TYPE_STRING, /* service_name */
321 G_TYPE_STRING, /* old_owner */
322 G_TYPE_STRING, /* new_owner */
323 G_TYPE_INVALID);
324 #endif
325
326 return proxy;
327 }
328
329 int
330 main (int argc, char **argv)
331 {
332 gboolean arg_version = FALSE;
333 gboolean arg_display_properties = FALSE;
334 gboolean arg_display_about = FALSE;
335 gboolean arg_consider_new_mail_as_read = FALSE;
336 gboolean arg_update = FALSE;
337 gboolean arg_print_summary = FALSE;
338 gboolean arg_unset_obsolete_configuration = FALSE;
339 gboolean arg_quit = FALSE;
340 const GOptionEntry options[] = {
341 {
342 "version",
343 'v',
344 0,
345 G_OPTION_ARG_NONE,
346 &arg_version,
347 N_("Show version information"),
348 NULL
349 },
350 {
351 "enable-info",
352 'i',
353 0,
354 G_OPTION_ARG_NONE,
355 &arg_enable_info,
356 N_("Enable informational output"),
357 NULL
358 },
359 {
360 "display-properties",
361 'p',
362 0,
363 G_OPTION_ARG_NONE,
364 &arg_display_properties,
365 N_("Display the properties dialog"),
366 NULL
367 },
368 {
369 "display-about",
370 'a',
371 0,
372 G_OPTION_ARG_NONE,
373 &arg_display_about,
374 N_("Display the about dialog"),
375 NULL
376 },
377 {
378 "consider-new-mail-as-read",
379 'r',
380 0,
381 G_OPTION_ARG_NONE,
382 &arg_consider_new_mail_as_read,
383 N_("Consider new mail as read"),
384 NULL
385 },
386 {
387 "update",
388 'u',
389 0,
390 G_OPTION_ARG_NONE,
391 &arg_update,
392 N_("Update the mail status"),
393 NULL
394 },
395 {
396 "print-summary",
397 's',
398 0,
399 G_OPTION_ARG_NONE,
400 &arg_print_summary,
401 N_("Print a XML mail summary"),
402 NULL
403 },
404 {
405 "unset-obsolete-configuration",
406 '\0',
407 0,
408 G_OPTION_ARG_NONE,
409 &arg_unset_obsolete_configuration,
410 N_("Unset obsolete GConf configuration"),
411 NULL
412 },
413 {
414 "quit",
415 'q',
416 0,
417 G_OPTION_ARG_NONE,
418 &arg_quit,
419 N_("Quit Mail Notification"),
420 NULL
421 },
422 { NULL }
423 };
424 GOptionContext *option_context;
425 DBusGConnection *bus;
426 DBusGProxy *bus_proxy;
427
428 g_log_set_fatal_mask(NULL, G_LOG_LEVEL_CRITICAL);
429 g_log_set_handler(NULL, G_LOG_LEVEL_INFO, info_log_cb, NULL);
430
431 #ifdef ENABLE_NLS
432 bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);
433 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
434 textdomain(GETTEXT_PACKAGE);
435 #endif
436
437 g_set_application_name(_("Mail Notification"));
438
439 g_thread_init(NULL);
440 if (! g_thread_supported())
441 /*
442 * We cannot use mn_fatal_error_dialog() because gtk_init() has
443 * not been called yet.
444 */
445 g_critical(_("multi-threading is not available"));
446 gdk_threads_init();
447
448 GDK_THREADS_ENTER();
449
450 option_context = g_option_context_new(NULL);
451 g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
452
453 gnome_program_init(PACKAGE,
454 VERSION,
455 LIBGNOMEUI_MODULE,
456 argc,
457 argv,
458 GNOME_PARAM_HUMAN_READABLE_NAME, _("Mail Notification"),
459 GNOME_PROGRAM_STANDARD_PROPERTIES,
460 GNOME_PARAM_GOPTION_CONTEXT, option_context,
461 NULL);
462
463 if (arg_version)
464 {
465 print_version();
466 goto end;
467 }
468
469 if (arg_unset_obsolete_configuration)
470 {
471 mn_conf_unset_obsolete();
472 goto end;
473 }
474
475 ensure_icon_path();
476 gtk_window_set_default_icon_name("mail-notification");
477
478 mn_stock_init();
479
480 bus = connect_to_session_bus();
481 bus_proxy = get_bus_proxy(bus);
482
483 if (mn_server_start(bus, bus_proxy)) /* not already running */
484 {
485 if (arg_quit)
486 g_message(_("Mail Notification is not running"));
487 else
488 {
489 mn_mailbox_init_types();
490 #if WITH_MBOX || WITH_MOZILLA || WITH_MH || WITH_MAILDIR || WITH_SYLPHEED
491 mn_vfs_mailbox_init_types();
492 #endif
493
494 /* mn-client-session uses sockets, we don't want to die on SIGPIPE */
495 signal(SIGPIPE, SIG_IGN);
496
497 if (! gnome_vfs_init())
498 mn_show_fatal_error_dialog(NULL, _("Unable to initialize the GnomeVFS library."));
499
500 gnome_authentication_manager_init();
501
502 /* must be called before init_gmime() */
503 mn_conf_init();
504
505 #if WITH_GMIME
506 init_gmime();
507 #endif
508
509 if (! notify_init(_("Mail Notification")))
510 mn_show_error_dialog(NULL,
511 _("An initialization error has occurred in Mail Notification"),
512 _("Unable to initialize the notification library. Message popups will not be displayed."));
513
514 /*
515 * Work around
516 * http://bugzilla.gnome.org/show_bug.cgi?id=64764:
517 * initialize the classes we will be using concurrently
518 * before any thread is created.
519 */
520 init_classes();
521
522 mn_shell_new(bus, bus_proxy);
523
524 /* also display the properties dialog if there are no mailboxes */
525 if (! mn_shell->mailboxes->list)
526 arg_display_properties = TRUE;
527
528 if (arg_display_properties)
529 mn_shell_show_properties_dialog(mn_shell, 0);
530 if (arg_display_about)
531 mn_shell_show_about_dialog(mn_shell, 0);
532 if (arg_consider_new_mail_as_read)
533 report_option_ignored("--consider-new-mail-as-read");
534 if (arg_update)
535 report_option_ignored("--update");
536 if (arg_print_summary)
537 report_option_ignored("--print-summary");
538
539 /* in case no window has been displayed */
540 gdk_notify_startup_complete();
541
542 gtk_main();
543 }
544 }
545 else /* already running */
546 {
547 DBusGProxy *proxy;
548 GError *err = NULL;
549
550 proxy = dbus_g_proxy_new_for_name(bus,
551 MN_SERVER_SERVICE,
552 MN_SERVER_PATH,
553 MN_SERVER_INTERFACE);
554
555 if (arg_quit)
556 {
557 g_message(_("quitting Mail Notification"));
558 CALL_SERVER(org_gnome_MailNotification_quit(proxy, &err));
559 }
560 else
561 {
562 /* also display the properties dialog if there are no mailboxes */
563 if (! arg_display_properties)
564 {
565 gboolean has;
566 CALL_SERVER(org_gnome_MailNotification_has_mailboxes(proxy, &has, &err));
567 arg_display_properties = ! has;
568 }
569
570 if (arg_display_properties)
571 CALL_SERVER(org_gnome_MailNotification_display_properties(proxy, &err));
572 if (arg_display_about)
573 CALL_SERVER(org_gnome_MailNotification_display_about(proxy, &err));
574 if (arg_consider_new_mail_as_read)
575 {
576 g_message(_("considering new mail as read"));
577 CALL_SERVER(org_gnome_MailNotification_consider_new_mail_as_read(proxy, &err));
578 }
579 if (arg_update)
580 {
581 g_message(_("updating the mail status"));
582 CALL_SERVER(org_gnome_MailNotification_update(proxy, &err));
583 }
584 if (arg_print_summary)
585 {
586 char *summary;
587
588 CALL_SERVER(org_gnome_MailNotification_get_summary(proxy, &summary, &err));
589 g_print("%s", summary);
590 g_free(summary);
591 }
592
593 if (! (arg_display_properties
594 || arg_display_about
595 || arg_consider_new_mail_as_read
596 || arg_update
597 || arg_print_summary))
598 g_message(_("Mail Notification is already running"));
599 }
600
601 /*
602 * Do not unref the proxy, since it might break when the
603 * DBusGProxy memory management issue is fixed
604 * (https://bugs.freedesktop.org/show_bug.cgi?id=14030).
605 */
606
607 /* no window has been displayed by this process */
608 gdk_notify_startup_complete();
609 }
610
611 end:
612 GDK_THREADS_LEAVE();
613
614 return 0;
615 }