extensions-app: Split out ExtensionManager
The extension handling is currently intertwined with the UI. Splitting it out provides a clearer separation, and will allow us to switch to a model-based UI later. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3067>
This commit is contained in:

committed by
Marge Bot

parent
3d070fdc6b
commit
f7ded3e509
@ -10,7 +10,6 @@ import * as Gettext from 'gettext';
|
||||
|
||||
import * as Config from './misc/config.js';
|
||||
import {ExtensionRow} from './extensionRow.js';
|
||||
import {ExtensionState, ExtensionType, deserializeExtension} from './misc/extensionUtils.js';
|
||||
|
||||
Gio._promisify(Gio.DBusConnection.prototype, 'call');
|
||||
Gio._promisify(Shew.WindowExporter.prototype, 'export');
|
||||
@ -36,8 +35,6 @@ export const ExtensionsWindow = GObject.registerClass({
|
||||
if (Config.PROFILE === 'development')
|
||||
this.add_css_class('devel');
|
||||
|
||||
this._updatesCheckId = 0;
|
||||
|
||||
this._exporter = new Shew.WindowExporter({window: this});
|
||||
this._exportedHandle = '';
|
||||
|
||||
@ -52,7 +49,8 @@ export const ExtensionsWindow = GObject.registerClass({
|
||||
name: 'user-extensions-enabled',
|
||||
state: 'false',
|
||||
change_state: (a, state) => {
|
||||
this._shellProxy.UserExtensionsEnabled = state.get_boolean();
|
||||
const {extensionManager} = this.application;
|
||||
extensionManager.userExtensionsEnabled = state.get_boolean();
|
||||
},
|
||||
}]);
|
||||
|
||||
@ -90,27 +88,38 @@ export const ExtensionsWindow = GObject.registerClass({
|
||||
}));
|
||||
this._systemList.connect('row-activated', (_list, row) => row.activate());
|
||||
|
||||
this._shellProxy.connectSignal('ExtensionStateChanged',
|
||||
this._onExtensionStateChanged.bind(this));
|
||||
|
||||
this._shellProxy.connect('g-properties-changed',
|
||||
const {extensionManager} = this.application;
|
||||
extensionManager.connect('notify::failed',
|
||||
() => this._syncVisiblePage());
|
||||
extensionManager.connect('notify::n-updates',
|
||||
() => this._checkUpdates());
|
||||
extensionManager.connect('notify::user-extensions-enabled',
|
||||
this._onUserExtensionsEnabledChanged.bind(this));
|
||||
this._onUserExtensionsEnabledChanged();
|
||||
|
||||
this._scanExtensions();
|
||||
extensionManager.connect('extension-added',
|
||||
(mgr, extension) => this._addExtensionRow(extension));
|
||||
extensionManager.connect('extension-removed',
|
||||
(mgr, extension) => this._removeExtensionRow(extension));
|
||||
extensionManager.connect('extension-changed',
|
||||
(mgr, extension) => {
|
||||
const row = this._findExtensionRow(extension);
|
||||
const isUser = row?.get_parent() === this._userList;
|
||||
if (extension.isUser !== isUser) {
|
||||
this._removeExtensionRow(extension);
|
||||
this._addExtensionRow(extension);
|
||||
}
|
||||
});
|
||||
|
||||
extensionManager.connect('extensions-loaded',
|
||||
() => this._extensionsLoaded());
|
||||
}
|
||||
|
||||
get _shellProxy() {
|
||||
return this.application.shellProxy;
|
||||
}
|
||||
|
||||
uninstall(uuid) {
|
||||
const row = this._findExtensionRow(uuid);
|
||||
|
||||
uninstall(extension) {
|
||||
const dialog = new Gtk.MessageDialog({
|
||||
transient_for: this,
|
||||
modal: true,
|
||||
text: _('Remove “%s”?').format(row.name),
|
||||
text: _('Remove “%s”?').format(extension.name),
|
||||
secondary_text: _('If you remove the extension, you need to return to download it if you want to enable it again'),
|
||||
});
|
||||
|
||||
@ -119,14 +128,16 @@ export const ExtensionsWindow = GObject.registerClass({
|
||||
.get_style_context().add_class('destructive-action');
|
||||
|
||||
dialog.connect('response', (dlg, response) => {
|
||||
const {extensionManager} = this.application;
|
||||
|
||||
if (response === Gtk.ResponseType.ACCEPT)
|
||||
this._shellProxy.UninstallExtensionAsync(uuid).catch(console.error);
|
||||
extensionManager.uninstallExtension(extension.uuid);
|
||||
dialog.destroy();
|
||||
});
|
||||
dialog.present();
|
||||
}
|
||||
|
||||
async openPrefs(uuid) {
|
||||
async openPrefs(extension) {
|
||||
if (!this._exportedHandle) {
|
||||
try {
|
||||
this._exportedHandle = await this._exporter.export();
|
||||
@ -135,9 +146,8 @@ export const ExtensionsWindow = GObject.registerClass({
|
||||
}
|
||||
}
|
||||
|
||||
this._shellProxy.OpenExtensionPrefsAsync(uuid,
|
||||
this._exportedHandle,
|
||||
{modal: new GLib.Variant('b', true)}).catch(console.error);
|
||||
const {extensionManager} = this.application;
|
||||
extensionManager.openExtensionPrefs(extension.uuid, this._exportedHandle);
|
||||
}
|
||||
|
||||
_showAbout() {
|
||||
@ -180,104 +190,68 @@ export const ExtensionsWindow = GObject.registerClass({
|
||||
}
|
||||
|
||||
_sortList(row1, row2) {
|
||||
return row1.name.localeCompare(row2.name);
|
||||
const {name: name1} = row1.extension;
|
||||
const {name: name2} = row2.extension;
|
||||
return name1.localeCompare(name2);
|
||||
}
|
||||
|
||||
_filterList(row) {
|
||||
const {keywords} = row.extension;
|
||||
return this._searchTerms.every(
|
||||
t => row.keywords.some(k => k.startsWith(t)));
|
||||
t => keywords.some(k => k.startsWith(t)));
|
||||
}
|
||||
|
||||
_findExtensionRow(uuid) {
|
||||
_findExtensionRow(extension) {
|
||||
return [
|
||||
...this._userList,
|
||||
...this._systemList,
|
||||
].find(c => c.uuid === uuid);
|
||||
].find(c => c.extension === extension);
|
||||
}
|
||||
|
||||
_onUserExtensionsEnabledChanged() {
|
||||
const {userExtensionsEnabled} = this.application.extensionManager;
|
||||
const action = this.lookup_action('user-extensions-enabled');
|
||||
action.set_state(
|
||||
new GLib.Variant('b', this._shellProxy.UserExtensionsEnabled));
|
||||
}
|
||||
|
||||
_onExtensionStateChanged(proxy, senderName, [uuid, newState]) {
|
||||
const extension = deserializeExtension(newState);
|
||||
let row = this._findExtensionRow(uuid);
|
||||
|
||||
this._queueUpdatesCheck();
|
||||
|
||||
// the extension's type changed; remove the corresponding row
|
||||
// and reset the variable to null so that we create a new row
|
||||
// below and add it to the appropriate list
|
||||
if (row && row.type !== extension.type) {
|
||||
row.get_parent().remove(row);
|
||||
row = null;
|
||||
}
|
||||
|
||||
if (row) {
|
||||
if (extension.state === ExtensionState.UNINSTALLED)
|
||||
row.get_parent().remove(row);
|
||||
} else {
|
||||
this._addExtensionRow(extension);
|
||||
}
|
||||
|
||||
this._syncListVisibility();
|
||||
}
|
||||
|
||||
async _scanExtensions() {
|
||||
try {
|
||||
const [extensionsMap] = await this._shellProxy.ListExtensionsAsync();
|
||||
|
||||
for (let uuid in extensionsMap) {
|
||||
const extension = deserializeExtension(extensionsMap[uuid]);
|
||||
this._addExtensionRow(extension);
|
||||
}
|
||||
this._extensionsLoaded();
|
||||
} catch (e) {
|
||||
if (e instanceof Gio.DBusError) {
|
||||
console.log(`Failed to connect to shell proxy: ${e}`);
|
||||
this._mainStack.visible_child_name = 'noshell';
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
action.set_state(new GLib.Variant('b', userExtensionsEnabled));
|
||||
}
|
||||
|
||||
_addExtensionRow(extension) {
|
||||
const row = new ExtensionRow(extension);
|
||||
|
||||
if (row.type === ExtensionType.PER_USER)
|
||||
if (extension.isUser)
|
||||
this._userList.append(row);
|
||||
else
|
||||
this._systemList.append(row);
|
||||
|
||||
this._syncListVisibility();
|
||||
}
|
||||
|
||||
_queueUpdatesCheck() {
|
||||
if (this._updatesCheckId)
|
||||
return;
|
||||
|
||||
this._updatesCheckId = GLib.timeout_add_seconds(
|
||||
GLib.PRIORITY_DEFAULT, 1, () => {
|
||||
this._checkUpdates();
|
||||
|
||||
this._updatesCheckId = 0;
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
_removeExtensionRow(extension) {
|
||||
const row = this._findExtensionRow(extension);
|
||||
if (row)
|
||||
row.get_parent().remove(row);
|
||||
this._syncListVisibility();
|
||||
}
|
||||
|
||||
_syncListVisibility() {
|
||||
this._userGroup.visible = [...this._userList].length > 1;
|
||||
this._systemGroup.visible = [...this._systemList].length > 1;
|
||||
|
||||
if (this._userGroup.visible || this._systemGroup.visible)
|
||||
this._syncVisiblePage();
|
||||
}
|
||||
|
||||
_syncVisiblePage() {
|
||||
const {extensionManager} = this.application;
|
||||
|
||||
if (extensionManager.failed)
|
||||
this._mainStack.visible_child_name = 'noshell';
|
||||
else if (this._userGroup.visible || this._systemGroup.visible)
|
||||
this._mainStack.visible_child_name = 'main';
|
||||
else
|
||||
this._mainStack.visible_child_name = 'placeholder';
|
||||
}
|
||||
|
||||
_checkUpdates() {
|
||||
const nUpdates = [...this._userList].filter(c => c.hasUpdate).length;
|
||||
const {nUpdates} = this.application.extensionManager;
|
||||
|
||||
this._updatesBanner.title = Gettext.ngettext(
|
||||
'%d extension will be updated on next login.',
|
||||
|
Reference in New Issue
Block a user