Compare commits

...

63 Commits

Author SHA1 Message Date
d471e3a23b Bump version to 3.33.4
Update NEWS.
2019-07-20 17:47:10 +02:00
ce1bee727a extensionSystem: Allow disabling session mode extensions
Trying to disable an extension that is enabled by the session mode
currently has no effect, which is clearly confusing. We could update
the various extension UIs to reflect that via sensitivity, but being
unable to configure extensions based on which session the user picked
at login isn't obvious either.

So instead, add a 'disabled-extensions' gsettings key to list extensions
that should not be enabled which takes precedence over 'enabled-extensions'
and can be used to disable session mode extensions.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
43cb3754d9 extensionSystem: Store extensions in a Map
After making the extensions map private to the ExtensionManager, we can
switch it to a proper hash table which is more appropriate.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
1d6ddf060b extensionSystem: Move extension loading into ExtensionManager
Now that extension loading and the extensions map are no longer shared
between the gnome-shell and gnome-shell-extension-prefs processes, we
can move both into the ExtensionManager which makes much more sense
conceptually.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
9928125e7d extensionPrefs: Switch to D-Bus API to get extension live state
By direclty using the underlying GSetting, whether or not an extension
appears as enabled or disabled currently depends only on whether it is
included in the 'enabled-extensions' list or not.

However this doesn't necessarily reflect the real extension state, as an
extension may be in error state, or enabled via the session mode.

Switch to the extensions D-Bus API to ensure that the list of extensions
and each extension's state correctly reflects the state in gnome-shell.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
1c63893c4b extensionPrefs: Override getCurrentExtension() for extensions
Extensions are used to calling the getCurrentExtension() utility function,
both from the extension itself and from its preferences. For the latter,
that relies on the extensions map in ExtensionUtils being populated from
the separated extension-prefs process just like from gnome-shell.

This won't be the case anymore when we switch to the extensions D-Bus API,
but as we know which extension we are showing the prefs dialog for, we
can patch in a simple replacement that gives extensions the expected API.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
a7ec7583aa extensionPrefs: Attach extension object to each row
Each row represents an extension, so it makes sense to associate the
rows with the actual extensions instead of linking rows and extensions
by looking up the UUID in the external extensions map in ExtensionUtils.

This will also make it much easier to stop using the shared extension
loading / map in favor of the extension D-Bus API.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
4a3476266f extensionSystem: Add canChange property to extensions
Whether or not an extension can be enabled/disabled depends on various
factors: Whether the extension is in error state, whether user extensions
are disabled and whether the underlying GSettings keys are writable.

This is complex enough to share the logic, so add it to the extension
properties that are exposed over D-Bus.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
32e0b895a4 shellDBus: Add new 'ExtensionStateChanged' signal
The existing 'ExtensionStatusChanged' signal has a fixed set of parameters,
which means we cannot add additional state without an API break.  Deprecate
it in favor of a new 'ExtensionStateChanged' signal which addresses this
issue by taking the full serialized extension as parameter.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
58806359ee extensionUtils: Add functions to (de)serialize extensions
Serializing an extension for sending over D-Bus is currently done by the
appropriate D-Bus method implementations. Split out the code as utility
function and add a corresponding deserialization function, which we will
soon use when consuming the D-Bus extension API from the extension-prefs
tool.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
4589da957b extensionSystem: Add methods to enable/disable extensions
Extensions are currently enabled or disabled by directly changing the
list in the 'enabled-extensions' GSettings key. As we will soon add
an overriding 'disabled-extensions' key as well, it makes sense to
offer explicit API for enabling/disabling to avoid duplicating the
logic.

For the corresponding D-Bus API, the methods were even mentioned in
the GSettings schema, albeit unimplemented until now.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
6a4c55b852 extensionSystem: Make methods to call extension functions private
While public methods to enable/disable extensions make sense for an
extension manager, the existing ones are only used internally. Make
them private and rename them, so that we can re-use the current
names for more useful public methods.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
ea17740719 extensionSystem: Turn into a class
The extension system started out as a set of simple functions, but
gained more state later, and even some hacks to emit signals without
having an object to emit them on.

