/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * vi:set noexpandtab tabstop=8 shiftwidth=8: * * Copyright (C) 2013-2017 Richard Hughes * Copyright (C) 2013 Matthias Clasen * Copyright (C) 2014-2018 Kalev Lember * * SPDX-License-Identifier: GPL-2.0+ */ #include "config.h" #include "gs-repos-dialog.h" #include "gnome-software-private.h" #include "gs-common.h" #include "gs-os-release.h" #include "gs-repo-row.h" #include "gs-repos-section.h" #include "gs-utils.h" #include struct _GsReposDialog { AdwWindow parent_instance; GSettings *settings; GsFedoraThirdParty *third_party; gboolean third_party_enabled; GHashTable *third_party_repos; /* (nullable) (owned), mapping from owned repo ID ā†’ owned plugin name */ GHashTable *sections; /* gchar * ~> GsReposSection * */ GCancellable *cancellable; GsPluginLoader *plugin_loader; GtkWidget *status_empty; GtkWidget *content_page; GtkWidget *spinner; GtkWidget *stack; }; G_DEFINE_TYPE (GsReposDialog, gs_repos_dialog, ADW_TYPE_WINDOW) static void reload_third_party_repos (GsReposDialog *dialog); typedef struct { GsReposDialog *dialog; GsApp *repo; GWeakRef row_weakref; GsPluginManageRepositoryFlags operation; } InstallRemoveData; static void install_remove_data_free (InstallRemoveData *data) { g_clear_object (&data->dialog); g_clear_object (&data->repo); g_weak_ref_clear (&data->row_weakref); g_slice_free (InstallRemoveData, data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(InstallRemoveData, install_remove_data_free); static void repo_enabled_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source); g_autoptr(InstallRemoveData) install_remove_data = (InstallRemoveData *) user_data; g_autoptr(GError) error = NULL; g_autoptr(GsRepoRow) row = NULL; const gchar *operation_str; operation_str = install_remove_data->operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INSTALL ? "install" : install_remove_data->operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_REMOVE ? "remove" : install_remove_data->operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_ENABLE ? "enable" : install_remove_data->operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_DISABLE ? "disable" : NULL; g_assert (operation_str != NULL); row = g_weak_ref_get (&install_remove_data->row_weakref); if (row) gs_repo_row_unmark_busy (row); if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) { if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_debug ("repo %s cancelled", operation_str); return; } g_warning ("failed to %s repo: %s", operation_str, error->message); return; } g_debug ("finished %s repo %s", operation_str, gs_app_get_id (install_remove_data->repo)); } static void _enable_repo (InstallRemoveData *install_data) { GsReposDialog *dialog = install_data->dialog; g_autoptr(GsPluginJob) plugin_job = NULL; g_debug ("enabling repo %s", gs_app_get_id (install_data->repo)); plugin_job = gs_plugin_job_manage_repository_new (install_data->repo, install_data->operation | GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE); gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job, dialog->cancellable, repo_enabled_cb, install_data); } static void enable_repo_response_cb (GtkDialog *confirm_dialog, gint response, gpointer user_data) { g_autoptr(InstallRemoveData) install_data = (InstallRemoveData *) user_data; /* unmap the dialog */ gtk_window_destroy (GTK_WINDOW (confirm_dialog)); /* not agreed */ if (response != GTK_RESPONSE_OK) { g_autoptr(GsRepoRow) row = g_weak_ref_get (&install_data->row_weakref); if (row) gs_repo_row_unmark_busy (row); return; } _enable_repo (g_steal_pointer (&install_data)); } static void enable_repo (GsReposDialog *dialog, GsRepoRow *row, GsApp *repo) { g_autoptr(InstallRemoveData) install_data = NULL; install_data = g_slice_new0 (InstallRemoveData); install_data->operation = GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_ENABLE; install_data->dialog = g_object_ref (dialog); install_data->repo = g_object_ref (repo); g_weak_ref_init (&install_data->row_weakref, row); gs_repo_row_mark_busy (row); /* user needs to confirm acceptance of an agreement */ if (gs_app_get_agreement (repo) != NULL) { GtkWidget *confirm_dialog; g_autofree gchar *message = NULL; g_autoptr(GError) error = NULL; /* convert from AppStream markup */ message = as_markup_convert_simple (gs_app_get_agreement (repo), &error); if (message == NULL) { /* failed, so just try and show the original markup */ message = g_strdup (gs_app_get_agreement (repo)); g_warning ("Failed to process AppStream markup: %s", error->message); } /* ask for confirmation */ confirm_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_CANCEL, /* TRANSLATORS: window title */ "%s", _("Enable Third-Party Software Repository?")); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (confirm_dialog), "%s", message); /* TRANSLATORS: button to accept the agreement */ gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("Enable"), GTK_RESPONSE_OK); /* handle this async */ g_signal_connect (confirm_dialog, "response", G_CALLBACK (enable_repo_response_cb), g_steal_pointer (&install_data)); gtk_window_set_modal (GTK_WINDOW (confirm_dialog), TRUE); gtk_window_present (GTK_WINDOW (confirm_dialog)); return; } /* no prompt required */ _enable_repo (g_steal_pointer (&install_data)); } static void remove_repo_response_cb (GtkDialog *confirm_dialog, gint response, gpointer user_data) { g_autoptr(InstallRemoveData) remove_data = (InstallRemoveData *) user_data; GsReposDialog *dialog = remove_data->dialog; g_autoptr(GsPluginJob) plugin_job = NULL; /* unmap the dialog */ gtk_window_destroy (GTK_WINDOW (confirm_dialog)); /* not agreed */ if (response != GTK_RESPONSE_OK) { g_autoptr(GsRepoRow) row = g_weak_ref_get (&remove_data->row_weakref); if (row) gs_repo_row_unmark_busy (row); return; } g_debug ("removing repo %s", gs_app_get_id (remove_data->repo)); plugin_job = gs_plugin_job_manage_repository_new (remove_data->repo, remove_data->operation | GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE); gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job, dialog->cancellable, repo_enabled_cb, g_steal_pointer (&remove_data)); } static void remove_confirm_repo (GsReposDialog *dialog, GsRepoRow *row, GsApp *repo, GsPluginManageRepositoryFlags operation) { InstallRemoveData *remove_data; GtkWidget *confirm_dialog; g_autofree gchar *message = NULL; GtkWidget *button; GtkStyleContext *context; remove_data = g_slice_new0 (InstallRemoveData); remove_data->operation = operation; remove_data->dialog = g_object_ref (dialog); remove_data->repo = g_object_ref (repo); g_weak_ref_init (&remove_data->row_weakref, row); /* TRANSLATORS: The '%s' is replaced with a repository name, like "Fedora Modular - x86_64" */ message = g_strdup_printf (_("Software that has been installed from ā€œ%sā€ will cease to receive updates."), gs_app_get_name (repo)); /* ask for confirmation */ confirm_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_CANCEL, "%s", operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_DISABLE ? _("Disable Repository?") : _("Remove Repository?")); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (confirm_dialog), "%s", message); if (operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_DISABLE) { /* TRANSLATORS: this is button text to disable a repo */ button = gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("_Disable"), GTK_RESPONSE_OK); } else { /* TRANSLATORS: this is button text to remove a repo */ button = gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("_Remove"), GTK_RESPONSE_OK); } context = gtk_widget_get_style_context (button); gtk_style_context_add_class (context, "destructive-action"); /* handle this async */ g_signal_connect (confirm_dialog, "response", G_CALLBACK (remove_repo_response_cb), remove_data); gtk_window_set_modal (GTK_WINDOW (confirm_dialog), TRUE); gtk_window_present (GTK_WINDOW (confirm_dialog)); gs_repo_row_mark_busy (row); } static void repo_section_switch_clicked_cb (GsReposSection *section, GsRepoRow *row, GsReposDialog *dialog) { GsApp *repo; repo = gs_repo_row_get_repo (row); switch (gs_app_get_state (repo)) { case GS_APP_STATE_AVAILABLE: case GS_APP_STATE_AVAILABLE_LOCAL: enable_repo (dialog, row, repo); break; case GS_APP_STATE_INSTALLED: remove_confirm_repo (dialog, row, repo, GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_DISABLE); break; default: g_warning ("repo %s button clicked in unexpected state %s", gs_app_get_id (repo), gs_app_state_to_string (gs_app_get_state (repo))); break; } } static void repo_section_remove_clicked_cb (GsReposSection *section, GsRepoRow *row, GsReposDialog *dialog) { GsApp *repo = gs_repo_row_get_repo (row); remove_confirm_repo (dialog, row, repo, GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_REMOVE); } static void fedora_third_party_switch_done_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { g_autoptr(GsReposDialog) self = user_data; g_autoptr(GError) error = NULL; if (!gs_fedora_third_party_switch_finish (GS_FEDORA_THIRD_PARTY (source_object), result, &error)) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to switch 'fedora-third-party' config: %s", error->message); } /* Reload the state, because the user could dismiss the authentication prompt or the repos could change their state. */ reload_third_party_repos (self); } static void fedora_third_party_repos_switch_notify_cb (GObject *object, GParamSpec *param, gpointer user_data) { GsReposDialog *self = user_data; gs_fedora_third_party_switch (self->third_party, gtk_switch_get_active (GTK_SWITCH (object)), TRUE, self->cancellable, fedora_third_party_switch_done_cb, g_object_ref (self)); } static gboolean is_third_party_repo (GsReposDialog *dialog, GsApp *repo) { g_autoptr(GsPlugin) plugin = gs_app_dup_management_plugin (repo); const gchar *plugin_name = (plugin != NULL) ? gs_plugin_get_name (plugin) : NULL; return gs_app_get_scope (repo) == AS_COMPONENT_SCOPE_SYSTEM && gs_fedora_third_party_util_is_third_party_repo (dialog->third_party_repos, gs_app_get_id (repo), plugin_name); } static void add_repo (GsReposDialog *dialog, GsApp *repo, GSList **third_party_repos) { GsAppState state; GtkWidget *section; g_autofree gchar *origin_ui = NULL; state = gs_app_get_state (repo); if (!(state == GS_APP_STATE_AVAILABLE || state == GS_APP_STATE_AVAILABLE_LOCAL || state == GS_APP_STATE_INSTALLED || state == GS_APP_STATE_INSTALLING || state == GS_APP_STATE_REMOVING)) { g_warning ("repo %s in invalid state %s", gs_app_get_id (repo), gs_app_state_to_string (state)); return; } if (third_party_repos && is_third_party_repo (dialog, repo)) { *third_party_repos = g_slist_prepend (*third_party_repos, repo); return; } origin_ui = gs_app_dup_origin_ui (repo, TRUE); if (!origin_ui) origin_ui = gs_app_get_packaging_format (repo); if (!origin_ui) { g_autoptr(GsPlugin) plugin = gs_app_dup_management_plugin (repo); origin_ui = (plugin != NULL) ? g_strdup (gs_plugin_get_name (plugin)) : NULL; } section = g_hash_table_lookup (dialog->sections, origin_ui); if (section == NULL) { section = gs_repos_section_new (FALSE); adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (section), origin_ui); g_signal_connect_object (section, "remove-clicked", G_CALLBACK (repo_section_remove_clicked_cb), dialog, 0); g_signal_connect_object (section, "switch-clicked", G_CALLBACK (repo_section_switch_clicked_cb), dialog, 0); g_hash_table_insert (dialog->sections, g_steal_pointer (&origin_ui), section); gtk_widget_show (section); } gs_repos_section_add_repo (GS_REPOS_SECTION (section), repo); } static gint repos_dialog_compare_sections_cb (gconstpointer aa, gconstpointer bb) { GsReposSection *section_a = (GsReposSection *) aa; GsReposSection *section_b = (GsReposSection *) bb; const gchar *section_sort_key_a; const gchar *section_sort_key_b; g_autofree gchar *title_sort_key_a = NULL; g_autofree gchar *title_sort_key_b = NULL; gint res; section_sort_key_a = gs_repos_section_get_sort_key (section_a); section_sort_key_b = gs_repos_section_get_sort_key (section_b); res = g_strcmp0 (section_sort_key_a, section_sort_key_b); if (res != 0) return res; title_sort_key_a = gs_utils_sort_key (adw_preferences_group_get_title (ADW_PREFERENCES_GROUP (section_a))); title_sort_key_b = gs_utils_sort_key (adw_preferences_group_get_title (ADW_PREFERENCES_GROUP (section_b))); return g_strcmp0 (title_sort_key_a, title_sort_key_b); } static void get_sources_cb (GsPluginLoader *plugin_loader, GAsyncResult *res, GsReposDialog *dialog) { GsApp *app; g_autoptr(GError) error = NULL; g_autoptr(GsAppList) list = NULL; g_autoptr(GSList) other_repos = NULL; g_autoptr(GList) sections = NULL; AdwPreferencesGroup *added_section; GHashTableIter iter; /* get the results */ list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error); if (list == NULL) { if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_debug ("get sources cancelled"); return; } else { g_warning ("failed to get sources: %s", error->message); } gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "empty"); return; } /* remove previous */ g_hash_table_iter_init (&iter, dialog->sections); while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&added_section)) { adw_preferences_page_remove (ADW_PREFERENCES_PAGE (dialog->content_page), added_section); g_hash_table_iter_remove (&iter); } /* stop the spinner */ gtk_spinner_stop (GTK_SPINNER (dialog->spinner)); /* no results */ if (gs_app_list_length (list) == 0) { g_debug ("no sources to show"); gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "empty"); return; } /* add each */ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "sources"); for (guint i = 0; i < gs_app_list_length (list); i++) { app = gs_app_list_index (list, i); add_repo (dialog, app, &other_repos); } sections = g_hash_table_get_values (dialog->sections); sections = g_list_sort (sections, repos_dialog_compare_sections_cb); for (GList *link = sections; link; link = g_list_next (link)) { AdwPreferencesGroup *section = link->data; adw_preferences_page_add (ADW_PREFERENCES_PAGE (dialog->content_page), section); } gtk_widget_set_visible (dialog->content_page, sections != NULL); if (other_repos) { GsReposSection *section; GtkWidget *widget; GtkWidget *row; g_autofree gchar *anchor = NULL; g_autofree gchar *hint = NULL; g_autofree gchar *section_id = NULL; widget = gtk_switch_new (); gtk_widget_set_valign (widget, GTK_ALIGN_CENTER); gtk_switch_set_active (GTK_SWITCH (widget), dialog->third_party_enabled); g_signal_connect_object (widget, "notify::active", G_CALLBACK (fedora_third_party_repos_switch_notify_cb), dialog, 0); gtk_widget_show (widget); row = adw_action_row_new (); #if ADW_CHECK_VERSION(1,2,0) adw_preferences_row_set_use_markup (ADW_PREFERENCES_ROW (row), FALSE); #endif adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), _("Enable New Repositories")); adw_action_row_set_subtitle (ADW_ACTION_ROW (row), _("Turn on new repositories when they are added.")); adw_action_row_set_activatable_widget (ADW_ACTION_ROW (row), widget); adw_action_row_add_suffix (ADW_ACTION_ROW (row), widget); gtk_widget_show (row); anchor = g_strdup_printf ("%s", "https://docs.fedoraproject.org/en-US/workstation-working-group/third-party-repos/", /* TRANSLATORS: this is the clickable * link on the third party repositories info bar */ _("more information")); hint = g_strdup_printf ( /* TRANSLATORS: this is the third party repositories info bar. The '%s' is replaced with a link consisting a text "more information", which constructs a sentence: "Additional repositories from selected third parties - more information."*/ _("Additional repositories from selected third parties ā€” %s."), anchor); widget = adw_preferences_group_new (); adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (widget), _("Fedora Third Party Repositories")); adw_preferences_group_set_description (ADW_PREFERENCES_GROUP (widget), hint); gtk_widget_show (widget); adw_preferences_group_add (ADW_PREFERENCES_GROUP (widget), row); adw_preferences_page_add (ADW_PREFERENCES_PAGE (dialog->content_page), ADW_PREFERENCES_GROUP (widget)); /* use something unique, not clashing with the other section names */ section_id = g_strdup_printf ("fedora-third-party::1::%p", widget); g_hash_table_insert (dialog->sections, g_steal_pointer (§ion_id), widget); section = GS_REPOS_SECTION (gs_repos_section_new (TRUE)); gs_repos_section_set_sort_key (section, "900"); g_signal_connect_object (section, "switch-clicked", G_CALLBACK (repo_section_switch_clicked_cb), dialog, 0); gtk_widget_show (GTK_WIDGET (section)); for (GSList *link = other_repos; link; link = g_slist_next (link)) { GsApp *repo = link->data; gs_repos_section_add_repo (section, repo); } /* use something unique, not clashing with the other section names */ section_id = g_strdup_printf ("fedora-third-party::2::%p", section); g_hash_table_insert (dialog->sections, g_steal_pointer (§ion_id), section); adw_preferences_page_add (ADW_PREFERENCES_PAGE (dialog->content_page), ADW_PREFERENCES_GROUP (section)); } } static void reload_sources (GsReposDialog *dialog) { g_autoptr(GsPluginJob) plugin_job = NULL; /* get the list of non-core software repositories */ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES, "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED | GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME | GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE, "dedupe-flags", GS_APP_LIST_FILTER_FLAG_NONE, NULL); gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job, dialog->cancellable, (GAsyncReadyCallback) get_sources_cb, dialog); } static void fedora_third_party_list_repos_done_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { g_autoptr(GsReposDialog) self = user_data; g_autoptr(GHashTable) repos = NULL; g_autoptr(GError) error = NULL; if (!gs_fedora_third_party_list_finish (GS_FEDORA_THIRD_PARTY (source_object), result, &repos, &error)) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to list 'fedora-third-party' repos: %s", error->message); } else { self->third_party_repos = g_steal_pointer (&repos); } reload_sources (self); } static void fedora_third_party_query_done_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GsFedoraThirdPartyState state = GS_FEDORA_THIRD_PARTY_STATE_UNKNOWN; g_autoptr(GsReposDialog) self = user_data; g_autoptr(GError) error = NULL; if (!gs_fedora_third_party_query_finish (GS_FEDORA_THIRD_PARTY (source_object), result, &state, &error)) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to query 'fedora-third-party': %s", error->message); } else { self->third_party_enabled = state == GS_FEDORA_THIRD_PARTY_STATE_ENABLED; } gs_fedora_third_party_list (self->third_party, self->cancellable, fedora_third_party_list_repos_done_cb, g_object_ref (self)); } static gboolean is_fedora (void) { const gchar *id = NULL; g_autoptr(GsOsRelease) os_release = NULL; os_release = gs_os_release_new (NULL); if (os_release == NULL) return FALSE; id = gs_os_release_get_id (os_release); if (g_strcmp0 (id, "fedora") == 0) return TRUE; return FALSE; } static void reload_third_party_repos (GsReposDialog *dialog) { /* Fedora-specific functionality */ if (!is_fedora ()) { reload_sources (dialog); return; } gs_fedora_third_party_invalidate (dialog->third_party); if (!gs_fedora_third_party_is_available (dialog->third_party)) { reload_sources (dialog); return; } g_clear_pointer (&dialog->third_party_repos, g_hash_table_unref); gs_fedora_third_party_query (dialog->third_party, dialog->cancellable, fedora_third_party_query_done_cb, g_object_ref (dialog)); } static gchar * get_os_name (void) { gchar *name = NULL; g_autoptr(GsOsRelease) os_release = NULL; os_release = gs_os_release_new (NULL); if (os_release != NULL) name = g_strdup (gs_os_release_get_name (os_release)); if (name == NULL) { /* TRANSLATORS: this is the fallback text we use if we can't figure out the name of the operating system */ name = g_strdup (_("the operating system")); } return name; } static void reload_cb (GsPluginLoader *plugin_loader, GsReposDialog *dialog) { reload_third_party_repos (dialog); } static void set_plugin_loader (GsReposDialog *dialog, GsPluginLoader *plugin_loader) { dialog->plugin_loader = g_object_ref (plugin_loader); g_signal_connect (dialog->plugin_loader, "reload", G_CALLBACK (reload_cb), dialog); } static void gs_repos_dialog_dispose (GObject *object) { GsReposDialog *dialog = GS_REPOS_DIALOG (object); if (dialog->plugin_loader != NULL) { g_signal_handlers_disconnect_by_func (dialog->plugin_loader, reload_cb, dialog); g_clear_object (&dialog->plugin_loader); } g_cancellable_cancel (dialog->cancellable); g_clear_pointer (&dialog->third_party_repos, g_hash_table_unref); g_clear_pointer (&dialog->sections, g_hash_table_unref); g_clear_object (&dialog->third_party); g_clear_object (&dialog->cancellable); g_clear_object (&dialog->settings); G_OBJECT_CLASS (gs_repos_dialog_parent_class)->dispose (object); } static void gs_repos_dialog_init (GsReposDialog *dialog) { g_autofree gchar *label_empty_text = NULL; g_autofree gchar *os_name = NULL; g_autoptr(GString) str = g_string_new (NULL); gtk_widget_init_template (GTK_WIDGET (dialog)); dialog->third_party = gs_fedora_third_party_new (); dialog->cancellable = g_cancellable_new (); dialog->settings = g_settings_new ("org.gnome.software"); dialog->sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); os_name = get_os_name (); /* TRANSLATORS: This is the description text displayed in the Software Repositories dialog. %s gets replaced by the name of the actual distro, e.g. Fedora. */ label_empty_text = g_strdup_printf (_("These repositories supplement the default software provided by %s."), os_name); adw_status_page_set_description (ADW_STATUS_PAGE (dialog->status_empty), label_empty_text); } static void gs_repos_dialog_class_init (GsReposDialogClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = gs_repos_dialog_dispose; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-repos-dialog.ui"); gtk_widget_class_bind_template_child (widget_class, GsReposDialog, status_empty); gtk_widget_class_bind_template_child (widget_class, GsReposDialog, content_page); gtk_widget_class_bind_template_child (widget_class, GsReposDialog, spinner); gtk_widget_class_bind_template_child (widget_class, GsReposDialog, stack); gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "window.close", NULL); } GtkWidget * gs_repos_dialog_new (GtkWindow *parent, GsPluginLoader *plugin_loader) { GsReposDialog *dialog; dialog = g_object_new (GS_TYPE_REPOS_DIALOG, "transient-for", parent, "modal", TRUE, NULL); set_plugin_loader (dialog, plugin_loader); gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "waiting"); gtk_spinner_start (GTK_SPINNER (dialog->spinner)); reload_third_party_repos (dialog); return GTK_WIDGET (dialog); }