diff --git a/src/Utils/SystemUpgrade.vala b/src/Utils/SystemUpgrade.vala new file mode 100644 index 000000000..e4731515f --- /dev/null +++ b/src/Utils/SystemUpgrade.vala @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022 elementary, Inc. (https://elementary.io) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authored by: Marius Meisenzahl + */ + +public class About.SystemUpgrade : Object { + public void restart () { + try { + system_instance.reboot (false); + } catch (GLib.Error e) { + critical (e.message); + } + } + + public bool system_upgrade_available { + get { + if (system_upgrade_instance == null) { + return false; + } + + return system_upgrade_instance.system_upgrade_available; + } + } + + public void start_upgrade () { + if (system_upgrade_instance != null) { + system_upgrade_instance.start_upgrade (); + } + } + + public signal void system_upgrade_progress (int percentage); + + public signal void system_upgrade_finished (); + + construct { + get_system_instance (); + get_system_upgrade_instance (); + } + + [DBus (name = "org.freedesktop.login1.Manager")] + interface SystemInterface : Object { + public abstract void reboot (bool interactive) throws GLib.Error; + } + + private SystemInterface? system_instance; + private void get_system_instance () { + if (system_instance == null) { + try { + system_instance = Bus.get_proxy_sync ( + BusType.SYSTEM, + "org.freedesktop.login1", + "/org/freedesktop/login1" + ); + } catch (GLib.Error e) { + warning ("%s", e.message); + } + } + } + + [DBus (name = "io.elementary.SystemUpgrade")] + interface SystemUpgradeInterface : Object { + public abstract bool system_upgrade_available { get; } + + public abstract void start_upgrade (); + + public signal void system_upgrade_progress (int percentage); + + public signal void system_upgrade_finished (); + } + + private SystemUpgradeInterface? system_upgrade_instance; + private void get_system_upgrade_instance () { + if (system_upgrade_instance == null) { + try { + system_upgrade_instance = Bus.get_proxy_sync ( + BusType.SESSION, + "io.elementary.settings-daemon", + "/io/elementary/settings_daemon" + ); + + system_upgrade_instance.system_upgrade_progress.connect ((percentage) => { system_upgrade_progress (percentage); }); + + system_upgrade_instance.system_upgrade_finished.connect (() => { system_upgrade_finished (); }); + } catch (GLib.Error e) { + warning ("%s", e.message); + } + } + } +} diff --git a/src/Views/OperatingSystemView.vala b/src/Views/OperatingSystemView.vala index 1d9ac1946..5ad9ebec7 100644 --- a/src/Views/OperatingSystemView.vala +++ b/src/Views/OperatingSystemView.vala @@ -19,11 +19,15 @@ */ public class About.OperatingSystemView : Gtk.Grid { + private SystemUpgrade system_upgrade; + private string support_url; private Gtk.Grid software_grid; construct { + system_upgrade = new SystemUpgrade (); + var style_provider = new Gtk.CssProvider (); style_provider.load_from_resource ("io/elementary/switchboard/system/OperatingSystemView.css"); @@ -112,6 +116,14 @@ public class About.OperatingSystemView : Gtk.Grid { margin_top = 12 }; + var upgrade_button = new Gtk.Button.with_label (_("Upgrade system")); + upgrade_button.get_style_context ().add_class (Granite.STYLE_CLASS_ACCENT); + upgrade_button.clicked.connect (show_upgrade_dialog); + + var upgrade_revealer = new Gtk.Revealer (); + upgrade_revealer.add (upgrade_button); + upgrade_revealer.reveal_child = system_upgrade.system_upgrade_available; + var bug_button = new Gtk.Button.with_label (_("Send Feedback")); Gtk.Button? update_button = null; @@ -131,6 +143,7 @@ public class About.OperatingSystemView : Gtk.Grid { spacing = 6 }; button_grid.add (settings_restore_button); + button_grid.add (upgrade_revealer); button_grid.add (bug_button); if (update_button != null) { button_grid.add (update_button); @@ -301,4 +314,142 @@ public class About.OperatingSystemView : Gtk.Grid { settings.apply (); GLib.Settings.sync (); } + + private const int RESTART_TIMEOUT = 30; + + enum State { + OVERVIEW, + WARNING, + PROGRESS, + SUCCESS, + RESTART + } + + private void show_upgrade_dialog () { + int seconds_remaining = RESTART_TIMEOUT; + + var message_dialog = new Granite.MessageDialog.with_image_from_icon_name ( + "elementary OS 7", + "To setup the installation of elementary OS 7, click Continue.", + "distributor-logo", + Gtk.ButtonsType.CANCEL + ) { + badge_icon = new ThemedIcon ("system-software-update"), + transient_for = (Gtk.Window) get_toplevel (), + modal = true + }; + + var suggested_button = new Gtk.Button.with_label (_("Continue")); + suggested_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION); + message_dialog.add_action_widget (suggested_button, Gtk.ResponseType.ACCEPT); + + var stack = new Gtk.Stack (); + stack.set_transition_type (Gtk.StackTransitionType.SLIDE_LEFT_RIGHT); + + var state = State.OVERVIEW; + + var overview_view = new Gtk.Label (""); + stack.add (overview_view); + + var warning_view = new Gtk.Label ( + "· " + _("Before you upgrade, we recommend that you back up your data") + "\n" + + "· " + _("To prevent data loss close all open applications and documents") + ) { + max_width_chars = 60, + wrap = true, + xalign = 0, + use_markup = true + }; + stack.add (warning_view); + + var progress_view = new Gtk.ProgressBar (); + progress_view.pulse (); + stack.add (progress_view); + + var success_view = new Gtk.Label ("") { + max_width_chars = 60, + wrap = true, + xalign = 0, + use_markup = true + }; + stack.add (success_view); + + message_dialog.custom_bin.add (stack); + + message_dialog.show_all (); + message_dialog.response.connect ((response_id) => { + if (response_id == Gtk.ResponseType.ACCEPT) { + state = state + 1; + } else if (response_id == Gtk.ResponseType.CANCEL) { + message_dialog.destroy (); + } + + switch (state) { + case State.WARNING: + message_dialog.badge_icon = new ThemedIcon ("dialog-warning"); + message_dialog.secondary_text = _("Make sure you are ready to upgrade."); + suggested_button.label = _("Upgrade"); + stack.set_visible_child (warning_view); + break; + case State.PROGRESS: + message_dialog.badge_icon = new ThemedIcon ("system-software-update"); + message_dialog.secondary_text = _("The upgrade is being prepared. Please do not shut down your device."); + suggested_button.visible = false; + stack.set_visible_child (progress_view); + + system_upgrade.system_upgrade_finished.connect (() => { + suggested_button.activate (); + }); + + system_upgrade.system_upgrade_progress.connect ((percentage) => { + progress_view.fraction = percentage / 100.0; + }); + + system_upgrade.start_upgrade (); + + break; + case State.SUCCESS: + message_dialog.badge_icon = new ThemedIcon ("process-completed"); + message_dialog.secondary_text = _("Upgrade was successfully prepared."); + success_view.label = get_restart_text (seconds_remaining); + suggested_button.label = _("Restart"); + suggested_button.visible = true; + stack.set_visible_child (success_view); + + Timeout.add_seconds (RESTART_TIMEOUT, () => { + suggested_button.activate (); + + return GLib.Source.REMOVE; + }); + + Timeout.add_seconds (1, () => { + seconds_remaining = seconds_remaining - 1; + success_view.label = get_restart_text (seconds_remaining); + + if (seconds_remaining == 0) { + return Source.REMOVE; + } + + return Source.CONTINUE; + }); + + break; + case State.RESTART: + message_dialog.destroy (); + system_upgrade.restart (); + break; + } + }); + } + + private string get_restart_text (int seconds_remaining) { + return "%s".printf ( + ngettext ( + "Your device will automatically restart in %i second.", + "Your device will automatically restart in %i seconds.", + seconds_remaining + ).printf (seconds_remaining) + " " + + _("Your device may restart more than once during installation.") + ); + } } diff --git a/src/meson.build b/src/meson.build index 818af5a4f..ad2d6d5cf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,6 +3,7 @@ plug_files = files( 'Interfaces/FirmwareClient.vala', 'Interfaces/LoginManager.vala', 'Utils/ARMPartDecoder.vala', + 'Utils/SystemUpgrade.vala', 'Views/FirmwareReleaseView.vala', 'Views/FirmwareView.vala', 'Views/HardwareView.vala',