There is no good reason for that weirdness, so rather than imitating an
object, wrap the existing system into a real ExtensionManager object.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
d82810240f extensionUtils: Move ExtensionState definition here
It makes sense to keep extension-related enums in the same module instead
of spreading them between ExtensionSystem and ExtensionUtils.

More importantly, this will make the type available to the extensions-prefs
tool (which runs in a different process and therefore only has access to
a limited set of modules).

https://bugzilla.gnome.org/show_bug.cgi?id=789852
2019-07-20 14:17:35 +00:00
2768b73015 build: Clean out unused version requirements
We haven't linked to those libraries in years ...
2019-07-20 12:39:16 +02:00
f9a7718dda background: Adjust to gnome-desktop API break
gnome-desktop broke API in commit ca5d61cf24, as it didn't *add* a property
as incorrectly stated in the commit message, but *replaced* an existing one.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1457
2019-07-20 12:26:28 +02:00
d9d9778a98 main: Avoid missing braces warnings when compiling with clang
Since -Werror=missing-braces is used, having missing braces warnings
aren't allowed. However, the first member of struct sigaction is a union
whose first member is a pointer, causing clang to produce warnings when
it is initialized to { 0 }.

Instead of initializing to a zero value, we can specify values of
members directly in the initializer to avoid warnings.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/633
2019-07-20 16:45:49 +08:00
bd5162105e power: Make sure we fall back to the correct icon
Commit bd18313d12 changed to a new naming scheme for battery icons,
and used to old icon names as fallback-icon-name for compatibility
with older/other icon themes.

However that fallback code isn't working correctly, as GThemedIcon's
default fallbacks will transform a name of `battery-level-90-symbolic`
to a list of names:
 - `battery-level-90-symbolic`
 - `battery-level-symbolic`
 - `battery-symbolic`

The last one frequently exists, so instead of the intended fallback,
we end up with a generic battery icon.

Address this by specifying the icon as GIcon instead of an icon-name,
where we have more control over how the icon is resolved.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1442
2019-07-19 19:11:37 +00:00
208c5e9562 shell: Don't use g_memmove()
Glib stopped providing any fallback implementations on systems without
memmove() all the way back in 2013. Since then, the symbol is a simple
macro around memmove(); use that function directly now that glib added
a deprecation warning.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/632
2019-07-19 17:43:56 +00:00
305e63750e workspacesView: Support horizontal layout
Just as we did for the workspace switcher popup, support workspaces
being laid out in a single row in the window picker.

Note that this takes care of the various workspace switch actions in
the overview (scrolling, panning, touch(pad) gestures) as well as the
switch animation, but not of the overview's workspace switcher component.

There are currently no plans to support other layouts there, as the
component is inherently vertical (in fact, it was the whole reason for
switching the layout in the first place).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/575
2019-07-19 11:01:24 +02:00
ab0f74aa15 workspaceSwitcherPopup: Support horizontal layout
While mutter supports a variety of different grid layouts (n columns/rows,
growing vertically or horizontally from any of the four corners), we
hardcode a fixed vertical layout of a single column.

Now that mutter exposes the actual layout to us, add support for a more
traditional horizontal layout as well.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/575
2019-07-19 11:01:24 +02:00
43443d08ae theme: app icon helper tweaks
Shell cannot composite multiple shadows, simplify to only use one.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/168
2019-07-18 05:17:56 +02:00
b82b553b9e extensionPrefs: Inherit from Gtk.Application
Extension preferences Application class is just a container for a GtkApplication
so instead of using composition we can inherit from the base GObject class.

Also replace signal connections with vfunc's.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/631
2019-07-17 12:59:08 +00:00
08464eadff theme: make overview thumbnails rounder
- match gtk Adwaita

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1449
2019-07-16 05:29:23 +02:00
49e56776e8 theme: unbreak acrive states for icon tiles
Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1446
2019-07-16 00:16:32 +02:00
043667dde5 theme: Provide icon helper classes
- to help present icons on light backgrounds, the new fullcolor
  icons need some help. Mimic gtk styles

