diff options
Diffstat (limited to '')
-rw-r--r-- | src/gs-updates-page.c | 1467 |
1 files changed, 1467 insertions, 0 deletions
diff --git a/src/gs-updates-page.c b/src/gs-updates-page.c new file mode 100644 index 0000000..4456ddd --- /dev/null +++ b/src/gs-updates-page.c @@ -0,0 +1,1467 @@ +/* -*- 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) 2014-2018 Kalev Lember <klember@redhat.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include "gs-shell.h" +#include "gs-updates-page.h" +#include "gs-common.h" +#include "gs-app-row.h" +#include "gs-plugin-private.h" +#include "gs-removal-dialog.h" +#include "gs-update-monitor.h" +#include "gs-updates-section.h" +#include "gs-upgrade-banner.h" +#include "gs-application.h" + +typedef enum { + GS_UPDATES_PAGE_FLAG_NONE = 0, + GS_UPDATES_PAGE_FLAG_HAS_UPDATES = 1 << 0, + GS_UPDATES_PAGE_FLAG_HAS_UPGRADES = 1 << 1, + GS_UPDATES_PAGE_FLAG_LAST +} GsUpdatesPageFlags; + +typedef enum { + GS_UPDATES_PAGE_STATE_STARTUP, + GS_UPDATES_PAGE_STATE_ACTION_REFRESH, + GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES, + GS_UPDATES_PAGE_STATE_MANAGED, + GS_UPDATES_PAGE_STATE_IDLE, + GS_UPDATES_PAGE_STATE_FAILED, + GS_UPDATES_PAGE_STATE_LAST, +} GsUpdatesPageState; + +struct _GsUpdatesPage +{ + GsPage parent_instance; + + GsPluginLoader *plugin_loader; + GCancellable *cancellable; + GCancellable *cancellable_refresh; + GCancellable *cancellable_upgrade_download; + GSettings *settings; + GSettings *desktop_settings; + gboolean cache_valid; + guint action_cnt; + GsShell *shell; + GsUpdatesPageState state; + GsUpdatesPageFlags result_flags; + GtkWidget *button_refresh; + GtkWidget *header_spinner_start; + GtkWidget *header_start_box; + gboolean has_agreed_to_mobile_data; + gboolean ampm_available; + guint updates_counter; + gboolean is_narrow; + + GtkWidget *updates_box; + GtkWidget *button_updates_mobile; + GtkWidget *button_updates_offline; + GtkWidget *updates_failed_page; + GtkLabel *uptodate_description; + GtkWidget *scrolledwindow_updates; + GtkWidget *spinner_updates; + GtkWidget *stack_updates; + GtkWidget *upgrade_banner; + GtkWidget *infobar_end_of_life; + GtkWidget *label_end_of_life; + + GtkSizeGroup *sizegroup_name; + GtkSizeGroup *sizegroup_button_label; + GtkSizeGroup *sizegroup_button_image; + GtkSizeGroup *sizegroup_header; + GsUpdatesSection *sections[GS_UPDATES_SECTION_KIND_LAST]; + + guint refresh_last_checked_id; +}; + +enum { + COLUMN_UPDATE_APP, + COLUMN_UPDATE_NAME, + COLUMN_UPDATE_VERSION, + COLUMN_UPDATE_LAST +}; + +G_DEFINE_TYPE (GsUpdatesPage, gs_updates_page, GS_TYPE_PAGE) + +typedef enum { + PROP_IS_NARROW = 1, + /* Overrides: */ + PROP_VADJUSTMENT, + PROP_TITLE, + PROP_COUNTER, +} GsUpdatesPageProperty; + +static GParamSpec *obj_props[PROP_IS_NARROW + 1] = { NULL, }; + +static void +gs_updates_page_set_flag (GsUpdatesPage *self, GsUpdatesPageFlags flag) +{ + self->result_flags |= flag; +} + +static void +gs_updates_page_clear_flag (GsUpdatesPage *self, GsUpdatesPageFlags flag) +{ + self->result_flags &= ~flag; +} + +static const gchar * +gs_updates_page_state_to_string (GsUpdatesPageState state) +{ + if (state == GS_UPDATES_PAGE_STATE_STARTUP) + return "startup"; + if (state == GS_UPDATES_PAGE_STATE_ACTION_REFRESH) + return "action-refresh"; + if (state == GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES) + return "action-get-updates"; + if (state == GS_UPDATES_PAGE_STATE_MANAGED) + return "managed"; + if (state == GS_UPDATES_PAGE_STATE_IDLE) + return "idle"; + if (state == GS_UPDATES_PAGE_STATE_FAILED) + return "failed"; + return NULL; +} + +static void +gs_updates_page_invalidate (GsUpdatesPage *self) +{ + self->cache_valid = FALSE; +} + +static GsUpdatesSectionKind +_get_app_section (GsApp *app) +{ + if (gs_app_get_state (app) == GS_APP_STATE_UPDATABLE_LIVE || + gs_app_get_state (app) == GS_APP_STATE_INSTALLING) { + if (gs_app_get_kind (app) == AS_COMPONENT_KIND_FIRMWARE) + return GS_UPDATES_SECTION_KIND_ONLINE_FIRMWARE; + return GS_UPDATES_SECTION_KIND_ONLINE; + } + if (gs_app_get_kind (app) == AS_COMPONENT_KIND_FIRMWARE) + return GS_UPDATES_SECTION_KIND_OFFLINE_FIRMWARE; + return GS_UPDATES_SECTION_KIND_OFFLINE; +} + +static GsAppList * +_get_all_apps (GsUpdatesPage *self) +{ + GsAppList *apps = gs_app_list_new (); + for (guint i = 0; i < GS_UPDATES_SECTION_KIND_LAST; i++) { + GsAppList *list = gs_updates_section_get_list (self->sections[i]); + gs_app_list_add_list (apps, list); + } + return apps; +} + +static guint +_get_num_updates (GsUpdatesPage *self) +{ + guint count = 0; + g_autoptr(GsAppList) apps = _get_all_apps (self); + + for (guint i = 0; i < gs_app_list_length (apps); ++i) { + GsApp *app = gs_app_list_index (apps, i); + if (gs_app_is_updatable (app) || + gs_app_get_state (app) == GS_APP_STATE_INSTALLING) + ++count; + } + return count; +} + +static gchar * +gs_updates_page_last_checked_time_string (GsUpdatesPage *self, + gint *out_hours_ago, + gint *out_days_ago) +{ + gint64 last_checked; + gchar *res; + + g_settings_get (self->settings, "check-timestamp", "x", &last_checked); + res = gs_utils_time_to_string (last_checked); + if (res) { + g_assert (gs_utils_split_time_difference (last_checked, NULL, out_hours_ago, out_days_ago, NULL, NULL, NULL)); + } + + return res; +} + +static void +refresh_headerbar_updates_counter (GsUpdatesPage *self) +{ + guint new_updates_counter; + + new_updates_counter = _get_num_updates (self); + if (!gs_plugin_loader_get_allow_updates (self->plugin_loader) || + self->state == GS_UPDATES_PAGE_STATE_FAILED) + new_updates_counter = 0; + + if (new_updates_counter == self->updates_counter) + return; + + self->updates_counter = new_updates_counter; + g_object_notify (G_OBJECT (self), "counter"); +} + +static void +gs_updates_page_remove_last_checked_timeout (GsUpdatesPage *self) +{ + if (self->refresh_last_checked_id) { + g_source_remove (self->refresh_last_checked_id); + self->refresh_last_checked_id = 0; + } +} + +static void +gs_updates_page_refresh_last_checked (GsUpdatesPage *self); + +static gboolean +gs_updates_page_refresh_last_checked_cb (gpointer user_data) +{ + GsUpdatesPage *self = user_data; + gs_updates_page_refresh_last_checked (self); + return G_SOURCE_REMOVE; +} + +static void +gs_updates_page_refresh_last_checked (GsUpdatesPage *self) +{ + g_autofree gchar *checked_str = NULL; + gint hours_ago, days_ago; + checked_str = gs_updates_page_last_checked_time_string (self, &hours_ago, &days_ago); + if (checked_str != NULL) { + g_autofree gchar *last_checked = NULL; + guint interval; + + /* TRANSLATORS: This is the time when we last checked for updates */ + last_checked = g_strdup_printf (_("Last checked: %s"), checked_str); + gtk_label_set_label (self->uptodate_description, last_checked); + gtk_widget_set_visible (GTK_WIDGET (self->uptodate_description), TRUE); + + if (hours_ago < 1) + interval = 60; + else if (days_ago < 7) + interval = 60 * 60; + else + interval = 60 * 60 * 24; + + gs_updates_page_remove_last_checked_timeout (self); + + self->refresh_last_checked_id = g_timeout_add_seconds (interval, + gs_updates_page_refresh_last_checked_cb, self); + } else { + gtk_widget_set_visible (GTK_WIDGET (self->uptodate_description), FALSE); + } +} + +static void +gs_updates_page_update_ui_state (GsUpdatesPage *self) +{ + gboolean allow_mobile_refresh = TRUE; + + gs_updates_page_remove_last_checked_timeout (self); + + if (gs_shell_get_mode (self->shell) != GS_SHELL_MODE_UPDATES) + return; + + /* spinners */ + switch (self->state) { + case GS_UPDATES_PAGE_STATE_STARTUP: + case GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES: + case GS_UPDATES_PAGE_STATE_ACTION_REFRESH: + gtk_spinner_start (GTK_SPINNER (self->spinner_updates)); + break; + default: + gtk_spinner_stop (GTK_SPINNER (self->spinner_updates)); + gtk_spinner_stop (GTK_SPINNER (self->header_spinner_start)); + gtk_widget_hide (self->header_spinner_start); + break; + } + + /* headerbar refresh icon */ + switch (self->state) { + case GS_UPDATES_PAGE_STATE_ACTION_REFRESH: + case GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES: + gtk_button_set_icon_name (GTK_BUTTON (self->button_refresh), "media-playback-stop-symbolic"); + gtk_widget_show (self->button_refresh); + break; + case GS_UPDATES_PAGE_STATE_STARTUP: + case GS_UPDATES_PAGE_STATE_MANAGED: + gtk_widget_hide (self->button_refresh); + break; + case GS_UPDATES_PAGE_STATE_IDLE: + gtk_button_set_icon_name (GTK_BUTTON (self->button_refresh), "view-refresh-symbolic"); + if (self->result_flags != GS_UPDATES_PAGE_FLAG_NONE) { + gtk_widget_show (self->button_refresh); + } else { + if (gs_plugin_loader_get_network_metered (self->plugin_loader) && + !self->has_agreed_to_mobile_data) + allow_mobile_refresh = FALSE; + gtk_widget_set_visible (self->button_refresh, allow_mobile_refresh); + } + break; + case GS_UPDATES_PAGE_STATE_FAILED: + gtk_button_set_icon_name (GTK_BUTTON (self->button_refresh), "view-refresh-symbolic"); + gtk_widget_show (self->button_refresh); + break; + default: + g_assert_not_reached (); + break; + } + gtk_widget_set_sensitive (self->button_refresh, + gs_plugin_loader_get_network_available (self->plugin_loader)); + + /* stack */ + switch (self->state) { + case GS_UPDATES_PAGE_STATE_MANAGED: + gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "managed"); + break; + case GS_UPDATES_PAGE_STATE_FAILED: + gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "failed"); + break; + case GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES: + gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), + "spinner"); + break; + case GS_UPDATES_PAGE_STATE_ACTION_REFRESH: + gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "spinner"); + break; + case GS_UPDATES_PAGE_STATE_STARTUP: + case GS_UPDATES_PAGE_STATE_IDLE: + + /* if have updates, just show the view, otherwise show network */ + if (self->result_flags != GS_UPDATES_PAGE_FLAG_NONE) { + gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "view"); + break; + } + + /* check we have a "free" network connection */ + if (gs_plugin_loader_get_network_available (self->plugin_loader) && + !gs_plugin_loader_get_network_metered (self->plugin_loader)) { + gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "uptodate"); + break; + } + + /* expensive network connection */ + if (gs_plugin_loader_get_network_metered (self->plugin_loader)) { + if (self->has_agreed_to_mobile_data) { + gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "uptodate"); + } else { + gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "mobile"); + } + break; + } + + /* no network connection */ + gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "offline"); + break; + default: + g_assert_not_reached (); + break; + } + + /* any updates? */ + gtk_widget_set_visible (self->updates_box, + self->result_flags & GS_UPDATES_PAGE_FLAG_HAS_UPDATES); + + /* last checked label */ + if (g_strcmp0 (gtk_stack_get_visible_child_name (GTK_STACK (self->stack_updates)), "uptodate") == 0) + gs_updates_page_refresh_last_checked (self); + + /* update the counter in headerbar */ + refresh_headerbar_updates_counter (self); +} + +static void +gs_updates_page_set_state (GsUpdatesPage *self, GsUpdatesPageState state) +{ + g_debug ("setting state from %s to %s (has-update:%i, has-upgrade:%i)", + gs_updates_page_state_to_string (self->state), + gs_updates_page_state_to_string (state), + (self->result_flags & GS_UPDATES_PAGE_FLAG_HAS_UPDATES) > 0, + (self->result_flags & GS_UPDATES_PAGE_FLAG_HAS_UPGRADES) > 0); + self->state = state; + gs_updates_page_update_ui_state (self); +} + +static void +gs_updates_page_decrement_refresh_count (GsUpdatesPage *self) +{ + /* every job increments this */ + if (self->action_cnt == 0) { + g_warning ("action_cnt already zero!"); + return; + } + if (--self->action_cnt > 0) + return; + + /* all done */ + gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_IDLE); +} + +static void +gs_updates_page_network_available_notify_cb (GsPluginLoader *plugin_loader, + GParamSpec *pspec, + GsUpdatesPage *self) +{ + gs_updates_page_update_ui_state (self); +} + +static void +gs_updates_page_get_updates_cb (GsPluginLoader *plugin_loader, + GAsyncResult *res, + GsUpdatesPage *self) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GsAppList) list = NULL; + + self->cache_valid = TRUE; + + /* get the results */ + list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error); + if (list == NULL) { + gs_updates_page_clear_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPDATES); + if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("updates-shell: failed to get updates: %s", error->message); + adw_status_page_set_description (ADW_STATUS_PAGE (self->updates_failed_page), + error->message); + gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_FAILED); + refresh_headerbar_updates_counter (self); + return; + } + + /* add the results */ + for (guint i = 0; i < gs_app_list_length (list); i++) { + GsApp *app = gs_app_list_index (list, i); + GsUpdatesSectionKind section = _get_app_section (app); + gs_updates_section_add_app (self->sections[section], app); + } + + /* update the counter in headerbar */ + refresh_headerbar_updates_counter (self); + + /* no results */ + if (gs_app_list_length (list) == 0) { + g_debug ("updates-shell: no updates to show"); + gs_updates_page_clear_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPDATES); + } else { + gs_updates_page_set_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPDATES); + } + + /* only when both set */ + gs_updates_page_decrement_refresh_count (self); +} + +static void +gs_updates_page_get_upgrades_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object); + GsUpdatesPage *self = GS_UPDATES_PAGE (user_data); + 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) { + gs_updates_page_clear_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPGRADES); + if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning ("updates-shell: failed to get upgrades: %s", + error->message); + } + } else if (gs_app_list_length (list) == 0) { + g_debug ("updates-shell: no upgrades to show"); + gs_updates_page_clear_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPGRADES); + gtk_widget_set_visible (self->upgrade_banner, FALSE); + } else { + /* rely on the app list already being sorted with the + * chronologically newest release last */ + GsApp *app = gs_app_list_index (list, gs_app_list_length (list) - 1); + g_debug ("got upgrade %s", gs_app_get_id (app)); + gs_upgrade_banner_set_app (GS_UPGRADE_BANNER (self->upgrade_banner), app); + gs_updates_page_set_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPGRADES); + gtk_widget_set_visible (self->upgrade_banner, TRUE); + } + + /* only when both set */ + gs_updates_page_decrement_refresh_count (self); +} + +typedef struct { + GsApp *app; /* (owned) */ + GsUpdatesPage *self; /* (owned) */ +} GsPageHelper; + +static GsPageHelper * +gs_page_helper_new (GsUpdatesPage *self, + GsApp *app) +{ + GsPageHelper *helper; + helper = g_slice_new0 (GsPageHelper); + helper->self = g_object_ref (self); + helper->app = g_object_ref (app); + return helper; +} + +static void +gs_page_helper_free (GsPageHelper *helper) +{ + g_clear_object (&helper->app); + g_clear_object (&helper->self); + g_slice_free (GsPageHelper, helper); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsPageHelper, gs_page_helper_free); + +static void +gs_updates_page_refine_system_finished_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object); + g_autoptr(GsPageHelper) helper = user_data; + GsUpdatesPage *self = helper->self; + GsApp *app = helper->app; + g_autoptr(GError) error = NULL; + g_autoptr(GString) str = g_string_new (NULL); + + /* get result */ + 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_warning ("Failed to refine system: %s", error->message); + return; + } + + /* show or hide the end of life notification */ + if (gs_app_get_state (app) != GS_APP_STATE_UNAVAILABLE) { + gtk_info_bar_set_revealed (GTK_INFO_BAR (self->infobar_end_of_life), FALSE); + return; + } + + /* construct a sufficiently scary message */ + if (gs_app_get_name (app) != NULL && gs_app_get_version (app) != NULL) { + /* TRANSLATORS: the first %s is the distro name, e.g. 'Fedora' + * and the second %s is the distro version, e.g. '25' */ + g_string_append_printf (str, _("%s %s is no longer supported."), + gs_app_get_name (app), + gs_app_get_version (app)); + } else { + g_string_append (str, _("Your operating system is no longer supported.")); + } + g_string_append (str, " "); + + /* TRANSLATORS: EOL distros do not get important updates */ + g_string_append (str, _("This means that it does not receive security updates.")); + g_string_append (str, " "); + + /* TRANSLATORS: upgrade refers to a major update, e.g. Fedora 25 to 26 */ + g_string_append (str, _("It is recommended that you upgrade to a more recent version.")); + + gtk_label_set_label (GTK_LABEL (self->label_end_of_life), str->str); + gtk_info_bar_set_revealed (GTK_INFO_BAR (self->infobar_end_of_life), TRUE); + +} + +static void +gs_updates_page_get_system_finished_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + guint64 refine_flags; + GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object); + GsUpdatesPage *self = user_data; + GsPageHelper *helper; + g_autoptr(GsApp) app = NULL; + g_autoptr(GsPluginJob) plugin_job = NULL; + g_autoptr(GError) error = NULL; + + app = gs_plugin_loader_get_system_app_finish (plugin_loader, res, &error); + if (app == 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_warning ("Failed to get system: %s", error->message); + return; + } + + g_return_if_fail (GS_IS_UPDATES_PAGE (self)); + + refine_flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION; + + helper = gs_page_helper_new (self, app); + plugin_job = gs_plugin_job_refine_new_for_app (app, refine_flags); + gs_plugin_job_set_interactive (plugin_job, TRUE); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->cancellable, + gs_updates_page_refine_system_finished_cb, + helper); +} + +static void +gs_updates_page_load (GsUpdatesPage *self) +{ + guint64 refine_flags; + g_autoptr(GsApp) app = NULL; + g_autoptr(GsPluginJob) plugin_job = NULL; + + if (self->action_cnt > 0) + return; + + /* remove all existing apps */ + for (guint i = 0; i < GS_UPDATES_SECTION_KIND_LAST; i++) + gs_updates_section_remove_all (self->sections[i]); + + refine_flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION; + gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES); + self->action_cnt++; + plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES, + "interactive", TRUE, + "refine-flags", refine_flags, + "dedupe-flags", GS_APP_LIST_FILTER_FLAG_NONE, + NULL); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->cancellable, + (GAsyncReadyCallback) gs_updates_page_get_updates_cb, + self); + + /* get the system state */ + gs_plugin_loader_get_system_app_async (self->plugin_loader, self->cancellable, + gs_updates_page_get_system_finished_cb, self); + + /* don't refresh every each time */ + if ((self->result_flags & GS_UPDATES_PAGE_FLAG_HAS_UPGRADES) == 0) { + refine_flags |= GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPGRADE_REMOVED; + g_object_unref (plugin_job); + plugin_job = gs_plugin_job_list_distro_upgrades_new (GS_PLUGIN_LIST_DISTRO_UPGRADES_FLAGS_INTERACTIVE, + refine_flags); + gs_plugin_loader_job_process_async (self->plugin_loader, + plugin_job, + self->cancellable, + gs_updates_page_get_upgrades_cb, + self); + self->action_cnt++; + } +} + +static void +gs_updates_page_reload (GsPage *page) +{ + GsUpdatesPage *self = GS_UPDATES_PAGE (page); + + if (self->state == GS_UPDATES_PAGE_STATE_ACTION_REFRESH) { + g_debug ("ignoring reload as refresh is already in progress"); + return; + } + + gs_updates_page_invalidate (self); + gs_updates_page_load (self); +} + +static void +gs_updates_page_switch_to (GsPage *page) +{ + GsUpdatesPage *self = GS_UPDATES_PAGE (page); + + if (gs_shell_get_mode (self->shell) != GS_SHELL_MODE_UPDATES) { + g_warning ("Called switch_to(updates) when in mode %s", + gs_shell_get_mode_string (self->shell)); + return; + } + + gtk_widget_set_visible (self->button_refresh, TRUE); + + /* no need to refresh */ + if (self->cache_valid) { + gs_updates_page_update_ui_state (self); + return; + } + + if (self->state == GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES) { + gs_updates_page_update_ui_state (self); + return; + } + gs_updates_page_load (self); +} + +static void +gs_updates_page_switch_from (GsPage *page) +{ + GsUpdatesPage *self = GS_UPDATES_PAGE (page); + gs_updates_page_remove_last_checked_timeout (self); +} + +static void +gs_updates_page_refresh_cb (GsPluginLoader *plugin_loader, + GAsyncResult *res, + GsUpdatesPage *self) +{ + gboolean ret; + g_autoptr(GDateTime) now = NULL; + g_autoptr(GError) error = NULL; + + /* get the results */ + ret = gs_plugin_loader_job_action_finish (plugin_loader, res, &error); + if (!ret) { + /* user cancel */ + if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_IDLE); + return; + } + g_warning ("failed to refresh: %s", error->message); + adw_status_page_set_description (ADW_STATUS_PAGE (self->updates_failed_page), + error->message); + gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_FAILED); + return; + } + + /* update the last checked timestamp */ + now = g_date_time_new_now_local (); + g_settings_set (self->settings, "check-timestamp", "x", + g_date_time_to_unix (now)); + + /* get the new list */ + gs_updates_page_invalidate (self); + gs_page_switch_to (GS_PAGE (self)); + gs_page_scroll_up (GS_PAGE (self)); +} + +static void +gs_updates_page_get_new_updates (GsUpdatesPage *self) +{ + g_autoptr(GsPluginJob) plugin_job = NULL; + + /* force a check for updates and download */ + gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_ACTION_REFRESH); + + g_cancellable_cancel (self->cancellable_refresh); + g_clear_object (&self->cancellable_refresh); + self->cancellable_refresh = g_cancellable_new (); + + plugin_job = gs_plugin_job_refresh_metadata_new (1, + GS_PLUGIN_REFRESH_METADATA_FLAGS_INTERACTIVE); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->cancellable_refresh, + (GAsyncReadyCallback) gs_updates_page_refresh_cb, + self); +} + +static void +gs_updates_page_show_network_settings (GsUpdatesPage *self) +{ + g_autoptr(GError) error = NULL; + if (!g_spawn_command_line_async ("gnome-control-center wifi", &error)) + g_warning ("Failed to open the control center: %s", error->message); +} + +static void +gs_updates_page_refresh_confirm_cb (GtkDialog *dialog, + GtkResponseType response_type, + GsUpdatesPage *self) +{ + /* unmap the dialog */ + gtk_window_destroy (GTK_WINDOW (dialog)); + + switch (response_type) { + case GTK_RESPONSE_REJECT: + /* open the control center */ + gs_updates_page_show_network_settings (self); + break; + case GTK_RESPONSE_ACCEPT: + self->has_agreed_to_mobile_data = TRUE; + gs_updates_page_get_new_updates (self); + break; + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + break; + default: + g_assert_not_reached (); + } +} + +static void +gs_updates_page_button_network_settings_cb (GtkWidget *widget, + GsUpdatesPage *self) +{ + gs_updates_page_show_network_settings (self); +} + +static void +gs_updates_page_button_mobile_refresh_cb (GtkWidget *widget, + GsUpdatesPage *self) +{ + self->has_agreed_to_mobile_data = TRUE; + gs_updates_page_get_new_updates (self); +} + +static void +gs_updates_page_button_refresh_cb (GtkWidget *widget, + GsUpdatesPage *self) +{ + GtkWidget *dialog; + GtkWindow *parent_window = GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW)); + + /* cancel existing action? */ + if (self->state == GS_UPDATES_PAGE_STATE_ACTION_REFRESH) { + g_cancellable_cancel (self->cancellable_refresh); + g_clear_object (&self->cancellable_refresh); + return; + } + + /* check we have a "free" network connection */ + if (gs_plugin_loader_get_network_available (self->plugin_loader) && + !gs_plugin_loader_get_network_metered (self->plugin_loader)) { + gs_updates_page_get_new_updates (self); + + /* expensive network connection */ + } else if (gs_plugin_loader_get_network_available (self->plugin_loader) && + gs_plugin_loader_get_network_metered (self->plugin_loader)) { + if (self->has_agreed_to_mobile_data) { + gs_updates_page_get_new_updates (self); + return; + } + dialog = gtk_message_dialog_new (parent_window, + GTK_DIALOG_MODAL | + GTK_DIALOG_USE_HEADER_BAR | + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CANCEL, + /* TRANSLATORS: this is to explain that downloading updates may cost money */ + _("Charges May Apply")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + /* TRANSLATORS: we need network + * to do the updates check */ + _("Checking for updates while using mobile broadband could cause you to incur charges.")); + gtk_dialog_add_button (GTK_DIALOG (dialog), + /* TRANSLATORS: this is a link to the + * control-center network panel */ + _("Check _Anyway"), + GTK_RESPONSE_ACCEPT); + g_signal_connect (dialog, "response", + G_CALLBACK (gs_updates_page_refresh_confirm_cb), + self); + gs_shell_modal_dialog_present (self->shell, GTK_WINDOW (dialog)); + + /* no network connection */ + } else { + dialog = gtk_message_dialog_new (parent_window, + GTK_DIALOG_MODAL | + GTK_DIALOG_USE_HEADER_BAR | + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CANCEL, + /* TRANSLATORS: can't do updates check */ + _("No Network")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + /* TRANSLATORS: we need network + * to do the updates check */ + _("Internet access is required to check for updates.")); + gtk_dialog_add_button (GTK_DIALOG (dialog), + /* TRANSLATORS: this is a link to the + * control-center network panel */ + _("Network Settings"), + GTK_RESPONSE_REJECT); + g_signal_connect (dialog, "response", + G_CALLBACK (gs_updates_page_refresh_confirm_cb), + self); + gs_shell_modal_dialog_present (self->shell, GTK_WINDOW (dialog)); + } +} + +static void +gs_updates_page_pending_apps_changed_cb (GsPluginLoader *plugin_loader, + GsUpdatesPage *self) +{ + gs_updates_page_invalidate (self); +} + +static void +upgrade_download_finished_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source); + g_autoptr(GsPageHelper) helper = user_data; + g_autoptr(GError) error = NULL; + + 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)) + return; + g_warning ("failed to upgrade-download: %s", error->message); + } +} + +static void +gs_updates_page_upgrade_download_cb (GsUpgradeBanner *upgrade_banner, + GsUpdatesPage *self) +{ + GsApp *app; + GsPageHelper *helper; + g_autoptr(GsPluginJob) plugin_job = NULL; + + app = gs_upgrade_banner_get_app (upgrade_banner); + if (app == NULL) { + g_warning ("no upgrade available to download"); + return; + } + + helper = gs_page_helper_new (self, app); + + if (self->cancellable_upgrade_download != NULL) + g_object_unref (self->cancellable_upgrade_download); + self->cancellable_upgrade_download = g_cancellable_new (); + g_debug ("Starting upgrade download with cancellable %p", self->cancellable_upgrade_download); + plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD, + "interactive", TRUE, + "app", app, + NULL); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->cancellable_upgrade_download, + upgrade_download_finished_cb, + helper); +} + +static void +_cancel_trigger_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data) +{ + GsUpdatesPage *self = GS_UPDATES_PAGE (user_data); + g_autoptr(GError) error = NULL; + if (!gs_plugin_loader_job_action_finish (self->plugin_loader, res, &error)) { + g_warning ("failed to cancel trigger: %s", error->message); + return; + } +} + +static void +upgrade_reboot_failed_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GsUpdatesPage *self = (GsUpdatesPage *) user_data; + GsApp *app; + g_autoptr(GError) error = NULL; + g_autoptr(GsPluginJob) plugin_job = NULL; + + /* get result */ + if (gs_utils_invoke_reboot_finish (source, res, &error)) + return; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_debug ("Calling reboot had been cancelled"); + else if (error != NULL) + g_warning ("Calling reboot failed: %s", error->message); + + app = gs_upgrade_banner_get_app (GS_UPGRADE_BANNER (self->upgrade_banner)); + if (app == NULL) { + g_warning ("no upgrade to cancel"); + return; + } + + /* cancel trigger */ + plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE_CANCEL, + "app", app, + NULL); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->cancellable, + _cancel_trigger_failed_cb, + self); +} + +static void +upgrade_trigger_finished_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GsUpdatesPage *self = (GsUpdatesPage *) user_data; + g_autoptr(GError) error = NULL; + + /* get the results */ + if (!gs_plugin_loader_job_action_finish (self->plugin_loader, res, &error)) { + g_warning ("Failed to trigger offline update: %s", error->message); + return; + } + + /* trigger reboot */ + gs_utils_invoke_reboot_async (NULL, upgrade_reboot_failed_cb, self); +} + +static void +trigger_upgrade (GsUpdatesPage *self) +{ + GsApp *upgrade; + g_autoptr(GsPluginJob) plugin_job = NULL; + + upgrade = gs_upgrade_banner_get_app (GS_UPGRADE_BANNER (self->upgrade_banner)); + if (upgrade == NULL) { + g_warning ("no upgrade available to install"); + return; + } + + plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPGRADE_TRIGGER, + "interactive", TRUE, + "app", upgrade, + NULL); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->cancellable, + upgrade_trigger_finished_cb, + self); +} + +static void +gs_updates_page_upgrade_confirm_cb (GtkDialog *dialog, + GtkResponseType response_type, + GsUpdatesPage *self) +{ + /* unmap the dialog */ + gtk_window_destroy (GTK_WINDOW (dialog)); + + switch (response_type) { + case GTK_RESPONSE_ACCEPT: + g_debug ("agreed to upgrade removing apps"); + trigger_upgrade (self); + break; + case GTK_RESPONSE_CANCEL: + g_debug ("cancelled removal dialog"); + break; + case GTK_RESPONSE_DELETE_EVENT: + break; + default: + g_assert_not_reached (); + } +} + +static void +gs_updates_page_upgrade_install_cb (GsUpgradeBanner *upgrade_banner, + GsUpdatesPage *self) +{ + GsAppList *removals; + GsApp *upgrade; + GtkWidget *dialog; + guint cnt = 0; + guint i; + + upgrade = gs_upgrade_banner_get_app (GS_UPGRADE_BANNER (self->upgrade_banner)); + if (upgrade == NULL) { + g_warning ("no upgrade available to install"); + return; + } + + /* count the removals */ + removals = gs_app_get_related (upgrade); + for (i = 0; i < gs_app_list_length (removals); i++) { + GsApp *app = gs_app_list_index (removals, i); + if (gs_app_get_state (app) != GS_APP_STATE_UNAVAILABLE) + continue; + cnt++; + } + + if (cnt == 0) { + /* no need for a removal confirmation dialog */ + trigger_upgrade (self); + return; + } + + dialog = gs_removal_dialog_new (); + g_signal_connect (dialog, "response", + G_CALLBACK (gs_updates_page_upgrade_confirm_cb), + self); + gs_removal_dialog_show_upgrade_removals (GS_REMOVAL_DIALOG (dialog), + upgrade); + gs_shell_modal_dialog_present (self->shell, GTK_WINDOW (dialog)); +} + +static void +gs_updates_page_invalidate_downloaded_upgrade (GsUpdatesPage *self) +{ + GsApp *app; + app = gs_upgrade_banner_get_app (GS_UPGRADE_BANNER (self->upgrade_banner)); + if (app == NULL) + return; + if (gs_app_get_state (app) != GS_APP_STATE_UPDATABLE) + return; + gs_app_set_state (app, GS_APP_STATE_AVAILABLE); + g_debug ("resetting %s to AVAILABLE as the updates have changed", + gs_app_get_id (app)); +} + +static gboolean +gs_shell_update_are_updates_in_progress (GsUpdatesPage *self) +{ + g_autoptr(GsAppList) list = _get_all_apps (self); + for (guint i = 0; i < gs_app_list_length (list); i++) { + GsApp *app = gs_app_list_index (list, i); + switch (gs_app_get_state (app)) { + case GS_APP_STATE_INSTALLING: + case GS_APP_STATE_REMOVING: + return TRUE; + break; + default: + break; + } + } + return FALSE; +} + +static void +gs_updates_page_changed_cb (GsPluginLoader *plugin_loader, + GsUpdatesPage *self) +{ + /* if we do a live update and the upgrade is waiting to be deployed + * then make sure all new packages are downloaded */ + gs_updates_page_invalidate_downloaded_upgrade (self); + + /* check to see if any apps in the app list are in a processing state */ + if (gs_shell_update_are_updates_in_progress (self)) { + g_debug ("ignoring updates-changed as updates in progress"); + return; + } + + /* refresh updates list */ + gs_updates_page_reload (GS_PAGE (self)); +} + +static void +gs_updates_page_status_changed_cb (GsPluginLoader *plugin_loader, + GsApp *app, + GsPluginStatus status, + GsUpdatesPage *self) +{ + switch (status) { + case GS_PLUGIN_STATUS_INSTALLING: + case GS_PLUGIN_STATUS_REMOVING: + if (app == NULL || + (gs_app_get_kind (app) != AS_COMPONENT_KIND_OPERATING_SYSTEM && + gs_app_get_id (app) != NULL)) { + /* if we do a install or remove then make sure all new + * packages are downloaded */ + gs_updates_page_invalidate_downloaded_upgrade (self); + } + break; + default: + break; + } + + gs_updates_page_update_ui_state (self); +} + +static void +gs_updates_page_allow_updates_notify_cb (GsPluginLoader *plugin_loader, + GParamSpec *pspec, + GsUpdatesPage *self) +{ + if (!gs_plugin_loader_get_allow_updates (plugin_loader)) { + gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_MANAGED); + return; + } + gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_IDLE); +} + +static void +gs_updates_page_upgrade_cancel_cb (GsUpgradeBanner *upgrade_banner, + GsUpdatesPage *self) +{ + g_debug ("Cancelling upgrade download with %p", self->cancellable_upgrade_download); + g_cancellable_cancel (self->cancellable_upgrade_download); +} + +static gboolean +gs_updates_page_setup (GsPage *page, + GsShell *shell, + GsPluginLoader *plugin_loader, + GCancellable *cancellable, + GError **error) +{ + GsUpdatesPage *self = GS_UPDATES_PAGE (page); + + g_return_val_if_fail (GS_IS_UPDATES_PAGE (self), TRUE); + + for (guint i = 0; i < GS_UPDATES_SECTION_KIND_LAST; i++) { + self->sections[i] = gs_updates_section_new (i, plugin_loader, page); + gs_updates_section_set_size_groups (self->sections[i], + self->sizegroup_name, + self->sizegroup_button_label, + self->sizegroup_button_image, + self->sizegroup_header); + gtk_widget_set_vexpand (GTK_WIDGET (self->sections[i]), FALSE); + g_object_bind_property (G_OBJECT (self), "is-narrow", + self->sections[i], "is-narrow", + G_BINDING_SYNC_CREATE); + gtk_box_append (GTK_BOX (self->updates_box), GTK_WIDGET (self->sections[i])); + } + + self->shell = shell; + + self->plugin_loader = g_object_ref (plugin_loader); + g_signal_connect (self->plugin_loader, "pending-apps-changed", + G_CALLBACK (gs_updates_page_pending_apps_changed_cb), + self); + g_signal_connect (self->plugin_loader, "status-changed", + G_CALLBACK (gs_updates_page_status_changed_cb), + self); + g_signal_connect (self->plugin_loader, "updates-changed", + G_CALLBACK (gs_updates_page_changed_cb), + self); + g_signal_connect_object (self->plugin_loader, "notify::allow-updates", + G_CALLBACK (gs_updates_page_allow_updates_notify_cb), + self, 0); + g_signal_connect_object (self->plugin_loader, "notify::network-available", + G_CALLBACK (gs_updates_page_network_available_notify_cb), + self, 0); + self->cancellable = g_object_ref (cancellable); + + /* setup system upgrades */ + g_signal_connect (self->upgrade_banner, "download-clicked", + G_CALLBACK (gs_updates_page_upgrade_download_cb), self); + g_signal_connect (self->upgrade_banner, "install-clicked", + G_CALLBACK (gs_updates_page_upgrade_install_cb), self); + g_signal_connect (self->upgrade_banner, "cancel-clicked", + G_CALLBACK (gs_updates_page_upgrade_cancel_cb), self); + + self->header_start_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_set_visible (self->header_start_box, TRUE); + gs_page_set_header_start_widget (GS_PAGE (self), self->header_start_box); + + self->header_spinner_start = gtk_spinner_new (); + gtk_box_prepend (GTK_BOX (self->header_start_box), self->header_spinner_start); + + /* setup update details window */ + self->button_refresh = gtk_button_new_from_icon_name ("view-refresh-symbolic"); + gtk_accessible_update_property (GTK_ACCESSIBLE (self->button_refresh), + GTK_ACCESSIBLE_PROPERTY_LABEL, _("Check for updates"), + -1); + gtk_box_prepend (GTK_BOX (self->header_start_box), self->button_refresh); + g_signal_connect (self->button_refresh, "clicked", + G_CALLBACK (gs_updates_page_button_refresh_cb), + self); + + g_signal_connect (self->button_updates_mobile, "clicked", + G_CALLBACK (gs_updates_page_button_mobile_refresh_cb), + self); + g_signal_connect (self->button_updates_offline, "clicked", + G_CALLBACK (gs_updates_page_button_network_settings_cb), + self); + + /* set initial state */ + if (!gs_plugin_loader_get_allow_updates (self->plugin_loader)) + self->state = GS_UPDATES_PAGE_STATE_MANAGED; + return TRUE; +} + +static void +gs_updates_page_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsUpdatesPage *self = GS_UPDATES_PAGE (object); + + switch ((GsUpdatesPageProperty) prop_id) { + case PROP_IS_NARROW: + g_value_set_boolean (value, gs_updates_page_get_is_narrow (self)); + break; + case PROP_VADJUSTMENT: + g_value_set_object (value, gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->scrolledwindow_updates))); + break; + case PROP_TITLE: + g_value_set_string (value, C_("Apps to be updated", "Updates")); + break; + case PROP_COUNTER: + g_value_set_uint (value, self->updates_counter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_updates_page_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsUpdatesPage *self = GS_UPDATES_PAGE (object); + + switch ((GsUpdatesPageProperty) prop_id) { + case PROP_IS_NARROW: + gs_updates_page_set_is_narrow (self, g_value_get_boolean (value)); + break; + case PROP_VADJUSTMENT: + case PROP_TITLE: + case PROP_COUNTER: + /* Read only */ + g_assert_not_reached (); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_updates_page_dispose (GObject *object) +{ + GsUpdatesPage *self = GS_UPDATES_PAGE (object); + + gs_updates_page_remove_last_checked_timeout (self); + + g_cancellable_cancel (self->cancellable_refresh); + g_clear_object (&self->cancellable_refresh); + g_cancellable_cancel (self->cancellable_upgrade_download); + g_clear_object (&self->cancellable_upgrade_download); + + for (guint i = 0; i < GS_UPDATES_SECTION_KIND_LAST; i++) { + if (self->sections[i] != NULL) { + gtk_widget_unparent (GTK_WIDGET (self->sections[i])); + self->sections[i] = NULL; + } + } + + g_clear_object (&self->plugin_loader); + g_clear_object (&self->cancellable); + g_clear_object (&self->settings); + g_clear_object (&self->desktop_settings); + + g_clear_object (&self->sizegroup_name); + g_clear_object (&self->sizegroup_button_label); + g_clear_object (&self->sizegroup_button_image); + g_clear_object (&self->sizegroup_header); + + G_OBJECT_CLASS (gs_updates_page_parent_class)->dispose (object); +} + +static void +gs_updates_page_class_init (GsUpdatesPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsPageClass *page_class = GS_PAGE_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = gs_updates_page_get_property; + object_class->set_property = gs_updates_page_set_property; + object_class->dispose = gs_updates_page_dispose; + + page_class->switch_to = gs_updates_page_switch_to; + page_class->switch_from = gs_updates_page_switch_from; + page_class->reload = gs_updates_page_reload; + page_class->setup = gs_updates_page_setup; + + /** + * GsUpdatesPage:is-narrow: + * + * Whether the page is in narrow mode. + * + * In narrow mode, the page will take up less horizontal space, doing so + * by e.g. using icons rather than labels in buttons. This is needed to + * keep the UI useable on small form-factors like smartphones. + * + * Since: 41 + */ + obj_props[PROP_IS_NARROW] = + g_param_spec_boolean ("is-narrow", NULL, NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props); + + g_object_class_override_property (object_class, PROP_VADJUSTMENT, "vadjustment"); + g_object_class_override_property (object_class, PROP_TITLE, "title"); + g_object_class_override_property (object_class, PROP_COUNTER, "counter"); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-updates-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, updates_box); + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, button_updates_mobile); + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, button_updates_offline); + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, updates_failed_page); + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, uptodate_description); + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, scrolledwindow_updates); + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, spinner_updates); + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, stack_updates); + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, upgrade_banner); + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, infobar_end_of_life); + gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, label_end_of_life); +} + +static void +gs_updates_page_init (GsUpdatesPage *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->state = GS_UPDATES_PAGE_STATE_STARTUP; + self->settings = g_settings_new ("org.gnome.software"); + self->desktop_settings = g_settings_new ("org.gnome.desktop.interface"); + + self->sizegroup_name = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->sizegroup_button_label = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->sizegroup_button_image = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->sizegroup_header = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); + +} + +/** + * gs_updates_page_get_is_narrow: + * @self: a #GsUpdatesPage + * + * Get the value of #GsUpdatesPage:is-narrow. + * + * Returns: %TRUE if the page is in narrow mode, %FALSE otherwise + * + * Since: 41 + */ +gboolean +gs_updates_page_get_is_narrow (GsUpdatesPage *self) +{ + g_return_val_if_fail (GS_IS_UPDATES_PAGE (self), FALSE); + + return self->is_narrow; +} + +/** + * gs_updates_page_set_is_narrow: + * @self: a #GsUpdatesPage + * @is_narrow: %TRUE to set the page in narrow mode, %FALSE otherwise + * + * Set the value of #GsUpdatesPage:is-narrow. + * + * Since: 41 + */ +void +gs_updates_page_set_is_narrow (GsUpdatesPage *self, gboolean is_narrow) +{ + g_return_if_fail (GS_IS_UPDATES_PAGE (self)); + + is_narrow = !!is_narrow; + + if (self->is_narrow == is_narrow) + return; + + self->is_narrow = is_narrow; + g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_IS_NARROW]); +} + +GsUpdatesPage * +gs_updates_page_new (void) +{ + return GS_UPDATES_PAGE (g_object_new (GS_TYPE_UPDATES_PAGE, NULL)); +} |