Compare commits
1 Commits
3.33.4
...
gbsneto/mo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
602cec9b8e |
26
NEWS
26
NEWS
@@ -1,29 +1,3 @@
|
|||||||
3.33.4
|
|
||||||
======
|
|
||||||
* Fix unintentional interference between gestures [Jonas; !598]
|
|
||||||
* Fix unintentional loop while polkit dialog is active [Ray; !602]
|
|
||||||
* Fix alt-tab icon size on HiDPI [Jonas; !587]
|
|
||||||
* Style fixes and improvements [Frederik, Jakub; !610, #1446, #1449]
|
|
||||||
* Fix style updates for non-background CSS properties [Florian; #1212]
|
|
||||||
* Fix cursor visibility in screen recordings [Illya; #1208]
|
|
||||||
* Add option for disabling the hot corner [Florian; #688320]
|
|
||||||
* Use more fine-grained levels in battery indicator [Florian; !561, #1442]
|
|
||||||
* Fix the calculation of the maximum number of app search results [Jonas; !110]
|
|
||||||
* Handle horizontal workspace layout with gestures/animations [Florian; !575]
|
|
||||||
* Improve handling of session mode extensions [Florian, Didier; #789852]
|
|
||||||
* Misc. bug fixes and cleanups [Jonas, Florian, Sonny, Carlos, Mario, Benjamin,
|
|
||||||
Marco, Ting-Wei; !599, !600, !591, !606, !152, !607, !604, !495, !608, !611,
|
|
||||||
!614, !612, !615, !618, #369, !620, #774, !621, !616, #1065, !609, !626,
|
|
||||||
!491, !631, !632, !633, #1457]
|
|
||||||
|
|
||||||
Contributors:
|
|
||||||
Benjamin Berg, Jonas Dreßler, Frederik Feichtmeier, Carlos Garnacho,
|
|
||||||
Illya Klymov, Ting-Wei Lan, Florian Müllner, Sonny Piers, Mario Sanchez Prada,
|
|
||||||
Didier Roche, Jakub Steiner, Ray Strode, Jor Teron, Marco Trevisan (Treviño)
|
|
||||||
|
|
||||||
Translators:
|
|
||||||
Jordi Mas [ca], Jor Teron [mjw]
|
|
||||||
|
|
||||||
3.33.3
|
3.33.3
|
||||||
======
|
======
|
||||||
* Prepare for optional X11 [Carlos; !378]
|
* Prepare for optional X11 [Carlos; !378]
|
||||||
|
@@ -173,30 +173,6 @@
|
|||||||
<arg type="s" direction="in" name="uuid"/>
|
<arg type="s" direction="in" name="uuid"/>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<!--
|
|
||||||
EnableExtension:
|
|
||||||
@uuid: The UUID of the extension
|
|
||||||
@success: Whether the operation was successful
|
|
||||||
|
|
||||||
Enable an extension.
|
|
||||||
-->
|
|
||||||
<method name="EnableExtension"> \
|
|
||||||
<arg type="s" direction="in" name="uuid"/> \
|
|
||||||
<arg type="b" direction="out" name="success"/> \
|
|
||||||
</method> \
|
|
||||||
|
|
||||||
<!--
|
|
||||||
DisableExtension:
|
|
||||||
@uuid: The UUID of the extension
|
|
||||||
@success: Whether the operation was successful
|
|
||||||
|
|
||||||
Disable an extension.
|
|
||||||
-->
|
|
||||||
<method name="DisableExtension"> \
|
|
||||||
<arg type="s" direction="in" name="uuid"/> \
|
|
||||||
<arg type="b" direction="out" name="success"/> \
|
|
||||||
</method> \
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
LaunchExtensionPrefs:
|
LaunchExtensionPrefs:
|
||||||
@uuid: The UUID of the extension
|
@uuid: The UUID of the extension
|
||||||
@@ -213,15 +189,6 @@
|
|||||||
-->
|
-->
|
||||||
<method name="CheckForUpdates"/>
|
<method name="CheckForUpdates"/>
|
||||||
|
|
||||||
<signal name="ExtensionStateChanged">
|
|
||||||
<arg type="s" name="uuid"/>
|
|
||||||
<arg type="a{sv}" name="state"/>
|
|
||||||
</signal>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
ExtensionStatusChanged:
|
|
||||||
Deprecated for ExtensionStateChanged
|
|
||||||
-->
|
|
||||||
<signal name="ExtensionStatusChanged">
|
<signal name="ExtensionStatusChanged">
|
||||||
<arg type="s" name="uuid"/>
|
<arg type="s" name="uuid"/>
|
||||||
<arg type="i" name="state"/>
|
<arg type="i" name="state"/>
|
||||||
|
@@ -21,17 +21,6 @@
|
|||||||
EnableExtension and DisableExtension D-Bus methods on org.gnome.Shell.
|
EnableExtension and DisableExtension D-Bus methods on org.gnome.Shell.
|
||||||
</description>
|
</description>
|
||||||
</key>
|
</key>
|
||||||
<key name="disabled-extensions" type="as">
|
|
||||||
<default>[]</default>
|
|
||||||
<summary>UUIDs of extensions to force disabling</summary>
|
|
||||||
<description>
|
|
||||||
GNOME Shell extensions have a UUID property; this key lists extensions
|
|
||||||
which should be disabled, even if loaded as part of the current mode.
|
|
||||||
You can also manipulate this list with the EnableExtension and
|
|
||||||
DisableExtension D-Bus methods on org.gnome.Shell.
|
|
||||||
This key takes precedence over the “enabled-extensions” setting.
|
|
||||||
</description>
|
|
||||||
</key>
|
|
||||||
<key name="disable-user-extensions" type="b">
|
<key name="disable-user-extensions" type="b">
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
<summary>Disable user extensions</summary>
|
<summary>Disable user extensions</summary>
|
||||||
|
@@ -624,12 +624,16 @@ StScrollBar {
|
|||||||
*************/
|
*************/
|
||||||
/* Outline for low res icons */
|
/* Outline for low res icons */
|
||||||
.lowres-icon {
|
.lowres-icon {
|
||||||
icon-shadow: 0 1px 2px rgba(0,0,0,0.3);
|
icon-shadow: 0 -1px rgba(0,0,0,0.05),
|
||||||
|
1px 0 rgba(0,0,0,0.1),
|
||||||
|
0 1px rgba(0,0,0,0.3),
|
||||||
|
-1px 0 rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Drapshadow for large icons */
|
/* Drapshadow for large icons */
|
||||||
.icon-dropshadow {
|
.icon-dropshadow {
|
||||||
icon-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
icon-shadow: 0 2px 12px rgba(0,0,0,0.1),
|
||||||
|
0 1px 2px rgba(0,0,0,0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* OSD */
|
/* OSD */
|
||||||
@@ -740,8 +744,7 @@ StScrollBar {
|
|||||||
spacing: 8px;
|
spacing: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ws-switcher-active-up, .ws-switcher-active-down,
|
.ws-switcher-active-up, .ws-switcher-active-down {
|
||||||
.ws-switcher-active-left, .ws-switcher-active-right {
|
|
||||||
height: 50px;
|
height: 50px;
|
||||||
background-color: $selected_bg_color;
|
background-color: $selected_bg_color;
|
||||||
color: $selected_fg_color;
|
color: $selected_fg_color;
|
||||||
|
@@ -8,8 +8,6 @@ const Config = imports.misc.config;
|
|||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
const { loadInterfaceXML } = imports.misc.fileUtils;
|
const { loadInterfaceXML } = imports.misc.fileUtils;
|
||||||
|
|
||||||
const { ExtensionState } = ExtensionUtils;
|
|
||||||
|
|
||||||
const GnomeShellIface = loadInterfaceXML('org.gnome.Shell.Extensions');
|
const GnomeShellIface = loadInterfaceXML('org.gnome.Shell.Extensions');
|
||||||
const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface);
|
const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface);
|
||||||
|
|
||||||
@@ -19,54 +17,74 @@ function stripPrefix(string, prefix) {
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
var Application = GObject.registerClass({
|
var Application = class {
|
||||||
GTypeName: 'ExtensionPrefs_Application'
|
constructor() {
|
||||||
}, class Application extends Gtk.Application {
|
|
||||||
_init() {
|
|
||||||
GLib.set_prgname('gnome-shell-extension-prefs');
|
GLib.set_prgname('gnome-shell-extension-prefs');
|
||||||
super._init({
|
this.application = new Gtk.Application({
|
||||||
application_id: 'org.gnome.shell.ExtensionPrefs',
|
application_id: 'org.gnome.shell.ExtensionPrefs',
|
||||||
flags: Gio.ApplicationFlags.HANDLES_COMMAND_LINE
|
flags: Gio.ApplicationFlags.HANDLES_COMMAND_LINE
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.application.connect('activate', this._onActivate.bind(this));
|
||||||
|
this.application.connect('command-line', this._onCommandLine.bind(this));
|
||||||
|
this.application.connect('startup', this._onStartup.bind(this));
|
||||||
|
|
||||||
|
this._extensionPrefsModules = {};
|
||||||
|
|
||||||
this._startupUuid = null;
|
this._startupUuid = null;
|
||||||
this._loaded = false;
|
this._loaded = false;
|
||||||
this._skipMainWindow = false;
|
this._skipMainWindow = false;
|
||||||
this._shellProxy = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get shellProxy() {
|
_extensionAvailable(uuid) {
|
||||||
return this._shellProxy;
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
}
|
|
||||||
|
|
||||||
_showPrefs(uuid) {
|
if (!extension)
|
||||||
let row = this._extensionSelector.get_children().find(c => {
|
|
||||||
return c.uuid === uuid && c.hasPrefs;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!row)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!extension.dir.get_child('prefs.js').query_exists(null))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getExtensionPrefsModule(extension) {
|
||||||
|
let uuid = extension.metadata.uuid;
|
||||||
|
|
||||||
|
if (this._extensionPrefsModules.hasOwnProperty(uuid))
|
||||||
|
return this._extensionPrefsModules[uuid];
|
||||||
|
|
||||||
|
ExtensionUtils.installImporter(extension);
|
||||||
|
|
||||||
|
let prefsModule = extension.imports.prefs;
|
||||||
|
prefsModule.init(extension.metadata);
|
||||||
|
|
||||||
|
this._extensionPrefsModules[uuid] = prefsModule;
|
||||||
|
return prefsModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectExtension(uuid) {
|
||||||
|
if (!this._extensionAvailable(uuid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
let widget;
|
let widget;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
widget = row.prefsModule.buildPrefsWidget();
|
let prefsModule = this._getExtensionPrefsModule(extension);
|
||||||
|
widget = prefsModule.buildPrefsWidget();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
widget = this._buildErrorUI(row, e);
|
widget = this._buildErrorUI(extension, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let dialog = new Gtk.Window({
|
let dialog = new Gtk.Window({ modal: !this._skipMainWindow,
|
||||||
modal: !this._skipMainWindow,
|
type_hint: Gdk.WindowTypeHint.DIALOG });
|
||||||
type_hint: Gdk.WindowTypeHint.DIALOG
|
dialog.set_titlebar(new Gtk.HeaderBar({ show_close_button: true,
|
||||||
});
|
title: extension.metadata.name,
|
||||||
dialog.set_titlebar(new Gtk.HeaderBar({
|
visible: true }));
|
||||||
show_close_button: true,
|
|
||||||
title: row.name,
|
|
||||||
visible: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (this._skipMainWindow) {
|
if (this._skipMainWindow) {
|
||||||
this.add_window(dialog);
|
this.application.add_window(dialog);
|
||||||
if (this._window)
|
if (this._window)
|
||||||
this._window.destroy();
|
this._window.destroy();
|
||||||
this._window = dialog;
|
this._window = dialog;
|
||||||
@@ -80,7 +98,7 @@ var Application = GObject.registerClass({
|
|||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildErrorUI(row, exc) {
|
_buildErrorUI(extension, exc) {
|
||||||
let scroll = new Gtk.ScrolledWindow({
|
let scroll = new Gtk.ScrolledWindow({
|
||||||
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||||||
propagate_natural_height: true
|
propagate_natural_height: true
|
||||||
@@ -152,7 +170,7 @@ var Application = GObject.registerClass({
|
|||||||
let clipboard = Gtk.Clipboard.get_default(w.get_display());
|
let clipboard = Gtk.Clipboard.get_default(w.get_display());
|
||||||
// markdown for pasting in gitlab issues
|
// markdown for pasting in gitlab issues
|
||||||
let lines = [
|
let lines = [
|
||||||
`The settings of extension ${row.uuid} had an error:`,
|
`The settings of extension ${extension.uuid} had an error:`,
|
||||||
'```',
|
'```',
|
||||||
`${exc}`,
|
`${exc}`,
|
||||||
'```',
|
'```',
|
||||||
@@ -174,13 +192,13 @@ var Application = GObject.registerClass({
|
|||||||
label: _("Homepage"),
|
label: _("Homepage"),
|
||||||
tooltip_text: _("Visit extension homepage"),
|
tooltip_text: _("Visit extension homepage"),
|
||||||
no_show_all: true,
|
no_show_all: true,
|
||||||
visible: row.url != null
|
visible: extension.metadata.url != null
|
||||||
});
|
});
|
||||||
toolbar.add(urlButton);
|
toolbar.add(urlButton);
|
||||||
|
|
||||||
urlButton.connect('clicked', w => {
|
urlButton.connect('clicked', w => {
|
||||||
let context = w.get_display().get_app_launch_context();
|
let context = w.get_display().get_app_launch_context();
|
||||||
Gio.AppInfo.launch_default_for_uri(row.url, context);
|
Gio.AppInfo.launch_default_for_uri(extension.metadata.url, context);
|
||||||
});
|
});
|
||||||
|
|
||||||
let expandedBox = new Gtk.Box({
|
let expandedBox = new Gtk.Box({
|
||||||
@@ -195,8 +213,8 @@ var Application = GObject.registerClass({
|
|||||||
return scroll;
|
return scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildUI() {
|
_buildUI(app) {
|
||||||
this._window = new Gtk.ApplicationWindow({ application: this,
|
this._window = new Gtk.ApplicationWindow({ application: app,
|
||||||
window_position: Gtk.WindowPosition.CENTER });
|
window_position: Gtk.WindowPosition.CENTER });
|
||||||
|
|
||||||
this._window.set_default_size(800, 500);
|
this._window.set_default_size(800, 500);
|
||||||
@@ -230,14 +248,18 @@ var Application = GObject.registerClass({
|
|||||||
this._mainStack.add_named(new EmptyPlaceholder(), 'placeholder');
|
this._mainStack.add_named(new EmptyPlaceholder(), 'placeholder');
|
||||||
|
|
||||||
this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell');
|
this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell');
|
||||||
this._shellProxy.connectSignal('ExtensionStateChanged',
|
this._shellProxy.connectSignal('ExtensionStatusChanged', (proxy, senderName, [uuid, state, error]) => {
|
||||||
this._onExtensionStateChanged.bind(this));
|
if (ExtensionUtils.extensions[uuid] !== undefined)
|
||||||
|
this._scanExtensions();
|
||||||
|
});
|
||||||
|
|
||||||
this._window.show_all();
|
this._window.show_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
_sortList(row1, row2) {
|
_sortList(row1, row2) {
|
||||||
return row1.name.localeCompare(row2.name);
|
let name1 = ExtensionUtils.extensions[row1.uuid].metadata.name;
|
||||||
|
let name2 = ExtensionUtils.extensions[row2.uuid].metadata.name;
|
||||||
|
return name1.localeCompare(name2);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateHeader(row, before) {
|
_updateHeader(row, before) {
|
||||||
@@ -248,55 +270,19 @@ var Application = GObject.registerClass({
|
|||||||
row.set_header(sep);
|
row.set_header(sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
_findExtensionRow(uuid) {
|
|
||||||
return this._extensionSelector.get_children().find(c => c.uuid === uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onExtensionStateChanged(proxy, senderName, [uuid, newState]) {
|
|
||||||
let row = this._findExtensionRow(uuid);
|
|
||||||
if (row) {
|
|
||||||
let { state } = ExtensionUtils.deserializeExtension(newState);
|
|
||||||
if (state == ExtensionState.UNINSTALLED)
|
|
||||||
row.destroy();
|
|
||||||
return; // we only deal with new and deleted extensions here
|
|
||||||
}
|
|
||||||
|
|
||||||
this._shellProxy.GetExtensionInfoRemote(uuid, ([serialized]) => {
|
|
||||||
let extension = ExtensionUtils.deserializeExtension(serialized);
|
|
||||||
if (!extension)
|
|
||||||
return;
|
|
||||||
// check the extension wasn't added in between
|
|
||||||
if (this._findExtensionRow(uuid) != null)
|
|
||||||
return;
|
|
||||||
this._addExtensionRow(extension);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_scanExtensions() {
|
_scanExtensions() {
|
||||||
this._shellProxy.ListExtensionsRemote(([extensionsMap], e) => {
|
let finder = new ExtensionUtils.ExtensionFinder();
|
||||||
if (e) {
|
finder.connect('extension-found', this._extensionFound.bind(this));
|
||||||
if (e instanceof Gio.DBusError) {
|
finder.scanExtensions();
|
||||||
log(`Failed to connect to shell proxy: ${e}`);
|
this._extensionsLoaded();
|
||||||
this._mainStack.add_named(new NoShellPlaceholder(), 'noshell');
|
|
||||||
this._mainStack.visible_child_name = 'noshell';
|
|
||||||
} else
|
|
||||||
throw e;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let uuid in extensionsMap) {
|
|
||||||
let extension = ExtensionUtils.deserializeExtension(extensionsMap[uuid]);
|
|
||||||
this._addExtensionRow(extension);
|
|
||||||
}
|
|
||||||
this._extensionsLoaded();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_addExtensionRow(extension) {
|
_extensionFound(finder, extension) {
|
||||||
let row = new ExtensionRow(extension);
|
let row = new ExtensionRow(extension.uuid);
|
||||||
|
|
||||||
|
row.prefsButton.visible = this._extensionAvailable(row.uuid);
|
||||||
row.prefsButton.connect('clicked', () => {
|
row.prefsButton.connect('clicked', () => {
|
||||||
this._showPrefs(row.uuid);
|
this._selectExtension(row.uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
row.show_all();
|
row.show_all();
|
||||||
@@ -309,26 +295,24 @@ var Application = GObject.registerClass({
|
|||||||
else
|
else
|
||||||
this._mainStack.visible_child_name = 'placeholder';
|
this._mainStack.visible_child_name = 'placeholder';
|
||||||
|
|
||||||
if (this._startupUuid)
|
if (this._startupUuid && this._extensionAvailable(this._startupUuid))
|
||||||
this._showPrefs(this._startupUuid);
|
this._selectExtension(this._startupUuid);
|
||||||
this._startupUuid = null;
|
this._startupUuid = null;
|
||||||
this._skipMainWindow = false;
|
this._skipMainWindow = false;
|
||||||
this._loaded = true;
|
this._loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
vfunc_activate() {
|
_onActivate() {
|
||||||
this._window.present();
|
this._window.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
vfunc_startup() {
|
_onStartup(app) {
|
||||||
super.vfunc_startup();
|
this._buildUI(app);
|
||||||
|
|
||||||
this._buildUI();
|
|
||||||
this._scanExtensions();
|
this._scanExtensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
vfunc_command_line(commandLine) {
|
_onCommandLine(app, commandLine) {
|
||||||
this.activate();
|
app.activate();
|
||||||
let args = commandLine.get_arguments();
|
let args = commandLine.get_arguments();
|
||||||
|
|
||||||
if (args.length) {
|
if (args.length) {
|
||||||
@@ -339,14 +323,16 @@ var Application = GObject.registerClass({
|
|||||||
// Strip off "extension:///" prefix which fakes a URI, if it exists
|
// Strip off "extension:///" prefix which fakes a URI, if it exists
|
||||||
uuid = stripPrefix(uuid, "extension:///");
|
uuid = stripPrefix(uuid, "extension:///");
|
||||||
|
|
||||||
if (!this._loaded)
|
if (this._extensionAvailable(uuid))
|
||||||
|
this._selectExtension(uuid);
|
||||||
|
else if (!this._loaded)
|
||||||
this._startupUuid = uuid;
|
this._startupUuid = uuid;
|
||||||
else if (!this._showPrefs(uuid))
|
else
|
||||||
this._skipMainWindow = false;
|
this._skipMainWindow = false;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
var Expander = GObject.registerClass({
|
var Expander = GObject.registerClass({
|
||||||
Properties: {
|
Properties: {
|
||||||
@@ -513,35 +499,6 @@ class EmptyPlaceholder extends Gtk.Box {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var NoShellPlaceholder = GObject.registerClass(
|
|
||||||
class NoShellPlaceholder extends Gtk.Box {
|
|
||||||
_init() {
|
|
||||||
super._init({
|
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
|
||||||
spacing: 12,
|
|
||||||
margin: 100,
|
|
||||||
margin_bottom: 60
|
|
||||||
});
|
|
||||||
|
|
||||||
let label = new Gtk.Label({
|
|
||||||
label: '<span size="x-large">%s</span>'.format(
|
|
||||||
_("Something’s gone wrong")),
|
|
||||||
use_markup: true
|
|
||||||
});
|
|
||||||
label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
|
||||||
this.add(label);
|
|
||||||
|
|
||||||
label = new Gtk.Label({
|
|
||||||
label: _("We’re very sorry, but it was not possible to get the list of installed extensions. Make sure you are logged into GNOME and try again."),
|
|
||||||
justify: Gtk.Justification.CENTER,
|
|
||||||
wrap: true
|
|
||||||
});
|
|
||||||
this.add(label);
|
|
||||||
|
|
||||||
this.show_all();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var DescriptionLabel = GObject.registerClass(
|
var DescriptionLabel = GObject.registerClass(
|
||||||
class DescriptionLabel extends Gtk.Label {
|
class DescriptionLabel extends Gtk.Label {
|
||||||
vfunc_get_preferred_height_for_width(width) {
|
vfunc_get_preferred_height_for_width(width) {
|
||||||
@@ -554,55 +511,30 @@ class DescriptionLabel extends Gtk.Label {
|
|||||||
|
|
||||||
var ExtensionRow = GObject.registerClass(
|
var ExtensionRow = GObject.registerClass(
|
||||||
class ExtensionRow extends Gtk.ListBoxRow {
|
class ExtensionRow extends Gtk.ListBoxRow {
|
||||||
_init(extension) {
|
_init(uuid) {
|
||||||
super._init();
|
super._init();
|
||||||
|
|
||||||
this._app = Gio.Application.get_default();
|
this.uuid = uuid;
|
||||||
this._extension = extension;
|
|
||||||
this._prefsModule = null;
|
|
||||||
|
|
||||||
this._extensionStateChangedId = this._app.shellProxy.connectSignal(
|
this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' });
|
||||||
'ExtensionStateChanged', (p, sender, [uuid, newState]) => {
|
this._settings.connect('changed::enabled-extensions', () => {
|
||||||
if (this.uuid !== uuid)
|
this._switch.state = this._isEnabled();
|
||||||
return;
|
});
|
||||||
|
this._settings.connect('changed::disable-extension-version-validation',
|
||||||
this._extension = ExtensionUtils.deserializeExtension(newState);
|
() => {
|
||||||
let state = (this._extension.state == ExtensionState.ENABLED);
|
this._switch.sensitive = this._canEnable();
|
||||||
this._switch.state = state;
|
});
|
||||||
this._switch.sensitive = this._canToggle();
|
this._settings.connect('changed::disable-user-extensions',
|
||||||
|
() => {
|
||||||
|
this._switch.sensitive = this._canEnable();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.connect('destroy', this._onDestroy.bind(this));
|
|
||||||
|
|
||||||
this._buildUI();
|
this._buildUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
get uuid() {
|
|
||||||
return this._extension.uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
get name() {
|
|
||||||
return this._extension.metadata.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasPrefs() {
|
|
||||||
return this._extension.hasPrefs;
|
|
||||||
}
|
|
||||||
|
|
||||||
get url() {
|
|
||||||
return this._extension.metadata.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onDestroy() {
|
|
||||||
if (!this._app.shellProxy)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this._extensionStateChangedId)
|
|
||||||
this._app.shellProxy.disconnectSignal(this._extensionStateChangedId);
|
|
||||||
this._extensionStateChangedId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_buildUI() {
|
_buildUI() {
|
||||||
|
let extension = ExtensionUtils.extensions[this.uuid];
|
||||||
|
|
||||||
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
|
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
hexpand: true, margin_end: 24, spacing: 24,
|
hexpand: true, margin_end: 24, spacing: 24,
|
||||||
margin: 12 });
|
margin: 12 });
|
||||||
@@ -612,20 +544,19 @@ class ExtensionRow extends Gtk.ListBoxRow {
|
|||||||
spacing: 6, hexpand: true });
|
spacing: 6, hexpand: true });
|
||||||
hbox.add(vbox);
|
hbox.add(vbox);
|
||||||
|
|
||||||
let name = GLib.markup_escape_text(this.name, -1);
|
let name = GLib.markup_escape_text(extension.metadata.name, -1);
|
||||||
let label = new Gtk.Label({ label: '<b>' + name + '</b>',
|
let label = new Gtk.Label({ label: '<b>' + name + '</b>',
|
||||||
use_markup: true,
|
use_markup: true,
|
||||||
halign: Gtk.Align.START });
|
halign: Gtk.Align.START });
|
||||||
vbox.add(label);
|
vbox.add(label);
|
||||||
|
|
||||||
let desc = this._extension.metadata.description.split('\n')[0];
|
let desc = extension.metadata.description.split('\n')[0];
|
||||||
label = new DescriptionLabel({ label: desc, wrap: true, lines: 2,
|
label = new DescriptionLabel({ label: desc, wrap: true, lines: 2,
|
||||||
ellipsize: Pango.EllipsizeMode.END,
|
ellipsize: Pango.EllipsizeMode.END,
|
||||||
xalign: 0, yalign: 0 });
|
xalign: 0, yalign: 0 });
|
||||||
vbox.add(label);
|
vbox.add(label);
|
||||||
|
|
||||||
let button = new Gtk.Button({ valign: Gtk.Align.CENTER,
|
let button = new Gtk.Button({ valign: Gtk.Align.CENTER,
|
||||||
visible: this.hasPrefs,
|
|
||||||
no_show_all: true });
|
no_show_all: true });
|
||||||
button.set_image(new Gtk.Image({ icon_name: 'emblem-system-symbolic',
|
button.set_image(new Gtk.Image({ icon_name: 'emblem-system-symbolic',
|
||||||
icon_size: Gtk.IconSize.BUTTON,
|
icon_size: Gtk.IconSize.BUTTON,
|
||||||
@@ -635,37 +566,51 @@ class ExtensionRow extends Gtk.ListBoxRow {
|
|||||||
|
|
||||||
this.prefsButton = button;
|
this.prefsButton = button;
|
||||||
|
|
||||||
this._switch = new Gtk.Switch({
|
this._switch = new Gtk.Switch({ valign: Gtk.Align.CENTER,
|
||||||
valign: Gtk.Align.CENTER,
|
sensitive: this._canEnable(),
|
||||||
sensitive: this._canToggle(),
|
state: this._isEnabled() });
|
||||||
state: this._extension.state === ExtensionState.ENABLED
|
|
||||||
});
|
|
||||||
this._switch.connect('notify::active', () => {
|
this._switch.connect('notify::active', () => {
|
||||||
if (this._switch.active)
|
if (this._switch.active)
|
||||||
this._app.shellProxy.EnableExtensionRemote(this.uuid);
|
this._enable();
|
||||||
else
|
else
|
||||||
this._app.shellProxy.DisableExtensionRemote(this.uuid);
|
this._disable();
|
||||||
});
|
});
|
||||||
this._switch.connect('state-set', () => true);
|
this._switch.connect('state-set', () => true);
|
||||||
hbox.add(this._switch);
|
hbox.add(this._switch);
|
||||||
}
|
}
|
||||||
|
|
||||||
_canToggle() {
|
_canEnable() {
|
||||||
return this._extension.canChange;
|
let extension = ExtensionUtils.extensions[this.uuid];
|
||||||
|
let checkVersion = !this._settings.get_boolean('disable-extension-version-validation');
|
||||||
|
|
||||||
|
return !this._settings.get_boolean('disable-user-extensions') &&
|
||||||
|
!(checkVersion && ExtensionUtils.isOutOfDate(extension));
|
||||||
}
|
}
|
||||||
|
|
||||||
get prefsModule() {
|
_isEnabled() {
|
||||||
if (!this._prefsModule) {
|
let extensions = this._settings.get_strv('enabled-extensions');
|
||||||
ExtensionUtils.installImporter(this._extension);
|
return extensions.includes(this.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
// give extension prefs access to their own extension object
|
_enable() {
|
||||||
ExtensionUtils.getCurrentExtension = () => this._extension;
|
let extensions = this._settings.get_strv('enabled-extensions');
|
||||||
|
if (extensions.includes(this.uuid))
|
||||||
|
return;
|
||||||
|
|
||||||
this._prefsModule = this._extension.imports.prefs;
|
extensions.push(this.uuid);
|
||||||
this._prefsModule.init(this._extension.metadata);
|
this._settings.set_strv('enabled-extensions', extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._prefsModule;
|
_disable() {
|
||||||
|
let extensions = this._settings.get_strv('enabled-extensions');
|
||||||
|
let pos = extensions.indexOf(this.uuid);
|
||||||
|
if (pos == -1)
|
||||||
|
return;
|
||||||
|
do {
|
||||||
|
extensions.splice(pos, 1);
|
||||||
|
pos = extensions.indexOf(this.uuid);
|
||||||
|
} while (pos != -1);
|
||||||
|
this._settings.set_strv('enabled-extensions', extensions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -693,5 +638,6 @@ function main(argv) {
|
|||||||
Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
|
Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
|
||||||
Gettext.textdomain(Config.GETTEXT_PACKAGE);
|
Gettext.textdomain(Config.GETTEXT_PACKAGE);
|
||||||
|
|
||||||
new Application().run(argv);
|
let app = new Application();
|
||||||
|
app.application.run(argv);
|
||||||
}
|
}
|
||||||
|
@@ -3,32 +3,21 @@
|
|||||||
// Common utils for the extension system and the extension
|
// Common utils for the extension system and the extension
|
||||||
// preferences tool
|
// preferences tool
|
||||||
|
|
||||||
const { Gio, GLib } = imports.gi;
|
|
||||||
|
|
||||||
const Gettext = imports.gettext;
|
const Gettext = imports.gettext;
|
||||||
const Lang = imports.lang;
|
const Signals = imports.signals;
|
||||||
|
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
|
|
||||||
const Config = imports.misc.config;
|
const Config = imports.misc.config;
|
||||||
|
const FileUtils = imports.misc.fileUtils;
|
||||||
|
|
||||||
var ExtensionType = {
|
var ExtensionType = {
|
||||||
SYSTEM: 1,
|
SYSTEM: 1,
|
||||||
PER_USER: 2
|
PER_USER: 2
|
||||||
};
|
};
|
||||||
|
|
||||||
var ExtensionState = {
|
// Maps uuid -> metadata object
|
||||||
ENABLED: 1,
|
var extensions = {};
|
||||||
DISABLED: 2,
|
|
||||||
ERROR: 3,
|
|
||||||
OUT_OF_DATE: 4,
|
|
||||||
DOWNLOADING: 5,
|
|
||||||
INITIALIZED: 6,
|
|
||||||
|
|
||||||
// Used as an error state for operations on unknown extensions,
|
|
||||||
// should never be in a real extensionMeta object.
|
|
||||||
UNINSTALLED: 99
|
|
||||||
};
|
|
||||||
|
|
||||||
const SERIALIZED_PROPERTIES = ['type', 'state', 'path', 'error', 'hasPrefs', 'canChange'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getCurrentExtension:
|
* getCurrentExtension:
|
||||||
@@ -60,17 +49,13 @@ function getCurrentExtension() {
|
|||||||
if (!match)
|
if (!match)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// local import, as the module is used from outside the gnome-shell process
|
|
||||||
// as well (not this function though)
|
|
||||||
let extensionManager = imports.ui.main.extensionManager;
|
|
||||||
|
|
||||||
let path = match[1];
|
let path = match[1];
|
||||||
let file = Gio.File.new_for_path(path);
|
let file = Gio.File.new_for_path(path);
|
||||||
|
|
||||||
// Walk up the directory tree, looking for an extension with
|
// Walk up the directory tree, looking for an extension with
|
||||||
// the same UUID as a directory name.
|
// the same UUID as a directory name.
|
||||||
while (file != null) {
|
while (file != null) {
|
||||||
let extension = extensionManager.lookup(file.get_basename());
|
let extension = extensions[file.get_basename()];
|
||||||
if (extension !== undefined)
|
if (extension !== undefined)
|
||||||
return extension;
|
return extension;
|
||||||
file = file.get_parent();
|
file = file.get_parent();
|
||||||
@@ -176,50 +161,52 @@ function isOutOfDate(extension) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function serializeExtension(extension) {
|
function createExtensionObject(uuid, dir, type) {
|
||||||
let obj = {};
|
let metadataFile = dir.get_child('metadata.json');
|
||||||
Lang.copyProperties(extension.metadata, obj);
|
if (!metadataFile.query_exists(null)) {
|
||||||
|
throw new Error('Missing metadata.json');
|
||||||
|
}
|
||||||
|
|
||||||
SERIALIZED_PROPERTIES.forEach(prop => {
|
let metadataContents, success, tag;
|
||||||
obj[prop] = extension[prop];
|
try {
|
||||||
});
|
[success, metadataContents, tag] = metadataFile.load_contents(null);
|
||||||
|
if (metadataContents instanceof Uint8Array)
|
||||||
|
metadataContents = imports.byteArray.toString(metadataContents);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Failed to load metadata.json: ${e}`);
|
||||||
|
}
|
||||||
|
let meta;
|
||||||
|
try {
|
||||||
|
meta = JSON.parse(metadataContents);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Failed to parse metadata.json: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
let res = {};
|
let requiredProperties = ['uuid', 'name', 'description', 'shell-version'];
|
||||||
for (let key in obj) {
|
for (let i = 0; i < requiredProperties.length; i++) {
|
||||||
let val = obj[key];
|
let prop = requiredProperties[i];
|
||||||
let type;
|
if (!meta[prop]) {
|
||||||
switch (typeof val) {
|
throw new Error(`missing "${prop}" property in metadata.json`);
|
||||||
case 'string':
|
|
||||||
type = 's';
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
type = 'd';
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
type = 'b';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
res[key] = GLib.Variant.new(type, val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
if (uuid != meta.uuid) {
|
||||||
}
|
throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`);
|
||||||
|
|
||||||
function deserializeExtension(variant) {
|
|
||||||
let res = { metadata: {} };
|
|
||||||
for (let prop in variant) {
|
|
||||||
let val = variant[prop].unpack();
|
|
||||||
if (SERIALIZED_PROPERTIES.includes(prop))
|
|
||||||
res[prop] = val;
|
|
||||||
else
|
|
||||||
res.metadata[prop] = val;
|
|
||||||
}
|
}
|
||||||
// add the 2 additional properties to create a valid extension object, as createExtensionObject()
|
|
||||||
res.uuid = res.metadata.uuid;
|
let extension = {};
|
||||||
res.dir = Gio.File.new_for_path(res.path);
|
|
||||||
return res;
|
extension.metadata = meta;
|
||||||
|
extension.uuid = meta.uuid;
|
||||||
|
extension.type = type;
|
||||||
|
extension.dir = dir;
|
||||||
|
extension.path = dir.get_path();
|
||||||
|
extension.error = '';
|
||||||
|
extension.hasPrefs = dir.get_child('prefs.js').query_exists(null);
|
||||||
|
|
||||||
|
extensions[uuid] = extension;
|
||||||
|
|
||||||
|
return extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
function installImporter(extension) {
|
function installImporter(extension) {
|
||||||
@@ -230,3 +217,36 @@ function installImporter(extension) {
|
|||||||
extension.imports = imports[extension.uuid];
|
extension.imports = imports[extension.uuid];
|
||||||
imports.searchPath = oldSearchPath;
|
imports.searchPath = oldSearchPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ExtensionFinder = class {
|
||||||
|
_loadExtension(extensionDir, info, perUserDir) {
|
||||||
|
let fileType = info.get_file_type();
|
||||||
|
if (fileType != Gio.FileType.DIRECTORY)
|
||||||
|
return;
|
||||||
|
let uuid = info.get_name();
|
||||||
|
let existing = extensions[uuid];
|
||||||
|
if (existing) {
|
||||||
|
log('Extension %s already installed in %s. %s will not be loaded'.format(uuid, existing.path, extensionDir.get_path()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let extension;
|
||||||
|
let type = extensionDir.has_prefix(perUserDir) ? ExtensionType.PER_USER
|
||||||
|
: ExtensionType.SYSTEM;
|
||||||
|
try {
|
||||||
|
extension = createExtensionObject(uuid, extensionDir, type);
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'Could not load extension %s'.format(uuid));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.emit('extension-found', extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
scanExtensions() {
|
||||||
|
let perUserDir = Gio.File.new_for_path(global.userdatadir);
|
||||||
|
FileUtils.collectFromDatadirs('extensions', true, (dir, info) => {
|
||||||
|
this._loadExtension(dir, info, perUserDir);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Signals.addSignalMethods(ExtensionFinder.prototype);
|
||||||
|
@@ -633,7 +633,7 @@ var Animation = class Animation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
load(callback) {
|
load(callback) {
|
||||||
this._show = new GnomeDesktop.BGSlideShow({ file: this.file });
|
this._show = new GnomeDesktop.BGSlideShow({ filename: this.file.get_path() });
|
||||||
|
|
||||||
this._show.load_async(null, (object, result) => {
|
this._show.load_async(null, (object, result) => {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
@@ -5,8 +5,8 @@ const { Clutter, Gio, GLib, GObject, Soup } = imports.gi;
|
|||||||
const Config = imports.misc.config;
|
const Config = imports.misc.config;
|
||||||
const Dialog = imports.ui.dialog;
|
const Dialog = imports.ui.dialog;
|
||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const ExtensionSystem = imports.ui.extensionSystem;
|
||||||
const FileUtils = imports.misc.fileUtils;
|
const FileUtils = imports.misc.fileUtils;
|
||||||
const Main = imports.ui.main;
|
|
||||||
const ModalDialog = imports.ui.modalDialog;
|
const ModalDialog = imports.ui.modalDialog;
|
||||||
|
|
||||||
var REPOSITORY_URL_BASE = 'https://extensions.gnome.org';
|
var REPOSITORY_URL_BASE = 'https://extensions.gnome.org';
|
||||||
@@ -24,7 +24,7 @@ function installExtension(uuid, invocation) {
|
|||||||
|
|
||||||
_httpSession.queue_message(message, (session, message) => {
|
_httpSession.queue_message(message, (session, message) => {
|
||||||
if (message.status_code != Soup.KnownStatusCode.OK) {
|
if (message.status_code != Soup.KnownStatusCode.OK) {
|
||||||
Main.extensionManager.logExtensionError(uuid, `downloading info: ${message.status_code}`);
|
ExtensionSystem.logExtensionError(uuid, `downloading info: ${message.status_code}`);
|
||||||
invocation.return_dbus_error('org.gnome.Shell.DownloadInfoError', message.status_code.toString());
|
invocation.return_dbus_error('org.gnome.Shell.DownloadInfoError', message.status_code.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ function installExtension(uuid, invocation) {
|
|||||||
try {
|
try {
|
||||||
info = JSON.parse(message.response_body.data);
|
info = JSON.parse(message.response_body.data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Main.extensionManager.logExtensionError(uuid, `parsing info: ${e}`);
|
ExtensionSystem.logExtensionError(uuid, `parsing info: ${e}`);
|
||||||
invocation.return_dbus_error('org.gnome.Shell.ParseInfoError', e.toString());
|
invocation.return_dbus_error('org.gnome.Shell.ParseInfoError', e.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ function installExtension(uuid, invocation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function uninstallExtension(uuid) {
|
function uninstallExtension(uuid) {
|
||||||
let extension = Main.extensionManager.lookup(uuid);
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
if (!extension)
|
if (!extension)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ function uninstallExtension(uuid) {
|
|||||||
if (extension.type != ExtensionUtils.ExtensionType.PER_USER)
|
if (extension.type != ExtensionUtils.ExtensionType.PER_USER)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!Main.extensionManager.unloadExtension(extension))
|
if (!ExtensionSystem.unloadExtension(extension))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
FileUtils.recursivelyDeleteDir(extension.dir, true);
|
FileUtils.recursivelyDeleteDir(extension.dir, true);
|
||||||
@@ -113,10 +113,10 @@ function updateExtension(uuid) {
|
|||||||
|
|
||||||
_httpSession.queue_message(message, (session, message) => {
|
_httpSession.queue_message(message, (session, message) => {
|
||||||
gotExtensionZipFile(session, message, uuid, newExtensionTmpDir, () => {
|
gotExtensionZipFile(session, message, uuid, newExtensionTmpDir, () => {
|
||||||
let oldExtension = Main.extensionManager.lookup(uuid);
|
let oldExtension = ExtensionUtils.extensions[uuid];
|
||||||
let extensionDir = oldExtension.dir;
|
let extensionDir = oldExtension.dir;
|
||||||
|
|
||||||
if (!Main.extensionManager.unloadExtension(oldExtension))
|
if (!ExtensionSystem.unloadExtension(oldExtension))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FileUtils.recursivelyMoveDir(extensionDir, oldExtensionTmpDir);
|
FileUtils.recursivelyMoveDir(extensionDir, oldExtensionTmpDir);
|
||||||
@@ -125,11 +125,11 @@ function updateExtension(uuid) {
|
|||||||
let extension = null;
|
let extension = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
extension = Main.extensionManager.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER);
|
extension = ExtensionUtils.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER);
|
||||||
Main.extensionManager.loadExtension(extension);
|
ExtensionSystem.loadExtension(extension);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (extension)
|
if (extension)
|
||||||
Main.extensionManager.unloadExtension(extension);
|
ExtensionSystem.unloadExtension(extension);
|
||||||
|
|
||||||
logError(e, 'Error loading extension %s'.format(uuid));
|
logError(e, 'Error loading extension %s'.format(uuid));
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ function updateExtension(uuid) {
|
|||||||
|
|
||||||
// Restore what was there before. We can't do much if we
|
// Restore what was there before. We can't do much if we
|
||||||
// fail here.
|
// fail here.
|
||||||
Main.extensionManager.loadExtension(oldExtension);
|
ExtensionSystem.loadExtension(oldExtension);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,9 +151,9 @@ function updateExtension(uuid) {
|
|||||||
|
|
||||||
function checkForUpdates() {
|
function checkForUpdates() {
|
||||||
let metadatas = {};
|
let metadatas = {};
|
||||||
Main.extensionManager.getUuids().forEach(uuid => {
|
for (let uuid in ExtensionUtils.extensions) {
|
||||||
metadatas[uuid] = Main.extensionManager.extensions[uuid].metadata;
|
metadatas[uuid] = ExtensionUtils.extensions[uuid].metadata;
|
||||||
});
|
}
|
||||||
|
|
||||||
let params = { shell_version: Config.PACKAGE_VERSION,
|
let params = { shell_version: Config.PACKAGE_VERSION,
|
||||||
installed: JSON.stringify(metadatas) };
|
installed: JSON.stringify(metadatas) };
|
||||||
@@ -224,11 +224,16 @@ class InstallExtensionDialog extends ModalDialog.ModalDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function callback() {
|
function callback() {
|
||||||
|
// Add extension to 'enabled-extensions' for the user, always...
|
||||||
|
let enabledExtensions = global.settings.get_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY);
|
||||||
|
if (!enabledExtensions.includes(uuid)) {
|
||||||
|
enabledExtensions.push(uuid);
|
||||||
|
global.settings.set_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY, enabledExtensions);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let extension = Main.extensionManager.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
|
let extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
|
||||||
Main.extensionManager.loadExtension(extension);
|
ExtensionSystem.loadExtension(extension);
|
||||||
if (!Main.extensionManager.enableExtension(uuid))
|
|
||||||
throw new Error(`Cannot add ${uuid} to enabled extensions gsettings key`);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uninstallExtension(uuid);
|
uninstallExtension(uuid);
|
||||||
errback('LoadExtensionError', e);
|
errback('LoadExtensionError', e);
|
||||||
|
@@ -4,519 +4,371 @@ const { Gio, St } = imports.gi;
|
|||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
|
|
||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
const FileUtils = imports.misc.fileUtils;
|
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
|
|
||||||
const { ExtensionState, ExtensionType } = ExtensionUtils;
|
var ExtensionState = {
|
||||||
|
ENABLED: 1,
|
||||||
|
DISABLED: 2,
|
||||||
|
ERROR: 3,
|
||||||
|
OUT_OF_DATE: 4,
|
||||||
|
DOWNLOADING: 5,
|
||||||
|
INITIALIZED: 6,
|
||||||
|
|
||||||
|
// Used as an error state for operations on unknown extensions,
|
||||||
|
// should never be in a real extensionMeta object.
|
||||||
|
UNINSTALLED: 99
|
||||||
|
};
|
||||||
|
|
||||||
|
// Arrays of uuids
|
||||||
|
var enabledExtensions;
|
||||||
|
// Contains the order that extensions were enabled in.
|
||||||
|
var extensionOrder = [];
|
||||||
|
|
||||||
|
// We don't really have a class to add signals on. So, create
|
||||||
|
// a simple dummy object, add the signal methods, and export those
|
||||||
|
// publically.
|
||||||
|
var _signals = {};
|
||||||
|
Signals.addSignalMethods(_signals);
|
||||||
|
|
||||||
|
var connect = _signals.connect.bind(_signals);
|
||||||
|
var disconnect = _signals.disconnect.bind(_signals);
|
||||||
|
|
||||||
const ENABLED_EXTENSIONS_KEY = 'enabled-extensions';
|
const ENABLED_EXTENSIONS_KEY = 'enabled-extensions';
|
||||||
const DISABLED_EXTENSIONS_KEY = 'disabled-extensions';
|
|
||||||
const DISABLE_USER_EXTENSIONS_KEY = 'disable-user-extensions';
|
const DISABLE_USER_EXTENSIONS_KEY = 'disable-user-extensions';
|
||||||
const EXTENSION_DISABLE_VERSION_CHECK_KEY = 'disable-extension-version-validation';
|
const EXTENSION_DISABLE_VERSION_CHECK_KEY = 'disable-extension-version-validation';
|
||||||
|
|
||||||
var ExtensionManager = class {
|
var initted = false;
|
||||||
constructor() {
|
var enabled;
|
||||||
this._initted = false;
|
|
||||||
this._enabled = false;
|
|
||||||
|
|
||||||
this._extensions = new Map();
|
function disableExtension(uuid) {
|
||||||
this._enabledExtensions = [];
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
this._extensionOrder = [];
|
if (!extension)
|
||||||
|
return;
|
||||||
|
|
||||||
Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
|
if (extension.state != ExtensionState.ENABLED)
|
||||||
}
|
return;
|
||||||
|
|
||||||
init() {
|
// "Rebase" the extension order by disabling and then enabling extensions
|
||||||
this._sessionUpdated();
|
// in order to help prevent conflicts.
|
||||||
}
|
|
||||||
|
|
||||||
lookup(uuid) {
|
// Example:
|
||||||
return this._extensions.get(uuid);
|
// order = [A, B, C, D, E]
|
||||||
}
|
// user disables C
|
||||||
|
// this should: disable E, disable D, disable C, enable D, enable E
|
||||||
|
|
||||||
getUuids() {
|
let orderIdx = extensionOrder.indexOf(uuid);
|
||||||
return [...this._extensions.keys()];
|
let order = extensionOrder.slice(orderIdx + 1);
|
||||||
}
|
let orderReversed = order.slice().reverse();
|
||||||
|
|
||||||
_callExtensionDisable(uuid) {
|
for (let i = 0; i < orderReversed.length; i++) {
|
||||||
let extension = this.lookup(uuid);
|
let uuid = orderReversed[i];
|
||||||
if (!extension)
|
try {
|
||||||
return;
|
ExtensionUtils.extensions[uuid].stateObj.disable();
|
||||||
|
} catch (e) {
|
||||||
if (extension.state != ExtensionState.ENABLED)
|
logExtensionError(uuid, e);
|
||||||
return;
|
|
||||||
|
|
||||||
// "Rebase" the extension order by disabling and then enabling extensions
|
|
||||||
// in order to help prevent conflicts.
|
|
||||||
|
|
||||||
// Example:
|
|
||||||
// order = [A, B, C, D, E]
|
|
||||||
// user disables C
|
|
||||||
// this should: disable E, disable D, disable C, enable D, enable E
|
|
||||||
|
|
||||||
let orderIdx = this._extensionOrder.indexOf(uuid);
|
|
||||||
let order = this._extensionOrder.slice(orderIdx + 1);
|
|
||||||
let orderReversed = order.slice().reverse();
|
|
||||||
|
|
||||||
for (let i = 0; i < orderReversed.length; i++) {
|
|
||||||
let uuid = orderReversed[i];
|
|
||||||
try {
|
|
||||||
this.lookup(uuid).stateObj.disable();
|
|
||||||
} catch (e) {
|
|
||||||
this.logExtensionError(uuid, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension.stylesheet) {
|
||||||
|
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
|
||||||
|
theme.unload_stylesheet(extension.stylesheet);
|
||||||
|
delete extension.stylesheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
extension.stateObj.disable();
|
||||||
|
} catch (e) {
|
||||||
|
logExtensionError(uuid, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < order.length; i++) {
|
||||||
|
let uuid = order[i];
|
||||||
|
try {
|
||||||
|
ExtensionUtils.extensions[uuid].stateObj.enable();
|
||||||
|
} catch (e) {
|
||||||
|
logExtensionError(uuid, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extensionOrder.splice(orderIdx, 1);
|
||||||
|
|
||||||
|
if ( extension.state != ExtensionState.ERROR ) {
|
||||||
|
extension.state = ExtensionState.DISABLED;
|
||||||
|
_signals.emit('extension-state-changed', extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableExtension(uuid) {
|
||||||
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
|
if (!extension)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (extension.state == ExtensionState.INITIALIZED)
|
||||||
|
initExtension(uuid);
|
||||||
|
|
||||||
|
if (extension.state != ExtensionState.DISABLED)
|
||||||
|
return;
|
||||||
|
|
||||||
|
extensionOrder.push(uuid);
|
||||||
|
|
||||||
|
let stylesheetNames = [`${global.session_mode}.css`, 'stylesheet.css'];
|
||||||
|
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
|
||||||
|
for (let i = 0; i < stylesheetNames.length; i++) {
|
||||||
|
try {
|
||||||
|
let stylesheetFile = extension.dir.get_child(stylesheetNames[i]);
|
||||||
|
theme.load_stylesheet(stylesheetFile);
|
||||||
|
extension.stylesheet = stylesheetFile;
|
||||||
|
break;
|
||||||
|
} catch (e) {
|
||||||
|
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||||||
|
continue; // not an error
|
||||||
|
log(`Failed to load stylesheet for extension ${uuid}: ${e.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
extension.stateObj.enable();
|
||||||
|
extension.state = ExtensionState.ENABLED;
|
||||||
|
_signals.emit('extension-state-changed', extension);
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
if (extension.stylesheet) {
|
if (extension.stylesheet) {
|
||||||
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
|
|
||||||
theme.unload_stylesheet(extension.stylesheet);
|
theme.unload_stylesheet(extension.stylesheet);
|
||||||
delete extension.stylesheet;
|
delete extension.stylesheet;
|
||||||
}
|
}
|
||||||
|
logExtensionError(uuid, e);
|
||||||
try {
|
return;
|
||||||
extension.stateObj.disable();
|
|
||||||
} catch (e) {
|
|
||||||
this.logExtensionError(uuid, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < order.length; i++) {
|
|
||||||
let uuid = order[i];
|
|
||||||
try {
|
|
||||||
this.lookup(uuid).stateObj.enable();
|
|
||||||
} catch (e) {
|
|
||||||
this.logExtensionError(uuid, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._extensionOrder.splice(orderIdx, 1);
|
|
||||||
|
|
||||||
if (extension.state != ExtensionState.ERROR) {
|
|
||||||
extension.state = ExtensionState.DISABLED;
|
|
||||||
this.emit('extension-state-changed', extension);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_callExtensionEnable(uuid) {
|
function logExtensionError(uuid, error) {
|
||||||
let extension = this.lookup(uuid);
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
if (!extension)
|
if (!extension)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (extension.state == ExtensionState.INITIALIZED)
|
let message = `${error}`;
|
||||||
this._callExtensionInit(uuid);
|
|
||||||
|
|
||||||
if (extension.state != ExtensionState.DISABLED)
|
extension.state = ExtensionState.ERROR;
|
||||||
return;
|
if (!extension.errors)
|
||||||
|
extension.errors = [];
|
||||||
|
extension.errors.push(message);
|
||||||
|
|
||||||
this._extensionOrder.push(uuid);
|
log('Extension "%s" had error: %s'.format(uuid, message));
|
||||||
|
_signals.emit('extension-state-changed', { uuid: uuid,
|
||||||
|
error: message,
|
||||||
|
state: extension.state });
|
||||||
|
}
|
||||||
|
|
||||||
let stylesheetNames = [`${global.session_mode}.css`, 'stylesheet.css'];
|
function loadExtension(extension) {
|
||||||
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
|
// Default to error, we set success as the last step
|
||||||
for (let i = 0; i < stylesheetNames.length; i++) {
|
extension.state = ExtensionState.ERROR;
|
||||||
try {
|
|
||||||
let stylesheetFile = extension.dir.get_child(stylesheetNames[i]);
|
let checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY);
|
||||||
theme.load_stylesheet(stylesheetFile);
|
|
||||||
extension.stylesheet = stylesheetFile;
|
if (checkVersion && ExtensionUtils.isOutOfDate(extension)) {
|
||||||
break;
|
extension.state = ExtensionState.OUT_OF_DATE;
|
||||||
} catch (e) {
|
} else {
|
||||||
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
let enabled = enabledExtensions.includes(extension.uuid);
|
||||||
continue; // not an error
|
if (enabled) {
|
||||||
log(`Failed to load stylesheet for extension ${uuid}: ${e.message}`);
|
if (!initExtension(extension.uuid))
|
||||||
return;
|
return;
|
||||||
}
|
if (extension.state == ExtensionState.DISABLED)
|
||||||
}
|
enableExtension(extension.uuid);
|
||||||
|
|
||||||
try {
|
|
||||||
extension.stateObj.enable();
|
|
||||||
extension.state = ExtensionState.ENABLED;
|
|
||||||
this.emit('extension-state-changed', extension);
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
|
||||||
if (extension.stylesheet) {
|
|
||||||
theme.unload_stylesheet(extension.stylesheet);
|
|
||||||
delete extension.stylesheet;
|
|
||||||
}
|
|
||||||
this.logExtensionError(uuid, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enableExtension(uuid) {
|
|
||||||
if (!this._extensions.has(uuid))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
|
|
||||||
let disabledExtensions = global.settings.get_strv(DISABLED_EXTENSIONS_KEY);
|
|
||||||
|
|
||||||
if (disabledExtensions.includes(uuid)) {
|
|
||||||
disabledExtensions = disabledExtensions.filter(item => item !== uuid);
|
|
||||||
global.settings.set_strv(DISABLED_EXTENSIONS_KEY, disabledExtensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!enabledExtensions.includes(uuid)) {
|
|
||||||
enabledExtensions.push(uuid);
|
|
||||||
global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
disableExtension(uuid) {
|
|
||||||
if (!this._extensions.has(uuid))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
|
|
||||||
let disabledExtensions = global.settings.get_strv(DISABLED_EXTENSIONS_KEY);
|
|
||||||
|
|
||||||
if (enabledExtensions.includes(uuid)) {
|
|
||||||
enabledExtensions = enabledExtensions.filter(item => item !== uuid);
|
|
||||||
global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!disabledExtensions.includes(uuid)) {
|
|
||||||
disabledExtensions.push(uuid);
|
|
||||||
global.settings.set_strv(DISABLED_EXTENSIONS_KEY, disabledExtensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
logExtensionError(uuid, error) {
|
|
||||||
let extension = this.lookup(uuid);
|
|
||||||
if (!extension)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let message = `${error}`;
|
|
||||||
|
|
||||||
extension.error = message;
|
|
||||||
extension.state = ExtensionState.ERROR;
|
|
||||||
if (!extension.errors)
|
|
||||||
extension.errors = [];
|
|
||||||
extension.errors.push(message);
|
|
||||||
|
|
||||||
log('Extension "%s" had error: %s'.format(uuid, message));
|
|
||||||
this.emit('extension-state-changed', extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
createExtensionObject(uuid, dir, type) {
|
|
||||||
let metadataFile = dir.get_child('metadata.json');
|
|
||||||
if (!metadataFile.query_exists(null)) {
|
|
||||||
throw new Error('Missing metadata.json');
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadataContents, success;
|
|
||||||
try {
|
|
||||||
[success, metadataContents] = metadataFile.load_contents(null);
|
|
||||||
if (metadataContents instanceof Uint8Array)
|
|
||||||
metadataContents = imports.byteArray.toString(metadataContents);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Failed to load metadata.json: ${e}`);
|
|
||||||
}
|
|
||||||
let meta;
|
|
||||||
try {
|
|
||||||
meta = JSON.parse(metadataContents);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Failed to parse metadata.json: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let requiredProperties = ['uuid', 'name', 'description', 'shell-version'];
|
|
||||||
for (let i = 0; i < requiredProperties.length; i++) {
|
|
||||||
let prop = requiredProperties[i];
|
|
||||||
if (!meta[prop]) {
|
|
||||||
throw new Error(`missing "${prop}" property in metadata.json`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uuid != meta.uuid) {
|
|
||||||
throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let extension = {
|
|
||||||
metadata: meta,
|
|
||||||
uuid: meta.uuid,
|
|
||||||
type,
|
|
||||||
dir,
|
|
||||||
path: dir.get_path(),
|
|
||||||
error: '',
|
|
||||||
hasPrefs: dir.get_child('prefs.js').query_exists(null),
|
|
||||||
canChange: false
|
|
||||||
};
|
|
||||||
this._extensions.set(uuid, extension);
|
|
||||||
|
|
||||||
return extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadExtension(extension) {
|
|
||||||
// Default to error, we set success as the last step
|
|
||||||
extension.state = ExtensionState.ERROR;
|
|
||||||
|
|
||||||
let checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY);
|
|
||||||
|
|
||||||
if (checkVersion && ExtensionUtils.isOutOfDate(extension)) {
|
|
||||||
extension.state = ExtensionState.OUT_OF_DATE;
|
|
||||||
} else {
|
} else {
|
||||||
let enabled = this._enabledExtensions.includes(extension.uuid);
|
extension.state = ExtensionState.INITIALIZED;
|
||||||
if (enabled) {
|
|
||||||
if (!this._callExtensionInit(extension.uuid))
|
|
||||||
return;
|
|
||||||
if (extension.state == ExtensionState.DISABLED)
|
|
||||||
this._callExtensionEnable(extension.uuid);
|
|
||||||
} else {
|
|
||||||
extension.state = ExtensionState.INITIALIZED;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._updateCanChange(extension);
|
|
||||||
this.emit('extension-state-changed', extension);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unloadExtension(extension) {
|
_signals.emit('extension-state-changed', extension);
|
||||||
// Try to disable it -- if it's ERROR'd, we can't guarantee that,
|
}
|
||||||
// but it will be removed on next reboot, and hopefully nothing
|
|
||||||
// broke too much.
|
|
||||||
this._callExtensionDisable(extension.uuid);
|
|
||||||
|
|
||||||
extension.state = ExtensionState.UNINSTALLED;
|
function unloadExtension(extension) {
|
||||||
this.emit('extension-state-changed', extension);
|
// Try to disable it -- if it's ERROR'd, we can't guarantee that,
|
||||||
|
// but it will be removed on next reboot, and hopefully nothing
|
||||||
|
// broke too much.
|
||||||
|
disableExtension(extension.uuid);
|
||||||
|
|
||||||
this._extensions.delete(extension.uuid);
|
extension.state = ExtensionState.UNINSTALLED;
|
||||||
return true;
|
_signals.emit('extension-state-changed', extension);
|
||||||
|
|
||||||
|
delete ExtensionUtils.extensions[extension.uuid];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadExtension(oldExtension) {
|
||||||
|
// Grab the things we'll need to pass to createExtensionObject
|
||||||
|
// to reload it.
|
||||||
|
let { uuid: uuid, dir: dir, type: type } = oldExtension;
|
||||||
|
|
||||||
|
// Then unload the old extension.
|
||||||
|
unloadExtension(oldExtension);
|
||||||
|
|
||||||
|
// Now, recreate the extension and load it.
|
||||||
|
let newExtension;
|
||||||
|
try {
|
||||||
|
newExtension = ExtensionUtils.createExtensionObject(uuid, dir, type);
|
||||||
|
} catch (e) {
|
||||||
|
logExtensionError(uuid, e);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadExtension(oldExtension) {
|
loadExtension(newExtension);
|
||||||
// Grab the things we'll need to pass to createExtensionObject
|
}
|
||||||
// to reload it.
|
|
||||||
let { uuid: uuid, dir: dir, type: type } = oldExtension;
|
|
||||||
|
|
||||||
// Then unload the old extension.
|
function initExtension(uuid) {
|
||||||
this.unloadExtension(oldExtension);
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
|
let dir = extension.dir;
|
||||||
|
|
||||||
// Now, recreate the extension and load it.
|
if (!extension)
|
||||||
let newExtension;
|
throw new Error("Extension was not properly created. Call loadExtension first");
|
||||||
|
|
||||||
|
let extensionJs = dir.get_child('extension.js');
|
||||||
|
if (!extensionJs.query_exists(null)) {
|
||||||
|
logExtensionError(uuid, new Error('Missing extension.js'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let extensionModule;
|
||||||
|
let extensionState = null;
|
||||||
|
|
||||||
|
ExtensionUtils.installImporter(extension);
|
||||||
|
try {
|
||||||
|
extensionModule = extension.imports.extension;
|
||||||
|
} catch (e) {
|
||||||
|
logExtensionError(uuid, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extensionModule.init) {
|
||||||
try {
|
try {
|
||||||
newExtension = this.createExtensionObject(uuid, dir, type);
|
extensionState = extensionModule.init(extension);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logExtensionError(uuid, e);
|
logExtensionError(uuid, e);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadExtension(newExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
_callExtensionInit(uuid) {
|
|
||||||
let extension = this.lookup(uuid);
|
|
||||||
let dir = extension.dir;
|
|
||||||
|
|
||||||
if (!extension)
|
|
||||||
throw new Error("Extension was not properly created. Call loadExtension first");
|
|
||||||
|
|
||||||
let extensionJs = dir.get_child('extension.js');
|
|
||||||
if (!extensionJs.query_exists(null)) {
|
|
||||||
this.logExtensionError(uuid, new Error('Missing extension.js'));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let extensionModule;
|
|
||||||
let extensionState = null;
|
|
||||||
|
|
||||||
ExtensionUtils.installImporter(extension);
|
|
||||||
try {
|
|
||||||
extensionModule = extension.imports.extension;
|
|
||||||
} catch (e) {
|
|
||||||
this.logExtensionError(uuid, e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extensionModule.init) {
|
|
||||||
try {
|
|
||||||
extensionState = extensionModule.init(extension);
|
|
||||||
} catch (e) {
|
|
||||||
this.logExtensionError(uuid, e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!extensionState)
|
|
||||||
extensionState = extensionModule;
|
|
||||||
extension.stateObj = extensionState;
|
|
||||||
|
|
||||||
extension.state = ExtensionState.DISABLED;
|
|
||||||
this.emit('extension-loaded', uuid);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getModeExtensions() {
|
if (!extensionState)
|
||||||
if (Array.isArray(Main.sessionMode.enabledExtensions))
|
extensionState = extensionModule;
|
||||||
return Main.sessionMode.enabledExtensions;
|
extension.stateObj = extensionState;
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateCanChange(extension) {
|
extension.state = ExtensionState.DISABLED;
|
||||||
let hasError =
|
_signals.emit('extension-loaded', uuid);
|
||||||
extension.state == ExtensionState.ERROR ||
|
return true;
|
||||||
extension.state == ExtensionState.OUT_OF_DATE;
|
}
|
||||||
|
|
||||||
let isMode = this._getModeExtensions().includes(extension.uuid);
|
function getEnabledExtensions() {
|
||||||
let modeOnly = global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY);
|
let extensions;
|
||||||
|
if (Array.isArray(Main.sessionMode.enabledExtensions))
|
||||||
|
extensions = Main.sessionMode.enabledExtensions;
|
||||||
|
else
|
||||||
|
extensions = [];
|
||||||
|
|
||||||
let changeKey = isMode
|
if (global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY))
|
||||||
? DISABLE_USER_EXTENSIONS_KEY
|
return extensions;
|
||||||
: ENABLED_EXTENSIONS_KEY;
|
|
||||||
|
|
||||||
extension.canChange =
|
return extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY));
|
||||||
!hasError &&
|
}
|
||||||
global.settings.is_writable(changeKey) &&
|
|
||||||
(isMode || !modeOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
_getEnabledExtensions() {
|
function onEnabledExtensionsChanged() {
|
||||||
let extensions = this._getModeExtensions();
|
let newEnabledExtensions = getEnabledExtensions();
|
||||||
|
|
||||||
if (!global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY))
|
if (!enabled)
|
||||||
extensions = extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY));
|
return;
|
||||||
|
|
||||||
// filter out 'disabled-extensions' which takes precedence
|
// Find and enable all the newly enabled extensions: UUIDs found in the
|
||||||
let disabledExtensions = global.settings.get_strv(DISABLED_EXTENSIONS_KEY);
|
// new setting, but not in the old one.
|
||||||
return extensions.filter(item => !disabledExtensions.includes(item));
|
newEnabledExtensions.filter(
|
||||||
}
|
uuid => !enabledExtensions.includes(uuid)
|
||||||
|
).forEach(uuid => {
|
||||||
|
enableExtension(uuid);
|
||||||
|
});
|
||||||
|
|
||||||
_onUserExtensionsEnabledChanged() {
|
// Find and disable all the newly disabled extensions: UUIDs found in the
|
||||||
this._onEnabledExtensionsChanged();
|
// old setting, but not in the new one.
|
||||||
this._onSettingsWritableChanged();
|
enabledExtensions.filter(
|
||||||
}
|
item => !newEnabledExtensions.includes(item)
|
||||||
|
).forEach(uuid => {
|
||||||
|
disableExtension(uuid);
|
||||||
|
});
|
||||||
|
|
||||||
_onEnabledExtensionsChanged() {
|
enabledExtensions = newEnabledExtensions;
|
||||||
let newEnabledExtensions = this._getEnabledExtensions();
|
}
|
||||||
|
|
||||||
if (!this._enabled)
|
function _onVersionValidationChanged() {
|
||||||
return;
|
// we want to reload all extensions, but only enable
|
||||||
|
// extensions when allowed by the sessionMode, so
|
||||||
|
// temporarily disable them all
|
||||||
|
enabledExtensions = [];
|
||||||
|
for (let uuid in ExtensionUtils.extensions)
|
||||||
|
reloadExtension(ExtensionUtils.extensions[uuid]);
|
||||||
|
enabledExtensions = getEnabledExtensions();
|
||||||
|
|
||||||
// Find and enable all the newly enabled extensions: UUIDs found in the
|
if (Main.sessionMode.allowExtensions) {
|
||||||
// new setting, but not in the old one.
|
enabledExtensions.forEach(uuid => {
|
||||||
newEnabledExtensions.filter(
|
enableExtension(uuid);
|
||||||
uuid => !this._enabledExtensions.includes(uuid)
|
|
||||||
).forEach(uuid => {
|
|
||||||
this._callExtensionEnable(uuid);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Find and disable all the newly disabled extensions: UUIDs found in the
|
function _loadExtensions() {
|
||||||
// old setting, but not in the new one.
|
global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`, onEnabledExtensionsChanged);
|
||||||
this._enabledExtensions.filter(
|
global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`, onEnabledExtensionsChanged);
|
||||||
item => !newEnabledExtensions.includes(item)
|
global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`, _onVersionValidationChanged);
|
||||||
).forEach(uuid => {
|
|
||||||
this._callExtensionDisable(uuid);
|
enabledExtensions = getEnabledExtensions();
|
||||||
|
|
||||||
|
let finder = new ExtensionUtils.ExtensionFinder();
|
||||||
|
finder.connect('extension-found', (finder, extension) => {
|
||||||
|
loadExtension(extension);
|
||||||
|
});
|
||||||
|
finder.scanExtensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableAllExtensions() {
|
||||||
|
if (enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!initted) {
|
||||||
|
_loadExtensions();
|
||||||
|
initted = true;
|
||||||
|
} else {
|
||||||
|
enabledExtensions.forEach(uuid => {
|
||||||
|
enableExtension(uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._enabledExtensions = newEnabledExtensions;
|
|
||||||
}
|
}
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
_onSettingsWritableChanged() {
|
function disableAllExtensions() {
|
||||||
for (let extension of this._extensions.values()) {
|
if (!enabled)
|
||||||
this._updateCanChange(extension);
|
return;
|
||||||
this.emit('extension-state-changed', extension);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onVersionValidationChanged() {
|
if (initted) {
|
||||||
// we want to reload all extensions, but only enable
|
extensionOrder.slice().reverse().forEach(uuid => {
|
||||||
// extensions when allowed by the sessionMode, so
|
disableExtension(uuid);
|
||||||
// temporarily disable them all
|
|
||||||
this._enabledExtensions = [];
|
|
||||||
|
|
||||||
// The loop modifies the extensions map, so iterate over a copy
|
|
||||||
let extensions = [...this._extensions.values()];
|
|
||||||
for (let extension of extensions)
|
|
||||||
this.reloadExtension(extension);
|
|
||||||
this._enabledExtensions = this._getEnabledExtensions();
|
|
||||||
|
|
||||||
if (Main.sessionMode.allowExtensions) {
|
|
||||||
this._enabledExtensions.forEach(uuid => {
|
|
||||||
this._callExtensionEnable(uuid);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadExtensions() {
|
|
||||||
global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`,
|
|
||||||
this._onEnabledExtensionsChanged.bind(this));
|
|
||||||
global.settings.connect(`changed::${DISABLED_EXTENSIONS_KEY}`,
|
|
||||||
this._onEnabledExtensionsChanged.bind(this));
|
|
||||||
global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`,
|
|
||||||
this._onUserExtensionsEnabledChanged.bind(this));
|
|
||||||
global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`,
|
|
||||||
this._onVersionValidationChanged.bind(this));
|
|
||||||
global.settings.connect(`writable-changed::${ENABLED_EXTENSIONS_KEY}`,
|
|
||||||
this._onSettingsWritableChanged.bind(this));
|
|
||||||
global.settings.connect(`writable-changed::${DISABLED_EXTENSIONS_KEY}`,
|
|
||||||
this._onSettingsWritableChanged.bind(this));
|
|
||||||
|
|
||||||
this._enabledExtensions = this._getEnabledExtensions();
|
|
||||||
|
|
||||||
let perUserDir = Gio.File.new_for_path(global.userdatadir);
|
|
||||||
FileUtils.collectFromDatadirs('extensions', true, (dir, info) => {
|
|
||||||
let fileType = info.get_file_type();
|
|
||||||
if (fileType != Gio.FileType.DIRECTORY)
|
|
||||||
return;
|
|
||||||
let uuid = info.get_name();
|
|
||||||
let existing = this.lookup(uuid);
|
|
||||||
if (existing) {
|
|
||||||
log(`Extension ${uuid} already installed in ${existing.path}. ${dir.get_path()} will not be loaded`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let extension;
|
|
||||||
let type = dir.has_prefix(perUserDir)
|
|
||||||
? ExtensionType.PER_USER
|
|
||||||
: ExtensionType.SYSTEM;
|
|
||||||
try {
|
|
||||||
extension = this.createExtensionObject(uuid, dir, type);
|
|
||||||
} catch (e) {
|
|
||||||
logError(e, `Could not load extension ${uuid}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loadExtension(extension);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_enableAllExtensions() {
|
enabled = false;
|
||||||
if (this._enabled)
|
}
|
||||||
return;
|
|
||||||
|
|
||||||
if (!this._initted) {
|
function _sessionUpdated() {
|
||||||
this._loadExtensions();
|
// For now sessionMode.allowExtensions controls extensions from both the
|
||||||
this._initted = true;
|
// 'enabled-extensions' preference and the sessionMode.enabledExtensions
|
||||||
} else {
|
// property; it might make sense to make enabledExtensions independent
|
||||||
this._enabledExtensions.forEach(uuid => {
|
// from allowExtensions in the future
|
||||||
this._callExtensionEnable(uuid);
|
if (Main.sessionMode.allowExtensions) {
|
||||||
});
|
if (initted)
|
||||||
}
|
enabledExtensions = getEnabledExtensions();
|
||||||
this._enabled = true;
|
enableAllExtensions();
|
||||||
|
} else {
|
||||||
|
disableAllExtensions();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_disableAllExtensions() {
|
function init() {
|
||||||
if (!this._enabled)
|
Main.sessionMode.connect('updated', _sessionUpdated);
|
||||||
return;
|
_sessionUpdated();
|
||||||
|
}
|
||||||
if (this._initted) {
|
|
||||||
this._extensionOrder.slice().reverse().forEach(uuid => {
|
|
||||||
this._callExtensionDisable(uuid);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this._enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_sessionUpdated() {
|
|
||||||
// For now sessionMode.allowExtensions controls extensions from both the
|
|
||||||
// 'enabled-extensions' preference and the sessionMode.enabledExtensions
|
|
||||||
// property; it might make sense to make enabledExtensions independent
|
|
||||||
// from allowExtensions in the future
|
|
||||||
if (Main.sessionMode.allowExtensions) {
|
|
||||||
if (this._initted)
|
|
||||||
this._enabledExtensions = this._getEnabledExtensions();
|
|
||||||
this._enableAllExtensions();
|
|
||||||
} else {
|
|
||||||
this._disableAllExtensions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Signals.addSignalMethods(ExtensionManager.prototype);
|
|
||||||
|
@@ -7,14 +7,13 @@ const Signals = imports.signals;
|
|||||||
const System = imports.system;
|
const System = imports.system;
|
||||||
|
|
||||||
const History = imports.misc.history;
|
const History = imports.misc.history;
|
||||||
|
const ExtensionSystem = imports.ui.extensionSystem;
|
||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
const ShellEntry = imports.ui.shellEntry;
|
const ShellEntry = imports.ui.shellEntry;
|
||||||
const Tweener = imports.ui.tweener;
|
const Tweener = imports.ui.tweener;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const JsParse = imports.misc.jsParse;
|
const JsParse = imports.misc.jsParse;
|
||||||
|
|
||||||
const { ExtensionState } = ExtensionUtils;
|
|
||||||
|
|
||||||
const CHEVRON = '>>> ';
|
const CHEVRON = '>>> ';
|
||||||
|
|
||||||
/* Imports...feel free to add here as needed */
|
/* Imports...feel free to add here as needed */
|
||||||
@@ -624,16 +623,15 @@ var Extensions = class Extensions {
|
|||||||
this._extensionsList.add(this._noExtensions);
|
this._extensionsList.add(this._noExtensions);
|
||||||
this.actor.add(this._extensionsList);
|
this.actor.add(this._extensionsList);
|
||||||
|
|
||||||
Main.extensionManager.getUuids().forEach(uuid => {
|
for (let uuid in ExtensionUtils.extensions)
|
||||||
this._loadExtension(null, uuid);
|
this._loadExtension(null, uuid);
|
||||||
});
|
|
||||||
|
|
||||||
Main.extensionManager.connect('extension-loaded',
|
ExtensionSystem.connect('extension-loaded',
|
||||||
this._loadExtension.bind(this));
|
this._loadExtension.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadExtension(o, uuid) {
|
_loadExtension(o, uuid) {
|
||||||
let extension = Main.extensionManager.lookup(uuid);
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
// There can be cases where we create dummy extension metadata
|
// There can be cases where we create dummy extension metadata
|
||||||
// that's not really a proper extension. Don't bother with these.
|
// that's not really a proper extension. Don't bother with these.
|
||||||
if (!extension.metadata.name)
|
if (!extension.metadata.name)
|
||||||
@@ -690,16 +688,16 @@ var Extensions = class Extensions {
|
|||||||
|
|
||||||
_stateToString(extensionState) {
|
_stateToString(extensionState) {
|
||||||
switch (extensionState) {
|
switch (extensionState) {
|
||||||
case ExtensionState.ENABLED:
|
case ExtensionSystem.ExtensionState.ENABLED:
|
||||||
return _("Enabled");
|
return _("Enabled");
|
||||||
case ExtensionState.DISABLED:
|
case ExtensionSystem.ExtensionState.DISABLED:
|
||||||
case ExtensionState.INITIALIZED:
|
case ExtensionSystem.ExtensionState.INITIALIZED:
|
||||||
return _("Disabled");
|
return _("Disabled");
|
||||||
case ExtensionState.ERROR:
|
case ExtensionSystem.ExtensionState.ERROR:
|
||||||
return _("Error");
|
return _("Error");
|
||||||
case ExtensionState.OUT_OF_DATE:
|
case ExtensionSystem.ExtensionState.OUT_OF_DATE:
|
||||||
return _("Out of date");
|
return _("Out of date");
|
||||||
case ExtensionState.DOWNLOADING:
|
case ExtensionSystem.ExtensionState.DOWNLOADING:
|
||||||
return _("Downloading");
|
return _("Downloading");
|
||||||
}
|
}
|
||||||
return 'Unknown'; // Not translated, shouldn't appear
|
return 'Unknown'; // Not translated, shouldn't appear
|
||||||
|
@@ -46,7 +46,6 @@ const LOG_DOMAIN = 'GNOME Shell';
|
|||||||
const GNOMESHELL_STARTED_MESSAGE_ID = 'f3ea493c22934e26811cd62abe8e203a';
|
const GNOMESHELL_STARTED_MESSAGE_ID = 'f3ea493c22934e26811cd62abe8e203a';
|
||||||
|
|
||||||
var componentManager = null;
|
var componentManager = null;
|
||||||
var extensionManager = null;
|
|
||||||
var panel = null;
|
var panel = null;
|
||||||
var overview = null;
|
var overview = null;
|
||||||
var runDialog = null;
|
var runDialog = null;
|
||||||
@@ -227,8 +226,7 @@ function _initializeUI() {
|
|||||||
_startDate = new Date();
|
_startDate = new Date();
|
||||||
|
|
||||||
ExtensionDownloader.init();
|
ExtensionDownloader.init();
|
||||||
extensionManager = new ExtensionSystem.ExtensionManager();
|
ExtensionSystem.init();
|
||||||
extensionManager.init();
|
|
||||||
|
|
||||||
if (sessionMode.isGreeter && screenShield) {
|
if (sessionMode.isGreeter && screenShield) {
|
||||||
layoutManager.connect('startup-prepared', () => {
|
layoutManager.connect('startup-prepared', () => {
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
const { Gio, GLib, Meta, Shell } = imports.gi;
|
const { Gio, GLib, Meta, Shell } = imports.gi;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
|
||||||
const Config = imports.misc.config;
|
const Config = imports.misc.config;
|
||||||
|
const ExtensionSystem = imports.ui.extensionSystem;
|
||||||
const ExtensionDownloader = imports.ui.extensionDownloader;
|
const ExtensionDownloader = imports.ui.extensionDownloader;
|
||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
@@ -249,26 +251,59 @@ var GnomeShellExtensions = class {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellExtensionsIface, this);
|
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellExtensionsIface, this);
|
||||||
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
|
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
|
||||||
Main.extensionManager.connect('extension-state-changed',
|
ExtensionSystem.connect('extension-state-changed',
|
||||||
this._extensionStateChanged.bind(this));
|
this._extensionStateChanged.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
ListExtensions() {
|
ListExtensions() {
|
||||||
let out = {};
|
let out = {};
|
||||||
Main.extensionManager.getUuids().forEach(uuid => {
|
for (let uuid in ExtensionUtils.extensions) {
|
||||||
let dbusObj = this.GetExtensionInfo(uuid);
|
let dbusObj = this.GetExtensionInfo(uuid);
|
||||||
out[uuid] = dbusObj;
|
out[uuid] = dbusObj;
|
||||||
});
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
GetExtensionInfo(uuid) {
|
GetExtensionInfo(uuid) {
|
||||||
let extension = Main.extensionManager.lookup(uuid) || {};
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
return ExtensionUtils.serializeExtension(extension);
|
if (!extension)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
let obj = {};
|
||||||
|
Lang.copyProperties(extension.metadata, obj);
|
||||||
|
|
||||||
|
// Only serialize the properties that we actually need.
|
||||||
|
const serializedProperties = ["type", "state", "path", "error", "hasPrefs"];
|
||||||
|
|
||||||
|
serializedProperties.forEach(prop => {
|
||||||
|
obj[prop] = extension[prop];
|
||||||
|
});
|
||||||
|
|
||||||
|
let out = {};
|
||||||
|
for (let key in obj) {
|
||||||
|
let val = obj[key];
|
||||||
|
let type;
|
||||||
|
switch (typeof val) {
|
||||||
|
case 'string':
|
||||||
|
type = 's';
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
type = 'd';
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
type = 'b';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out[key] = GLib.Variant.new(type, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
GetExtensionErrors(uuid) {
|
GetExtensionErrors(uuid) {
|
||||||
let extension = Main.extensionManager.lookup(uuid);
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
if (!extension)
|
if (!extension)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
@@ -286,14 +321,6 @@ var GnomeShellExtensions = class {
|
|||||||
return ExtensionDownloader.uninstallExtension(uuid);
|
return ExtensionDownloader.uninstallExtension(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
EnableExtension(uuid) {
|
|
||||||
return Main.extensionManager.enableExtension(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
DisableExtension(uuid) {
|
|
||||||
return Main.extensionManager.disableExtension(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchExtensionPrefs(uuid) {
|
LaunchExtensionPrefs(uuid) {
|
||||||
let appSys = Shell.AppSystem.get_default();
|
let appSys = Shell.AppSystem.get_default();
|
||||||
let app = appSys.lookup_app('gnome-shell-extension-prefs.desktop');
|
let app = appSys.lookup_app('gnome-shell-extension-prefs.desktop');
|
||||||
@@ -304,11 +331,11 @@ var GnomeShellExtensions = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReloadExtension(uuid) {
|
ReloadExtension(uuid) {
|
||||||
let extension = Main.extensionManager.lookup(uuid);
|
let extension = ExtensionUtils.extensions[uuid];
|
||||||
if (!extension)
|
if (!extension)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Main.extensionManager.reloadExtension(extension);
|
ExtensionSystem.reloadExtension(extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckForUpdates() {
|
CheckForUpdates() {
|
||||||
@@ -320,10 +347,6 @@ var GnomeShellExtensions = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_extensionStateChanged(_, newState) {
|
_extensionStateChanged(_, newState) {
|
||||||
let state = ExtensionUtils.serializeExtension(newState);
|
|
||||||
this._dbusImpl.emit_signal('ExtensionStateChanged',
|
|
||||||
new GLib.Variant('(sa{sv})', [newState.uuid, state]));
|
|
||||||
|
|
||||||
this._dbusImpl.emit_signal('ExtensionStatusChanged',
|
this._dbusImpl.emit_signal('ExtensionStatusChanged',
|
||||||
GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error]));
|
GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error]));
|
||||||
}
|
}
|
||||||
|
@@ -114,15 +114,8 @@ var Indicator = class extends PanelMenu.SystemIndicator {
|
|||||||
? 'battery-level-100-charged-symbolic'
|
? 'battery-level-100-charged-symbolic'
|
||||||
: `battery-level-${fillLevel}${chargingState}-symbolic`;
|
: `battery-level-${fillLevel}${chargingState}-symbolic`;
|
||||||
|
|
||||||
// Make sure we fall back to fallback-icon-name and not GThemedIcon's
|
this._indicator.icon_name = icon;
|
||||||
// default fallbacks
|
this._item.icon.icon_name = icon;
|
||||||
let gicon = new Gio.ThemedIcon({
|
|
||||||
name: icon,
|
|
||||||
use_default_fallbacks: false
|
|
||||||
});
|
|
||||||
|
|
||||||
this._indicator.gicon = gicon;
|
|
||||||
this._item.icon.gicon = gicon;
|
|
||||||
|
|
||||||
let fallbackIcon = this._proxy.IconName;
|
let fallbackIcon = this._proxy.IconName;
|
||||||
this._indicator.fallback_icon_name = fallbackIcon;
|
this._indicator.fallback_icon_name = fallbackIcon;
|
||||||
|
@@ -2132,8 +2132,6 @@ var WindowManager = class {
|
|||||||
let [action,,, target] = binding.get_name().split('-');
|
let [action,,, target] = binding.get_name().split('-');
|
||||||
let newWs;
|
let newWs;
|
||||||
let direction;
|
let direction;
|
||||||
let vertical = workspaceManager.layout_rows == -1;
|
|
||||||
let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;
|
|
||||||
|
|
||||||
if (action == 'move') {
|
if (action == 'move') {
|
||||||
// "Moving" a window to another workspace doesn't make sense when
|
// "Moving" a window to another workspace doesn't make sense when
|
||||||
@@ -2146,12 +2144,7 @@ var WindowManager = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (target == 'last') {
|
if (target == 'last') {
|
||||||
if (vertical)
|
direction = Meta.MotionDirection.DOWN;
|
||||||
direction = Meta.MotionDirection.DOWN;
|
|
||||||
else if (rtl)
|
|
||||||
direction = Meta.MotionDirection.LEFT;
|
|
||||||
else
|
|
||||||
direction = Meta.MotionDirection.RIGHT;
|
|
||||||
newWs = workspaceManager.get_workspace_by_index(workspaceManager.n_workspaces - 1);
|
newWs = workspaceManager.get_workspace_by_index(workspaceManager.n_workspaces - 1);
|
||||||
} else if (isNaN(target)) {
|
} else if (isNaN(target)) {
|
||||||
// Prepend a new workspace dynamically
|
// Prepend a new workspace dynamically
|
||||||
@@ -2167,33 +2160,16 @@ var WindowManager = class {
|
|||||||
target--;
|
target--;
|
||||||
newWs = workspaceManager.get_workspace_by_index(target);
|
newWs = workspaceManager.get_workspace_by_index(target);
|
||||||
|
|
||||||
if (workspaceManager.get_active_workspace().index() > target) {
|
if (workspaceManager.get_active_workspace().index() > target)
|
||||||
if (vertical)
|
direction = Meta.MotionDirection.UP;
|
||||||
direction = Meta.MotionDirection.UP;
|
else
|
||||||
else if (rtl)
|
direction = Meta.MotionDirection.DOWN;
|
||||||
direction = Meta.MotionDirection.RIGHT;
|
|
||||||
else
|
|
||||||
direction = Meta.MotionDirection.LEFT;
|
|
||||||
} else {
|
|
||||||
if (vertical)
|
|
||||||
direction = Meta.MotionDirection.DOWN;
|
|
||||||
else if (rtl)
|
|
||||||
direction = Meta.MotionDirection.LEFT;
|
|
||||||
else
|
|
||||||
direction = Meta.MotionDirection.RIGHT;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workspaceManager.layout_rows == -1 &&
|
if (direction != Meta.MotionDirection.UP &&
|
||||||
direction != Meta.MotionDirection.UP &&
|
|
||||||
direction != Meta.MotionDirection.DOWN)
|
direction != Meta.MotionDirection.DOWN)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (workspaceManager.layout_columns == -1 &&
|
|
||||||
direction != Meta.MotionDirection.LEFT &&
|
|
||||||
direction != Meta.MotionDirection.RIGHT)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (action == 'switch')
|
if (action == 'switch')
|
||||||
this.actionMoveWorkspace(newWs);
|
this.actionMoveWorkspace(newWs);
|
||||||
else
|
else
|
||||||
|
@@ -86,7 +86,7 @@ class WindowCloneLayout extends Clutter.LayoutManager {
|
|||||||
if (child == container._delegate._windowClone)
|
if (child == container._delegate._windowClone)
|
||||||
realWindow = container._delegate.realWindow;
|
realWindow = container._delegate.realWindow;
|
||||||
else
|
else
|
||||||
realWindow = child.source;
|
realWindow = child.content.window_actor;
|
||||||
|
|
||||||
child.allocate(this._makeBoxForWindow(realWindow.meta_window),
|
child.allocate(this._makeBoxForWindow(realWindow.meta_window),
|
||||||
flags);
|
flags);
|
||||||
@@ -111,7 +111,7 @@ var WindowClone = GObject.registerClass({
|
|||||||
this.metaWindow._delegate = this;
|
this.metaWindow._delegate = this;
|
||||||
this._workspace = workspace;
|
this._workspace = workspace;
|
||||||
|
|
||||||
this._windowClone = new Clutter.Clone({ source: realWindow });
|
this._windowClone = new Clutter.Actor({ content: realWindow.content });
|
||||||
// We expect this to be used for all interaction rather than
|
// We expect this to be used for all interaction rather than
|
||||||
// this._windowClone; as the former is reactive and the latter
|
// this._windowClone; as the former is reactive and the latter
|
||||||
// is not, this just works for most cases. However, for DND all
|
// is not, this just works for most cases. However, for DND all
|
||||||
@@ -205,7 +205,7 @@ var WindowClone = GObject.registerClass({
|
|||||||
// Delete all windows, starting from the bottom-most (most-modal) one
|
// Delete all windows, starting from the bottom-most (most-modal) one
|
||||||
let windows = this.get_children();
|
let windows = this.get_children();
|
||||||
for (let i = windows.length - 1; i >= 1; i--) {
|
for (let i = windows.length - 1; i >= 1; i--) {
|
||||||
let realWindow = windows[i].source;
|
let realWindow = windows[i].content.window_actor;
|
||||||
let metaWindow = realWindow.meta_window;
|
let metaWindow = realWindow.meta_window;
|
||||||
|
|
||||||
metaWindow.delete(global.get_current_time());
|
metaWindow.delete(global.get_current_time());
|
||||||
@@ -237,7 +237,7 @@ var WindowClone = GObject.registerClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
_doAddAttachedDialog(metaWin, realWin) {
|
_doAddAttachedDialog(metaWin, realWin) {
|
||||||
let clone = new Clutter.Clone({ source: realWin });
|
let clone = new Clutter.Actor({ content: realWin.content });
|
||||||
clone._sizeChangedId = metaWin.connect('size-changed',
|
clone._sizeChangedId = metaWin.connect('size-changed',
|
||||||
this._onMetaWindowSizeChanged.bind(this));
|
this._onMetaWindowSizeChanged.bind(this));
|
||||||
clone._posChangedId = metaWin.connect('position-changed',
|
clone._posChangedId = metaWin.connect('position-changed',
|
||||||
@@ -290,7 +290,7 @@ var WindowClone = GObject.registerClass({
|
|||||||
if (child == this._windowClone)
|
if (child == this._windowClone)
|
||||||
realWindow = this.realWindow;
|
realWindow = this.realWindow;
|
||||||
else
|
else
|
||||||
realWindow = child.source;
|
realWindow = child.content.window_actor;
|
||||||
|
|
||||||
let metaWindow = realWindow.meta_window;
|
let metaWindow = realWindow.meta_window;
|
||||||
rect = rect.union(metaWindow.get_frame_rect());
|
rect = rect.union(metaWindow.get_frame_rect());
|
||||||
@@ -335,7 +335,7 @@ var WindowClone = GObject.registerClass({
|
|||||||
if (child == this._windowClone)
|
if (child == this._windowClone)
|
||||||
realWindow = this.realWindow;
|
realWindow = this.realWindow;
|
||||||
else
|
else
|
||||||
realWindow = child.source;
|
realWindow = child.content.window_actor;
|
||||||
|
|
||||||
realWindow.meta_window.disconnect(child._sizeChangedId);
|
realWindow.meta_window.disconnect(child._sizeChangedId);
|
||||||
realWindow.meta_window.disconnect(child._posChangedId);
|
realWindow.meta_window.disconnect(child._posChangedId);
|
||||||
|
@@ -17,74 +17,41 @@ class WorkspaceSwitcherPopupList extends St.Widget {
|
|||||||
this._itemSpacing = 0;
|
this._itemSpacing = 0;
|
||||||
this._childHeight = 0;
|
this._childHeight = 0;
|
||||||
this._childWidth = 0;
|
this._childWidth = 0;
|
||||||
this._orientation = global.workspace_manager.layout_rows == -1
|
|
||||||
? Clutter.Orientation.VERTICAL
|
|
||||||
: Clutter.Orientation.HORIZONTAL;
|
|
||||||
|
|
||||||
this.connect('style-changed', () => {
|
this.connect('style-changed', () => {
|
||||||
this._itemSpacing = this.get_theme_node().get_length('spacing');
|
this._itemSpacing = this.get_theme_node().get_length('spacing');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPreferredSizeForOrientation(forSize) {
|
vfunc_get_preferred_height(forWidth) {
|
||||||
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||||
let themeNode = this.get_theme_node();
|
let themeNode = this.get_theme_node();
|
||||||
|
|
||||||
let availSize;
|
let availHeight = workArea.height;
|
||||||
if (this._orientation == Clutter.Orientation.HORIZONTAL)
|
availHeight -= themeNode.get_vertical_padding();
|
||||||
availSize = workArea.width - themeNode.get_horizontal_padding();
|
|
||||||
else
|
|
||||||
availSize = workArea.height - themeNode.get_vertical_padding();
|
|
||||||
|
|
||||||
let size = 0;
|
let height = 0;
|
||||||
for (let child of this.get_children()) {
|
for (let child of this.get_children()) {
|
||||||
let [childMinHeight, childNaturalHeight] = child.get_preferred_height(-1);
|
let [childMinHeight, childNaturalHeight] = child.get_preferred_height(-1);
|
||||||
let height = childNaturalHeight * workArea.width / workArea.height;
|
let [childMinWidth, childNaturalWidth] = child.get_preferred_width(childNaturalHeight);
|
||||||
|
height += childNaturalHeight * workArea.width / workArea.height;
|
||||||
if (this._orientation == Clutter.Orientation.HORIZONTAL)
|
|
||||||
size += height * workArea.width / workArea.height;
|
|
||||||
else
|
|
||||||
size += height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let workspaceManager = global.workspace_manager;
|
let workspaceManager = global.workspace_manager;
|
||||||
let spacing = this._itemSpacing * (workspaceManager.n_workspaces - 1);
|
let spacing = this._itemSpacing * (workspaceManager.n_workspaces - 1);
|
||||||
size += spacing;
|
height += spacing;
|
||||||
size = Math.min(size, availSize);
|
height = Math.min(height, availHeight);
|
||||||
|
|
||||||
if (this._orientation == Clutter.Orientation.HORIZONTAL) {
|
this._childHeight = (height - spacing) / workspaceManager.n_workspaces;
|
||||||
this._childWidth = (size - spacing) / workspaceManager.n_workspaces;
|
|
||||||
return themeNode.adjust_preferred_width(size, size);
|
|
||||||
} else {
|
|
||||||
this._childHeight = (size - spacing) / workspaceManager.n_workspaces;
|
|
||||||
return themeNode.adjust_preferred_height(size, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_getSizeForOppositeOrientation() {
|
return themeNode.adjust_preferred_height(height, height);
|
||||||
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
|
||||||
|
|
||||||
if (this._orientation == Clutter.Orientation.HORIZONTAL) {
|
|
||||||
this._childHeight = Math.round(this._childWidth * workArea.height / workArea.width);
|
|
||||||
return [this._childHeight, this._childHeight];
|
|
||||||
} else {
|
|
||||||
this._childWidth = Math.round(this._childHeight * workArea.width / workArea.height);
|
|
||||||
return [this._childWidth, this._childWidth];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vfunc_get_preferred_height(forWidth) {
|
|
||||||
if (this._orientation == Clutter.Orientation.HORIZONTAL)
|
|
||||||
return this._getSizeForOppositeOrientation();
|
|
||||||
else
|
|
||||||
return this._getPreferredSizeForOrientation(forWidth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vfunc_get_preferred_width(forHeight) {
|
vfunc_get_preferred_width(forHeight) {
|
||||||
if (this._orientation == Clutter.Orientation.HORIZONTAL)
|
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||||
return this._getPreferredSizeForOrientation(forHeight);
|
this._childWidth = Math.round(this._childHeight * workArea.width / workArea.height);
|
||||||
else
|
|
||||||
return this._getSizeForOppositeOrientation();
|
return [this._childWidth, this._childWidth];
|
||||||
}
|
}
|
||||||
|
|
||||||
vfunc_allocate(box, flags) {
|
vfunc_allocate(box, flags) {
|
||||||
@@ -95,23 +62,15 @@ class WorkspaceSwitcherPopupList extends St.Widget {
|
|||||||
|
|
||||||
let childBox = new Clutter.ActorBox();
|
let childBox = new Clutter.ActorBox();
|
||||||
|
|
||||||
let rtl = this.text_direction == Clutter.TextDirection.RTL;
|
|
||||||
let x = rtl ? box.x2 - this._childWidth : box.x1;
|
|
||||||
let y = box.y1;
|
let y = box.y1;
|
||||||
|
let prevChildBoxY2 = box.y1 - this._itemSpacing;
|
||||||
for (let child of this.get_children()) {
|
for (let child of this.get_children()) {
|
||||||
childBox.x1 = Math.round(x);
|
childBox.x1 = box.x1;
|
||||||
childBox.x2 = Math.round(x + this._childWidth);
|
childBox.x2 = box.x1 + this._childWidth;
|
||||||
childBox.y1 = Math.round(y);
|
childBox.y1 = prevChildBoxY2 + this._itemSpacing;
|
||||||
childBox.y2 = Math.round(y + this._childHeight);
|
childBox.y2 = Math.round(y + this._childHeight);
|
||||||
|
y += this._childHeight + this._itemSpacing;
|
||||||
if (this._orientation == Clutter.Orientation.HORIZONTAL) {
|
prevChildBoxY2 = childBox.y2;
|
||||||
if (rtl)
|
|
||||||
x -= this._childWidth + this._itemSpacing;
|
|
||||||
else
|
|
||||||
x += this._childWidth + this._itemSpacing;
|
|
||||||
} else {
|
|
||||||
y += this._childHeight + this._itemSpacing;
|
|
||||||
}
|
|
||||||
child.allocate(childBox, flags);
|
child.allocate(childBox, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,10 +121,6 @@ class WorkspaceSwitcherPopup extends St.Widget {
|
|||||||
indicator = new St.Bin({ style_class: 'ws-switcher-active-up' });
|
indicator = new St.Bin({ style_class: 'ws-switcher-active-up' });
|
||||||
else if (i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.DOWN)
|
else if (i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.DOWN)
|
||||||
indicator = new St.Bin({ style_class: 'ws-switcher-active-down' });
|
indicator = new St.Bin({ style_class: 'ws-switcher-active-down' });
|
||||||
else if (i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.LEFT)
|
|
||||||
indicator = new St.Bin({ style_class: 'ws-switcher-active-left' });
|
|
||||||
else if (i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.RIGHT)
|
|
||||||
indicator = new St.Bin({ style_class: 'ws-switcher-active-right' });
|
|
||||||
else
|
else
|
||||||
indicator = new St.Bin({ style_class: 'ws-switcher-box' });
|
indicator = new St.Bin({ style_class: 'ws-switcher-box' });
|
||||||
|
|
||||||
|
@@ -47,7 +47,8 @@ class PrimaryActorLayout extends Clutter.FixedLayout {
|
|||||||
|
|
||||||
var WindowClone = class {
|
var WindowClone = class {
|
||||||
constructor(realWindow) {
|
constructor(realWindow) {
|
||||||
this.clone = new Clutter.Clone({ source: realWindow });
|
this.clone = new Clutter.Actor({ content: realWindow.content,
|
||||||
|
request_mode: Clutter.RequestMode.CONTENT_SIZE });
|
||||||
|
|
||||||
/* Can't use a Shell.GenericContainer because of DND and reparenting... */
|
/* Can't use a Shell.GenericContainer because of DND and reparenting... */
|
||||||
this.actor = new Clutter.Actor({ layout_manager: new PrimaryActorLayout(this.clone),
|
this.actor = new Clutter.Actor({ layout_manager: new PrimaryActorLayout(this.clone),
|
||||||
@@ -139,7 +140,8 @@ var WindowClone = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_doAddAttachedDialog(metaDialog, realDialog) {
|
_doAddAttachedDialog(metaDialog, realDialog) {
|
||||||
let clone = new Clutter.Clone({ source: realDialog });
|
let clone = new Clutter.Actor({ content: realDialog.content,
|
||||||
|
request_mode: Clutter.RequestMode.CONTENT_SIZE });
|
||||||
this._updateDialogPosition(realDialog, clone);
|
this._updateDialogPosition(realDialog, clone);
|
||||||
|
|
||||||
clone._updateId = realDialog.connect('notify::position', dialog => {
|
clone._updateId = realDialog.connect('notify::position', dialog => {
|
||||||
@@ -165,7 +167,7 @@ var WindowClone = class {
|
|||||||
|
|
||||||
_disconnectSignals() {
|
_disconnectSignals() {
|
||||||
this.actor.get_children().forEach(child => {
|
this.actor.get_children().forEach(child => {
|
||||||
let realWindow = child.source;
|
let realWindow = child.content.window_actor;
|
||||||
|
|
||||||
realWindow.disconnect(child._updateId);
|
realWindow.disconnect(child._updateId);
|
||||||
realWindow.disconnect(child._destroyId);
|
realWindow.disconnect(child._destroyId);
|
||||||
|
@@ -189,32 +189,26 @@ var WorkspacesView = class extends WorkspacesViewBase {
|
|||||||
|
|
||||||
Tweener.removeTweens(workspace.actor);
|
Tweener.removeTweens(workspace.actor);
|
||||||
|
|
||||||
let params = {};
|
let y = (w - active) * this._fullGeometry.height;
|
||||||
if (workspaceManager.layout_rows == -1)
|
|
||||||
params.y = (w - active) * this._fullGeometry.height;
|
|
||||||
else if (this.actor.text_direction == Clutter.TextDirection.RTL)
|
|
||||||
params.x = (active - w) * this._fullGeometry.width;
|
|
||||||
else
|
|
||||||
params.x = (w - active) * this._fullGeometry.width;
|
|
||||||
|
|
||||||
if (showAnimation) {
|
if (showAnimation) {
|
||||||
let tweenParams = Object.assign(params, {
|
let params = { y: y,
|
||||||
time: WORKSPACE_SWITCH_TIME,
|
time: WORKSPACE_SWITCH_TIME,
|
||||||
transition: 'easeOutQuad'
|
transition: 'easeOutQuad'
|
||||||
});
|
};
|
||||||
// we have to call _updateVisibility() once before the
|
// we have to call _updateVisibility() once before the
|
||||||
// animation and once afterwards - it does not really
|
// animation and once afterwards - it does not really
|
||||||
// matter which tween we use, so we pick the first one ...
|
// matter which tween we use, so we pick the first one ...
|
||||||
if (w == 0) {
|
if (w == 0) {
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
tweenParams.onComplete = () => {
|
params.onComplete = () => {
|
||||||
this._animating = false;
|
this._animating = false;
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Tweener.addTween(workspace.actor, tweenParams);
|
Tweener.addTween(workspace.actor, params);
|
||||||
} else {
|
} else {
|
||||||
workspace.actor.set(params);
|
workspace.actor.set_position(0, y);
|
||||||
if (w == 0)
|
if (w == 0)
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
}
|
}
|
||||||
@@ -352,39 +346,22 @@ var WorkspacesView = class extends WorkspacesViewBase {
|
|||||||
metaWorkspace.activate(global.get_current_time());
|
metaWorkspace.activate(global.get_current_time());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let last = this._workspaces.length - 1;
|
||||||
|
let firstWorkspaceY = this._workspaces[0].actor.y;
|
||||||
|
let lastWorkspaceY = this._workspaces[last].actor.y;
|
||||||
|
let workspacesHeight = lastWorkspaceY - firstWorkspaceY;
|
||||||
|
|
||||||
if (adj.upper == 1)
|
if (adj.upper == 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let last = this._workspaces.length - 1;
|
let currentY = firstWorkspaceY;
|
||||||
|
let newY = - adj.value / (adj.upper - 1) * workspacesHeight;
|
||||||
|
|
||||||
if (workspaceManager.layout_rows == -1) {
|
let dy = newY - currentY;
|
||||||
let firstWorkspaceY = this._workspaces[0].actor.y;
|
|
||||||
let lastWorkspaceY = this._workspaces[last].actor.y;
|
|
||||||
let workspacesHeight = lastWorkspaceY - firstWorkspaceY;
|
|
||||||
|
|
||||||
let currentY = firstWorkspaceY;
|
for (let i = 0; i < this._workspaces.length; i++) {
|
||||||
let newY = -adj.value / (adj.upper - 1) * workspacesHeight;
|
this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1;
|
||||||
|
this._workspaces[i].actor.y += dy;
|
||||||
let dy = newY - currentY;
|
|
||||||
|
|
||||||
for (let i = 0; i < this._workspaces.length; i++) {
|
|
||||||
this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1;
|
|
||||||
this._workspaces[i].actor.y += dy;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let firstWorkspaceX = this._workspaces[0].actor.x;
|
|
||||||
let lastWorkspaceX = this._workspaces[last].actor.x;
|
|
||||||
let workspacesWidth = lastWorkspaceX - firstWorkspaceX;
|
|
||||||
|
|
||||||
let currentX = firstWorkspaceX;
|
|
||||||
let newX = -adj.value / (adj.upper - 1) * workspacesWidth;
|
|
||||||
|
|
||||||
let dx = newX - currentX;
|
|
||||||
|
|
||||||
for (let i = 0; i < this._workspaces.length; i++) {
|
|
||||||
this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1;
|
|
||||||
this._workspaces[i].actor.x += dx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -532,12 +509,7 @@ var WorkspacesDisplay = class {
|
|||||||
_onPan(action) {
|
_onPan(action) {
|
||||||
let [dist, dx, dy] = action.get_motion_delta(0);
|
let [dist, dx, dy] = action.get_motion_delta(0);
|
||||||
let adjustment = this._scrollAdjustment;
|
let adjustment = this._scrollAdjustment;
|
||||||
if (global.workspace_manager.layout_rows == -1)
|
adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
|
||||||
adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
|
|
||||||
else if (this.actor.text_direction == Clutter.TextDirection.RTL)
|
|
||||||
adjustment.value += (dx / this.actor.width) * adjustment.page_size;
|
|
||||||
else
|
|
||||||
adjustment.value -= (dx / this.actor.width) * adjustment.page_size;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,12 +541,7 @@ var WorkspacesDisplay = class {
|
|||||||
let workspaceManager = global.workspace_manager;
|
let workspaceManager = global.workspace_manager;
|
||||||
let active = workspaceManager.get_active_workspace_index();
|
let active = workspaceManager.get_active_workspace_index();
|
||||||
let adjustment = this._scrollAdjustment;
|
let adjustment = this._scrollAdjustment;
|
||||||
if (workspaceManager.layout_rows == -1)
|
adjustment.value = (active - yRel / this.actor.height) * adjustment.page_size;
|
||||||
adjustment.value = (active - yRel / this.actor.height) * adjustment.page_size;
|
|
||||||
else if (this.actor.text_direction == Clutter.TextDirection.RTL)
|
|
||||||
adjustment.value = (active + xRel / this.actor.width) * adjustment.page_size;
|
|
||||||
else
|
|
||||||
adjustment.value = (active - xRel / this.actor.width) * adjustment.page_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSwitchWorkspaceActivated(action, direction) {
|
_onSwitchWorkspaceActivated(action, direction) {
|
||||||
@@ -798,12 +765,6 @@ var WorkspacesDisplay = class {
|
|||||||
case Clutter.ScrollDirection.DOWN:
|
case Clutter.ScrollDirection.DOWN:
|
||||||
ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN);
|
ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN);
|
||||||
break;
|
break;
|
||||||
case Clutter.ScrollDirection.LEFT:
|
|
||||||
ws = activeWs.get_neighbor(Meta.MotionDirection.LEFT);
|
|
||||||
break;
|
|
||||||
case Clutter.ScrollDirection.RIGHT:
|
|
||||||
ws = activeWs.get_neighbor(Meta.MotionDirection.RIGHT);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return Clutter.EVENT_PROPAGATE;
|
return Clutter.EVENT_PROPAGATE;
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
project('gnome-shell', 'c',
|
project('gnome-shell', 'c',
|
||||||
version: '3.33.4',
|
version: '3.33.3',
|
||||||
meson_version: '>= 0.47.0',
|
meson_version: '>= 0.47.0',
|
||||||
license: 'GPLv2+'
|
license: 'GPLv2+'
|
||||||
)
|
)
|
||||||
@@ -17,11 +17,13 @@ croco_req = '>= 0.6.8'
|
|||||||
ecal_req = '>= 3.33.1'
|
ecal_req = '>= 3.33.1'
|
||||||
eds_req = '>= 3.17.2'
|
eds_req = '>= 3.17.2'
|
||||||
gcr_req = '>= 3.7.5'
|
gcr_req = '>= 3.7.5'
|
||||||
|
gdesktop_req = '>= 3.7.90'
|
||||||
gio_req = '>= 2.56.0'
|
gio_req = '>= 2.56.0'
|
||||||
gi_req = '>= 1.49.1'
|
gi_req = '>= 1.49.1'
|
||||||
gjs_req = '>= 1.57.3'
|
gjs_req = '>= 1.57.3'
|
||||||
gtk_req = '>= 3.15.0'
|
gtk_req = '>= 3.15.0'
|
||||||
mutter_req = '>= 3.33.4'
|
json_glib_req = '>= 0.13.2'
|
||||||
|
mutter_req = '>= 3.33.3'
|
||||||
polkit_req = '>= 0.100'
|
polkit_req = '>= 0.100'
|
||||||
schemas_req = '>= 3.27.90'
|
schemas_req = '>= 3.27.90'
|
||||||
startup_req = '>= 0.11'
|
startup_req = '>= 0.11'
|
||||||
|
10
src/main.c
10
src/main.c
@@ -372,7 +372,7 @@ dump_gjs_stack_alarm_sigaction (int signo)
|
|||||||
static void
|
static void
|
||||||
dump_gjs_stack_on_signal_handler (int signo)
|
dump_gjs_stack_on_signal_handler (int signo)
|
||||||
{
|
{
|
||||||
struct sigaction sa = { .sa_handler = dump_gjs_stack_alarm_sigaction };
|
struct sigaction sa = { 0 };
|
||||||
gsize i;
|
gsize i;
|
||||||
|
|
||||||
/* Ignore all the signals starting this point, a part the one we'll raise
|
/* Ignore all the signals starting this point, a part the one we'll raise
|
||||||
@@ -388,6 +388,7 @@ dump_gjs_stack_on_signal_handler (int signo)
|
|||||||
|
|
||||||
/* Waiting at least 5 seconds for the dumpstack, if it fails, we raise the error */
|
/* Waiting at least 5 seconds for the dumpstack, if it fails, we raise the error */
|
||||||
caught_signal = signo;
|
caught_signal = signo;
|
||||||
|
sa.sa_handler = dump_gjs_stack_alarm_sigaction;
|
||||||
sigemptyset (&sa.sa_mask);
|
sigemptyset (&sa.sa_mask);
|
||||||
sigaction (SIGALRM, &sa, NULL);
|
sigaction (SIGALRM, &sa, NULL);
|
||||||
|
|
||||||
@@ -401,11 +402,10 @@ dump_gjs_stack_on_signal_handler (int signo)
|
|||||||
static void
|
static void
|
||||||
dump_gjs_stack_on_signal (int signo)
|
dump_gjs_stack_on_signal (int signo)
|
||||||
{
|
{
|
||||||
struct sigaction sa = {
|
struct sigaction sa = { 0 };
|
||||||
.sa_flags = SA_RESETHAND | SA_NODEFER,
|
|
||||||
.sa_handler = dump_gjs_stack_on_signal_handler,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
sa.sa_flags = SA_RESETHAND | SA_NODEFER;
|
||||||
|
sa.sa_handler = dump_gjs_stack_on_signal_handler;
|
||||||
sigemptyset (&sa.sa_mask);
|
sigemptyset (&sa.sa_mask);
|
||||||
|
|
||||||
sigaction (signo, &sa, NULL);
|
sigaction (signo, &sa, NULL);
|
||||||
|
@@ -108,7 +108,7 @@ shell_secure_text_buffer_real_insert_text (ClutterTextBuffer *buffer,
|
|||||||
|
|
||||||
/* Actual text insertion */
|
/* Actual text insertion */
|
||||||
at = g_utf8_offset_to_pointer (self->text, position) - self->text;
|
at = g_utf8_offset_to_pointer (self->text, position) - self->text;
|
||||||
memmove (self->text + at + n_bytes, self->text + at, self->text_bytes - at);
|
g_memmove (self->text + at + n_bytes, self->text + at, self->text_bytes - at);
|
||||||
memcpy (self->text + at, chars, n_bytes);
|
memcpy (self->text + at, chars, n_bytes);
|
||||||
|
|
||||||
/* Book keeping */
|
/* Book keeping */
|
||||||
@@ -138,7 +138,7 @@ shell_secure_text_buffer_real_delete_text (ClutterTextBuffer *buffer,
|
|||||||
start = g_utf8_offset_to_pointer (self->text, position) - self->text;
|
start = g_utf8_offset_to_pointer (self->text, position) - self->text;
|
||||||
end = g_utf8_offset_to_pointer (self->text, position + n_chars) - self->text;
|
end = g_utf8_offset_to_pointer (self->text, position + n_chars) - self->text;
|
||||||
|
|
||||||
memmove (self->text + start, self->text + end, self->text_bytes + 1 - end);
|
g_memmove (self->text + start, self->text + end, self->text_bytes + 1 - end);
|
||||||
self->text_chars -= n_chars;
|
self->text_chars -= n_chars;
|
||||||
self->text_bytes -= (end - start);
|
self->text_bytes -= (end - start);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user