Needed for https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/168
2019-07-16 00:07:51 +02:00
f583a7c6d8 Update Karbi translation 2019-07-16 02:39:07 +00:00
2d908e80fc search: Remove wrong additional argument for _createResultDisplay
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/110
2019-07-15 23:00:34 +00:00
8f0e9abe47 iconGrid: Make sure the style is updated before computing the layout
In some cases the style-changed signal hasn't been emitted when
_computeLayout() is called, resulting in the use of the default spacing
and item size values for the calculations.

One case where this happens is when starting a search. Right after the
initialization of GridSearchResults, _computeLayout() is called from
_getMaxDisplayedResults() and the style-changed signal hasn't been
emitted yet. The computed layout will be wrong and the maximum
number of results will also be wrong.

To prevent this from happening, make sure the style has been updated
before doing the calculations in _computeLayout().

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/110
2019-07-15 23:00:34 +00:00
1a27ff6130 search: Fix calculation of max number of displayed results for grid
The calculation of how many results can be shown in GridSearchResults is
broken: The width of the parent container (resultsView.actor) we're
using as the maximum width right now is the width of the scrollView of
SearchResults (which always expands to the whole screen size). This
width will only be correct if the scrollView (ie. the whole screen) is
smaller than the max width of searchResultsContent, which only is the
case for screens smaller than 1000px.

To fix the calculation, use the width of our own actor and don't get it
using clutter_actor_get_width(), but using the last allocation of the
actor. This way we don't get the preferred width if the actor is not
allocated at this point (it's hidden by _ensureProviderDisplay() when
starting a new search).

Then, when the allocation of the actor changes, rebuild the grid search
results by calling updateSearch() with the old arguments to ensure the
number of visible results is correct. The fact that we're only listening
for allocation changes here is the reason why we never want to use the
preferred width of the actor inside _getMaxDisplayedResults(): While
the actor is hidden clutter_actor_get_width() would return the preferred
width, which we'd then use the as the maximum width. But if the actor
had a correct allocation before, no notify::allocation signal will be
emitted when the actor is shown again because the allocation is still
the same, and we'll end up using the preferred width as maximium width
forever.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/110
2019-07-15 23:00:34 +00:00
3f2cffc2e6 theme: Don't apply overlap-preventing padding to search results
Unlike the app grid, we show the search results while the dash is hidden
and with a small scrollbar instead of page indicator dots. This
means there's nothing the search results might horizontally overlap
with and the padding here is unneccessary.

The spacing between the search results and the screen edges is still
sufficient because of the paddings applied to searchResultsContent.

On very small screens (< 1000px), this allows the search results to
utilize a lot more of the horizontal screen space.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/110
2019-07-15 23:00:34 +00:00
a78527050a search: Remove unnecessary containers
The functionality the searchResultsBin container provides can easily be
moved into a subclass of St.BoxLayout, no need for an additional StBin.
The "searchResultsBin" css class isn't used in the stylesheets either.

Same with the scrollChild container.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/110
2019-07-15 23:00:34 +00:00
a823a213ba Update Karbi translation 2019-07-15 10:54:50 +00:00
2c8d380e67 shellDBus: Rename ShowMonitorLabels2 to ShowMonitorLabels
The original ShowMonitorLabels has been removed so we can change things
back to use ShowMonitorLabels again.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/491
2019-07-15 11:18:29 +02:00
3996309f8a Add Karbi translation 2019-07-14 04:02:53 +00:00
bd18313d12 power: Use more fine-grained battery levels
Adwaita-icon-theme added new battery icons which represent battery levels
in 10% steps[0]. Use these if they are available, otherwise fall back to
the existing icon names for compatibility with older icon themes.

[0] https://gitlab.gnome.org/GNOME/adwaita-icon-theme/issues/6

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/561
2019-07-12 23:24:35 +02:00
2ff7a78b56 calendar: Simplify code a bit
Just make the error case the same as the no-appointments one.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/626
2019-07-12 18:54:49 +00:00
c765082f72 calendar: Avoid a warning
We will always get a results array from gjs' proxy wrapper, but it
will be empty in the error case; that is, `results` is always defined,
but `results[0]` may not be.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/626
2019-07-12 18:54:49 +00:00
7d2c5c1ac9 dialog: Use Object.assign() for default property value
Either Params.parse() or Object.assign() are more concise for providing
default values in object literals (sadly default parameters won't work
here).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/626
2019-07-12 18:54:49 +00:00
404bc34089 cleanup: Use default parameters where appropriate
Since ES6 it is possible to set an explicit default value for optional
parameters (overriding the implicit value of 'undefined'). Use them
for a nice small cleanup.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/626
2019-07-12 18:54:49 +00:00
16ca7a21a7 panel: Relax check for existing signal handler
Object.prototype.hasOwnProperty() is more precise than checking for
falsiness, for instance the following is true:

  { foo: undefined }.hasOwnProperty('foo');

