[gnome-shell-extensions/wip/rstrode/heads-up-display: 5/62] Add updates-dialog extension
- From: Ray Strode <halfline src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell-extensions/wip/rstrode/heads-up-display: 5/62] Add updates-dialog extension
- Date: Thu, 26 Aug 2021 19:31:30 +0000 (UTC)
commit f2d94ea367bfdcdcc9ed0346feab328c9abc5099
Author: Florian Müllner <fmuellner gnome org>
Date: Fri Mar 4 17:07:21 2016 +0100
Add updates-dialog extension
extensions/updates-dialog/extension.js | 503 +++++++++++++++++++++
extensions/updates-dialog/meson.build | 7 +
extensions/updates-dialog/metadata.json.in | 10 +
...ome.shell.extensions.updates-dialog.gschema.xml | 30 ++
extensions/updates-dialog/stylesheet.css | 1 +
meson.build | 1 +
po/POTFILES.in | 2 +
7 files changed, 554 insertions(+)
---
diff --git a/extensions/updates-dialog/extension.js b/extensions/updates-dialog/extension.js
new file mode 100644
index 0000000..59f6dcf
--- /dev/null
+++ b/extensions/updates-dialog/extension.js
@@ -0,0 +1,503 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * 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 2 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* exported enable disable */
+
+const { Clutter, Gio, GLib, PackageKitGlib: PkgKit, Pango, Polkit, St } = imports.gi;
+const Signals = imports.signals;
+
+const EndSessionDialog = imports.ui.endSessionDialog;
+const ModalDialog = imports.ui.modalDialog;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const Me = ExtensionUtils.getCurrentExtension();
+
+const PkIface = '<node> \
+<interface name="org.freedesktop.PackageKit"> \
+ <method name="CreateTransaction"> \
+ <arg type="o" name="object_path" direction="out"/> \
+ </method> \
+ <signal name="UpdatesChanged"/> \
+</interface> \
+</node>';
+
+const PkOfflineIface = '<node> \
+<interface name="org.freedesktop.PackageKit.Offline"> \
+ <property name="UpdatePrepared" type="b" access="read"/> \
+ <property name="TriggerAction" type="s" access="read"/> \
+ <method name="Trigger"> \
+ <arg type="s" name="action" direction="in"/> \
+ </method> \
+ <method name="Cancel"/> \
+</interface> \
+</node>';
+
+const PkTransactionIface = '<node> \
+<interface name="org.freedesktop.PackageKit.Transaction"> \
+ <method name="SetHints"> \
+ <arg type="as" name="hints" direction="in"/> \
+ </method> \
+ <method name="GetUpdates"> \
+ <arg type="t" name="filter" direction="in"/> \
+ </method> \
+ <method name="UpdatePackages"> \
+ <arg type="t" name="transaction_flags" direction="in"/> \
+ <arg type="as" name="package_ids" direction="in"/> \
+ </method> \
+ <signal name="Package"> \
+ <arg type="u" name="info" direction="out"/> \
+ <arg type="s" name="package_id" direction="out"/> \
+ <arg type="s" name="summary" direction="out"/> \
+ </signal> \
+ <signal name="Finished"> \
+ <arg type="u" name="exit" direction="out"/> \
+ <arg type="u" name="runtime" direction="out"/> \
+ </signal> \
+</interface> \
+</node>';
+
+const LoginManagerIface = '<node> \
+<interface name="org.freedesktop.login1.Manager"> \
+<method name="Reboot"> \
+ <arg type="b" direction="in"/> \
+</method> \
+<method name="CanReboot"> \
+ <arg type="s" direction="out"/> \
+</method> \
+</interface> \
+</node>';
+
+const PkProxy = Gio.DBusProxy.makeProxyWrapper(PkIface);
+const PkOfflineProxy = Gio.DBusProxy.makeProxyWrapper(PkOfflineIface);
+const PkTransactionProxy = Gio.DBusProxy.makeProxyWrapper(PkTransactionIface);
+const LoginManagerProxy = Gio.DBusProxy.makeProxyWrapper(LoginManagerIface);
+
+let pkProxy = null;
+let pkOfflineProxy = null;
+let loginManagerProxy = null;
+let updatesDialog = null;
+let extensionSettings = null;
+let cancellable = null;
+
+let updatesCheckInProgress = false;
+let updatesCheckRequested = false;
+let securityUpdates = [];
+
+function getDetailText(period) {
+ let text = _('Important security updates need to be installed.\n');
+ if (period < 60) {
+ text += ngettext(
+ 'You can close this dialog and get %d minute to finish your work.',
+ 'You can close this dialog and get %d minutes to finish your work.',
+ period)
+ .format(period);
+ } else {
+ text += ngettext(
+ 'You can close this dialog and get %d hour to finish your work.',
+ 'You can close this dialog and get %d hours to finish your work.',
+ Math.floor(period / 60))
+ .format(Math.floor(period / 60));
+ }
+ return text;
+}
+
+const UpdatesDialog = class extends ModalDialog.ModalDialog {
+ constructor(settings) {
+ super({
+ styleClass: 'end-session-dialog',
+ destroyOnClose: false
+ });
+
+ this._gracePeriod = settings.get_uint('grace-period');
+ this._gracePeriod = Math.min(Math.max(10, this._gracePeriod), 24 * 60);
+ this._lastWarningPeriod = settings.get_uint('last-warning-period');
+ this._lastWarningPeriod = Math.min(
+ Math.max(1, this._lastWarningPeriod),
+ this._gracePeriod - 1);
+ this._lastWarnings = settings.get_uint('last-warnings');
+ this._lastWarnings = Math.min(
+ Math.max(1, this._lastWarnings),
+ Math.floor((this._gracePeriod - 1) / this._lastWarningPeriod));
+
+ let messageLayout = new St.BoxLayout({
+ vertical: true,
+ style_class: 'end-session-dialog-layout'
+ });
+ this.contentLayout.add(messageLayout, {
+ x_fill: true,
+ y_fill: true,
+ y_expand: true
+ });
+
+ let subjectLabel = new St.Label({
+ style_class: 'end-session-dialog-subject',
+ style: 'padding-bottom: 1em;',
+ text: _('Important security updates')
+ });
+ messageLayout.add(subjectLabel, {
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.START,
+ y_align: St.Align.START
+ });
+
+ this._detailLabel = new St.Label({
+ style_class: 'end-session-dialog-description',
+ style: 'padding-bottom: 0em;',
+ text: getDetailText(this._gracePeriod)
+ });
+ this._detailLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
+ this._detailLabel.clutter_text.line_wrap = true;
+
+ messageLayout.add(this._detailLabel, {
+ y_fill: true,
+ y_align: St.Align.START
+ });
+
+ let buttons = [{
+ action: this.close.bind(this),
+ label: _('Close'),
+ key: Clutter.Escape
+ }, {
+ action: this._done.bind(this),
+ label: _('Restart & Install')
+ }];
+
+ this.setButtons(buttons);
+
+ this._openTimeoutId = 0;
+ this.connect('destroy', this._clearOpenTimeout.bind(this));
+
+ this._startTimer();
+ }
+
+ _clearOpenTimeout() {
+ if (this._openTimeoutId > 0) {
+ GLib.source_remove(this._openTimeoutId);
+ this._openTimeoutId = 0;
+ }
+ }
+
+ tryOpen() {
+ if (this._openTimeoutId > 0 || this.open())
+ return;
+
+ this._openTimeoutId = GLib.timeout_add_seconds(
+ GLib.PRIORITY_DEFAULT, 1, () => {
+ if (!this.open())
+ return GLib.SOURCE_CONTINUE;
+
+ this._clearOpenTimeout();
+ return GLib.SOURCE_REMOVE;
+ });
+ }
+
+ _startTimer() {
+ this._secondsLeft = this._gracePeriod * 60;
+
+ this._timerId = GLib.timeout_add_seconds(
+ GLib.PRIORITY_DEFAULT, 1, () => {
+ this._secondsLeft -= 1;
+ let minutesLeft = this._secondsLeft / 60;
+ let periodLeft = Math.floor(minutesLeft);
+
+ if (this._secondsLeft == 60 ||
+ (periodLeft > 0 && periodLeft <= this._lastWarningPeriod * this._lastWarnings &&
+ minutesLeft % this._lastWarningPeriod == 0)) {
+ this.tryOpen();
+ this._detailLabel.text = getDetailText(periodLeft);
+ }
+
+ if (this._secondsLeft > 0) {
+ if (this._secondsLeft < 60) {
+ let seconds = EndSessionDialog._roundSecondsToInterval(
+ this._gracePeriod * 60, this._secondsLeft, 10);
+ this._detailLabel.text =
+ _('Important security updates need to be installed now.\n') +
+ ngettext(
+ 'This computer will restart in %d second.',
+ 'This computer will restart in %d seconds.',
+ seconds).format(seconds);
+ }
+ return GLib.SOURCE_CONTINUE;
+ }
+
+ this._done();
+ return GLib.SOURCE_REMOVE;
+ });
+ this.connect('destroy', () => {
+ if (this._timerId > 0) {
+ GLib.source_remove(this._timerId);
+ this._timerId = 0;
+ }
+ });
+ }
+
+ _done() {
+ this.emit('done');
+ this.destroy();
+ }
+
+ getState() {
+ return [this._gracePeriod, this._lastWarningPeriod, this._lastWarnings, this._secondsLeft];
+ }
+
+ setState(state) {
+ [this._gracePeriod, this._lastWarningPeriod, this._lastWarnings, this._secondsLeft] = state;
+ }
+};
+Signals.addSignalMethods(UpdatesDialog.prototype);
+
+function showDialog() {
+ if (updatesDialog)
+ return;
+
+ updatesDialog = new UpdatesDialog(extensionSettings);
+ updatesDialog.tryOpen();
+ updatesDialog.connect('destroy', () => updatesDialog = null);
+ updatesDialog.connect('done', () => {
+ if (pkOfflineProxy.TriggerAction == 'power-off' ||
+ pkOfflineProxy.TriggerAction == 'reboot') {
+ loginManagerProxy.RebootRemote(false);
+ } else {
+ pkOfflineProxy.TriggerRemote('reboot', (result, error) => {
+ if (!error)
+ loginManagerProxy.RebootRemote(false);
+ else
+ log('Failed to trigger offline update: %s'.format(error.message));
+ });
+ }
+ });
+}
+
+function cancelDialog(save) {
+ if (!updatesDialog)
+ return;
+
+ if (save) {
+ let state = GLib.Variant.new('(uuuu)', updatesDialog.getState());
+ global.set_runtime_state(Me.uuid, state);
+ }
+ updatesDialog.destroy();
+}
+
+function restoreExistingState() {
+ let state = global.get_runtime_state('(uuuu)', Me.uuid);
+ if (state === null)
+ return false;
+
+ global.set_runtime_state(Me.uuid, null);
+ showDialog();
+ updatesDialog.setState(state.deep_unpack());
+ return true;
+}
+
+function syncState() {
+ if (!pkOfflineProxy || !loginManagerProxy)
+ return;
+
+ if (restoreExistingState())
+ return;
+
+ if (!updatesCheckInProgress &&
+ securityUpdates.length > 0 &&
+ pkOfflineProxy.UpdatePrepared)
+ showDialog();
+ else
+ cancelDialog();
+}
+
+function doPkTransaction(callback) {
+ if (!pkProxy)
+ return;
+
+ pkProxy.CreateTransactionRemote((result, error) => {
+ if (error) {
+ log('Error creating PackageKit transaction: %s'.format(error.message));
+ checkUpdatesDone();
+ return;
+ }
+
+ new PkTransactionProxy(Gio.DBus.system,
+ 'org.freedesktop.PackageKit',
+ String(result),
+ (proxy, error) => {
+ if (!error) {
+ proxy.SetHintsRemote(
+ ['background=true', 'interactive=false'],
+ (result, error) => {
+ if (error) {
+ log('Error connecting to PackageKit: %s'.format(error.message));
+ checkUpdatesDone();
+ return;
+ }
+ callback(proxy);
+ });
+ } else {
+ log('Error connecting to PackageKit: %s'.format(error.message));
+ }
+ });
+ });
+}
+
+function pkUpdatePackages(proxy) {
+ proxy.connectSignal('Finished', (p, e, params) => {
+ let [exit, runtime_] = params;
+
+ if (exit == PkgKit.ExitEnum.CANCELLED_PRIORITY) {
+ // try again
+ checkUpdates();
+ } else if (exit != PkgKit.ExitEnum.SUCCESS) {
+ log('UpdatePackages failed: %s'.format(PkgKit.ExitEnum.to_string(exit)));
+ }
+
+ checkUpdatesDone();
+ });
+ proxy.UpdatePackagesRemote(1 << PkgKit.TransactionFlagEnum.ONLY_DOWNLOAD, securityUpdates);
+}
+
+function pkGetUpdates(proxy) {
+ proxy.connectSignal('Package', (p, e, params) => {
+ let [info, packageId, summary_] = params;
+
+ if (info == PkgKit.InfoEnum.SECURITY)
+ securityUpdates.push(packageId);
+ });
+ proxy.connectSignal('Finished', (p, e, params) => {
+ let [exit, runtime_] = params;
+
+ if (exit == PkgKit.ExitEnum.SUCCESS) {
+ if (securityUpdates.length > 0) {
+ doPkTransaction(pkUpdatePackages);
+ return;
+ }
+ } else if (exit == PkgKit.ExitEnum.CANCELLED_PRIORITY) {
+ // try again
+ checkUpdates();
+ } else {
+ log('GetUpdates failed: %s'.format(PkgKit.ExitEnum.to_string(exit)));
+ }
+
+ checkUpdatesDone();
+ });
+ proxy.GetUpdatesRemote(0);
+}
+
+function checkUpdatesDone() {
+ updatesCheckInProgress = false;
+ if (updatesCheckRequested) {
+ updatesCheckRequested = false;
+ checkUpdates();
+ } else {
+ syncState();
+ }
+}
+
+function checkUpdates() {
+ if (updatesCheckInProgress) {
+ updatesCheckRequested = true;
+ return;
+ }
+ updatesCheckInProgress = true;
+ securityUpdates = [];
+ doPkTransaction(pkGetUpdates);
+}
+
+function initSystemProxies() {
+ new PkProxy(Gio.DBus.system,
+ 'org.freedesktop.PackageKit',
+ '/org/freedesktop/PackageKit',
+ (proxy, error) => {
+ if (!error) {
+ pkProxy = proxy;
+ let id = pkProxy.connectSignal('UpdatesChanged', checkUpdates);
+ pkProxy._signalId = id;
+ checkUpdates();
+ } else {
+ log('Error connecting to PackageKit: %s'.format(error.message));
+ }
+ },
+ cancellable);
+ new PkOfflineProxy(Gio.DBus.system,
+ 'org.freedesktop.PackageKit',
+ '/org/freedesktop/PackageKit',
+ (proxy, error) => {
+ if (!error) {
+ pkOfflineProxy = proxy;
+ let id = pkOfflineProxy.connect('g-properties-changed', syncState);
+ pkOfflineProxy._signalId = id;
+ syncState();
+ } else {
+ log('Error connecting to PackageKit: %s'.format(error.message));
+ }
+ },
+ cancellable);
+ new LoginManagerProxy(Gio.DBus.system,
+ 'org.freedesktop.login1',
+ '/org/freedesktop/login1',
+ (proxy, error) => {
+ if (!error) {
+ proxy.CanRebootRemote(cancellable, (result, error) => {
+ if (!error && result == 'yes') {
+ loginManagerProxy = proxy;
+ syncState();
+ } else {
+ log('Reboot is not available');
+ }
+ });
+ } else {
+ log('Error connecting to Login manager: %s'.format(error.message));
+ }
+ },
+ cancellable);
+}
+
+function enable() {
+ cancellable = new Gio.Cancellable();
+ extensionSettings = ExtensionUtils.getSettings();
+ Polkit.Permission.new('org.freedesktop.packagekit.trigger-offline-update',
+ null,
+ cancellable,
+ (p, result) => {
+ try {
+ let permission = Polkit.Permission.new_finish(result);
+ if (permission && permission.allowed)
+ initSystemProxies();
+ else
+ throw (new Error('not allowed'));
+ } catch (e) {
+ log('No permission to trigger offline updates: %s'.format(e.toString()));
+ }
+ });
+}
+
+function disable() {
+ cancelDialog(true);
+ cancellable.cancel();
+ cancellable = null;
+ extensionSettings = null;
+ updatesDialog = null;
+ loginManagerProxy = null;
+ if (pkOfflineProxy) {
+ pkOfflineProxy.disconnect(pkOfflineProxy._signalId);
+ pkOfflineProxy = null;
+ }
+ if (pkProxy) {
+ pkProxy.disconnectSignal(pkProxy._signalId);
+ pkProxy = null;
+ }
+}
diff --git a/extensions/updates-dialog/meson.build b/extensions/updates-dialog/meson.build
new file mode 100644
index 0000000..585c02d
--- /dev/null
+++ b/extensions/updates-dialog/meson.build
@@ -0,0 +1,7 @@
+extension_data += configure_file(
+ input: metadata_name + '.in',
+ output: metadata_name,
+ configuration: metadata_conf
+)
+
+extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
diff --git a/extensions/updates-dialog/metadata.json.in b/extensions/updates-dialog/metadata.json.in
new file mode 100644
index 0000000..9946abb
--- /dev/null
+++ b/extensions/updates-dialog/metadata.json.in
@@ -0,0 +1,10 @@
+{
+"extension-id": "@extension_id@",
+"uuid": "@uuid@",
+"settings-schema": "@gschemaname@",
+"gettext-domain": "@gettext_domain@",
+"name": "Updates Dialog",
+"description": "Shows a modal dialog when there are software updates.",
+"shell-version": [ "@shell_current@" ],
+"url": "http://rtcm.fedorapeople.org/updates-dialog"
+}
diff --git a/extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml
b/extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml
new file mode 100644
index 0000000..c08d33c
--- /dev/null
+++ b/extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schemalist>
+ <schema path="/org/gnome/shell/extensions/updates-dialog/"
+ id="org.gnome.shell.extensions.updates-dialog">
+ <key name="grace-period" type="u">
+ <default>300</default>
+ <summary>Grace period in minutes</summary>
+ <description>
+ When the grace period is over, the computer will automatically
+ reboot and install security updates.
+ </description>
+ </key>
+ <key name="last-warning-period" type="u">
+ <default>10</default>
+ <summary>Last warning dialog period</summary>
+ <description>
+ A last warning dialog is displayed this many minutes before
+ the automatic reboot.
+ </description>
+ </key>
+ <key name="last-warnings" type="u">
+ <default>1</default>
+ <summary>Number of last warning dialogs</summary>
+ <description>
+ How many warning dialogs are displayed. Each is displayed at
+ 'last-warning-period' minute intervals.
+ </description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/extensions/updates-dialog/stylesheet.css b/extensions/updates-dialog/stylesheet.css
new file mode 100644
index 0000000..25134b6
--- /dev/null
+++ b/extensions/updates-dialog/stylesheet.css
@@ -0,0 +1 @@
+/* This extensions requires no special styling */
diff --git a/meson.build b/meson.build
index e8e00dc..d129e6c 100644
--- a/meson.build
+++ b/meson.build
@@ -53,6 +53,7 @@ all_extensions += [
'native-window-placement',
'panel-favorites',
'top-icons',
+ 'updates-dialog',
'user-theme'
]
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 9c1438a..55f0e9a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -10,6 +10,8 @@ extensions/native-window-placement/org.gnome.shell.extensions.native-window-plac
extensions/places-menu/extension.js
extensions/places-menu/placeDisplay.js
extensions/screenshot-window-sizer/org.gnome.shell.extensions.screenshot-window-sizer.gschema.xml
+extensions/updates-dialog/extension.js
+extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml
extensions/user-theme/extension.js
extensions/user-theme/org.gnome.shell.extensions.user-theme.gschema.xml
extensions/window-list/extension.js
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]