1 /*
2 * e-shell.c
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 *
17 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
18 *
19 */
20
21 /**
22 * SECTION: e-shell
23 * @short_description: the backbone of Evolution
24 * @include: shell/e-shell.h
25 **/
26
27 #include "evolution-config.h"
28
29 #include "e-shell.h"
30
31 #include <errno.h>
32 #include <glib/gi18n.h>
33 #include <glib/gstdio.h>
34 #include <libebackend/libebackend.h>
35 #include <libedataserver/libedataserver.h>
36
37 #include "e-util/e-util-private.h"
38
39 #include "e-shell-backend.h"
40 #include "e-shell-enumtypes.h"
41 #include "e-shell-window.h"
42 #include "e-shell-utils.h"
43
44 #define E_SHELL_GET_PRIVATE(obj) \
45 (G_TYPE_INSTANCE_GET_PRIVATE \
46 ((obj), E_TYPE_SHELL, EShellPrivate))
47
48 #define SET_ONLINE_TIMEOUT_SECONDS 5
49
50 struct _EShellPrivate {
51 GQueue alerts;
52 ESourceRegistry *registry;
53 ECredentialsPrompter *credentials_prompter;
54 EClientCache *client_cache;
55 GtkWidget *preferences_window;
56 GCancellable *cancellable;
57
58 /* Shell Backends */
59 GList *loaded_backends; /* not referenced */
60 GHashTable *backends_by_name;
61 GHashTable *backends_by_scheme;
62
63 GHashTable *auth_prompt_parents; /* gchar *ESource::uid ~> gpointer ( only remembered, not referenced, GtkWindow * )*/
64
65 gboolean preparing_for_online;
66 gpointer preparing_for_line_change; /* weak pointer */
67 gpointer preparing_for_quit; /* weak pointer */
68
69 gchar *geometry;
70 gchar *module_directory;
71
72 guint inhibit_cookie;
73 guint set_online_timeout_id;
74 guint prepare_quit_timeout_id;
75
76 gulong backend_died_handler_id;
77 gulong allow_auth_prompt_handler_id;
78 gulong get_dialog_parent_handler_id;
79 gulong get_dialog_parent_full_handler_id;
80 gulong credentials_required_handler_id;
81
82 guint auto_reconnect : 1;
83 guint express_mode : 1;
84 guint modules_loaded : 1;
85 guint network_available : 1;
86 guint network_available_set : 1;
87 guint network_available_locked : 1;
88 guint online : 1;
89 guint quit_cancelled : 1;
90 guint ready_to_quit : 1;
91 guint safe_mode : 1;
92 guint requires_shutdown : 1;
93 };
94
95 enum {
96 PROP_0,
97 PROP_CLIENT_CACHE,
98 PROP_EXPRESS_MODE,
99 PROP_GEOMETRY,
100 PROP_MODULE_DIRECTORY,
101 PROP_NETWORK_AVAILABLE,
102 PROP_ONLINE,
103 PROP_REGISTRY,
104 PROP_CREDENTIALS_PROMPTER
105 };
106
107 enum {
108 EVENT,
109 HANDLE_URI,
110 PREPARE_FOR_OFFLINE,
111 PREPARE_FOR_ONLINE,
112 PREPARE_FOR_QUIT,
113 QUIT_REQUESTED,
114 LAST_SIGNAL
115 };
116
117 static gpointer default_shell;
118 static guint signals[LAST_SIGNAL];
119
120 /* Forward Declarations */
121 static void e_shell_initable_init (GInitableIface *iface);
122
G_DEFINE_TYPE_WITH_CODE(EShell,e_shell,GTK_TYPE_APPLICATION,G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,e_shell_initable_init)G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE,NULL))123 G_DEFINE_TYPE_WITH_CODE (
124 EShell,
125 e_shell,
126 GTK_TYPE_APPLICATION,
127 G_IMPLEMENT_INTERFACE (
128 G_TYPE_INITABLE, e_shell_initable_init)
129 G_IMPLEMENT_INTERFACE (
130 E_TYPE_EXTENSIBLE, NULL))
131
132 static void
133 shell_alert_response_cb (EShell *shell,
134 gint response_id,
135 EAlert *alert)
136 {
137 g_signal_handlers_disconnect_by_func (
138 alert, shell_alert_response_cb, shell);
139
140 g_queue_remove (&shell->priv->alerts, alert);
141
142 g_object_unref (alert);
143 }
144
145 static void
shell_notify_online_cb(EShell * shell)146 shell_notify_online_cb (EShell *shell)
147 {
148 gboolean online;
149
150 online = e_shell_get_online (shell);
151 e_passwords_set_online (online);
152 }
153
154 static void
shell_window_removed_cb(EShell * shell)155 shell_window_removed_cb (EShell *shell)
156 {
157 g_return_if_fail (E_IS_SHELL (shell));
158
159 if (!gtk_application_get_windows (GTK_APPLICATION (shell)) &&
160 !shell->priv->ready_to_quit)
161 e_shell_quit (shell, E_SHELL_QUIT_LAST_WINDOW);
162 }
163
164 static gboolean
shell_window_delete_event_cb(GtkWindow * window,GdkEvent * event,GtkApplication * application)165 shell_window_delete_event_cb (GtkWindow *window,
166 GdkEvent *event,
167 GtkApplication *application)
168 {
169 /* If other windows are open we can safely close this one. */
170 if (g_list_length (gtk_application_get_windows (application)) > 1)
171 return FALSE;
172
173 /* Otherwise we initiate application quit. */
174 e_shell_quit (E_SHELL (application), E_SHELL_QUIT_LAST_WINDOW);
175
176 return TRUE;
177 }
178
179 static void
shell_action_new_window_cb(GSimpleAction * action,GVariant * parameter,EShell * shell)180 shell_action_new_window_cb (GSimpleAction *action,
181 GVariant *parameter,
182 EShell *shell)
183 {
184 GtkApplication *application;
185 const gchar *view_name;
186
187 application = GTK_APPLICATION (shell);
188
189 view_name = parameter ? g_variant_get_string (parameter, NULL) : NULL;
190 if (view_name && !*view_name)
191 view_name = NULL;
192
193 if (view_name) {
194 GList *list;
195 gboolean get_current = g_strcmp0 (view_name, "current") == 0;
196
197 list = gtk_application_get_windows (application);
198
199 /* Present the first EShellWindow showing 'view_name'. */
200 while (list != NULL) {
201 GtkWindow *window = GTK_WINDOW (list->data);
202
203 if (E_IS_SHELL_WINDOW (window)) {
204 const gchar *active_view;
205
206 active_view = e_shell_window_get_active_view (
207 E_SHELL_WINDOW (window));
208 if (g_strcmp0 (active_view, view_name) == 0) {
209 gtk_window_present (window);
210 return;
211 } else if (get_current && active_view) {
212 view_name = active_view;
213 break;
214 }
215 }
216
217 list = g_list_next (list);
218 }
219 } else {
220 GtkWindow *window;
221
222 window = e_shell_get_active_window (shell);
223
224 if (E_IS_SHELL_WINDOW (window))
225 view_name = e_shell_window_get_active_view (E_SHELL_WINDOW (window));
226 }
227
228 /* No suitable EShellWindow found, so create one. */
229 e_shell_create_shell_window (shell, view_name);
230 }
231
232 static void
shell_action_handle_uris_cb(GSimpleAction * action,GVariant * parameter,EShell * shell)233 shell_action_handle_uris_cb (GSimpleAction *action,
234 GVariant *parameter,
235 EShell *shell)
236 {
237 const gchar **uris;
238 gchar *change_dir = NULL;
239 gint ii;
240
241 /* Do not use g_strfreev() here. */
242 uris = g_variant_get_strv (parameter, NULL);
243 if (uris && g_strcmp0 (uris[0], "--use-cwd") == 0 && uris[1] && *uris[1]) {
244 change_dir = g_get_current_dir ();
245
246 if (g_chdir (uris[1]) != 0)
247 g_warning ("%s: Failed to change directory to '%s': %s", G_STRFUNC, uris[1], g_strerror (errno));
248
249 for (ii = 0; uris[ii + 2]; ii++) {
250 uris[ii] = uris[ii + 2];
251 }
252
253 uris[ii] = NULL;
254 }
255
256 e_shell_handle_uris (shell, uris, FALSE);
257 g_free (uris);
258
259 if (change_dir) {
260 if (g_chdir (change_dir) != 0)
261 g_warning ("%s: Failed to return back to '%s': %s", G_STRFUNC, change_dir, g_strerror (errno));
262
263 g_free (change_dir);
264 }
265 }
266
267 static void
shell_action_quit_cb(GSimpleAction * action,GVariant * parameter,EShell * shell)268 shell_action_quit_cb (GSimpleAction *action,
269 GVariant *parameter,
270 EShell *shell)
271 {
272 e_shell_quit (shell, E_SHELL_QUIT_REMOTE_REQUEST);
273 }
274
275 static void
shell_add_actions(GApplication * application)276 shell_add_actions (GApplication *application)
277 {
278 GActionMap *action_map;
279 GSimpleAction *action;
280
281 action_map = G_ACTION_MAP (application);
282
283 /* Add actions that remote instances can invoke. */
284
285 action = g_simple_action_new ("create-from-remote", G_VARIANT_TYPE_STRING);
286 g_signal_connect (
287 action, "activate",
288 G_CALLBACK (shell_action_new_window_cb), application);
289 g_action_map_add_action (action_map, G_ACTION (action));
290 g_object_unref (action);
291
292 action = g_simple_action_new (
293 "handle-uris", G_VARIANT_TYPE_STRING_ARRAY);
294 g_signal_connect (
295 action, "activate",
296 G_CALLBACK (shell_action_handle_uris_cb), application);
297 g_action_map_add_action (action_map, G_ACTION (action));
298 g_object_unref (action);
299
300 action = g_simple_action_new ("quit", NULL);
301 g_signal_connect (
302 action, "activate",
303 G_CALLBACK (shell_action_quit_cb), application);
304 g_action_map_add_action (action_map, G_ACTION (action));
305 g_object_unref (action);
306 }
307
308 static gboolean
e_shell_set_online_cb(gpointer user_data)309 e_shell_set_online_cb (gpointer user_data)
310 {
311 EShell *shell = user_data;
312
313 g_return_val_if_fail (E_IS_SHELL (shell), FALSE);
314
315 shell->priv->set_online_timeout_id = 0;
316
317 e_shell_set_online (shell, TRUE);
318
319 return FALSE;
320 }
321
322 static void
shell_ready_for_online_change(EShell * shell,EActivity * activity,gboolean is_last_ref)323 shell_ready_for_online_change (EShell *shell,
324 EActivity *activity,
325 gboolean is_last_ref)
326 {
327 gboolean is_cancelled;
328
329 if (!is_last_ref)
330 return;
331
332 /* Increment the reference count so we can safely emit
333 * a signal without triggering the toggle reference. */
334 g_object_ref (activity);
335
336 is_cancelled = e_activity_get_state (activity) == E_ACTIVITY_CANCELLED ||
337 g_cancellable_is_cancelled (e_activity_get_cancellable (activity));
338 e_activity_set_state (activity, is_cancelled ? E_ACTIVITY_CANCELLED : E_ACTIVITY_COMPLETED);
339
340 g_object_remove_toggle_ref (
341 G_OBJECT (activity), (GToggleNotify)
342 shell_ready_for_online_change, shell);
343
344 /* Finalize the activity. */
345 g_object_unref (activity);
346
347 if (!is_cancelled)
348 shell->priv->online = shell->priv->preparing_for_online;
349
350 g_object_notify (G_OBJECT (shell), "online");
351 }
352
353 static void
shell_cancel_ongoing_preparing_line_change(EShell * shell)354 shell_cancel_ongoing_preparing_line_change (EShell *shell)
355 {
356 EActivity *activity;
357
358 activity = g_object_ref (shell->priv->preparing_for_line_change);
359 shell->priv->preparing_for_line_change = NULL;
360
361 g_object_remove_toggle_ref (G_OBJECT (activity), (GToggleNotify) shell_ready_for_online_change, shell);
362
363 g_object_remove_weak_pointer (G_OBJECT (activity), &shell->priv->preparing_for_line_change);
364
365 e_activity_cancel (activity);
366
367 g_clear_object (&activity);
368 }
369
370 static void
shell_prepare_for_offline(EShell * shell)371 shell_prepare_for_offline (EShell *shell)
372 {
373 /* Are preparations already in progress? */
374 if (shell->priv->preparing_for_line_change != NULL)
375 shell_cancel_ongoing_preparing_line_change (shell);
376
377 shell->priv->preparing_for_line_change = e_activity_new ();
378 shell->priv->preparing_for_online = FALSE;
379
380 e_activity_set_text (
381 shell->priv->preparing_for_line_change,
382 _("Preparing to go offline…"));
383
384 g_object_add_toggle_ref (
385 G_OBJECT (shell->priv->preparing_for_line_change),
386 (GToggleNotify) shell_ready_for_online_change, shell);
387
388 g_object_add_weak_pointer (
389 G_OBJECT (shell->priv->preparing_for_line_change),
390 &shell->priv->preparing_for_line_change);
391
392 g_signal_emit (
393 shell, signals[PREPARE_FOR_OFFLINE], 0,
394 shell->priv->preparing_for_line_change);
395
396 g_object_unref (shell->priv->preparing_for_line_change);
397 }
398
399 static void
shell_prepare_for_online(EShell * shell)400 shell_prepare_for_online (EShell *shell)
401 {
402 /* Are preparations already in progress? */
403 if (shell->priv->preparing_for_line_change != NULL)
404 shell_cancel_ongoing_preparing_line_change (shell);
405
406 shell->priv->preparing_for_line_change = e_activity_new ();
407 shell->priv->preparing_for_online = TRUE;
408
409 e_activity_set_text (
410 shell->priv->preparing_for_line_change,
411 _("Preparing to go online…"));
412
413 g_object_add_toggle_ref (
414 G_OBJECT (shell->priv->preparing_for_line_change),
415 (GToggleNotify) shell_ready_for_online_change, shell);
416
417 g_object_add_weak_pointer (
418 G_OBJECT (shell->priv->preparing_for_line_change),
419 &shell->priv->preparing_for_line_change);
420
421 g_signal_emit (
422 shell, signals[PREPARE_FOR_ONLINE], 0,
423 shell->priv->preparing_for_line_change);
424
425 g_object_unref (shell->priv->preparing_for_line_change);
426 }
427
428 static void
shell_ready_for_quit(EShell * shell,EActivity * activity,gboolean is_last_ref)429 shell_ready_for_quit (EShell *shell,
430 EActivity *activity,
431 gboolean is_last_ref)
432 {
433 GtkApplication *application;
434 GList *list;
435
436 g_return_if_fail (E_IS_SHELL (shell));
437
438 if (!is_last_ref)
439 return;
440
441 shell->priv->ready_to_quit = TRUE;
442
443 application = GTK_APPLICATION (shell);
444
445 /* Increment the reference count so we can safely emit
446 * a signal without triggering the toggle reference. */
447 g_object_ref (activity);
448
449 e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
450
451 g_object_remove_toggle_ref (
452 G_OBJECT (activity), (GToggleNotify)
453 shell_ready_for_quit, shell);
454
455 /* Finalize the activity. */
456 g_object_unref (activity);
457
458 /* XXX Inhibiting session manager actions currently only
459 * works on GNOME, so check that we obtained a valid
460 * inhibit cookie before attempting to uninhibit. */
461 if (shell->priv->inhibit_cookie > 0) {
462 gtk_application_uninhibit (
463 application, shell->priv->inhibit_cookie);
464 shell->priv->inhibit_cookie = 0;
465 }
466
467 if (shell->priv->prepare_quit_timeout_id) {
468 g_source_remove (shell->priv->prepare_quit_timeout_id);
469 shell->priv->prepare_quit_timeout_id = 0;
470 }
471
472 /* Destroy all watched windows. Note, we iterate over a -copy-
473 * of the watched windows list because the act of destroying a
474 * watched window will modify the watched windows list, which
475 * would derail the iteration. */
476 list = g_list_copy (gtk_application_get_windows (application));
477 g_list_foreach (list, (GFunc) gtk_widget_destroy, NULL);
478 g_list_free (list);
479
480 if (gtk_main_level () > 0)
481 gtk_main_quit ();
482 }
483
484 static gboolean
shell_ask_quit_with_pending_activities(EShell * shell)485 shell_ask_quit_with_pending_activities (EShell *shell)
486 {
487 GList *windows;
488
489 windows = gtk_application_get_windows (GTK_APPLICATION (shell));
490
491 return e_alert_run_dialog_for_args (windows ? windows->data : NULL,
492 "shell:ask-quit-with-pending", NULL) == GTK_RESPONSE_OK;
493 }
494
495 static gboolean
496 shell_prepare_for_quit_timeout_cb (gpointer user_data);
497
498 static void
shell_prepare_for_quit(EShell * shell)499 shell_prepare_for_quit (EShell *shell)
500 {
501 GtkApplication *application;
502 GList *list, *iter;
503
504 /* Are preparations already in progress? */
505 if (shell->priv->preparing_for_quit != NULL) {
506 if (shell_ask_quit_with_pending_activities (shell)) {
507 e_activity_cancel (shell->priv->preparing_for_quit);
508 camel_operation_cancel_all ();
509
510 shell_ready_for_quit (shell, shell->priv->preparing_for_quit, TRUE);
511 }
512 return;
513 }
514
515 application = GTK_APPLICATION (shell);
516
517 shell->priv->inhibit_cookie = gtk_application_inhibit (
518 application, NULL,
519 GTK_APPLICATION_INHIBIT_SWITCH |
520 GTK_APPLICATION_INHIBIT_LOGOUT |
521 GTK_APPLICATION_INHIBIT_SUSPEND,
522 _("Preparing to quit"));
523
524 shell->priv->preparing_for_quit = e_activity_new ();
525
526 e_activity_set_text (
527 shell->priv->preparing_for_quit,
528 _("Preparing to quit…"));
529
530 g_object_add_toggle_ref (
531 G_OBJECT (shell->priv->preparing_for_quit),
532 (GToggleNotify) shell_ready_for_quit, shell);
533
534 g_object_add_weak_pointer (
535 G_OBJECT (shell->priv->preparing_for_quit),
536 &shell->priv->preparing_for_quit);
537
538 g_signal_emit (
539 shell, signals[PREPARE_FOR_QUIT], 0,
540 shell->priv->preparing_for_quit);
541
542 shell->priv->prepare_quit_timeout_id =
543 e_named_timeout_add_seconds (60, shell_prepare_for_quit_timeout_cb, shell);
544
545 g_object_unref (shell->priv->preparing_for_quit);
546
547 /* Desensitize all watched windows to prevent user action. */
548 list = gtk_application_get_windows (application);
549 for (iter = list; iter != NULL; iter = iter->next)
550 gtk_widget_set_sensitive (GTK_WIDGET (iter->data), FALSE);
551 }
552
553 static gboolean
shell_prepare_for_quit_timeout_cb(gpointer user_data)554 shell_prepare_for_quit_timeout_cb (gpointer user_data)
555 {
556 EShell *shell = user_data;
557
558 g_return_val_if_fail (E_IS_SHELL (shell), FALSE);
559 g_return_val_if_fail (shell->priv->preparing_for_quit != 0, FALSE);
560
561 shell->priv->prepare_quit_timeout_id = 0;
562
563 /* This asks whether to quit or wait and does all the work */
564 shell_prepare_for_quit (shell);
565
566 return FALSE;
567 }
568
569 static gboolean
shell_request_quit(EShell * shell,EShellQuitReason reason)570 shell_request_quit (EShell *shell,
571 EShellQuitReason reason)
572 {
573 /* Are preparations already in progress? */
574 if (shell->priv->preparing_for_quit != NULL)
575 return TRUE;
576
577 /* Give the application a chance to cancel quit. */
578 shell->priv->quit_cancelled = FALSE;
579 g_signal_emit (shell, signals[QUIT_REQUESTED], 0, reason);
580
581 return !shell->priv->quit_cancelled;
582 }
583
584 /* Helper for shell_add_backend() */
585 static void
shell_split_and_insert_items(GHashTable * hash_table,const gchar * items,EShellBackend * shell_backend)586 shell_split_and_insert_items (GHashTable *hash_table,
587 const gchar *items,
588 EShellBackend *shell_backend)
589 {
590 gpointer key;
591 gchar **strv;
592 gint ii;
593
594 strv = g_strsplit_set (items, ":", -1);
595
596 for (ii = 0; strv[ii] != NULL; ii++) {
597 key = (gpointer) g_intern_string (strv[ii]);
598 g_hash_table_insert (hash_table, key, shell_backend);
599 }
600
601 g_strfreev (strv);
602 }
603
604 static void
shell_process_backend(EShellBackend * shell_backend,EShell * shell)605 shell_process_backend (EShellBackend *shell_backend,
606 EShell *shell)
607 {
608 EShellBackendClass *class;
609 GHashTable *backends_by_name;
610 GHashTable *backends_by_scheme;
611 const gchar *string;
612
613 class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
614 backends_by_name = shell->priv->backends_by_name;
615 backends_by_scheme = shell->priv->backends_by_scheme;
616
617 if ((string = class->name) != NULL)
618 g_hash_table_insert (
619 backends_by_name, (gpointer)
620 g_intern_string (string), shell_backend);
621
622 if ((string = class->aliases) != NULL)
623 shell_split_and_insert_items (
624 backends_by_name, string, shell_backend);
625
626 if ((string = class->schemes) != NULL)
627 shell_split_and_insert_items (
628 backends_by_scheme, string, shell_backend);
629 }
630
631 static void
shell_backend_died_cb(EClientCache * client_cache,EClient * client,EAlert * alert,EShell * shell)632 shell_backend_died_cb (EClientCache *client_cache,
633 EClient *client,
634 EAlert *alert,
635 EShell *shell)
636 {
637 /* Backend crashes are quite serious.
638 * Post the alert to all shell views. */
639 e_shell_submit_alert (shell, alert);
640 }
641
642 static void
shell_allow_auth_prompt_cb(EClientCache * client_cache,ESource * source,EShell * shell)643 shell_allow_auth_prompt_cb (EClientCache *client_cache,
644 ESource *source,
645 EShell *shell)
646 {
647 g_return_if_fail (E_IS_SOURCE (source));
648 g_return_if_fail (E_IS_SHELL (shell));
649
650 e_shell_allow_auth_prompt_for (shell, source);
651 }
652
653 static void
shell_source_connection_status_notify_cb(ESource * source,GParamSpec * param,EAlert * alert)654 shell_source_connection_status_notify_cb (ESource *source,
655 GParamSpec *param,
656 EAlert *alert)
657 {
658 g_return_if_fail (E_IS_ALERT (alert));
659
660 if (e_source_get_connection_status (source) == E_SOURCE_CONNECTION_STATUS_DISCONNECTED ||
661 e_source_get_connection_status (source) == E_SOURCE_CONNECTION_STATUS_CONNECTING ||
662 e_source_get_connection_status (source) == E_SOURCE_CONNECTION_STATUS_CONNECTED)
663 e_alert_response (alert, GTK_RESPONSE_CLOSE);
664 }
665
666 static void
shell_submit_source_connection_alert(EShell * shell,ESource * source,EAlert * alert)667 shell_submit_source_connection_alert (EShell *shell,
668 ESource *source,
669 EAlert *alert)
670 {
671 g_return_if_fail (E_IS_SHELL (shell));
672 g_return_if_fail (E_IS_SOURCE (source));
673 g_return_if_fail (E_IS_ALERT (alert));
674
675 e_signal_connect_notify_object (source, "notify::connection-status",
676 G_CALLBACK (shell_source_connection_status_notify_cb), alert, 0);
677
678 e_shell_submit_alert (shell, alert);
679 }
680
681 static void
shell_source_invoke_authenticate_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)682 shell_source_invoke_authenticate_cb (GObject *source_object,
683 GAsyncResult *result,
684 gpointer user_data)
685 {
686 ESource *source;
687 EShell *shell = user_data;
688 GError *error = NULL;
689
690 g_return_if_fail (E_IS_SOURCE (source_object));
691
692 source = E_SOURCE (source_object);
693
694 if (!e_source_invoke_authenticate_finish (source, result, &error)) {
695 /* Can be cancelled only if the shell is disposing/disposed */
696 if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
697 EAlert *alert;
698 gchar *display_name;
699
700 g_return_if_fail (E_IS_SHELL (shell));
701
702 display_name = e_util_get_source_full_name (shell->priv->registry, source);
703 alert = e_alert_new ("shell:source-invoke-authenticate-failed",
704 display_name,
705 error->message,
706 NULL);
707 e_shell_submit_alert (shell, alert);
708 g_object_unref (alert);
709 g_free (display_name);
710 }
711
712 g_clear_error (&error);
713 }
714 }
715
716 static void
shell_wrote_ssl_trust_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)717 shell_wrote_ssl_trust_cb (GObject *source_object,
718 GAsyncResult *result,
719 gpointer user_data)
720 {
721 ESource *source;
722 GError *error = NULL;
723
724 g_return_if_fail (E_IS_SOURCE (source_object));
725
726 source = E_SOURCE (source_object);
727
728 if (!e_source_write_finish (source, result, &error) &&
729 !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
730 g_warning ("%s: Failed to save changes to source '%s' (%s): %s", G_STRFUNC,
731 e_source_get_display_name (source),
732 e_source_get_uid (source),
733 error ? error->message : "Unknown error");
734 }
735
736 g_clear_error (&error);
737 }
738
739 static gchar *
shell_extract_ssl_trust(ESource * source)740 shell_extract_ssl_trust (ESource *source)
741 {
742 gchar *ssl_trust = NULL;
743
744 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
745
746 if (e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
747 ESourceWebdav *webdav_extension;
748
749 webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
750 ssl_trust = e_source_webdav_dup_ssl_trust (webdav_extension);
751 }
752
753 return ssl_trust;
754 }
755
756 static gboolean
shell_maybe_propagate_ssl_trust(EShell * shell,ESource * source,const gchar * original_ssl_trust)757 shell_maybe_propagate_ssl_trust (EShell *shell,
758 ESource *source,
759 const gchar *original_ssl_trust)
760 {
761 gchar *new_ssl_trust;
762 gboolean changed;
763
764 g_return_val_if_fail (E_IS_SHELL (shell), FALSE);
765 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
766
767 new_ssl_trust = shell_extract_ssl_trust (source);
768 changed = g_strcmp0 (original_ssl_trust, new_ssl_trust) != 0;
769
770 if (changed && new_ssl_trust && *new_ssl_trust) {
771 g_object_ref (source);
772
773 while (source && !e_source_has_extension (source, E_SOURCE_EXTENSION_COLLECTION)) {
774 ESource *parent = NULL;
775
776 if (e_source_get_parent (source))
777 parent = e_source_registry_ref_source (shell->priv->registry, e_source_get_parent (source));
778
779 g_clear_object (&source);
780
781 source = parent;
782 }
783
784 if (source) {
785 const gchar *uid;
786 GList *sources, *link;
787 gchar *ssl_trust;
788
789 if (e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
790 ssl_trust = shell_extract_ssl_trust (source);
791
792 if (g_strcmp0 (ssl_trust, original_ssl_trust) == 0) {
793 ESourceWebdav *webdav_extension;
794
795 webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
796 e_source_webdav_set_ssl_trust (webdav_extension, new_ssl_trust);
797
798 e_source_write (source, shell->priv->cancellable, shell_wrote_ssl_trust_cb, NULL);
799 }
800
801 g_free (ssl_trust);
802 }
803
804 uid = e_source_get_uid (source);
805
806 sources = e_source_registry_list_sources (shell->priv->registry, NULL);
807
808 for (link = sources; link; link = g_list_next (link)) {
809 ESource *child = link->data;
810
811 if (g_strcmp0 (uid, e_source_get_parent (child)) == 0 &&
812 e_source_has_extension (child, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
813 ssl_trust = shell_extract_ssl_trust (child);
814
815 if (g_strcmp0 (ssl_trust, original_ssl_trust) == 0) {
816 ESourceWebdav *webdav_extension;
817
818 webdav_extension = e_source_get_extension (child, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
819 e_source_webdav_set_ssl_trust (webdav_extension, new_ssl_trust);
820
821 e_source_write (child, shell->priv->cancellable, shell_wrote_ssl_trust_cb, NULL);
822 }
823
824 g_free (ssl_trust);
825 }
826 }
827
828 g_list_free_full (sources, g_object_unref);
829 }
830
831 g_clear_object (&source);
832 }
833
834 g_free (new_ssl_trust);
835
836 return changed;
837 }
838
839 static ETrustPromptResponse
shell_get_source_last_trust_response(ESource * source)840 shell_get_source_last_trust_response (ESource *source)
841 {
842 g_return_val_if_fail (E_IS_SOURCE (source), E_TRUST_PROMPT_RESPONSE_UNKNOWN);
843
844 if (!e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND))
845 return E_TRUST_PROMPT_RESPONSE_UNKNOWN;
846
847 return e_source_webdav_get_ssl_trust_response (e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND));
848 }
849
850 #define SOURCE_ALERT_KEY_SOURCE "source-alert-key-source"
851 #define SOURCE_ALERT_KEY_CERTIFICATE_PEM "source-alert-key-certificate-pem"
852 #define SOURCE_ALERT_KEY_CERTIFICATE_ERRORS "source-alert-key-certificate-errors"
853 #define SOURCE_ALERT_KEY_ERROR_TEXT "source-alert-key-error-text"
854
855 typedef struct _TrustPromptData {
856 EShell *shell; /* not referenced */
857 gchar *original_ssl_trust;
858 } TrustPromptData;
859
860 static void
trust_prompt_data_free(gpointer ptr)861 trust_prompt_data_free (gpointer ptr)
862 {
863 TrustPromptData *tpd = ptr;
864
865 if (tpd) {
866 g_free (tpd->original_ssl_trust);
867 g_slice_free (TrustPromptData, tpd);
868 }
869 }
870
871 static void
shell_trust_prompt_done_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)872 shell_trust_prompt_done_cb (GObject *source_object,
873 GAsyncResult *result,
874 gpointer user_data)
875 {
876 ESource *source;
877 ETrustPromptResponse response = E_TRUST_PROMPT_RESPONSE_UNKNOWN;
878 TrustPromptData *tpd = user_data;
879 GError *error = NULL;
880
881 g_return_if_fail (E_IS_SOURCE (source_object));
882 g_return_if_fail (tpd != NULL);
883
884 source = E_SOURCE (source_object);
885
886 if (!e_trust_prompt_run_for_source_finish (source, result, &response, &error)) {
887 /* Can be cancelled only if the shell is disposing/disposed */
888 if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
889 EAlert *alert;
890 gchar *display_name;
891
892 g_return_if_fail (E_IS_SHELL (tpd->shell));
893
894 display_name = e_util_get_source_full_name (tpd->shell->priv->registry, source);
895 alert = e_alert_new ("shell:source-trust-prompt-failed",
896 display_name,
897 error->message,
898 NULL);
899 e_shell_submit_alert (tpd->shell, alert);
900 g_object_unref (alert);
901 g_free (display_name);
902 }
903
904 g_clear_error (&error);
905 trust_prompt_data_free (tpd);
906 return;
907 }
908
909 g_return_if_fail (E_IS_SHELL (tpd->shell));
910
911 if (response == E_TRUST_PROMPT_RESPONSE_UNKNOWN) {
912 e_credentials_prompter_set_auto_prompt_disabled_for (tpd->shell->priv->credentials_prompter, source, TRUE);
913 trust_prompt_data_free (tpd);
914 return;
915 }
916
917 /* If a credentials prompt is required, then it'll be shown immediately. */
918 e_credentials_prompter_set_auto_prompt_disabled_for (tpd->shell->priv->credentials_prompter, source, FALSE);
919
920 if (shell_maybe_propagate_ssl_trust (tpd->shell, source, tpd->original_ssl_trust)) {
921 /* NULL credentials to retry with those used the last time */
922 e_source_invoke_authenticate (source, NULL, tpd->shell->priv->cancellable,
923 shell_source_invoke_authenticate_cb, tpd->shell);
924 }
925
926 trust_prompt_data_free (tpd);
927 }
928
929 static void
shell_credentials_prompt_done_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)930 shell_credentials_prompt_done_cb (GObject *source_object,
931 GAsyncResult *result,
932 gpointer user_data)
933 {
934 EShell *shell = user_data;
935 ESource *source = NULL;
936 ENamedParameters *credentials = NULL;
937 GError *error = NULL;
938
939 g_return_if_fail (E_IS_SHELL (shell));
940
941 if (e_credentials_prompter_prompt_finish (E_CREDENTIALS_PROMPTER (source_object), result, &source, &credentials, &error)) {
942 e_source_invoke_authenticate (source, credentials, shell->priv->cancellable,
943 shell_source_invoke_authenticate_cb, shell);
944 } else if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
945 EAlert *alert;
946 gchar *display_name;
947
948 g_return_if_fail (E_IS_SHELL (shell));
949
950 display_name = e_util_get_source_full_name (shell->priv->registry, source);
951 alert = e_alert_new ("shell:source-credentials-prompt-failed",
952 display_name,
953 error->message,
954 NULL);
955 e_shell_submit_alert (shell, alert);
956 g_object_unref (alert);
957 g_free (display_name);
958 }
959
960 e_named_parameters_free (credentials);
961 g_clear_object (&source);
962 g_clear_object (&shell);
963 g_clear_error (&error);
964 }
965
966 static void
shell_connection_error_alert_response_cb(EAlert * alert,gint response_id,EShell * shell)967 shell_connection_error_alert_response_cb (EAlert *alert,
968 gint response_id,
969 EShell *shell)
970 {
971 ESource *source;
972
973 g_return_if_fail (E_IS_SHELL (shell));
974
975 if (response_id != GTK_RESPONSE_APPLY)
976 return;
977
978 source = g_object_get_data (G_OBJECT (alert), SOURCE_ALERT_KEY_SOURCE);
979 g_return_if_fail (E_IS_SOURCE (source));
980
981 e_credentials_prompter_set_auto_prompt_disabled_for (shell->priv->credentials_prompter, source, FALSE);
982
983 e_credentials_prompter_prompt (shell->priv->credentials_prompter, source, NULL,
984 E_CREDENTIALS_PROMPTER_PROMPT_FLAG_ALLOW_STORED_CREDENTIALS,
985 shell_credentials_prompt_done_cb, g_object_ref (shell));
986 }
987
988 static void
shell_connect_error_open_settings_goa_clicked_cb(GtkButton * button,EAlert * alert)989 shell_connect_error_open_settings_goa_clicked_cb (GtkButton *button,
990 EAlert *alert)
991 {
992 const gchar *account_id;
993 gchar *command_line, *control_center_path;
994 GError *error = NULL;
995
996 /* The SOURCE_ALERT_KEY_SOURCE is not an ESource here */
997 account_id = g_object_get_data (G_OBJECT (button), SOURCE_ALERT_KEY_SOURCE);
998
999 control_center_path = g_find_program_in_path ("gnome-control-center");
1000 command_line = g_strjoin (
1001 " ",
1002 control_center_path,
1003 "online-accounts",
1004 account_id,
1005 NULL);
1006
1007 g_spawn_command_line_async (command_line, &error);
1008
1009 g_free (command_line);
1010 g_free (control_center_path);
1011
1012 if (error != NULL) {
1013 g_warning ("%s: %s", G_STRFUNC, error->message);
1014 g_error_free (error);
1015 }
1016 }
1017
1018 static void
shell_connect_trust_error_alert_response_cb(EAlert * alert,gint response_id,EShell * shell)1019 shell_connect_trust_error_alert_response_cb (EAlert *alert,
1020 gint response_id,
1021 EShell *shell)
1022 {
1023 ESource *source;
1024 const gchar *certificate_pem;
1025 GTlsCertificateFlags certificate_errors;
1026 const gchar *error_text;
1027 TrustPromptData *tpd;
1028
1029 g_return_if_fail (E_IS_SHELL (shell));
1030
1031 if (response_id != GTK_RESPONSE_APPLY)
1032 return;
1033
1034 source = g_object_get_data (G_OBJECT (alert), SOURCE_ALERT_KEY_SOURCE);
1035 certificate_pem = g_object_get_data (G_OBJECT (alert), SOURCE_ALERT_KEY_CERTIFICATE_PEM);
1036 certificate_errors = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (alert), SOURCE_ALERT_KEY_CERTIFICATE_ERRORS));
1037 error_text = g_object_get_data (G_OBJECT (alert), SOURCE_ALERT_KEY_ERROR_TEXT);
1038
1039 g_return_if_fail (E_IS_SOURCE (source));
1040
1041 g_object_set_data_full (G_OBJECT (source), SOURCE_ALERT_KEY_CERTIFICATE_PEM, g_strdup (certificate_pem), g_free);
1042
1043 tpd = g_slice_new0 (TrustPromptData);
1044 tpd->shell = shell;
1045 tpd->original_ssl_trust = shell_extract_ssl_trust (source);
1046
1047 e_trust_prompt_run_for_source (gtk_application_get_active_window (GTK_APPLICATION (shell)),
1048 source, certificate_pem, certificate_errors, error_text, TRUE,
1049 shell->priv->cancellable, shell_trust_prompt_done_cb, tpd);
1050 }
1051
1052 static void
shell_maybe_add_connect_error_goa_button(EAlert * alert,ESource * source,ESourceRegistry * registry)1053 shell_maybe_add_connect_error_goa_button (EAlert *alert,
1054 ESource *source,
1055 ESourceRegistry *registry)
1056 {
1057 gchar *account_id = NULL;
1058
1059 g_return_if_fail (E_IS_ALERT (alert));
1060 g_return_if_fail (E_IS_SOURCE (source));
1061 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
1062
1063 if (e_source_has_extension (source, E_SOURCE_EXTENSION_GOA)) {
1064 account_id = e_source_goa_dup_account_id (e_source_get_extension (source, E_SOURCE_EXTENSION_GOA));
1065 } else if (e_source_get_parent (source)) {
1066 ESource *parent;
1067
1068 parent = e_source_registry_ref_source (registry, e_source_get_parent (source));
1069 if (parent && e_source_has_extension (parent, E_SOURCE_EXTENSION_GOA))
1070 account_id = e_source_goa_dup_account_id (e_source_get_extension (parent, E_SOURCE_EXTENSION_GOA));
1071
1072 g_clear_object (&parent);
1073 }
1074
1075 if (account_id) {
1076 gchar *control_center_path;
1077
1078 control_center_path = g_find_program_in_path ("gnome-control-center");
1079
1080 if (!control_center_path || !*control_center_path) {
1081 g_free (account_id);
1082 account_id = NULL;
1083 }
1084
1085 g_free (control_center_path);
1086 }
1087
1088 if (account_id) {
1089 GtkWidget *button;
1090
1091 button = gtk_button_new_with_mnemonic (_("Open _Settings"));
1092 /* The SOURCE_ALERT_KEY_SOURCE is not an ESource here */
1093 g_object_set_data_full (G_OBJECT (button), SOURCE_ALERT_KEY_SOURCE, g_strdup (account_id), g_free);
1094 gtk_widget_show (button);
1095
1096 g_signal_connect (button, "clicked",
1097 G_CALLBACK (shell_connect_error_open_settings_goa_clicked_cb), alert);
1098
1099 e_alert_add_widget (alert, button);
1100 }
1101
1102 g_free (account_id);
1103 }
1104
1105 static const gchar *
shell_get_connection_error_tag_for_source(ESource * source)1106 shell_get_connection_error_tag_for_source (ESource *source)
1107 {
1108 const gchar *tag = "shell:source-connection-error";
1109 const gchar *override_tag = NULL;
1110
1111 g_return_val_if_fail (E_IS_SOURCE (source), tag);
1112
1113 if (e_source_has_extension (source, E_SOURCE_EXTENSION_ADDRESS_BOOK)) {
1114 override_tag = "shell:addressbook-connection-error";
1115 }
1116
1117 if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) {
1118 if (!override_tag)
1119 override_tag = "shell:calendar-connection-error";
1120 else
1121 override_tag = "";
1122 }
1123
1124 if (e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_ACCOUNT) ||
1125 e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_TRANSPORT)) {
1126 if (!override_tag)
1127 override_tag = "shell:mail-connection-error";
1128 else
1129 override_tag = "";
1130 }
1131
1132 if (e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST)) {
1133 if (!override_tag)
1134 override_tag = "shell:memo-list-connection-error";
1135 else
1136 override_tag = "";
1137 }
1138
1139 if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) {
1140 if (!override_tag)
1141 override_tag = "shell:task-list-connection-error";
1142 else
1143 override_tag = "";
1144 }
1145
1146 if (override_tag && *override_tag)
1147 return override_tag;
1148
1149 return tag;
1150 }
1151
1152 static const gchar *
shell_get_connection_trust_error_tag_for_source(ESource * source)1153 shell_get_connection_trust_error_tag_for_source (ESource *source)
1154 {
1155 const gchar *tag = "shell:source-connection-trust-error";
1156 const gchar *override_tag = NULL;
1157
1158 g_return_val_if_fail (E_IS_SOURCE (source), tag);
1159
1160 if (e_source_has_extension (source, E_SOURCE_EXTENSION_ADDRESS_BOOK)) {
1161 override_tag = "shell:addressbook-connection-trust-error";
1162 }
1163
1164 if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) {
1165 if (!override_tag)
1166 override_tag = "shell:calendar-connection-trust-error";
1167 else
1168 override_tag = "";
1169 }
1170
1171 if (e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_ACCOUNT) ||
1172 e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_TRANSPORT)) {
1173 if (!override_tag)
1174 override_tag = "shell:mail-connection-trust-error";
1175 else
1176 override_tag = "";
1177 }
1178
1179 if (e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST)) {
1180 if (!override_tag)
1181 override_tag = "shell:memo-list-connection-trust-error";
1182 else
1183 override_tag = "";
1184 }
1185
1186 if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) {
1187 if (!override_tag)
1188 override_tag = "shell:task-list-connection-trust-error";
1189 else
1190 override_tag = "";
1191 }
1192
1193 if (override_tag && *override_tag)
1194 return override_tag;
1195
1196 return tag;
1197 }
1198
1199 static void
shell_process_credentials_required_errors(EShell * shell,ESource * source,ESourceCredentialsReason reason,const gchar * certificate_pem,GTlsCertificateFlags certificate_errors,const GError * op_error)1200 shell_process_credentials_required_errors (EShell *shell,
1201 ESource *source,
1202 ESourceCredentialsReason reason,
1203 const gchar *certificate_pem,
1204 GTlsCertificateFlags certificate_errors,
1205 const GError *op_error)
1206 {
1207 g_return_if_fail (E_IS_SHELL (shell));
1208 g_return_if_fail (E_IS_SOURCE (source));
1209
1210 /* Skip disabled sources */
1211 if (!e_source_registry_check_enabled (shell->priv->registry, source))
1212 return;
1213
1214 switch (reason) {
1215 case E_SOURCE_CREDENTIALS_REASON_UNKNOWN:
1216 /* This should not be here */
1217 g_warn_if_reached ();
1218 return;
1219 case E_SOURCE_CREDENTIALS_REASON_REQUIRED:
1220 case E_SOURCE_CREDENTIALS_REASON_REJECTED:
1221 /* These are handled by the credentials prompter, if not disabled */
1222 if (e_credentials_prompter_get_auto_prompt_disabled_for (shell->priv->credentials_prompter, source))
1223 break;
1224
1225 return;
1226 case E_SOURCE_CREDENTIALS_REASON_SSL_FAILED:
1227 case E_SOURCE_CREDENTIALS_REASON_ERROR:
1228 break;
1229 }
1230
1231 if (reason == E_SOURCE_CREDENTIALS_REASON_ERROR) {
1232 EAlert *alert;
1233 gchar *display_name;
1234
1235 display_name = e_util_get_source_full_name (shell->priv->registry, source);
1236 alert = e_alert_new (shell_get_connection_error_tag_for_source (source),
1237 display_name,
1238 op_error && *(op_error->message) ? op_error->message : _("Unknown error"),
1239 NULL);
1240 g_free (display_name);
1241
1242 shell_maybe_add_connect_error_goa_button (alert, source, shell->priv->registry);
1243
1244 g_signal_connect (alert, "response", G_CALLBACK (shell_connection_error_alert_response_cb), shell);
1245 g_object_set_data_full (G_OBJECT (alert), SOURCE_ALERT_KEY_SOURCE, g_object_ref (source), g_object_unref);
1246
1247 shell_submit_source_connection_alert (shell, source, alert);
1248 g_object_unref (alert);
1249 } else if (reason == E_SOURCE_CREDENTIALS_REASON_SSL_FAILED) {
1250 if (shell_get_source_last_trust_response (source) != E_TRUST_PROMPT_RESPONSE_REJECT) {
1251 if (e_credentials_prompter_get_auto_prompt_disabled_for (shell->priv->credentials_prompter, source)) {
1252 /* Only show an alert */
1253 EAlert *alert;
1254 gchar *cert_errors_str;
1255 gchar *display_name;
1256
1257 cert_errors_str = e_trust_prompt_describe_certificate_errors (certificate_errors);
1258
1259 display_name = e_util_get_source_full_name (shell->priv->registry, source);
1260 alert = e_alert_new (shell_get_connection_trust_error_tag_for_source (source),
1261 display_name,
1262 (cert_errors_str && *cert_errors_str) ? cert_errors_str :
1263 op_error && *(op_error->message) ? op_error->message : _("Unknown error"),
1264 NULL);
1265 g_free (display_name);
1266
1267 g_signal_connect (alert, "response", G_CALLBACK (shell_connect_trust_error_alert_response_cb), shell);
1268
1269 g_object_set_data_full (G_OBJECT (alert), SOURCE_ALERT_KEY_SOURCE, g_object_ref (source), g_object_unref);
1270 g_object_set_data_full (G_OBJECT (alert), SOURCE_ALERT_KEY_CERTIFICATE_PEM, g_strdup (certificate_pem), g_free);
1271 g_object_set_data (G_OBJECT (alert), SOURCE_ALERT_KEY_CERTIFICATE_ERRORS, GUINT_TO_POINTER (certificate_errors));
1272 g_object_set_data_full (G_OBJECT (alert), SOURCE_ALERT_KEY_ERROR_TEXT, op_error ? g_strdup (op_error->message) : NULL, g_free);
1273
1274 shell_submit_source_connection_alert (shell, source, alert);
1275
1276 g_free (cert_errors_str);
1277 g_object_unref (alert);
1278 } else {
1279 TrustPromptData *tpd;
1280
1281 g_object_set_data_full (G_OBJECT (source), SOURCE_ALERT_KEY_CERTIFICATE_PEM, g_strdup (certificate_pem), g_free);
1282
1283 tpd = g_slice_new0 (TrustPromptData);
1284 tpd->shell = shell;
1285 tpd->original_ssl_trust = shell_extract_ssl_trust (source);
1286
1287 e_trust_prompt_run_for_source (gtk_application_get_active_window (GTK_APPLICATION (shell)),
1288 source, certificate_pem, certificate_errors, op_error ? op_error->message : NULL, TRUE,
1289 shell->priv->cancellable, shell_trust_prompt_done_cb, tpd);
1290 }
1291 }
1292 } else if (reason == E_SOURCE_CREDENTIALS_REASON_REQUIRED ||
1293 reason == E_SOURCE_CREDENTIALS_REASON_REJECTED) {
1294 EAlert *alert;
1295 gchar *display_name;
1296
1297 display_name = e_util_get_source_full_name (shell->priv->registry, source);
1298 alert = e_alert_new (shell_get_connection_error_tag_for_source (source),
1299 display_name,
1300 op_error && *(op_error->message) ? op_error->message : _("Credentials are required to connect to the destination host."),
1301 NULL);
1302 g_free (display_name);
1303
1304 shell_maybe_add_connect_error_goa_button (alert, source, shell->priv->registry);
1305
1306 g_signal_connect (alert, "response", G_CALLBACK (shell_connection_error_alert_response_cb), shell);
1307 g_object_set_data_full (G_OBJECT (alert), SOURCE_ALERT_KEY_SOURCE, g_object_ref (source), g_object_unref);
1308
1309 shell_submit_source_connection_alert (shell, source, alert);
1310 g_object_unref (alert);
1311 } else {
1312 g_warn_if_reached ();
1313 }
1314 }
1315
1316 static void
shell_get_last_credentials_required_arguments_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)1317 shell_get_last_credentials_required_arguments_cb (GObject *source_object,
1318 GAsyncResult *result,
1319 gpointer user_data)
1320 {
1321 EShell *shell = user_data;
1322 ESource *source;
1323 ESourceCredentialsReason reason = E_SOURCE_CREDENTIALS_REASON_UNKNOWN;
1324 gchar *certificate_pem = NULL;
1325 GTlsCertificateFlags certificate_errors = 0;
1326 GError *op_error = NULL;
1327 GError *error = NULL;
1328
1329 g_return_if_fail (E_IS_SOURCE (source_object));
1330
1331 source = E_SOURCE (source_object);
1332
1333 if (!e_source_get_last_credentials_required_arguments_finish (source, result, &reason,
1334 &certificate_pem, &certificate_errors, &op_error, &error)) {
1335 /* Can be cancelled only if the shell is disposing/disposed */
1336 if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
1337 EAlert *alert;
1338 gchar *display_name;
1339
1340 g_return_if_fail (E_IS_SHELL (shell));
1341
1342 display_name = e_util_get_source_full_name (shell->priv->registry, source);
1343 alert = e_alert_new ("shell:source-get-values-failed",
1344 display_name,
1345 error->message,
1346 NULL);
1347 e_shell_submit_alert (shell, alert);
1348 g_object_unref (alert);
1349 g_free (display_name);
1350 }
1351
1352 g_clear_error (&error);
1353 return;
1354 }
1355
1356 g_return_if_fail (E_IS_SHELL (shell));
1357
1358 if (reason != E_SOURCE_CREDENTIALS_REASON_UNKNOWN)
1359 shell_process_credentials_required_errors (shell, source, reason, certificate_pem, certificate_errors, op_error);
1360
1361 g_free (certificate_pem);
1362 g_clear_error (&op_error);
1363 }
1364
1365 static void
shell_process_failed_authentications(EShell * shell)1366 shell_process_failed_authentications (EShell *shell)
1367 {
1368 GList *sources, *link;
1369
1370 g_return_if_fail (E_IS_SHELL (shell));
1371
1372 sources = e_source_registry_list_enabled (shell->priv->registry, NULL);
1373
1374 for (link = sources; link; link = g_list_next (link)) {
1375 ESource *source = link->data;
1376
1377 if (source && (
1378 e_source_get_connection_status (source) == E_SOURCE_CONNECTION_STATUS_DISCONNECTED ||
1379 e_source_get_connection_status (source) == E_SOURCE_CONNECTION_STATUS_SSL_FAILED)) {
1380 /* Only show alerts, do not open windows */
1381 e_credentials_prompter_set_auto_prompt_disabled_for (shell->priv->credentials_prompter, source, TRUE);
1382
1383 e_source_get_last_credentials_required_arguments (source, shell->priv->cancellable,
1384 shell_get_last_credentials_required_arguments_cb, shell);
1385 }
1386 }
1387
1388 g_list_free_full (sources, g_object_unref);
1389 }
1390
1391 static void
shell_credentials_required_cb(ESourceRegistry * registry,ESource * source,ESourceCredentialsReason reason,const gchar * certificate_pem,GTlsCertificateFlags certificate_errors,const GError * op_error,EShell * shell)1392 shell_credentials_required_cb (ESourceRegistry *registry,
1393 ESource *source,
1394 ESourceCredentialsReason reason,
1395 const gchar *certificate_pem,
1396 GTlsCertificateFlags certificate_errors,
1397 const GError *op_error,
1398 EShell *shell)
1399 {
1400 g_return_if_fail (E_IS_SHELL (shell));
1401
1402 shell_process_credentials_required_errors (shell, source, reason, certificate_pem, certificate_errors, op_error);
1403 }
1404
1405 static GtkWindow *
shell_get_dialog_parent_full_cb(ECredentialsPrompter * prompter,ESource * auth_source,EShell * shell)1406 shell_get_dialog_parent_full_cb (ECredentialsPrompter *prompter,
1407 ESource *auth_source,
1408 EShell *shell)
1409 {
1410 GList *windows, *link;
1411 GtkWindow *override = NULL, *adept = NULL;
1412
1413 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
1414
1415 if (auth_source)
1416 override = g_hash_table_lookup (shell->priv->auth_prompt_parents, e_source_get_uid (auth_source));
1417
1418 windows = gtk_application_get_windows (GTK_APPLICATION (shell));
1419 for (link = windows; link; link = g_list_next (link)) {
1420 GtkWindow *window = link->data;
1421
1422 if (!adept && E_IS_SHELL_WINDOW (window))
1423 adept = window;
1424
1425 if (override == window || (!override && adept))
1426 return window;
1427 }
1428
1429 return adept;
1430 }
1431
1432 static GtkWindow *
shell_get_dialog_parent_cb(ECredentialsPrompter * prompter,EShell * shell)1433 shell_get_dialog_parent_cb (ECredentialsPrompter *prompter,
1434 EShell *shell)
1435 {
1436 return shell_get_dialog_parent_full_cb (prompter, NULL, shell);
1437 }
1438
1439 static void
shell_sm_quit_cb(EShell * shell,gpointer user_data)1440 shell_sm_quit_cb (EShell *shell,
1441 gpointer user_data)
1442 {
1443 if (!shell->priv->ready_to_quit)
1444 shell_prepare_for_quit (shell);
1445 }
1446
1447 static void
shell_set_express_mode(EShell * shell,gboolean express_mode)1448 shell_set_express_mode (EShell *shell,
1449 gboolean express_mode)
1450 {
1451 shell->priv->express_mode = express_mode;
1452 }
1453
1454 static void
shell_set_geometry(EShell * shell,const gchar * geometry)1455 shell_set_geometry (EShell *shell,
1456 const gchar *geometry)
1457 {
1458 g_return_if_fail (shell->priv->geometry == NULL);
1459
1460 shell->priv->geometry = g_strdup (geometry);
1461 }
1462
1463 static void
shell_set_module_directory(EShell * shell,const gchar * module_directory)1464 shell_set_module_directory (EShell *shell,
1465 const gchar *module_directory)
1466 {
1467 g_return_if_fail (shell->priv->module_directory == NULL);
1468
1469 shell->priv->module_directory = g_strdup (module_directory);
1470 }
1471
1472 static void
shell_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)1473 shell_set_property (GObject *object,
1474 guint property_id,
1475 const GValue *value,
1476 GParamSpec *pspec)
1477 {
1478 switch (property_id) {
1479 case PROP_EXPRESS_MODE:
1480 shell_set_express_mode (
1481 E_SHELL (object),
1482 g_value_get_boolean (value));
1483 return;
1484
1485 case PROP_GEOMETRY:
1486 shell_set_geometry (
1487 E_SHELL (object),
1488 g_value_get_string (value));
1489 return;
1490
1491 case PROP_MODULE_DIRECTORY:
1492 shell_set_module_directory (
1493 E_SHELL (object),
1494 g_value_get_string (value));
1495 return;
1496
1497 case PROP_NETWORK_AVAILABLE:
1498 e_shell_set_network_available (
1499 E_SHELL (object),
1500 g_value_get_boolean (value));
1501 return;
1502
1503 case PROP_ONLINE:
1504 e_shell_set_online (
1505 E_SHELL (object),
1506 g_value_get_boolean (value));
1507 return;
1508 }
1509
1510 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1511 }
1512
1513 static void
shell_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)1514 shell_get_property (GObject *object,
1515 guint property_id,
1516 GValue *value,
1517 GParamSpec *pspec)
1518 {
1519 switch (property_id) {
1520 case PROP_CLIENT_CACHE:
1521 g_value_set_object (
1522 value, e_shell_get_client_cache (
1523 E_SHELL (object)));
1524 return;
1525
1526 case PROP_EXPRESS_MODE:
1527 g_value_set_boolean (
1528 value, e_shell_get_express_mode (
1529 E_SHELL (object)));
1530 return;
1531
1532 case PROP_MODULE_DIRECTORY:
1533 g_value_set_string (
1534 value, e_shell_get_module_directory (
1535 E_SHELL (object)));
1536 return;
1537
1538 case PROP_NETWORK_AVAILABLE:
1539 g_value_set_boolean (
1540 value, e_shell_get_network_available (
1541 E_SHELL (object)));
1542 return;
1543
1544 case PROP_ONLINE:
1545 g_value_set_boolean (
1546 value, e_shell_get_online (
1547 E_SHELL (object)));
1548 return;
1549
1550 case PROP_REGISTRY:
1551 g_value_set_object (
1552 value, e_shell_get_registry (
1553 E_SHELL (object)));
1554 return;
1555
1556 case PROP_CREDENTIALS_PROMPTER:
1557 g_value_set_object (
1558 value, e_shell_get_credentials_prompter (
1559 E_SHELL (object)));
1560 return;
1561 }
1562
1563 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1564 }
1565
1566 static void
shell_dispose(GObject * object)1567 shell_dispose (GObject *object)
1568 {
1569 EShellPrivate *priv;
1570 EAlert *alert;
1571
1572 priv = E_SHELL_GET_PRIVATE (object);
1573
1574 if (priv->set_online_timeout_id > 0) {
1575 g_source_remove (priv->set_online_timeout_id);
1576 priv->set_online_timeout_id = 0;
1577 }
1578
1579 if (priv->prepare_quit_timeout_id) {
1580 g_source_remove (priv->prepare_quit_timeout_id);
1581 priv->prepare_quit_timeout_id = 0;
1582 }
1583
1584 if (priv->cancellable) {
1585 g_cancellable_cancel (priv->cancellable);
1586 g_clear_object (&priv->cancellable);
1587 }
1588
1589 while ((alert = g_queue_pop_head (&priv->alerts)) != NULL) {
1590 g_signal_handlers_disconnect_by_func (
1591 alert, shell_alert_response_cb, object);
1592 g_object_unref (alert);
1593 }
1594
1595 while ((alert = g_queue_pop_head (&priv->alerts)) != NULL) {
1596 g_signal_handlers_disconnect_by_func (
1597 alert, shell_alert_response_cb, object);
1598 g_object_unref (alert);
1599 }
1600
1601 if (priv->backend_died_handler_id > 0) {
1602 g_signal_handler_disconnect (
1603 priv->client_cache,
1604 priv->backend_died_handler_id);
1605 priv->backend_died_handler_id = 0;
1606 }
1607
1608 if (priv->allow_auth_prompt_handler_id > 0) {
1609 g_signal_handler_disconnect (
1610 priv->client_cache,
1611 priv->allow_auth_prompt_handler_id);
1612 priv->allow_auth_prompt_handler_id = 0;
1613 }
1614
1615 if (priv->credentials_required_handler_id > 0) {
1616 g_signal_handler_disconnect (
1617 priv->registry,
1618 priv->credentials_required_handler_id);
1619 priv->credentials_required_handler_id = 0;
1620 }
1621
1622 if (priv->get_dialog_parent_handler_id > 0) {
1623 g_signal_handler_disconnect (
1624 priv->credentials_prompter,
1625 priv->get_dialog_parent_handler_id);
1626 priv->get_dialog_parent_handler_id = 0;
1627 }
1628
1629 if (priv->get_dialog_parent_full_handler_id > 0) {
1630 g_signal_handler_disconnect (
1631 priv->credentials_prompter,
1632 priv->get_dialog_parent_full_handler_id);
1633 priv->get_dialog_parent_full_handler_id = 0;
1634 }
1635
1636 g_clear_object (&priv->registry);
1637 g_clear_object (&priv->credentials_prompter);
1638 g_clear_object (&priv->client_cache);
1639
1640 g_clear_pointer (&priv->preferences_window, gtk_widget_destroy);
1641
1642 if (priv->preparing_for_line_change != NULL) {
1643 g_object_remove_weak_pointer (
1644 G_OBJECT (priv->preparing_for_line_change),
1645 &priv->preparing_for_line_change);
1646 }
1647
1648 /* Chain up to parent's dispose() method. */
1649 G_OBJECT_CLASS (e_shell_parent_class)->dispose (object);
1650 }
1651
1652 static void
shell_finalize(GObject * object)1653 shell_finalize (GObject *object)
1654 {
1655 EShellPrivate *priv;
1656
1657 priv = E_SHELL_GET_PRIVATE (object);
1658
1659 g_hash_table_destroy (priv->backends_by_name);
1660 g_hash_table_destroy (priv->backends_by_scheme);
1661 g_hash_table_destroy (priv->auth_prompt_parents);
1662
1663 g_list_foreach (priv->loaded_backends, (GFunc) g_object_unref, NULL);
1664 g_list_free (priv->loaded_backends);
1665
1666 g_free (priv->geometry);
1667 g_free (priv->module_directory);
1668
1669 /* Chain up to parent's finalize() method. */
1670 G_OBJECT_CLASS (e_shell_parent_class)->finalize (object);
1671 }
1672
1673 static void
shell_constructed(GObject * object)1674 shell_constructed (GObject *object)
1675 {
1676 GNetworkMonitor *monitor;
1677
1678 /* The first EShell instance is the default. */
1679 if (default_shell == NULL) {
1680 default_shell = object;
1681 g_object_add_weak_pointer (object, &default_shell);
1682 }
1683
1684 /* Synchronize network monitoring. */
1685
1686 monitor = e_network_monitor_get_default ();
1687
1688 e_binding_bind_property (
1689 monitor, "network-available",
1690 object, "network-available",
1691 G_BINDING_SYNC_CREATE);
1692
1693 /* Chain up to parent's constructed() method. */
1694 G_OBJECT_CLASS (e_shell_parent_class)->constructed (object);
1695
1696 g_signal_connect (
1697 object, "window-removed",
1698 G_CALLBACK (shell_window_removed_cb), NULL);
1699 }
1700
1701 static void
shell_startup(GApplication * application)1702 shell_startup (GApplication *application)
1703 {
1704 EShell *shell;
1705
1706 g_return_if_fail (E_IS_SHELL (application));
1707
1708 shell = E_SHELL (application);
1709 g_warn_if_fail (!shell->priv->requires_shutdown);
1710
1711 shell->priv->requires_shutdown = TRUE;
1712
1713 e_file_lock_create ();
1714
1715 /* Destroy the lock file when the EShell is finalized
1716 * to indicate a clean shut down to the next session. */
1717 g_object_weak_ref (
1718 G_OBJECT (application),
1719 (GWeakNotify) e_file_lock_destroy, NULL);
1720
1721 /* Chain up to parent's startup() method. */
1722 G_APPLICATION_CLASS (e_shell_parent_class)->startup (application);
1723 }
1724
1725 static void
shell_shutdown(GApplication * application)1726 shell_shutdown (GApplication *application)
1727 {
1728 EShell *shell;
1729
1730 g_return_if_fail (E_IS_SHELL (application));
1731
1732 shell = E_SHELL (application);
1733
1734 g_warn_if_fail (shell->priv->requires_shutdown);
1735
1736 shell->priv->requires_shutdown = FALSE;
1737
1738 /* Chain up to parent's method. */
1739 G_APPLICATION_CLASS (e_shell_parent_class)->shutdown (application);
1740 }
1741
1742 static void
shell_activate(GApplication * application)1743 shell_activate (GApplication *application)
1744 {
1745 GList *list;
1746
1747 /* Do not chain up. Default method just emits a warning. */
1748
1749 list = gtk_application_get_windows (GTK_APPLICATION (application));
1750
1751 /* Present the first EShellWindow, if found. */
1752 while (list != NULL) {
1753 GtkWindow *window = GTK_WINDOW (list->data);
1754
1755 if (E_IS_SHELL_WINDOW (window)) {
1756 gtk_window_present (window);
1757 return;
1758 }
1759
1760 list = g_list_next (list);
1761 }
1762
1763 /* No EShellWindow found, so create one. */
1764 e_shell_create_shell_window (E_SHELL (application), NULL);
1765 }
1766
1767 static void
shell_window_added(GtkApplication * application,GtkWindow * window)1768 shell_window_added (GtkApplication *application,
1769 GtkWindow *window)
1770 {
1771 gchar *role;
1772
1773 /* Chain up to parent's window_added() method. */
1774 GTK_APPLICATION_CLASS (e_shell_parent_class)->
1775 window_added (application, window);
1776
1777 g_signal_connect (
1778 window, "delete-event",
1779 G_CALLBACK (shell_window_delete_event_cb), application);
1780
1781 /* We use the window's own type name and memory
1782 * address to form a unique window role for X11. */
1783 role = g_strdup_printf (
1784 "%s-%" G_GINTPTR_FORMAT,
1785 G_OBJECT_TYPE_NAME (window),
1786 (gintptr) window);
1787 gtk_window_set_role (window, role);
1788 g_free (role);
1789 }
1790
1791 static gboolean
shell_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)1792 shell_initable_init (GInitable *initable,
1793 GCancellable *cancellable,
1794 GError **error)
1795 {
1796 GApplication *application = G_APPLICATION (initable);
1797 EShell *shell = E_SHELL (initable);
1798 ESourceRegistry *registry;
1799 ESource *proxy_source;
1800 gulong handler_id;
1801
1802 shell_add_actions (application);
1803
1804 if (!g_application_register (application, cancellable, error))
1805 return FALSE;
1806
1807 registry = e_source_registry_new_sync (cancellable, error);
1808 if (registry == NULL)
1809 return FALSE;
1810
1811 shell->priv->registry = g_object_ref (registry);
1812 shell->priv->credentials_prompter = e_credentials_prompter_new (registry);
1813 shell->priv->client_cache = e_client_cache_new (registry);
1814
1815 shell->priv->credentials_required_handler_id = g_signal_connect (
1816 shell->priv->registry, "credentials-required",
1817 G_CALLBACK (shell_credentials_required_cb), shell);
1818
1819 shell->priv->get_dialog_parent_handler_id = g_signal_connect (
1820 shell->priv->credentials_prompter, "get-dialog-parent",
1821 G_CALLBACK (shell_get_dialog_parent_cb), shell);
1822
1823 shell->priv->get_dialog_parent_full_handler_id = g_signal_connect (
1824 shell->priv->credentials_prompter, "get-dialog-parent-full",
1825 G_CALLBACK (shell_get_dialog_parent_full_cb), shell);
1826
1827 handler_id = g_signal_connect (
1828 shell->priv->client_cache, "backend-died",
1829 G_CALLBACK (shell_backend_died_cb), shell);
1830 shell->priv->backend_died_handler_id = handler_id;
1831
1832 handler_id = g_signal_connect (
1833 shell->priv->client_cache, "allow-auth-prompt",
1834 G_CALLBACK (shell_allow_auth_prompt_cb), shell);
1835 shell->priv->allow_auth_prompt_handler_id = handler_id;
1836
1837 /* Configure WebKit's default SoupSession. */
1838
1839 proxy_source = e_source_registry_ref_builtin_proxy (registry);
1840 /* FIXME WK2
1841 g_object_set (
1842 webkit_get_default_session (),
1843 SOUP_SESSION_PROXY_RESOLVER,
1844 G_PROXY_RESOLVER (proxy_source),
1845 NULL);
1846 */
1847 g_object_unref (proxy_source);
1848 g_object_unref (registry);
1849
1850 /* Forbid header bars in stock GTK+ dialogs.
1851 * They look very out of place in Evolution. */
1852 g_object_set (
1853 gtk_settings_get_default (),
1854 "gtk-dialogs-use-header", FALSE, NULL);
1855
1856 return TRUE;
1857 }
1858
1859 static void
e_shell_class_init(EShellClass * class)1860 e_shell_class_init (EShellClass *class)
1861 {
1862 GObjectClass *object_class;
1863 GApplicationClass *application_class;
1864 GtkApplicationClass *gtk_application_class;
1865
1866 g_type_class_add_private (class, sizeof (EShellPrivate));
1867
1868 object_class = G_OBJECT_CLASS (class);
1869 object_class->set_property = shell_set_property;
1870 object_class->get_property = shell_get_property;
1871 object_class->dispose = shell_dispose;
1872 object_class->finalize = shell_finalize;
1873 object_class->constructed = shell_constructed;
1874
1875 application_class = G_APPLICATION_CLASS (class);
1876 application_class->startup = shell_startup;
1877 application_class->shutdown = shell_shutdown;
1878 application_class->activate = shell_activate;
1879
1880 gtk_application_class = GTK_APPLICATION_CLASS (class);
1881 gtk_application_class->window_added = shell_window_added;
1882
1883 /**
1884 * EShell:client-cache:
1885 *
1886 * Shared #EClient instances.
1887 **/
1888 g_object_class_install_property (
1889 object_class,
1890 PROP_CLIENT_CACHE,
1891 g_param_spec_object (
1892 "client-cache",
1893 "Client Cache",
1894 "Shared EClient instances",
1895 E_TYPE_CLIENT_CACHE,
1896 G_PARAM_READABLE |
1897 G_PARAM_STATIC_STRINGS));
1898
1899 /**
1900 * EShell:express-mode
1901 *
1902 * Express mode alters Evolution's user interface to be mode
1903 * usable on devices with small screens.
1904 **/
1905 g_object_class_install_property (
1906 object_class,
1907 PROP_EXPRESS_MODE,
1908 g_param_spec_boolean (
1909 "express-mode",
1910 "Express Mode",
1911 "Whether express mode is enabled",
1912 FALSE,
1913 G_PARAM_READWRITE |
1914 G_PARAM_CONSTRUCT_ONLY |
1915 G_PARAM_STATIC_STRINGS));
1916
1917 /**
1918 * EShell:geometry
1919 *
1920 * User-specified initial window geometry string to apply
1921 * to the first #EShellWindow created.
1922 **/
1923 g_object_class_install_property (
1924 object_class,
1925 PROP_GEOMETRY,
1926 g_param_spec_string (
1927 "geometry",
1928 "Geometry",
1929 "Initial window geometry string",
1930 NULL,
1931 G_PARAM_WRITABLE |
1932 G_PARAM_CONSTRUCT_ONLY |
1933 G_PARAM_STATIC_STRINGS));
1934
1935 /**
1936 * EShell:module-directory
1937 *
1938 * The directory from which to load #EModule<!-- -->s.
1939 **/
1940 g_object_class_install_property (
1941 object_class,
1942 PROP_MODULE_DIRECTORY,
1943 g_param_spec_string (
1944 "module-directory",
1945 "Module Directory",
1946 "The directory from which to load EModules",
1947 NULL,
1948 G_PARAM_READWRITE |
1949 G_PARAM_CONSTRUCT_ONLY |
1950 G_PARAM_STATIC_STRINGS));
1951
1952 /**
1953 * EShell:network-available
1954 *
1955 * Whether the network is available.
1956 **/
1957 g_object_class_install_property (
1958 object_class,
1959 PROP_NETWORK_AVAILABLE,
1960 g_param_spec_boolean (
1961 "network-available",
1962 "Network Available",
1963 "Whether the network is available",
1964 TRUE,
1965 G_PARAM_READWRITE |
1966 G_PARAM_STATIC_STRINGS));
1967
1968 /**
1969 * EShell:online
1970 *
1971 * Whether the shell is online.
1972 **/
1973 g_object_class_install_property (
1974 object_class,
1975 PROP_ONLINE,
1976 g_param_spec_boolean (
1977 "online",
1978 "Online",
1979 "Whether the shell is online",
1980 FALSE,
1981 G_PARAM_READWRITE |
1982 G_PARAM_CONSTRUCT |
1983 G_PARAM_STATIC_STRINGS));
1984
1985 /**
1986 * EShell:registry
1987 *
1988 * The #ESourceRegistry manages #ESource instances.
1989 **/
1990 g_object_class_install_property (
1991 object_class,
1992 PROP_REGISTRY,
1993 g_param_spec_object (
1994 "registry",
1995 "Registry",
1996 "Data source registry",
1997 E_TYPE_SOURCE_REGISTRY,
1998 G_PARAM_READABLE |
1999 G_PARAM_STATIC_STRINGS));
2000
2001 /**
2002 * EShell:credentials-prompter
2003 *
2004 * The #ECredentialsPrompter managing #ESource credential requests.
2005 *
2006 * Since: 3.16
2007 **/
2008 g_object_class_install_property (
2009 object_class,
2010 PROP_CREDENTIALS_PROMPTER,
2011 g_param_spec_object (
2012 "credentials-prompter",
2013 "Credentials Prompter",
2014 "Credentials Prompter",
2015 E_TYPE_CREDENTIALS_PROMPTER,
2016 G_PARAM_READABLE |
2017 G_PARAM_STATIC_STRINGS));
2018
2019 /**
2020 * EShell::event
2021 * @shell: the #EShell which emitted the signal
2022 * @event_data: data associated with the event
2023 *
2024 * This signal is used to broadcast custom events to the entire
2025 * application. The nature of @event_data depends on the event
2026 * being broadcast. The signal's detail denotes the event.
2027 **/
2028 signals[EVENT] = g_signal_new (
2029 "event",
2030 G_OBJECT_CLASS_TYPE (object_class),
2031 G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED | G_SIGNAL_ACTION,
2032 0, NULL, NULL,
2033 g_cclosure_marshal_VOID__POINTER,
2034 G_TYPE_NONE, 1,
2035 G_TYPE_POINTER);
2036
2037 /**
2038 * EShell::handle-uri
2039 * @shell: the #EShell which emitted the signal
2040 * @uri: the URI to be handled
2041 *
2042 * Emitted when @shell receives a URI to be handled, usually by
2043 * way of a command-line argument. An #EShellBackend should listen
2044 * for this signal and try to handle the URI, usually by opening an
2045 * editor window for the identified resource.
2046 *
2047 * Returns: %TRUE if the URI could be handled, %FALSE otherwise
2048 **/
2049 signals[HANDLE_URI] = g_signal_new (
2050 "handle-uri",
2051 G_OBJECT_CLASS_TYPE (object_class),
2052 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
2053 G_STRUCT_OFFSET (EShellClass, handle_uri),
2054 g_signal_accumulator_true_handled, NULL,
2055 e_marshal_BOOLEAN__STRING,
2056 G_TYPE_BOOLEAN, 1,
2057 G_TYPE_STRING);
2058
2059 /**
2060 * EShell::prepare-for-offline
2061 * @shell: the #EShell which emitted the signal
2062 * @activity: the #EActivity for offline preparations
2063 *
2064 * Emitted when the user elects to work offline. An #EShellBackend
2065 * should listen for this signal and make preparations for working
2066 * in offline mode.
2067 *
2068 * If preparations for working offline cannot immediately be
2069 * completed (such as when synchronizing with a remote server),
2070 * the #EShellBackend should reference the @activity until
2071 * preparations are complete, and then unreference the @activity.
2072 * This will delay Evolution from actually going to offline mode
2073 * until all backends have unreferenced @activity.
2074 **/
2075 signals[PREPARE_FOR_OFFLINE] = g_signal_new (
2076 "prepare-for-offline",
2077 G_OBJECT_CLASS_TYPE (object_class),
2078 G_SIGNAL_RUN_FIRST,
2079 G_STRUCT_OFFSET (EShellClass, prepare_for_offline),
2080 NULL, NULL,
2081 g_cclosure_marshal_VOID__OBJECT,
2082 G_TYPE_NONE, 1,
2083 E_TYPE_ACTIVITY);
2084
2085 /**
2086 * EShell::prepare-for-online
2087 * @shell: the #EShell which emitted the signal
2088 * @activity: the #EActivity for offline preparations
2089 *
2090 * Emitted when the user elects to work online. An #EShellBackend
2091 * should listen for this signal and make preparations for working
2092 * in online mode.
2093 *
2094 * If preparations for working online cannot immediately be
2095 * completed (such as when re-connecting to a remote server), the
2096 * #EShellBackend should reference the @activity until preparations
2097 * are complete, and then unreference the @activity. This will
2098 * delay Evolution from actually going to online mode until all
2099 * backends have unreferenced @activity.
2100 **/
2101 signals[PREPARE_FOR_ONLINE] = g_signal_new (
2102 "prepare-for-online",
2103 G_OBJECT_CLASS_TYPE (object_class),
2104 G_SIGNAL_RUN_FIRST,
2105 G_STRUCT_OFFSET (EShellClass, prepare_for_online),
2106 NULL, NULL,
2107 g_cclosure_marshal_VOID__OBJECT,
2108 G_TYPE_NONE, 1,
2109 E_TYPE_ACTIVITY);
2110
2111 /**
2112 * EShell::prepare-for-quit
2113 * @shell: the #EShell which emitted the signal
2114 * @activity: the #EActivity for quit preparations
2115 *
2116 * Emitted when the user elects to quit the application, after
2117 * #EShell::quit-requested. An #EShellBackend should listen for
2118 * this signal and make preparations for shutting down.
2119 *
2120 * If preparations for shutting down cannot immediately be completed
2121 * (such as when there are uncompleted network operations), the
2122 * #EShellBackend should reference the @activity until preparations
2123 * are complete, and then unreference the @activity. This will
2124 * delay Evolution from actually shutting down until all backends
2125 * have unreferenced @activity.
2126 **/
2127 signals[PREPARE_FOR_QUIT] = g_signal_new (
2128 "prepare-for-quit",
2129 G_OBJECT_CLASS_TYPE (object_class),
2130 G_SIGNAL_RUN_FIRST,
2131 G_STRUCT_OFFSET (EShellClass, prepare_for_quit),
2132 NULL, NULL,
2133 g_cclosure_marshal_VOID__OBJECT,
2134 G_TYPE_NONE, 1,
2135 E_TYPE_ACTIVITY);
2136
2137 /**
2138 * EShell::quit-requested
2139 * @shell: the #EShell which emitted the signal
2140 * @reason: the reason for quitting
2141 *
2142 * Emitted when the user elects to quit the application, before
2143 * #EShell::prepare-for-quit.
2144 *
2145 * #EShellBackend<!-- -->s and editor windows can listen for
2146 * this signal to prompt the user to save changes or finish
2147 * scheduled operations immediately (such as sending mail in
2148 * Outbox). If the user elects to cancel, the signal handler
2149 * should call e_shell_cancel_quit() to abort the quit.
2150 **/
2151 signals[QUIT_REQUESTED] = g_signal_new (
2152 "quit-requested",
2153 G_OBJECT_CLASS_TYPE (object_class),
2154 G_SIGNAL_RUN_FIRST,
2155 G_STRUCT_OFFSET (EShellClass, quit_requested),
2156 NULL, NULL,
2157 g_cclosure_marshal_VOID__ENUM,
2158 G_TYPE_NONE, 1,
2159 E_TYPE_SHELL_QUIT_REASON);
2160 }
2161
2162 static void
e_shell_initable_init(GInitableIface * iface)2163 e_shell_initable_init (GInitableIface *iface)
2164 {
2165 iface->init = shell_initable_init;
2166 }
2167
2168 static void
e_shell_init(EShell * shell)2169 e_shell_init (EShell *shell)
2170 {
2171 GHashTable *backends_by_name;
2172 GHashTable *backends_by_scheme;
2173 GtkIconTheme *icon_theme;
2174
2175 shell->priv = E_SHELL_GET_PRIVATE (shell);
2176
2177 backends_by_name = g_hash_table_new (g_str_hash, g_str_equal);
2178 backends_by_scheme = g_hash_table_new (g_str_hash, g_str_equal);
2179
2180 g_queue_init (&shell->priv->alerts);
2181
2182 shell->priv->cancellable = g_cancellable_new ();
2183 shell->priv->preferences_window = e_preferences_window_new (shell);
2184 shell->priv->backends_by_name = backends_by_name;
2185 shell->priv->backends_by_scheme = backends_by_scheme;
2186 shell->priv->auth_prompt_parents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
2187 shell->priv->safe_mode = e_file_lock_exists ();
2188 shell->priv->requires_shutdown = FALSE;
2189
2190 /* Add our icon directory to the theme's search path
2191 * here instead of in main() so Anjal picks it up. */
2192 icon_theme = gtk_icon_theme_get_default ();
2193 gtk_icon_theme_append_search_path (icon_theme, EVOLUTION_ICONDIR);
2194
2195 e_signal_connect_notify (
2196 shell, "notify::online",
2197 G_CALLBACK (shell_notify_online_cb), NULL);
2198
2199 g_signal_connect_swapped (
2200 G_APPLICATION (shell), "shutdown",
2201 G_CALLBACK (shell_sm_quit_cb), shell);
2202 }
2203
2204 /**
2205 * e_shell_get_default:
2206 *
2207 * Returns the #EShell created by <function>main()</function>.
2208 *
2209 * Try to obtain the #EShell from elsewhere if you can. This function
2210 * is intended as a temporary workaround for when that proves difficult.
2211 *
2212 * Returns: the #EShell singleton
2213 **/
2214 EShell *
e_shell_get_default(void)2215 e_shell_get_default (void)
2216 {
2217 return default_shell;
2218 }
2219
2220 /**
2221 * e_shell_load_modules:
2222 * @shell: an #EShell
2223 *
2224 * Loads all installed modules and performs some internal bookkeeping.
2225 * This function should be called after creating the #EShell instance
2226 * but before initiating migration or starting the main loop.
2227 **/
2228 void
e_shell_load_modules(EShell * shell)2229 e_shell_load_modules (EShell *shell)
2230 {
2231 GList *list;
2232
2233 g_return_if_fail (E_IS_SHELL (shell));
2234
2235 if (shell->priv->modules_loaded)
2236 return;
2237
2238 /* Process shell backends. */
2239
2240 list = g_list_sort (
2241 e_extensible_list_extensions (
2242 E_EXTENSIBLE (shell), E_TYPE_SHELL_BACKEND),
2243 (GCompareFunc) e_shell_backend_compare);
2244 g_list_foreach (list, (GFunc) shell_process_backend, shell);
2245 shell->priv->loaded_backends = list;
2246
2247 shell->priv->modules_loaded = TRUE;
2248 }
2249
2250 /**
2251 * e_shell_get_shell_backends:
2252 * @shell: an #EShell
2253 *
2254 * Returns a list of loaded #EShellBackend instances. The list is
2255 * owned by @shell and should not be modified or freed.
2256 *
2257 * Returns: a list of loaded #EShellBackend instances
2258 **/
2259 GList *
e_shell_get_shell_backends(EShell * shell)2260 e_shell_get_shell_backends (EShell *shell)
2261 {
2262 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2263
2264 return shell->priv->loaded_backends;
2265 }
2266
2267 /**
2268 * e_shell_get_canonical_name:
2269 * @shell: an #EShell
2270 * @name: the name or alias of an #EShellBackend
2271 *
2272 * Returns the canonical name for the #EShellBackend whose name or alias
2273 * is @name.
2274 *
2275 * Returns: the canonical #EShellBackend name
2276 **/
2277 const gchar *
e_shell_get_canonical_name(EShell * shell,const gchar * name)2278 e_shell_get_canonical_name (EShell *shell,
2279 const gchar *name)
2280 {
2281 EShellBackend *shell_backend;
2282
2283 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2284
2285 /* Handle NULL or empty name arguments silently. */
2286 if (name == NULL || *name == '\0')
2287 return NULL;
2288
2289 shell_backend = e_shell_get_backend_by_name (shell, name);
2290
2291 if (shell_backend == NULL)
2292 return NULL;
2293
2294 return E_SHELL_BACKEND_GET_CLASS (shell_backend)->name;
2295 }
2296
2297 /**
2298 * e_shell_get_backend_by_name:
2299 * @shell: an #EShell
2300 * @name: the name or alias of an #EShellBackend
2301 *
2302 * Returns the corresponding #EShellBackend for the given name or alias,
2303 * or %NULL if @name is not recognized.
2304 *
2305 * Returns: the #EShellBackend named @name, or %NULL
2306 **/
2307 EShellBackend *
e_shell_get_backend_by_name(EShell * shell,const gchar * name)2308 e_shell_get_backend_by_name (EShell *shell,
2309 const gchar *name)
2310 {
2311 GHashTable *hash_table;
2312
2313 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2314 g_return_val_if_fail (name != NULL, NULL);
2315
2316 hash_table = shell->priv->backends_by_name;
2317
2318 return g_hash_table_lookup (hash_table, name);
2319 }
2320
2321 /**
2322 * e_shell_get_backend_by_scheme:
2323 * @shell: an #EShell
2324 * @scheme: a URI scheme
2325 *
2326 * Returns the #EShellBackend that implements the given URI scheme,
2327 * or %NULL if @scheme is not recognized.
2328 *
2329 * Returns: the #EShellBackend that implements @scheme, or %NULL
2330 **/
2331 EShellBackend *
e_shell_get_backend_by_scheme(EShell * shell,const gchar * scheme)2332 e_shell_get_backend_by_scheme (EShell *shell,
2333 const gchar *scheme)
2334 {
2335 GHashTable *hash_table;
2336
2337 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2338 g_return_val_if_fail (scheme != NULL, NULL);
2339
2340 hash_table = shell->priv->backends_by_scheme;
2341
2342 return g_hash_table_lookup (hash_table, scheme);
2343 }
2344
2345 /**
2346 * e_shell_get_client_cache:
2347 * @shell: an #EShell
2348 *
2349 * Returns the #EClientCache instance for @shell.
2350 *
2351 * Returns: the #EClientCache instance for @shell
2352 **/
2353 EClientCache *
e_shell_get_client_cache(EShell * shell)2354 e_shell_get_client_cache (EShell *shell)
2355 {
2356 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2357
2358 return shell->priv->client_cache;
2359 }
2360
2361 /**
2362 * e_shell_get_registry:
2363 * @shell: an #EShell
2364 *
2365 * Returns the shell's #ESourceRegistry which holds all #ESource instances.
2366 *
2367 * Returns: the #ESourceRegistry
2368 **/
2369 ESourceRegistry *
e_shell_get_registry(EShell * shell)2370 e_shell_get_registry (EShell *shell)
2371 {
2372 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2373
2374 return shell->priv->registry;
2375 }
2376
2377 /**
2378 * e_shell_get_credentials_prompter:
2379 * @shell: an #EShell
2380 *
2381 * Returns the shell's #ECredentialsPrompter which responds
2382 * to #ESource instances credential requests.
2383 *
2384 * Returns: the #ECredentialsPrompter
2385 *
2386 * Since: 3.16
2387 **/
2388 ECredentialsPrompter *
e_shell_get_credentials_prompter(EShell * shell)2389 e_shell_get_credentials_prompter (EShell *shell)
2390 {
2391 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2392
2393 return shell->priv->credentials_prompter;
2394 }
2395
2396 /**
2397 * e_shell_allow_auth_prompt_for:
2398 * @shell: an #EShell
2399 * @source: an #ESource
2400 *
2401 * Allows direct credentials prompt for @source. That means,
2402 * when the @source will emit 'credentials-required' signal,
2403 * then a user will be asked accordingly. When the auth prompt
2404 * is disabled, aonly an #EAlert is shown.
2405 *
2406 * Since: 3.16
2407 **/
2408 void
e_shell_allow_auth_prompt_for(EShell * shell,ESource * source)2409 e_shell_allow_auth_prompt_for (EShell *shell,
2410 ESource *source)
2411 {
2412 gboolean source_enabled;
2413
2414 g_return_if_fail (E_IS_SHELL (shell));
2415 g_return_if_fail (E_IS_SOURCE (source));
2416
2417 source_enabled = e_source_registry_check_enabled (shell->priv->registry, source);
2418
2419 e_credentials_prompter_set_auto_prompt_disabled_for (shell->priv->credentials_prompter, source, !source_enabled);
2420
2421 if (!source_enabled)
2422 return;
2423
2424 if (e_source_get_connection_status (source) == E_SOURCE_CONNECTION_STATUS_AWAITING_CREDENTIALS) {
2425 e_credentials_prompter_process_source (shell->priv->credentials_prompter, source);
2426 } else if (e_source_get_connection_status (source) == E_SOURCE_CONNECTION_STATUS_SSL_FAILED) {
2427 e_source_get_last_credentials_required_arguments (source, shell->priv->cancellable,
2428 shell_get_last_credentials_required_arguments_cb, shell);
2429 }
2430 }
2431
2432 /**
2433 * e_shell_create_shell_window:
2434 * @shell: an #EShell
2435 * @view_name: name of the initial shell view, or %NULL
2436 *
2437 * Creates a new #EShellWindow. Use this function instead of
2438 * e_shell_window_new() so that @shell can properly configure
2439 * the window.
2440 *
2441 * Returns: a new #EShellWindow
2442 **/
2443 GtkWidget *
e_shell_create_shell_window(EShell * shell,const gchar * view_name)2444 e_shell_create_shell_window (EShell *shell,
2445 const gchar *view_name)
2446 {
2447 GtkWidget *shell_window;
2448 GList *link;
2449 gboolean can_change_default_view;
2450
2451 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2452
2453 if (g_application_get_is_remote (G_APPLICATION (shell)))
2454 goto remote;
2455
2456 can_change_default_view = !view_name || *view_name != '*';
2457 view_name = e_shell_get_canonical_name (shell, can_change_default_view ? view_name : (view_name + 1));
2458
2459 /* EShellWindow initializes its active view from a GSetting key,
2460 * so set the key ahead of time to control the initial view. */
2461 if (view_name && can_change_default_view) {
2462 GSettings *settings;
2463
2464 settings = e_util_ref_settings ("org.gnome.evolution.shell");
2465 g_settings_set_string (
2466 settings, "default-component-id", view_name);
2467 g_object_unref (settings);
2468 }
2469
2470 shell_window = e_shell_window_new (
2471 shell,
2472 shell->priv->safe_mode,
2473 shell->priv->geometry);
2474
2475 if (view_name && !can_change_default_view) {
2476 GSettings *settings;
2477 gchar *active_view;
2478
2479 settings = e_util_ref_settings ("org.gnome.evolution.shell");
2480
2481 /* This is ugly, but nothing better with GSettings bindings, I'm afraid. */
2482 active_view = g_settings_get_string (settings, "default-component-id");
2483
2484 e_shell_window_set_active_view (E_SHELL_WINDOW (shell_window), view_name);
2485
2486 g_settings_set_string (settings, "default-component-id", active_view);
2487
2488 g_object_unref (settings);
2489 g_free (active_view);
2490 }
2491
2492 /* Submit any outstanding alerts. */
2493 link = g_queue_peek_head_link (&shell->priv->alerts);
2494 while (link != NULL) {
2495 e_alert_sink_submit_alert (
2496 E_ALERT_SINK (shell_window),
2497 E_ALERT (link->data));
2498 link = g_list_next (link);
2499 }
2500
2501 /* Clear the first-time-only options. */
2502 shell->priv->safe_mode = FALSE;
2503 g_free (shell->priv->geometry);
2504 shell->priv->geometry = NULL;
2505
2506 gtk_widget_show (shell_window);
2507
2508 if (g_list_length (gtk_application_get_windows (GTK_APPLICATION (shell))) == 1) {
2509 /* It's the first window, process outstanding credential requests now */
2510 e_credentials_prompter_process_awaiting_credentials (shell->priv->credentials_prompter);
2511
2512 /* Also check alerts for failed authentications */
2513 shell_process_failed_authentications (shell);
2514 }
2515
2516 return shell_window;
2517
2518 remote: /* Send a message to the other Evolution process. */
2519
2520 if (view_name != NULL) {
2521 g_action_group_activate_action (
2522 G_ACTION_GROUP (shell), "create-from-remote",
2523 g_variant_new_string (view_name));
2524 } else
2525 g_application_activate (G_APPLICATION (shell));
2526
2527 return NULL;
2528 }
2529
2530 /**
2531 * e_shell_handle_uris:
2532 * @shell: an #EShell
2533 * @uris: %NULL-terminated list of URIs
2534 * @do_import: request an import of the URIs
2535 *
2536 * Emits the #EShell::handle-uri signal for each URI.
2537 *
2538 * Returns: the number of URIs successfully handled
2539 **/
2540 guint
e_shell_handle_uris(EShell * shell,const gchar * const * uris,gboolean do_import)2541 e_shell_handle_uris (EShell *shell,
2542 const gchar * const *uris,
2543 gboolean do_import)
2544 {
2545 GPtrArray *args;
2546 gchar *cwd;
2547 guint n_handled = 0;
2548 guint ii;
2549
2550 g_return_val_if_fail (E_IS_SHELL (shell), FALSE);
2551 g_return_val_if_fail (uris != NULL, FALSE);
2552
2553 if (g_application_get_is_remote (G_APPLICATION (shell)))
2554 goto remote;
2555
2556 if (do_import) {
2557 n_handled = e_shell_utils_import_uris (shell, uris);
2558 } else {
2559 for (ii = 0; uris[ii] != NULL; ii++) {
2560 gboolean handled;
2561
2562 g_signal_emit (
2563 shell, signals[HANDLE_URI],
2564 0, uris[ii], &handled);
2565 n_handled += handled ? 1 : 0;
2566 }
2567
2568 if (n_handled == 0)
2569 n_handled = e_shell_utils_import_uris (shell, uris);
2570 }
2571
2572 return n_handled;
2573
2574 remote: /* Send a message to the other Evolution process. */
2575
2576 cwd = g_get_current_dir ();
2577 args = g_ptr_array_sized_new (g_strv_length ((gchar **) uris) + 2);
2578
2579 g_ptr_array_add (args, (gchar *) "--use-cwd");
2580 g_ptr_array_add (args, cwd);
2581
2582 for (ii = 0; uris[ii]; ii++) {
2583 g_ptr_array_add (args, (gchar *) uris[ii]);
2584 }
2585
2586 g_action_group_activate_action (
2587 G_ACTION_GROUP (shell), "handle-uris",
2588 g_variant_new_strv ((const gchar * const *) args->pdata, args->len));
2589
2590 g_ptr_array_free (args, TRUE);
2591 g_free (cwd);
2592
2593 /* As far as we're concerned, all URIs have been handled. */
2594
2595 return g_strv_length ((gchar **) uris);
2596 }
2597
2598 /**
2599 * e_shell_submit_alert:
2600 * @shell: an #EShell
2601 * @alert: an #EAlert
2602 *
2603 * Broadcasts @alert to all #EShellWindow<!-- -->s. This should only
2604 * be used for application-wide alerts such as a network outage. Submit
2605 * view-specific alerts to the appropriate #EShellContent instance.
2606 **/
2607 void
e_shell_submit_alert(EShell * shell,EAlert * alert)2608 e_shell_submit_alert (EShell *shell,
2609 EAlert *alert)
2610 {
2611 GtkApplication *application;
2612 GList *list, *iter;
2613
2614 g_return_if_fail (E_IS_SHELL (shell));
2615 g_return_if_fail (E_IS_ALERT (alert));
2616
2617 application = GTK_APPLICATION (shell);
2618
2619 g_queue_push_tail (&shell->priv->alerts, g_object_ref (alert));
2620
2621 g_signal_connect_swapped (
2622 alert, "response",
2623 G_CALLBACK (shell_alert_response_cb), shell);
2624
2625 list = gtk_application_get_windows (application);
2626
2627 /* Submit the alert to all available EShellWindows. */
2628 for (iter = list; iter != NULL; iter = g_list_next (iter))
2629 if (E_IS_SHELL_WINDOW (iter->data))
2630 e_alert_sink_submit_alert (
2631 E_ALERT_SINK (iter->data), alert);
2632 }
2633
2634 /**
2635 * e_shell_get_active_window:
2636 * @shell: an #EShell or %NULL to use the default shell
2637 *
2638 * Returns the most recently focused watched window, according to
2639 * gtk_application_get_windows(). Convenient for finding a parent
2640 * for a transient window.
2641 *
2642 * Note the returned window is not necessarily an #EShellWindow.
2643 *
2644 * Returns: the most recently focused watched window
2645 **/
2646 GtkWindow *
e_shell_get_active_window(EShell * shell)2647 e_shell_get_active_window (EShell *shell)
2648 {
2649 GtkApplication *application;
2650 GList *list;
2651
2652 if (shell == NULL)
2653 shell = e_shell_get_default ();
2654
2655 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2656
2657 application = GTK_APPLICATION (shell);
2658 list = gtk_application_get_windows (application);
2659
2660 if (list == NULL)
2661 return NULL;
2662
2663 /* Sanity check */
2664 g_return_val_if_fail (GTK_IS_WINDOW (list->data), NULL);
2665
2666 return GTK_WINDOW (list->data);
2667 }
2668
2669 /**
2670 * e_shell_get_express_mode:
2671 * @shell: an #EShell
2672 *
2673 * Returns %TRUE if Evolution is in express mode.
2674 *
2675 * Returns: %TRUE if Evolution is in express mode
2676 **/
2677 gboolean
e_shell_get_express_mode(EShell * shell)2678 e_shell_get_express_mode (EShell *shell)
2679 {
2680 g_return_val_if_fail (E_IS_SHELL (shell), FALSE);
2681
2682 return shell->priv->express_mode;
2683 }
2684
2685 /**
2686 * e_shell_get_module_directory:
2687 * @shell: an #EShell
2688 *
2689 * Returns the directory from which #EModule<!-- -->s were loaded.
2690 *
2691 * Returns: the #EModule directory
2692 **/
2693 const gchar *
e_shell_get_module_directory(EShell * shell)2694 e_shell_get_module_directory (EShell *shell)
2695 {
2696 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2697
2698 return shell->priv->module_directory;
2699 }
2700
2701 /**
2702 * e_shell_get_network_available:
2703 * @shell: an #EShell
2704 *
2705 * Returns %TRUE if a network is available.
2706 *
2707 * Returns: %TRUE if a network is available
2708 **/
2709 gboolean
e_shell_get_network_available(EShell * shell)2710 e_shell_get_network_available (EShell *shell)
2711 {
2712 g_return_val_if_fail (E_IS_SHELL (shell), FALSE);
2713
2714 return shell->priv->network_available;
2715 }
2716
2717 /**
2718 * e_shell_set_network_available:
2719 * @shell: an #EShell
2720 * @network_available: whether a network is available
2721 *
2722 * Sets whether a network is available. This is usually called in
2723 * response to a status change signal from NetworkManager. If the
2724 * network becomes unavailable while #EShell:online is %TRUE, the
2725 * @shell will force #EShell:online to %FALSE until the network
2726 * becomes available again.
2727 **/
2728 void
e_shell_set_network_available(EShell * shell,gboolean network_available)2729 e_shell_set_network_available (EShell *shell,
2730 gboolean network_available)
2731 {
2732 g_return_if_fail (E_IS_SHELL (shell));
2733
2734 if (shell->priv->network_available_locked)
2735 return;
2736
2737 /* Network availablity is in an indeterminate state until
2738 * the first time this function is called. Don't let our
2739 * arbitrary default value block this from being handled. */
2740 if (!shell->priv->network_available_set)
2741 shell->priv->network_available_set = TRUE;
2742 else if (shell->priv->network_available == network_available)
2743 return;
2744
2745 shell->priv->network_available = network_available;
2746 g_object_notify (G_OBJECT (shell), "network-available");
2747
2748 /* If we're being forced offline, perhaps due to a network outage,
2749 * reconnect automatically when the network becomes available. */
2750 if (!network_available && (shell->priv->online || shell->priv->preparing_for_line_change)) {
2751 g_message ("Network disconnected. Forced offline.");
2752
2753 if (shell->priv->set_online_timeout_id > 0) {
2754 g_source_remove (shell->priv->set_online_timeout_id);
2755 shell->priv->set_online_timeout_id = 0;
2756 }
2757
2758 e_shell_set_online (shell, FALSE);
2759 shell->priv->auto_reconnect = TRUE;
2760 } else if (network_available && shell->priv->auto_reconnect) {
2761 g_message ("Connection established. Going online.");
2762
2763 /* Wait some seconds to give the network enough time to become
2764 * fully available. */
2765 if (shell->priv->set_online_timeout_id > 0) {
2766 g_source_remove (shell->priv->set_online_timeout_id);
2767 shell->priv->set_online_timeout_id = 0;
2768 }
2769
2770 shell->priv->set_online_timeout_id = e_named_timeout_add_seconds_full (
2771 G_PRIORITY_DEFAULT, SET_ONLINE_TIMEOUT_SECONDS, e_shell_set_online_cb,
2772 g_object_ref (shell), g_object_unref);
2773
2774 shell->priv->auto_reconnect = FALSE;
2775 }
2776 }
2777
2778 /**
2779 * e_shell_lock_network_available:
2780 * @shell: an #EShell
2781 *
2782 * Locks the value of #EShell:network-available to %TRUE. Further
2783 * attempts to set the property will be ignored.
2784 *
2785 * This is used for the --force-online command-line option, which is
2786 * intended to override the network availability status as reported
2787 * by NetworkManager or other network monitoring software.
2788 **/
2789 void
e_shell_lock_network_available(EShell * shell)2790 e_shell_lock_network_available (EShell *shell)
2791 {
2792 g_return_if_fail (E_IS_SHELL (shell));
2793
2794 e_shell_set_network_available (shell, TRUE);
2795 shell->priv->network_available_locked = TRUE;
2796
2797 /* As this is a user choice to go online, do not wait and switch online immediately */
2798 if (shell->priv->set_online_timeout_id > 0) {
2799 g_source_remove (shell->priv->set_online_timeout_id);
2800 shell->priv->set_online_timeout_id = 0;
2801
2802 e_shell_set_online (shell, TRUE);
2803 }
2804 }
2805
2806 /**
2807 * e_shell_get_online:
2808 * @shell: an #EShell
2809 *
2810 * Returns %TRUE if Evolution is online, %FALSE if Evolution is offline.
2811 * Evolution may be offline because the user elected to work offline, or
2812 * because the network has become unavailable.
2813 *
2814 * Returns: %TRUE if Evolution is online
2815 **/
2816 gboolean
e_shell_get_online(EShell * shell)2817 e_shell_get_online (EShell *shell)
2818 {
2819 g_return_val_if_fail (E_IS_SHELL (shell), FALSE);
2820
2821 return shell->priv->online;
2822 }
2823
2824 /**
2825 * e_shell_set_online:
2826 * @shell: an #EShell
2827 * @online: %TRUE to go online, %FALSE to go offline
2828 *
2829 * Asynchronously places Evolution in online or offline mode.
2830 **/
2831 void
e_shell_set_online(EShell * shell,gboolean online)2832 e_shell_set_online (EShell *shell,
2833 gboolean online)
2834 {
2835 g_return_if_fail (E_IS_SHELL (shell));
2836
2837 if (online == shell->priv->online && !shell->priv->preparing_for_line_change)
2838 return;
2839
2840 if (online)
2841 shell_prepare_for_online (shell);
2842 else
2843 shell_prepare_for_offline (shell);
2844 }
2845
2846 /**
2847 * e_shell_get_preferences_window:
2848 * @shell: an #EShell
2849 *
2850 * Returns the Evolution Preferences window.
2851 *
2852 * Returns: the preferences window
2853 **/
2854 GtkWidget *
e_shell_get_preferences_window(EShell * shell)2855 e_shell_get_preferences_window (EShell *shell)
2856 {
2857 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2858
2859 return shell->priv->preferences_window;
2860 }
2861
2862 /**
2863 * e_shell_event:
2864 * @shell: an #EShell
2865 * @event_name: the name of the event
2866 * @event_data: data associated with the event
2867 *
2868 * The #EShell::event signal acts as a cheap mechanism for broadcasting
2869 * events to the rest of the application, such as new mail arriving. The
2870 * @event_name is used as the signal detail, and @event_data may point to
2871 * an object or data structure associated with the event.
2872 **/
2873 void
e_shell_event(EShell * shell,const gchar * event_name,gpointer event_data)2874 e_shell_event (EShell *shell,
2875 const gchar *event_name,
2876 gpointer event_data)
2877 {
2878 GQuark detail;
2879
2880 g_return_if_fail (E_IS_SHELL (shell));
2881 g_return_if_fail (event_name != NULL);
2882
2883 detail = g_quark_from_string (event_name);
2884 g_signal_emit (shell, signals[EVENT], detail, event_data);
2885 }
2886
2887 /**
2888 * e_shell_quit:
2889 * @shell: an #EShell
2890 * @reason: the reason for quitting
2891 *
2892 * Requests an application shutdown. This happens in two phases: the
2893 * first is synchronous, the second is asynchronous.
2894 *
2895 * In the first phase, the @shell emits an #EShell::quit-requested signal
2896 * to potentially give the user a chance to cancel shutdown. If the user
2897 * cancels shutdown, the function returns %FALSE. Otherwise it proceeds
2898 * into the second phase.
2899 *
2900 * In the second phase, the @shell emits an #EShell::prepare-for-quit
2901 * signal and immediately returns %TRUE. Signal handlers may delay the
2902 * actual application shutdown while they clean up resources, but there
2903 * is no way to cancel shutdown at this point.
2904 *
2905 * Consult the documentation for these two signals for details on how
2906 * to handle them.
2907 *
2908 * Returns: %TRUE if shutdown is underway, %FALSE if it was cancelled
2909 **/
2910 gboolean
e_shell_quit(EShell * shell,EShellQuitReason reason)2911 e_shell_quit (EShell *shell,
2912 EShellQuitReason reason)
2913 {
2914 g_return_val_if_fail (E_IS_SHELL (shell), FALSE);
2915
2916 if (g_application_get_is_remote (G_APPLICATION (shell)))
2917 goto remote;
2918
2919 /* Last Window reason can be used multiple times;
2920 this is to ask for a forced quit before the timeout is reached. */
2921 if (reason == E_SHELL_QUIT_LAST_WINDOW && shell->priv->preparing_for_quit != NULL) {
2922 shell_prepare_for_quit (shell);
2923 return TRUE;
2924 }
2925
2926 if (!shell_request_quit (shell, reason))
2927 return FALSE;
2928
2929 shell_prepare_for_quit (shell);
2930
2931 return TRUE;
2932
2933 remote: /* Send a message to the other Evolution process. */
2934
2935 g_action_group_activate_action (
2936 G_ACTION_GROUP (shell), "quit", NULL);
2937
2938 return TRUE;
2939 }
2940
2941 /**
2942 * e_shell_cancel_quit:
2943 * @shell: an #EShell
2944 *
2945 * This function may only be called from #EShell::quit-requested signal
2946 * handlers to prevent Evolution from quitting. Calling this will stop
2947 * further emission of the #EShell::quit-requested signal.
2948 *
2949 * Note: This function has no effect during an #EShell::prepare-for-quit
2950 * signal emission.
2951 **/
2952 void
e_shell_cancel_quit(EShell * shell)2953 e_shell_cancel_quit (EShell *shell)
2954 {
2955 g_return_if_fail (E_IS_SHELL (shell));
2956
2957 shell->priv->quit_cancelled = TRUE;
2958
2959 g_signal_stop_emission (shell, signals[QUIT_REQUESTED], 0);
2960 }
2961
2962 gboolean
e_shell_requires_shutdown(EShell * shell)2963 e_shell_requires_shutdown (EShell *shell)
2964 {
2965 g_return_val_if_fail (E_IS_SHELL (shell), FALSE);
2966
2967 return shell->priv->requires_shutdown;
2968 }
2969
2970 /**
2971 * e_shell_set_auth_prompt_parent:
2972 * @shell: an #EShell
2973 * @source: an #ESource
2974 * @parent: (nullable): a #GtkWindow
2975 *
2976 * Sets an override for a credential prompt parent window.
2977 *
2978 * Since: 3.42
2979 **/
2980 void
e_shell_set_auth_prompt_parent(EShell * shell,ESource * source,GtkWindow * parent)2981 e_shell_set_auth_prompt_parent (EShell *shell,
2982 ESource *source,
2983 GtkWindow *parent)
2984 {
2985 g_return_if_fail (E_IS_SHELL (shell));
2986 g_return_if_fail (E_IS_SOURCE (source));
2987 g_return_if_fail (e_source_get_uid (source));
2988
2989 if (parent) {
2990 g_hash_table_insert (shell->priv->auth_prompt_parents, g_strdup (e_source_get_uid (source)), parent);
2991 } else {
2992 g_hash_table_remove (shell->priv->auth_prompt_parents, e_source_get_uid (source));
2993 }
2994 }
2995