However when checking for a handler ID, a more relaxed check is more
appropriate, as particularly 0 is not a valid handler ID.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/626
2019-07-12 18:54:49 +00:00
1b31fd5afe cleanup: Don't call method via a parent's prototype
We cannot disconnect a signal handler via the usual disconnect() as
nm_device_disconnect() shadows the GObject method, but we can use
g_signal_handler_disconnect().

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/626
2019-07-12 18:54:49 +00:00
e0457b6dc4 lint: Add "legacy" configuration
Regarding coding style, gjs is moving in a direction that departs quite
significantly from the established style, in particular when indenting
multi-line array/object literals or method arguments:

Currently we are keeping those elements aligned, while the gjs rules now
expect them to use the regular 4-space indentation.

There are certainly good arguments that can be made for that move - it's
much less prone to leading to overly-long lines, and matches popluar JS
styles elsewhere. But switching coding style implies large diffs which
interfere with git-blame and friends, so in order to allow for a more
gradual change, add a separate set of "legacy" rules that match more
closely the style we would expect up to now.

It also disables the rules for quotes and template strings - the former
because we cannot match the current style to use double-quotes for
translatable strings and single-quotes otherwise, the latter because
template strings are still relatively new, so we haven't adopted them
yet.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/609
2019-07-12 16:01:07 +00:00
42b77e7ba5 lint: Allow multiple spaces before key values
This is useful for imitating namespaced flags/enums:

```
const FooFlags = {
    NONE:    0,
    SMEAGLY: 1 << 0,
    SMOGLEY: 1 << 1,
    MUGGLY:  1 << 2
};
```

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/609
2019-07-12 16:01:07 +00:00
f6bed08993 lint: Enforce camelCase
All variables should be in camelCase, so configure the corresponding
rule to enforce this. Exempt properties for now, to accommodate the
existing practice of using C-style underscore names for construct
properties of introspected objects.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/609
2019-07-12 16:01:07 +00:00
5f77cdb0b9 lint: Enforce arrow notation
We replaced all Lang.bind() calls with arrow functions or the standardized
Function.prototype.bind(), at least for the former eslint has some options
to ensure that the old custom doesn't sneak back in.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/609
2019-07-12 16:01:07 +00:00
109b8e8f38 lint: Require spaces inside braces in object literals
Prohibiting spaces where the established GNOME style has required
them for a decade would be a harsh change for no good reason.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/609
2019-07-12 16:01:07 +00:00
4c0bd88a2c lint: Tweak the whitelist of globals
gjs doesn't include any gettext wrappers, and obviously can't know
about the shell's global object, so include those in the list of
globals for all sources in the gnome-shell context.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/609
2019-07-12 16:01:07 +00:00
3731be9947 lint: Import eslint rules from gjs
gjs started to run eslint during its CI a while ago, so there is an
existing rules set we can use as a starting point for our own setup.

