/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * Copyright © 2000-2004 Marco Pesenti Gritti * Copyright © 2003, 2004, 2006 Christian Persch * Copyright © 2011 Igalia S.L. * * This file is part of Epiphany. * * Epiphany is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Epiphany is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Epiphany. If not, see . */ #include "config.h" #include "ephy-shell.h" #include "ephy-debug.h" #include "ephy-desktop-utils.h" #include "ephy-embed-container.h" #include "ephy-embed-utils.h" #include "ephy-file-helpers.h" #include "ephy-firefox-sync-dialog.h" #include "ephy-gui.h" #include "ephy-header-bar.h" #include "ephy-history-dialog.h" #include "ephy-link.h" #include "ephy-lockdown.h" #include "ephy-notification.h" #include "ephy-prefs.h" #include "ephy-prefs-dialog.h" #include "ephy-session.h" #include "ephy-settings.h" #include "ephy-sync-utils.h" #include "ephy-title-box.h" #include "ephy-title-widget.h" #include "ephy-type-builtins.h" #include "ephy-web-app-utils.h" #include "ephy-web-view.h" #include "ephy-window.h" #include "window-commands.h" #include #include #include struct _EphyShell { EphyEmbedShell parent_instance; EphySession *session; EphySyncService *sync_service; GObject *lockdown; EphyBookmarksManager *bookmarks_manager; EphyHistoryManager *history_manager; EphyOpenTabsManager *open_tabs_manager; EphyWebExtensionManager *web_extension_manager; GNetworkMonitor *network_monitor; GtkWidget *history_dialog; GtkWidget *firefox_sync_dialog; GObject *prefs_dialog; EphyShellStartupContext *local_startup_context; EphyShellStartupContext *remote_startup_context; GSList *open_uris_idle_ids; GHashTable *notifications; gchar *open_notification_id; gboolean startup_finished; }; static EphyShell *ephy_shell = NULL; static void ephy_shell_dispose (GObject *object); static void ephy_shell_finalize (GObject *object); G_DEFINE_TYPE (EphyShell, ephy_shell, EPHY_TYPE_EMBED_SHELL) /** * ephy_shell_startup_context_new: * @startup_mode: An #EphyStartupMode (new tab or new window) * @session_filename: A session to restore. * @arguments: A %NULL-terminated array of URLs and file URIs to be opened. * @user_time: The user time when the EphyShell startup was invoked. * * Creates a new startup context. All string parameters, including * @arguments, are copied. * * Returns: a newly allocated #EphyShellStartupContext **/ EphyShellStartupContext * ephy_shell_startup_context_new (EphyStartupMode startup_mode, char *session_filename, char **arguments, guint32 user_time) { EphyShellStartupContext *ctx = g_new0 (EphyShellStartupContext, 1); ctx->startup_mode = startup_mode; ctx->session_filename = g_strdup (session_filename); ctx->arguments = g_strdupv (arguments); ctx->user_time = user_time; return ctx; } static void ephy_shell_startup_context_free (EphyShellStartupContext *ctx) { g_assert (ctx != NULL); g_free (ctx->session_filename); g_strfreev (ctx->arguments); g_free (ctx); } static void ephy_shell_startup_continue (EphyShell *shell, EphyShellStartupContext *ctx) { EphySession *session = ephy_shell_get_session (shell); gboolean new_window_option = (ctx->startup_mode == EPHY_STARTUP_NEW_WINDOW); GtkWindow *active_window = gtk_application_get_active_window (GTK_APPLICATION (shell)); EphyEmbedShellMode mode; mode = ephy_embed_shell_get_mode (EPHY_EMBED_SHELL (shell)); if (ctx->session_filename != NULL) { g_assert (session != NULL); ephy_session_load (session, (const char *)ctx->session_filename, ctx->user_time, NULL, NULL, NULL); } else if (new_window_option && shell->remote_startup_context) { char *homepage_url = g_settings_get_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_HOMEPAGE_URL); const char *default_uris[] = { homepage_url, NULL }; const char **uris = NULL; if (ctx->arguments) uris = (const char **)ctx->arguments; else /* If there are no URLs passed in arguments but the --new-window option */ /* has been used, then use the default_uris list to open the homepage */ uris = default_uris; ephy_shell_open_uris (shell, uris, ctx->startup_mode, ctx->user_time); g_free (homepage_url); } else if (active_window && (!ctx->arguments || mode == EPHY_EMBED_SHELL_MODE_APPLICATION)) { /* If the application already has an active window and: */ /* Option 1: the --new-window option was not passed */ /* Option 2: mode is application */ /* then we should just present it */ /* This can happen for example if the user starts a long download and then */ /* closes the browser or the web app is running in background .*/ /* The window will still remain active and presenting it will have a */ /* session-resumed feel for the user. */ gtk_window_present (active_window); } else if (ctx->arguments || !session) { /* Don't queue any window openings if no extra arguments given, */ /* since session autoresume will open one for us. */ ephy_shell_open_uris (shell, (const char **)ctx->arguments, ctx->startup_mode, ctx->user_time); } else if (ephy_shell_get_n_windows (shell) == 0) { EphyWindow *window = ephy_window_new (); ephy_link_open (EPHY_LINK (window), NULL, NULL, EPHY_LINK_HOME_PAGE); } shell->startup_finished = TRUE; } static void new_window (GSimpleAction *action, GVariant *parameter, gpointer user_data) { window_cmd_new_window (NULL, NULL, NULL); } static void new_incognito_window (GSimpleAction *action, GVariant *parameter, gpointer user_data) { window_cmd_new_incognito_window (NULL, NULL, NULL); } static void reopen_closed_tab (GSimpleAction *action, GVariant *parameter, gpointer user_data) { window_cmd_reopen_closed_tab (NULL, NULL, NULL); } static void import_bookmarks (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GtkWindow *window; window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell)); window_cmd_import_bookmarks (NULL, NULL, EPHY_WINDOW (window)); } static void export_bookmarks (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GtkWindow *window; window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell)); window_cmd_export_bookmarks (NULL, NULL, EPHY_WINDOW (window)); } static void import_passwords (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GtkWindow *window; window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell)); window_cmd_import_passwords (NULL, NULL, EPHY_WINDOW (window)); } static void show_history (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GtkWindow *window; window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell)); window_cmd_show_history (NULL, NULL, EPHY_WINDOW (window)); } static void show_firefox_sync (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GtkWindow *window; window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell)); window_cmd_show_firefox_sync (NULL, NULL, EPHY_WINDOW (window)); } static void show_preferences (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GtkWindow *window; window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell)); window_cmd_show_preferences (NULL, NULL, EPHY_WINDOW (window)); } static void show_shortcuts (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GtkWindow *window; window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell)); window_cmd_show_shortcuts (NULL, NULL, EPHY_WINDOW (window)); } static void show_help (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GtkWindow *window; window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell)); window_cmd_show_help (NULL, NULL, GTK_WIDGET (window)); } static void show_about (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GtkWindow *window; window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell)); window_cmd_show_about (NULL, NULL, GTK_WIDGET (window)); } static void quit_application (GSimpleAction *action, GVariant *parameter, gpointer user_data) { window_cmd_quit (NULL, NULL, NULL); } static void show_downloads (GSimpleAction *action, GVariant *parameter, gpointer user_data) { GtkWindow *window; EphyDownloadsManager *manager = ephy_embed_shell_get_downloads_manager (EPHY_EMBED_SHELL (ephy_shell_get_default ())); window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell)); g_application_withdraw_notification (G_APPLICATION (ephy_shell), ephy_shell->open_notification_id); g_clear_pointer (&ephy_shell->open_notification_id, g_free); gtk_widget_show (GTK_WIDGET (window)); g_signal_emit_by_name (manager, "show-downloads", NULL); } static void launch_app (GSimpleAction *action, GVariant *parameter, gpointer user_data) { const gchar *desktop_file = g_variant_get_string (parameter, NULL); /* We can't get here under flatpak because all web app functionality * is disabled when running under flatpak. */ ephy_file_launch_desktop_file (desktop_file, gtk_get_current_event_time (), EPHY_FILE_HELPERS_I_UNDERSTAND_I_MUST_NOT_USE_THIS_FUNCTION_UNDER_FLATPAK); } static void notification_clicked (GSimpleAction *action, GVariant *parameter, gpointer user_data) { EphyShell *shell = ephy_shell_get_default (); guint64 id = g_variant_get_uint64 (parameter); WebKitNotification *notification; notification = g_hash_table_lookup (shell->notifications, GINT_TO_POINTER (id)); if (notification) webkit_notification_clicked (notification); } static GActionEntry app_entries[] = { { "new-window", new_window, NULL, NULL, NULL }, { "new-incognito", new_incognito_window, NULL, NULL, NULL }, { "import-bookmarks", import_bookmarks, NULL, NULL, NULL }, { "export-bookmarks", export_bookmarks, NULL, NULL, NULL }, { "import-passwords", import_passwords, NULL, NULL, NULL }, { "history", show_history, NULL, NULL, NULL }, { "firefox-sync-dialog", show_firefox_sync, NULL, NULL, NULL }, { "preferences", show_preferences, NULL, NULL, NULL }, { "shortcuts", show_shortcuts, NULL, NULL, NULL }, { "help", show_help, NULL, NULL, NULL }, { "about", show_about, NULL, NULL, NULL }, { "quit", quit_application, NULL, NULL, NULL }, { "show-downloads", show_downloads, NULL, NULL, NULL }, { "launch-app", launch_app, "s", NULL, NULL }, { "notification-clicked", notification_clicked, "t", NULL, NULL}, }; static GActionEntry non_incognito_extra_app_entries[] = { { "reopen-closed-tab", reopen_closed_tab, NULL, NULL, NULL }, }; static GActionEntry app_mode_app_entries[] = { { "history", show_history, NULL, NULL, NULL }, { "preferences", show_preferences, NULL, NULL, NULL }, { "about", show_about, NULL, NULL, NULL }, { "quit", quit_application, NULL, NULL, NULL }, { "run-in-background", NULL, NULL, "false", NULL}, { "notification-clicked", notification_clicked, "t", NULL, NULL}, }; static void register_synchronizable_managers (EphyShell *shell, EphySyncService *service) { EphySynchronizableManager *manager; g_assert (EPHY_IS_SYNC_SERVICE (service)); g_assert (EPHY_IS_SHELL (shell)); if (ephy_sync_utils_history_sync_is_enabled ()) { manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_history_manager (shell)); ephy_sync_service_register_manager (service, manager); } if (ephy_sync_utils_bookmarks_sync_is_enabled ()) { manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_bookmarks_manager (shell)); ephy_sync_service_register_manager (service, manager); } if (ephy_sync_utils_passwords_sync_is_enabled ()) { manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_embed_shell_get_password_manager (EPHY_EMBED_SHELL (shell))); ephy_sync_service_register_manager (service, manager); } if (ephy_sync_utils_open_tabs_sync_is_enabled ()) { manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_open_tabs_manager (shell)); ephy_sync_service_register_manager (service, manager); } } static gboolean start_sync_after_sign_in (EphySyncService *service) { g_assert (EPHY_IS_SYNC_SERVICE (service)); ephy_sync_service_start_sync (service); return G_SOURCE_REMOVE; } static void sync_secrets_store_finished_cb (EphySyncService *service, GError *error, EphyShell *shell) { g_assert (EPHY_IS_SYNC_SERVICE (service)); g_assert (EPHY_IS_SHELL (shell)); if (!error) { register_synchronizable_managers (shell, service); /* Allow a 30 seconds window for the user to select their sync options. */ g_timeout_add_seconds (30, (GSourceFunc)start_sync_after_sign_in, service); } } static void sync_secrets_load_finished_cb (EphySyncService *service, EphyShell *shell) { g_assert (EPHY_IS_SYNC_SERVICE (service)); g_assert (EPHY_IS_SHELL (shell)); register_synchronizable_managers (shell, service); ephy_sync_service_start_sync (service); } static void set_accel_for_action (EphyShell *shell, const gchar *detailed_action_name, const gchar *accel) { const char *accels[] = { accel, NULL }; gtk_application_set_accels_for_action (GTK_APPLICATION (shell), detailed_action_name, accels); } static gboolean run_in_background_get_mapping (GValue *value, GVariant *variant, gpointer user_data) { g_value_set_variant (value, variant); return TRUE; } static GVariant * run_in_background_set_mapping (const GValue *value, const GVariantType *expected_type, gpointer user_data) { GVariant *var = g_value_get_variant (value); return g_variant_new_boolean (g_variant_get_boolean (var)); } static void ephy_shell_startup (GApplication *application) { EphyEmbedShell *embed_shell = EPHY_EMBED_SHELL (application); EphyShell *shell = EPHY_SHELL (application); EphyEmbedShellMode mode; GAction *action; G_APPLICATION_CLASS (ephy_shell_parent_class)->startup (application); hdy_init (); hdy_style_manager_set_color_scheme (hdy_style_manager_get_default (), HDY_COLOR_SCHEME_PREFER_LIGHT); /* If we are under Pantheon set the icon-theme and cursor-theme accordingly. */ if (is_desktop_pantheon ()) { GtkSettings *settings = gtk_settings_get_default (); g_object_set (settings, "gtk-icon-theme-name", "elementary", "gtk-cursor-theme-name", "elementary", NULL); } /* We're not remoting; start our services */ mode = ephy_embed_shell_get_mode (embed_shell); if (mode != EPHY_EMBED_SHELL_MODE_APPLICATION) { g_action_map_add_action_entries (G_ACTION_MAP (application), app_entries, G_N_ELEMENTS (app_entries), application); if (mode != EPHY_EMBED_SHELL_MODE_INCOGNITO && mode != EPHY_EMBED_SHELL_MODE_AUTOMATION) { g_action_map_add_action_entries (G_ACTION_MAP (application), non_incognito_extra_app_entries, G_N_ELEMENTS (non_incognito_extra_app_entries), application); g_object_bind_property (G_OBJECT (ephy_shell_get_session (shell)), "can-undo-tab-closed", g_action_map_lookup_action (G_ACTION_MAP (application), "reopen-closed-tab"), "enabled", G_BINDING_SYNC_CREATE); if (mode == EPHY_EMBED_SHELL_MODE_BROWSER && ephy_sync_utils_user_is_signed_in ()) { /* Create the sync service. */ ephy_shell_get_sync_service (shell); } } /* Actions that are disabled in app mode */ set_accel_for_action (shell, "app.new-window", "n"); set_accel_for_action (shell, "app.new-incognito", "n"); set_accel_for_action (shell, "app.reopen-closed-tab", "t"); set_accel_for_action (shell, "app.import-bookmarks", "m"); set_accel_for_action (shell, "app.export-bookmarks", "x"); set_accel_for_action (shell, "app.shortcuts", "question"); set_accel_for_action (shell, "app.help", "F1"); } else { g_action_map_add_action_entries (G_ACTION_MAP (application), app_mode_app_entries, G_N_ELEMENTS (app_mode_app_entries), application); action = g_action_map_lookup_action (G_ACTION_MAP (application), "run-in-background"); g_settings_bind_with_mapping (EPHY_SETTINGS_WEB_APP, EPHY_PREFS_WEB_APP_RUN_IN_BACKGROUND, action, "state", G_SETTINGS_BIND_DEFAULT, run_in_background_get_mapping, run_in_background_set_mapping, NULL, NULL); } /* Actions that are available in both app mode and browser mode */ set_accel_for_action (shell, "app.history", "h"); set_accel_for_action (shell, "app.preferences", "e"); set_accel_for_action (shell, "app.quit", "q"); } static GtkWidget * create_web_view_for_automation_cb (WebKitAutomationSession *session, EphyShell *shell) { EphyEmbed *embed; EphyWindow *window; EphyWebView *web_view; guint n_embeds; window = EPHY_WINDOW (gtk_application_get_active_window (GTK_APPLICATION (shell))); embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (window)); n_embeds = ephy_embed_container_get_n_children (EPHY_EMBED_CONTAINER (window)); web_view = ephy_embed_get_web_view (embed); if (n_embeds == 1 && ephy_web_view_get_visit_type (web_view) == EPHY_PAGE_VISIT_HOMEPAGE) { gtk_widget_grab_focus (GTK_WIDGET (embed)); return GTK_WIDGET (web_view); } embed = ephy_shell_new_tab (shell, window, NULL, EPHY_NEW_TAB_JUMP); gtk_widget_grab_focus (GTK_WIDGET (embed)); return GTK_WIDGET (ephy_embed_get_web_view (embed)); } static void automation_started_cb (WebKitWebContext *web_context, WebKitAutomationSession *session, EphyShell *shell) { WebKitApplicationInfo *info = webkit_application_info_new (); webkit_application_info_set_name (info, "Epiphany"); webkit_application_info_set_version (info, EPHY_MAJOR_VERSION, EPHY_MINOR_VERSION, EPHY_MICRO_VERSION); webkit_automation_session_set_application_info (session, info); webkit_application_info_unref (info); g_signal_connect (session, "create-web-view", G_CALLBACK (create_web_view_for_automation_cb), shell); } static void session_load_cb (GObject *object, GAsyncResult *result, gpointer user_data) { EphySession *session = EPHY_SESSION (object); EphyShellStartupContext *ctx = (EphyShellStartupContext *)user_data; ephy_session_resume_finish (session, result, NULL); ephy_shell_startup_continue (ephy_shell_get_default (), ctx); } static void portal_check (EphyShell *shell) { if (g_network_monitor_get_connectivity (ephy_shell_get_net_monitor (shell)) == G_NETWORK_CONNECTIVITY_PORTAL) { EphyWindow *window = EPHY_WINDOW (gtk_application_get_active_window (GTK_APPLICATION (shell))); ephy_link_open (EPHY_LINK (window), "http://nmcheck.gnome.org/", NULL, EPHY_LINK_NEW_TAB | EPHY_LINK_JUMP_TO); } } static void connectivity_changed (GNetworkMonitor *monitor, GParamSpec *pspec, EphyShell *shell) { portal_check (shell); } static void ephy_shell_activate (GApplication *application) { EphyShell *shell = EPHY_SHELL (application); EphyEmbedShell *embed_shell = EPHY_EMBED_SHELL (shell); if (!is_desktop_gnome () && !is_desktop_pantheon ()) { g_signal_connect (ephy_shell_get_net_monitor (shell), "notify::connectivity", G_CALLBACK (connectivity_changed), shell); portal_check (shell); } if (ephy_embed_shell_get_mode (embed_shell) == EPHY_EMBED_SHELL_MODE_AUTOMATION) { WebKitWebContext *web_context = ephy_embed_shell_get_web_context (embed_shell); g_signal_connect (web_context, "automation-started", G_CALLBACK (automation_started_cb), shell); } if (shell->open_notification_id) { g_application_withdraw_notification (G_APPLICATION (shell), shell->open_notification_id); g_clear_pointer (&shell->open_notification_id, g_free); } if (shell->remote_startup_context == NULL) { EphySession *session = ephy_shell_get_session (shell); /* We are activating the primary instance for the first time. If we * have a saved session, resume it first, then run any startup * commands in session_load_cb. Otherwise, run them now. */ if (session) { ephy_session_resume (session, shell->local_startup_context->user_time, NULL, session_load_cb, shell->local_startup_context); } else ephy_shell_startup_continue (shell, shell->local_startup_context); } else { /* We are activating the primary instance in response to the launch * of a secondary instance. Execute the commands immediately. We * have to be careful because if we don't handle the commands * immediately, the remote startup context could be invalidated by * the launch of another remote instance. */ ephy_shell_startup_continue (shell, shell->remote_startup_context); g_clear_pointer (&shell->remote_startup_context, ephy_shell_startup_context_free); } } /* * We use this enumeration to conveniently fill and read from the * dictionary variant that is sent from the remote to the primary * instance. */ typedef enum { CTX_STARTUP_MODE, CTX_SESSION_FILENAME, CTX_ARGUMENTS, CTX_USER_TIME } CtxEnum; static void ephy_shell_add_platform_data (GApplication *application, GVariantBuilder *builder) { EphyShell *app; EphyShellStartupContext *ctx; GVariantBuilder *ctx_builder; app = EPHY_SHELL (application); G_APPLICATION_CLASS (ephy_shell_parent_class)->add_platform_data (application, builder); if (app->local_startup_context) { /* * We create an array variant that contains only the elements in * ctx that are non-NULL. */ ctx_builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); ctx = app->local_startup_context; if (ctx->startup_mode) g_variant_builder_add (ctx_builder, "{iv}", CTX_STARTUP_MODE, g_variant_new_byte (ctx->startup_mode)); if (ctx->session_filename) g_variant_builder_add (ctx_builder, "{iv}", CTX_SESSION_FILENAME, g_variant_new_string (ctx->session_filename)); /* * If there are no URIs specified, pass an empty string, so that * the primary instance opens a new window. */ if (ctx->arguments) g_variant_builder_add (ctx_builder, "{iv}", CTX_ARGUMENTS, g_variant_new_strv ((const char **)ctx->arguments, -1)); g_variant_builder_add (ctx_builder, "{iv}", CTX_USER_TIME, g_variant_new_uint32 (ctx->user_time)); g_variant_builder_add (builder, "{sv}", "ephy-shell-startup-context", g_variant_builder_end (ctx_builder)); g_variant_builder_unref (ctx_builder); } } static void ephy_shell_before_emit (GApplication *application, GVariant *platform_data) { GVariantIter iter, ctx_iter; const char *key; CtxEnum ctx_key; GVariant *value, *ctx_value; EphyShellStartupContext *ctx = NULL; EphyShell *shell = EPHY_SHELL (application); g_variant_iter_init (&iter, platform_data); while (g_variant_iter_loop (&iter, "{&sv}", &key, &value)) { if (strcmp (key, "ephy-shell-startup-context") == 0) { ctx = g_new0 (EphyShellStartupContext, 1); /* * Iterate over the startup context variant and fill the members * that were wired. Everything else is just NULL. */ g_variant_iter_init (&ctx_iter, value); while (g_variant_iter_loop (&ctx_iter, "{iv}", &ctx_key, &ctx_value)) { switch (ctx_key) { case CTX_STARTUP_MODE: ctx->startup_mode = g_variant_get_byte (ctx_value); break; case CTX_SESSION_FILENAME: ctx->session_filename = g_variant_dup_string (ctx_value, NULL); break; case CTX_ARGUMENTS: ctx->arguments = g_variant_dup_strv (ctx_value, NULL); break; case CTX_USER_TIME: ctx->user_time = g_variant_get_uint32 (ctx_value); break; default: g_assert_not_reached (); break; } } break; } } /* We have already processed and discarded any previous remote startup contexts. */ g_assert (shell->remote_startup_context == NULL); shell->remote_startup_context = ctx; G_APPLICATION_CLASS (ephy_shell_parent_class)->before_emit (application, platform_data); } static GObject * ephy_shell_get_lockdown (EphyShell *shell) { g_assert (EPHY_IS_SHELL (shell)); if (shell->lockdown == NULL) shell->lockdown = g_object_new (EPHY_TYPE_LOCKDOWN, NULL); return G_OBJECT (shell->session); } static void ephy_shell_constructed (GObject *object) { if (ephy_embed_shell_get_mode (EPHY_EMBED_SHELL (object)) != EPHY_EMBED_SHELL_MODE_BROWSER && ephy_embed_shell_get_mode (EPHY_EMBED_SHELL (object)) != EPHY_EMBED_SHELL_MODE_APPLICATION) { GApplicationFlags flags; flags = g_application_get_flags (G_APPLICATION (object)); flags |= G_APPLICATION_NON_UNIQUE; g_application_set_flags (G_APPLICATION (object), flags); } if (ephy_embed_shell_get_mode (EPHY_EMBED_SHELL (object)) == EPHY_EMBED_SHELL_MODE_APPLICATION) { dzl_application_add_resources (DZL_APPLICATION (object), "resource:///org/gnome/Epiphany"); } /* FIXME: not sure if this is the best place to put this stuff. */ ephy_shell_get_lockdown (EPHY_SHELL (object)); if (G_OBJECT_CLASS (ephy_shell_parent_class)->constructed) G_OBJECT_CLASS (ephy_shell_parent_class)->constructed (object); } static void ephy_shell_class_init (EphyShellClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GApplicationClass *application_class = G_APPLICATION_CLASS (klass); object_class->dispose = ephy_shell_dispose; object_class->finalize = ephy_shell_finalize; object_class->constructed = ephy_shell_constructed; application_class->startup = ephy_shell_startup; application_class->activate = ephy_shell_activate; application_class->before_emit = ephy_shell_before_emit; application_class->add_platform_data = ephy_shell_add_platform_data; } static void ephy_shell_init (EphyShell *shell) { EphyShell **ptr = &ephy_shell; /* globally accessible singleton */ g_assert (ephy_shell == NULL); ephy_shell = shell; ephy_shell->startup_finished = FALSE; g_object_add_weak_pointer (G_OBJECT (ephy_shell), (gpointer *)ptr); ephy_shell->notifications = g_hash_table_new (g_direct_hash, g_direct_equal); } static void remove_open_uris_idle_cb (gpointer data) { g_source_remove (GPOINTER_TO_UINT (data)); } static void ephy_shell_dispose (GObject *object) { EphyShell *shell = EPHY_SHELL (object); LOG ("EphyShell disposing"); g_clear_object (&shell->session); g_clear_object (&shell->lockdown); g_clear_pointer (&shell->history_dialog, gtk_widget_destroy); g_clear_object (&shell->prefs_dialog); g_clear_object (&shell->network_monitor); g_clear_object (&shell->sync_service); g_clear_object (&shell->bookmarks_manager); g_clear_object (&shell->history_manager); g_clear_object (&shell->open_tabs_manager); g_clear_object (&shell->web_extension_manager); g_hash_table_destroy (shell->notifications); if (shell->open_notification_id) { g_application_withdraw_notification (G_APPLICATION (shell), shell->open_notification_id); g_clear_pointer (&shell->open_notification_id, g_free); } g_slist_free_full (shell->open_uris_idle_ids, remove_open_uris_idle_cb); shell->open_uris_idle_ids = NULL; G_OBJECT_CLASS (ephy_shell_parent_class)->dispose (object); } static void ephy_shell_finalize (GObject *object) { EphyShell *shell = EPHY_SHELL (object); g_clear_pointer (&shell->local_startup_context, ephy_shell_startup_context_free); g_clear_pointer (&shell->remote_startup_context, ephy_shell_startup_context_free); G_OBJECT_CLASS (ephy_shell_parent_class)->finalize (object); LOG ("Ephy shell finalised"); } /** * ephy_shell_get_default: * * Retrieve the default #EphyShell object * * Return value: (transfer none): the default #EphyShell **/ EphyShell * ephy_shell_get_default (void) { return ephy_shell; } static void webkit_notification_clicked_cb (WebKitNotification *notification, gpointer user_data) { WebKitWebView *notification_webview = WEBKIT_WEB_VIEW (user_data); EphyShell *shell; GApplication *application; GList *windows; shell = ephy_shell_get_default (); application = G_APPLICATION (shell); windows = gtk_application_get_windows (GTK_APPLICATION (application)); for (guint win_idx = 0; win_idx < g_list_length (windows); win_idx++) { EphyWindow *window; EphyTabView *tab_view; int n_pages; window = EPHY_WINDOW (g_list_nth_data (windows, win_idx)); tab_view = ephy_window_get_tab_view (window); n_pages = ephy_tab_view_get_n_pages (tab_view); for (int i = 0; i < n_pages; i++) { EphyEmbed *embed; WebKitWebView *webview; embed = EPHY_EMBED (ephy_tab_view_get_nth_page (tab_view, i)); webview = WEBKIT_WEB_VIEW (ephy_embed_get_web_view (embed)); if (webview == notification_webview) { ephy_tab_view_select_page (tab_view, GTK_WIDGET (embed)); gtk_window_present (GTK_WINDOW (window)); return; } } } } static void webkit_notification_closed_cb (WebKitNotification *notification, gpointer user_data) { EphyShell *shell = ephy_shell_get_default (); g_autofree char *id = NULL; id = g_strdup_printf ("%" G_GUINT64_FORMAT, webkit_notification_get_id (notification)); g_application_withdraw_notification (G_APPLICATION (g_application_get_default ()), id); g_hash_table_remove (shell->notifications, GINT_TO_POINTER (webkit_notification_get_id (notification))); } static gboolean show_notification_cb (WebKitWebView *web_view, WebKitNotification *notification, gpointer user_data) { EphyShell *shell = ephy_shell_get_default (); GNotification *notify; g_autofree char *id = NULL; id = g_strdup_printf ("%" G_GUINT64_FORMAT, webkit_notification_get_id (notification)); g_signal_connect_object (notification, "clicked", G_CALLBACK (webkit_notification_clicked_cb), web_view, 0); g_signal_connect_object (notification, "closed", G_CALLBACK (webkit_notification_closed_cb), web_view, 0); notify = g_notification_new (webkit_notification_get_title (notification)); g_notification_set_body (notify, webkit_notification_get_body (notification)); g_notification_set_priority (notify, G_NOTIFICATION_PRIORITY_LOW); g_notification_set_default_action_and_target (notify, "app.notification-clicked", "t", webkit_notification_get_id (notification)); g_hash_table_insert (shell->notifications, GINT_TO_POINTER (webkit_notification_get_id (notification)), notification); g_application_send_notification (G_APPLICATION (g_application_get_default ()), id, notify); return TRUE; } /** * ephy_shell_new_tab_full: * @shell: a #EphyShell * @window: the target #EphyWindow or %NULL * @previous_embed: the referrer embed, or %NULL * @user_time: a timestamp, or 0 * * Create a new tab and the parent window when necessary. * Use this function to open urls in new window/tabs. * * Return value: (transfer none): the created #EphyEmbed **/ EphyEmbed * ephy_shell_new_tab_full (EphyShell *shell, const char *title, WebKitWebView *related_view, EphyWindow *window, EphyEmbed *previous_embed, EphyNewTabFlags flags, guint32 user_time) { EphyEmbedShell *embed_shell; GtkWidget *web_view; EphyEmbed *embed = NULL; gboolean jump_to = FALSE; int position = -1; EphyEmbed *parent = NULL; g_assert (EPHY_IS_SHELL (shell)); g_assert (EPHY_IS_WINDOW (window)); g_assert (EPHY_IS_EMBED (previous_embed) || !previous_embed); embed_shell = EPHY_EMBED_SHELL (shell); if (flags & EPHY_NEW_TAB_JUMP) jump_to = TRUE; LOG ("Opening new tab window %p parent-embed %p jump-to:%s", window, previous_embed, jump_to ? "t" : "f"); if (flags & EPHY_NEW_TAB_APPEND_AFTER) { if (previous_embed) parent = previous_embed; else g_warning ("Requested to append new tab after parent, but 'previous_embed' was NULL"); } if (flags & EPHY_NEW_TAB_FIRST) position = 0; if (related_view) web_view = ephy_web_view_new_with_related_view (related_view); else web_view = ephy_web_view_new (); g_signal_connect (web_view, "show-notification", G_CALLBACK (show_notification_cb), NULL); embed = EPHY_EMBED (g_object_new (EPHY_TYPE_EMBED, "web-view", web_view, "title", title, "progress-bar-enabled", ephy_embed_shell_get_mode (embed_shell) == EPHY_EMBED_SHELL_MODE_APPLICATION, NULL)); gtk_widget_show (GTK_WIDGET (embed)); ephy_embed_container_add_child (EPHY_EMBED_CONTAINER (window), embed, parent, position, jump_to); if ((flags & EPHY_NEW_TAB_DONT_SHOW_WINDOW) == 0 && ephy_embed_shell_get_mode (embed_shell) != EPHY_EMBED_SHELL_MODE_TEST) { gtk_widget_show (GTK_WIDGET (window)); } return embed; } /** * ephy_shell_new_tab: * @shell: a #EphyShell * @parent_window: the target #EphyWindow or %NULL * @previous_embed: the referrer embed, or %NULL * * Create a new tab and the parent window when necessary. * Use this function to open urls in new window/tabs. * * Return value: (transfer none): the created #EphyEmbed **/ EphyEmbed * ephy_shell_new_tab (EphyShell *shell, EphyWindow *parent_window, EphyEmbed *previous_embed, EphyNewTabFlags flags) { return ephy_shell_new_tab_full (shell, NULL, NULL, parent_window, previous_embed, flags, 0); } /** * ephy_shell_get_session: * @shell: the #EphyShell * * Returns current session. * * Return value: (transfer none): the current session. **/ EphySession * ephy_shell_get_session (EphyShell *shell) { EphyEmbedShellMode mode; g_assert (EPHY_IS_SHELL (shell)); mode = ephy_embed_shell_get_mode (EPHY_EMBED_SHELL (shell)); if (mode == EPHY_EMBED_SHELL_MODE_APPLICATION || mode == EPHY_EMBED_SHELL_MODE_INCOGNITO || mode == EPHY_EMBED_SHELL_MODE_AUTOMATION) return NULL; if (shell->session == NULL) shell->session = g_object_new (EPHY_TYPE_SESSION, NULL); return shell->session; } /** * ephy_shell_get_sync_service: * @shell: the #EphyShell * * Returns the sync service. * * Return value: (transfer none): the global #EphySyncService **/ EphySyncService * ephy_shell_get_sync_service (EphyShell *shell) { g_assert (EPHY_IS_SHELL (shell)); if (shell->sync_service == NULL) { shell->sync_service = ephy_sync_service_new (TRUE); g_signal_connect_object (shell->sync_service, "sync-secrets-store-finished", G_CALLBACK (sync_secrets_store_finished_cb), shell, 0); g_signal_connect_object (shell->sync_service, "sync-secrets-load-finished", G_CALLBACK (sync_secrets_load_finished_cb), shell, 0); } return shell->sync_service; } /** * ephy_shell_get_bookmarks_manager: * @shell: the #EphyShell * * Returns bookmarks manager. * * Return value: (transfer none): An #EphyBookmarksManager. */ EphyBookmarksManager * ephy_shell_get_bookmarks_manager (EphyShell *shell) { g_assert (EPHY_IS_SHELL (shell)); if (shell->bookmarks_manager == NULL) shell->bookmarks_manager = ephy_bookmarks_manager_new (); return shell->bookmarks_manager; } /** * ephy_shell_get_history_manager: * @shell: the #EphyShell * * Returns the history manager. * * Return value: (transfer none): An #EphyHistoryManager. */ EphyHistoryManager * ephy_shell_get_history_manager (EphyShell *shell) { EphyEmbedShell *embed_shell; EphyHistoryService *service; g_assert (EPHY_IS_SHELL (shell)); if (shell->history_manager == NULL) { embed_shell = ephy_embed_shell_get_default (); service = ephy_embed_shell_get_global_history_service (embed_shell); shell->history_manager = ephy_history_manager_new (service); } return shell->history_manager; } EphyOpenTabsManager * ephy_shell_get_open_tabs_manager (EphyShell *shell) { g_assert (EPHY_IS_SHELL (shell)); if (shell->open_tabs_manager == NULL) shell->open_tabs_manager = ephy_open_tabs_manager_new (EPHY_TABS_CATALOG (shell)); return shell->open_tabs_manager; } /** * ephy_shell_get_net_monitor: * * Return value: (transfer none): **/ GNetworkMonitor * ephy_shell_get_net_monitor (EphyShell *shell) { if (shell->network_monitor == NULL) shell->network_monitor = g_network_monitor_get_default (); return shell->network_monitor; } /** * ephy_shell_get_history_dialog: * * Return value: (transfer none): **/ GtkWidget * ephy_shell_get_history_dialog (EphyShell *shell) { EphyEmbedShell *embed_shell; EphyHistoryService *service; embed_shell = ephy_embed_shell_get_default (); if (shell->history_dialog == NULL) { service = ephy_embed_shell_get_global_history_service (embed_shell); shell->history_dialog = ephy_history_dialog_new (service); g_signal_connect (shell->history_dialog, "destroy", G_CALLBACK (gtk_widget_destroyed), &shell->history_dialog); } return shell->history_dialog; } /** * ephy_shell_get_firefox_sync_dialog: * * Return value: (transfer none): **/ GtkWidget * ephy_shell_get_firefox_sync_dialog (EphyShell *shell) { if (shell->firefox_sync_dialog == NULL) { shell->firefox_sync_dialog = ephy_firefox_sync_dialog_new (); g_signal_connect (shell->firefox_sync_dialog, "destroy", G_CALLBACK (gtk_widget_destroyed), &shell->firefox_sync_dialog); } return shell->firefox_sync_dialog; } /** * ephy_shell_get_prefs_dialog: * * Return value: (transfer none): **/ GObject * ephy_shell_get_prefs_dialog (EphyShell *shell) { if (shell->prefs_dialog == NULL) { shell->prefs_dialog = g_object_new (EPHY_TYPE_PREFS_DIALOG, NULL); g_signal_connect (shell->prefs_dialog, "destroy", G_CALLBACK (gtk_widget_destroyed), &shell->prefs_dialog); } return shell->prefs_dialog; } void _ephy_shell_create_instance (EphyEmbedShellMode mode) { const char *id = NULL; g_assert (ephy_shell == NULL); if (mode == EPHY_EMBED_SHELL_MODE_APPLICATION) { const char *profile_dir = ephy_profile_dir (); id = ephy_web_application_get_gapplication_id_from_profile_directory (profile_dir); if (id == NULL) g_error ("Cannot start web app instance: %s is not a valid profile directory", profile_dir); } else { id = APPLICATION_ID; } ephy_shell = EPHY_SHELL (g_object_new (EPHY_TYPE_SHELL, "application-id", id, "mode", mode, "resource-base-path", "/org/gnome/Epiphany", NULL)); /* FIXME weak ref */ g_assert (ephy_shell != NULL); } /** * ephy_shell_set_startup_context: * @shell: A #EphyShell * @ctx: (transfer full): a #EphyShellStartupContext * * Sets the local startup context to be used during activation of a new instance. * See ephy_shell_set_startup_new(). **/ void ephy_shell_set_startup_context (EphyShell *shell, EphyShellStartupContext *ctx) { g_assert (EPHY_IS_SHELL (shell)); g_assert (shell->local_startup_context == NULL); shell->local_startup_context = ctx; } guint ephy_shell_get_n_windows (EphyShell *shell) { GList *list; g_assert (EPHY_IS_SHELL (shell)); list = gtk_application_get_windows (GTK_APPLICATION (shell)); return g_list_length (list); } gboolean ephy_shell_close_all_windows (EphyShell *shell) { GList *windows; gboolean retval = TRUE; EphySession *session = ephy_shell_get_session (shell); g_assert (EPHY_IS_SHELL (shell)); if (session) ephy_session_close (session); windows = gtk_application_get_windows (GTK_APPLICATION (shell)); while (windows) { EphyWindow *window = EPHY_WINDOW (windows->data); windows = windows->next; if (ephy_window_close (window)) gtk_widget_destroy (GTK_WIDGET (window)); else retval = FALSE; } if (shell->open_notification_id) { g_application_withdraw_notification (G_APPLICATION (shell), shell->open_notification_id); g_clear_pointer (&shell->open_notification_id, g_free); } return retval; } void ephy_shell_try_quit (EphyShell *shell) { if (ephy_shell_close_all_windows (shell)) g_application_quit (G_APPLICATION (shell)); } typedef struct { EphyShell *shell; EphySession *session; EphyWindow *window; char **uris; EphyNewTabFlags flags; guint32 user_time; EphyEmbed *previous_embed; guint current_uri; gboolean reuse_empty_tab; guint source_id; } OpenURIsData; static OpenURIsData * open_uris_data_new (EphyShell *shell, const char **uris, EphyStartupMode startup_mode, guint32 user_time) { OpenURIsData *data; gboolean fullscreen_lockdown; EphySession *session = ephy_shell_get_session (shell); data = g_new0 (OpenURIsData, 1); data->shell = shell; data->session = session ? g_object_ref (session) : NULL; data->uris = g_strdupv ((char **)uris); data->user_time = user_time; fullscreen_lockdown = g_settings_get_boolean (EPHY_SETTINGS_LOCKDOWN, EPHY_PREFS_LOCKDOWN_FULLSCREEN); if (startup_mode == EPHY_STARTUP_NEW_WINDOW && !fullscreen_lockdown) { data->window = ephy_window_new (); } else { data->flags |= EPHY_NEW_TAB_JUMP; data->window = EPHY_WINDOW (gtk_application_get_active_window (GTK_APPLICATION (shell))); data->reuse_empty_tab = TRUE; } g_application_hold (G_APPLICATION (shell)); return data; } static void open_uris_data_free (OpenURIsData *data) { g_application_release (G_APPLICATION (data->shell)); g_clear_object (&data->session); g_strfreev (data->uris); g_free (data); } static gboolean ephy_shell_open_uris_idle (OpenURIsData *data) { EphyEmbed *embed = NULL; EphyHeaderBar *header_bar; EphyTitleWidget *title_widget; EphyEmbedShellMode mode; EphyNewTabFlags page_flags = 0; gboolean reusing_empty_tab = FALSE; const char *url; mode = ephy_embed_shell_get_mode (EPHY_EMBED_SHELL (data->shell)); if (!data->window) data->window = ephy_window_new (); else if (data->previous_embed) page_flags |= EPHY_NEW_TAB_APPEND_AFTER; else if (data->reuse_empty_tab) { embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (data->window)); /* Only load a new page in this embed if it was showing or loading the homepage */ if (ephy_web_view_get_visit_type (ephy_embed_get_web_view (embed)) == EPHY_PAGE_VISIT_HOMEPAGE) reusing_empty_tab = TRUE; } if (!reusing_empty_tab) { embed = ephy_shell_new_tab_full (data->shell, NULL, NULL, data->window, data->previous_embed, data->flags | page_flags, data->user_time); } url = data->uris ? data->uris[data->current_uri] : NULL; if (url && url[0] != '\0') { ephy_web_view_load_url (ephy_embed_get_web_view (embed), url); /* When reusing an empty tab, the focus is in the location entry */ if (reusing_empty_tab || data->flags & EPHY_NEW_TAB_JUMP) gtk_widget_grab_focus (GTK_WIDGET (embed)); if (data->flags & EPHY_NEW_TAB_JUMP && mode != EPHY_EMBED_SHELL_MODE_TEST) gtk_window_present_with_time (GTK_WINDOW (data->window), data->user_time); } else { ephy_web_view_load_new_tab_page (ephy_embed_get_web_view (embed)); if (data->flags & EPHY_NEW_TAB_JUMP) ephy_window_activate_location (data->window); } /* Set address from the very beginning. Looks odd in app mode if it appears later on. */ header_bar = EPHY_HEADER_BAR (ephy_window_get_header_bar (data->window)); title_widget = ephy_header_bar_get_title_widget (header_bar); ephy_title_widget_set_address (title_widget, url); data->current_uri++; data->previous_embed = embed; return data->uris && data->uris[data->current_uri] != NULL; } static void ephy_shell_open_uris_idle_done (OpenURIsData *data) { data->shell->open_uris_idle_ids = g_slist_remove (data->shell->open_uris_idle_ids, GUINT_TO_POINTER (data->source_id)); open_uris_data_free (data); } void ephy_shell_open_uris (EphyShell *shell, const char **uris, EphyStartupMode startup_mode, guint32 user_time) { OpenURIsData *data; guint id; g_assert (EPHY_IS_SHELL (shell)); data = open_uris_data_new (shell, uris, startup_mode, user_time); id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc)ephy_shell_open_uris_idle, data, (GDestroyNotify)ephy_shell_open_uris_idle_done); data->source_id = id; shell->open_uris_idle_ids = g_slist_prepend (shell->open_uris_idle_ids, GUINT_TO_POINTER (id)); } void ephy_shell_send_notification (EphyShell *shell, gchar *id, GNotification *notification) { if (ephy_shell->open_notification_id) { g_application_withdraw_notification (G_APPLICATION (ephy_shell), ephy_shell->open_notification_id); g_clear_pointer (&ephy_shell->open_notification_id, g_free); } shell->open_notification_id = g_strdup (id); g_application_send_notification (G_APPLICATION (shell), id, notification); } gboolean ephy_shell_startup_finished (EphyShell *shell) { return shell->startup_finished; } EphyWebExtensionManager * ephy_shell_get_web_extension_manager (EphyShell *shell) { g_assert (EPHY_IS_SHELL (shell)); if (shell->web_extension_manager == NULL) shell->web_extension_manager = ephy_web_extension_manager_new (); return shell->web_extension_manager; } /* Helper functions: better place for this? */ EphyWebView * ephy_shell_get_web_view (EphyShell *shell, guint64 id) { GList *windows; GtkWindow *window; EphyTabView *tab_view; windows = gtk_application_get_windows (GTK_APPLICATION (shell)); for (GList *list = windows; list && list->data; list = list->next) { window = GTK_WINDOW (list->data); tab_view = ephy_window_get_tab_view (EPHY_WINDOW (window)); for (int i = 0; i < ephy_tab_view_get_n_pages (tab_view); i++) { GtkWidget *page = ephy_tab_view_get_nth_page (tab_view, i); EphyWebView *web_view = ephy_embed_get_web_view (EPHY_EMBED (page)); if (ephy_web_view_get_uid (web_view) == id) return web_view; } } return NULL; } EphyWebView * ephy_shell_get_active_web_view (EphyShell *shell) { GtkWindow *window; EphyTabView *tab_view; GtkWidget *page; window = gtk_application_get_active_window (GTK_APPLICATION (shell)); if (!window) return NULL; tab_view = ephy_window_get_tab_view (EPHY_WINDOW (window)); page = ephy_tab_view_get_selected_page (tab_view); return ephy_embed_get_web_view (EPHY_EMBED (page)); }