[gnome-shell/gbsneto/automatic-updates: 1/2] Introduce Automatic Updates component
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/gbsneto/automatic-updates: 1/2] Introduce Automatic Updates component
- Date: Tue, 19 Mar 2019 12:17:17 +0000 (UTC)
commit 60eb75da87b067baa29370d3cd392ba5c760aa2b
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date: Tue Mar 19 12:15:37 2019 +0000
Introduce Automatic Updates component
data/gnome-shell-theme.gresource.xml | 3 +
data/theme/automatic-updates-off-symbolic.svg | 1 +
data/theme/automatic-updates-on-symbolic.svg | 1 +
.../theme/automatic-updates-scheduled-symbolic.svg | 1 +
js/js-resources.gresource.xml | 2 +
js/misc/updateManager.js | 338 +++++++++++++++++++++
js/ui/components/updates.js | 135 ++++++++
js/ui/sessionMode.js | 6 +-
po/POTFILES.in | 1 +
9 files changed, 486 insertions(+), 2 deletions(-)
---
diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml
index b77825414..171ca56f0 100644
--- a/data/gnome-shell-theme.gresource.xml
+++ b/data/gnome-shell-theme.gresource.xml
@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/shell/theme">
+ <file>automatic-updates-off-symbolic.svg</file>
+ <file>automatic-updates-on-symbolic.svg</file>
+ <file>automatic-updates-scheduled-symbolic.svg</file>
<file>calendar-today.svg</file>
<file>checkbox-focused.svg</file>
<file>checkbox-off-focused.svg</file>
diff --git a/data/theme/automatic-updates-off-symbolic.svg b/data/theme/automatic-updates-off-symbolic.svg
new file mode 100644
index 000000000..fb5f24446
--- /dev/null
+++ b/data/theme/automatic-updates-off-symbolic.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16
16"><title>EOS_symbolic-icons_v0.1auto-updates_OFF</title><path
d="M11.03,10.074a.125.125,0,0,0-.086.213l1.347,1.35a5.733,5.733,0,0,1-4.238,1.9,5.6,5.6,0,0,1-2.282-.484l-1.11,1.11a7.024,7.024,0,0,0,3.392.875,7.3,7.3,0,0,0,5.3-2.339l1.357,1.36a.125.125,0,0,0,.213-.086L15,10Z"
style="fill:#999"/><path
d="M12.921,5.9a6.354,6.354,0,0,1,.326,1.863.248.248,0,0,0,.244.244h1a.261.261,0,0,0,.257-.265,7.543,7.543,0,0,0-.677-2.991Z"
style="fill:#999"/><path
d="M6.286,9.707a.994.994,0,0,0,.715.3H9a1,1,0,0,0,1-1v-2A.994.994,0,0,0,9.7,6.3l2.175-2.175,0,0L12.93,3.067l0,0,1.4-1.4a.25.25,0,0,0,0-.354L13.617.608a.25.25,0,0,0-.354,0L11.772,2.1A6.97,6.97,0,0,0,7.948.961,7.3,7.3,0,0,0,2.651,3.3L1.293,1.94a.125.125,0,0,0-.214.086L1,6l3.971-.074a.125.125,0,0,0,.086-.213L3.71,4.363a5.733,5.733,0,0,1,4.238-1.9,5.523,5.523,0,0,1,2.723.739L7.86,6.011H7a1,1,0,0,0-1,1V7.87L3.291,10.58a6,6,0,0,1-.537-2.346.248.248,0,0,0-.244-.245h-1a.261.261,0,0,0-.
257.265A
7.329,7.329,0,0,0,2.172,11.7L.608,13.263a.25.25,0,0,0,0,.354l.707.707a.25.25,0,0,0,.354,0l1.4-1.4,0,0,1.056-1.056,0,0Z"
style="fill:#999"/></svg>
\ No newline at end of file
diff --git a/data/theme/automatic-updates-on-symbolic.svg b/data/theme/automatic-updates-on-symbolic.svg
new file mode 100644
index 000000000..8b23fe360
--- /dev/null
+++ b/data/theme/automatic-updates-on-symbolic.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16
16"><title>EOS_symbolic-icons_v0.1auto-updates_ON</title><rect x="6.001" y="6.011" width="4" height="4"
rx="1" ry="1" style="fill:#999"/><path
d="M5.057,5.713,3.71,4.362a5.733,5.733,0,0,1,4.238-1.9,5.173,5.173,0,0,1,5.3,5.305.248.248,0,0,0,.244.244h1a.261.261,0,0,0,.257-.265A6.684,6.684,0,0,0,7.948.961,7.3,7.3,0,0,0,2.65,3.3L1.293,1.94a.125.125,0,0,0-.213.086L1,6l3.971-.074A.125.125,0,0,0,5.057,5.713Z"
style="fill:#999"/><path
d="M11.03,10.074a.125.125,0,0,0-.086.213l1.347,1.35a5.733,5.733,0,0,1-4.238,1.9,5.173,5.173,0,0,1-5.3-5.305.248.248,0,0,0-.244-.245h-1a.261.261,0,0,0-.257.265,6.684,6.684,0,0,0,6.8,6.785,7.3,7.3,0,0,0,5.3-2.339l1.357,1.36a.125.125,0,0,0,.214-.086L15,10Z"
style="fill:#999"/></svg>
\ No newline at end of file
diff --git a/data/theme/automatic-updates-scheduled-symbolic.svg
b/data/theme/automatic-updates-scheduled-symbolic.svg
new file mode 100644
index 000000000..c62eb2419
--- /dev/null
+++ b/data/theme/automatic-updates-scheduled-symbolic.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16
16"><title>EOS_symbolic-icons_v0.1update-scheduled_OUTLINE</title><path
d="M7.99,15.054A6.7,6.7,0,0,1,1.06,8,6.7,6.7,0,0,1,7.99.954,6.71,6.71,0,0,1,14.948,8,6.71,6.71,0,0,1,7.99,15.054Zm0-12.6A5.2,5.2,0,0,0,2.56,8a5.2,5.2,0,0,0,5.43,5.55A5.215,5.215,0,0,0,13.448,8,5.216,5.216,0,0,0,7.99,2.454Z"
style="fill:#999"/><path
d="M9.209,10.443,7.2,8.437a.25.25,0,0,1-.073-.177l0-4.01A.25.25,0,0,1,7.379,4h1.25a.25.25,0,0,1,.25.25l0,3.283a.25.25,0,0,0,.073.177l1.5,1.494a.25.25,0,0,1,0,.354l-.883.884A.25.25,0,0,1,9.209,10.443Z"
style="fill:#999"/></svg>
\ No newline at end of file
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 836d1c674..a969b6292 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -9,6 +9,7 @@
<file>gdm/realmd.js</file>
<file>gdm/util.js</file>
+ <file>misc/updateManager.js</file>
<file>misc/config.js</file>
<file>misc/extensionUtils.js</file>
<file>misc/fileUtils.js</file>
@@ -116,6 +117,7 @@
<file>ui/components/networkAgent.js</file>
<file>ui/components/polkitAgent.js</file>
<file>ui/components/telepathyClient.js</file>
+ <file>ui/components/updates.js</file>
<file>ui/components/keyring.js</file>
<file>ui/status/accessibility.js</file>
diff --git a/js/misc/updateManager.js b/js/misc/updateManager.js
new file mode 100644
index 000000000..a057e9208
--- /dev/null
+++ b/js/misc/updateManager.js
@@ -0,0 +1,338 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+//
+// Copyright (C) 2019 Endless Mobile, Inc.
+//
+// This is a GNOME Shell component to wrap the interactions over
+// D-Bus with the Mogwai system daemon.
+//
+// Licensed under the GNU General Public License Version 2
+//
+// 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, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+const { Clutter, Gio, GLib,
+ GObject, Gtk, NM, Shell, St } = imports.gi;
+
+const NM_SETTING_AUTOMATIC_UPDATES_NOTIFICATION_TIME = "connection.automatic-updates-notification-time";
+const NM_SETTING_ALLOW_DOWNLOADS = 'connection.allow-downloads';
+const NM_SETTING_TARIFF_ENABLED = "connection.tariff-enabled";
+
+const SchedulerInterface = '\
+<node> \
+ <interface name="com.endlessm.DownloadManager1.Scheduler"> \
+ <property name="ActiveEntryCount" type="u" access="read" /> \
+ <property name="EntryCount" type="u" access="read" /> \
+ </interface> \
+</node>';
+
+const SchedulerProxy = Gio.DBusProxy.makeProxyWrapper(SchedulerInterface);
+
+let _updateManager = null;
+
+function getUpdateManager() {
+ if (_updateManager == null)
+ _updateManager = new UpdateManager();
+ return _updateManager;
+}
+
+var State = {
+ UNKNOWN: 0,
+ DISCONNECTED: 1,
+ DISABLED: 2,
+ IDLE: 3,
+ SCHEDULED: 4,
+ DOWNLOADING: 5
+};
+
+function stateToIconName(state) {
+ switch (state) {
+ case State.UNKNOWN:
+ case State.DISCONNECTED:
+ return null;
+
+ case State.DISABLED:
+ return 'resource:///org/gnome/shell/theme/automatic-updates-off-symbolic.svg';
+
+ case State.IDLE:
+ case State.DOWNLOADING:
+ return 'resource:///org/gnome/shell/theme/automatic-updates-on-symbolic.svg';
+
+ case State.SCHEDULED:
+ return 'resource:///org/gnome/shell/theme/automatic-updates-scheduled-symbolic.svg';
+ }
+
+ return null;
+}
+
+var UpdateManager = GObject.registerClass ({
+ Properties: {
+ 'last-notification-time': GObject.ParamSpec.int('last-notification-time',
+ 'last-notification-time',
+ 'last-notification-time',
+ GObject.ParamFlags.READWRITE,
+ null),
+ 'icon': GObject.ParamSpec.object('icon', 'icon', 'icon',
+ GObject.ParamFlags.READABLE,
+ Gio.Icon.$gtype),
+ 'state': GObject.ParamSpec.uint('state', 'state', 'state',
+ GObject.ParamFlags.READABLE,
+ null),
+ },
+}, class UpdateManager extends GObject.Object {
+ _init() {
+ super._init();
+
+ this._activeConnection = null;
+ this._settingChangedSignalId = 0;
+ this._updateTimeoutId = 0;
+
+ this._state = State.UNKNOWN;
+
+ NM.Client.new_async(null, this._clientGot.bind(this));
+ }
+
+ _clientGot(obj, result) {
+ this._client = NM.Client.new_finish(result);
+
+ this._client.connect('notify::primary-connection', this._sync.bind(this));
+ this._client.connect('notify::state', this._sync.bind(this));
+
+ // Start retrieving the Mogwai proxy
+ this._proxy = new SchedulerProxy(Gio.DBus.system,
+ 'com.endlessm.MogwaiSchedule1',
+ '/com/endlessm/DownloadManager1',
+ (proxy, error) => {
+ if (error) {
+ log(error.message);
+ return;
+ }
+ this._proxy.connect('g-properties-changed',
+ this._sync.bind(this));
+ this._updateStatus();
+ });
+
+ this._sync();
+ }
+
+ _sync() {
+ if (!this._client || !this._proxy)
+ return;
+
+ if (this._updateTimeoutId > 0) {
+ GLib.source_remove(this._updateTimeoutId);
+ this._updateTimeoutId = 0;
+ }
+
+ // Intermediate states (connecting or disconnecting) must not trigger
+ // any kind of state change.
+ if (this._client.state == NM.State.CONNECTING || this._client.state == NM.State.DISCONNECTING)
+ return;
+
+ // Use a timeout to avoid instantly throwing the notification at
+ // the user's face, and to avoid a series of unecessary updates
+ // that happen when NetworkManager is still figuring out details.
+ this._updateTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
+ 2,
+ () => {
+ this._updateStatus();
+ this._updateTimeoutId = 0;
+ return GLib.SOURCE_REMOVE;
+ });
+ GLib.Source.set_name_by_id(this._updateTimeoutId, '[update] updateStatus');
+ }
+
+ _updateStatus() {
+ // Update the current active connection. This will connect to the
+ // NM.SettingUser signal to sync every time someone updates the
+ // NM_SETTING_ALLOW_DOWNLOADS setting.
+ this._updateActiveConnection();
+
+ let state = this._getState();
+ if (state != this._state) {
+ this._state = state;
+ this.notify('state');
+
+ this._updateIcon();
+ }
+ }
+
+ _updateActiveConnection() {
+ let currentActiveConnection = this._getActiveConnection();
+
+ if (this._activeConnection == currentActiveConnection)
+ return;
+
+ // Disconnect from the previous active connection
+ if (this._settingChangedSignalId > 0) {
+ this._activeConnection.disconnect(this._settingChangedSignalId);
+ this._settingChangedSignalId = 0;
+ }
+
+ this._activeConnection = currentActiveConnection;
+
+ // Connect from the current active connection
+ if (currentActiveConnection)
+ this._settingChangedSignalId = currentActiveConnection.connect('changed',
this._updateStatus.bind(this));
+ }
+
+ _ensureUserSetting(connection) {
+ let userSetting = connection.get_setting(NM.SettingUser.$gtype);
+ if (!userSetting) {
+ userSetting = new NM.SettingUser();
+ connection.add_setting(userSetting);
+ }
+ return userSetting;
+ }
+
+ _getState() {
+ if (!this._activeConnection)
+ return State.DISCONNECTED;
+
+ let userSetting = this._ensureUserSetting(this._activeConnection);
+
+ // We only return true when:
+ // * Automatic Updates are on
+ // * A schedule was set
+ // * Something is being downloaded
+
+ let allowDownloadsValue = userSetting.get_data(NM_SETTING_ALLOW_DOWNLOADS);
+ if (allowDownloadsValue) {
+ let allowDownloads = (allowDownloadsValue === '1');
+
+ if (!allowDownloads)
+ return State.DISABLED;
+ } else {
+ // Guess the default value from the metered state. Only return
+ // if it's disabled - if it's not, we want to follow the regular
+ // code paths and fetch the correct state
+ let connectionSetting = this._activeConnection.get_setting_connection();
+
+ if (!connectionSetting)
+ return State.DISABLED;
+
+ let metered = connectionSetting.get_metered();
+ if (metered == NM.Metered.YES || metered == NM.Metered.GUESS_YES)
+ return State.DISABLED;
+ }
+
+ // Without the proxy, we can't really know the state
+ if (!this._proxy)
+ return State.UNKNOWN;
+
+ let scheduleSet = userSetting.get_data(NM_SETTING_TARIFF_ENABLED) === '1';
+ if (!scheduleSet)
+ return State.IDLE;
+
+ let downloading = this._proxy.ActiveEntryCount > 0;
+ if (downloading)
+ return State.DOWNLOADING;
+
+ // At this point we're not downloading anything, but something
+ // might be queued
+ let downloadsQueued = this._proxy.EntryCount > 0;
+ if (downloadsQueued)
+ return State.SCHEDULED;
+ else
+ return State.IDLE;
+ }
+
+ _getActiveConnection() {
+ let activeConnection = this._client.get_primary_connection();
+ return activeConnection ? activeConnection.get_connection() : null;
+ }
+
+ _updateIcon() {
+ let state = this._state;
+ let iconName = stateToIconName(state);
+
+ if (iconName) {
+ let iconFile = Gio.File.new_for_uri(iconName);
+ this._icon = new Gio.FileIcon({ file: iconFile });
+ } else {
+ this._icon = null;
+ }
+
+ this.notify('icon');
+ }
+
+ get state() {
+ return this._state;
+ }
+
+ get lastNotificationTime() {
+ let connection = this._getActiveConnection();
+ if (!connection)
+ return -1;
+
+ let userSetting = connection.get_setting(NM.SettingUser.$gtype);
+ if (!userSetting)
+ return -1;
+
+ let time = userSetting.get_data(NM_SETTING_AUTOMATIC_UPDATES_NOTIFICATION_TIME);
+ return time ? parseInt(time) : -1;
+ }
+
+ set lastNotificationTime(time) {
+ if (!this._activeConnection)
+ return;
+
+ let userSetting = this._ensureUserSetting(this._activeConnection);
+ userSetting.set_data(NM_SETTING_AUTOMATIC_UPDATES_NOTIFICATION_TIME,
+ '%s'.format(time));
+
+ this._activeConnection.commit_changes(true, null);
+ }
+
+
+ get active() {
+ return this._active;
+ }
+
+ set active(_active) {
+ if (this._active == _active)
+ return;
+
+ this._active = _active;
+ this.notify('active');
+ }
+
+ get icon() {
+ return this._icon;
+ }
+
+ toggleAutomaticUpdates() {
+ if (!this._activeConnection)
+ return;
+
+ let userSetting = this._ensureUserSetting(this._activeConnection);
+
+ let state = this._getState();
+ let value;
+
+ if (state == State.IDLE ||
+ state == State.SCHEDULED ||
+ state == State.DOWNLOADING) {
+ value = '0';
+ } else {
+ value = '1';
+ }
+
+ userSetting.set_data(NM_SETTING_ALLOW_DOWNLOADS, value);
+
+ this._activeConnection.commit_changes_async(true, null, (con, res, data) => {
+ this._activeConnection.commit_changes_finish(res);
+ this._updateStatus();
+ });
+ }
+});
diff --git a/js/ui/components/updates.js b/js/ui/components/updates.js
new file mode 100644
index 000000000..801dd8e80
--- /dev/null
+++ b/js/ui/components/updates.js
@@ -0,0 +1,135 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+//
+// Copyright (C) 2018 Endless Mobile, Inc.
+//
+// This is a GNOME Shell component to wrap the interactions over
+// D-Bus with the Mogwai system daemon.
+//
+// Licensed under the GNU General Public License Version 2
+//
+// 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, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+const { Gio, GLib, Shell } = imports.gi;
+
+const UpdateManager = imports.misc.updateManager;
+
+const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
+
+var UpdateComponent = class {
+ constructor() {
+ this._notification = null;
+ this._state = UpdateManager.State.UNKNOWN;
+
+ this._manager = UpdateManager.getUpdateManager();
+ this._manager.connect('notify::state', this._updateState.bind(this));
+
+ this._updateState();
+ }
+
+ enable() {
+ }
+
+ disable() {
+ }
+
+ _updateState() {
+ let newState = this._manager.state;
+
+ if (this._state == newState)
+ return;
+
+ this._updateNotification(newState);
+ this._state = newState;
+ }
+
+ _updateNotification(newState) {
+ // Don't notify when starting up
+ if (this._manager.state == UpdateManager.State.UNKNOWN)
+ return;
+
+ let alreadySentNotification = this._manager.lastNotificationTime != -1;
+
+ let wasDisconnected = this._state == UpdateManager.State.DISCONNECTED;
+ let wasActive = this._state >= UpdateManager.State.IDLE;
+ let isActive = newState >= UpdateManager.State.IDLE;
+
+ // The criteria to notify about the Automatic Updates setting is:
+ // 1. If the user was disconnected and connects to a new network; or
+ // 2. If the user was connected and connects to a network with different status;
+ if ((wasDisconnected && alreadySentNotification) || (!wasDisconnected && isActive == wasActive))
+ return;
+
+ if (this._notification)
+ this._notification.destroy();
+
+ if (newState == UpdateManager.State.DISCONNECTED)
+ return;
+
+ let source = new MessageTray.SystemNotificationSource();
+ Main.messageTray.add(source);
+
+ // Figure out the title, subtitle and icon
+ let title, subtitle, iconFile;
+
+ if (isActive) {
+ title = _("Automatic updates on");
+ subtitle = _("Your connection has unlimited data so automatic updates have been turned on.");
+ iconFile = UpdateManager.stateToIconName(UpdateManager.State.IDLE);
+ } else {
+ title = _("Automatic updates are turned off to save your data");
+ subtitle = _("You will need to choose which updates to apply when on this connection.");
+ iconFile = UpdateManager.stateToIconName(UpdateManager.State.DISABLED);
+ }
+
+ let gicon = new Gio.FileIcon({ file: Gio.File.new_for_uri(iconFile) });
+
+ // Create the notification.
+ // The first time we notify the user for a given connection,
+ // we set the urgency to critical so that we make sure the
+ // user understands how we may be changing their settings.
+ // On subsequent notifications for the given connection,
+ // for instance if the user regularly switches between
+ // metered and unmetered connections, we set the urgency
+ // to normal so as not to be too obtrusive.
+ this._notification = new MessageTray.Notification(source, title, subtitle, { gicon: gicon });
+ this._notification.setUrgency(alreadySentNotification ?
+ MessageTray.Urgency.NORMAL : MessageTray.Urgency.CRITICAL);
+ this._notification.setTransient(false);
+
+ this._notification.addAction(_("Close"), () => {
+ this._notification.destroy();
+ });
+
+ this._notification.addAction(_("Change Settings…"), () => {
+ // FIXME: this requires the Automatic Updates panel in GNOME
+ // Settings. Going with the Network panel for now…
+ let app = Shell.AppSystem.get_default().lookup_app('gnome-network-panel.desktop');
+ Main.overview.hide();
+ app.activate();
+ });
+
+ source.notify(this._notification);
+
+ this._notification.connect('destroy', () => {
+ this._notification = null;
+ });
+
+ // Now that we first detected this connection, mark it as such
+ this._manager.lastNotificationTime = GLib.get_real_time();
+ }
+};
+
+var Component = UpdateComponent;
diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js
index 25aa75a3d..3783f79e8 100644
--- a/js/ui/sessionMode.js
+++ b/js/ui/sessionMode.js
@@ -92,9 +92,11 @@ const _modes = {
unlockDialog: imports.ui.unlockDialog.UnlockDialog,
components: Config.HAVE_NETWORKMANAGER ?
['networkAgent', 'polkitAgent', 'telepathyClient',
- 'keyring', 'autorunManager', 'automountManager'] :
+ 'keyring', 'autorunManager', 'automountManager',
+ 'updates'] :
['polkitAgent', 'telepathyClient',
- 'keyring', 'autorunManager', 'automountManager'],
+ 'keyring', 'autorunManager', 'automountManager',
+ 'updates'],
panel: {
left: ['activities', 'appMenu'],
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 43ea408ac..7bcded50b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -52,6 +52,7 @@ js/ui/search.js
js/ui/shellEntry.js
js/ui/shellMountOperation.js
js/ui/status/accessibility.js
+js/ui/status/automaticUpdates.js
js/ui/status/bluetooth.js
js/ui/status/brightness.js
js/ui/status/keyboard.js
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]