As we will adapt those rules to our code base, we don't want those
changes to make it harder to synchronize the copy with future gjs
changes, so include the rules from a separate file rather than using
the configuration directly.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/609
2019-07-12 16:01:07 +00:00
6cc19ee6f0 workspacesView: Work around spurious allocation changes
For some reason, people are still seeing those after commit d5ebd8c8.
While this is something we really should figure out, we can work around
the issue by keeping the view actors hidden until the update is complete.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1065
2019-07-12 16:48:03 +02:00
1570f838f3 cleanup: Remove bogus file
This was accidentally added in commit 14d7897a9.
2019-07-12 10:36:37 +00:00
74feb110b5 layout: Fix off-by-one indent
This sneaked into commit dbb71f0d :-(
2019-07-11 03:02:20 +02:00
6ba03ac2a6 params: Don't use Lang module
To copy the passed in default parameters, we can just as well use
another Object.assign() call.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/616
2019-07-10 22:09:09 +00:00
55c717c2dc appDisplay: Fix logic error
Commit f6b4b96737 accidentally swapped the conditions here from
!includes() to includes().

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/621
2019-07-10 21:50:17 +00:00
355b5eebec workspace: Set offscreen redirect on window previews
Window previews are sometimes shown translucent, for example during
drags or animations. They can also have attached dialogs, in which
case the opacity should affect the combination of all windows instead
of being applied to each window individually, blended together, so
make sure they are redirected as a whole when necessary.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/774
2019-07-10 21:41:58 +00:00
51938c398a workspace: Let WindowClone inherit from StWidget
Using inheritance over delegation will give us more control over
the actor drawing.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/774
2019-07-10 21:41:58 +00:00
dbb71f0dfc layout: Make the hot corner optional
Whether people love or hate the hot corner depends in large extents
on hardware sensitivity and habits, which is hard to get right
universally. So bite the bullet and support an option to enable or
disable hot corners ...

https://bugzilla.gnome.org/show_bug.cgi?id=688320
2019-07-10 17:29:24 +02:00
1cac7b2218 windowManager: Remove unused property
The last commit removed the only code that set _blockAnimations,
so stop reading it as well.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/620
2019-07-09 14:46:36 +02:00
ff9bb5399b windowManager: Use new reorder_workspace() API
With the new Mutter API, inserting a workspace at a particular position
becomes as easy as creating the workspace and moving it to the desired
index.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/620
2019-07-09 14:41:35 +02:00
68e45eb051 workspaceThumbnails: Handle reordering of workspaces
MetaWorkspaceManager gained the ability to reorder workspaces, so make
sure to pick up the new order when that happens.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/620
2019-07-09 14:41:35 +02:00
d0da96ad29 workspacesView: Handle reordering of workspaces
MetaWorkspaceManager gained the ability to reorder workspaces, so make
sure to pick up the new order when that happens.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/620
2019-07-09 14:41:35 +02:00
55b036170b shell-recorder: Restore cursor recording
Due to changes introduced in 5357e0a1 cursor recording interaction with
magnifier was reversed. This fix restores original correct behavior
Related issue: https://gitlab.gnome.org/GNOME/gnome-shell/issues/1208
2019-07-08 21:08:51 +00:00
5473637736 cleanup: Fix style nits in last commit
Missing space after catch and wrong double quotes.
2019-07-08 20:15:15 +02:00
46 changed files with 3659 additions and 875 deletions

6
.eslintrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": [
"./lint/eslintrc-gjs.json",
"./lint/eslintrc-shell.json"
]
}

26
NEWS
View File

@ -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]

View File

@ -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"/>

View File

@ -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"/>

View File

@ -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>

View File

@ -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

View File

@ -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(
_("Somethings gone wrong")),
use_markup: true
});
label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
this.add(label);
label = new Gtk.Label({
label: _("Were 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);
}

View File

@ -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);

View File

@ -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 {

View File

@ -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);
}

View File

@ -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)

View File

@ -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;

View File

@ -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,

View File

@ -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;

View File

@ -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')

View File

@ -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,

View File

@ -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()));
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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) {

View File

@ -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

View File

@ -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', () => {

View File

@ -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';

View File

@ -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);
}
};

View File

@ -151,9 +151,7 @@ var OsdWindow = class {
}
}
setMaxLevel(maxLevel) {
if (maxLevel === undefined)
maxLevel = 100;
setMaxLevel(maxLevel = 100) {
this._level.maxLevel = maxLevel;
}

View File

@ -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',

View File

View File

@ -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);

View File

@ -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]));
}

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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' });

View File

@ -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);

View File

@ -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
View 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
View 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
View 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
}
}
]
}

View File

@ -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'

View File

@ -44,6 +44,7 @@ ku
ky
lt
lv
mjw
ml
mk
mr

2239
po/mjw.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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);
}

View File

@ -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);