summaryrefslogtreecommitdiffstats
path: root/src/gs-repos-dialog.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gs-repos-dialog.c')
-rw-r--r--src/gs-repos-dialog.c890
1 files changed, 890 insertions, 0 deletions
diff --git a/src/gs-repos-dialog.c b/src/gs-repos-dialog.c
new file mode 100644
index 0000000..53bbf2f
--- /dev/null
+++ b/src/gs-repos-dialog.c
@@ -0,0 +1,890 @@
+/* -*- 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 <richard@hughsie.com>
+ * Copyright (C) 2013 Matthias Clasen <mclasen@redhat.com>
+ * Copyright (C) 2014-2018 Kalev Lember <klember@redhat.com>
+ *
+ * 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-third-party-repo-row.h"
+#include "gs-utils.h"
+#include <glib/gi18n.h>
+
+struct _GsReposDialog
+{
+ GtkDialog parent_instance;
+ GSettings *settings;
+ GsApp *third_party_repo;
+
+ GCancellable *cancellable;
+ GsPluginLoader *plugin_loader;
+ GtkWidget *frame_third_party;
+ GtkWidget *label_description;
+ GtkWidget *label_empty;
+ GtkWidget *label_header;
+ GtkWidget *listbox;
+ GtkWidget *listbox_third_party;
+ GtkWidget *row_third_party;
+ GtkWidget *spinner;
+ GtkWidget *stack;
+};
+
+G_DEFINE_TYPE (GsReposDialog, gs_repos_dialog, GTK_TYPE_DIALOG)
+
+typedef struct {
+ GsReposDialog *dialog;
+ GsApp *repo;
+ GsPluginAction action;
+} InstallRemoveData;
+
+static void
+install_remove_data_free (InstallRemoveData *data)
+{
+ g_clear_object (&data->dialog);
+ g_clear_object (&data->repo);
+ g_slice_free (InstallRemoveData, data);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(InstallRemoveData, install_remove_data_free);
+
+static void reload_sources (GsReposDialog *dialog);
+static void reload_third_party_repo (GsReposDialog *dialog);
+
+static gchar *
+get_repo_installed_text (GsApp *repo)
+{
+ GsAppList *related;
+ guint cnt_addon = 0;
+ guint cnt_apps = 0;
+ g_autofree gchar *addons_text = NULL;
+ g_autofree gchar *apps_text = NULL;
+
+ related = gs_app_get_related (repo);
+ for (guint i = 0; i < gs_app_list_length (related); i++) {
+ GsApp *app_tmp = gs_app_list_index (related, i);
+ switch (gs_app_get_kind (app_tmp)) {
+ case AS_APP_KIND_WEB_APP:
+ case AS_APP_KIND_DESKTOP:
+ cnt_apps++;
+ break;
+ case AS_APP_KIND_FONT:
+ case AS_APP_KIND_CODEC:
+ case AS_APP_KIND_INPUT_METHOD:
+ case AS_APP_KIND_ADDON:
+ cnt_addon++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (cnt_apps == 0 && cnt_addon == 0) {
+ /* nothing! */
+ return NULL;
+ }
+ if (cnt_addon == 0) {
+ /* TRANSLATORS: This string is used to construct the 'X applications
+ installed' sentence, describing a software repository. */
+ return g_strdup_printf (ngettext ("%u application installed",
+ "%u applications installed",
+ cnt_apps), cnt_apps);
+ }
+ if (cnt_apps == 0) {
+ /* TRANSLATORS: This string is used to construct the 'X add-ons
+ installed' sentence, describing a software repository. */
+ return g_strdup_printf (ngettext ("%u add-on installed",
+ "%u add-ons installed",
+ cnt_addon), cnt_addon);
+ }
+
+ /* TRANSLATORS: This string is used to construct the 'X applications
+ and y add-ons installed' sentence, describing a software repository.
+ The correct form here depends on the number of applications. */
+ apps_text = g_strdup_printf (ngettext ("%u application",
+ "%u applications",
+ cnt_apps), cnt_apps);
+ /* TRANSLATORS: This string is used to construct the 'X applications
+ and y add-ons installed' sentence, describing a software repository.
+ The correct form here depends on the number of add-ons. */
+ addons_text = g_strdup_printf (ngettext ("%u add-on",
+ "%u add-ons",
+ cnt_addon), cnt_addon);
+ /* TRANSLATORS: This string is used to construct the 'X applications
+ and y add-ons installed' sentence, describing a software repository.
+ The correct form here depends on the total number of
+ applications and add-ons. */
+ return g_strdup_printf (ngettext ("%s and %s installed",
+ "%s and %s installed",
+ cnt_apps + cnt_addon),
+ apps_text, addons_text);
+}
+
+static gboolean
+repo_supports_removal (GsApp *repo)
+{
+ const gchar *management_plugin = gs_app_get_management_plugin (repo);
+
+ /* can't remove a repo, only enable/disable existing ones */
+ if (g_strcmp0 (management_plugin, "fwupd") == 0 ||
+ g_strcmp0 (management_plugin, "packagekit") == 0 ||
+ g_strcmp0 (management_plugin, "rpm-ostree") == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+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;
+ const gchar *action_str;
+
+ action_str = gs_plugin_action_to_string (install_remove_data->action);
+
+ if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
+ if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
+ g_debug ("repo %s cancelled", action_str);
+ return;
+ }
+
+ g_warning ("failed to %s repo: %s", action_str, error->message);
+ return;
+ }
+
+ g_debug ("finished %s repo %s", action_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_newv (install_data->action,
+ "interactive", TRUE,
+ "app", install_data->repo,
+ NULL);
+ 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_widget_destroy (GTK_WIDGET (confirm_dialog));
+
+ /* not agreed */
+ if (response != GTK_RESPONSE_OK)
+ return;
+
+ _enable_repo (g_steal_pointer (&install_data));
+}
+
+static void
+enable_repo (GsReposDialog *dialog, GsApp *repo)
+{
+ g_autoptr(InstallRemoveData) install_data = NULL;
+
+ install_data = g_slice_new0 (InstallRemoveData);
+ install_data->action = GS_PLUGIN_ACTION_INSTALL;
+ install_data->repo = g_object_ref (repo);
+ install_data->dialog = g_object_ref (dialog);
+
+ /* 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_widget_destroy (GTK_WIDGET (confirm_dialog));
+
+ /* not agreed */
+ if (response != GTK_RESPONSE_OK)
+ return;
+
+ g_debug ("removing repo %s", gs_app_get_id (remove_data->repo));
+ plugin_job = gs_plugin_job_newv (remove_data->action,
+ "interactive", TRUE,
+ "app", remove_data->repo,
+ NULL);
+ 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, GsApp *repo)
+{
+ InstallRemoveData *remove_data;
+ GtkWidget *confirm_dialog;
+ g_autofree gchar *message = NULL;
+ g_autofree gchar *title = NULL;
+ GtkWidget *button;
+ GtkStyleContext *context;
+
+ remove_data = g_slice_new0 (InstallRemoveData);
+ remove_data->action = GS_PLUGIN_ACTION_REMOVE;
+ remove_data->repo = g_object_ref (repo);
+ remove_data->dialog = g_object_ref (dialog);
+
+ if (repo_supports_removal (repo)) {
+ /* TRANSLATORS: this is a prompt message, and '%s' is a
+ * repository name, e.g. 'GNOME Nightly' */
+ title = g_strdup_printf (_("Remove “%s”?"),
+ gs_app_get_name (repo));
+ } else {
+ /* TRANSLATORS: this is a prompt message, and '%s' is a
+ * repository name, e.g. 'GNOME Nightly' */
+ title = g_strdup_printf (_("Disable “%s”?"),
+ gs_app_get_name (repo));
+ }
+ /* TRANSLATORS: longer dialog text */
+ message = g_strdup (_("Software that has been installed from this "
+ "repository will no longer receive updates, "
+ "including security fixes."));
+
+ /* ask for confirmation */
+ confirm_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_CANCEL,
+ "%s", title);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (confirm_dialog),
+ "%s", message);
+
+ if (repo_supports_removal (repo)) {
+ /* TRANSLATORS: this is button text to remove the repo */
+ button = gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("Remove"), GTK_RESPONSE_OK);
+ } else {
+ /* TRANSLATORS: this is button text to remove the repo */
+ button = gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("Disable"), 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));
+}
+
+static void
+repo_button_clicked_cb (GsRepoRow *row,
+ GsReposDialog *dialog)
+{
+ GsApp *repo;
+
+ repo = gs_repo_row_get_repo (row);
+
+ switch (gs_app_get_state (repo)) {
+ case AS_APP_STATE_AVAILABLE:
+ case AS_APP_STATE_AVAILABLE_LOCAL:
+ enable_repo (dialog, repo);
+ break;
+ case AS_APP_STATE_INSTALLED:
+ remove_confirm_repo (dialog, repo);
+ break;
+ default:
+ g_warning ("repo %s button clicked in unexpected state %s",
+ gs_app_get_id (repo),
+ as_app_state_to_string (gs_app_get_state (repo)));
+ break;
+ }
+}
+
+static GtkListBox *
+get_list_box_for_repo (GsReposDialog *dialog, GsApp *repo)
+{
+ if (dialog->third_party_repo != NULL) {
+ const gchar *source_repo;
+ const gchar *source_third_party_package;
+
+ source_repo = gs_app_get_source_id_default (repo);
+ source_third_party_package = gs_app_get_source_id_default (dialog->third_party_repo);
+
+ /* group repos from the same repo-release package together */
+ if (g_strcmp0 (source_repo, source_third_party_package) == 0)
+ return GTK_LIST_BOX (dialog->listbox_third_party);
+ }
+
+ return GTK_LIST_BOX (dialog->listbox);
+}
+
+static void
+add_repo (GsReposDialog *dialog, GsApp *repo)
+{
+ GtkWidget *row;
+ g_autofree gchar *text = NULL;
+ AsAppState state;
+
+ state = gs_app_get_state (repo);
+ if (!(state == AS_APP_STATE_AVAILABLE ||
+ state == AS_APP_STATE_AVAILABLE_LOCAL ||
+ state == AS_APP_STATE_INSTALLED ||
+ state == AS_APP_STATE_INSTALLING ||
+ state == AS_APP_STATE_REMOVING)) {
+ g_warning ("repo %s in invalid state %s",
+ gs_app_get_id (repo),
+ as_app_state_to_string (state));
+ return;
+ }
+
+ row = gs_repo_row_new ();
+ gs_repo_row_set_name (GS_REPO_ROW (row),
+ gs_app_get_name (repo));
+ text = get_repo_installed_text (repo);
+ gs_repo_row_set_comment (GS_REPO_ROW (row), text);
+ gs_repo_row_set_url (GS_REPO_ROW (row),
+ gs_app_get_url (repo, AS_URL_KIND_HOMEPAGE));
+ gs_repo_row_show_status (GS_REPO_ROW (row));
+ gs_repo_row_set_repo (GS_REPO_ROW (row), repo);
+
+ g_signal_connect (row, "button-clicked",
+ G_CALLBACK (repo_button_clicked_cb), dialog);
+
+ gtk_list_box_prepend (get_list_box_for_repo (dialog, repo), row);
+ gtk_widget_show (row);
+}
+
+static void
+third_party_repo_installed_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+ g_autoptr(InstallRemoveData) install_data = (InstallRemoveData *) user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
+ const gchar *action_str = gs_plugin_action_to_string (install_data->action);
+
+ if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
+ g_debug ("third party repo %s cancelled", action_str);
+ return;
+ }
+
+ g_warning ("failed to %s third party repo: %s", action_str, error->message);
+ return;
+ }
+
+ reload_sources (install_data->dialog);
+}
+
+static void
+install_third_party_repo (GsReposDialog *dialog, gboolean install)
+{
+ InstallRemoveData *install_data;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ install_data = g_slice_new0 (InstallRemoveData);
+ install_data->dialog = g_object_ref (dialog);
+ install_data->action = install ? GS_PLUGIN_ACTION_INSTALL : GS_PLUGIN_ACTION_REMOVE;
+
+ plugin_job = gs_plugin_job_newv (install_data->action,
+ "interactive", TRUE,
+ "app", dialog->third_party_repo,
+ NULL);
+ gs_plugin_loader_job_process_async (dialog->plugin_loader,
+ plugin_job,
+ dialog->cancellable,
+ third_party_repo_installed_cb,
+ install_data);
+}
+
+static void
+third_party_repo_button_clicked_cb (GsThirdPartyRepoRow *row,
+ gpointer user_data)
+{
+ GsReposDialog *dialog = (GsReposDialog *) user_data;
+ GsApp *app;
+
+ app = gs_third_party_repo_row_get_app (row);
+
+ switch (gs_app_get_state (app)) {
+ case AS_APP_STATE_UNAVAILABLE:
+ case AS_APP_STATE_AVAILABLE:
+ case AS_APP_STATE_AVAILABLE_LOCAL:
+ install_third_party_repo (dialog, TRUE);
+ break;
+ case AS_APP_STATE_UPDATABLE_LIVE:
+ case AS_APP_STATE_UPDATABLE:
+ case AS_APP_STATE_INSTALLED:
+ install_third_party_repo (dialog, FALSE);
+ break;
+ default:
+ g_warning ("third party repo %s button clicked in unexpected state %s",
+ gs_app_get_id (app),
+ as_app_state_to_string (gs_app_get_state (app)));
+ break;
+ }
+
+ g_settings_set_boolean (dialog->settings, "show-nonfree-prompt", FALSE);
+}
+
+static void
+refresh_third_party_repo (GsReposDialog *dialog)
+{
+ if (dialog->third_party_repo == NULL) {
+ gtk_widget_hide (dialog->frame_third_party);
+ return;
+ }
+
+ gtk_widget_show (dialog->frame_third_party);
+}
+
+static void
+remove_all_repo_rows_cb (GtkWidget *widget, gpointer user_data)
+{
+ GtkContainer *container = GTK_CONTAINER (user_data);
+
+ if (GS_IS_REPO_ROW (widget))
+ gtk_container_remove (container, widget);
+}
+
+static void
+container_remove_all_repo_rows (GtkContainer *container)
+{
+ gtk_container_foreach (container, remove_all_repo_rows_cb, container);
+}
+
+static void
+get_sources_cb (GsPluginLoader *plugin_loader,
+ GAsyncResult *res,
+ GsReposDialog *dialog)
+{
+ GsApp *app;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GsAppList) list = NULL;
+
+ /* 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_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");
+ gtk_style_context_add_class (gtk_widget_get_style_context (dialog->label_header),
+ "dim-label");
+ return;
+ }
+
+ /* remove previous */
+ gs_container_remove_all (GTK_CONTAINER (dialog->listbox));
+ container_remove_all_repo_rows (GTK_CONTAINER (dialog->listbox_third_party));
+
+ /* stop the spinner */
+ gs_stop_spinner (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");
+ gtk_style_context_add_class (gtk_widget_get_style_context (dialog->label_header), "dim-label");
+ return;
+ }
+
+ gtk_style_context_remove_class (gtk_widget_get_style_context (dialog->label_header),
+ "dim-label");
+
+ /* 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);
+ }
+}
+
+static void
+resolve_third_party_repo_cb (GsPluginLoader *plugin_loader,
+ GAsyncResult *res,
+ GsReposDialog *dialog)
+{
+ GsApp *app;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GsAppList) list = NULL;
+
+ /* 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_debug ("resolve third party repo cancelled");
+ return;
+ } else {
+ g_warning ("failed to resolve third party repo: %s", error->message);
+ return;
+ }
+ }
+
+ /* we should only get one result */
+ if (gs_app_list_length (list) > 0)
+ app = gs_app_list_index (list, 0);
+ else
+ app = NULL;
+
+ g_set_object (&dialog->third_party_repo, app);
+ gs_third_party_repo_row_set_app (GS_THIRD_PARTY_REPO_ROW (dialog->row_third_party), app);
+
+ /* refresh widget */
+ refresh_third_party_repo (dialog);
+}
+
+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,
+ NULL);
+ gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
+ dialog->cancellable,
+ (GAsyncReadyCallback) get_sources_cb,
+ dialog);
+}
+
+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_repo (GsReposDialog *dialog)
+{
+ const gchar *third_party_repo_package = "fedora-workstation-repositories";
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* Fedora-specific functionality */
+ if (!is_fedora ())
+ return;
+
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH_PROVIDES,
+ "search", third_party_repo_package,
+ "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+ GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES,
+ NULL);
+ gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
+ dialog->cancellable,
+ (GAsyncReadyCallback) resolve_third_party_repo_cb,
+ dialog);
+}
+
+static void
+list_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ GtkWidget *header = NULL;
+ if (before != NULL)
+ header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_list_box_row_set_header (row, header);
+}
+
+static gchar *
+get_row_sort_key (GtkListBoxRow *row)
+{
+ GsApp *app;
+ guint sort_order;
+ g_autofree gchar *sort_key = NULL;
+
+ /* sort third party repo rows first */
+ if (GS_IS_THIRD_PARTY_REPO_ROW (row)) {
+ sort_order = 1;
+ app = gs_third_party_repo_row_get_app (GS_THIRD_PARTY_REPO_ROW (row));
+ } else {
+ sort_order = 2;
+ app = gs_repo_row_get_repo (GS_REPO_ROW (row));
+ }
+
+ if (gs_app_get_name (app) != NULL) {
+ sort_key = gs_utils_sort_key (gs_app_get_name (app));
+ return g_strdup_printf ("%u:%s", sort_order, sort_key);
+ } else {
+ return g_strdup_printf ("%u:", sort_order);
+ }
+}
+
+static gint
+list_sort_func (GtkListBoxRow *a,
+ GtkListBoxRow *b,
+ gpointer user_data)
+{
+ g_autofree gchar *key1 = get_row_sort_key (a);
+ g_autofree gchar *key2 = get_row_sort_key (b);
+
+ /* compare the keys according to the algorithm above */
+ return g_strcmp0 (key1, key2);
+}
+
+static void
+list_row_activated_cb (GtkListBox *list_box,
+ GtkListBoxRow *row,
+ GsReposDialog *dialog)
+{
+ GtkListBoxRow *other_row;
+
+ if (!GS_IS_REPO_ROW (row))
+ return;
+
+ gs_repo_row_show_details (GS_REPO_ROW (row));
+
+ for (guint i = 0; (other_row = gtk_list_box_get_row_at_index (list_box, i)) != NULL; i++) {
+ if (!GS_IS_REPO_ROW (other_row))
+ continue;
+ if (other_row == row)
+ continue;
+
+ gs_repo_row_hide_details (GS_REPO_ROW (other_row));
+ }
+}
+
+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_sources (dialog);
+ reload_third_party_repo (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_object (&dialog->cancellable);
+ g_clear_object (&dialog->settings);
+ g_clear_object (&dialog->third_party_repo);
+
+ G_OBJECT_CLASS (gs_repos_dialog_parent_class)->dispose (object);
+}
+
+static void
+gs_repos_dialog_init (GsReposDialog *dialog)
+{
+ g_autofree gchar *label_description_text = NULL;
+ 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->cancellable = g_cancellable_new ();
+ dialog->settings = g_settings_new ("org.gnome.software");
+
+ os_name = get_os_name ();
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (dialog->listbox),
+ list_header_func,
+ dialog,
+ NULL);
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (dialog->listbox),
+ list_sort_func,
+ dialog, NULL);
+ g_signal_connect (dialog->listbox, "row-activated",
+ G_CALLBACK (list_row_activated_cb), dialog);
+
+ /* 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_description_text = g_strdup_printf (_("These repositories supplement the default software provided by %s."),
+ os_name);
+ gtk_label_set_text (GTK_LABEL (dialog->label_description), label_description_text);
+
+ /* set up third party repository row */
+ gtk_list_box_set_header_func (GTK_LIST_BOX (dialog->listbox_third_party),
+ list_header_func,
+ dialog,
+ NULL);
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (dialog->listbox_third_party),
+ list_sort_func,
+ dialog, NULL);
+ g_signal_connect (dialog->listbox_third_party, "row-activated",
+ G_CALLBACK (list_row_activated_cb), dialog);
+ g_signal_connect (dialog->row_third_party, "button-clicked",
+ G_CALLBACK (third_party_repo_button_clicked_cb), dialog);
+ gs_third_party_repo_row_set_name (GS_THIRD_PARTY_REPO_ROW (dialog->row_third_party),
+ /* TRANSLATORS: info bar title in the software repositories dialog */
+ _("Third Party Repositories"));
+ g_string_append (str,
+ /* TRANSLATORS: this is the third party repositories info bar. */
+ _("Access additional software from selected third party sources."));
+ g_string_append (str, " ");
+ g_string_append (str,
+ /* TRANSLATORS: this is the third party repositories info bar. */
+ _("Some of this software is proprietary and therefore has restrictions on use, sharing, and access to source code."));
+ g_string_append_printf (str, " <a href=\"%s\">%s</a>",
+ "https://fedoraproject.org/wiki/Workstation/Third_Party_Software_Repositories",
+ /* TRANSLATORS: this is the clickable
+ * link on the third party repositories info bar */
+ _("Find out more…"));
+ gs_third_party_repo_row_set_comment (GS_THIRD_PARTY_REPO_ROW (dialog->row_third_party), str->str);
+ refresh_third_party_repo (dialog);
+
+ /* 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);
+ gtk_label_set_text (GTK_LABEL (dialog->label_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, frame_third_party);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, label_description);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, label_empty);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, label_header);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, listbox);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, listbox_third_party);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, row_third_party);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, spinner);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, stack);
+}
+
+GtkWidget *
+gs_repos_dialog_new (GtkWindow *parent, GsPluginLoader *plugin_loader)
+{
+ GsReposDialog *dialog;
+
+ dialog = g_object_new (GS_TYPE_REPOS_DIALOG,
+ "use-header-bar", TRUE,
+ "transient-for", parent,
+ "modal", TRUE,
+ NULL);
+ set_plugin_loader (dialog, plugin_loader);
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "waiting");
+ gs_start_spinner (GTK_SPINNER (dialog->spinner));
+ reload_sources (dialog);
+ reload_third_party_repo (dialog);
+
+ return GTK_WIDGET (dialog);
+}