Skip to content
105 changes: 105 additions & 0 deletions src/Utils/SystemUpgrade.vala
Original file line number Diff line number Diff line change
@@ -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 <mariusmeisenzahl@gmail.com>
*/

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);
}
}
}
}
151 changes: 151 additions & 0 deletions src/Views/OperatingSystemView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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 "<span font-features='tnum'>%s</span>".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.")
);
}
}
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down