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