Compare commits
63 Commits
Author | SHA1 | Date | |
---|---|---|---|
d471e3a23b | |||
ce1bee727a | |||
43cb3754d9 | |||
1d6ddf060b | |||
9928125e7d | |||
1c63893c4b | |||
a7ec7583aa | |||
4a3476266f | |||
32e0b895a4 | |||
58806359ee | |||
4589da957b | |||
6a4c55b852 | |||
ea17740719 | |||
d82810240f | |||
2768b73015 | |||
f9a7718dda | |||
d9d9778a98 | |||
bd5162105e | |||
208c5e9562 | |||
305e63750e | |||
ab0f74aa15 | |||
43443d08ae | |||
b82b553b9e | |||
08464eadff | |||
49e56776e8 | |||
043667dde5 | |||
f583a7c6d8 | |||
2d908e80fc | |||
8f0e9abe47 | |||
1a27ff6130 | |||
3f2cffc2e6 | |||
a78527050a | |||
a823a213ba | |||
2c8d380e67 | |||
3996309f8a | |||
bd18313d12 | |||
2ff7a78b56 | |||
c765082f72 | |||
7d2c5c1ac9 | |||
404bc34089 | |||
16ca7a21a7 | |||
1b31fd5afe | |||
e0457b6dc4 | |||
42b77e7ba5 | |||
f6bed08993 | |||
5f77cdb0b9 | |||
109b8e8f38 | |||
4c0bd88a2c | |||
3731be9947 | |||
6cc19ee6f0 | |||
1570f838f3 | |||
74feb110b5 | |||
6ba03ac2a6 | |||
55c717c2dc | |||
355b5eebec | |||
51938c398a | |||
dbb71f0dfc | |||
1cac7b2218 | |||
ff9bb5399b | |||
68e45eb051 | |||
d0da96ad29 | |||
55b036170b | |||
5473637736 |
6
.eslintrc.json
Normal file
6
.eslintrc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": [
|
||||
"./lint/eslintrc-gjs.json",
|
||||
"./lint/eslintrc-shell.json"
|
||||
]
|
||||
}
|
26
NEWS
26
NEWS
@ -1,3 +1,29 @@
|
||||
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
|
||||
======
|
||||
* Prepare for optional X11 [Carlos; !378]
|
||||
|
@ -173,6 +173,30 @@
|
||||
<arg type="s" direction="in" name="uuid"/>
|
||||
</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:
|
||||
@uuid: The UUID of the extension
|
||||
@ -189,6 +213,15 @@
|
||||
-->
|
||||
<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">
|
||||
<arg type="s" name="uuid"/>
|
||||
<arg type="i" name="state"/>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<method name="ShowOSD">
|
||||
<arg type="a{sv}" direction="in" name="params"/>
|
||||
</method>
|
||||
<method name="ShowMonitorLabels2">
|
||||
<method name="ShowMonitorLabels">
|
||||
<arg type="a{sv}" direction="in" name="params"/>
|
||||
</method>
|
||||
<method name="HideMonitorLabels"/>
|
||||
|
@ -21,6 +21,17 @@
|
||||
EnableExtension and DisableExtension D-Bus methods on org.gnome.Shell.
|
||||
</description>
|
||||
</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">
|
||||
<default>false</default>
|
||||
<summary>Disable user extensions</summary>
|
||||
|
@ -619,6 +619,18 @@ StScrollBar {
|
||||
app menu inside the main app window itself rather than the top bar
|
||||
*/
|
||||
|
||||
/*************
|
||||
* App Icons *
|
||||
*************/
|
||||
/* Outline for low res icons */
|
||||
.lowres-icon {
|
||||
icon-shadow: 0 1px 2px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Drapshadow for large icons */
|
||||
.icon-dropshadow {
|
||||
icon-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
/* OSD */
|
||||
.osd-window {
|
||||
@ -728,7 +740,8 @@ StScrollBar {
|
||||
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;
|
||||
background-color: $selected_bg_color;
|
||||
color: $selected_fg_color;
|
||||
@ -1326,8 +1339,8 @@ StScrollBar {
|
||||
|
||||
.window-clone-border {
|
||||
$_bg: transparentize(white, 0.65);
|
||||
border: 5px solid $_bg;
|
||||
border-radius: 6px;
|
||||
border: 7px solid $_bg;
|
||||
border-radius: $modal_radius;
|
||||
// For window decorations with round corners we can't match
|
||||
// the exact shape when the window is scaled. So apply a shadow
|
||||
// to fix that case
|
||||
@ -1364,11 +1377,8 @@ StScrollBar {
|
||||
|
||||
//search results
|
||||
|
||||
#searchResultsBin {
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
#searchResultsContent {
|
||||
max-width: 1000px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
spacing: 16px;
|
||||
@ -1484,11 +1494,11 @@ StScrollBar {
|
||||
.search-provider-icon,
|
||||
.list-search-result {
|
||||
@extend %icon_tile;
|
||||
&:active, &:checked { background-color: transparentize(darken($osd_bg_color,10%),.1); }
|
||||
&:focus, &:selected, &:hover {
|
||||
background-color: transparentize($osd_fg_color,.9);
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
&:active, &:checked { background-color: transparentize(darken($osd_bg_color,10%),.1); }
|
||||
}
|
||||
.app-well-app,
|
||||
.app-well-app.app-folder,
|
||||
@ -1497,10 +1507,6 @@ StScrollBar {
|
||||
& .overview-icon {
|
||||
@extend %icon_tile;
|
||||
}
|
||||
&:active .overview-icon,
|
||||
&:checked .overview-icon {
|
||||
background-color: transparentize(darken($osd_bg_color,10%), 0.5);
|
||||
}
|
||||
&:hover .overview-icon,
|
||||
&:focus .overview-icon,
|
||||
&:selected .overview-icon {
|
||||
@ -1509,7 +1515,10 @@ StScrollBar {
|
||||
border-image: none;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
&:active .overview-icon,
|
||||
&:checked .overview-icon {
|
||||
background-color: transparentize(darken($osd_bg_color,10%), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.app-well-app-running-dot { //running apps indicator
|
||||
@ -1599,7 +1608,6 @@ StScrollBar {
|
||||
}
|
||||
|
||||
//Some hacks I don't even
|
||||
.search-display > StBoxLayout,
|
||||
.all-apps,
|
||||
.frequent-apps > StBoxLayout {
|
||||
// horizontal padding to make sure scrollbars or dash don't overlap content
|
||||
|
@ -8,6 +8,8 @@ const Config = imports.misc.config;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const { loadInterfaceXML } = imports.misc.fileUtils;
|
||||
|
||||
const { ExtensionState } = ExtensionUtils;
|
||||
|
||||
const GnomeShellIface = loadInterfaceXML('org.gnome.Shell.Extensions');
|
||||
const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface);
|
||||
|
||||
@ -17,74 +19,54 @@ function stripPrefix(string, prefix) {
|
||||
return string;
|
||||
}
|
||||
|
||||
var Application = class {
|
||||
constructor() {
|
||||
var Application = GObject.registerClass({
|
||||
GTypeName: 'ExtensionPrefs_Application'
|
||||
}, class Application extends Gtk.Application {
|
||||
_init() {
|
||||
GLib.set_prgname('gnome-shell-extension-prefs');
|
||||
this.application = new Gtk.Application({
|
||||
super._init({
|
||||
application_id: 'org.gnome.shell.ExtensionPrefs',
|
||||
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._loaded = false;
|
||||
this._skipMainWindow = false;
|
||||
this._shellProxy = null;
|
||||
}
|
||||
|
||||
_extensionAvailable(uuid) {
|
||||
let extension = ExtensionUtils.extensions[uuid];
|
||||
get shellProxy() {
|
||||
return this._shellProxy;
|
||||
}
|
||||
|
||||
if (!extension)
|
||||
_showPrefs(uuid) {
|
||||
let row = this._extensionSelector.get_children().find(c => {
|
||||
return c.uuid === uuid && c.hasPrefs;
|
||||
});
|
||||
|
||||
if (!row)
|
||||
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;
|
||||
|
||||
try {
|
||||
let prefsModule = this._getExtensionPrefsModule(extension);
|
||||
widget = prefsModule.buildPrefsWidget();
|
||||
widget = row.prefsModule.buildPrefsWidget();
|
||||
} catch (e) {
|
||||
widget = this._buildErrorUI(extension, e);
|
||||
widget = this._buildErrorUI(row, e);
|
||||
}
|
||||
|
||||
let dialog = new Gtk.Window({ modal: !this._skipMainWindow,
|
||||
type_hint: Gdk.WindowTypeHint.DIALOG });
|
||||
dialog.set_titlebar(new Gtk.HeaderBar({ show_close_button: true,
|
||||
title: extension.metadata.name,
|
||||
visible: true }));
|
||||
let dialog = new Gtk.Window({
|
||||
modal: !this._skipMainWindow,
|
||||
type_hint: Gdk.WindowTypeHint.DIALOG
|
||||
});
|
||||
dialog.set_titlebar(new Gtk.HeaderBar({
|
||||
show_close_button: true,
|
||||
title: row.name,
|
||||
visible: true
|
||||
}));
|
||||
|
||||
if (this._skipMainWindow) {
|
||||
this.application.add_window(dialog);
|
||||
this.add_window(dialog);
|
||||
if (this._window)
|
||||
this._window.destroy();
|
||||
this._window = dialog;
|
||||
@ -98,7 +80,7 @@ var Application = class {
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
_buildErrorUI(extension, exc) {
|
||||
_buildErrorUI(row, exc) {
|
||||
let scroll = new Gtk.ScrolledWindow({
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||||
propagate_natural_height: true
|
||||
@ -170,7 +152,7 @@ var Application = class {
|
||||
let clipboard = Gtk.Clipboard.get_default(w.get_display());
|
||||
// markdown for pasting in gitlab issues
|
||||
let lines = [
|
||||
`The settings of extension ${extension.uuid} had an error:`,
|
||||
`The settings of extension ${row.uuid} had an error:`,
|
||||
'```',
|
||||
`${exc}`,
|
||||
'```',
|
||||
@ -192,13 +174,13 @@ var Application = class {
|
||||
label: _("Homepage"),
|
||||
tooltip_text: _("Visit extension homepage"),
|
||||
no_show_all: true,
|
||||
visible: extension.metadata.url != null
|
||||
visible: row.url != null
|
||||
});
|
||||
toolbar.add(urlButton);
|
||||
|
||||
urlButton.connect('clicked', w => {
|
||||
let context = w.get_display().get_app_launch_context();
|
||||
Gio.AppInfo.launch_default_for_uri(extension.metadata.url, context);
|
||||
Gio.AppInfo.launch_default_for_uri(row.url, context);
|
||||
});
|
||||
|
||||
let expandedBox = new Gtk.Box({
|
||||
@ -213,8 +195,8 @@ var Application = class {
|
||||
return scroll;
|
||||
}
|
||||
|
||||
_buildUI(app) {
|
||||
this._window = new Gtk.ApplicationWindow({ application: app,
|
||||
_buildUI() {
|
||||
this._window = new Gtk.ApplicationWindow({ application: this,
|
||||
window_position: Gtk.WindowPosition.CENTER });
|
||||
|
||||
this._window.set_default_size(800, 500);
|
||||
@ -248,18 +230,14 @@ var Application = class {
|
||||
this._mainStack.add_named(new EmptyPlaceholder(), 'placeholder');
|
||||
|
||||
this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell');
|
||||
this._shellProxy.connectSignal('ExtensionStatusChanged', (proxy, senderName, [uuid, state, error]) => {
|
||||
if (ExtensionUtils.extensions[uuid] !== undefined)
|
||||
this._scanExtensions();
|
||||
});
|
||||
this._shellProxy.connectSignal('ExtensionStateChanged',
|
||||
this._onExtensionStateChanged.bind(this));
|
||||
|
||||
this._window.show_all();
|
||||
}
|
||||
|
||||
_sortList(row1, row2) {
|
||||
let name1 = ExtensionUtils.extensions[row1.uuid].metadata.name;
|
||||
let name2 = ExtensionUtils.extensions[row2.uuid].metadata.name;
|
||||
return name1.localeCompare(name2);
|
||||
return row1.name.localeCompare(row2.name);
|
||||
}
|
||||
|
||||
_updateHeader(row, before) {
|
||||
@ -270,19 +248,55 @@ var Application = class {
|
||||
row.set_header(sep);
|
||||
}
|
||||
|
||||
_scanExtensions() {
|
||||
let finder = new ExtensionUtils.ExtensionFinder();
|
||||
finder.connect('extension-found', this._extensionFound.bind(this));
|
||||
finder.scanExtensions();
|
||||
this._extensionsLoaded();
|
||||
_findExtensionRow(uuid) {
|
||||
return this._extensionSelector.get_children().find(c => c.uuid === uuid);
|
||||
}
|
||||
|
||||
_extensionFound(finder, extension) {
|
||||
let row = new ExtensionRow(extension.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() {
|
||||
this._shellProxy.ListExtensionsRemote(([extensionsMap], e) => {
|
||||
if (e) {
|
||||
if (e instanceof Gio.DBusError) {
|
||||
log(`Failed to connect to shell proxy: ${e}`);
|
||||
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) {
|
||||
let row = new ExtensionRow(extension);
|
||||
|
||||
row.prefsButton.visible = this._extensionAvailable(row.uuid);
|
||||
row.prefsButton.connect('clicked', () => {
|
||||
this._selectExtension(row.uuid);
|
||||
this._showPrefs(row.uuid);
|
||||
});
|
||||
|
||||
row.show_all();
|
||||
@ -295,24 +309,26 @@ var Application = class {
|
||||
else
|
||||
this._mainStack.visible_child_name = 'placeholder';
|
||||
|
||||
if (this._startupUuid && this._extensionAvailable(this._startupUuid))
|
||||
this._selectExtension(this._startupUuid);
|
||||
if (this._startupUuid)
|
||||
this._showPrefs(this._startupUuid);
|
||||
this._startupUuid = null;
|
||||
this._skipMainWindow = false;
|
||||
this._loaded = true;
|
||||
}
|
||||
|
||||
_onActivate() {
|
||||
vfunc_activate() {
|
||||
this._window.present();
|
||||
}
|
||||
|
||||
_onStartup(app) {
|
||||
this._buildUI(app);
|
||||
vfunc_startup() {
|
||||
super.vfunc_startup();
|
||||
|
||||
this._buildUI();
|
||||
this._scanExtensions();
|
||||
}
|
||||
|
||||
_onCommandLine(app, commandLine) {
|
||||
app.activate();
|
||||
vfunc_command_line(commandLine) {
|
||||
this.activate();
|
||||
let args = commandLine.get_arguments();
|
||||
|
||||
if (args.length) {
|
||||
@ -323,16 +339,14 @@ var Application = class {
|
||||
// Strip off "extension:///" prefix which fakes a URI, if it exists
|
||||
uuid = stripPrefix(uuid, "extension:///");
|
||||
|
||||
if (this._extensionAvailable(uuid))
|
||||
this._selectExtension(uuid);
|
||||
else if (!this._loaded)
|
||||
if (!this._loaded)
|
||||
this._startupUuid = uuid;
|
||||
else
|
||||
else if (!this._showPrefs(uuid))
|
||||
this._skipMainWindow = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var Expander = GObject.registerClass({
|
||||
Properties: {
|
||||
@ -499,6 +513,35 @@ 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(
|
||||
class DescriptionLabel extends Gtk.Label {
|
||||
vfunc_get_preferred_height_for_width(width) {
|
||||
@ -511,30 +554,55 @@ class DescriptionLabel extends Gtk.Label {
|
||||
|
||||
var ExtensionRow = GObject.registerClass(
|
||||
class ExtensionRow extends Gtk.ListBoxRow {
|
||||
_init(uuid) {
|
||||
_init(extension) {
|
||||
super._init();
|
||||
|
||||
this.uuid = uuid;
|
||||
this._app = Gio.Application.get_default();
|
||||
this._extension = extension;
|
||||
this._prefsModule = null;
|
||||
|
||||
this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' });
|
||||
this._settings.connect('changed::enabled-extensions', () => {
|
||||
this._switch.state = this._isEnabled();
|
||||
});
|
||||
this._settings.connect('changed::disable-extension-version-validation',
|
||||
() => {
|
||||
this._switch.sensitive = this._canEnable();
|
||||
});
|
||||
this._settings.connect('changed::disable-user-extensions',
|
||||
() => {
|
||||
this._switch.sensitive = this._canEnable();
|
||||
this._extensionStateChangedId = this._app.shellProxy.connectSignal(
|
||||
'ExtensionStateChanged', (p, sender, [uuid, newState]) => {
|
||||
if (this.uuid !== uuid)
|
||||
return;
|
||||
|
||||
this._extension = ExtensionUtils.deserializeExtension(newState);
|
||||
let state = (this._extension.state == ExtensionState.ENABLED);
|
||||
this._switch.state = state;
|
||||
this._switch.sensitive = this._canToggle();
|
||||
});
|
||||
|
||||
this.connect('destroy', this._onDestroy.bind(this));
|
||||
|
||||
this._buildUI();
|
||||
}
|
||||
|
||||
_buildUI() {
|
||||
let extension = ExtensionUtils.extensions[this.uuid];
|
||||
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() {
|
||||
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
|
||||
hexpand: true, margin_end: 24, spacing: 24,
|
||||
margin: 12 });
|
||||
@ -544,19 +612,20 @@ class ExtensionRow extends Gtk.ListBoxRow {
|
||||
spacing: 6, hexpand: true });
|
||||
hbox.add(vbox);
|
||||
|
||||
let name = GLib.markup_escape_text(extension.metadata.name, -1);
|
||||
let name = GLib.markup_escape_text(this.name, -1);
|
||||
let label = new Gtk.Label({ label: '<b>' + name + '</b>',
|
||||
use_markup: true,
|
||||
halign: Gtk.Align.START });
|
||||
vbox.add(label);
|
||||
|
||||
let desc = extension.metadata.description.split('\n')[0];
|
||||
let desc = this._extension.metadata.description.split('\n')[0];
|
||||
label = new DescriptionLabel({ label: desc, wrap: true, lines: 2,
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
xalign: 0, yalign: 0 });
|
||||
vbox.add(label);
|
||||
|
||||
let button = new Gtk.Button({ valign: Gtk.Align.CENTER,
|
||||
visible: this.hasPrefs,
|
||||
no_show_all: true });
|
||||
button.set_image(new Gtk.Image({ icon_name: 'emblem-system-symbolic',
|
||||
icon_size: Gtk.IconSize.BUTTON,
|
||||
@ -566,51 +635,37 @@ class ExtensionRow extends Gtk.ListBoxRow {
|
||||
|
||||
this.prefsButton = button;
|
||||
|
||||
this._switch = new Gtk.Switch({ valign: Gtk.Align.CENTER,
|
||||
sensitive: this._canEnable(),
|
||||
state: this._isEnabled() });
|
||||
this._switch = new Gtk.Switch({
|
||||
valign: Gtk.Align.CENTER,
|
||||
sensitive: this._canToggle(),
|
||||
state: this._extension.state === ExtensionState.ENABLED
|
||||
});
|
||||
this._switch.connect('notify::active', () => {
|
||||
if (this._switch.active)
|
||||
this._enable();
|
||||
this._app.shellProxy.EnableExtensionRemote(this.uuid);
|
||||
else
|
||||
this._disable();
|
||||
this._app.shellProxy.DisableExtensionRemote(this.uuid);
|
||||
});
|
||||
this._switch.connect('state-set', () => true);
|
||||
hbox.add(this._switch);
|
||||
}
|
||||
|
||||
_canEnable() {
|
||||
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));
|
||||
_canToggle() {
|
||||
return this._extension.canChange;
|
||||
}
|
||||
|
||||
_isEnabled() {
|
||||
let extensions = this._settings.get_strv('enabled-extensions');
|
||||
return extensions.includes(this.uuid);
|
||||
}
|
||||
get prefsModule() {
|
||||
if (!this._prefsModule) {
|
||||
ExtensionUtils.installImporter(this._extension);
|
||||
|
||||
_enable() {
|
||||
let extensions = this._settings.get_strv('enabled-extensions');
|
||||
if (extensions.includes(this.uuid))
|
||||
return;
|
||||
// give extension prefs access to their own extension object
|
||||
ExtensionUtils.getCurrentExtension = () => this._extension;
|
||||
|
||||
extensions.push(this.uuid);
|
||||
this._settings.set_strv('enabled-extensions', extensions);
|
||||
}
|
||||
this._prefsModule = this._extension.imports.prefs;
|
||||
this._prefsModule.init(this._extension.metadata);
|
||||
}
|
||||
|
||||
_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);
|
||||
return this._prefsModule;
|
||||
}
|
||||
});
|
||||
|
||||
@ -638,6 +693,5 @@ function main(argv) {
|
||||
Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
|
||||
Gettext.textdomain(Config.GETTEXT_PACKAGE);
|
||||
|
||||
let app = new Application();
|
||||
app.application.run(argv);
|
||||
new Application().run(argv);
|
||||
}
|
||||
|
@ -3,21 +3,32 @@
|
||||
// Common utils for the extension system and the extension
|
||||
// preferences tool
|
||||
|
||||
const Gettext = imports.gettext;
|
||||
const Signals = imports.signals;
|
||||
const { Gio, GLib } = imports.gi;
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gettext = imports.gettext;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const FileUtils = imports.misc.fileUtils;
|
||||
|
||||
var ExtensionType = {
|
||||
SYSTEM: 1,
|
||||
PER_USER: 2
|
||||
};
|
||||
|
||||
// Maps uuid -> metadata object
|
||||
var extensions = {};
|
||||
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
|
||||
};
|
||||
|
||||
const SERIALIZED_PROPERTIES = ['type', 'state', 'path', 'error', 'hasPrefs', 'canChange'];
|
||||
|
||||
/**
|
||||
* getCurrentExtension:
|
||||
@ -49,13 +60,17 @@ function getCurrentExtension() {
|
||||
if (!match)
|
||||
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 file = Gio.File.new_for_path(path);
|
||||
|
||||
// Walk up the directory tree, looking for an extension with
|
||||
// the same UUID as a directory name.
|
||||
while (file != null) {
|
||||
let extension = extensions[file.get_basename()];
|
||||
let extension = extensionManager.lookup(file.get_basename());
|
||||
if (extension !== undefined)
|
||||
return extension;
|
||||
file = file.get_parent();
|
||||
@ -161,52 +176,50 @@ function isOutOfDate(extension) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function createExtensionObject(uuid, dir, type) {
|
||||
let metadataFile = dir.get_child('metadata.json');
|
||||
if (!metadataFile.query_exists(null)) {
|
||||
throw new Error('Missing metadata.json');
|
||||
}
|
||||
function serializeExtension(extension) {
|
||||
let obj = {};
|
||||
Lang.copyProperties(extension.metadata, obj);
|
||||
|
||||
let metadataContents, success, tag;
|
||||
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}`);
|
||||
}
|
||||
SERIALIZED_PROPERTIES.forEach(prop => {
|
||||
obj[prop] = extension[prop];
|
||||
});
|
||||
|
||||
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`);
|
||||
let res = {};
|
||||
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;
|
||||
}
|
||||
res[key] = GLib.Variant.new(type, val);
|
||||
}
|
||||
|
||||
if (uuid != meta.uuid) {
|
||||
throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`);
|
||||
return res;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let extension = {};
|
||||
|
||||
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;
|
||||
// add the 2 additional properties to create a valid extension object, as createExtensionObject()
|
||||
res.uuid = res.metadata.uuid;
|
||||
res.dir = Gio.File.new_for_path(res.path);
|
||||
return res;
|
||||
}
|
||||
|
||||
function installImporter(extension) {
|
||||
@ -217,36 +230,3 @@ function installImporter(extension) {
|
||||
extension.imports = imports[extension.uuid];
|
||||
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);
|
||||
|
@ -151,11 +151,7 @@ function getAllProps(obj) {
|
||||
// e.g., expr="({ foo: null, bar: null, 4: null })" will
|
||||
// return ["foo", "bar", ...] but the list will not include "4",
|
||||
// since methods accessed with '.' notation must star with a letter or _.
|
||||
function getPropertyNamesFromExpression(expr, commandHeader) {
|
||||
if (commandHeader == null) {
|
||||
commandHeader = '';
|
||||
}
|
||||
|
||||
function getPropertyNamesFromExpression(expr, commandHeader = '') {
|
||||
let obj = {};
|
||||
if (!isUnsafeExpression(expr)) {
|
||||
try {
|
||||
|
@ -1,7 +1,5 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Lang = imports.lang;
|
||||
|
||||
// parse:
|
||||
// @params: caller-provided parameter object, or %null
|
||||
// @defaults-provided defaults object
|
||||
@ -23,7 +21,6 @@ function parse(params = {}, defaults, allowExtras) {
|
||||
throw new Error(`Unrecognized parameter "${prop}"`);
|
||||
}
|
||||
|
||||
let defaultsCopy = {};
|
||||
Lang.copyProperties(defaults, defaultsCopy);
|
||||
let defaultsCopy = Object.assign({}, defaults);
|
||||
return Object.assign(defaultsCopy, params);
|
||||
}
|
||||
|
@ -1416,7 +1416,7 @@ var AppFolderPopup = class AppFolderPopup {
|
||||
Signals.addSignalMethods(AppFolderPopup.prototype);
|
||||
|
||||
var AppIcon = class AppIcon {
|
||||
constructor(app, iconParams) {
|
||||
constructor(app, iconParams = {}) {
|
||||
this.app = app;
|
||||
this.id = app.get_id();
|
||||
this.name = app.get_name();
|
||||
@ -1442,9 +1442,6 @@ var AppIcon = class AppIcon {
|
||||
|
||||
this.actor._delegate = this;
|
||||
|
||||
if (!iconParams)
|
||||
iconParams = {};
|
||||
|
||||
// Get the isDraggable property without passing it on to the BaseIcon:
|
||||
let appIconParams = Params.parse(iconParams, { isDraggable: true }, true);
|
||||
let isDraggable = appIconParams['isDraggable'];
|
||||
@ -1713,7 +1710,7 @@ var AppIconMenu = class AppIconMenu extends PopupMenu.PopupMenu {
|
||||
let appInfo = this._source.app.get_app_info();
|
||||
let actions = appInfo.list_actions();
|
||||
if (this._source.app.can_open_new_window() &&
|
||||
actions.includes('new-window')) {
|
||||
!actions.includes('new-window')) {
|
||||
this._newWindowMenuItem = this._appendMenuItem(_("New Window"));
|
||||
this._newWindowMenuItem.connect('activate', () => {
|
||||
if (this._source.app.state == Shell.AppState.STOPPED)
|
||||
@ -1727,7 +1724,7 @@ var AppIconMenu = class AppIconMenu extends PopupMenu.PopupMenu {
|
||||
|
||||
if (discreteGpuAvailable &&
|
||||
this._source.app.state == Shell.AppState.STOPPED &&
|
||||
actions.includes('activate-discrete-gpu')) {
|
||||
!actions.includes('activate-discrete-gpu')) {
|
||||
this._onDiscreteGpuMenuItem = this._appendMenuItem(_("Launch using Dedicated Graphics Card"));
|
||||
this._onDiscreteGpuMenuItem.connect('activate', () => {
|
||||
if (this._source.app.state == Shell.AppState.STOPPED)
|
||||
|
@ -633,7 +633,7 @@ var Animation = class Animation {
|
||||
}
|
||||
|
||||
load(callback) {
|
||||
this._show = new GnomeDesktop.BGSlideShow({ filename: this.file.get_path() });
|
||||
this._show = new GnomeDesktop.BGSlideShow({ file: this.file });
|
||||
|
||||
this._show.load_async(null, (object, result) => {
|
||||
this.loaded = true;
|
||||
|
@ -4,7 +4,7 @@ const { Atk, Clutter, St } = imports.gi;
|
||||
const Signals = imports.signals;
|
||||
|
||||
var BarLevel = class {
|
||||
constructor(value, params) {
|
||||
constructor(value, params = {}) {
|
||||
if (isNaN(value))
|
||||
// Avoid spreading NaNs around
|
||||
throw TypeError('The bar level value must be a number');
|
||||
@ -13,9 +13,6 @@ var BarLevel = class {
|
||||
this._overdriveStart = 1;
|
||||
this._barLevelWidth = 0;
|
||||
|
||||
if (params == undefined)
|
||||
params = {};
|
||||
|
||||
this.actor = new St.DrawingArea({ styleClass: params['styleClass'] || 'barlevel',
|
||||
can_focus: params['canFocus'] || false,
|
||||
reactive: params['reactive'] || false,
|
||||
|
@ -237,20 +237,18 @@ var DBusEventSource = class DBusEventSource {
|
||||
|
||||
_onEventsReceived(results, error) {
|
||||
let newEvents = [];
|
||||
let appointments = results ? results[0] : null;
|
||||
if (appointments != null) {
|
||||
for (let n = 0; n < appointments.length; n++) {
|
||||
let a = appointments[n];
|
||||
let date = new Date(a[4] * 1000);
|
||||
let end = new Date(a[5] * 1000);
|
||||
let id = a[0];
|
||||
let summary = a[1];
|
||||
let allDay = a[3];
|
||||
let event = new CalendarEvent(id, date, end, summary, allDay);
|
||||
newEvents.push(event);
|
||||
}
|
||||
newEvents.sort((ev1, ev2) => ev1.date.getTime() - ev2.date.getTime());
|
||||
let appointments = results[0] || [];
|
||||
for (let n = 0; n < appointments.length; n++) {
|
||||
let a = appointments[n];
|
||||
let date = new Date(a[4] * 1000);
|
||||
let end = new Date(a[5] * 1000);
|
||||
let id = a[0];
|
||||
let summary = a[1];
|
||||
let allDay = a[3];
|
||||
let event = new CalendarEvent(id, date, end, summary, allDay);
|
||||
newEvents.push(event);
|
||||
}
|
||||
newEvents.sort((ev1, ev2) => ev1.date.getTime() - ev2.date.getTime());
|
||||
|
||||
this._events = newEvents;
|
||||
this.isLoading = false;
|
||||
|
@ -122,10 +122,7 @@ var ContentTypeDiscoverer = class {
|
||||
}
|
||||
}
|
||||
|
||||
_emitCallback(mount, contentTypes) {
|
||||
if (!contentTypes)
|
||||
contentTypes = [];
|
||||
|
||||
_emitCallback(mount, contentTypes = []) {
|
||||
// we're not interested in win32 software content types here
|
||||
contentTypes = contentTypes.filter(
|
||||
type => (type != 'x-content/win32-software')
|
||||
|
@ -180,10 +180,8 @@ var MessageDialogContent = GObject.registerClass({
|
||||
this._subtitle.clutter_text.set(textProps);
|
||||
this._body.clutter_text.set(textProps);
|
||||
|
||||
if (!params.hasOwnProperty('style_class'))
|
||||
params.style_class = 'message-dialog-main-layout';
|
||||
|
||||
super._init(params);
|
||||
let defaultParams = { style_class: 'message-dialog-main-layout' };
|
||||
super._init(Object.assign(defaultParams, params));
|
||||
|
||||
this.messageBox = new St.BoxLayout({ style_class: 'message-dialog-content',
|
||||
x_expand: true,
|
||||
|
@ -353,8 +353,8 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
|
||||
// It only makes sense to check for this permission if PackageKit is available.
|
||||
try {
|
||||
this._updatesPermission = Polkit.Permission.new_sync(
|
||||
"org.freedesktop.packagekit.trigger-offline-update", null, null);
|
||||
} catch(e) {
|
||||
'org.freedesktop.packagekit.trigger-offline-update', null, null);
|
||||
} catch (e) {
|
||||
log('No permission to trigger offline updates: %s'.format(e.toString()));
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ const { Clutter, Gio, GLib, GObject, Soup } = imports.gi;
|
||||
const Config = imports.misc.config;
|
||||
const Dialog = imports.ui.dialog;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const ExtensionSystem = imports.ui.extensionSystem;
|
||||
const FileUtils = imports.misc.fileUtils;
|
||||
const Main = imports.ui.main;
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
|
||||
var REPOSITORY_URL_BASE = 'https://extensions.gnome.org';
|
||||
@ -24,7 +24,7 @@ function installExtension(uuid, invocation) {
|
||||
|
||||
_httpSession.queue_message(message, (session, message) => {
|
||||
if (message.status_code != Soup.KnownStatusCode.OK) {
|
||||
ExtensionSystem.logExtensionError(uuid, `downloading info: ${message.status_code}`);
|
||||
Main.extensionManager.logExtensionError(uuid, `downloading info: ${message.status_code}`);
|
||||
invocation.return_dbus_error('org.gnome.Shell.DownloadInfoError', message.status_code.toString());
|
||||
return;
|
||||
}
|
||||
@ -33,7 +33,7 @@ function installExtension(uuid, invocation) {
|
||||
try {
|
||||
info = JSON.parse(message.response_body.data);
|
||||
} catch (e) {
|
||||
ExtensionSystem.logExtensionError(uuid, `parsing info: ${e}`);
|
||||
Main.extensionManager.logExtensionError(uuid, `parsing info: ${e}`);
|
||||
invocation.return_dbus_error('org.gnome.Shell.ParseInfoError', e.toString());
|
||||
return;
|
||||
}
|
||||
@ -44,7 +44,7 @@ function installExtension(uuid, invocation) {
|
||||
}
|
||||
|
||||
function uninstallExtension(uuid) {
|
||||
let extension = ExtensionUtils.extensions[uuid];
|
||||
let extension = Main.extensionManager.lookup(uuid);
|
||||
if (!extension)
|
||||
return false;
|
||||
|
||||
@ -52,7 +52,7 @@ function uninstallExtension(uuid) {
|
||||
if (extension.type != ExtensionUtils.ExtensionType.PER_USER)
|
||||
return false;
|
||||
|
||||
if (!ExtensionSystem.unloadExtension(extension))
|
||||
if (!Main.extensionManager.unloadExtension(extension))
|
||||
return false;
|
||||
|
||||
FileUtils.recursivelyDeleteDir(extension.dir, true);
|
||||
@ -113,10 +113,10 @@ function updateExtension(uuid) {
|
||||
|
||||
_httpSession.queue_message(message, (session, message) => {
|
||||
gotExtensionZipFile(session, message, uuid, newExtensionTmpDir, () => {
|
||||
let oldExtension = ExtensionUtils.extensions[uuid];
|
||||
let oldExtension = Main.extensionManager.lookup(uuid);
|
||||
let extensionDir = oldExtension.dir;
|
||||
|
||||
if (!ExtensionSystem.unloadExtension(oldExtension))
|
||||
if (!Main.extensionManager.unloadExtension(oldExtension))
|
||||
return;
|
||||
|
||||
FileUtils.recursivelyMoveDir(extensionDir, oldExtensionTmpDir);
|
||||
@ -125,11 +125,11 @@ function updateExtension(uuid) {
|
||||
let extension = null;
|
||||
|
||||
try {
|
||||
extension = ExtensionUtils.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER);
|
||||
ExtensionSystem.loadExtension(extension);
|
||||
extension = Main.extensionManager.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER);
|
||||
Main.extensionManager.loadExtension(extension);
|
||||
} catch (e) {
|
||||
if (extension)
|
||||
ExtensionSystem.unloadExtension(extension);
|
||||
Main.extensionManager.unloadExtension(extension);
|
||||
|
||||
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
|
||||
// fail here.
|
||||
ExtensionSystem.loadExtension(oldExtension);
|
||||
Main.extensionManager.loadExtension(oldExtension);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -151,9 +151,9 @@ function updateExtension(uuid) {
|
||||
|
||||
function checkForUpdates() {
|
||||
let metadatas = {};
|
||||
for (let uuid in ExtensionUtils.extensions) {
|
||||
metadatas[uuid] = ExtensionUtils.extensions[uuid].metadata;
|
||||
}
|
||||
Main.extensionManager.getUuids().forEach(uuid => {
|
||||
metadatas[uuid] = Main.extensionManager.extensions[uuid].metadata;
|
||||
});
|
||||
|
||||
let params = { shell_version: Config.PACKAGE_VERSION,
|
||||
installed: JSON.stringify(metadatas) };
|
||||
@ -224,16 +224,11 @@ class InstallExtensionDialog extends ModalDialog.ModalDialog {
|
||||
}
|
||||
|
||||
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 {
|
||||
let extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
|
||||
ExtensionSystem.loadExtension(extension);
|
||||
let extension = Main.extensionManager.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
|
||||
Main.extensionManager.loadExtension(extension);
|
||||
if (!Main.extensionManager.enableExtension(uuid))
|
||||
throw new Error(`Cannot add ${uuid} to enabled extensions gsettings key`);
|
||||
} catch (e) {
|
||||
uninstallExtension(uuid);
|
||||
errback('LoadExtensionError', e);
|
||||
|
@ -4,371 +4,519 @@ const { Gio, St } = imports.gi;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const FileUtils = imports.misc.fileUtils;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
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 { ExtensionState, ExtensionType } = ExtensionUtils;
|
||||
|
||||
const ENABLED_EXTENSIONS_KEY = 'enabled-extensions';
|
||||
const DISABLED_EXTENSIONS_KEY = 'disabled-extensions';
|
||||
const DISABLE_USER_EXTENSIONS_KEY = 'disable-user-extensions';
|
||||
const EXTENSION_DISABLE_VERSION_CHECK_KEY = 'disable-extension-version-validation';
|
||||
|
||||
var initted = false;
|
||||
var enabled;
|
||||
var ExtensionManager = class {
|
||||
constructor() {
|
||||
this._initted = false;
|
||||
this._enabled = false;
|
||||
|
||||
function disableExtension(uuid) {
|
||||
let extension = ExtensionUtils.extensions[uuid];
|
||||
if (!extension)
|
||||
return;
|
||||
this._extensions = new Map();
|
||||
this._enabledExtensions = [];
|
||||
this._extensionOrder = [];
|
||||
|
||||
if (extension.state != ExtensionState.ENABLED)
|
||||
return;
|
||||
Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
|
||||
}
|
||||
|
||||
// "Rebase" the extension order by disabling and then enabling extensions
|
||||
// in order to help prevent conflicts.
|
||||
init() {
|
||||
this._sessionUpdated();
|
||||
}
|
||||
|
||||
// Example:
|
||||
// order = [A, B, C, D, E]
|
||||
// user disables C
|
||||
// this should: disable E, disable D, disable C, enable D, enable E
|
||||
lookup(uuid) {
|
||||
return this._extensions.get(uuid);
|
||||
}
|
||||
|
||||
let orderIdx = extensionOrder.indexOf(uuid);
|
||||
let order = extensionOrder.slice(orderIdx + 1);
|
||||
let orderReversed = order.slice().reverse();
|
||||
getUuids() {
|
||||
return [...this._extensions.keys()];
|
||||
}
|
||||
|
||||
_callExtensionDisable(uuid) {
|
||||
let extension = this.lookup(uuid);
|
||||
if (!extension)
|
||||
return;
|
||||
|
||||
if (extension.state != ExtensionState.ENABLED)
|
||||
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;
|
||||
}
|
||||
|
||||
for (let i = 0; i < orderReversed.length; i++) {
|
||||
let uuid = orderReversed[i];
|
||||
try {
|
||||
ExtensionUtils.extensions[uuid].stateObj.disable();
|
||||
extension.stateObj.disable();
|
||||
} catch (e) {
|
||||
logExtensionError(uuid, 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);
|
||||
}
|
||||
}
|
||||
|
||||
if (extension.stylesheet) {
|
||||
_callExtensionEnable(uuid) {
|
||||
let extension = this.lookup(uuid);
|
||||
if (!extension)
|
||||
return;
|
||||
|
||||
if (extension.state == ExtensionState.INITIALIZED)
|
||||
this._callExtensionInit(uuid);
|
||||
|
||||
if (extension.state != ExtensionState.DISABLED)
|
||||
return;
|
||||
|
||||
this._extensionOrder.push(uuid);
|
||||
|
||||
let stylesheetNames = [`${global.session_mode}.css`, 'stylesheet.css'];
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
extension.stateObj.enable();
|
||||
extension.state = ExtensionState.ENABLED;
|
||||
this.emit('extension-state-changed', extension);
|
||||
return;
|
||||
} 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}`);
|
||||
if (extension.stylesheet) {
|
||||
theme.unload_stylesheet(extension.stylesheet);
|
||||
delete extension.stylesheet;
|
||||
}
|
||||
this.logExtensionError(uuid, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
extension.stateObj.enable();
|
||||
extension.state = ExtensionState.ENABLED;
|
||||
_signals.emit('extension-state-changed', extension);
|
||||
return;
|
||||
} catch (e) {
|
||||
if (extension.stylesheet) {
|
||||
theme.unload_stylesheet(extension.stylesheet);
|
||||
delete extension.stylesheet;
|
||||
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);
|
||||
}
|
||||
logExtensionError(uuid, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function logExtensionError(uuid, error) {
|
||||
let extension = ExtensionUtils.extensions[uuid];
|
||||
if (!extension)
|
||||
return;
|
||||
|
||||
let message = `${error}`;
|
||||
|
||||
extension.state = ExtensionState.ERROR;
|
||||
if (!extension.errors)
|
||||
extension.errors = [];
|
||||
extension.errors.push(message);
|
||||
|
||||
log('Extension "%s" had error: %s'.format(uuid, message));
|
||||
_signals.emit('extension-state-changed', { uuid: uuid,
|
||||
error: message,
|
||||
state: extension.state });
|
||||
}
|
||||
|
||||
function 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 {
|
||||
let enabled = enabledExtensions.includes(extension.uuid);
|
||||
if (enabled) {
|
||||
if (!initExtension(extension.uuid))
|
||||
return;
|
||||
if (extension.state == ExtensionState.DISABLED)
|
||||
enableExtension(extension.uuid);
|
||||
} else {
|
||||
extension.state = ExtensionState.INITIALIZED;
|
||||
if (!enabledExtensions.includes(uuid)) {
|
||||
enabledExtensions.push(uuid);
|
||||
global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_signals.emit('extension-state-changed', extension);
|
||||
}
|
||||
disableExtension(uuid) {
|
||||
if (!this._extensions.has(uuid))
|
||||
return false;
|
||||
|
||||
function unloadExtension(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);
|
||||
let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
|
||||
let disabledExtensions = global.settings.get_strv(DISABLED_EXTENSIONS_KEY);
|
||||
|
||||
extension.state = ExtensionState.UNINSTALLED;
|
||||
_signals.emit('extension-state-changed', extension);
|
||||
if (enabledExtensions.includes(uuid)) {
|
||||
enabledExtensions = enabledExtensions.filter(item => item !== uuid);
|
||||
global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
|
||||
}
|
||||
|
||||
delete ExtensionUtils.extensions[extension.uuid];
|
||||
return true;
|
||||
}
|
||||
if (!disabledExtensions.includes(uuid)) {
|
||||
disabledExtensions.push(uuid);
|
||||
global.settings.set_strv(DISABLED_EXTENSIONS_KEY, disabledExtensions);
|
||||
}
|
||||
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
loadExtension(newExtension);
|
||||
}
|
||||
logExtensionError(uuid, error) {
|
||||
let extension = this.lookup(uuid);
|
||||
if (!extension)
|
||||
return;
|
||||
|
||||
function initExtension(uuid) {
|
||||
let extension = ExtensionUtils.extensions[uuid];
|
||||
let dir = extension.dir;
|
||||
let message = `${error}`;
|
||||
|
||||
if (!extension)
|
||||
throw new Error("Extension was not properly created. Call loadExtension first");
|
||||
extension.error = message;
|
||||
extension.state = ExtensionState.ERROR;
|
||||
if (!extension.errors)
|
||||
extension.errors = [];
|
||||
extension.errors.push(message);
|
||||
|
||||
let extensionJs = dir.get_child('extension.js');
|
||||
if (!extensionJs.query_exists(null)) {
|
||||
logExtensionError(uuid, new Error('Missing extension.js'));
|
||||
return false;
|
||||
log('Extension "%s" had error: %s'.format(uuid, message));
|
||||
this.emit('extension-state-changed', extension);
|
||||
}
|
||||
|
||||
let extensionModule;
|
||||
let extensionState = null;
|
||||
createExtensionObject(uuid, dir, type) {
|
||||
let metadataFile = dir.get_child('metadata.json');
|
||||
if (!metadataFile.query_exists(null)) {
|
||||
throw new Error('Missing metadata.json');
|
||||
}
|
||||
|
||||
ExtensionUtils.installImporter(extension);
|
||||
try {
|
||||
extensionModule = extension.imports.extension;
|
||||
} catch (e) {
|
||||
logExtensionError(uuid, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extensionModule.init) {
|
||||
let metadataContents, success;
|
||||
try {
|
||||
extensionState = extensionModule.init(extension);
|
||||
[success, metadataContents] = metadataFile.load_contents(null);
|
||||
if (metadataContents instanceof Uint8Array)
|
||||
metadataContents = imports.byteArray.toString(metadataContents);
|
||||
} catch (e) {
|
||||
logExtensionError(uuid, 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 {
|
||||
let enabled = this._enabledExtensions.includes(extension.uuid);
|
||||
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) {
|
||||
// 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;
|
||||
this.emit('extension-state-changed', extension);
|
||||
|
||||
this._extensions.delete(extension.uuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
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.
|
||||
this.unloadExtension(oldExtension);
|
||||
|
||||
// Now, recreate the extension and load it.
|
||||
let newExtension;
|
||||
try {
|
||||
newExtension = this.createExtensionObject(uuid, dir, type);
|
||||
} catch (e) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!extensionState)
|
||||
extensionState = extensionModule;
|
||||
extension.stateObj = extensionState;
|
||||
_getModeExtensions() {
|
||||
if (Array.isArray(Main.sessionMode.enabledExtensions))
|
||||
return Main.sessionMode.enabledExtensions;
|
||||
return [];
|
||||
}
|
||||
|
||||
extension.state = ExtensionState.DISABLED;
|
||||
_signals.emit('extension-loaded', uuid);
|
||||
return true;
|
||||
}
|
||||
_updateCanChange(extension) {
|
||||
let hasError =
|
||||
extension.state == ExtensionState.ERROR ||
|
||||
extension.state == ExtensionState.OUT_OF_DATE;
|
||||
|
||||
function getEnabledExtensions() {
|
||||
let extensions;
|
||||
if (Array.isArray(Main.sessionMode.enabledExtensions))
|
||||
extensions = Main.sessionMode.enabledExtensions;
|
||||
else
|
||||
extensions = [];
|
||||
let isMode = this._getModeExtensions().includes(extension.uuid);
|
||||
let modeOnly = global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY);
|
||||
|
||||
if (global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY))
|
||||
return extensions;
|
||||
let changeKey = isMode
|
||||
? DISABLE_USER_EXTENSIONS_KEY
|
||||
: ENABLED_EXTENSIONS_KEY;
|
||||
|
||||
return extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY));
|
||||
}
|
||||
extension.canChange =
|
||||
!hasError &&
|
||||
global.settings.is_writable(changeKey) &&
|
||||
(isMode || !modeOnly);
|
||||
}
|
||||
|
||||
function onEnabledExtensionsChanged() {
|
||||
let newEnabledExtensions = getEnabledExtensions();
|
||||
_getEnabledExtensions() {
|
||||
let extensions = this._getModeExtensions();
|
||||
|
||||
if (!enabled)
|
||||
return;
|
||||
if (!global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY))
|
||||
extensions = extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY));
|
||||
|
||||
// Find and enable all the newly enabled extensions: UUIDs found in the
|
||||
// new setting, but not in the old one.
|
||||
newEnabledExtensions.filter(
|
||||
uuid => !enabledExtensions.includes(uuid)
|
||||
).forEach(uuid => {
|
||||
enableExtension(uuid);
|
||||
});
|
||||
// filter out 'disabled-extensions' which takes precedence
|
||||
let disabledExtensions = global.settings.get_strv(DISABLED_EXTENSIONS_KEY);
|
||||
return extensions.filter(item => !disabledExtensions.includes(item));
|
||||
}
|
||||
|
||||
// Find and disable all the newly disabled extensions: UUIDs found in the
|
||||
// old setting, but not in the new one.
|
||||
enabledExtensions.filter(
|
||||
item => !newEnabledExtensions.includes(item)
|
||||
).forEach(uuid => {
|
||||
disableExtension(uuid);
|
||||
});
|
||||
_onUserExtensionsEnabledChanged() {
|
||||
this._onEnabledExtensionsChanged();
|
||||
this._onSettingsWritableChanged();
|
||||
}
|
||||
|
||||
enabledExtensions = newEnabledExtensions;
|
||||
}
|
||||
_onEnabledExtensionsChanged() {
|
||||
let newEnabledExtensions = this._getEnabledExtensions();
|
||||
|
||||
function _onVersionValidationChanged() {
|
||||
// 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();
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
if (Main.sessionMode.allowExtensions) {
|
||||
enabledExtensions.forEach(uuid => {
|
||||
enableExtension(uuid);
|
||||
// Find and enable all the newly enabled extensions: UUIDs found in the
|
||||
// new setting, but not in the old one.
|
||||
newEnabledExtensions.filter(
|
||||
uuid => !this._enabledExtensions.includes(uuid)
|
||||
).forEach(uuid => {
|
||||
this._callExtensionEnable(uuid);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _loadExtensions() {
|
||||
global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`, onEnabledExtensionsChanged);
|
||||
global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`, onEnabledExtensionsChanged);
|
||||
global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`, _onVersionValidationChanged);
|
||||
|
||||
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);
|
||||
// Find and disable all the newly disabled extensions: UUIDs found in the
|
||||
// old setting, but not in the new one.
|
||||
this._enabledExtensions.filter(
|
||||
item => !newEnabledExtensions.includes(item)
|
||||
).forEach(uuid => {
|
||||
this._callExtensionDisable(uuid);
|
||||
});
|
||||
|
||||
this._enabledExtensions = newEnabledExtensions;
|
||||
}
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
function disableAllExtensions() {
|
||||
if (!enabled)
|
||||
return;
|
||||
_onSettingsWritableChanged() {
|
||||
for (let extension of this._extensions.values()) {
|
||||
this._updateCanChange(extension);
|
||||
this.emit('extension-state-changed', extension);
|
||||
}
|
||||
}
|
||||
|
||||
if (initted) {
|
||||
extensionOrder.slice().reverse().forEach(uuid => {
|
||||
disableExtension(uuid);
|
||||
_onVersionValidationChanged() {
|
||||
// we want to reload all extensions, but only enable
|
||||
// extensions when allowed by the sessionMode, so
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
enabled = false;
|
||||
}
|
||||
_enableAllExtensions() {
|
||||
if (this._enabled)
|
||||
return;
|
||||
|
||||
function _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 (initted)
|
||||
enabledExtensions = getEnabledExtensions();
|
||||
enableAllExtensions();
|
||||
} else {
|
||||
disableAllExtensions();
|
||||
if (!this._initted) {
|
||||
this._loadExtensions();
|
||||
this._initted = true;
|
||||
} else {
|
||||
this._enabledExtensions.forEach(uuid => {
|
||||
this._callExtensionEnable(uuid);
|
||||
});
|
||||
}
|
||||
this._enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
Main.sessionMode.connect('updated', _sessionUpdated);
|
||||
_sessionUpdated();
|
||||
}
|
||||
_disableAllExtensions() {
|
||||
if (!this._enabled)
|
||||
return;
|
||||
|
||||
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);
|
||||
|
@ -602,6 +602,8 @@ var IconGrid = GObject.registerClass({
|
||||
}
|
||||
|
||||
_computeLayout(forWidth) {
|
||||
this.ensure_style();
|
||||
|
||||
let nColumns = 0;
|
||||
let usedWidth = this.leftPadding + this.rightPadding;
|
||||
let spacing = this._getSpacing();
|
||||
|
@ -1,6 +1,6 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi;
|
||||
const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Background = imports.ui.background;
|
||||
@ -274,6 +274,13 @@ var LayoutManager = GObject.registerClass({
|
||||
this._backgroundGroup.lower_bottom();
|
||||
this._bgManagers = [];
|
||||
|
||||
this._interfaceSettings = new Gio.Settings({
|
||||
schema_id: 'org.gnome.desktop.interface'
|
||||
});
|
||||
|
||||
this._interfaceSettings.connect('changed::enable-hot-corners',
|
||||
this._updateHotCorners.bind(this));
|
||||
|
||||
// Need to update struts on new workspaces when they are added
|
||||
let workspaceManager = global.workspace_manager;
|
||||
workspaceManager.connect('notify::n-workspaces',
|
||||
@ -377,6 +384,11 @@ var LayoutManager = GObject.registerClass({
|
||||
});
|
||||
this.hotCorners = [];
|
||||
|
||||
if (!this._interfaceSettings.get_boolean('enable-hot-corners')) {
|
||||
this.emit('hot-corners-changed');
|
||||
return;
|
||||
}
|
||||
|
||||
let size = this.panelBox.height;
|
||||
|
||||
// build new hot corners
|
||||
@ -829,7 +841,7 @@ var LayoutManager = GObject.registerClass({
|
||||
// @params can have any of the same values as in addChrome(),
|
||||
// though some possibilities don't make sense. By default, @actor has
|
||||
// the same params as its chrome ancestor.
|
||||
trackChrome(actor, params) {
|
||||
trackChrome(actor, params = {}) {
|
||||
let ancestor = actor.get_parent();
|
||||
let index = this._findActor(ancestor);
|
||||
while (ancestor && index == -1) {
|
||||
@ -839,8 +851,6 @@ var LayoutManager = GObject.registerClass({
|
||||
|
||||
let ancestorData = ancestor ? this._trackedActors[index]
|
||||
: defaultParams;
|
||||
if (!params)
|
||||
params = {};
|
||||
// We can't use Params.parse here because we want to drop
|
||||
// the extra values like ancestorData.actor
|
||||
for (let prop in defaultParams) {
|
||||
|
@ -7,13 +7,14 @@ const Signals = imports.signals;
|
||||
const System = imports.system;
|
||||
|
||||
const History = imports.misc.history;
|
||||
const ExtensionSystem = imports.ui.extensionSystem;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const ShellEntry = imports.ui.shellEntry;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const Main = imports.ui.main;
|
||||
const JsParse = imports.misc.jsParse;
|
||||
|
||||
const { ExtensionState } = ExtensionUtils;
|
||||
|
||||
const CHEVRON = '>>> ';
|
||||
|
||||
/* Imports...feel free to add here as needed */
|
||||
@ -623,15 +624,16 @@ var Extensions = class Extensions {
|
||||
this._extensionsList.add(this._noExtensions);
|
||||
this.actor.add(this._extensionsList);
|
||||
|
||||
for (let uuid in ExtensionUtils.extensions)
|
||||
Main.extensionManager.getUuids().forEach(uuid => {
|
||||
this._loadExtension(null, uuid);
|
||||
});
|
||||
|
||||
ExtensionSystem.connect('extension-loaded',
|
||||
this._loadExtension.bind(this));
|
||||
Main.extensionManager.connect('extension-loaded',
|
||||
this._loadExtension.bind(this));
|
||||
}
|
||||
|
||||
_loadExtension(o, uuid) {
|
||||
let extension = ExtensionUtils.extensions[uuid];
|
||||
let extension = Main.extensionManager.lookup(uuid);
|
||||
// There can be cases where we create dummy extension metadata
|
||||
// that's not really a proper extension. Don't bother with these.
|
||||
if (!extension.metadata.name)
|
||||
@ -688,16 +690,16 @@ var Extensions = class Extensions {
|
||||
|
||||
_stateToString(extensionState) {
|
||||
switch (extensionState) {
|
||||
case ExtensionSystem.ExtensionState.ENABLED:
|
||||
case ExtensionState.ENABLED:
|
||||
return _("Enabled");
|
||||
case ExtensionSystem.ExtensionState.DISABLED:
|
||||
case ExtensionSystem.ExtensionState.INITIALIZED:
|
||||
case ExtensionState.DISABLED:
|
||||
case ExtensionState.INITIALIZED:
|
||||
return _("Disabled");
|
||||
case ExtensionSystem.ExtensionState.ERROR:
|
||||
case ExtensionState.ERROR:
|
||||
return _("Error");
|
||||
case ExtensionSystem.ExtensionState.OUT_OF_DATE:
|
||||
case ExtensionState.OUT_OF_DATE:
|
||||
return _("Out of date");
|
||||
case ExtensionSystem.ExtensionState.DOWNLOADING:
|
||||
case ExtensionState.DOWNLOADING:
|
||||
return _("Downloading");
|
||||
}
|
||||
return 'Unknown'; // Not translated, shouldn't appear
|
||||
|
@ -46,6 +46,7 @@ const LOG_DOMAIN = 'GNOME Shell';
|
||||
const GNOMESHELL_STARTED_MESSAGE_ID = 'f3ea493c22934e26811cd62abe8e203a';
|
||||
|
||||
var componentManager = null;
|
||||
var extensionManager = null;
|
||||
var panel = null;
|
||||
var overview = null;
|
||||
var runDialog = null;
|
||||
@ -226,7 +227,8 @@ function _initializeUI() {
|
||||
_startDate = new Date();
|
||||
|
||||
ExtensionDownloader.init();
|
||||
ExtensionSystem.init();
|
||||
extensionManager = new ExtensionSystem.ExtensionManager();
|
||||
extensionManager.init();
|
||||
|
||||
if (sessionMode.isGreeter && screenShield) {
|
||||
layoutManager.connect('startup-prepared', () => {
|
||||
|
@ -33,9 +33,7 @@ function _fixMarkup(text, allowMarkup) {
|
||||
}
|
||||
|
||||
var URLHighlighter = class URLHighlighter {
|
||||
constructor(text, lineWrap, allowMarkup) {
|
||||
if (!text)
|
||||
text = '';
|
||||
constructor(text = '', lineWrap, allowMarkup) {
|
||||
this.actor = new St.Label({ reactive: true, style_class: 'url-highlighter',
|
||||
x_expand: true, x_align: Clutter.ActorAlign.START });
|
||||
this._linkColor = '#ccccff';
|
||||
|
@ -473,9 +473,7 @@ var Notification = class Notification {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
destroy(reason) {
|
||||
if (!reason)
|
||||
reason = NotificationDestroyedReason.DISMISSED;
|
||||
destroy(reason = NotificationDestroyedReason.DISMISSED) {
|
||||
this.emit('destroy', reason);
|
||||
}
|
||||
};
|
||||
|
@ -151,9 +151,7 @@ var OsdWindow = class {
|
||||
}
|
||||
}
|
||||
|
||||
setMaxLevel(maxLevel) {
|
||||
if (maxLevel === undefined)
|
||||
maxLevel = 100;
|
||||
setMaxLevel(maxLevel = 100) {
|
||||
this._level.maxLevel = maxLevel;
|
||||
}
|
||||
|
||||
|
@ -706,9 +706,7 @@ var PanelCorner = class {
|
||||
|
||||
var AggregateLayout = GObject.registerClass(
|
||||
class AggregateLayout extends Clutter.BoxLayout {
|
||||
_init(params) {
|
||||
if (!params)
|
||||
params = {};
|
||||
_init(params = {}) {
|
||||
params['orientation'] = Clutter.Orientation.VERTICAL;
|
||||
super._init(params);
|
||||
|
||||
@ -1167,7 +1165,7 @@ class Panel extends St.Widget {
|
||||
}
|
||||
|
||||
_onMenuSet(indicator) {
|
||||
if (!indicator.menu || indicator.menu.hasOwnProperty('_openChangedId'))
|
||||
if (!indicator.menu || indicator.menu._openChangedId)
|
||||
return;
|
||||
|
||||
indicator.menu._openChangedId = indicator.menu.connect('open-state-changed',
|
||||
|
@ -14,8 +14,8 @@ const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';
|
||||
var MAX_LIST_SEARCH_RESULTS_ROWS = 5;
|
||||
var MAX_GRID_SEARCH_RESULTS_ROWS = 1;
|
||||
|
||||
var MaxWidthBin = GObject.registerClass(
|
||||
class MaxWidthBin extends St.Bin {
|
||||
var MaxWidthBox = GObject.registerClass(
|
||||
class MaxWidthBox extends St.BoxLayout {
|
||||
vfunc_allocate(box, flags) {
|
||||
let themeNode = this.get_theme_node();
|
||||
let maxWidth = themeNode.get_max_width();
|
||||
@ -309,7 +309,7 @@ var ListSearchResults = class extends SearchResultsBase {
|
||||
}
|
||||
|
||||
_createResultDisplay(meta) {
|
||||
return super._createResultDisplay(meta, this._resultsView) ||
|
||||
return super._createResultDisplay(meta) ||
|
||||
new ListSearchResult(this.provider, meta, this._resultsView);
|
||||
}
|
||||
|
||||
@ -329,12 +329,6 @@ Signals.addSignalMethods(ListSearchResults.prototype);
|
||||
var GridSearchResults = class extends SearchResultsBase {
|
||||
constructor(provider, resultsView) {
|
||||
super(provider, resultsView);
|
||||
// We need to use the parent container to know how much results we can show.
|
||||
// None of the actors in this class can be used for that, since the main actor
|
||||
// goes hidden when no results are displayed, and then it lost its allocation.
|
||||
// Then on the next use of _getMaxDisplayedResults allocation is 0, en therefore
|
||||
// it doesn't show any result although we have some.
|
||||
this._parentContainer = resultsView.actor;
|
||||
|
||||
this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
|
||||
xAlign: St.Align.START });
|
||||
@ -345,10 +339,23 @@ var GridSearchResults = class extends SearchResultsBase {
|
||||
this._resultDisplayBin.set_child(this._bin);
|
||||
}
|
||||
|
||||
updateSearch(...args) {
|
||||
if (this._notifyAllocationId)
|
||||
this.actor.disconnect(this._notifyAllocationId);
|
||||
|
||||
// Make sure the maximum number of results calculated by
|
||||
// _getMaxDisplayedResults() is updated after width changes.
|
||||
this._notifyAllocationId = this.actor.connect('notify::allocation', () => {
|
||||
super.updateSearch(...args);
|
||||
});
|
||||
|
||||
super.updateSearch(...args);
|
||||
}
|
||||
|
||||
_getMaxDisplayedResults() {
|
||||
let parentThemeNode = this._parentContainer.get_theme_node();
|
||||
let availableWidth = parentThemeNode.adjust_for_width(this._parentContainer.width);
|
||||
return this._grid.columnsForWidth(availableWidth) * this._grid.getRowLimit();
|
||||
let allocation = this.actor.allocation;
|
||||
let nCols = this._grid.columnsForWidth(allocation.x2 - allocation.x1);
|
||||
return nCols * this._grid.getRowLimit();
|
||||
}
|
||||
|
||||
_clearResultDisplay() {
|
||||
@ -356,7 +363,7 @@ var GridSearchResults = class extends SearchResultsBase {
|
||||
}
|
||||
|
||||
_createResultDisplay(meta) {
|
||||
return super._createResultDisplay(meta, this._resultsView) ||
|
||||
return super._createResultDisplay(meta) ||
|
||||
new GridSearchResult(this.provider, meta, this._resultsView);
|
||||
}
|
||||
|
||||
@ -378,22 +385,16 @@ var SearchResults = class {
|
||||
this.actor = new St.BoxLayout({ name: 'searchResults',
|
||||
vertical: true });
|
||||
|
||||
this._content = new St.BoxLayout({ name: 'searchResultsContent',
|
||||
vertical: true });
|
||||
this._contentBin = new MaxWidthBin({ name: 'searchResultsBin',
|
||||
x_fill: true,
|
||||
y_fill: true,
|
||||
child: this._content });
|
||||
|
||||
let scrollChild = new St.BoxLayout();
|
||||
scrollChild.add(this._contentBin, { expand: true });
|
||||
this._content = new MaxWidthBox({ name: 'searchResultsContent',
|
||||
vertical: true });
|
||||
|
||||
this._scrollView = new St.ScrollView({ x_fill: true,
|
||||
y_fill: false,
|
||||
overlay_scrollbars: true,
|
||||
style_class: 'search-display vfade' });
|
||||
this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
|
||||
this._scrollView.add_actor(scrollChild);
|
||||
this._scrollView.add_actor(this._content);
|
||||
|
||||
let action = new Clutter.PanAction({ interpolate: true });
|
||||
action.connect('pan', this._onPan.bind(this));
|
||||
this._scrollView.add_action(action);
|
||||
|
@ -1,10 +1,8 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const { Gio, GLib, Meta, Shell } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const ExtensionSystem = imports.ui.extensionSystem;
|
||||
const ExtensionDownloader = imports.ui.extensionDownloader;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Main = imports.ui.main;
|
||||
@ -207,7 +205,7 @@ var GnomeShell = class {
|
||||
this._grabbers.delete(name);
|
||||
}
|
||||
|
||||
ShowMonitorLabels2Async(params, invocation) {
|
||||
ShowMonitorLabelsAsync(params, invocation) {
|
||||
let sender = invocation.get_sender();
|
||||
let [dict] = params;
|
||||
Main.osdMonitorLabeler.show(sender, dict);
|
||||
@ -251,59 +249,26 @@ var GnomeShellExtensions = class {
|
||||
constructor() {
|
||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellExtensionsIface, this);
|
||||
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
|
||||
ExtensionSystem.connect('extension-state-changed',
|
||||
this._extensionStateChanged.bind(this));
|
||||
Main.extensionManager.connect('extension-state-changed',
|
||||
this._extensionStateChanged.bind(this));
|
||||
}
|
||||
|
||||
ListExtensions() {
|
||||
let out = {};
|
||||
for (let uuid in ExtensionUtils.extensions) {
|
||||
Main.extensionManager.getUuids().forEach(uuid => {
|
||||
let dbusObj = this.GetExtensionInfo(uuid);
|
||||
out[uuid] = dbusObj;
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
GetExtensionInfo(uuid) {
|
||||
let extension = ExtensionUtils.extensions[uuid];
|
||||
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;
|
||||
let extension = Main.extensionManager.lookup(uuid) || {};
|
||||
return ExtensionUtils.serializeExtension(extension);
|
||||
}
|
||||
|
||||
GetExtensionErrors(uuid) {
|
||||
let extension = ExtensionUtils.extensions[uuid];
|
||||
let extension = Main.extensionManager.lookup(uuid);
|
||||
if (!extension)
|
||||
return [];
|
||||
|
||||
@ -321,6 +286,14 @@ var GnomeShellExtensions = class {
|
||||
return ExtensionDownloader.uninstallExtension(uuid);
|
||||
}
|
||||
|
||||
EnableExtension(uuid) {
|
||||
return Main.extensionManager.enableExtension(uuid);
|
||||
}
|
||||
|
||||
DisableExtension(uuid) {
|
||||
return Main.extensionManager.disableExtension(uuid);
|
||||
}
|
||||
|
||||
LaunchExtensionPrefs(uuid) {
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
let app = appSys.lookup_app('gnome-shell-extension-prefs.desktop');
|
||||
@ -331,11 +304,11 @@ var GnomeShellExtensions = class {
|
||||
}
|
||||
|
||||
ReloadExtension(uuid) {
|
||||
let extension = ExtensionUtils.extensions[uuid];
|
||||
let extension = Main.extensionManager.lookup(uuid);
|
||||
if (!extension)
|
||||
return;
|
||||
|
||||
ExtensionSystem.reloadExtension(extension);
|
||||
Main.extensionManager.reloadExtension(extension);
|
||||
}
|
||||
|
||||
CheckForUpdates() {
|
||||
@ -347,6 +320,10 @@ var GnomeShellExtensions = class {
|
||||
}
|
||||
|
||||
_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',
|
||||
GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error]));
|
||||
}
|
||||
|
@ -331,11 +331,11 @@ var NMConnectionDevice = class NMConnectionDevice extends NMConnectionSection {
|
||||
|
||||
destroy() {
|
||||
if (this._stateChangedId) {
|
||||
GObject.Object.prototype.disconnect.call(this._device, this._stateChangedId);
|
||||
GObject.signal_handler_disconnect(this._device, this._stateChangedId);
|
||||
this._stateChangedId = 0;
|
||||
}
|
||||
if (this._activeConnectionChangedId) {
|
||||
GObject.Object.prototype.disconnect.call(this._device, this._activeConnectionChangedId);
|
||||
GObject.signal_handler_disconnect(this._device, this._activeConnectionChangedId);
|
||||
this._activeConnectionChangedId = 0;
|
||||
}
|
||||
|
||||
@ -1187,11 +1187,11 @@ var NMDeviceWireless = class {
|
||||
|
||||
destroy() {
|
||||
if (this._activeApChangedId) {
|
||||
GObject.Object.prototype.disconnect.call(this._device, this._activeApChangedId);
|
||||
GObject.signal_handler_disconnect(this._device, this._activeApChangedId);
|
||||
this._activeApChangedId = 0;
|
||||
}
|
||||
if (this._stateChangedId) {
|
||||
GObject.Object.prototype.disconnect.call(this._device, this._stateChangedId);
|
||||
GObject.signal_handler_disconnect(this._device, this._stateChangedId);
|
||||
this._stateChangedId = 0;
|
||||
}
|
||||
if (this._strengthChangedId > 0) {
|
||||
|
@ -107,9 +107,26 @@ var Indicator = class extends PanelMenu.SystemIndicator {
|
||||
}
|
||||
|
||||
// The icons
|
||||
let icon = this._proxy.IconName;
|
||||
this._indicator.icon_name = icon;
|
||||
this._item.icon.icon_name = icon;
|
||||
let chargingState = this._proxy.State == UPower.DeviceState.CHARGING
|
||||
? '-charging' : '';
|
||||
let fillLevel = 10 * Math.floor(this._proxy.Percentage / 10);
|
||||
let icon = this._proxy.State == UPower.DeviceState.FULLY_CHARGED
|
||||
? 'battery-level-100-charged-symbolic'
|
||||
: `battery-level-${fillLevel}${chargingState}-symbolic`;
|
||||
|
||||
// Make sure we fall back to fallback-icon-name and not GThemedIcon's
|
||||
// default fallbacks
|
||||
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;
|
||||
this._indicator.fallback_icon_name = fallbackIcon;
|
||||
this._item.icon.fallback_icon_name = fallbackIcon;
|
||||
|
||||
// The icon label
|
||||
let label;
|
||||
|
@ -1203,41 +1203,9 @@ var WindowManager = class {
|
||||
if (!Meta.prefs_get_dynamic_workspaces())
|
||||
return;
|
||||
|
||||
workspaceManager.append_new_workspace(false, global.get_current_time());
|
||||
|
||||
let windows = global.get_window_actors().map(a => a.meta_window);
|
||||
|
||||
// To create a new workspace, we slide all the windows on workspaces
|
||||
// below us to the next workspace, leaving a blank workspace for us
|
||||
// to recycle.
|
||||
windows.forEach(window => {
|
||||
// If the window is attached to an ancestor, we don't need/want
|
||||
// to move it
|
||||
if (window.get_transient_for() != null)
|
||||
return;
|
||||
// Same for OR windows
|
||||
if (window.is_override_redirect())
|
||||
return;
|
||||
// Sticky windows don't need moving, in fact moving would
|
||||
// unstick them
|
||||
if (window.on_all_workspaces)
|
||||
return;
|
||||
// Windows on workspaces below pos don't need moving
|
||||
let index = window.get_workspace().index();
|
||||
if (index < pos)
|
||||
return;
|
||||
window.change_workspace_by_index(index + 1, true);
|
||||
});
|
||||
|
||||
// If the new workspace was inserted before the active workspace,
|
||||
// activate the workspace to which its windows went
|
||||
let activeIndex = workspaceManager.get_active_workspace_index();
|
||||
if (activeIndex >= pos) {
|
||||
let newWs = workspaceManager.get_workspace_by_index(activeIndex + 1);
|
||||
this._blockAnimations = true;
|
||||
newWs.activate(global.get_current_time());
|
||||
this._blockAnimations = false;
|
||||
}
|
||||
let newWs = workspaceManager.append_new_workspace(
|
||||
false, global.get_current_time());
|
||||
workspaceManager.reorder_workspace(newWs, pos);
|
||||
}
|
||||
|
||||
keepWorkspaceAlive(workspace, duration) {
|
||||
@ -1273,7 +1241,7 @@ var WindowManager = class {
|
||||
}
|
||||
|
||||
_shouldAnimate() {
|
||||
return !(Main.overview.visible || this._blockAnimations);
|
||||
return !Main.overview.visible;
|
||||
}
|
||||
|
||||
_shouldAnimateActor(actor, types) {
|
||||
@ -2164,6 +2132,8 @@ var WindowManager = class {
|
||||
let [action,,, target] = binding.get_name().split('-');
|
||||
let newWs;
|
||||
let direction;
|
||||
let vertical = workspaceManager.layout_rows == -1;
|
||||
let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;
|
||||
|
||||
if (action == 'move') {
|
||||
// "Moving" a window to another workspace doesn't make sense when
|
||||
@ -2176,7 +2146,12 @@ var WindowManager = class {
|
||||
}
|
||||
|
||||
if (target == 'last') {
|
||||
direction = Meta.MotionDirection.DOWN;
|
||||
if (vertical)
|
||||
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);
|
||||
} else if (isNaN(target)) {
|
||||
// Prepend a new workspace dynamically
|
||||
@ -2192,16 +2167,33 @@ var WindowManager = class {
|
||||
target--;
|
||||
newWs = workspaceManager.get_workspace_by_index(target);
|
||||
|
||||
if (workspaceManager.get_active_workspace().index() > target)
|
||||
direction = Meta.MotionDirection.UP;
|
||||
else
|
||||
direction = Meta.MotionDirection.DOWN;
|
||||
if (workspaceManager.get_active_workspace().index() > target) {
|
||||
if (vertical)
|
||||
direction = Meta.MotionDirection.UP;
|
||||
else if (rtl)
|
||||
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 (direction != Meta.MotionDirection.UP &&
|
||||
if (workspaceManager.layout_rows == -1 &&
|
||||
direction != Meta.MotionDirection.UP &&
|
||||
direction != Meta.MotionDirection.DOWN)
|
||||
return;
|
||||
|
||||
if (workspaceManager.layout_columns == -1 &&
|
||||
direction != Meta.MotionDirection.LEFT &&
|
||||
direction != Meta.MotionDirection.RIGHT)
|
||||
return;
|
||||
|
||||
if (action == 'switch')
|
||||
this.actionMoveWorkspace(newWs);
|
||||
else
|
||||
|
@ -94,15 +94,25 @@ class WindowCloneLayout extends Clutter.LayoutManager {
|
||||
}
|
||||
});
|
||||
|
||||
var WindowClone = class {
|
||||
constructor(realWindow, workspace) {
|
||||
var WindowClone = GObject.registerClass({
|
||||
Signals: {
|
||||
'drag-begin': {},
|
||||
'drag-cancelled': {},
|
||||
'drag-end': {},
|
||||
'hide-chrome': {},
|
||||
'selected': { param_types: [GObject.TYPE_UINT] },
|
||||
'show-chrome': {},
|
||||
'size-changed': {}
|
||||
},
|
||||
}, class WindowClone extends St.Widget {
|
||||
_init(realWindow, workspace) {
|
||||
this.realWindow = realWindow;
|
||||
this.metaWindow = realWindow.meta_window;
|
||||
this.metaWindow._delegate = this;
|
||||
this._workspace = workspace;
|
||||
|
||||
this._windowClone = new Clutter.Clone({ source: realWindow });
|
||||
// We expect this.actor 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
|
||||
// is not, this just works for most cases. However, for DND all
|
||||
// actors are picked, so DND operations would operate on the clone.
|
||||
@ -113,14 +123,18 @@ var WindowClone = class {
|
||||
// the invisible border; this is inconvenient; rather than trying
|
||||
// to compensate all over the place we insert a ClutterActor into
|
||||
// the hierarchy that is sized to only the visible portion.
|
||||
this.actor = new St.Widget({ reactive: true,
|
||||
can_focus: true,
|
||||
accessible_role: Atk.Role.PUSH_BUTTON,
|
||||
layout_manager: new WindowCloneLayout() });
|
||||
super._init({
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
accessible_role: Atk.Role.PUSH_BUTTON,
|
||||
layout_manager: new WindowCloneLayout()
|
||||
});
|
||||
|
||||
this.actor.add_child(this._windowClone);
|
||||
this.set_offscreen_redirect(Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY);
|
||||
|
||||
this.actor._delegate = this;
|
||||
this.add_child(this._windowClone);
|
||||
|
||||
this._delegate = this;
|
||||
|
||||
this.slotId = 0;
|
||||
this._slot = [0, 0, 0, 0];
|
||||
@ -142,23 +156,23 @@ var WindowClone = class {
|
||||
|
||||
this._updateAttachedDialogs();
|
||||
this._computeBoundingBox();
|
||||
this.actor.x = this._boundingBox.x;
|
||||
this.actor.y = this._boundingBox.y;
|
||||
this.x = this._boundingBox.x;
|
||||
this.y = this._boundingBox.y;
|
||||
|
||||
let clickAction = new Clutter.ClickAction();
|
||||
clickAction.connect('clicked', this._onClicked.bind(this));
|
||||
clickAction.connect('long-press', this._onLongPress.bind(this));
|
||||
this.actor.add_action(clickAction);
|
||||
this.actor.connect('destroy', this._onDestroy.bind(this));
|
||||
this.actor.connect('key-press-event', this._onKeyPress.bind(this));
|
||||
this.add_action(clickAction);
|
||||
this.connect('destroy', this._onDestroy.bind(this));
|
||||
this.connect('key-press-event', this._onKeyPress.bind(this));
|
||||
|
||||
this.actor.connect('enter-event', () => this.emit('show-chrome'));
|
||||
this.actor.connect('key-focus-in', () => this.emit('show-chrome'));
|
||||
this.connect('enter-event', () => this.emit('show-chrome'));
|
||||
this.connect('key-focus-in', () => this.emit('show-chrome'));
|
||||
|
||||
this.actor.connect('leave-event', () => this.emit('hide-chrome'));
|
||||
this.actor.connect('key-focus-out', () => this.emit('hide-chrome'));
|
||||
this.connect('leave-event', () => this.emit('hide-chrome'));
|
||||
this.connect('key-focus-out', () => this.emit('hide-chrome'));
|
||||
|
||||
this._draggable = DND.makeDraggable(this.actor,
|
||||
this._draggable = DND.makeDraggable(this,
|
||||
{ restoreOnSuccess: true,
|
||||
manualMode: true,
|
||||
dragActorMaxSize: WINDOW_DND_SIZE,
|
||||
@ -172,6 +186,10 @@ var WindowClone = class {
|
||||
this._closeRequested = false;
|
||||
}
|
||||
|
||||
vfunc_has_overlaps() {
|
||||
return this.hasAttachedDialogs();
|
||||
}
|
||||
|
||||
set slot(slot) {
|
||||
this._slot = slot;
|
||||
}
|
||||
@ -185,7 +203,7 @@ var WindowClone = class {
|
||||
|
||||
deleteAll() {
|
||||
// Delete all windows, starting from the bottom-most (most-modal) one
|
||||
let windows = this.actor.get_children();
|
||||
let windows = this.get_children();
|
||||
for (let i = windows.length - 1; i >= 1; i--) {
|
||||
let realWindow = windows[i].source;
|
||||
let metaWindow = realWindow.meta_window;
|
||||
@ -215,7 +233,7 @@ var WindowClone = class {
|
||||
}
|
||||
|
||||
hasAttachedDialogs() {
|
||||
return this.actor.get_n_children() > 1;
|
||||
return this.get_n_children() > 1;
|
||||
}
|
||||
|
||||
_doAddAttachedDialog(metaWin, realWin) {
|
||||
@ -229,7 +247,7 @@ var WindowClone = class {
|
||||
|
||||
this._onMetaWindowSizeChanged();
|
||||
});
|
||||
this.actor.add_child(clone);
|
||||
this.add_child(clone);
|
||||
}
|
||||
|
||||
_updateAttachedDialogs() {
|
||||
@ -267,7 +285,7 @@ var WindowClone = class {
|
||||
_computeBoundingBox() {
|
||||
let rect = this.metaWindow.get_frame_rect();
|
||||
|
||||
this.actor.get_children().forEach(child => {
|
||||
this.get_children().forEach(child => {
|
||||
let realWindow;
|
||||
if (child == this._windowClone)
|
||||
realWindow = this.realWindow;
|
||||
@ -280,7 +298,7 @@ var WindowClone = class {
|
||||
|
||||
// Convert from a MetaRectangle to a native JS object
|
||||
this._boundingBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
||||
this.actor.layout_manager.boundingBox = rect;
|
||||
this.layout_manager.boundingBox = rect;
|
||||
}
|
||||
|
||||
// Find the actor just below us, respecting reparenting done by DND code
|
||||
@ -306,17 +324,13 @@ var WindowClone = class {
|
||||
|
||||
let actualAbove = this.getActualStackAbove();
|
||||
if (actualAbove == null)
|
||||
this.actor.lower_bottom();
|
||||
this.lower_bottom();
|
||||
else
|
||||
this.actor.raise(actualAbove);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.actor.destroy();
|
||||
this.raise(actualAbove);
|
||||
}
|
||||
|
||||
_disconnectSignals() {
|
||||
this.actor.get_children().forEach(child => {
|
||||
this.get_children().forEach(child => {
|
||||
let realWindow;
|
||||
if (child == this._windowClone)
|
||||
realWindow = this.realWindow;
|
||||
@ -338,14 +352,12 @@ var WindowClone = class {
|
||||
this._disconnectSignals();
|
||||
|
||||
this.metaWindow._delegate = null;
|
||||
this.actor._delegate = null;
|
||||
this._delegate = null;
|
||||
|
||||
if (this.inDrag) {
|
||||
this.emit('drag-end');
|
||||
this.inDrag = false;
|
||||
}
|
||||
|
||||
this.disconnectAll();
|
||||
}
|
||||
|
||||
_activate() {
|
||||
@ -393,8 +405,8 @@ var WindowClone = class {
|
||||
|
||||
_onDragBegin(draggable, time) {
|
||||
this._dragSlot = this._slot;
|
||||
[this.dragOrigX, this.dragOrigY] = this.actor.get_position();
|
||||
this.dragOrigScale = this.actor.scale_x;
|
||||
[this.dragOrigX, this.dragOrigY] = this.get_position();
|
||||
this.dragOrigScale = this.scale_x;
|
||||
this.inDrag = true;
|
||||
this.emit('drag-begin');
|
||||
}
|
||||
@ -417,18 +429,17 @@ var WindowClone = class {
|
||||
// We may not have a parent if DnD completed successfully, in
|
||||
// which case our clone will shortly be destroyed and replaced
|
||||
// with a new one on the target workspace.
|
||||
if (this.actor.get_parent() != null) {
|
||||
if (this.get_parent() != null) {
|
||||
if (this._stackAbove == null)
|
||||
this.actor.lower_bottom();
|
||||
this.lower_bottom();
|
||||
else
|
||||
this.actor.raise(this._stackAbove);
|
||||
this.raise(this._stackAbove);
|
||||
}
|
||||
|
||||
|
||||
this.emit('drag-end');
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(WindowClone.prototype);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
@ -452,7 +463,7 @@ var WindowOverlay = class {
|
||||
this.title = new St.Label({ style_class: 'window-caption',
|
||||
text: this._getCaption() });
|
||||
this.title.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
||||
windowClone.actor.label_actor = this.title;
|
||||
windowClone.label_actor = this.title;
|
||||
|
||||
this._maxTitleWidth = -1;
|
||||
|
||||
@ -467,7 +478,7 @@ var WindowOverlay = class {
|
||||
|
||||
this.closeButton.connect('clicked', () => this._windowClone.deleteAll());
|
||||
|
||||
windowClone.actor.connect('destroy', this._onDestroy.bind(this));
|
||||
windowClone.connect('destroy', this._onDestroy.bind(this));
|
||||
windowClone.connect('show-chrome', this._onShowChrome.bind(this));
|
||||
windowClone.connect('hide-chrome', this._onHideChrome.bind(this));
|
||||
|
||||
@ -503,7 +514,7 @@ var WindowOverlay = class {
|
||||
show() {
|
||||
this._hidden = false;
|
||||
|
||||
if (this._windowClone.actor['has-pointer'])
|
||||
if (this._windowClone['has-pointer'])
|
||||
this._animateVisible();
|
||||
}
|
||||
|
||||
@ -675,7 +686,7 @@ var WindowOverlay = class {
|
||||
_idleHideOverlay() {
|
||||
this._idleHideOverlayId = 0;
|
||||
|
||||
if (!this._windowClone.actor['has-pointer'] &&
|
||||
if (!this._windowClone['has-pointer'] &&
|
||||
!this.closeButton['has-pointer'])
|
||||
this._animateInvisible();
|
||||
|
||||
@ -1296,8 +1307,8 @@ var Workspace = class {
|
||||
if (clone.inDrag)
|
||||
continue;
|
||||
|
||||
let cloneWidth = clone.actor.width * scale;
|
||||
let cloneHeight = clone.actor.height * scale;
|
||||
let cloneWidth = clone.width * scale;
|
||||
let cloneHeight = clone.height * scale;
|
||||
clone.slot = [x, y, cloneWidth, cloneHeight];
|
||||
|
||||
let cloneCenter = x + cloneWidth / 2;
|
||||
@ -1312,10 +1323,10 @@ var Workspace = class {
|
||||
if (!clone.positioned) {
|
||||
// This window appeared after the overview was already up
|
||||
// Grow the clone from the center of the slot
|
||||
clone.actor.x = x + cloneWidth / 2;
|
||||
clone.actor.y = y + cloneHeight / 2;
|
||||
clone.actor.scale_x = 0;
|
||||
clone.actor.scale_y = 0;
|
||||
clone.x = x + cloneWidth / 2;
|
||||
clone.y = y + cloneHeight / 2;
|
||||
clone.scale_x = 0;
|
||||
clone.scale_y = 0;
|
||||
clone.positioned = true;
|
||||
}
|
||||
|
||||
@ -1325,14 +1336,14 @@ var Workspace = class {
|
||||
* therefore we need to resize them now so they
|
||||
* can be scaled up later */
|
||||
if (initialPositioning) {
|
||||
clone.actor.opacity = 0;
|
||||
clone.actor.scale_x = 0;
|
||||
clone.actor.scale_y = 0;
|
||||
clone.actor.x = x;
|
||||
clone.actor.y = y;
|
||||
clone.opacity = 0;
|
||||
clone.scale_x = 0;
|
||||
clone.scale_y = 0;
|
||||
clone.x = x;
|
||||
clone.y = y;
|
||||
}
|
||||
|
||||
Tweener.addTween(clone.actor,
|
||||
Tweener.addTween(clone,
|
||||
{ opacity: 255,
|
||||
time: Overview.ANIMATION_TIME,
|
||||
transition: 'easeInQuad'
|
||||
@ -1342,10 +1353,10 @@ var Workspace = class {
|
||||
this._animateClone(clone, clone.overlay, x, y, scale);
|
||||
} else {
|
||||
// cancel any active tweens (otherwise they might override our changes)
|
||||
Tweener.removeTweens(clone.actor);
|
||||
clone.actor.set_position(x, y);
|
||||
clone.actor.set_scale(scale, scale);
|
||||
clone.actor.set_opacity(255);
|
||||
Tweener.removeTweens(clone);
|
||||
clone.set_position(x, y);
|
||||
clone.set_scale(scale, scale);
|
||||
clone.set_opacity(255);
|
||||
clone.overlay.relayout(false);
|
||||
this._showWindowOverlay(clone, clone.overlay);
|
||||
}
|
||||
@ -1366,13 +1377,13 @@ var Workspace = class {
|
||||
clone.setStackAbove(this._dropRect);
|
||||
} else {
|
||||
let previousClone = clones[i - 1];
|
||||
clone.setStackAbove(previousClone.actor);
|
||||
clone.setStackAbove(previousClone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_animateClone(clone, overlay, x, y, scale) {
|
||||
Tweener.addTween(clone.actor,
|
||||
Tweener.addTween(clone,
|
||||
{ x: x,
|
||||
y: y,
|
||||
scale_x: scale,
|
||||
@ -1411,7 +1422,7 @@ var Workspace = class {
|
||||
|
||||
let actorUnderPointer = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
|
||||
for (let i = 0; i < this._windows.length; i++) {
|
||||
if (this._windows[i].actor == actorUnderPointer)
|
||||
if (this._windows[i] == actorUnderPointer)
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
@ -1433,12 +1444,12 @@ var Workspace = class {
|
||||
// destroyed; we'd like to animate this, but it's too late at
|
||||
// this point.)
|
||||
if (win) {
|
||||
let [stageX, stageY] = clone.actor.get_transformed_position();
|
||||
let [stageWidth, stageHeight] = clone.actor.get_transformed_size();
|
||||
let [stageX, stageY] = clone.get_transformed_position();
|
||||
let [stageWidth, stageHeight] = clone.get_transformed_size();
|
||||
win._overviewHint = {
|
||||
x: stageX,
|
||||
y: stageY,
|
||||
scale: stageWidth / clone.actor.width
|
||||
scale: stageWidth / clone.width
|
||||
};
|
||||
}
|
||||
clone.destroy();
|
||||
@ -1518,11 +1529,11 @@ var Workspace = class {
|
||||
let scale = win._overviewHint.scale;
|
||||
delete win._overviewHint;
|
||||
|
||||
clone.slot = [x, y, clone.actor.width * scale, clone.actor.height * scale];
|
||||
clone.slot = [x, y, clone.width * scale, clone.height * scale];
|
||||
clone.positioned = true;
|
||||
|
||||
clone.actor.set_position(x, y);
|
||||
clone.actor.set_scale(scale, scale);
|
||||
clone.set_position(x, y);
|
||||
clone.set_scale(scale, scale);
|
||||
clone.overlay.relayout(false);
|
||||
}
|
||||
|
||||
@ -1596,7 +1607,7 @@ var Workspace = class {
|
||||
let overlay = this._windowOverlays[i];
|
||||
if (overlay)
|
||||
overlay.hide();
|
||||
this._windows[i].actor.opacity = 0;
|
||||
this._windows[i].opacity = 0;
|
||||
} else {
|
||||
let fromTop = topIndex - i;
|
||||
let time;
|
||||
@ -1605,7 +1616,7 @@ var Workspace = class {
|
||||
else
|
||||
time = windowBaseTime;
|
||||
|
||||
this._windows[i].actor.opacity = 255;
|
||||
this._windows[i].opacity = 255;
|
||||
this._fadeWindow(i, time, 0);
|
||||
}
|
||||
}
|
||||
@ -1619,7 +1630,7 @@ var Workspace = class {
|
||||
|
||||
for (let i = 0; i < this._windows.length; i++) {
|
||||
let clone = this._windows[i];
|
||||
Tweener.removeTweens(clone.actor);
|
||||
Tweener.removeTweens(clone);
|
||||
}
|
||||
|
||||
if (this._repositionWindowsId > 0) {
|
||||
@ -1654,7 +1665,7 @@ var Workspace = class {
|
||||
let overlay = this._windowOverlays[i];
|
||||
if (overlay)
|
||||
overlay.hide();
|
||||
this._windows[i].actor.opacity = 0;
|
||||
this._windows[i].opacity = 0;
|
||||
} else {
|
||||
let fromTop = topIndex - i;
|
||||
let time;
|
||||
@ -1663,7 +1674,7 @@ var Workspace = class {
|
||||
else
|
||||
time = windowBaseTime * nTimeSlots;
|
||||
|
||||
this._windows[i].actor.opacity = 0;
|
||||
this._windows[i].opacity = 0;
|
||||
this._fadeWindow(i, time, 255);
|
||||
}
|
||||
}
|
||||
@ -1678,18 +1689,18 @@ var Workspace = class {
|
||||
|
||||
if (clone.metaWindow.showing_on_its_workspace()) {
|
||||
let [origX, origY] = clone.getOriginalPosition();
|
||||
clone.actor.scale_x = 1;
|
||||
clone.actor.scale_y = 1;
|
||||
clone.actor.x = origX;
|
||||
clone.actor.y = origY;
|
||||
Tweener.addTween(clone.actor,
|
||||
clone.scale_x = 1;
|
||||
clone.scale_y = 1;
|
||||
clone.x = origX;
|
||||
clone.y = origY;
|
||||
Tweener.addTween(clone,
|
||||
{ time: time,
|
||||
opacity: opacity,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
} else {
|
||||
// The window is hidden
|
||||
clone.actor.opacity = 0;
|
||||
clone.opacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1706,7 +1717,7 @@ var Workspace = class {
|
||||
|
||||
for (let i = 0; i < this._windows.length; i++) {
|
||||
let clone = this._windows[i];
|
||||
Tweener.removeTweens(clone.actor);
|
||||
Tweener.removeTweens(clone);
|
||||
}
|
||||
|
||||
if (this._repositionWindowsId > 0) {
|
||||
@ -1732,7 +1743,7 @@ var Workspace = class {
|
||||
|
||||
if (clone.metaWindow.showing_on_its_workspace()) {
|
||||
let [origX, origY] = clone.getOriginalPosition();
|
||||
Tweener.addTween(clone.actor,
|
||||
Tweener.addTween(clone,
|
||||
{ x: origX,
|
||||
y: origY,
|
||||
scale_x: 1.0,
|
||||
@ -1743,7 +1754,7 @@ var Workspace = class {
|
||||
});
|
||||
} else {
|
||||
// The window is hidden, make it shrink and fade it out
|
||||
Tweener.addTween(clone.actor,
|
||||
Tweener.addTween(clone,
|
||||
{ scale_x: 0,
|
||||
scale_y: 0,
|
||||
opacity: 0,
|
||||
@ -1834,16 +1845,16 @@ var Workspace = class {
|
||||
clone.connect('size-changed', () => {
|
||||
this._recalculateWindowPositions(WindowPositionFlags.NONE);
|
||||
});
|
||||
clone.actor.connect('destroy', () => {
|
||||
clone.connect('destroy', () => {
|
||||
this._removeWindowClone(clone.metaWindow);
|
||||
});
|
||||
|
||||
this.actor.add_actor(clone.actor);
|
||||
this.actor.add_actor(clone);
|
||||
|
||||
overlay.connect('chrome-visible', () => {
|
||||
let focus = global.stage.key_focus;
|
||||
if (focus == null || this.actor.contains(focus))
|
||||
clone.actor.grab_key_focus();
|
||||
clone.grab_key_focus();
|
||||
|
||||
this._windowOverlays.forEach(o => {
|
||||
if (o != overlay)
|
||||
@ -1854,7 +1865,7 @@ var Workspace = class {
|
||||
if (this._windows.length == 0)
|
||||
clone.setStackAbove(null);
|
||||
else
|
||||
clone.setStackAbove(this._windows[this._windows.length - 1].actor);
|
||||
clone.setStackAbove(this._windows[this._windows.length - 1]);
|
||||
|
||||
this._windows.push(clone);
|
||||
this._windowOverlays.push(overlay);
|
||||
|
@ -17,41 +17,74 @@ class WorkspaceSwitcherPopupList extends St.Widget {
|
||||
this._itemSpacing = 0;
|
||||
this._childHeight = 0;
|
||||
this._childWidth = 0;
|
||||
this._orientation = global.workspace_manager.layout_rows == -1
|
||||
? Clutter.Orientation.VERTICAL
|
||||
: Clutter.Orientation.HORIZONTAL;
|
||||
|
||||
this.connect('style-changed', () => {
|
||||
this._itemSpacing = this.get_theme_node().get_length('spacing');
|
||||
});
|
||||
}
|
||||
|
||||
vfunc_get_preferred_height(forWidth) {
|
||||
_getPreferredSizeForOrientation(forSize) {
|
||||
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||
let themeNode = this.get_theme_node();
|
||||
|
||||
let availHeight = workArea.height;
|
||||
availHeight -= themeNode.get_vertical_padding();
|
||||
let availSize;
|
||||
if (this._orientation == Clutter.Orientation.HORIZONTAL)
|
||||
availSize = workArea.width - themeNode.get_horizontal_padding();
|
||||
else
|
||||
availSize = workArea.height - themeNode.get_vertical_padding();
|
||||
|
||||
let height = 0;
|
||||
let size = 0;
|
||||
for (let child of this.get_children()) {
|
||||
let [childMinHeight, childNaturalHeight] = child.get_preferred_height(-1);
|
||||
let [childMinWidth, childNaturalWidth] = child.get_preferred_width(childNaturalHeight);
|
||||
height += childNaturalHeight * workArea.width / workArea.height;
|
||||
let 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 spacing = this._itemSpacing * (workspaceManager.n_workspaces - 1);
|
||||
height += spacing;
|
||||
height = Math.min(height, availHeight);
|
||||
size += spacing;
|
||||
size = Math.min(size, availSize);
|
||||
|
||||
this._childHeight = (height - spacing) / workspaceManager.n_workspaces;
|
||||
if (this._orientation == Clutter.Orientation.HORIZONTAL) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return themeNode.adjust_preferred_height(height, height);
|
||||
_getSizeForOppositeOrientation() {
|
||||
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) {
|
||||
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||
this._childWidth = Math.round(this._childHeight * workArea.width / workArea.height);
|
||||
|
||||
return [this._childWidth, this._childWidth];
|
||||
if (this._orientation == Clutter.Orientation.HORIZONTAL)
|
||||
return this._getPreferredSizeForOrientation(forHeight);
|
||||
else
|
||||
return this._getSizeForOppositeOrientation();
|
||||
}
|
||||
|
||||
vfunc_allocate(box, flags) {
|
||||
@ -62,15 +95,23 @@ class WorkspaceSwitcherPopupList extends St.Widget {
|
||||
|
||||
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 prevChildBoxY2 = box.y1 - this._itemSpacing;
|
||||
for (let child of this.get_children()) {
|
||||
childBox.x1 = box.x1;
|
||||
childBox.x2 = box.x1 + this._childWidth;
|
||||
childBox.y1 = prevChildBoxY2 + this._itemSpacing;
|
||||
childBox.x1 = Math.round(x);
|
||||
childBox.x2 = Math.round(x + this._childWidth);
|
||||
childBox.y1 = Math.round(y);
|
||||
childBox.y2 = Math.round(y + this._childHeight);
|
||||
y += this._childHeight + this._itemSpacing;
|
||||
prevChildBoxY2 = childBox.y2;
|
||||
|
||||
if (this._orientation == Clutter.Orientation.HORIZONTAL) {
|
||||
if (rtl)
|
||||
x -= this._childWidth + this._itemSpacing;
|
||||
else
|
||||
x += this._childWidth + this._itemSpacing;
|
||||
} else {
|
||||
y += this._childHeight + this._itemSpacing;
|
||||
}
|
||||
child.allocate(childBox, flags);
|
||||
}
|
||||
}
|
||||
@ -121,6 +162,10 @@ class WorkspaceSwitcherPopup extends St.Widget {
|
||||
indicator = new St.Bin({ style_class: 'ws-switcher-active-up' });
|
||||
else if (i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.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
|
||||
indicator = new St.Bin({ style_class: 'ws-switcher-box' });
|
||||
|
||||
|
@ -865,6 +865,13 @@ class ThumbnailsBox extends St.Widget {
|
||||
this._nWorkspacesNotifyId =
|
||||
workspaceManager.connect('notify::n-workspaces',
|
||||
this._workspacesChanged.bind(this));
|
||||
this._workspacesReorderedId =
|
||||
workspaceManager.connect('workspaces-reordered', () => {
|
||||
this._thumbnails.sort((a, b) => {
|
||||
return a.metaWorkspace.index() - b.metaWorkspace.index();
|
||||
});
|
||||
this.queue_relayout();
|
||||
});
|
||||
this._syncStackingId =
|
||||
Main.overview.connect('windows-restacked',
|
||||
this._syncStacking.bind(this));
|
||||
@ -896,6 +903,11 @@ class ThumbnailsBox extends St.Widget {
|
||||
workspaceManager.disconnect(this._nWorkspacesNotifyId);
|
||||
this._nWorkspacesNotifyId = 0;
|
||||
}
|
||||
if (this._workspacesReorderedId > 0) {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
workspaceManager.disconnect(this._workspacesReorderedId);
|
||||
this._workspacesReorderedId = 0;
|
||||
}
|
||||
|
||||
if (this._syncStackingId > 0) {
|
||||
Main.overview.disconnect(this._syncStackingId);
|
||||
|
@ -101,6 +101,14 @@ var WorkspacesView = class extends WorkspacesViewBase {
|
||||
this._updateWorkspacesId =
|
||||
workspaceManager.connect('notify::n-workspaces',
|
||||
this._updateWorkspaces.bind(this));
|
||||
this._reorderWorkspacesId =
|
||||
workspaceManager.connect('workspaces-reordered', () => {
|
||||
this._workspaces.sort((a, b) => {
|
||||
return a.metaWorkspace.index() - b.metaWorkspace.index();
|
||||
});
|
||||
this._updateWorkspaceActors(false);
|
||||
});
|
||||
|
||||
|
||||
this._overviewShownId =
|
||||
Main.overview.connect('shown', () => {
|
||||
@ -181,26 +189,32 @@ var WorkspacesView = class extends WorkspacesViewBase {
|
||||
|
||||
Tweener.removeTweens(workspace.actor);
|
||||
|
||||
let y = (w - active) * this._fullGeometry.height;
|
||||
let params = {};
|
||||
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) {
|
||||
let params = { y: y,
|
||||
time: WORKSPACE_SWITCH_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
};
|
||||
let tweenParams = Object.assign(params, {
|
||||
time: WORKSPACE_SWITCH_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
// we have to call _updateVisibility() once before the
|
||||
// animation and once afterwards - it does not really
|
||||
// matter which tween we use, so we pick the first one ...
|
||||
if (w == 0) {
|
||||
this._updateVisibility();
|
||||
params.onComplete = () => {
|
||||
tweenParams.onComplete = () => {
|
||||
this._animating = false;
|
||||
this._updateVisibility();
|
||||
};
|
||||
}
|
||||
Tweener.addTween(workspace.actor, params);
|
||||
Tweener.addTween(workspace.actor, tweenParams);
|
||||
} else {
|
||||
workspace.actor.set_position(0, y);
|
||||
workspace.actor.set(params);
|
||||
if (w == 0)
|
||||
this._updateVisibility();
|
||||
}
|
||||
@ -287,6 +301,7 @@ var WorkspacesView = class extends WorkspacesViewBase {
|
||||
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
|
||||
let workspaceManager = global.workspace_manager;
|
||||
workspaceManager.disconnect(this._updateWorkspacesId);
|
||||
workspaceManager.disconnect(this._reorderWorkspacesId);
|
||||
}
|
||||
|
||||
startSwipeScroll() {
|
||||
@ -337,22 +352,39 @@ var WorkspacesView = class extends WorkspacesViewBase {
|
||||
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)
|
||||
return;
|
||||
|
||||
let currentY = firstWorkspaceY;
|
||||
let newY = - adj.value / (adj.upper - 1) * workspacesHeight;
|
||||
let last = this._workspaces.length - 1;
|
||||
|
||||
let dy = newY - currentY;
|
||||
if (workspaceManager.layout_rows == -1) {
|
||||
let firstWorkspaceY = this._workspaces[0].actor.y;
|
||||
let lastWorkspaceY = this._workspaces[last].actor.y;
|
||||
let workspacesHeight = lastWorkspaceY - firstWorkspaceY;
|
||||
|
||||
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;
|
||||
let currentY = firstWorkspaceY;
|
||||
let newY = -adj.value / (adj.upper - 1) * workspacesHeight;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -500,7 +532,12 @@ var WorkspacesDisplay = class {
|
||||
_onPan(action) {
|
||||
let [dist, dx, dy] = action.get_motion_delta(0);
|
||||
let adjustment = this._scrollAdjustment;
|
||||
adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
|
||||
if (global.workspace_manager.layout_rows == -1)
|
||||
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;
|
||||
}
|
||||
|
||||
@ -532,7 +569,12 @@ var WorkspacesDisplay = class {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
let active = workspaceManager.get_active_workspace_index();
|
||||
let adjustment = this._scrollAdjustment;
|
||||
adjustment.value = (active - yRel / this.actor.height) * adjustment.page_size;
|
||||
if (workspaceManager.layout_rows == -1)
|
||||
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) {
|
||||
@ -629,10 +671,15 @@ var WorkspacesDisplay = class {
|
||||
this._scrollValueChanged.bind(this));
|
||||
}
|
||||
|
||||
// HACK: Avoid spurious allocation changes while updating views
|
||||
view.actor.hide();
|
||||
|
||||
this._workspacesViews.push(view);
|
||||
Main.layoutManager.overviewGroup.add_actor(view.actor);
|
||||
}
|
||||
|
||||
this._workspacesViews.forEach(v => v.actor.show());
|
||||
|
||||
this._updateWorkspacesFullGeometry();
|
||||
this._updateWorkspacesActualGeometry();
|
||||
}
|
||||
@ -751,6 +798,12 @@ var WorkspacesDisplay = class {
|
||||
case Clutter.ScrollDirection.DOWN:
|
||||
ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN);
|
||||
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:
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
|
130
lint/eslintrc-gjs.json
Normal file
130
lint/eslintrc-gjs.json
Normal file
@ -0,0 +1,130 @@
|
||||
{
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"array-bracket-newline": [
|
||||
"error",
|
||||
"consistent"
|
||||
],
|
||||
"array-bracket-spacing": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"arrow-spacing": "error",
|
||||
"brace-style": "error",
|
||||
"comma-spacing": [
|
||||
"error",
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"indent": [
|
||||
"error",
|
||||
4,
|
||||
{
|
||||
"ignoredNodes": [
|
||||
"CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child"
|
||||
],
|
||||
"MemberExpression": "off"
|
||||
}
|
||||
],
|
||||
"key-spacing": [
|
||||
"error",
|
||||
{
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}
|
||||
],
|
||||
"keyword-spacing": [
|
||||
"error",
|
||||
{
|
||||
"before": true,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"no-empty": [
|
||||
"error",
|
||||
{
|
||||
"allowEmptyCatch": true
|
||||
}
|
||||
],
|
||||
"no-implicit-coercion": [
|
||||
"error",
|
||||
{
|
||||
"allow": ["!!"]
|
||||
}
|
||||
],
|
||||
"no-restricted-properties": [
|
||||
"error",
|
||||
{
|
||||
"object": "Lang",
|
||||
"property": "bind",
|
||||
"message": "Use arrow notation or Function.prototype.bind()"
|
||||
},
|
||||
{
|
||||
"object": "Lang",
|
||||
"property": "Class",
|
||||
"message": "Use ES6 classes"
|
||||
}
|
||||
],
|
||||
"nonblock-statement-body-position": [
|
||||
"error",
|
||||
"below"
|
||||
],
|
||||
"object-curly-newline": [
|
||||
"error",
|
||||
{
|
||||
"consistent": true
|
||||
}
|
||||
],
|
||||
"object-curly-spacing": "error",
|
||||
"prefer-template": "error",
|
||||
"quotes": [
|
||||
"error",
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"semi-spacing": [
|
||||
"error",
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"space-before-blocks": "error",
|
||||
"space-infix-ops": [
|
||||
"error",
|
||||
{
|
||||
"int32Hint": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"globals": {
|
||||
"ARGV": false,
|
||||
"Debugger": false,
|
||||
"GIRepositoryGType": false,
|
||||
"imports": false,
|
||||
"Intl": false,
|
||||
"log": false,
|
||||
"logError": false,
|
||||
"print": false,
|
||||
"printerr": false,
|
||||
"window": false
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2017
|
||||
}
|
||||
}
|
21
lint/eslintrc-legacy.json
Normal file
21
lint/eslintrc-legacy.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
4,
|
||||
{
|
||||
"ignoredNodes": [
|
||||
"ConditionalExpression",
|
||||
"CallExpression > ArrowFunctionExpression",
|
||||
"CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child"
|
||||
],
|
||||
"CallExpression": { "arguments": "first" },
|
||||
"ArrayExpression": "first",
|
||||
"ObjectExpression": "first",
|
||||
"MemberExpression": "off"
|
||||
}
|
||||
],
|
||||
"prefer-template": "off",
|
||||
"quotes": "off"
|
||||
}
|
||||
}
|
38
lint/eslintrc-shell.json
Normal file
38
lint/eslintrc-shell.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"rules": {
|
||||
"camelcase": [
|
||||
"error",
|
||||
{
|
||||
"properties": "never",
|
||||
"allow": ["^vfunc_", "^on_"]
|
||||
}
|
||||
],
|
||||
"key-spacing": [
|
||||
"error",
|
||||
{
|
||||
"mode": "minimum",
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}
|
||||
],
|
||||
"object-curly-spacing": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"prefer-arrow-callback": "error"
|
||||
},
|
||||
|
||||
"overrides": [
|
||||
{
|
||||
"files": "js/**",
|
||||
"excludedFiles": ["js/extensionPrefs/*", "js/portalHelper/*"],
|
||||
"globals": {
|
||||
"global": false,
|
||||
"_": false,
|
||||
"C_": false,
|
||||
"N_": false,
|
||||
"ngettext": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
project('gnome-shell', 'c',
|
||||
version: '3.33.3',
|
||||
version: '3.33.4',
|
||||
meson_version: '>= 0.47.0',
|
||||
license: 'GPLv2+'
|
||||
)
|
||||
@ -17,13 +17,11 @@ croco_req = '>= 0.6.8'
|
||||
ecal_req = '>= 3.33.1'
|
||||
eds_req = '>= 3.17.2'
|
||||
gcr_req = '>= 3.7.5'
|
||||
gdesktop_req = '>= 3.7.90'
|
||||
gio_req = '>= 2.56.0'
|
||||
gi_req = '>= 1.49.1'
|
||||
gjs_req = '>= 1.57.3'
|
||||
gtk_req = '>= 3.15.0'
|
||||
json_glib_req = '>= 0.13.2'
|
||||
mutter_req = '>= 3.33.3'
|
||||
mutter_req = '>= 3.33.4'
|
||||
polkit_req = '>= 0.100'
|
||||
schemas_req = '>= 3.27.90'
|
||||
startup_req = '>= 0.11'
|
||||
|
@ -44,6 +44,7 @@ ku
|
||||
ky
|
||||
lt
|
||||
lv
|
||||
mjw
|
||||
ml
|
||||
mk
|
||||
mr
|
||||
|
10
src/main.c
10
src/main.c
@ -372,7 +372,7 @@ dump_gjs_stack_alarm_sigaction (int signo)
|
||||
static void
|
||||
dump_gjs_stack_on_signal_handler (int signo)
|
||||
{
|
||||
struct sigaction sa = { 0 };
|
||||
struct sigaction sa = { .sa_handler = dump_gjs_stack_alarm_sigaction };
|
||||
gsize i;
|
||||
|
||||
/* Ignore all the signals starting this point, a part the one we'll raise
|
||||
@ -388,7 +388,6 @@ dump_gjs_stack_on_signal_handler (int signo)
|
||||
|
||||
/* Waiting at least 5 seconds for the dumpstack, if it fails, we raise the error */
|
||||
caught_signal = signo;
|
||||
sa.sa_handler = dump_gjs_stack_alarm_sigaction;
|
||||
sigemptyset (&sa.sa_mask);
|
||||
sigaction (SIGALRM, &sa, NULL);
|
||||
|
||||
@ -402,10 +401,11 @@ dump_gjs_stack_on_signal_handler (int signo)
|
||||
static void
|
||||
dump_gjs_stack_on_signal (int signo)
|
||||
{
|
||||
struct sigaction sa = { 0 };
|
||||
struct sigaction sa = {
|
||||
.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);
|
||||
|
||||
sigaction (signo, &sa, NULL);
|
||||
|
@ -465,7 +465,7 @@ recorder_record_frame (ShellRecorder *recorder,
|
||||
|
||||
g_object_get (settings, "magnifier-active", &magnifier_active, NULL);
|
||||
|
||||
if (magnifier_active)
|
||||
if (!magnifier_active)
|
||||
recorder_draw_cursor (recorder, buffer);
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,7 @@ shell_secure_text_buffer_real_insert_text (ClutterTextBuffer *buffer,
|
||||
|
||||
/* Actual text insertion */
|
||||
at = g_utf8_offset_to_pointer (self->text, position) - self->text;
|
||||
g_memmove (self->text + at + n_bytes, self->text + at, self->text_bytes - at);
|
||||
memmove (self->text + at + n_bytes, self->text + at, self->text_bytes - at);
|
||||
memcpy (self->text + at, chars, n_bytes);
|
||||
|
||||
/* 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;
|
||||
end = g_utf8_offset_to_pointer (self->text, position + n_chars) - self->text;
|
||||
|
||||
g_memmove (self->text + start, self->text + end, self->text_bytes + 1 - end);
|
||||
memmove (self->text + start, self->text + end, self->text_bytes + 1 - end);
|
||||
self->text_chars -= n_chars;
|
||||
self->text_bytes -= (end - start);
|
||||
|
||||
|
Reference in New Issue
Block a user