Compare commits

...

788 Commits

Author SHA1 Message Date
460cf9df30 Bump version to 3.35.3
Update NEWS.
2020-01-05 12:22:35 +01:00
42af514c51 keyboard: Fix input-source switcher alignment
More fallout from commit 104071acbd.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/916
2020-01-04 23:00:02 +00:00
3244c280d8 Update Catalan translation 2020-01-03 23:07:19 +01:00
e07a5749b7 switchMonitor: Center align switcher icon's label
Fallout from 104071acbd.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/913
2020-01-02 13:03:08 +05:30
a8f44be5c1 Updated Spanish translation 2019-12-30 12:50:57 +01:00
8f8892aa20 Update Catalan translation 2019-12-27 14:16:47 +01:00
45a8b92d84 Update Galician translation 2019-12-25 02:40:58 +00:00
6b4017fbb6 Update Friulian translation 2019-12-22 18:59:02 +00:00
28d42628d1 windowManager: Always reset window actors when minimize animation is cancelled
This was left out in b6d47c18c. Fixes an occasional warning:
`this._minimizeWindowOverwritten is not a function`

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/911
2019-12-21 15:28:32 +01:00
c1d738d60c st/password-entry: Initialize :show-peek-icon property
The icon is shown by default, but the struct member that backs the
property starts out as FALSE.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/910
2019-12-21 04:12:03 +01:00
c2609227ff st/password-entry: Fix memory leak
The peek-password icon is currently created twice, whoops.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/910
2019-12-21 04:12:03 +01:00
f5f9bd2e5e iconGrid: Fix icon alignment
We don't want the icon to fill extra space, so set the alignment
accordingly. Otherwise we get an unexpected result when adding
a background just to the icon part (as far as I can tell: just
system-action-icon).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/909
2019-12-20 22:58:26 +01:00
15d74c9cd4 st: Fix generated GLSL dependency
StScrollViewFade depends on st-scroll-view-fade-generated.c, but
that dependency isn't expressed to the build system; we just hope
that the custom target runs before compiling the effect.

Instead, add the generated source to the st target so the dependency
is expressed properly.

(The change from .c to .h is to prevent the file from being both
included and compiled, resulting in a duplicated symbol)

https://bugzilla.gnome.org/show_bug.cgi?id=789937
2019-12-20 21:02:45 +01:00
3c87ad5aab screenshot: Promisify PickPixel
Same as the previous commit, but for PickPixel.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/903
2019-12-20 15:43:01 +01:00
9db62236da screenshot: Promisify SelectArea
The screenshot code has a fair bit of nested callbacks, which means
that it is a good use case for async functions and Promises.

Most code uses GIO's async pattern, which means it can be easily turned
into promises with Gio._promisify(); first handle the couple of cases
that need custom code though, starting with SelectArea.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/903
2019-12-20 15:42:42 +01:00
93fa1034f5 grabHelper: Add (promised-based) grabAsync()
Some GrabHelper uses are in the form:

    doPreGrabStuff();

    this._grabHelper.grab({
        onUngrab: () => {
            undoPreGrabStuff();
        },
    });

A promise-based variant allows to write this more cleanly as:

    doPreGrabStuff();

    await this._grabHelper.grabAsync();

    undoPreGrabStuff();

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/903
2019-12-20 15:41:32 +01:00
35494f5d08 popupMenu: Close when source actor gets hidden
If a menu is anchored to a source actor, it is odd to leave it floating
around when the source actor gets hidden.

Close the menu instead in that case.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/2002
2019-12-20 14:20:49 +00:00
4f66b301e7 panel: Disable menu-toggle shortcuts while top bar is hidden
We currently handle the case where the indicator itself is disabled
(read: hidden), but not when the entire top bar is invisible (for
instance when the primary monitor is in fullscreen state).

It is odd to pop up a top bar menu without the top bar, so check for
the indicator's mapped- instead of visible state.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/2002
2019-12-20 14:20:49 +00:00
d1f87ca115 extension-tool: Don't treat missing .js handler as error
After creating a new extension, we try to open the main source
file with the default handler, which fails when there is none.

But given that the extension was created successfully, don't treat
a missing handler as failure, and print the path to the new extension
instead.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/905
2019-12-20 14:11:03 +00:00
fe106358f5 appFavorites: unbreak my previous commits
Oops, apparently even a trivial commit is too hard for me to do without CI.
2019-12-20 13:59:53 +00:00
1c3ad0f1cf Update desktop IDs of favorite apps
Epiphany and Evolution updated their desktop IDs years ago. I assume the
rename list in appFavorites.js ensures this is still working properly
despite the obsolete names.
2019-12-20 13:53:12 +00:00
89b3104f8f appFavorites: add seahorse to rename list 2019-12-20 13:49:07 +00:00
624cf1dad4 extensions-tool: Set up translations for standalone builds
While we can now build gnome-extensions-tool as stand-alone project,
we are currently missing any translations, as those are part of
gnome-shell.

The easiest option for addressing this would be to symlink the toplevel
po directory into the subproject, however that would mean duplicating
the entire gnome-shell message catalogs.

So instead, set up a bare po directory and provide a script to populate
it from the translations in the toplevel po directory.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/877
2019-12-18 19:13:24 +00:00
4e9154ca64 extensions-tool: Add (back) README.md
This was dropped when importing the project into gnome-shell. Now
that is a bit more separate again, it makes sense to add it back.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/877
2019-12-18 19:13:24 +00:00
51518d4d96 extensions-tool: Move to a subproject
The gnome-extensions tool code is really independent from the rest of the
code base, and could be used either as part of the gnome-shell build or as
stand-alone project (for example for the extension-ci docker image).

We can actually support both cases by moving the code to a subproject.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/877
2019-12-18 19:13:24 +00:00
26dc2a439d build: Remove left-over file
The file was generated when importing gnome-extensions-tool from
a standalone repository. It isn't used by the build system, and
really shouldn't have ended up in the repository at all.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/877
2019-12-18 19:13:24 +00:00
bfd5fc3f24 doap: Add Georges Stavracas as a maintainer
Looks like he's is working too much in this module, and received
permission from Florian to add himself as a maintainer.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/901
2019-12-18 15:24:38 -03:00
60721a7c23 StPasswordEntry: Add the peek-password-icon for show/hide passwords
Also introduce a "show-peek-icon" property to enable/disable
the peek-password-icon in the password entry. This is useful
in cases where the peeking the password functionality needs
to be avoided.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/619
2019-12-17 23:08:43 +01:00
df230989b1 st-entry: Remove caps-lock feedback warning
This frees the entry's secondary icon that for other uses.
Caps-lock-warning feedback has been moved to be shown in
the various dialogs instead in the password-entries itself.
StPasswordEntry can now use a peek-password icon as the
secondary icon to show/hide the password present in the
entry.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/619
2019-12-17 23:08:43 +01:00
474dda7ffe js: Add caps-lock Warning to the dialogs
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/619
2019-12-17 23:08:43 +01:00
1d54f1e6ab shellEntry: Add CapsLockWarning class
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/619
2019-12-17 23:08:43 +01:00
a1238a0ea4 shellEntry: Remove isPassword Property
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/619
2019-12-17 23:08:43 +01:00
684b918915 js: Use StPasswordEntry for password entry fields
Use the new StPasswordEntry for password entry fields
and remove all direct handling of clutter text of the entry
 via clutter_text_set_password_char to show/hide the password
text. StPasswordEntry will provides a peek-password-icon which
will allow to show/hide the password present in the field to
the user in subsequent commits.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/619
2019-12-17 23:08:43 +01:00
b166de08dc shellEntry: Handle PasswordEntries automatically
shellEntry should not need to query the clutter-text directly
in order to know if the entry is for passport-input purpose.
shellEntry can easily determine a password-entry by querying
if it is an instance of StPasswordEntry and use it's API
whereever relevant.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/619
2019-12-17 23:08:43 +01:00
281c87d11b St: Add a StPasswordEntry subclass based on StEntry
StPasswordEntry will be put to use for password entries
in various shell dialogs. This is done to have a consistent
behaviour for all password entries and introduce a peek
password functionality for these password entries in the
subsequent commits.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/619
2019-12-17 23:08:43 +01:00
14eeaf4a2a padOsd: Re-query action labels after mode switches
Do this so the pad OSD is able to update dynamically to mode changes,
showing immediately the new actions for the current mode(s).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/898
2019-12-17 13:07:39 +01:00
9115f6e796 workspace: Pass device to startDrag()
This is necessary to make DnD operations work from tablet devices on
wayland, as it's not the same onscreen pointer sprite than mice. Fixes
window DnD in the overview on tablet devices, no longer having them stick
to the wrong pointer.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/897
2019-12-13 17:53:41 +00:00
0223d38602 shell-app: Add discrete GPU support for NVidia drivers
Use data from switcheroo-control to know which environment variables
to use to launch an application on the discrete GPU. switcheroo-control
version 2.0 or newer should be installed on Linux platforms.

Closes: https://gitlab.gnome.org/GNOME/gnome-shell/issues/1810
2019-12-13 00:44:28 +01:00
33c10e9180 shell: Prime the GPUs property cache for switcheroo-control
See: https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/781
2019-12-13 00:44:28 +01:00
c7dec4130d shell: Add API to access switcheroo-control D-Bus proxy
See: https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/781
2019-12-13 00:44:28 +01:00
512130172c main: Add switcheroo-control generated code
See: https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/781
2019-12-13 00:44:28 +01:00
a849945bc4 data: Update switcheroo-control D-Bus interface
It's backwards compatible, so just new properties.

See: https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/781
2019-12-13 00:44:24 +01:00
4d16d2ceed appDisplay: Remove unimplemented 'activate-discrete-gpu'
It was added in commit 009d021 but not advertised, and likely not used
by an application since then.

See: https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/781
2019-12-12 17:19:41 +01:00
9a45d9692a Bump version to 3.35.2
Update NEWS.
2019-12-11 18:51:11 +01:00
4a6c2f1fe6 util: Place spawned processes into a systemd scope
This improves the separation from the shell for applications launched
with Alt+F2 and in a few other cases.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/863
2019-12-11 09:34:36 +01:00
086ba11621 shell-global: Place launched applications into a systemd scope
This improves separation from the shells service scope for applications
launched using an XDG desktop file.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/863
2019-12-11 09:34:36 +01:00
154655838d weather: Only require auto-location authorization if sandboxed
Since commit 87e60ed97843, geoclue no longer pretends that authorization
is useful for system-installed apps (as they can easily lie about their
ID). Unfortunately this broke our auto-location support in case Weather
is installed non-sandboxed, as we are waiting for an authorization that
will never happen.

Unbreak it by only requiring authorization when installed as Flatpak.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1823
2019-12-09 11:10:54 +00:00
0795d8df5f magnifier: Adapt to painting and picking API change
This was left out in 988a0e7314

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/891
2019-12-07 13:08:28 +00:00
85bec783ee screenShield: Adapt to painting and picking API change
This was apparently forgotten in 988a0e7314, causing the warning:
`JS ERROR: Error: Too few arguments to method Clutter.Actor.paint`

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/891
2019-12-07 13:08:28 +00:00
ccd8b47d30 popupMenu: Close when a system modal pops up
Just like switcher popups, popup menus don't play well together with
system modals, and generally have a lower priority. So just like
switcher popups, close popup menus when a system modal dialog pops
up.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1536
2019-12-06 20:26:01 +01:00
c0309d9732 switcherPopup: Dismiss when a system modal dialog opens
As system modal dialogs may open without user interaction (for instance
polkit or network agent requests), it is possible for them to pop up
while the app/window switcher is up.

The current result of having both up simultaneously is clearly broken,
so we can either dismiss the popup or prevent the modal dialog from
opening. Assume that the dialog indicates a more important action and
should therefore take precedence, so go with the former.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1536
2019-12-06 19:55:39 +01:00
0185c288c3 perf-helper: Remove unused atoms
Those aren't used for anything, but make the helper dependent on X11.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/887
2019-12-05 16:51:00 +01:00
3c4c37e4d0 perf-helper: Add content for custom drawing
Drawing windows got a lot more involved with the advent of client-side
decorations. Instead of accounting for visible and invisible borders,
titlebar and shadows when necessary, just add an empty child for the
custom drawing.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/887
2019-12-05 16:51:00 +01:00
2ba4108838 appDisplay: Rename _allItems array to _orderedItems
Since both the `_items` object and the `_allItems` array include the
same items, the difference between those variables seems unclear. The
real difference between them (except the different data type) is that
`_allItems` is ordered in the same order as the visible grid, so rename
`_allItems` to `_orderedItems` which makes that more obvious.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/799
2019-12-05 15:43:34 +00:00
203c3f9949 appDisplay: Make AllViews folderIcons property private
This property is not used anywhere outside the class, make it private.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/799
2019-12-05 15:43:34 +00:00
61b71998a0 appDisplay: Make _items object a Map
Use a Map instead of an Object here makes it easier to loop through
keys, which we're going to do in the next commit.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/799
2019-12-05 15:43:34 +00:00
c4fa052b03 appDisplay: Use _getCategories function instead of duplicating the code
We already have a function which gets the categories of an app and
handles the null-return case, use it.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/799
2019-12-05 15:43:34 +00:00
f3eeb94c90 checkBox: Fix expand and align properties
In f2bd39b20c, the expand property for
checkboxes was accidentally set to the grandchild (the StBin), instead
of the child (the StBoxLayout) of the StButton. Fix that and let the
BoxLayout expand instead of the Bin.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/885
2019-12-05 15:35:22 +00:00
a2044c61ae extensionPrefs: Always redefine getCurrentExtension() on prefsModule access
We have three interactions with an extension's prefs module:
 - we import the module
 - we call its init() hook
 - we call its buildPrefsWidget() hook

The first two are one-time actions where we expect most getCurrentExtension()
calls (local imports, initTranslations() etc.).

However it's still possible that the extension will use the utility function
in buildPrefsWidget() as well, either directly or via other functions like
getSettings(): Make sure getCurrentExtension() returns the correct extension
in that case, not the last one whose preferences were initialized.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/873
2019-12-05 15:24:34 +00:00
2703eed446 extensionPrefs: Simplify state change handling
The new `ExtensionStateChanged` signal already passes the changed
extension object, no need to request it again with `GetExtensionInfo`.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/873
2019-12-05 15:24:34 +00:00
59a43f496d appDisplay: Move to rename folder location
Following the same reasoning of the previous commit, move to
the renamed folder location.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/883
2019-12-05 12:00:11 +00:00
d28bc7afe6 appDisplay: Show newly created folder when creating
The icon grid currently sorts icons by their names. When creating new
folders, the folder may end up being in a different page, and that's
confusing since we don't actually move to where the new folder is.

Move the icon grid to the newly created folder.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/883
2019-12-05 12:00:11 +00:00
22cb0b002d closeDialog: Fix scale of dialog for x11 clients in Wayland sessions
We missed this case in b6e57a5ae8,
XWayland clients obviously don't use MetaWindowWayland and thus they
don't apply the double scaling that commit was meant to fix.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/884
2019-12-04 22:25:03 +00:00
021f3e49b5 keyboard: Update extended key size on parent size changes
Extended keys should have the same size as their parent key, so
make sure to update them when the parent size changes.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1976
2019-12-04 20:48:37 +01:00
91bf7f1e44 keyboard: Use parent key's allocation for extended key size
An outdated allocation is likely still better in this case than the
preferred size, so use that instead of width/height which may fall
back.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1976
2019-12-04 20:48:37 +01:00
7fbdaadce2 keyboard: Create extended keys before updating hover state
We want extended keys to have the same size as their parent key,
but this is currently broken and the keys end up with their
parent key's preferred size (which is smaller than its allocated
size).

This is due to the way ClutterActor's width/height properties work,
which only return the "real" (i.e. allocated) size when the allocation
is valid, and fall back to the preferred size otherwise.

As changing an StWidget's hover state involves adding or removing
the `:hover` pseudo class, this is currently always the case.
Creating the extended keys first means the keyButton's allocation
is probably valid, so do that.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1976
2019-12-04 20:48:37 +01:00
ff7dfa9259 keyboard: Fix widget leak
We currently create an extended-keys popup every time it is requested,
but only destroy it (or rather: the last one) when the keyboard itself
is destroyed.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1976
2019-12-04 20:48:37 +01:00
85f10f1f6a keyboard: Use camelCase
Those aren't GObject properties, so should follow the regular JS style.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1976
2019-12-04 20:48:35 +01:00
9b4780fa1d keyboard: Reindent timeout handlers
We are going to touch some of the code, so take that opportunity
to move them to the new indentation style.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1976
2019-12-04 20:47:09 +01:00
8c4d07ba92 HACKING.md: Update sample code to use paint context
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/827
2019-12-03 19:07:15 +00:00
632a643994 Use paint and pick context to get framebuffer
Mutter and Clutter was changed to pass around the current target
framebuffer via the paint context instead of via the deprecated Cogl
framebuffer stack.

The framebuffer stack has also been removed from Cogl so change to use
the one in the paint context instead.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/827
2019-12-03 19:07:15 +00:00
988a0e7314 Adapt to painting and picking API change
While still leaving them unused, pass around ClutterPaintContext and
ClutterPickContext when painting and picking.

The reason for splitting this change up in two is to make it possible to
bisect easier in between the API change and the change to using the
framebuffer passed around with the temporary contexts.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/827
2019-12-03 19:07:15 +00:00
73776508b3 st: Remove broken parsing of @media rules
This code didn't even pay attention to the
cur_stmt->kind.media_rule->media_list, and unconditonally considered
each statement in the ->ruleset to be of kind ruleset.  That seems
broken.

(The theme doesn't use any @media queries, and they are unsupported
anyway.)

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1979
2019-12-03 18:53:36 +01:00
01c0803a4a Fix always-true condition
CrCascadePrivate->sheets is a statically-sized array inside the
struct; it can't be NULL.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/861#note_659216
2019-12-02 08:04:51 -06:00
582bfe830a cr-rgb: remove handling of "inherit" and "transparent"
st-theme-node.c already handles those by itself.

Part of https://gitlab.gnome.org/GNOME/gnome-shell/issues/1934
2019-11-29 17:50:03 +00:00
236bdaa53c Handle "color: inherit" directly in get_color_from_term(), not in libcroco
The idea is to move handling of "inherit" as early in the parsing as possible.

Part of https://gitlab.gnome.org/GNOME/gnome-shell/issues/1934
2019-11-29 17:50:03 +00:00
52f5793c9b Use get_color_from_term() instead of get_background_color_from_term()
The former already checks for term_is_transparent() as its first
thing.

Part of https://gitlab.gnome.org/GNOME/gnome-shell/issues/1934
2019-11-29 17:50:03 +00:00
1e8e08ce61 Simplify if statement
And make the end of st_theme_node_lookup_length() consistent with
st_theme_node_lookup_color() and st_theme_node_lookup_shadow().

Part of https://gitlab.gnome.org/GNOME/gnome-shell/issues/1934
2019-11-29 17:50:03 +00:00
05c3ac2359 get_length_internal() - remove unused argument 'suffixed'
It was passed as NULL in the single caller of this function.

Part of https://gitlab.gnome.org/GNOME/gnome-shell/issues/1934
2019-11-29 17:50:03 +00:00
47758d16ff Include the libcroco sources directly under src/st/croco
This is all of the original libcroco, minus these two which we don't use:

  - cr-sel-eng - the CSS selection engine for xmlNode.
  - cr-style.

Part of https://gitlab.gnome.org/GNOME/gnome-shell/issues/1934
2019-11-29 17:50:03 +00:00
867cffaf20 switcherPopup: Fix scrollable check
When commit c6cea277e replaced Shell.GenericContainer, the check
whether the required width exceeds the avilable width was changed
from using the minimum widths of items to the natural width of the
scroll view.

That doesn't work correctly, as the *natural* width may well exceed
the actually used width: SwitcherList bases its width request on
children's minimum sizes to force labels to ellipsize.

Fix this by using the minimum width of the scroll view's child instead.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1834
2019-11-29 16:47:48 +01:00
5f9036e815 calendar-server: Use correct timezone for all-day events
Since commit 28c535e34, we use the timezone associated with the ICalTime
instead of the default timezone when converting to time_t. However while
that is correct for most events, for ICalTimes that don't have a timezone
associated we still want to fall back to the default timezone instead of
UTC.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1895
2019-11-28 01:42:50 +01:00
5b957f69d8 theme: Add light styling to message buttons
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/865
2019-11-27 19:11:24 +00:00
749a4c9f6c appIcon: Draw running dot above the overview icon
Prevent the app-well-app-running dot from getting unintentionally
hidden behind the overview-icon background by initializing the
running-dot after its sibling overview-icon.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/866
2019-11-27 02:59:13 +01:00
0a9e1b4173 fileUtils: Delete deleteGFile hack
It is true that delete is a javascript keyword, but that doesn't
prevent it from being used as method name - there are event built-in
types like Map or Set with delete() methods!

So if that hack was ever needed, this hasn't been the case for years
now; just removed the hack now.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/862
2019-11-26 22:17:28 +00:00
66f4feeb16 Update Brazilian Portuguese translation 2019-11-26 19:58:38 +00:00
e642e1c106 texture-cache: Remove also scaled keys from the cache
We're storing in the texture cache images and scaled images appending
the scaling factor to the key. When a file changes the cache key
corresponding to that file is removed, but not the keys for the scaled
ones so that images in the cache are never reloaded.

This patch removes all keys from the cache related to the file that
changes, including those with the scaling factor.

A new set (hash table) was added to keep track of scale used to be able
to remove all possible images in the cache.

When the KEY is removed from the cache, we can look now in the scale set
for and each scale we also remove the key "KEY1.000000", "KEY2.000000",
etc.

Assuming that the number of used scales is small (I would typically
expect one or two), the overhead should be negligible.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/567
2019-11-26 08:28:21 +01:00
d9ef612302 data: Enable clean session shutdown after gnome-shell failure
If the GNOME shell crashes, we run a service that may disable
extensions. This is important so that users will not be locked out of
their own session in case an extension is causing crashes.

As this is a very agressive action, we tried to only do this in the
first two minutes of the session. Unfortunately, the logic was broken
and would result in an unclean session shutdown.

Fix this by using the newly introduced gnome-shell-disable-extensions
file. This is created by the extension subsystem for a period of time to
indicate the extensions may be the cause of a gnome-shell failure.

See
  https://gitlab.gnome.org/GNOME/gnome-session/issues/43
for a log of the bug happening and the gnome-session part to fix this.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/858
2019-11-25 21:49:49 +00:00
f742484795 extensionSystem: Create a file to flag that extensions are being loaded
When the extension system is loaded, create the
gnome-shell-disable-extensions file in the users runtime directory. This
file is automatically removed 60s later. The sole purpose of this file
is to be consumed by the systemd units. If the file exists, the systemd
units will disable extensions when the gnome-shell fails.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/858
2019-11-25 21:49:49 +00:00
1ecdb393d7 Update Greek translation 2019-11-25 19:20:20 +00:00
eee1ab4890 introspect: Fix whitelist check
The whitelist is a list of well-known D-Bus names, which we then search
for the unique name we get from the method invocation - unsuccesfully.

Fix this by watching the bus for any name in the whitelist in order
to maintain a map from wel-known to unique name that we can use for
matching.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1916
2019-11-25 20:00:10 +01:00
42eb9f4a28 theme: Add :active styling to message-close and media control buttons
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/855
2019-11-25 18:02:07 +02:00
18421e8aed theme: Add message close button styling
Since the notification message close button had no border, or mouse
over effect, there was no way to determine whether the mouse cursor
were over the button.

Improve this by adding a message-close-button class for the close
button, and a styling for its hovered state, based on media control
button styling.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/855
2019-11-25 18:02:07 +02:00
c255b4d14e theme: Darken hovered message-media-control button
Increases contrast between normal and hovered states in
message-media-control buttons. Previously there was very little
difference between the two states, making it hard to distinguish
whether the mouse cursor was over the button.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/855
2019-11-25 15:52:59 +00:00
bb48205aae extensions-tool: Fix removing from settings list
When removing a string from a settings list, we iterate over all
existing entries and copy all strings except the one that's being
removed to a new list, which is then written to GSettings.

However we currently always increment the index, so we end up with
a NULL entry in place of the removed entry, which is then interpreted
as the end of the list. In other words, we also remove all entries
that follow the removed string.

Fix this by looping over the list entries instead of the index, and
only increment the index for entries we copy.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1946
2019-11-25 16:18:08 +01:00
5a287a4205 appDisplay: Add a timeout when switching pages during DnD
Currently when dragging an icon to the space above or below the appGrid
to switch pages, we do so very quickly without checking when the last
page-switch happened. This makes it hard to move icons to pages which
are not the first or the last one, since the other pages are skipped
very quickly.

To fix this, add a timeout of 1 second that blocks switching pages after
a page-switch using drag overshoot occured.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1693
2019-11-24 19:35:30 +01:00
b0c8192496 appDisplay: Add threshold after overshoot page switches
We currently always switch app pages when a dragged app icon
moves outside the grid boundaries, regardless of any previous
page switches. This makes it too easy to switch multiple pages
accidentally, so add a small threshold that the icon has to
move back towards the grid before allowing another page switch.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1693
2019-11-23 20:11:21 +01:00
0897915b05 appDisplay: Simplify event blocking while folder is opened
There's no need for a `inhibitEventBlocker` interface. Since we connect
to "open-state-changed" of our folders in the AllView anyway, we can
just make the event blocker visible while a folder is opened, and hide
the event blocker during DnD.

This allows keeping the eventBlocker reactive at all times and fixes an
issue where DnD to create a new folder is impossible if no folders are
present because the eventBlocker would not get inhibited.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1652
2019-11-23 18:31:46 +01:00
2894085c45 volume: Skip volume-change feedback while playing
The audio feedback for volume changes is useful when nothing is outputting
sound, but only then. Skip the sound notification in that case.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/53
2019-11-23 15:16:51 +01:00
c506eda20a gvc: Update submodule
gnome-volume-control now has API to expose the stream state, which
we will need in the following commit.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/53
2019-11-23 15:16:51 +01:00
a8005e3c30 slider: Stop emulating drags in key handling
Emitting ::drag-end after changing the slider value via arrow keys
was a cheap way to make the sound feedback work for keyboard input.
But now that the volume indicator plays the sound on ::value-changed
as well, we can stop doing that - after all, key presses aren't drags.

Besides that, this will make the limiting of feedback to actual volume
changes from the previous commit work for key events as well.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/53
2019-11-23 15:08:54 +01:00
6c8eb1a18e volume: Only emit sound feedback after volume changes
gnome-settings-daemon doesn't play the volume change sound when
the volume stayed the same (that is, it is already at its maximum
or minimum). This looks like the right thing to do, so copy its
behavior.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/53
2019-11-23 15:08:54 +01:00
5af8bf2788 volume: Add back sound feedback on scroll
Commit 8d4855f100 accidentally removed the volume change feedback
for scroll events. Add it back to be consistent again with moving
the slider via arrow keys, slider drags/clicks and gsd's media keys
handling.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/53
2019-11-23 15:08:54 +01:00
7e9f30da0a appDisplay: Ensure we don't recreate existing AppIcons for folders
This was missed in 910037f014, make sure
we do the same thing for AppIcons that are created when reloading
folders.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/851
2019-11-23 22:01:09 +01:00
2842670082 cleanup: Remove another pair of unneeded parentheses
Eslint didn't spot this before version 6.5, so this fell through
the cracks.
2019-11-23 01:29:20 +01:00
95f388b9a7 dateMenu: Don't ellipsize forecast times and temps
While those should be concise enough to fit, they may not where
temperatures drop into double-digit negatives. It seems better
to accept some awkward horizontal scrolling in that case than
shorten relevant information.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1926
2019-11-23 01:13:08 +01:00
e72c38b5ab dateMenu: Move weather forecast validity check
Commit b779f6f728 added a check to filter out invalid weather forecasts.

However the check is currently done when creating UI for the forecasts,
which means we end up with fewer forecasts than we could display if any
forecasts are invalid.

We can avoid that issue by checking the validity while collecting the
forecasts, so do that instead.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1927
2019-11-23 01:13:08 +01:00
f6f373b0c2 dateMenu: Only show forecasts
We currently always start with the current weather info, then append
forecasts. This is slightly confusing, as the only hint that the
first item is special is the past (and potentially "odd") time.

Stop doing that and base all items on forecasts.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1927
2019-11-23 01:13:05 +01:00
b757f5c655 dateMenu: Don't limit weather forecasts to the same day
As we get closer to midnight, we show fewer forecasts than we could
fit, or none at all. It makes more sense to continue the forecasts
into the wee hours in that case, so only exclude past forecasts,
but not ones from following days.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1927
2019-11-23 01:13:05 +01:00
784c0b7e4b dateMenu: Try harder finding a reasonable weather location name
Weather stations can have unwieldy long names, which don't fit the
limited space we have available. City names are usually more suitable,
so use the name of the nearest city instead if possible.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1927
2019-11-23 01:13:05 +01:00
f2df9f1ae4 dateMenu: Add some spacing between weather header and location
In case of a very long location name, the label may take up all
available space. Make sure there is at least some spacing between
header and location in that case.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1927
2019-11-23 01:13:05 +01:00
18a1435c25 dateMenu: Bottom-align weather title/location
The two labels use different font sizes, so they don't align properly.
Unfortunately we don't have BASELINE alignment in Clutter, but at least
END comes closer than the default FILL.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1927
2019-11-23 01:13:05 +01:00
669d12f957 dateMenu: Re-indent weather section
Before making code changes, make sure the class confirms to the
new coding style.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1927
2019-11-23 01:13:05 +01:00
d52b23dec3 switcherPopup: Improve modifier-less keybinding navigation
Commit c899453800 lifted the requirement of switcher keybindings
to contain a modifier, however it is currently only possible to
finish it by letting it time out.

Improve that by also accepting space/enter key presses to confirm the
selection immediately.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1883
2019-11-22 23:46:31 +00:00
998fe58482 switcherPopup: Use roundtrip time when the popup is modifier-less
The noModsTimeout obviously finishes inside a timeout callback, which
means `global.get_current_time()` might return Clutter.CURRENT_TIME (ie.
0) when called inside it, because it's not called while handling an
event. This means when switching apps or activating a window, the
timestamp passed to `activate_window` may be 0, which is the reason why
the altTab switcher is currently broken when using modifier-less
keybindings.

Fix that by using `meta_display_get_current_time_roundtrip`, which
always return a valid timestamp, instead of
`shell_global_get_current_time`.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/847
2019-11-22 22:09:47 +00:00
109f39afa5 pageIndicators: Redesign and add position-based animation
Remove setCurrentPage() function, introduce setCurrentPosition() instead,
which allows to have fractional positions.

Make inactive dots smaller, filled and partially transparent, as opposed to
larger and fully opaque active dot. Make dots smaller overall, remove
borders. Interpolate each dot between active and inactive state based on
scroll position.

Make it impossible to "uncheck" the active dot.

Thanks Florian Müllner for parts of the code.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1932

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/843
2019-11-23 03:01:51 +05:00
9790b0ee5d st/button: Notify :pressed changes
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/846
2019-11-22 18:55:40 +01:00
c48330a986 cleanup: Use g_clear_handle_id() for g_source_remove()
It makes sure we do not forget to zero the id and lets us avoid
zero checks before. We use it for all new code, lets clean up the
existing code base.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/845
2019-11-22 01:45:25 +01:00
9132063b87 switcherPopup: Show immediately on second key press
We slightly delay showing the switcher popup to avoid flashing it
briefly in the common case of quickly switching back-and-forth
between two windows.

However some users perceive this delay as slowness. Address this by
showing the popup immediately when another key press is consumed
(that is, a key like Tab is pressed).

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1928
2019-11-22 00:20:29 +01:00
d5e8f8cdf7 mpris: Use a scope specific message instead of a global one
Since the mpris implementation of the notification tray supports showing
multiple notification (one for each player), it doesn't make sense to
have only one global property to store the message, since that only
allows referencing one message at a time.

Instead, handle the MediaMessages completely inside the scope of
`_addPlayer()`, this should allow showing more than one message again,
which broke with commit 4dc44304df [1].

[1] https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/791

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/833
2019-11-21 22:54:07 +00:00
55362aed3d messageList: Don't include message actor in error message
Because the message actor could also be undefined or a already
deallocated ClutterActor, we sometimes fail to show the error message
and get an error from Gjs instead.

So make sure we always log the proper error message and just leave out
the actor.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/833
2019-11-21 22:54:07 +00:00
135d178d08 cleanup: Use g_clear_signal_handler() where possible
`g_clear_signal_handler()` is usually cleaner and saver than
`g_signal_handler_disconnect()`. We use it new code, lets also
adopt the existing one.

See also https://gitlab.gnome.org/GNOME/mutter/merge_requests/868
and https://gitlab.gnome.org/GNOME/mutter/merge_requests/940

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/842
2019-11-21 22:37:37 +00:00
e7b9bd75d8 appIcon: Remove drag monitor on destroy
It may happen that the app icon is destroyed with a drag
monitor still around, in which case, a load of warnings
will be shown.

Make sure to remove any pending drag monitor on destroy.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/841
2019-11-21 22:28:46 +00:00
bd173ac5d2 folderView: Reset schemas before removing the folder
When removing the last icon of a folder, FolderView first removes
the folder from org.gnome.desktop.app-folders.folder-children, then
proceeds to reset all its keys, which removes the relocatable schema.

That order of operations turns out to be problematic. Removing the
folder from 'folder-children' destroys the folder icon, which in turn
destroys the folder view, which throws a load of warnings in the
journal.

Fix that by removing the folder after resetting the schema keys. In
fact, what we're doing here is not using 'this' anymore.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/841
2019-11-21 22:28:46 +00:00
bfc7c1cd65 baseAppView: Destroy icon when removing
We cannot rely on the garbage collector to do that in a timely
manner, so destroy it explicitly.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/841
2019-11-21 22:28:46 +00:00
cae69b3a88 allView: Rename variable
The variable that holds the list of application icons is
called 'newApps', but that technically was never true,
since we only create new app icons when necessary.

Rename it to 'appIcons'.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/841
2019-11-21 22:28:46 +00:00
910037f014 allView, frequentView: Only create icons when necessary
The views (AllView and FrequentView) build a list of all applications
they contain. BaseView then diffs between what's currently added, and
what needs to be added, and removed.

This approach has a problem though: creating an AppIcon or a FolderIcon
connects to various signals, and we confuse the garbage collector.

When building the list of applications, instead of always creating new
icons, try to use already existing icons first.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1610
Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1694
2019-11-21 22:28:46 +00:00
acaa9f7f77 polkitAgent: Fix spinner
Commit 6af25b282c accidentally changed the case of the property.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/840
2019-11-21 22:04:39 +01:00
b779f6f728 dateMenu: Skip weather forecast if not valid
GWeather.Info.get_value_update() may indicate that the forecast is not
valid, or it may return a timestamp of 0 to indicate the information has
never been updated. In both of these cases, skip creating a widget for
it, as the information will not be accurate.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/835
2019-11-20 13:08:22 -08:00
83f224e08b dateMenu: Format weather forecast times without AM/PM
If the clock is set to 12h, the AM/PM in the weather forecast times
should be clear from the context, because they are the immediately
following hours. This makes it less likely that the times will be
ellipsized (in which case the AM/PM wouldn't be shown anyway.)

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/835
2019-11-20 13:08:06 -08:00
c1a7c71549 Increase .calendar-today visibility
Adds some needed contrast to the calendar widget current day, solving #1873.
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/823
2019-11-20 12:33:44 +00:00
c68bd33432 appMenu: Hide stopped spinner actor
Get rid of leftover empty space from the application menu panel
button, that was used by the spinner actor, which remained visible
even after the spinner had stopped.

Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/issues/1679
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/832
2019-11-19 20:56:16 +02:00
8f4e91a738 animation: Add parameter for hiding stopped Spinner actor
Not hiding leaves the empty actor space visible, which may have an
undesirable effect on the parent element's size or spacing/padding.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/832
2019-11-19 19:55:28 +02:00
6af25b282c animation: Turn Spinner animate parameter into Params option
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/832
2019-11-19 19:54:13 +02:00
e3e1a27f2d Update Friulian translation 2019-11-18 08:56:08 +00:00
c1ec7b2ffa keyboard: Try harder to find a matching layout
While we support a reasonable list of layouts nowadays, we don't
include many variants like `fr+oss`. Instead of directly falling
back to the `us` layout, try stripping the variant first, as the
base layout is likely closer to the expectation than `us`.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1907
2019-11-16 16:12:06 +01:00
45ebb94b33 polkitAgent: Cancel session after disconnecting signal handlers
When cancelling the PolkitAgent session before disconnecting the signal
handlers, we receive a "completed" signal where `gained_authorization`
is set to FALSE, which means we show an error message inside
`_onSessionCompleted()`.

This in turn means we show an error message every time we cancel a
session. In practice this wasn't really relevant so far since we only
destroyed the session when an actual error occurred before. Now that the
dialog supports empty passwords, we also call `_destroySession()` when
the user changes and no longer has a password set, and in this case we
want to cancel the current session without showing an error message.

So to fix this, disconnect the signal handlers before cancelling the
session, which makes sure we don't receive the last "completed" signal
in case we cancelled the session ourselves. This change also allows
removing `this._wasDismissed`.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/829
2019-11-16 12:09:25 +01:00
89bf360bad polkitAgent: Use dialog as confirmation when the user has no password
When a user has no password and a polkit authentication is started,
instead of blindly initiating the admin session, show the regular
"Authentication Requested" dialog (but without the password entry). This
makes sure that the user's admin session is only effectively started
after the user chooses to proceed with the authentication, which
provides an extra confirmation step that can be vital for critical
tasks.

To do this, we show the dialog inside `_onUserChanged()` right after the
dialog was created instead of calling `performAuthentication()` from
`_onInitiate()`. The bug mentioned in `_onInitiate()` is no longer an
issue since we show the dialog in all cases now anyway.

Ideally we should use a different wording than "authentication" when the
user has no password set, and use "confirmation" instead. However polkit
already sends the requests with such messages (e.g. "Authentication is
required to configure software repositories"), and it's important to
show those to the user, so this patch keeps the regular wording.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/829
2019-11-16 12:09:25 +01:00
3a7228cf2f polkitAgent: Only reset UI on session resets while opened
Since `_destroySession()` is not only called before we try to initiate a
new authentication session with Polkit, but also when the dialog is
closed, it's currently possible that key focus is grabbed by the close
button after the dialog was dismissed and hidden. This is causing a bug
where after dismissing one of multiple queued dialogs, key focus goes
away and keyboard navigation with the new dialog is impossible.

Fix this by only resetting the UI of the dialog if the dialog is still
opened/visible at that point.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/828
2019-11-15 23:26:47 +00:00
c1ae634174 panel: Don't chain up to non-existent parent vfunc
Just as with c35b4cede5, there's no
default vfunc implemented by any parent which causes gjs to crash when
trying to call it.

So return EVENT_STOP if the key press successfully toggled the button,
and EVENT_PROPAGATE otherwise.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/830
2019-11-14 19:52:34 +01:00
88bcaafe86 Stop referring to ClutterTexture
ClutterTexture is removed from mutter's clutter fork, so lets stop
referring to it. This also happens to fix an incorrect type cast.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/822
2019-11-12 22:05:13 +01:00
66fc5c07bb background: Add exception to no-loop-func rule
Modifying variables from an outer scope in functions created in a loop
is considered problematic by eslint, because the variable value in the
resulting closure is often not what the coder intended.

In this particular case however, the scoping is correct, so add a comment
to disable the rule locally.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/818
2019-11-11 23:51:17 +00:00
a32c4f30d1 style: Allow lonely ifs where appropriate
We now have a lint rule to disallow lonely ifs, however there are
cases where the "lonely" part mirrors code from the preceding if
clause. Opt out of the lint rule in those cases.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/818
2019-11-11 23:51:17 +00:00
65c5cfd4dc lint: Disable eqeqeq in legacy configuration
Using type-safe comparisons is a good idea, but after not doing so
for a decade, there's too much existing code around for flipping
the switch right away.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/818
2019-11-11 23:51:17 +00:00
0483c78dd1 lint: Sync configuration with gjs
gjs updates its configuration to a much more complete and
thorough set, follow suite.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/818
2019-11-11 23:51:17 +00:00
abc7cc9a26 lint: Convert eslint JSON to YAML
gjs has changed its configuration to YAML, so switch to that format
to keep syncing possible.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/818
2019-11-11 23:51:17 +00:00
913990b9ea folderView: Center folder icon
The FolderView class is responsible for creating the 4-item
grid of the folder icon, with the preview of the first four
apps inside the folder.

However, with the deprecation of StAlign as child properties,
the folder icon stopped being horizontally centralized.

Center the folder icon horizontally again.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/817
2019-11-11 18:12:32 -03:00
61210fdae1 cleanup: Use JSDoc for documentation comments
It's a better fit than gtk-doc, and eslint can validate that they
are complete and use correct syntax.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
077d8f33fb cleanup: Don't use gtk-doc syntax for regular comments
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
e44adb92cf cleanup: Avoid unnecessary parentheses
Extra parentheses usually add noise rather than clarity, so avoid
them.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
ebf77748a8 cleanup: Require "dangling" commas
Since ES5, trailing commas in arrays and object literals are valid.
We generally haven't used them so far, but they are actually a good
idea, as they make additions and removals in diffs much cleaner.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
07cc84f632 cleanup: Only omit braces for single-line blocks
Braces can be avoided when a block consists of a single statement,
but readability suffers when the statement spans more than a single
line.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
c860409da5 cleanup: Use object shorthand where possible
ES6 allows to omit property names where they match the name of the
assigned variable, which makes code less redunant and thus cleaner.
We will soon enforce that in our eslint rules, so make sure we use
the shorthand wherever possible.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
9eaa0089d0 cleanup: Fix missing/stray spaces
Those are wrong according to our style guidelines, but the previous
eslint ruleset didn't catch them.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
682bd7e97c cleanup: Don't shadow variables
Having variables that share the same name in overlapping scopes is
confusing and error-prone, and is best avoided.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
2e4e2500dd cleanup: Avoid "lonely" ifs where it makes sense
If an else block only contains an if statement, the two can be
combined into an if-else block, which cuts down on indentation
and usually helps legibility.

There are exceptions (for instance where the outer if and else
blocks are mirrored), but where it makes sense, change the code
to avoid lonely ifs.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
67ea424525 cleanup: Avoid unnecessary braces
Our coding style has always been to avoid braces when all blocks
are single-lines.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
69f63dc94f ctrlAltTab: Use arrow function for callback
This was left over because we are binding a different `this`, but
it is easy enough to replace.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
bef5043135 jsParse: Unnest functions
Nesting functions can be helpful for private helper functions, but
here they are accessing some variables from the outer scope and
shadowing others. Split them out to avoid any ambiguity.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
2019-11-11 19:25:14 +00:00
fea5ecc9e8 allView: Ensure event blocker is reactive before popup is open
The event blocker in AllView is responsible for stealing click events
from the icon grid and closing the folder popup. The event blocker is
kept unreactive when no folder popup is visible, and it's made reactive
as a response to the 'open-state-changed' signal.

Using this signal, though, is problematic. When opening an app folder,
the icon grid first opens space for the folder to fit in; during this
period, it's possible to click on another folder icon, and break the
icon grid state.

Make sure the event blocker is reactive immediately after clicking on
a folder icon.

Related: https://gitlab.gnome.org/GNOME/gnome-shell/issues/1470

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/816
2019-11-11 16:59:45 +00:00
697912d8a4 js: Fix alignment
The old StBin alignment properties defaulted to MIDDLE, while the
ClutterActor properties use FILL. Fix the resulting fallout by
setting the alignment explicitly where necessary.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/809
2019-11-11 16:52:10 +00:00
a0b0237689 Update Indonesian translation 2019-11-10 02:35:26 +00:00
8eb5d5aac5 style: Don't specify font-family
Now that the default font follows the interface setting, stop
overriding it in the CSS.

https://bugzilla.gnome.org/show_bug.cgi?id=688288
2019-11-08 23:48:42 +00:00
f28f041a95 theme-context: Use interface font instead of hardcoded default
With this, gnome-shell will now follow the interface setting instead
of hardcoding a default font, unless when overwritten by the CSS.

https://bugzilla.gnome.org/show_bug.cgi?id=688288
2019-11-08 23:48:42 +00:00
cd84fa824f st: Add Settings:font-name property
This maps to the corresponding interface setting and will be used to
replace the hard-coded font in the CSS.

https://bugzilla.gnome.org/show_bug.cgi?id=688288
2019-11-08 23:48:42 +00:00
40bd65c9ba st: Fix a minor leak
StSetting is used as a singleton, so this leak does not matter in
practise.

Spotted by Jonas Dreßler.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/486
2019-11-08 23:48:42 +00:00
5e43f282a1 calendar: Define EventSourceBase and extend EventSource's
Objects implementing EventSource should have some mandatory methods and
properties, we can ensure this by defining an EventSourceBase abstract
class.

So inherit EmptyEventSource and DBusEventSource from it making sure that
they implement all the needed methods, using native properties and
replacing the 'notify::*' fake signal emissions with proper object
notifications.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/563
2019-11-08 23:12:47 +00:00
d83d8f2c45 modemManager: Define ModemBase GObject class for modems
Use GObject based objects for ModemGsm, ModemCdma and BroadbandModem.
This allows to define a base class that we can use to natively define
properties and notify property changes.

We can now remove the "fake" notify signals with proper properties
notifications.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/563
2019-11-08 23:12:47 +00:00
348e4ac901 background: Inherit Animation from GnomeDesktop.BGSlideShow
Animation background is just wrapping a native GnomeDesktop BGSlideShow
object, so instead of using composition we can now just inherit from the
native GObject, re-using native properties when possible, and avoiding
to keep an extra wrapper to the bg file.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/563
2019-11-08 23:12:47 +00:00
5944a1e74b keyring: Inherit KeyringPrompter from Gcr.SystemPrompter
Keyring is just a simple wrapper to a Gcr.SystemPrompter object, so use
inheritance instead.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/563
2019-11-08 23:12:47 +00:00
0ed702d1af polkitAgent: Inherit AuthenticationAgent from Shell.PolkitAuthenticationAgent
AuthenticationAgent is just a wrapper of Shell's PolkitAuthenticationAgent, so
instead of using composition we can simply extend the base GObject.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/563
2019-11-08 23:12:47 +00:00
fc71f8b33a windowManager: Complete interrupted size change effects
Resizing effects are more finicky as other effects, as the actual
animation is delayed until we receive the ::size-changed signal.

However that signal may never be emitted if the window is destroyed
just after starting the size-change effect, in which case the effect
is never completed, blocking mutter from destroying the corresponding
window actor.

Address this by tracking when a resize effect is pending, and complete
the effect when appropriate.

https://gitlab.gnome.org/GNOME/mutter/issues/655
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/815
2019-11-08 18:58:55 +01:00
cb7374b1ec windowManager: Use Sets to track ongoing effects
We only care whether an effect is ongoing for an actor, not about
any particular order. Sets are more convenient than arrays in that
case, so use them instead.

https://gitlab.gnome.org/GNOME/mutter/issues/655
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/815
2019-11-08 18:58:48 +01:00
f5996a9232 inputMethod: Compare ibus context before processing key event result
In the xwayland-on-demand scenario, it may happen that Xwayland is
shutdown (causing a restart of ibus-daemon to drop ibus-x11) while
we are typing.

If we have a bit of bad luck, this will cause the IBusInputContext
to be disposed (due to its bus "closing") at a time when we have
an ibus_input_context_process_key_event_async() request on the fly.

As the object is disposed in between this would tickle JS (rightfully
complaining that it's been disposed under its feet) and make us pass
an actually NULL IBusInputContext to the corresponding _finish()
function (despite the IBusInputContext being still held alive by some
other refs). This will assert and abort in
ibus_input_context_process_key_event_async_finish() then.

To handle this, listen for IBusInputContext::destroy, and reset our
internal state, this way we can compare on the JS side that the
IBusInputContext is indeed an up-to-date one.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/813
2019-11-08 12:23:15 +00:00
5fd52e99d3 power: Handle "100% but charging" case
I've observed that UPower can occasionally report a charge level of 100%
while the state is still "charging". This usually doesn't last very long
but it is noticeable because the power icon changes to a "missing icon"
icon. This will handle that rare case correctly.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/814
2019-11-07 12:58:54 -08:00
fd5989e99a ci: Fix checking out mutter on stable branches
For stable branches, we currently only check out the correct mutter
branch for merge requests. For the regular pipeline, our code to
determine the current shell branch fails because CI runs on a
temporary "pipeline/12345" branch that doesn't exist for mutter.

Switching to the correct gitlab environment variable fixes that.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/811
2019-11-06 22:52:38 +00:00
cf6beee9e2 screenshot: Allow saving to clipboard
If no target file is specified (i.e. filename is an empty string), the
screenshot will be stored on the clipboard instead.

https://gitlab.gnome.org/GNOME/mutter/issues/789
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/810
2019-11-06 22:45:22 +00:00
be5f5ec9d4 shell: Make screenshot API stream based
Instead of dealing with filenames, make the low-level API use streams
so the target remains generic.

This so far means JS code now determines the appropriate filename to
use for storing the screenshot, but will be used in other ways in
future commits.

https://gitlab.gnome.org/GNOME/mutter/issues/789
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/810
2019-11-06 22:45:22 +00:00
a58bdbfbd4 st: Add StClipboard method to set arbitrary clipboard content
This complements the current text-based API.

https://gitlab.gnome.org/GNOME/mutter/issues/789
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/810
2019-11-06 22:45:22 +00:00
f51952f5d6 shell: Remove format_date() utility function
It is now unused.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/807
2019-11-06 20:19:57 +00:00
ac1f896107 environment: Reimplement Date.toLocaleFormat() override
Now that we no longer go through GTimeVal to convert from Date to
GDateTime, there is no more reason for using a C helper function.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/807
2019-11-06 20:19:57 +00:00
3913fa5044 environment: Stop adding child_set() to layout managers
We no longer use any layout managers that use custom child properties
instead of the generic Clutter.Actor properties, so this override
is completely unused.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/808
2019-11-06 09:42:57 +01:00
32185c17d0 environment: Stop monkey-patching Clutter.TableLayout
It's not used for anything anymore, and may well end up being removed.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/808
2019-11-06 09:42:57 +01:00
d3d165243c cleanup: Use non-deprecated key symbols
Clutter originally cluttered its namespace with key symbols, before
prefixing all symbols with KEY. We still use the unprefixed symbols
occasionally, replace them so mutter can drop the deprecated symbols.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/808
2019-11-06 09:42:57 +01:00
1e203f4631 cleanup: Replace deprecated lower/raise calls
Those methods have been deprecated for a long time, so
move to the drop-in replacement.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/808
2019-11-06 09:42:57 +01:00
0617be9fb9 windowManager: Stop using Clutter.Actor.prototype.reparent()
It has been deprecated for ages, and is about to be dropped from mutter.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/808
2019-11-06 09:42:57 +01:00
0749ac27ce calendar: Use Clutter.GridLayout
Clutter.TableLayout has been deprecated, so move to the recommended
replacement.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/808
2019-11-05 23:50:06 +01:00
d5eafbad87 polkitAgent: Use a timeout for resetting the dialog
Since polkit takes a few milliseconds from initiating the session to
emitting the "request" signal, don't introduce visual distraction by
hiding the password entry and showing it again a few ms later.

So start a timeout of 200 ms when we destroy a session and if no session
request (i.e. a request for a password-authentication) happened during
this timeout, hide the password entry.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/788
2019-11-05 18:52:33 +01:00
5c7a701a68 polkitAgent: Reset dialog to defaults after cancelling polkit session
Since we don't know if polkit/PAM will request a password (emitting the
"request" signal) or use another authentication method like a
fingerprint after the current authentication failed, hide the password
field and make the "Authenticate" button insensitive after cancelling
the session, just like we do when creating the dialog.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/788
2019-11-05 18:52:33 +01:00
cd36301d2b polkitAgent: Make authenticate button insensitive if password is empty
According to the mockups, make the polkit dialogs "Authenticate" button
insensitive and don't respond to pressing the enter key if no password
is supplied.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/788
2019-11-05 18:52:33 +01:00
70203b58ca polkitAgent: Only set key focus to password entry after opening dialog
Set the key focus to the password field only after we got a request
(and therefore know that a password is requested) instead of using
`setInitialKeyFocus()`. This way we don't try to focus the password
field by default if we aren't showing it (e.g. in case the user has no
password or is using fingerprint login).

Also we have to move the call to `grab_key_focus()` to happen after
`_ensureOpen()`, because otherwise the ModalDialog will set the focus to
one of the buttons while opening itself.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/788
2019-11-05 18:52:33 +01:00
c627d47019 polkitAgent: Also show user avatar for root user
Show the user avatar for all users, including the root user. The root
user will always have the generic avatar, but it looks more consistent
than showing no avatar at all.

This way we also don't have to worry about the spacing introduced by the
polkit-dialog-user-layout CSS class, which would give the
"Administrator" label a small offset to the left.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/788
2019-11-05 18:52:31 +01:00
f546715cc3 polkitAgent: Update user name on user changes
Right now we only update the user avatar on the user-changed signal, but
since we also display the users real name we should also update that if
the user changes.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/788
2019-11-05 18:52:15 +01:00
f5e179f03d polkitAgent: Fix a typo of a signal name
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/788
2019-11-05 18:49:05 +01:00
35a265a183 Updated Spanish translation 2019-11-05 15:38:42 +01:00
147a743d8d system: Replace action icons with regular menu items
Besides making the menu a bit less special, it allows us to fit both
shutdown and suspend actions without any hidden alt-key Easter eggs.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/270
2019-11-05 13:05:59 +00:00
e4147f3611 altTab: Use correct actor in label height computation
Commit f2bd39b20 removed an intermediate bin, and now we use the
thumbnail bin instead of the label actor to compute the label
height, whoops.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/804
2019-11-05 12:52:29 +00:00
6a42d77261 st: Track stylesheet changes on the StThemeContext
Instead of every individual StThemeNode. There are essentially two kinds
of theme nodes: Those we create for lookups, and those interned by the
theme context and used by StWidgets. Listening to the signal on the former
is pointless as they are short lived and not meant to be really used for
drawing. So it is only essential to track stylesheet changes in those we
intern for later use.

This change does precisely that, it lets the StThemeContext track the
stylesheet changes and let all known theme nodes reset their state for
it.

The internal array holding all connected handlers for this signal in glib
was about the biggest single allocation made in gnome-shell, as interned
theme nodes nodes are around the 4 to 5 digit numbers. This essentially
makes it disappear.

This however means that widgets that are explicitly set a theme through
st_widget_set_theme() don't get their theme node implicitly updated.
There's little reasons to use that API, so perhaps this is an acceptable
tradeoff.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/779
2019-11-05 12:36:28 +00:00
55867c40c4 st: Drop StWidget theme overriding API
A StWidget could get its style from a) a theme set in the StThemeContext,
and b) directly through it's ::theme property. Generally, overriding CSS
through the latter cannot be recommended as it loses any connection with
the global theme (eg. the ones you get through selector specificity).

It sounds a bit too powerful and pervasive, there's no use for it in
gnome-shell and doesn't look like something that could be recommended on
extensions. So, just drop this piece of API.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/779
2019-11-05 12:36:28 +00:00
28c535e341 calendar-server: Uses wrong timezone for event times
The conversion to UTC/time_t time was not using correct timezone.

Closes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1714
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/806
2019-11-05 12:27:06 +01:00
f309d98bc8 cleanup: Use more template strings
xgettext got better at recognizing template strings, so we can
replace more string concatenations. Alas xgettext is still buggy
(surprise, regular expressions are hard), so there are still a
handful of holdouts that prevent us from making a complete switch.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/792
2019-11-05 01:51:29 +00:00
2c62e45168 st: Remove StBin's align properties
They are now completely unused, so remove them and stop the confusing
shadowing of ClutterActor's own x/y-align properties.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/803
2019-11-04 21:27:56 +01:00
f2bd39b20c js: Use generic actor properties to align StBin children
StBin's fill/align properties are now no-ops; get back the intended
child allocation by setting the corresponding x/y-align on the child.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/803
2019-11-04 21:27:56 +01:00
72af64d964 st: Remove st_get_align_factor() utility method
It is now unused.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/803
2019-11-04 21:23:32 +01:00
2f39bd8ba4 st/bin: Use child's align properties
By now, all containers and layout managers except StBin (and its
subclasses) use the generic ClutterActor expand/align properties
to control how their children are laid out.

This is particularly confusing as two or the properties StBin uses
for layout - x-align and y-align - shadow the generic ClutterActor
ones, but work very differently: They use a different enum and
determine how the bin lays out its child, instead of how the bin
is laid out by its parent.

Address this by deprecating the StBin properties and using the same
generic ClutterActor properties as everyone else.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/803
2019-11-04 21:23:32 +01:00
f0a5170473 st: Deprecate StBoxLayout child properties
We are no longer using them, and so shouldn't anyone else :-)

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/780
2019-11-01 19:42:02 +00:00
104071acbd js: Replace child properties
Every since commit aa394754, StBoxLayout has supported ClutterActor's
expand/align properties in addition to the container-specific child
properties. Given that that's the only container left with a special
child meta, it's time to fully embrace the generic properties (and
eventually remove the child meta).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/780
2019-11-01 19:42:01 +00:00
4338ca5fd9 padOsd: Add missing 'closed' signal
This wasn't added on the GObject-ification in commit c4c5c4fd5c. This
introduced warnings and misbehaviors when closing the dialog. (Eg.
pressing the "show OSD" pad action would show stack different dialogs
on top each other).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/800
2019-11-01 19:23:14 +00:00
e06421b04b layout: Drop no-clear-hint code
Mutter is doing the right thing by default, we no longer need this.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/798
2019-11-01 12:29:00 +01:00
5687035c9b appDisplay: Check instanceof AppIcon using constructor inside the class
It seems like some recent change (maybe the move to a ClutterActor
subclass for AppIcon) broke the check whether the drag source is an
instance of AppIcon. While the drag source indeed is an AppIcon and
everything else works correctly, the check still returns false, which
breaks the creation of new folders using DnD.

Theoretically it makes sense that this doesn't work, because we're
assigning AppIcon using `var AppIcon =` and that will only get set after
`GObject.register_class()` finished, so accessing `AppIcon` inside that
function seems risky and is probably wrong.

Fix this by comparing to `this.constructor` instead of `AppIcon`, which
works fine and we know for sure exists at this point.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/794
2019-10-31 19:35:37 +00:00
8cb819926a po: Sort LINGUAS
In Endless, we add support for a number of additional languages. Keeping
this file sorted makes it easier to rebase the patch which does this. No
functional change.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/797
2019-10-31 09:46:15 +00:00
284ace5b5f cleanup: Use (un)block_signal_handler() convenience wrapper
We now depend on a gjs version that is guaranteed to provide those
more idiomatic wrappers, so use them instead of the clunkier static
methods.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/795
2019-10-30 19:40:15 +00:00
7bc39ba750 ci: Run tests through dbus-run-session
Something changed recently in the test initialization code, causing
the test-theme invocation to fail. Make sure there is a D-Bus session
by running the tests through dbus-run-session to get them going again.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/796
2019-10-30 19:43:53 +01:00
aa9031d8e7 st/scroll-view: Remove scrollbars references on dispose
As we're destroying the scrollbars on destruction, we should remove any
reference of it, not to cause multiple-calls to disposal to unreference them
again.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/190
2019-10-30 01:08:27 +01:00
4dc44304df mpris: Hide notification when !CanPlay, instead of closing player
I have observed a client in the wild (Chromium again) set CanPlay to
false momentarily while it is loading the next song. Previously, the
code would close the player proxy in that case, meaning that after
playing one track, the MPRIS message would disappear and never come
back.

However, I think this use of CanPlay, while apparently not usual, is not
incorrect according to the spec. We should hide the message instead of
closing the player proxy.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1362
2019-10-29 19:25:16 +00:00
975280fc50 mpris: Validate received data against the expected types from the spec
In the wild we have buggy clients (notably Chromium 77 and earlier) that
send metadata with the wrong types. Previously, this would throw an
exception and prevent the MPRIS information from showing up in the
message list.

This changes the code to check if any incoming metadata is of the type
it is expected to be, and logs a warning if not, then continues on with
a default value.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1362
2019-10-29 19:25:16 +00:00
39e6fc9e9d js: Use Gjs GTypeName computation for all classes
As per previous commit we can remove the explicit GTypeName definitions
and use gjs auto computation for all the GObject registered classes.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/790
2019-10-29 18:38:35 +00:00
91707f4f82 environment: Use gjs smart GObject GTypeName computation
Make gjs to compute the GType name for registered GObject-derived
classes using the file basename and the first directory name, so that we
can avoid name clashing, and ensure that no extension will break the
shell by registering a name that is already used (by the shell or by any
other extension).

This requires gjs commit 02568304 [1] that will be part of release 3.35.2,
so bump the required version as gjs does post-release version bumps.

[1] https://gitlab.gnome.org/GNOME/gjs/merge_requests/337

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/790
2019-10-29 18:38:35 +00:00
b7bf9e09e9 ci: Switch to mutter's v3 docker image
As we start depending on newer stuff, switch to an image that is based
on a more recent base image to minimize the pain a bit.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/792
2019-10-29 17:38:06 +00:00
db9249a1b7 padOsd: Work around xgettext confusion
Similar to the previous work-around, xgettext gets thrown off by
embedded quotes in template strings, in particular where a template
"breaks up" a pair of quotes.

Throw in some more comments to make xgettext happy, but whoever makes
the gettext toolchain not depend on fragile regular expressions will
be drowned in beverages of their choice ...

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/792
2019-10-29 17:38:06 +00:00
10b2083d3e extensionPrefs: Trick xgettext into accepting odd number of backticks
Xgettext learned about template strings now, which is good. However
it's still buggy, so instead of the "classic" xgettext issue with
backticks, we now have exciting new issues to find work-arounds for.

One issue is that it doesn't detect backticks inside string constants
as regular characters, so having an odd number of backticks throws off
its regex.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/792
2019-10-29 17:38:06 +00:00
fa1b7a9ef5 overview: Set searchEntry offscreen-redirected always
This corrects weird-looking blending visible as it fades out when the
overview closes. Previously the entry's dark background would drown out
the text as it fades out, but now they maintain a consistent contrast ratio
during the fade.

There's no noticeable change in performance, but in theory it should be
faster as text entries don't change at full frame rate. So stage redraws
will usually have a cached searchEntry drawn and require less effort.
Though the main purpose here is to correct the appearance.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/778
2019-10-29 15:41:06 +00:00
b6e57a5ae8 closeDialog: Fix dialog size when using geometry scaling
The close dialog is added as a child to MetaWindowActor, and, in Wayland
sessions, since commit [1] MetaWindowActor applies a transformation
matrix which scales all it's children using the geometry scale factor.
Now because the dialog actor is not a window (i.e. a MetaSurfaceActor),
but a subclass of StWidget, the scale factor is also applied to the
properties of the dialog by StThemeNode, so we end up applying the
geometry scale twice to the close dialog.

Fix this by applying the inverted scale to the dialog, which leaves the
scaling only to MetaWindowActor. This means we also can't apply a pivot
point other than 0 to the dialog actor, so apply the 0.5-pivot point to
the `_dialog` child of the Dialog class (the actual visible dialog box)
and also perform scaling animations on this child.

[1] https://gitlab.gnome.org/GNOME/mutter/commit/fb9e8768

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/783
2019-10-28 17:30:50 +00:00
b6d47c18c3 windowManager: Always reset window actors when animations are cancelled
Remove all transformations from window actors after a window animation
was either cancelled or finished. Right now we only do that if the
transition finished successfully, which seems kind of pointless (it can
probably be historically explained because the callbacks inside the
"kill-window-effects" signal handler are connected to those
`*WindowDone()` functions though and the `*WindowOverwritten()`
functions were only added later in [1]).

This fixes a recent regression where a window animation would get
cancelled and remain stuck by switching workspaces. The regression
probably happened due to different behaviour of the `onOverwritten`
callback of Tweener and the `onStopped` callback of Clutter transitions:
For the workspace-switching animation the window actors get reparented
to a temporary container, which makes Clutter transititons emit
"stopped" (`clutter_actor_remove_child_internal()` stops transitions on
its children), while Tweener would continue the animation.

[1] 6dd302e5ce

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/784
2019-10-28 14:28:49 +00:00
c35b4cede5 popupMenu: Don't chain up vfuncs if the parent doesn't implement them
Some vfuncs like `button_press_event`, `button_release_event` and
`touch_event` don't have handlers in the parent classes of
PopupBaseMenuItem. So don't call those handlers and return the default
Clutter.EVENT_PROPAGATE there.

This fixes a crash of the shell that happens when pressing a mouse
button inside the system popup menu and releasing it above a slider like
the volume slider again.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/787
2019-10-28 13:43:55 +00:00
6cfcfc72cc panel: Update window section items on title changes
We currently only update the windows section when either the focus app changes,
or when the app's windows change (that is, a window is opened or closed). This
allows the menu item labels to become stale if the window title changes after
one of those events (for example when switching tabs).

Fix this by updating menu items when the corresponding window title changes.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1830
2019-10-28 12:42:28 +01:00
0732e1426a appDisplay: Don't crash if app is missing categories
g_desktop_app_info_get_categories() may return null. In that case, the
previous code would fail to create a folder when dragging an app with
no categories onto another app. Instead, simply continue with the next
app info.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/782
2019-10-25 15:48:52 -07:00
055c007ac2 dnd: Skip drag target when its acceptDrop() throws an exception
In the case of bugs in a drag target's acceptDrop() function, it may
throw an exception. In the previous code, this would break out of the
loop entirely and never cancel the drag, so the mouse button release
event would be ignored and you would have to press Esc to get out of the
drag.

In this change, if acceptDrop() throws an exception, we log it and move
on to the next parent target instead.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/777
2019-10-22 18:08:10 -07:00
43cf466d09 js: Replace Clutter.Actor.get_allocation_geometry()
The function was deprecated and has now been dropped from mutter.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/776
2019-10-21 18:41:35 +02:00
6965781d59 st: Use clutter_actor_pick() in pick
Use the new function to perform picking and avoid
going through any painting-related code paths.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/773
2019-10-21 13:53:56 +00:00
80680803aa Update Persian translation 2019-10-19 15:16:39 +00:00
51601f3ead Update shotwell desktop file name references
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/774
2019-10-18 16:50:57 +02:00
b25a73c243 authPrompt: Wiggle on failure
Add a wiggle effect to the password entry on failure. The
parameters are set as per design review during GNOME Shell
Hackfest 2019.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/768
2019-10-18 11:25:45 +00:00
d0690c3952 util: Add wiggle helper
Add Util.wiggle(), which accepts the wiggle offset, duration,
and number of times, as parameters.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/768
2019-10-18 11:25:45 +00:00
f2466caef3 environment: Parse repeat-count and auto-reverse
Those are two useful ClutterTimeline properties and
will be needed for wiggling the search entry when
failing the password.

Add support for passing repeat-count and auto-reverse
to ClutterActor.ease and ClutterActor.ease_property.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/768
2019-10-18 11:25:45 +00:00
b1d22d2058 search: Drop SearchResultInterface again
It adds a significant cost to AppIcons which are used
 - quite a log (depending on installed apps)
 - in preformance-sensitive contexts (spring animation)

Just rely on duck typing and revert 91a5133116.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1799
2019-10-17 15:56:07 +00:00
6f7e5976e2 Update Friulian translation 2019-10-17 13:54:18 +00:00
29543f369f Update Croatian translation 2019-10-17 12:08:58 +00:00
a144a1c76d workspace: Use graphene instead of clutter
This was forgotten after the graphene type port landed.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/770
2019-10-17 11:31:59 +02:00
d91927674d workspace: Sort windows in overview grid using cached center
When accessing properties on ClutterActor for size and position there is
a notable access time overhead. This overhead adds considerable user lag
when opening the overview if many windows are open.

This is primarily due to these properties being accessed while sorting
WindowClone instances by their window's center for placement in the
overview. By pre-computing this center value only once when
initializing WindowClone, the induced lag can be significantly reduced.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/763
2019-10-17 07:27:59 +00:00
d12cd12e1b ci: Make run-eslint more convenient for local use
The script can be helpful outside of CI, in particular for gradually
transitioning to the new style.

Reverting commit f00201fa6c it is already possible to do something
like

 $ CI_MERGE_REQUEST_PROJECT_URL=https://gitlab.gnome.org/GNOME/gnome-shell \
   CI_MERGE_REQUEST_TARGET_BRANCH_NAME=master CI_COMMIT_SHA=HEAD \
   .gitlab-ci/run-eslint.sh

but that is hardly convenient.

Instead, allow passing the required parameters on the command line:

 $ .gitlab-ci/run-eslint.sh origin master

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/730
2019-10-16 15:37:12 +00:00
caa50dc1a3 ci: Ensure eslint output exists
We are a long time away from an error-free eslint run,
but when we get there eventually, let's not trip over
non-existent files ...

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/730
2019-10-16 15:37:12 +00:00
55b57421dc cleanup: Replace signal connections with virtual functions
Inheriting from actors allows to use virtual functions instead of signal
connections for multiple cases, so just use them when possible.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
320df13b65 st/button: Add the clicked button to virtual function signature
clicked signal includes a clicked mouse button parameter, but the vfunc
signature doesn't include it, so it won't be passed to the functions when
the signal is emitted.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
e4920b2f80 pageIndicators: Use Clutter.Orientation as orientation parameter
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
c9fbae3408 docs: Update actor and delegate_ paragraph in HACKING
Deprecate the usage of the `actor` property, while keep the `_delegate` part
as it is needed for DnD for now.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
a3c6217875 overview: Make public properties read-only
Overview's animationInProgress, visible and visibleTarget properties are not
meant to be modified from others, but be read only.

So make this clearer using properties getters and private values.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
db7726c5bf avatar: Use Property bindings to sync reactivity
Instead of manually updating properties on change, use native properties
bindings to keep the them synchronized.

Disable hover-tracking and focus-ability when the avatar is not sensitive.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
0b91dee5a9 windowManager: Inherit WindowDimmer from Clutter.BrightnessContrastEffect
As result add the effect to the actor on the caller function.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
3838220961 calendarMessageList: Remove sections map and use clutter children
Now that the calendar message list and the message sections are actors, there's
no need to keep track of the sections in a different Map as we can just use the
native clutter functions to manage the children and access to their properties.

Also cleanup the signal connection/disconnection.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
9bb12f6f87 messageList: Use St.Bin as message container and use clutter to manage the list
When messages are added to the message list, we create a container for those,
however since now the messages are actor themselves we can just create a list
item actor that holds the message actor and refer to the message parent in order
to get their container.

This allows to remove the obj container map we used, using the native clutter
parent-child hierarchy and handle signal connections cleanly.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
4dea1f801a lookingGlass: Use resultsArea to keep track of results
Now that results are actors we can just use their container to keep
track of them

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
91a5133116 search: Define SearchResultInterface and implement valid results with it
Since all the search result classes are now GObject classes, we can enforce
the methods we want to have in there (just activate() for now) using an
interface, to make sure they are implementing what we require and to easily
group all the classes that can be used as search results, even though they
are not extending SearchResult.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
c4c5c4fd5c cleanup: Use inheritance for Actor classes instead of composition
Remove the `this.actor = ...` and `this.actor._delegate = this` patterns in most
of classes, by inheriting all the actor container classes.

Uses interfaces when needed for making sure that multiple classes will implement
some required methods or to avoid redefining the same code multiple times.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
f67b409fc1 screenshot: Pass a Graphene.Point as PickPixel 'finished' signal
This will allow to pass the data as native object when porting this to Clutter
actor.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
22fe4e92c7 screenshot: Return a Meta.Rectangle as geometry
Since the geometry is used as a signal parameter, this can't be used when using
a GObject based class, so use the Meta boxed type instead of a JS object.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
91eb84fa4e overview: Add OverviewActor and use as main actor of the Overlay
Use the Overview class as controller, while create the actual overlay actor
using a GObject-derived class.

Replace actual properties with getter-only properties.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
4e1492c926 messageTray: Dispose Source on destruction
Dispose the Source Object when dispose() is called, avoiding that it could be
called twice on a destroyed Source.

So, notify count changes before destroying the object, and don't emit this
twice on destroyNonResidentNotifications (as if a notification is destroyed
the property notify will happen in the notification destroy callback anyways).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:13 +00:00
ed97f61750 messageTray: Dispose Notification on destroy
When the notification is destroyed we should also dispose the underneath GLib
object, and ensure that we don't dispose this twice.

In order to avoid this, don't destroy transient notifications that have been
already been removed and only destroy the resident notifications on activation
if they have not been destroyed earlier.
Thus connect after to the 'activated' signal and once the default handler has
been called destroy the notification if not requested earlier.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:12 +00:00
b5676a2a5c messageTray: Inherit Notification, Source and NotificationPolicy from GObject
Register notifications, sources and policies as GObject gtypes so that they can
be passed in signals and use native properties and signals.

Reimplement all the extending classes.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:12 +00:00
7059dcced3 keyboard: Add KeyboardManager to manage the lifetime of the keyboard actor
The Keyboard class used to be both a view and controller class, however in order
to make the keyboard a native Clutter.Actor, we need to separate the widget from
the controller class, so that we can manage the actor lifetime from the JS side.

Thus, initialize the keyboard actor on the Keyboard constructor and create a
KeyboardManager class to manage its state and lifetime.

Add proxy methods for the public functions that were used by other shell
components

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:12 +00:00
c7e0c7eb79 background: Rename Background 'changed' signal to 'bg-changed'
Meta.Background has already a 'changed' signal and not to confuse the source
signal with the wrapper one, rename the wrapper class signal into 'bg-changed'.

This will be relevant when we'll inherit from Meta.Background, as signal
emissions from the base class could interfere with the wanted derived class
behavior and with the the grouping of successive changes into a single ::change
emission.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:12 +00:00
ff775213a5 calendar: Use GDateTime for selected-date-changed signal
Since GObject derived classes can't use JS objects as signal parameters, let's
go native and use GLib.DateTime instead.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:12 +00:00
7f9c709c85 appDisplay: Use an St.Widget as base actor for FolderView
This is needed to make possible to convert BaseAppView into a St.Widget so that
all views can inherit from it.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:12 +00:00
74d7d3e259 animation: Don't sync animation sizes on change
When the actor size changes, we might incur into an allocation cycle.

This was introduced by commit b6ec02ce, but when the animation will be an actor
itself, there will be no need to update the children size on actor size update,
as this will be managed by the actor allocation cycle itself.

Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/issues/1384
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:12 +00:00
0353a5bf2c cleanup: Rename signals/methods that will conflict with Clutter.Actor
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
2019-10-16 15:26:12 +00:00
ab6a629955 screenShield: Compute lock timeout fade duration using animation settings
When the screen is marked as idle, we normally start a fading animation and
a timeout to finally lock the screen. This timeout is configured using the
fade time if no longer delay is set in settings.

However if animations are disabled or slowed-down/up, the fade time is
different from the STANDARD_FADE_TIME and so we might end up showing the
lock shield without actually locking for STANDARD_FADE_TIME in the disabled
or slowed-up animations case, or locking too early in case of slowed-down
animations.

So, just adjust the timeout time using the same logic of animations so that
this value is matching all the times.

Related to https://gitlab.gnome.org/GNOME/gnome-shell/issues/1744

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/749
2019-10-16 15:20:58 +00:00
6cad251187 volume: Show indicator when microphone is active
Devices like cameras and microphones are privacy sensitive, as they can
be used to spy on the user. We cannot prevent non-sandboxed apps from
doing that, but as we already track when the microphone is recording,
we can at least show an indicator to make sure it doesn't happen behind
the user's back.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/729
2019-10-16 13:08:20 +00:00
d7c569c692 st: Remove color from ClutterActor pick virtual function
It's unused since commit mutter@14c706e51

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/759
2019-10-16 12:01:41 +00:00
0615370930 Replace Clutter.Point by Graphene.Point
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/421
2019-10-16 10:49:04 +00:00
7a92a9ba21 st: Replace ClutterSize by graphene_size_t
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/421
2019-10-16 10:49:04 +00:00
0199857c5b Replace ClutterVertex by graphene_point3d_t
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/421
2019-10-16 10:49:04 +00:00
59e3a1a816 doap: Clean up list of maintainers
Move historic entries from "maintainers" to "authors" to reflect
current reality :-(
2019-10-16 12:47:05 +02:00
6533690fff search: Activate SearchResult from the result itself
Search result views can include also objects that are not inheriting from
SearchResults (such as the AppIcon) that has not any 'activate' signal, to
connect to. Since we want to enforce a more formal interface, we want to
have just a simpler requirement as an activate() method.

So, instead using the 'activate' signal in SearchResult to activate a result
via SearchResultsBase just implement activate() in the result.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/765
2019-10-16 12:27:49 +02:00
d0d1845bb6 search: Rename SearchResults to SearchResultsView
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/765
2019-10-16 11:52:55 +02:00
20f4fc7c87 shell-screenshots: Do not pass a clip for window screenshots
Design team wants us not to clip away the shadows, lets do that.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/762
2019-10-14 17:56:01 +02:00
b4128967a1 st/scroll-view: Remove container foreach vfunc
foreach_with_internals vfunc is deprecated for long time and not used
anymore, so remove it

Related to: https://gitlab.gnome.org/GNOME/mutter/merge_requests/816

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/747
2019-10-14 14:57:49 +00:00
38ad1d7c13 environment: Only disable unredirection of ongoing transitions
When a transition is set up with a delay, it may be removed before it
actually started. We won't get a ::stopped signal in that case, with
the result that we currently end up with a mismatched unredirection
disabling.

Address this by only disable unredirection once the transition has
actually started.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1788
2019-10-14 10:47:36 +02:00
f78136182f Updated Spanish translation 2019-10-14 09:15:23 +02:00
11d46cf5b3 Bump version to 3.35.1
Update NEWS.
2019-10-12 22:38:36 +02:00
7326e7a9fa main: Show a warning when gdm is missing
If we are not running under gdm, some functionaliy (such as
the lock screen) does not work, and we should inform the
user about this.

https://bugzilla.gnome.org/show_bug.cgi?id=701212
2019-10-12 20:36:38 +00:00
a65164e540 main: Show a warning when running as root
gnome-session used to show a dialog in this case, but a
notification is more natural nowadays. Doing it in gnome-shell
avoids complicated synchronization between gnome-session and
gnome-shell.

https://bugzilla.gnome.org/show_bug.cgi?id=701212
2019-10-12 20:36:38 +00:00
279024afc2 js: Replace Tweener time leftovers with ease duration
In some places we were not properly animating the actors as still using the
non-existent 'time' property instead of 'duration'

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/758
2019-10-10 02:14:28 +02:00
ef8000d2e6 animation: Finish porting to clutter transitions
Animation when stopping the spinner was still using the old
parameters that not working, although if silently failing.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/758
2019-10-10 02:08:59 +02:00
986600ab31 Bump version to 3.34.1
Update NEWS.
2019-10-09 02:50:29 +02:00
3d39b32a0b Revert "windowManager: Use new reorder_workspace() API"
It's too easy to break, so revert to the old code until we
figure out a fix.

This reverts commit ff9bb5399b.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1497
2019-10-08 21:11:19 +02:00
6205d5eb27 windowManager: 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/issues/1497
2019-10-08 21:11:19 +02:00
a722b4c51d ibusCandidatePopup: Use an internal actor to track IM focus position
We share this actor with other shell menus, which arguably track a different
"cursor" as we care of the caret/anchor text positions, and menus care about
pointer click coordinates.

Use a standalone actor for this, so popups/IM are entirely decoupled.

Closes: https://gitlab.gnome.org/GNOME/gnome-shell/issues/1571
2019-10-08 17:12:43 +00:00
31fe517007 shell-global: Ignore modal operations if we have no compositor
Modal dialog actions might be triggered during display closing, and in such
cases we should just ignore the requests.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/746
2019-10-08 17:51:52 +02:00
31d915a38a shell-global: Disconnect from stage events on X11 display close
As per GNOME/mutter!385 [1], the compositor is finalized an its pointer
cleared on display close.

However, since the shell reacts to such events instead of controlling them,
when the shell is stopping or restarting and its display closing, the shell
stage destroys its children after the display closing is finished and during
this process the focus is unset, causing focus_actor_changed() to be called
and thus calls to meta_stage_is_focused() which deferences the now NULL
compositor, leading to a crash on shutdown.

Since after this point we should just ignore any stage event, disconnect
from them all.

[1] https://gitlab.gnome.org/GNOME/mutter/merge_requests/385

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/746
2019-10-08 17:49:30 +02:00
e00878ab75 shellDBus: Don't assume devices have a node
Clutter.InputDevice.get_device_node() may return null. Handle that
case when building the 'AcceleratorActivated' vardict parameter.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1641
2019-10-07 21:24:13 +00:00
3b5675b79a networkAgent: add support for SAE secrets
NetworkManager supports "WPA3 Personal" networks for some time now, they
use the SAE authentication. Add support for it alongside other
password-based mechanisms.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/751
2019-10-07 22:44:39 +02:00
ee97512bcc Updated Danish translation 2019-10-06 18:16:32 +02:00
085531b43d main, util: Notify systemd once we are fully initialised
If graphical applications want to start from systemd units, they need to
start after we're properly ready to display them. This is particularly
important under X where `_GTK_FRAME_EXTENTS` and other xprops are needed
to have the right theming.

We're doing this in an idle callback so that the dynamic starting of
`gnome-session-x11-service.target` (which launches `gsd-xsettings`) as
the result of a signal emission happens before us signalling we're ready
for later things to start.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/750
2019-10-04 15:40:05 +00:00
9e8b97d474 Update Japanese translation 2019-10-04 14:54:06 +00:00
a3a7953704 Update Japanese translation 2019-10-04 14:52:49 +00:00
92c0171aeb lightbox: Show the actor before easing it
As per clutter optimizations in should_skip_implicit_transition() any
transition will be ignored if applied to an actor with unmapped clones.

Since we initialize the lightbox as hidden, when we use it standalone (as it
happens for the long fade in screenShield) the transition will be ignored.
This causes the lockscreen fade-out after the idle delay not to work, but
instead to have an apparently locked system that is instead not locked at
all.

So, just ensure that the lightbox actor is visible before applying to it any
transition.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1683
2019-10-03 15:26:13 +02:00
6a6d66486d ibusManager: Cancel the preload engines timeout on clear
This is created when preloading but never cancelled if ibus disappears

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/743
2019-10-02 18:19:53 +02:00
1cc766d636 ibusManager, inputMethod: Cancel async ibus calls chain on disconnect
The shell tries to spawn the ibus daemon on startup if unavailable, however
as per commit 8adfc5b1 we also force restarting it once the X11 server is
available.
Unfortunately this could cause a race if we disconnect while we were already
connected to an ibus daemon, but still in the process of going through the
various nested calls.
In fact the ::disconnect callback didn't stop any further async ibus call
that, even if failing, would have eventually triggered the emission of a
'ready' signal and to the Keyboard's callback, leading under X11 to a full
grab owned by ibus daemon.

In order to avoid this and keep control of the calls order, use in both
IbusManager and InputMethod a cancellable that is setup before connecting to
the bus, and that is cancelled on disconnection.
Then handle the finish() calls properly, using try/catch to validate the
returned value, taking in account the potential error and just not
proceeding in case of cancellation.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1712
2019-10-02 18:19:53 +02:00
60cad01880 inputMethod: Do not change IBus.Capabilite by focus events
gnome-shell calls ibus_input_context_focus_in() in InputMethod.focus_in()
but the event is not actually forwarded to panels and engines in GNOME
Wayland because gnome-shell changes IBus.Capabilite by focus events and
disables IBus.Capabilite.FOCUS when ibus_input_context_focus_in() is called.

IBus.Capabilite is assumed a fixed value per input context in the
first place and it should not be changed by focus events.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/573
2019-10-02 08:40:27 +00:00
63c9a6efd0 Updated Danish translation 2019-10-02 05:35:36 +02:00
1d1b42756f Updated Czech translation 2019-10-02 03:57:00 +02:00
a95601afdb Updated Slovenian translation 2019-09-28 21:13:30 +02:00
2dbdf792db Updated Slovenian translation 2019-09-26 22:02:27 +02:00
e23ce37e62 Update Friulian translation 2019-09-26 14:20:38 +00:00
a05cb76e0d Update Slovak translation 2019-09-26 06:43:01 +00:00
60cab56f86 shell-util: Handle NULL from meta_window_get_image()
Until commit 506b75fc7f we got away with not handling a NULL return
value, as cairo_surface_destroy() deals with a NULL surface; the same
isn't true for get_width/get_height, so guard to code in question to
prevent a crash.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1678
2019-09-25 14:34:27 +00:00
70a5c3875c Update Dutch translation 2019-09-25 11:11:56 +00:00
0fdbde9101 main: Remove NOTIFY_SOCKET from environment
It is only used exactly once to notify systemd about the service
startup. So unset it as soon as possible as it can leak into
subprocesses we spawn otherwise.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/741
2019-09-23 11:09:39 +02:00
2156577333 slider: Unset signal IDs when ending the slider drag
Unset the signal IDs we connected to when starting the drag. Otherwise
we get error messages if a touch drag is ended after a mouse drag
happened because the signal IDs are still set but no signals are
connected.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/740
2019-09-22 22:20:41 +00:00
f3e09b2b2f Update Japanese translation 2019-09-22 11:29:14 +00:00
6180f59c13 Update Japanese translation 2019-09-22 11:27:23 +00:00
506b75fc7f shell-util: Do not scale the clip in get_content_for_window_actor()
This is now handled in Mutter. Also, respect the result size instead
of assuming it to be equal to the clip size, as the clip takes actor
coordinates while the result takes buffer coordinates.

This can be fixed in a future API iteration.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/728
2019-09-20 17:27:34 +00:00
a0d0a17d68 Update Galician translation 2019-09-20 17:19:17 +00:00
92e5713e29 screenShield: Stop using deprecated actor property
Both LoginDialog and UnlockDialog are now actor subclasses, so stop
using the deprecated actor delegate that will trigger a warning.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/736
2019-09-20 16:58:00 +00:00
856c32db91 unlockDialog: Use inheritance instead of composition
The screen shield creates the unlock dialog based on the session mode.

However since commit 0c0d76f7d6 turned LoginDialog into an actor
subclass (while UnlockDialog kept using the delegate pattern), it is
no longer possible to handle both objects the same way without warnings.

Allow this again by turning UnlockDialog into an actor subclass as well.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/736
2019-09-20 16:58:00 +00:00
7b45ffa511 loginDialog: Stop using deprecated actor property
Commit 0c0d76f7d6 made the class an actor subclass, so the actor
property is just a deprecated synonym of the object itself.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/736
2019-09-20 16:58:00 +00:00
b6754d7db7 environment: Try harder to find a transition
When easing, we need the transition of one of the involved properties
to connect our callbacks. Currently we simply get the transition for
the first property, however as Clutter optimizes the case where a
property doesn't actually change, that transition may be NULL even
though we still animate other properties.

So instead of only looking at the transition of the first property,
try to find a transition for any of the involved properties.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1572
2019-09-20 16:40:46 +00:00
2a9977a5b3 layout: Don't use the actors pick workaround on update regions
As per introduction of geometric picking in mutter [1], the workaround we
had consisting in picking all the actors to ensure that all the stage
projections were computed to get valid actors stage-transformed coordinates
and sizes is not working anymore as there is more caching involved.

However since this can be now handled properly in mutter [2], initializing a
valid projection matrix since the beginning, we can get rid of the
workaround.

[1] https://gitlab.gnome.org/GNOME/mutter/commit/14c706e51
[2] https://gitlab.gnome.org/GNOME/mutter/merge_requests/803

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/734
2019-09-20 16:05:32 +00:00
dab60d5580 renameFolderMenu: Use a custom menu item inheriting from PopupBaseMenuItem
The RenameFolderMenu uses the internal box as a menu item, while PopupMenu
expects to have PopupBaseMenuItem based children with a delegate set.

Instead of using a custom menu with a customized box acting as menu
item,just add a RenameFolderMenuItem that inherits from the parent,
adjusting the features as we need them. In fact, the rename folder menu item
doesn't need any label, padding or default styling so we can reuse
PopupMenuBaseItem after we use our styling properties and we set the
Ornament to HIDDEN.

To get the proper style in place, define rename-folder-popup and
rename-folder-popup-item to override the default popup-menu-item rule
padding instead of using margins.

Pass the menu item as menu's focusActor as this will key-focus it on pop-up,
by overriding the key_focus_in() vfunc we can then delegate the focus
handling to the entry's clutter-text.

Also override the map() vfunc in order to update the entry's content before
mapping the entry.

Finally, use the item's activate method in order to tell the parent menu
we're done with it and that the menu can be closed.

As consequence we can also remove the menu's popup() method, and just use
the default open().

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/720
2019-09-20 15:53:42 +00:00
8e3aac8ed7 renameFolderMenu: Move to non-legacy coding style
Use proper indentation on multi-line methods calls and use single quotes on
button label.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/720
2019-09-20 15:53:42 +00:00
147cb53140 renameFolderMenu: Set the entry as menu focus actor
When the rename folder menu is opened the text entry is expected to be
focused and selected for a quick editing.
While this is required it doesn't actually happens since PopupMenu by
default gives the key focus to the source actor, that is then free to pass
the key focus to the menu if there's an user interaction.

In this case however, we want the text entry to be focused once we prompt
the menu, so just use the PopupMenu's focusActor property to ensure it will
handle it for us.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1604
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/720
2019-09-20 15:53:42 +00:00
54f369404a popupMenu: Add focusActor property to define the default focus actor
The PopupMenuManager is supposed to grab and focus the menu actors, with
normal menus we always need to grab the actual menu but set the key focus to
the source actor so that it will be able to move the focus to the menu
child, if requested.

However there are menus such as the RenameFolderMenu that requires the
key-focus once prompted, so provide a focusActor property (defaulting to the
sourceActor) that can be set in order to define the actor to give the
keyboard focus to, when the menu is popped-up.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1604
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/720
2019-09-20 15:53:42 +00:00
af1aabff75 popupBaseMenuItem: Add support for Hidden Ornament
The menu item ornament is used to put dots or checks in menus or otherwise
to define a padding for a label.
However in some cases we want to create a menu item with no left (in ltr)
padding.

In order to do that, define a HIDDEN Ornament mode that completely hides the
ornament actor.

The naming here might be confusing as this should probably be called NONE,
while the default mode is the invisible one, but it's too late to change it
now.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/720
2019-09-20 15:53:42 +00:00
d6ba6dc554 renameFolderMenu: Don't save the source
The source actor is already tracked by the PopupMenu internally as
sourceActor, while nothing in RenameFolderMenu uses the source, so we can
drop this.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/720
2019-09-20 15:53:42 +00:00
42188b7698 folderIcon: Remove duplicated addMenu call
The RenameFolderMenu is added already to its menu manager, so no need to
repeat the operation.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/720
2019-09-20 15:53:42 +00:00
48adb2ef4b workspace: Fix an incorrect signal check
While commit 3094f863 was intended to cancel the ongoing idle hide
timeout before we start a new one, a mistake slipped in there while
rebasing: Obviously we should check if the signal id is NOT 0 here.

This didn't prevent timeouts being started while old ones are still
running and did override `this._idleHideOverlayId`, which caused the old
timeouts to run indefinitely after an overlay actor was destroyed
because we fail early (and don't return TRUE) in `_idleHideOverlay()`.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/735
2019-09-20 11:50:24 +02:00
f8e648b7e3 appDisplay: Animate DnD app icons at the position they were dropped
Indicate whether dropping an app icon was successful or not by using the
newly added `animateLaunchAtPos()` API of AppIcon which starts a zoom
out animation of the icon at the position the drop happened.

To get the position of the drag actor, we have to forward the arguments
passed to `acceptDrop()` and `handleDragOver()` to the internal drag
handlers of the WorkspaceThumbnails. We can use this position directly
without transforming it to stage coordinates because the actor is a
child of `Main.uiGroup` and the animation actor will also be a child of
this container.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/121
2019-09-18 17:14:16 +00:00
daa5452af2 appDisplay: Add API to animate launch at given position
Add a `animateLaunchAtPos()` method to the AppIcon class to animate the
launch of an app at a given position. This allows for a visual
indication of whether dropping an app icon using DnD was successful at
the position the drop happened in a later commit.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/121
2019-09-18 17:14:16 +00:00
259874d731 workspace: Return results of forwarded acceptDrop
Return the results of calls to acceptDrop that we forwarded to the
Workspace object.

This fixes a bug where app icons that were dragged and released above a
window clone would get animated back to their original position
(indicating that nothing happened) even though they opened correctly.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/121
2019-09-18 17:14:16 +00:00
23344701de overview: Only accept AppIcon drops if the app can open a new window
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/121
2019-09-18 17:14:16 +00:00
00e95de114 shell-app: Never allow opening new windows while an app is starting
We allow opening new windows as a fallback in case the app doesn't give
us explicit information about it, but we don't want to allow opening new
windows if we're unable to ask for this information (we can only use the
APIs to get this information while the app is RUNNING).

So always return FALSE in case the app is STARTING, always return TRUE
in case the app is STOPPED (starting an app always opens a new window)
and go through the usual checks in case the app is RUNNING (and
eventually fall back to FALSE).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/121
2019-09-18 17:14:16 +00:00
942758bb30 workspace: Use AppIcon.app to check action-support by the drag source
`AppIcon.shellWorkspaceLaunch()` can easily be replaced by checking for
`AppIcon.app` and calling `AppIcon.app.open_new_window()` directly.

For compatibility and to prevent breaking extensions implementing the
function, keep supporting the `shellWorkspaceLaunch` API in AppIcon
while logging a deprecation warning. Also keep supporting the API on
drag sources (without deprecating it) to allow extensions to define
custom actions on their drag sources.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/121
2019-09-18 17:14:16 +00:00
e0947b01bd introspect: Check whitelist also for GetRunningWindows()
Otherwise the xdg-desktop-portal-gtk screen cast widget won't work.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/732
2019-09-18 16:17:25 +00:00
cf00231aa8 st: Fix box-shadow drawing with prerendered_texture
The use of box-shadow on a StWidget that has a background-gradient was
not been rendered correctly, the shadow borders was calculated inside
the st_theme_node_prerender_shadow function and in the case that we've a
prerendered_texture the max_borders was not calculated and are 0.

This patch creates a new static function to compute shadow maximum
borders copying the code from st_theme_node_prerender_shadow, and call
this new method in the case that we've a prerendered_texture.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1186
2019-09-18 16:09:36 +00:00
5c3f4f5f8b workspace: Remove unused properties of WindowClone
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/683
2019-09-18 15:56:26 +00:00
5f10047b58 viewSelector: Only reset search entry if it has key focus
If the search entry does not have any text typed in and a button press
happens outside of the search entry, we set key focus to NULL to make
the search entry appear unfocused.

This is quite intrusive and can easily cause unwanted focus changes, so
change the captured-event handler to only call `reset()` if the search
entry actually is focused.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/683
2019-09-18 15:56:26 +00:00
3094f86334 workspace: Reset idle hide timeout for overlays on reentry
Instead of returning and waiting until the old timeout is finished,
start a new idle hide timeout for the overlay when the pointer enters a
window clone. This makes sure the timeout for hiding the overlay after
the pointer left the clone mostly stays the same (except when leaving
the clone via the title or the close button).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/683
2019-09-18 15:56:26 +00:00
8ffea9d5c5 workspace: Make title of overlay reactive
Also make the title of the overlay reactive so we can keep the overlay
visible if the mouse is hovering over the title.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/683
2019-09-18 15:56:26 +00:00
4f3c8b8d69 workspace: Continue overlay idle hide timeout when pointing at close button
While it makes sense to remove the timeout in every other case, restart it
if the pointer is hovering over the close button to make sure the overlay
will be hidden afterwards.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/683
2019-09-18 15:56:26 +00:00
edf6bd6909 Update Norwegian Bokmål translation 2019-09-18 13:34:34 +00:00
3e58af10ca Update French translation 2019-09-17 18:47:13 +00:00
9e55d262f9 Update French translation 2019-09-17 11:44:09 +00:00
252e694979 util: Handle trailing LTR/RTL markers in URLs
Some electron apps apparently spread those generously over their
notification text, so may sure to not include them accidentally
in URLs.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1614
2019-09-16 20:41:47 +00:00
efed695eca cleanup: Don't add linebreak before operators
When breaking overly long conditions into multiple lines, the operator
should end the previous line instead of starting the new one.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
b446667df6 cleanup: Disambiguate assignments in arrow functions
As arrow functions have an implicit return value, an assignment of
this.foo = bar could have been intended as a this.foo === bar
comparison. To catch those errors, we will disallow these kinds
of assignments unless they are marked explicitly by an extra pair
of parentheses.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
133a1e7bef cleanup: Remove trailing spaces
We generally avoid these (not least because Linus hates them enough
to make git complain loudly), but some sneaked in over time ...

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
5b3935fa43 cleanup: Fix up "special" comments
Our coding style asks for a space after the comment start, which
some of our ASCII-artsy comments violate. Adjust them to fit the
rule, or remove them altogether.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
471165ca9b cleanup: Use consistent brace style of blocks
Our coding style has always been to either put braces around all
blocks, or avoid them for all. Fix the couple of places that slipped
through.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
111f87a1b2 cleanup: Avoid pointless "renames" in destructuring
ES5 allows to rename variables in object destructuring, however this
only makes sense when we want to use a different name than the object
property.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
93525539c2 cleanup: Avoid unnecessary parentheses in arrow functions
Parentheses are only needed if there are zero or multiple arguments.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
a77377efe7 cleanup: Avoid useless return statements
Return statements are only useful if they return a value or break
the regular function flow (i.e. early returns). Remove all returns
that do neither.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
81ab2865f7 cleanup: Don't use Array/Object constructors
The corresponding literals are more concise and cleaner.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
e585f7d97b scripting: Disable an eslint warning
Calling await in a loop means the asynchronous operations are
run sequentially instead of in-parallel. Usually that's not
what's wanted, so eslint has a rule to warn about this.

However here we use async/await to handle control back to the
mainloop between steps, so running operations sequentially is
actually intended.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
1a32e3e74a volume: Clarify some code
We have more idiomatic ways to check whether any element fullfills
some condition than breaking out of a loop.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
8d6820c4df magnifier: Do not modify function argument
The intention of the code is clearly to operate on a copy, but that's
not how the Object constructor works. While it doesn't matter in
practice that we modify the passed-in object parameter, it's still
a good idea to fix the code.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
2546445884 magnifier: Simplify some code
We don't need nested if blocks to set or unset the crosshairs'
clip size.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/731
2019-09-15 16:02:45 +02:00
e44b7df078 lint: Drop extra ignored nodes in legacy config
They didn't just allow for the style patterns they were added to,
but allowed for some messed up indentation to slip through. Now
that we adapted the code to not use the old style, it's time to
drop the exceptions.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/725
2019-09-15 13:30:19 +00:00
3a9eaa39ea cleanup: Switch some method calls to new indentation style
The legacy indent rule currently ignores arrow functions in parameters
to allow callbacks to not align with the other arguments:

    this._someFunctionWithFairlyLongishName(arg1, arg2, arg3,
        () => {
            this._someOtherFunctionWithLongName(arg1);
        });

But as ignoring entire nodes means we can end up with arbitrary
indentation, we should drop the exception. While this would make
the above "illegal" under the legacy config, it conforms with the
non-legacy style, so everything should be fine ...

... except that eslint starts to complain about some function args
that should be fine under the legacy config. Maybe it's thrown off
by the function-arg-in-arrow-function-in-function-arg structure, but
rather than figuring it out, let's just move those to the new style.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/725
2019-09-15 13:30:19 +00:00
af87bd8c87 cleanup: Use consistent style for ternary operator
We are currently inconsistent whether to put the operators in front
of the corresponding line or at the end of the preceding one. The
most dominant style for now is to put condition and first branch on
the same line, and then align the second branch:

  let foo = condition ? fooValue
                      : notFooValue;

Unfortunately that's a style that eslint doesn't support, so to account
for it, our legacy configuration currently plainly ignores all indentation
in conditionals.

In order to drop that exception and not let messed up indentation slip
through, change all ternary operators to the non-legacy style.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/725
2019-09-15 13:30:19 +00:00
4bfb4a0e3d cleanup: Fix wrong indentation
Some more places where the indentation doesn't comply with either
the old or new style. They slipped through because the legacy eslint
configuration accounts for some patterns by plainly ignoring certain
nodes. We'll address that later, first fix up the indentation errors.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/725
2019-09-15 13:30:19 +00:00
d1a6601e60 theme: Equalize dimensions of ws switcher popup boxes
The indicator for the active workspace is currently two pixels
smaller because it doesn't account for the border used for
inactive workspaces.

Fix that to make sure all indicators have the same size.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1243
2019-09-15 01:23:56 +02:00
817aec5466 overview: Fix fading out desktop icons
A typo in commit 0846238f69 broke the animation.

Signed-off-by: Harshula Jayasuriya <harshula@hj.id.au>

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1616
2019-09-14 14:05:12 +02:00
314a89a837 Update Serbian translation 2019-09-13 19:43:24 +00:00
57ed68541a environment: Disable fullscreen unredirect during all transitions
When there is a transition, it's likely that we are animating some part
of the desktop, and in such situations we don't want to unredirect
fullscreen windows.

This fixes unwanted unredirection when e.g. hiding a modal dialog by
re-enabling the unredirection after the animation has finished, instead
of when it starts.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/721
2019-09-13 16:10:17 +00:00
413c677fcf iconGrid: Only animate visible icons
Mutter recently added an optimization to only allocate
visible children [1]. That broke ClutterClones, but it
was subsequently fixed [2].

However, that exposed a third problem, this time with
FrequentView: visible but transparent icons, that are
not allocated by the icon grid, were cloned and animated
during the spring animation.

Only animate visible icons with opacity greater than 0.

[1] https://gitlab.gnome.org/GNOME/mutter/commit/0eab73dc
[2] https://gitlab.gnome.org/GNOME/mutter/commit/08a3cbfc

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/696
2019-09-13 15:33:45 +00:00
3d86e6e791 Update Italian translation 2019-09-13 12:55:53 +00:00
3fbd61cbf0 Updated Spanish translation 2019-09-13 08:27:06 +02:00
43b4f2c7d5 lookingGlass: Only update window list when visible
Updating the window list in the Looking Glass is a costly
operation: it destroys a whole lot of actors, and recreates
them. This triggers CSS changes, repaints, and allocations.

It is specially bad when paired with Wayland's big number
of window creations and deletions when showing Builder's
and Epiphany's popup window.

Only update the window list in the Looking Glass when it is
visible.

Related: https://gitlab.gnome.org/GNOME/mutter/issues/556

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/719
2019-09-13 00:49:38 +00:00
7eb4088f45 Update Swedish translation 2019-09-12 22:16:22 +00:00
f00201fa6c ci: Disable MR handling for now
We aren't quite ready to enforce non-legacy style for now, mostly due
to the xgettext bug that prevents us from using template strings in
some places, and vast amounts of legacy indentation.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/716
2019-09-12 23:18:27 +02:00
1aca2ba6bb ci: Disallow legacy style in merge requests
In order to transition from the current legacy style to the modern
gjs one, use the MR line-filtering we used to do on common errors
to ensure new code complies with the new style.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/716
2019-09-12 23:18:27 +02:00
e9131465dd ci: Fail on any common lint errors
Now that we fixed all pre-existing errors that are common between
regular and legacy configuration, we can stop filtering the result
by the lines modified by the merge request.

This will allow us to catch errors in merge requests that slipped
through until now, for example when leaving a newly-unused import
behind.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/716
2019-09-12 23:18:27 +02:00
0ee7f02f8e cleanup: Don't assume hasOwnProperty() method on objects
Since ES5, it is possible to create objects with no prototype at all:

    let foo = Object.create(null);

Those object won't have any builtin properties like hasOwnProperty(),
which is why eslint added a corresponding rule to its default rule set.

While this isn't an issue that affects our code, there's no harm in fol-
lowing the recommendation and call the method through Object.prototype.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/716
2019-09-12 23:18:27 +02:00
451f4e3636 cleanup: "Only" use two indentation styles for object literals
We currently use no less than three different ways of indenting
object literals:

    let obj1 = {
        foo: 42,
        bar: 23,
    };

    let obj2 = { foo: 42,
                 bar: 23 };

    let obj3 = { foo: 42,
                 bar: 23
               };

The first is the one we want to use everywhere eventually, while the
second is the most commonly used "legacy" style.

It is the third one that is most problematic, as it throws off eslint
fairly badly: It violates both the rule to have consistent line breaks
in braces as well as the indentation style of both regular and legacy
configurations.

Fortunately the third style was mostly used for tween parameters, so
is quite rare after the Tweener purge. Get rid of the remaining ones
to cut down on pre-existing eslint errors.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/716
2019-09-12 23:18:24 +02:00
2fc4987c73 cleanup: Stop using Mainloop module
It is deprecated in favor of the regular GLib functions.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/718
2019-09-12 19:09:24 +02:00
4525ad346d windowMenu: Animate menu
Most menus have an animation, so add one to the window menu for
consistency and better looks (like !712).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/717
2019-09-12 16:01:46 +00:00
e4b8a4b432 Update Persian translation 2019-09-12 12:27:27 +00:00
62e594af6d Update Brazilian Portuguese translation 2019-09-12 11:35:40 +00:00
ce92270626 extensionSystem: Add missing return value
_callExtensionInit() should return whether the extension was initialized
successfully or not, but the early return added in commit 2a9e065cfb
doesn't.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/715
2019-09-12 12:45:05 +02:00
bdcf3037ca extensionSystem: Always disable multiple extensions in reverse order
Since disabling an extension will lead to disabling and re-enabling all
following extensions in the list, always disable multiple extensions by
looping through the list in reverse order.

This lowers the execution time of the event handlers quite a bit if many
extensions are installed.

Thanks to Philippe Troin for identifying the problem and proposing the
initial patch to change the extension order when reloading.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/177

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96
2019-09-12 10:24:49 +00:00
9698ff491a extensionSystem: Only add to extensionOrder array if enabling worked
Only push uuids of newly enabled extensions to the `_extensionOrder`
array if enabling them was successful.

Otherwise, since `_callExtensionDisable()` doesn't remove uuids that
weren't successfully enabled from the array, those extensions get added
to the array multiple times when they're disabled and enabled.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96
2019-09-12 10:24:49 +00:00
2a9e065cfb extensionSystem: Always enforce disallowing extensions using sessionMode
It's currently possible to circumvent the `sessionMode.allowExtensions`
property: For already enabled extensions one can call reloadExtension
via DBus, for new extensions it's possible by adding the extension to
the enabled-extensions gsettings key and setting the
disable-extension-version-validation key (which triggers a reload of
`this._enabledExtensions`) and then calling reloadExtension via DBus.

So to enforce `allowExtensions` while still allowing to update
extensions and keeping the extensionSystem synced with various gsettings
keys, replace the checks for `this._enabled` with simple checks for
`Main.sessionMode.allowExtensions` inside `_callExtensionInit()` and
`_callExtensionEnable()`.

The remaining checks for `this._enabled` are only small optimizations to
prevent running code on irrelevant sessionMode updates.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96
2019-09-12 10:24:49 +00:00
4c93ef39fa extensionSystem: Handle added or removed sessionMode extensions
Right now we're only handling added sessionMode extensions correctly on
sessionMode updates, also handle the other case and disable removed
sessionMode extensions on sessionMode updates.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96
2019-09-12 10:24:49 +00:00
22107c183b extensionSystem: Rename initted to initialized
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96
2019-09-12 10:24:49 +00:00
c06eb5d0a7 extensionSystem: Log an extension error if loading the stylesheet failed
Instead of only logging a message that loading the extension stylesheet
failed and silently returning we should use `logExtensionError` for that
instead. This also sets the extension state to ERROR and makes sure we
don't try to enable it again.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96
2019-09-12 10:24:49 +00:00
e76877c4b8 extensionSystem: Check if extension exists before accessing property
If the extension doesn't exist in the `this._extensions` Map, we'd try
to access `extension.dir` on undefined/null. So set the `dir` variable
after checking if `extension` is defined.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96
2019-09-12 10:24:49 +00:00
2a32fb2e72 extensionSystem: Fix a wrong error message
The extension object is added to the `this._extensions` Map inside
`createExtensionObject`, not inside `loadExtension`.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96
2019-09-12 10:24:49 +00:00
de86920e0e extensionSystem: Remove unncessary return statements
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96
2019-09-12 10:24:49 +00:00
8754736fda extensionUtils: Check version variable for undefined first
Avoid a warning message when trying to access requiredArray[2] by
checking if its undefined first.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96
2019-09-12 10:24:49 +00:00
d2ead59d74 data: Lower TimeoutStartSec in systemd units
If there's a stubborn process in our cgroup, we shouldn't hang around
waiting for the default (30 seconds) before the session closes. We've
logged out, SIGTERMed and the thing is refusing to go away, let's not
make people hang around for ages.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/699
2019-09-12 08:16:10 +00:00
2f4fcc59a1 Update Turkish translation 2019-09-12 04:26:07 +00:00
ba6dbb228d workspace: Minor clarification
Code and comment were based on the old get_input_rect() and get_outer_rect()
method names that were changed to the more appropriate get_buffer_rect() and
get_frame_rect() a long time ago.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/713
2019-09-11 23:32:16 +00:00
60e386048b backgroundMenu: Animate menu opening
The animation was removed in commit 6a00a504d4 for consistency with
other menus. However commit a9b12d5d73 then *added* animations to
those just four minutes later.

So add back the original animations for consistency, both with menu
closing and with other menus.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1595
2019-09-11 23:25:21 +00:00
c2904fa14d Update German translation 2019-09-11 21:14:44 +00:00
dfdb139d9c workspaceThumbnails: Replace loops with Array.find()
This is much more idiomatic and concise than iterating over
thumbnails until we find the one we are looking for.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
ce63d21dcc overview: Minor cleanup
Safe one indentation level by combining conditions.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
1da9937453 workspace: Use operator shorthand
Shorthands like a += b are well-established, so prefer them over the
less concise a = a + b.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
9f11fbad16 jsParse: Disambiguate regex
Make it clear that /= is part of a regex and not an operator shorthand.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
f54e7804c5 workspace: Don't initialize variables to undefined
The declaration itself already does this implicitly.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
7db5f8b28e calendar: Use template strings over concatenation
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
743ce23fbc util: Separate statements with linebreak
Our eslint rules will soon forbid more than one statement per line.
We already follow that rule except for one lone offender; fix that.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
a3267be192 network: Don't omit parens when constructing
While it is legal to omit parentheses when invoking a constructor
with no arguments, we generally avoid that in our coding style.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
4ad2523877 messageTray: Add missing linebreak
Methods should be separated by an empty lines, even when short.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
4bfee3a8ca ibusManager: Wrap line before dot
When chaining function calls, our coding style asks for the dot to
start the new line, not end the preceding one.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
fc964f975a kbdA11yDialog: Avoid unnecessary ternary operators
A condition is already boolean, there's no point in explicitly
turning it into true/false.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
52f85c9465 system: Properly separate statements
Whoops, that's some typo that sneaked into commit 9c3b3320f8 ...

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
691610f23c lookingGlass: Fix misleading typeof use
typeof is an operator, not a function. Putting unneeded parentheses
around the expression obfuscates that.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
b6a2b2b8a5 cleanup: Remove left-over imports
Unfortunately this slipped through our CI tests, as the script
filters errors by lines that are modified by the corresponding
merge request.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/710
2019-09-11 20:59:31 +00:00
1ad8a2fcf6 Update Romanian translation 2019-09-11 20:13:21 +00:00
7ce08845f1 Update Hungarian translation 2019-09-11 20:11:51 +00:00
d469250130 Updated Lithuanian translation 2019-09-11 22:37:29 +03:00
7fd5c47e06 Update Catalan translation 2019-09-11 21:21:08 +02:00
8704b1004e Update Indonesian translation 2019-09-11 18:31:47 +00:00
65a9fb8c01 Update Polish translation 2019-09-11 19:22:04 +02:00
25a7a8006a gnome-extensions: Translate help command
The quotes and COMMAND string aren't part of the syntax, so they
should be translated.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1538
2019-09-11 17:09:30 +00:00
6fe1d3248a Updated Czech translation 2019-09-11 18:01:13 +02:00
13f97532bf overviewControls: Remove slide transitions before setting value manually
Remove transitions of the `slide-x` property of the layout manager
before we set the property to a fixed value, otherwise the transitions
might still be running and change the value after we set it.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/707
2019-09-11 11:39:44 +00:00
1acee3d702 Update Swedish translation 2019-09-10 23:53:06 +00:00
1d17404471 selectArea: Ignore motion events once we got a result
When selecting an area for screenshot we monitor the events while we've valid
coordinates in order to redraw the rubber band.
However, we don't stop ignore the motion events after button release and so
while animating. This might cause an unwanted effect if moving the mouse away
during fade out that is way more visible slowing-down the animations.

To fix this ignore any motion event once we've set the results.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/711
2019-09-10 22:08:25 +02:00
48b860b69f ci: Turn on -Werror
Now that all compiler warnings are fixed, let's try to keep it that
way by making warnings fatal during CI.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/709
2019-09-10 17:51:02 +00:00
a030c54661 shell: Replace another GTimeVal
This slipped through in commit 9b7f228f8e.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/709
2019-09-10 17:51:02 +00:00
dcf7bae6c7 calendar-server: Temporarily ignore all deprecations in eds includes
Evolution draws in libsoup, which exposes deprecated types in its
API. While its headers have been fixed to guard the affected symbols,
those fixes aren't in our CI images yet.

Until we get a fixed version, just disable all deprecation warnings
during the include in order to not trip over "foreign" bugs.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/709
2019-09-10 17:51:02 +00:00
d0ace108e5 calendar-server: Disable deprecated e-d-s API
We aren't using any deprecated evolution-data-server API, so we can
turn it off; this avoids compiler warnings for glib deprecations
used by those functions, which makes it harder to spot warnings for
our own code base.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/709
2019-09-10 17:51:02 +00:00
32d5744014 build: Exclude private headers from GIR
They contain API that is explicitly not meant to be used externally
and - as it uses the "wrong" namespace due to the _ prefix - doesn't
end up in the introspection data anyway.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/709
2019-09-10 17:51:02 +00:00
d16094774b build: Remove st-private.h from "public" headers
It is currently listed both as public and private header, which is
clearly bonkers. As the name implies, the latter is correct ...

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/709
2019-09-10 17:51:02 +00:00
ac664ba321 Update Polish translation 2019-09-10 17:58:11 +02:00
0888a9bffd environment: Skip property animations while hidden
For implicit animations, Clutter will skip any transitions while
an actor is unmapped, and just set the property directly. Do the
same in our ease_property() convencience method.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/708
2019-09-10 14:41:26 +00:00
5e82d72424 Updated Spanish translation 2019-09-10 15:56:03 +02:00
2513835e89 Update Finnish translation 2019-09-10 11:11:21 +00:00
98b70ef00f Update Indonesian translation 2019-09-10 07:12:48 +00:00
ae11381b88 Update Romanian translation 2019-09-10 06:48:29 +00:00
e9596f2775 Update Italian translation 2019-09-10 06:47:05 +00:00
8adbc8010a Update Hungarian translation 2019-09-10 04:40:55 +00:00
76fb559964 Update Galician translation 2019-09-09 22:45:51 +00:00
1bc1b4d9d8 Update Brazilian Portuguese translation 2019-09-09 22:44:55 +00:00
dfc0ef56f6 appDisplay: Allow editing folder names
Add a new popover with a regular entry + button to rename
folders. The layout is similar to other GNOME applications.

The popup is implemented as a PopupMenu subclass, leaving
the grab management to PopupMenuManager.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/675
2019-09-09 22:15:49 +02:00
1e68e78d8e extensionPrefs: Block notify::active signal handler while updating state
We disable and enable extensions inside the `notify::active` signal
handler, but we shouldn't do that in case the change didn't come from
the user but because something else changed the state of the extension.

This causes an issue when the extensionPrefs window is open and the
session gets locked: The extensions are temporarily disabled by the
shell, extensionPrefs updates its switches on the state change and adds
those extensions to the `disabled-extensions` gsettings key inside the
signal handler. Now when the session is unlocked again, the extensions
won't be enabled again since they're forced-disabled.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/705
2019-09-09 19:50:31 +00:00
17fa5a2db4 extensionPrefs: Connect to ExtensionStateChanged after building UI
Since we manipulate parts of the UI (like the switch) in this signal
handler, let's only connect it after setting up the UI.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/705
2019-09-09 19:50:31 +00:00
004a5e1042 iconGrid: Queue a relayout after child opacity changes
We're using a vfunc override for `get_paint_volume` to exclude children
with an opacity of 0 from the paint volume and thus decrease the size of
the area we need to paint.

Now if the paint volume is requested during the spring animation (the
real icons are hidden using an opacity of 0 and clones are used for the
animation), `get_paint_volume` returns a paint volume with a height of
0. After that, the spring animation finishes and the icon-opacities are
set to 255 in `_resetAnimationActors`, and since we cache paint volumes
and there's no reason for Clutter to assume it got invalid, the icons
end up not being painted.

Fix this by queuing a relayout of the grid when the opacity of a child
is changed from or to 0, which manually invalidates the paint volume.

The reason why this is not an issue with the paginated icon grid
(all-apps view) is probably because StScrollView invalidates the paint
volume a lot more often than regular containers.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1502
2019-09-09 19:39:28 +00:00
4915a9e8e4 iconGrid: Delete private child property when removing child
Delete a private property we set when the child got added to make sure
the reference is deleted after the child got removed from the grid.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/704
2019-09-09 19:39:28 +00:00
8a7e44ccf0 extensionSystem: Use logError to record extension errors with stack trace
Extensions might emit JS errors explicitly or implicitly, however GNOME
Shell doesn't present any stack trace for those making them quite hard
to debug.

Make this easier by logging errors with logError() whichs includes the
stack dump.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:19 +00:00
a497afe695 system: Track buttonGroup visibility using a group of actors
Cleanup the visibility check on actions by using an array of actors to
track, so that we don't repeat the same variables multiple times.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:19 +00:00
15c252c11d popupMenu: Remove extra parameter on boolean ParamSpec initialization
PopupBaseMenuItem properties were initialized using wrong param spec
signature, fix this by removing the extra parameter.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
27da3ed1fe calendar: Use binding for clearButton visibility
When the calendar PlaceHolder is visible, the ClearButton shouldn't
be. Instead of setting the visibility explicitly, we can use a property
binding.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
8656102182 workspacesDisplay: Disconnect MetaLater and parent signals on destroy
When the WorkspacesDisplay actor is destroyed we should remove the
ongoing later and parent widget connections to avoid accessing an
invalid object on callback.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
24d3744cb9 workspace: Don't use clones' delegate to check children
The WindowClones are now themselves actors, so we can just check for
their type instead of checking for the delegate.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
031913b9df workspace: Use Workspace prefix for WindowClone class
We have both Workspace's WindowClone and workspaceThumbnail's WindowClone,
so better to be clear about them using a class prefix.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
e53443daf9 workspace: Remove Long-press later with the actor
Ensure that the long-press later is removed with the actor, otherwise
it will try to use invalidated data.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
06317f4f6a status/keyboard: Make Input source types var
These are used by keyboard so they need to be readable outside the
status.keyboard module.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
c69e195441 search: Remove updateSearch later on destruction
When the GridSearchBase actor is destroyed we should remove the
ongoing later that might try to access to invalid resources.

To do this, add an _onDestroy() callback function to SearchResultsBase
and override it in GridSearchBase.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
a53b48de4c locatePointer: Bind ripples creation to settings
Don't create ripples if locate pointer is not enabled, and bind
creation to the relative desktop interface settings key.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
eca98aee42 ripples: Add destroy() method and remove them when unneeded
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
ea5aaa8ab2 realmd: Set login format to null on start and update if invalid
We were checking an undefined property but that would lead to a a warning.
Instead we can consider the login format unset until is null, and in case
update it.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
72566eda43 messageTray: Remove unused source object parameter
This is has been added in commit b150869b5 but is never used

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
7a4f9a5ff3 keyboard: Treat menu items as actors
This is a leftover of GNOME/gnome-shell!499, since menu items are all
actors now, we can just avoid using the actor property when adding an
action item.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
ba23fd9989 lookingGlass: Throw a clearer error on referencing invalid result index
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
c101196f5b lightbox: Use common ease parameters and avoid similar codepaths
Easing calls on show/hide functions have some parameters in common whether the
radial effect is enabled or not.

So instead of doing repeated calls with similar parameters, initialize common
values in params objects.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
1687a5451e altTab: use AltTab prefix on AppIcon class
There's an AppIcon class on appDisplay, so it won't be easy to discern
the one we're going to add in altTab.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
ea4d5f89eb animation: Stop the animation before removing all the children
If the resource scale or the scale factor changes while the animation
is playing, we need to stop the animation and start it again once the
texture is loaded, as the idle might try to access an invalidated
animation child otherwise.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
2019-09-09 19:28:18 +00:00
9e388ebcfd Bump version to 3.34.0
Update NEWS.
2019-09-09 19:21:11 +00:00
8d9cae45f9 st-clipboard: Add trailing 0 to pasted text
We translate the raw stream content far too directly into a char*,
it notably forgets that the stream does not have nul-ended data,
this means we are potentially adding garbage after the pasted content.

Tentatively fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1570
2019-09-09 19:04:31 +00:00
406d0900a7 Update French translation 2019-09-08 15:44:41 +00:00
cf611d2be8 Update Hungarian translation 2019-09-07 18:25:17 +00:00
7875fc831b Update Slovak translation 2019-09-07 14:20:05 +00:00
44bca36385 Update Turkish translation 2019-09-07 07:16:54 +00:00
e2c3198627 Update Esperanto translation 2019-09-06 23:22:30 +00:00
8b549f3d5b Update Greek translation 2019-09-06 21:05:07 +00:00
a0e3c342a6 Update Japanese translation 2019-09-06 13:58:48 +00:00
a80331dbcf Update Japanese translation 2019-09-06 13:52:41 +00:00
0068dab001 Update Italian translation 2019-09-06 08:41:20 +00:00
7d42990462 Update Greek translation 2019-09-05 16:30:45 +00:00
e6dec7a9dd volume: Ignore slider changes we initiated ourselves
Commit 21e14bd46f fixed this for the
brightness slider, but we have the same problem for volume too. When the
volume is muted - for example in Settings or via a media key, we update
the slider to '0' to indicate this visually. But we also actually invoke
the slider's callback to *set* the volume to zero. That means that the
previous level is overwritten so it can't be restored when unmuting.

The fix is the same - when we update the slider internally ourselves,
don't call the signal handler.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1557
2019-09-05 11:30:33 +01:00
8adfc5b106 windowManager: Handle starting/stopping of X11 services
We now do 2 things along Xwayland startup/shutdown:
- Start or stop the gnome-session-x11-services target, that will
  pull all X11 related services that the session might depend on.
- As we start ibus-daemon manually, trigger a restart in order to
  toggle the XIM daemon on and off along with Xwayland presence.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/680
2019-09-05 07:42:46 +00:00
8be95b5785 ibusManager: Add call to restart the ibus daemon
We may need to restart it with different arguments, so make it
possible to do that. Also, avoid to just restart it on _clear(),
this is now most likely through our --replace call than it is
through ibus-daemon eg. dying, avoids some noise in logs as
there is already an ongoing ibus-daemon.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/680
2019-09-05 07:42:46 +00:00
9194de8460 shell: Add helpers to start/stop systemd units
These just send the respective DBus message to the systemd user service.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/680
2019-09-05 07:42:46 +00:00
6dccbc5a90 Bump version to 3.33.92
Update NEWS.
2019-09-04 20:54:34 +02:00
efba1e83c7 Update Brazilian Portuguese translation 2019-09-03 19:36:12 +00:00
d1442765a6 Update Croatian translation 2019-09-03 15:48:10 +00:00
72e5caf6e1 Update Indonesian translation 2019-09-03 12:19:11 +00:00
3768b6b701 keyboard: Fix EmojiSelection:delta range
The setter clamps the values to the range (-width, width), so we
must not limit the property to positive values.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/698
2019-09-03 03:20:17 +02:00
e5cde4700f notificationDaemon: Catch exceptions while loading notifications
An Endless OS system was found in the wild with a malformed
.local/share/gnome-shell/notifications which causes _loadNotifications()
to raise an exception. This exception was not previously handled and
bubbles all the way out to gnome_shell_plugin_start(), whereupon the
shell exit(1)s. The user could no longer log into their computer.

Handle exceptions from _loadNotifications(), log them, and attempt to
continue. Ensure that this._isLoading is set to 'false' even on error,
so that future calls to _saveNotifications() can overwrite the (corrupt)
state file.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1552
2019-09-03 01:00:50 +00:00
a207f67f73 global: Don't trust persistent/runtime state data
An Endless OS system was found in the wild with a malformed
.local/share/gnome-shell/notifications. When deserialized in Python,
after passing trusted=True to g_variant_new_from_bytes(), the first
element of the first struct in the array looks like this:

    In [41]: _38.get_child_value(0).get_child_value(0)
    Out[41]: GLib.Variant('s', '\Uffffffff\Uffffffff\Uffffffff\Uffffffff\Uffffffff')

When deserialised in GJS, we get:

    gjs> v.get_child_value(0).get_child_value(0)
    [object variant of type "s"]
    gjs> v.get_child_value(0).get_child_value(0).get_string()
    typein:43:1 malformed UTF-8 character sequence at offset 0
      @typein:43:1
      @<stdin>:1:34

While g_variant_new_from_bytes() doesn't have much to say about its
'trusted' parameter, g_variant_new_from_data() does:

> If data is trusted to be serialised data in normal form then trusted
> should be TRUE. This applies to serialised data created within this
> process or read from a trusted location on the disk (such as a file
> installed in /usr/lib alongside your application). You should set
> trusted to FALSE if data is read from the network, a file in the
> user's home directory, etc.

Persistent state is read from the user's home directory, so it should
not be trusted. With trusted=False, the string value above comes out as
"".

I don't have an explanation for how this file ended up being malformed.
I also don't have an explanation for when this started crashing: my
guess is that recent GJS became stricter about validating UTF-8 but I
could be wrong!

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1552
2019-09-03 01:00:50 +00:00
b73aace476 shell: Get cairo surface via window actor
Instead of going via the MetaShapedTexture to get the cairo surface, get
it from the window actor. The window actor can then handle this in a way
that makes it include potential subsurfaces.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/692
2019-09-02 16:53:55 +00:00
346d37ecbb Updated Czech translation 2019-09-02 08:53:00 +02:00
7bb29817f7 Update Czech translation 2019-09-01 21:21:10 +00:00
92b92a2e0a Updated Lithuanian translation 2019-09-01 22:32:34 +03:00
8e79f9f2dc Update Catalan translation 2019-09-01 16:37:08 +02:00
05b345cc92 endSessionDialog: Initialize Polkit permission asynchronously
The updatesPermission is currently initialized synchronously, which
blocks the Mainloop for quite some time and therefore slows down startup
of the shell, let's do it asynchronously instead.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/689
2019-09-01 12:45:49 +02:00
89f9925208 Update Korean translation 2019-09-01 09:00:28 +00:00
fcc1d7beff Update Punjabi translation 2019-08-31 13:45:30 +00:00
f226398c7c Update Japanese translation 2019-08-29 11:49:39 +00:00
890ac9ff38 Update Japanese translation 2019-08-29 11:36:39 +00:00
6a027cd566 Update Nepali translation 2019-08-29 08:32:16 +00:00
1dc971d760 Update Friulian translation 2019-08-28 09:19:25 +00:00
dcf0bf0bb1 Update German translation 2019-08-27 21:24:29 +00:00
cf156b469c Notify service startup to systemd
Using the bus name to notify service startup to systemd has some
disadvantages. The main one being that systemd will consider a
gnome-shell restart (Alt+F2 r) a service failure and restart the shell,
cleaning up all its children (i.e. user launched applications). In the
future the shell should launch applications in their own transient unit
so that a service restart does not affect applications.

Another potential issue is that we must never load
gnome-shell-wayland.service and gnome-shell-x11.service at the same
time, as systemd does not like two services providing the same bus
name.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1496

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/690
2019-08-27 17:42:32 +00:00
da6c154ceb ci: Set XDG_RUNTIME_DIR for tests
Mutter's backend code depends on it being set.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/691
2019-08-27 15:30:51 +03:00
957fa910b3 st: Adjust theme test
Mutter's Clutter fork can no longer be initialized separatedly, as
its backend now draws from MetaBackend. Adjust the code to use the
newly added test initialization function instead to get the test
back up.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/691
2019-08-27 15:30:51 +03:00
8ac5be95d3 tests: Don't initialize Clutter
Clutter's backend code depends on MetaBackend now, which makes it
impossible to initialize without resorting to private mutter API.

Luckily we only need Clutter for interactive tests which are broken
anyway, as Clutter.main() and friends were removed a while ago.

So for now, get at least unit tests working again by simply the
unnecessary Clutter initialization.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/691
2019-08-27 15:30:51 +03:00
c27bd62106 Update Japanese translation 2019-08-27 11:26:32 +00:00
480e8b8842 Update Japanese translation 2019-08-27 10:45:55 +00:00
c6580421b3 shell-extension-tool: Fix typo
Spotted by Amr Ibrahim.
2019-08-27 12:16:27 +03:00
c2f5331187 pointerA11yTimeout: Create new PieTimer object for each timeout
Since we now put a short timeout in before the start of the actual pie
timer we don't start the timer as often as we used to. This allows us to
create a new PieTimer object each time a timeout is started and
therefore play a finish animation independently of other (new) timeouts.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/688
2019-08-27 08:03:49 +00:00
5d0c403f1d pointerA11yTimeout: Add a zoom out+fade animation on success
We currently don't indicate success of the pie timer at all, let's do
this by animating the circle's scale and fading it out at the same time.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/688
2019-08-27 08:03:49 +00:00
20fc4b4490 pointerA11yTimeout: Draw a clean circle if the pie is finished
If the circle is complete and the pie timeout finished, we don't need
the lines to the center point indicating the ends of the pie anymore.
We just draw a clean circle instead, which allows for a zoom-out and
fade animation of the circle when we're done.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/688
2019-08-27 08:03:49 +00:00
ea3f906f38 pointerA11yTimeout: Don't stop the pie timer if it finished successfully
If the pie timeout has finished successfully there's no need to cancel
the pie animation, instead we can just wait for that animation to finish
and show some visual feedback like a zoom-out animation to indicate the
click afterwards.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/688
2019-08-27 08:03:49 +00:00
2c4df6abcf pointerA11yTimeout: Fade the pie timer in
Fade the pie timer in using a duration of 1/4 of the timeout and a
EASE_IN_QUAD animation. This significantly reduces flickering of the pie
timer while moving the cursor and makes the timer less distracting.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/688
2019-08-27 08:03:49 +00:00
67a0b3b98e pointerA11yTimeout: Remove unused properties
Those properties were overlooked in 5cb02c1c, remove them now.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/688
2019-08-27 08:03:49 +00:00
c366e9f3ca Update British English translation 2019-08-26 21:21:58 +00:00
812a8552e5 Updated Slovenian translation 2019-08-26 22:20:53 +02:00
069d7d6cac Update Galician translation 2019-08-26 15:22:53 +00:00
785a8b78b1 Updated Spanish translation 2019-08-26 12:26:03 +02:00
2d927639fe Update Catalan translation 2019-08-25 21:54:37 +02:00
d5cad10181 Update Galician translation 2019-08-25 16:47:04 +00:00
441a56b916 Update Swedish translation 2019-08-24 20:05:19 +00:00
c2a6a6c939 Update Latvian translation 2019-08-24 16:21:58 +00:00
15d1aee21a Update Polish translation 2019-08-23 18:41:05 +02:00
f1bc2d56f4 Update Basque translation 2019-08-23 07:18:19 +00:00
6f62965305 Update Basque translation 2019-08-22 21:45:29 +00:00
a6aa0ac74a Update Finnish translation 2019-08-22 16:36:37 +00:00
32ed4ee12e Updated Spanish translation 2019-08-22 15:06:34 +02:00
33a48aecb7 Update Persian translation 2019-08-22 12:10:52 +00:00
3b63062a30 Update Romanian translation 2019-08-22 08:05:48 +00:00
b680952197 Update POTFILES.in 2019-08-22 01:17:50 +02:00
a4ec460f96 Bump version to 3.33.91
Update NEWS.
2019-08-21 19:47:37 +00:00
5bd295842b extensionPrefs: Request correct GDK/GTK versions
gjs no longer has an implicit dependency on GTK 3.0, so without
requesting an explicit version, we will get the highest available.

Our code isn't GTK-4 ready, so request 3.0 explicitly.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/686
2019-08-21 19:43:33 +02:00
db9a7ea7a9 shell: Ignore non-toplevels for ShellWindowTracker::tracked-windows-changed
Popups and other override-redirect windows are meaningless to everything
that depends on the ShellWindowTracker. Ignoring those windows will result
in less ShellApp::windows-changed signal emissions, and less activity in
the AppMenuButton and everything else that depends on them.

Reduces gnome-shell CPU activity while typing on the Epiphany addressbar,
as the pop up animation there results in a number of xdg_popup being
created and destroyed.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/642
https://gitlab.gnome.org/GNOME/mutter/issues/556
2019-08-21 16:53:36 +00:00
490a62e781 shell-extension-tool: Turn into a small wrapper
Now that we have a replacement, deprecate the existing tool and make
it call out to the new one instead of implementing any functionality
on its own.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
d4b8912c0e extensions-tool: Add 'reset' command
Now that we allow to disable session mode extensions, it can be useful
to reset an extension to its original state, that is disabled in the
regular session, but possibly enabled via the session mode.

Add a corresponding command.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
532acf4c4a extensions-tool: Adjust enable/disable commands to shell changes
Change both 'enabled-extensions' and 'disabled-extensions' keys as in
commit ce1bee727. While those actions are now also exposed by the
shell's D-Bus API, there is some value in allowing the tool to be used
outside a running GNOME session (for example in setup scripts), so
keep changing the GSettings keys directly.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
7141c5be6d extensions-tool: Split out settings_list_add/remove helpers
With the addition of the 'disabled-extensions' key in commit ce1bee727,
the way extensions are enabled/disabled changed: Now a UUID is always
added to one list and removed from another.

Prepare for that by generalizing the relevant bits of the existing
enable/disable commands as helper functions.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
2df7757905 extensions-tool: Add 'uninstall' command
Uninstall is another action that can be useful from the command line,
so expose that as well.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
9d5c743a98 extensions-tool: Handle existing extension folder
g_file_make_directory_with_parents() returns an error if the directory
already exists (unlike g_mkdir_with_parents()), so it is currently
impossible to install more than a single extension, whoops.

Handle the error properly by just carrying on.

https://gitlab.gnome.org/fmuellner/gnome-extensions-tool/merge_requests/3
https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
653e6c85bb extensions-tool: Add 'prefs' command
Easily launching an extension's preference dialog from the command
line can be quicker than using a UI like Tweaks or the extension
website, so add that functionality.

https://gitlab.gnome.org/fmuellner/gnome-extensions-tool/merge_requests/2
https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
d9fa389079 extensions-tool: Add '--prefs' list option
We are about to add a 'prefs' command for launching an extension's
preference dialog. As that command only makes sense for extensions
that actually have preferences, shell completion should filter the
list of extensions accordingly. To allow that, add an appropriate
list option.

https://gitlab.gnome.org/fmuellner/gnome-extensions-tool/merge_requests/2
https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
a429fdbd08 extensions-tool: Add 'install' command
The ability to install unaudited extensions directly from a zip file
can be useful for testing and code review, so implement a corresponding
command that complements the previously added 'pack' command.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
f9357457bf extensions-tool: Add new 'pack' command
Extensions are uploaded to extensions.gnome.org as zip files that
not only contain the extension sources, but also compiled GSettings
schemas and message catalogues. To make this more convenient, add
a corresponding command for creating an archive suitable for up-
loading.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
369e400e32 extensions-tool: Show Philip some info
Add a 'show' alias for the previously added 'info' command
because it makes sense:

https://twitter.com/pwithnall/status/1036895943004762112

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
07ad4d8911 extensions-tool: Add info command
We already support displaying extension details for the list command,
so it's a logical extension to also support showing extension info
for a particular extension (not least because the shell has a
corresponding D-Bus method).

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
803a096b7e ci: Turn off man pages
There is little to test there, and excluding them slightly reduces the
dependencies in the CI image.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
1b40abe37a extensions-tool: Add a man page
https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
0de5209cf1 extensions-tool: Add bash completion
Completions are clearly helpful, in particular for extension UUIDs
that are often lengthy strings containing random usernames.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
07fad38a50 extensions-tool: Add list options
In addition to a plain list of all extensions, add options to display
additional details of each extensions and to filter the list by
enabled state or install location.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
ac4b88f25d extensions-tool: Implement list command
Seeing at a glance which extensions are installed is surely useful,
so add a corresponding command.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
23a7aa5740 extensions-tool: Add create options
As we are using git-style command syntax, we can allow for data to be
provided on the command line rather than asking for it interactively.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
0b1e29e5e3 extensions-tool: Implement create command
This implements more functionality of the existing tool and, as
'reload' is an unreliable feature that doesn't work more often
than not, the last bit that we will replicate.

The command follows the original for the most part, with the most
important difference being the installed template, which doesn't
provide any sample functionality and uses modern JS syntax.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
c8c93b2a70 extensions-tool: Implement enable/disable commands
This replicates the most basic functionality of the existing
gnome-shell-extension-tool, albeit using a git/gio/gsettings
style command interface rather than plain options; this will
allow us to implement more complex commands that have options
on their own in the future.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
d8c7cac536 extensions-tool: Start import
The gnome-extensions tool[0] was started as an external project, and
provides a replacement for the existing gnome-shell-extension-tool.
It was decided to ship the tool as part of the gnome-shell repository,
so start importing it with its history into a subdir.

[0] https://gitlab.gnome.org/fmuellner/gnome-extensions-tool

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
2019-08-21 18:28:02 +02:00
5cb02c1cb5 pieTimer: Use custom GObject property for animating pie
Before the move to Clutters implicit animations with 0846238f6 and
bf497ed64, we used Tweener to do a fake-animation of the opacity and
Tweeners `onUpdate` signal to queue a repaint of the PieTimer everytime
Tweener tries to update the animation.

Now, with Clutters implicit animations, there is no `onUpdate` signal
anymore and also `notify::opacity` no longer gets emitted since the
value doesn't actually change. This lead to the PieTimer no longer being
repainted, which broke the animation.

Fix this by implementing the current angle of the pie using a custom
GObject property `angle` and animating this property using the new
`actor.ease_property` method.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1533
2019-08-21 17:14:40 +02:00
10c1df61cd Update Persian translation 2019-08-21 11:31:51 +00:00
387e5ef0f1 Update Polish translation
Closes https://gitlab.gnome.org/Teams/Translation/pl/issues/4
2019-08-20 19:19:52 +02:00
f8f40f247f Updated Spanish translation 2019-08-20 12:43:51 +02:00
16cb918e0d shell-global: Set stage input region via MetaX11Display
The API in mutter changed to set it via MetaX11Display; lets jump on the
train.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/682
2019-08-19 09:20:44 +00:00
638b315e40 shell-global: Only sync input region if X11 session
We'd do an early out in the called functions eventually anyway.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/682
2019-08-19 09:20:44 +00:00
a20b8dc1ad Updated Lithuanian translation 2019-08-18 22:05:31 +03:00
4370aee81e Update Finnish translation 2019-08-18 14:38:55 +00:00
779e37fbd9 Update Romanian translation 2019-08-18 12:26:25 +00:00
6f4c5022eb iconGrid: Only add one onComplete callback for the animations
We only need a callback on the last animated actor, so no need to
register the callback for all actors.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/678
2019-08-16 14:55:25 +00:00
b499ca47a3 iconGrid: Restore grid actors when cancelling animations
When cancelling the animations of the icon grid, right now we simply
destroy all the clones without resetting the opacity and making the
actor reactive again. So if the spring animation to show the grid is
cancelled by pressing a key to start a search, the icon clones would be
destroyed, but the icon-opacity would still be set to 0. Now if the
Escape key is pressed, viewSelector will show the last active page (ie.
the iconGrid) without a custom animation and only fade in the page, and
because the icons still have an opacity of 0, they will be invisible.

Fix this by always restoring the opacity and reactive property of the
original actors if the animation is cancelled instead of only destroying
the clones.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/678
2019-08-16 14:55:25 +00:00
dc38e48202 Update Indonesian translation 2019-08-16 08:38:32 +00:00
7efdb97641 Update Catalan translation 2019-08-15 09:24:26 +02:00
14fd7c7532 Update Basque translation 2019-08-14 14:31:21 +00:00
21e14bd46f brightness: Ignore slider changes we initiated ourselves
Since we set the proxy value when the slider changes and set the slider
value on proxy property changes, we run into a cycle.

Before commit 3d3dca4aa this was addressed by not notifying on all slider
changes, but only in reaction to direct user action. Given that since the
splitting out of the BarLevel class those events are handled in a subclass,
that approach is at least unconvential and fairly fragile.

Instead, make the brightness indicator ignore any changes to the slider it
initiated itself.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1500
2019-08-13 16:35:17 +02:00
f0e1dc5715 slider: Do not notify on parent's behalf
Instead just use the regular property setter which since commit 3d3dca4aa
already emits the signal and queues a redraw.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1500
2019-08-13 16:35:17 +02:00
6b7af407e1 barLevel: Remove duplicated assignment
https://gitlab.gnome.org/GNOME/gnome-shell/issues/1500
2019-08-13 16:35:17 +02:00
d67c64af83 Update Swedish translation 2019-08-12 19:40:31 +00:00
5d2e5fe85a Update Icelandic translation
(cherry picked from commit 40db793230)
2019-08-12 07:47:40 +00:00
308da6ae53 Update Basque translation 2019-08-11 14:29:45 +00:00
76eceec1f5 Bump version to 3.33.90
Update NEWS.
2019-08-09 23:47:13 +02:00
209d332a30 AppIcon: Nullify _draggable if available on destruction
When an AppIcon actor is destroyed we try to unset a non-existent draggable
property.

Fix the typo, doing this even if we're not currently in a drag operation.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/677
2019-08-09 23:30:52 +02:00
35dbc3fcc9 appDisplay: Disconnect Main item-drag signals on icons destruction
Icons connect to overview's item-drag events to react to start/end drags,
however the icons should disconnect from signals once destroyed.

So, disconnect from Main events once the actors have been destroyed.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1511

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/677
2019-08-09 23:30:52 +02:00
ada01507a4 viewSelector: Use onStopped callback when fading out pages
With 8b368d010 we fixed a bug where the onComplete callback was always
called no matter whether the transition was interrupted before or not.
This exposed another bug: viewSelector depends on this behaviour when
fading out pages: After fading out a page, we call `this._animateIn` to
show the new page. Now if the fade-out animation gets interrupted, with
the correct behaviour of onComplete we end up not showing a new page and
the viewSelector remains empty instead. One case where this happens is
when pressing a key to start a search during the overview-animation.

Obviously we also want to show the new page in case the fade-out
animation was interrupted, so use the onStopped callback instead of the
onComplete callback here.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/674
2019-08-09 21:02:58 +00:00
826ac95726 environment: Don't use actor if an ease callback destroys it
An actor ease callback could destroy the actor, in such case we should do not
touch the actor anymore.

So, before calling the callback, reset restore the easing state and don't
perform any further action with it.

Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1507
2019-08-09 21:00:22 +00:00
9b7f228f8e src: Stop using g_get_current_time()
It isn't 2k38 safe and has therefore been deprecated. Replace it
with GDateTime or g_get_real_time() as appropriate.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/657
2019-08-09 19:22:18 +00:00
5d8ea4f9a3 main: Remove left-over variable
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/657
2019-08-09 19:22:18 +00:00
4c89eac9a4 folderIcon: Properly reject drop
When a drop is rejected, we are mistakenly returning
true instead of false.

Return false when the rejecting the drop.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/671
2019-08-09 10:58:46 -03:00
f76f30fd6a dnd: Fix drag cancel animation scale
When a drag is cancelled and the source actor
is visible, the drag actor is animated back to
the source position. The scale that the drag
actor will become is calculated as:

    scale = this._dragActor.width / sourceScaledWidth

However, this is wrong; what we wanted to do
is the opposite:

    scale = sourceScaledWidth / this._dragActor.width

Fix the scale calculation to match the math
above.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/671
2019-08-09 10:58:46 -03:00
488d98289c appIcon: Create and delete folders with DnD
Create a new folder when dropping an icon over another
icon. Try and find a good folder name by looking into
the categories of the applications.

Delete the folder when removing the last icon.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/671
2019-08-09 10:58:45 -03:00
ff3d32dd18 appIcon: Make AppIcon a drop target
Because the Dash icons are not drop targets themselves,
add a tiny DashIcon class, which is an AppDisplay.AppIcon
subclass, and disable all DND drop code from it.

Show a folder preview when dragging an app icon over another
app icon.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/671
2019-08-09 10:58:38 -03:00
be6ce3c5b4 appIcon: Scale and fade itself when starting drag
As per design direction, scale and fade the app icon
when starting dragging it, and show it again if the
drop is accepted. Clutter takes care of animating the
rest of icon positions through implicit animations.

Scale and fade the dragged icon while it's being dragged.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/671
2019-08-09 10:58:38 -03:00
21966afbc6 search: Defer updating results on allocation changes until redraw
Since the `notify::allocation` signal will obviously get emitted while
the actor is inside an allocation cycle and we might end up doing
changes to its allocation inside `updateSearch` by hiding or showing the
actor (which queues a relayout), we get a warning from Clutter.

Fix this by delaying the call to the parent method until the next
redraw, which should happen a few moments after the current relayout.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/672
2019-08-08 17:37:12 +00:00
68e3f74ffd search: Don't use max number of results if allocation width is 0
Since commit 1a27ff6130 we use the
allocation width of the GridSearchResults actor to calculate the max
number of results that can be shown for this search provider.

On the first run of the search, when no previous (cached) allocation is
available for the actor of GridSearchResults, the allocation width used
in `_getMaxDisplayedResults` will be 0, which in turn will make
`updateSearch` filter out all results returned by the search provider.

Now if this search provider is the only search provider that's enabled,
after calling `updateSearch`, the `SearchResults` class will call
`_updateSearchProgress` to check if any results are visible, assume
nothing was found and therefore hide the scrollView. This in turn causes
the GridSearchResults actor to not get a new allocation, which prevents
our code to fixup the max number of results on `notify::allocation` from
working: The number will continue to be 0 and we'll never show any
results.

To fix this regression, return -1 in `_getMaxDisplayedResults` if the
allocation width is 0 to inform `updateSearch` that we can't calculate
the maximum number yet and interpret a return value of -1 as "show all
results" in `updateSearch`. The same problem would probably also appear
if the allocation width is anything between 0 and the width of the
iconGrid with one icon in it, although this might as well be a valid
width in case a very small screen is used or with very large icons. So
let's only check for a width of 0 and hope the GridSearchResults actor
won't get weird temporary allocations in that range.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/672
2019-08-08 17:37:12 +00:00
87f5aa7a13 appDisplay: Animate activate-discrete-gpu action in the AppIconMenu
Just as we animate the apps launch using the zoom out animation if the
'new-window' action provided by the app is launched, we should also show
this animation if the 'activate-discrete-gpu' action provided by the app
via its AppInfo is launched.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/673
2019-08-08 16:48:48 +00:00
1dadbd0cbb appDisplay: Always animate our own new window and discrete gpu actions
For the "New Window" entry we add to the AppIcons popup menu we should
always animate the app icon if the menu entry is activated as it was
intended by commit 62786c09a8.

For the "Launch using Dedicated Graphics Card" entry we can also always
show the animation if the entry is activated since the entry should only
be visible if the app is stopped.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/673
2019-08-08 16:48:48 +00:00
481490fdc7 util: Remove AppSettingsMonitor
It is now unused.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1158
2019-08-08 17:41:46 +02:00
3114a24d1f dateMenu: Stop accessing app settings directly
Clocks has exactly the same issue as Weather: Its integration currently
relies on accessing its settings directly, which isn't possible when
the app is sandboxed.

Fix this the same way we did for Weather, by adding our own setting
and syncing it with the app via a custom D-Bus interface.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1158
2019-08-08 17:41:46 +02:00
73850fee02 appDisplay: Animate launch of new-window action in the AppIconMenu
We add our own "New Window" menu entry if the app doesn't already
provide a 'new-window' action. For this menu entry, we show the zoom out
animation on the app icon when the user clicks the entry.

To be consistent in case the app already provides its own 'new-window'
action via its AppInfo, also show the zoom out animation when this
action is activated.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/662
2019-08-08 15:28:48 +00:00
c0047cd11d shell/app-system: Monitor for icon theme changes
Whenever an app is installed, the usual routine is
to run 'gtk-update-icon-cache' after installing all
of the app's files.

The side effect of that is that the .desktop file of
the application is installed before the icon theme
is updated. By the time GAppInfoMonitor emits the
'changed' signal, the icon theme is not yet updated,
leading to StIcon use the fallback icon.

Under some circumstances (e.g. on very slow spinning
disks) the app icon is never actually loaded, and we
see the fallback icon forever.

Monitor the icon theme for changes when an app is
installed. Try as many as 6 times before giving up
on detecting an icon theme update.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/661
2019-08-08 15:22:44 +00:00
dd9a452594 st/texture-cache: Monitor GtkIconTheme changes
The texture cache, right now, only monitors for
complete theme changes. If the contents of the
icon theme change, however, the texture cache
isn't properly invalidated.

This manifests itself as a randomly reproducible
bug when installing an app; the app icon may be
the fallback forever, or as long as something else
updates the icon theme.

Watch for the GtkIconTheme:changed signal, and
evict the texture cache when the theme changes.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/661
2019-08-08 15:22:44 +00:00
e45c917811 controlsManager: Don't fade icon grid while dragging
As pointed out by designers, fading it signals that the
icon grid is not a drop target, when now it actually is.

Remove the fade effect applied to the icon grid when
dragging. Since this is the only caller of fadeIn() and
fadeHalf(), also remove these methods.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/664
2019-08-08 09:12:00 -03:00
fd19906c64 allView: Scale in when moving icons from folders
App icons inside folders are already animated when the folder is
opened, but moving an app icon from a folder doesn't, making the
transition abrupt.

Fortunately, it's easy to detect icons that were previously hidden
but are not anymore.

Add an animation to these icons when showing.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/664
2019-08-08 09:12:00 -03:00
54a2773046 folderIcon: Add visual drag-over feedback
WIP: This is not exactly what was discussed on IRC, but
it's looking alright as a first iteration. Design feedback
welcomed.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/664
2019-08-08 09:11:59 -03:00
ec8b7bc7b2 allView: Remove icon from folder when dropping outside
When dropping an app icon to outside the folder, remove the
app from that folder. GNOME Shell is already smart enough
to figure out the setting changes and update the icons.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/664
2019-08-08 09:11:59 -03:00
ea71172d44 allView: Switch pages when dragging above or below the grid
This is necessary for being able to drag application icons
to folders in different pages.

Add a drag motion handler to AllView and handle overshoots
when dragging. Only handle it when dragging from AllView.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/664
2019-08-08 09:11:59 -03:00
5dfa620f86 folderIcon: Update folder icon after dropping
After dropping an application into the folder icon, the
list of applications is updated but the folder icon itself
is not.

Introduce BaseIcon.update() and call it from FolderIcon
when redisplaying.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/664
2019-08-08 09:11:59 -03:00
09d5f0779d folderIcon: Allow dropping application icons
Connect to the overview signals related to Drag n' Drop, and
allow dropping application icons in it. Dropping an icon
appends the application id to the folder's GSettings key.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/664
2019-08-08 09:11:56 -03:00
d1880dc987 appDisplay: Add event blocker inhibition API
The event blocker is an actor that is added in between the
icon grid and the app folder popup in order to guarantee
that clicking outside the app folder will collapse it.

However, the next patch will require allowing dragging events
to be passed to folder icons, and the event blocker gets in
our way here, preventing drag n' drop to work properly.

Add an API to inhibit the event blocker. This API will be
used by the app folders while an item is dragged.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/664
2019-08-08 09:09:46 -03:00
928b49705f systemActions: Create SensorProxy unconditionally
When plugging in a device with sensors that are unsupported by
iio-sensor-proxy, the proxy may quit so fast that the name disappears
from the bus before we get to construct the SensorProxy in response
to the name-appeared handler, resulting in the following warning:

JS ERROR: TypeError: this._sensorProxy is null
_sensorProxyAppeared/this._sensorProxy<@resource:///org/gnome/shell/misc/systemActions.js:217:17
_makeProxyWrapper/</<@resource:///org/gnome/gjs/modules/overrides/Gio.js:243:21

Address this by creating the proxy unconditionally instead of monitoring
the bus name, and using the g-name-owner property to determine whether
iio-sensor-proxy is active.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1357
2019-08-08 11:42:28 +00:00
f50cac3005 workspace: Sort windows to minimize travel distance
When transitioning to or from the overview, windows travel
a certain distance between their real desktop position and
their place in the overview window grid. The less this travel
distance is, the smoother, more polished, and less jarring
the overall transition looks. This is why it makes sense to
try reordering and repositioning windows to minimize their
travel distance. That being said, there are other factors
that impact the quality of the overview layout, such as how
much the windows get scaled and what portion of the overall
available space they take up.

The existing code tries to minimize the travel distance by
sorting the windows in each row by their horizontal position.
There are, however, two problems with this implementation.
First, it compares the coordinates of windows' left edges as
opposed to their centers, which means it yields unexpected
results when a small window is positioned next to the left
edge of a large window. Second, it completely disregards
vertical coordinates, instead assigning windows to the grid
rows using their monotonically increasing window numbers,
effectively vertically sorting them by the order they were
created in.

This commit changes both vertical and horizontal ordering
to work based on the coordinates of the geometric centers
of the windows. That is to say, windows are first assigned
to grid rows based on the vertical coordinates of their
centers, and subsequently sorted inside each row based on
the horizontal coordinates of said centers. In my testing,
this leads to a much more intuitive and visually pleasing
window placement.

Signed-off-by: Sergey Bugaev <bugaevc@gmail.com>

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/267
2019-08-08 13:13:35 +02:00
ec6e1315a5 weather: Pick up original settings values
We mirror Weather's location settings to not depend on the app running,
but then don't read the original values, oops.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1158
2019-08-07 18:53:41 +00:00
ad55cb6d5d weather: Fix warning on closing Weather
GDBusProxy::g-properties-changed is also emitted when the name drops from
the bus, at which point any properties will be null. That's not a valid
gsettings value, so to avoid the corresponding warning, move the g-name-owner
check accordingly.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1158
2019-08-07 18:53:41 +00:00
0ce0376725 docs: Update animation section
Replace the existing Tweener section with a small outline of the
Clutter animation API and our wrappers.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/669
2019-08-07 18:40:49 +02:00
015ca2c507 lookingGlass: Remove Tweener from preimported modules
We want to encourage using Clutter's implicit animations directly,
so stop importing the discouraged animation framework by default.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/669
2019-08-07 18:40:49 +02:00
21e752e5e4 keybindings: Remove pause-resume-tweens shortcut
It is no longer useful since we replaced Tweener.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/669
2019-08-07 18:40:49 +02:00
1e20a1249a dnd: Stop using getTweenCount()
Those checks were carried over from the very first DND implementation;
if they were ever actually required at all, this is no longer the case
as we moved away from Tweener for all our animations.

The number of cases where an extension is still using Tweener, creates
draggable actors, *AND* requires the checks for proper functioning
should be indistinguishable from zero, so drop the code.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/669
2019-08-07 18:40:49 +02:00
b67c300484 js: Use Clutter transitions for adjustment changes
This concludes our quest of moving from Tweener to Clutter's
animation framework.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/669
2019-08-07 18:40:49 +02:00
8ac2086ed1 environment: Add convenience method for adjustment transitions
By now, Tweener is used exclusively to animate changes to the
StAdjustment:value property. But not for long, as now that we
implement the same transition API as Clutter.Actor, we can
re-use the existing convenience method for property transitions
for adjustment changes as well.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/669
2019-08-07 18:40:49 +02:00
72defaa97e st: Use StAdjustment's new transition API
Now that the adjustment itself can be used to track its transitions,
do that for animating the scroll bar handle.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/669
2019-08-07 18:40:47 +02:00
9097c5e9c0 st: Add transition API to StAdjustment
StAdjustment implements the ClutterAnimatable interface, so we can
already animate its properties with ClutterPropertyTransitions.

But as it is currently not possible to associate a transition with
an adjustment, it must be owned (and kept alive in case of GC) by
the calling code.

Change that by implementing the same (add|remove|get)_transition() API
as ClutterActor, so we can use a familiar API and even duck typing in
case of javascript.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/669
2019-08-07 18:40:44 +02:00
79b54f65b4 volume: Fix overdrive in slider
When setting a maximum value above 1, we currently set a non-existant
maximum_level property instead of the actual 'maximum-value' one.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/670
2019-08-07 16:17:45 +00:00
52c2417685 barLevel: Fix value range
The range is usually within [0, 1], but since we support overdrive
as well, the upper limit must be the maximum value we allow for
overdrive.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/670
2019-08-07 16:17:45 +00:00
9073debe60 environment: Remove transition tracking
The tracking was important in an earlier iteration, but as the helper
functions now remove overwritten transitions before setting up the
new ones, we can just as well connect to the ::stopped signal directly.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/668
2019-08-07 02:40:17 +02:00
8b368d010f environment: Fix transition callback
This is rather embarrassing - we currently confuse the transition with
the finished parameter, which means we always run the onComplete handler
no matter whether the transition was interrupted or actually completed.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/668
2019-08-07 02:40:13 +02:00
8b97a06961 allView: Always update currentPage
Commit 0f178c3b3d added a shortcirtuit to avoid running
an animation on an invisible actor. However, it introduced
a bug where the current page is not properly updated. That
leads to the wrong set of icons being animated under some
circumstances.

Update the current page even if we bail out early.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/667
2019-08-06 21:09:13 -03:00
fffe7bdf9c js: Ease non-animatable actor properties
Properties that aren't marked as animatable don't support *implicit*
animations, but they can still be animated with explicit transitions.
Use the newly added convenience method to cut down further on Tweener
usage.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/666
2019-08-06 23:54:29 +02:00
ef18f621ac environment: Add convenience method for property transitions
While we are now using implicit animations for all animatable properties,
there are still some cases where we animate other actor properties (for
example from a custom subclass) or associated objects like effects.

Those can still be animated using Clutter animations, as long as we use
the explicit API rather than implicit animations. Again this API is
cumbersome and tricky enough to warrant a convenience wrapper.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/666
2019-08-06 23:54:29 +02:00
dfa41f6926 js: Use GObject properties for animated properties
Clutter animations work on GObject properties on animatables. The
last commit took care of the latter by turning all animated objects
into actor subclasses, now it's time to make all properties used
in Tweens into GObject properties.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/666
2019-08-06 23:54:29 +02:00
3d3dca4aa2 js: Actorize animated objects
We have a couple of places where we don't tween the actor, but a
custom property on the delegate object. In order to move those
to Clutter animations, we will need an animatable, so turn those
objects into widget subclasses.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/666
2019-08-06 23:54:29 +02:00
928595fe21 windowManager: Change effect's brightness property
The set_brightness() method is the most convenient way of controlling
the effect's brightness, but Clutter animations only deal with properties,
so start using the (ClutterColor) brightness property instead.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/666
2019-08-06 23:54:29 +02:00
fc958f4215 windowManager: Move animation into WindowDimmer
Currently WindowDimmer exposes a JS property that is used to control the
underlying effect. This works fine with Tweener, but not with Clutter
animations which we want to use ultimately.

As a first step towards that, expose a setDimmed() method instead of
the property and handle the animation internally, so it can be adapted
more easily.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/666
2019-08-06 23:54:29 +02:00
0846238f69 js: Use implicit animations for animatable properties
We now have everything in place to replace Tweener for all animatable
properties with implicit animations, which has the following benefits:

 - they run entirely in C, while Tweener requires context switches
   to JS each frame

 - they are more reliable, as Tweener only detects when an animation
   is overwritten with another Tween, while Clutter considers any
   property change

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 23:54:29 +02:00
007b6ca2e8 environment: Add convenience method for implicit animations
Setting up implicit animations is more verbose than using tweener, in
particular when setting up a callbacks to run on overwrite or completion.
In order to make its use more convenient, monkey-patch ClutterActor
with an ease() method that works similarly to Tweener.addTween().

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 20:50:43 +02:00
0b4a4487a0 environment: Support slow down factor when easing
Being able to slow down animations is a helpful debugging tool; to not
lose it when starting to use Clutter's implicit animations, monkey-patch
the appropriate methods to support our global slow down factor.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 20:50:43 +02:00
99b4e047dd tweener: Use new adjustAnimationTime() helper
Now that we have a new helper function for adjusting animation times,
make Tweener use it so that we keep the code in a single place.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 20:50:43 +02:00
ae2af34453 environment: Add adjustAnimationTime() helper
As we currently use Tweener for all animations, we have a single place
for hooking up the enable-animations and slow-down-factor settings.
However that will no longer hold true when we'll start to use Clutter's
built-in animation facilities, so add a small helper function that
applies the necessary adjustments.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 20:50:43 +02:00
fdf24ceecc messageTray: Stop tweening custom opacity property
Notifications use a transition that overshoots the target value, however
we can only really do that for the position and not the opacity where
some values would end up out of the valid range.

We currently address this by proxying the actual opacity property in a
javascript property, and clamp it to the valid range in an onUpdate()
callback.

This won't be an option if we want to use Clutter animations, so instead,
use separate tweens for opacity and position.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 20:50:43 +02:00
870dd84a50 pageIndicators: Defer IN animation until redraw
Unlike Tweener, which doesn't know or care about actor state, Clutter
will skip implicit animations on actors that aren't mapped. This makes
sense of course, but it will break the page indicator animation we
do on map: When we get notified about the container being mapped, the
individual indicators (which are the actors being animated) are still
unmapped.

Resolve this by deferring the animation to a LaterFunc.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 20:50:43 +02:00
5d6db923b7 screenShield: Stop using custom transition
For animating the arrows on the screenshield, we currently use a custom
transition function that tweens the opacity from 0 to maxOpacity in the
first half of the animation, and from maxOpacity back to 0 in the second
half.

This doesn't easily translate to Clutter's own animation framework, so
replace the custom transition with two consecutive tweens which do.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 20:50:43 +02:00
8eb88d17fe dash: Directly tween actors
Dash items are currently animated via the custom "childScale" and
"childOpacity" properties. However since commit efb3025d8c, those
properties actually control the scale-x/scale-y and opacity properties
of the actor itself (not the child), so cut out the intermediate
custom properties in favor of the "real" ones.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 20:50:43 +02:00
abe012b9fc ripple: Split animation
Similar to the previous patch, splitting the existing tween into two
will allow us to use implicit animations.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 20:50:43 +02:00
749f52fc8b popupMenu: Split submenu expansion and arrow rotation
When expanding a submenu, we currently use a single tween to animate
both the submenu actor and the source arrow. We do this by tweening
a monkey-patched JS property on the main actor, which we then use
to update the arrow's GObject property on updates. As Clutter cannot
animate random JS properties, this trick will prevent us from using
implicit animations here.

The only reason I can think of for using a single tween is to keep
both animations in perfect lock step, but as expansion and rotation
are visually quite distinct, this shouldn't be required, so just
set up separate animations for each actor.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 20:50:43 +02:00
1e6cb43815 shell-app: Consider desktop actions in can_open_new_window()
We currently only consider a remote "app.new-window" action when running,
but not a fixed "new-window" action in the .desktop file. The latter is
clearly useful as well, in particular as open_new_window() already does,
so add it.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/665
2019-08-06 15:04:24 +02:00
213d10bf4e messageTray: Drop tween helper function
It makes the code harder to follow and saves little in terms of code
duplication.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/654
2019-08-06 14:52:41 +02:00
1abfbb82c5 windowManager: Small cleanup
We start with a dim factor of 0, so the syncEnabled() call in the
constructor will always disable the effect. Just create the effect
disabled and save the method call.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/654
2019-08-06 14:52:41 +02:00
bf36d99a33 barLevel: Don't reset value on overview-start changes
While it makes sense to cap the current value to a new maximum value,
I can't think of a good reason why changing the overdrive-start should
reset the current value to that.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/654
2019-08-06 14:52:41 +02:00
3ee525833e loginDialog: Use GObject bindings over onUpdate handler
Instead of iterating over all actors each frame and sync'ing their
opacities, we can set up bindings once before the animation.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/654
2019-08-06 14:52:41 +02:00
bf497ed643 pointerA11yTimeout: Use notify handler instead of onUpdate callback
We either need to queue a repaint on opacity updates or we don't,
whether the opacity change happens in an animation or not shouldn't
matter.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/654
2019-08-06 14:52:41 +02:00
9b8c0f7519 pointerA11yTimeout: Fix style nit
There's a stray space after the opening parentheses. While at it,
reindent the object literal.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/654
2019-08-06 14:52:41 +02:00
12ec5d1cbe switcherPopup: Avoid unnecessary animation
Ever since commit 28bb0c1fb2, the popup's actual visibility has been
controlled by its :opacity property, not :visible.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/654
2019-08-06 14:52:41 +02:00
0f178c3b3d appDisplay: Skip animation when hidden
The time computation isn't just unnecessary in that case, it's likely
wrong as well: If we don't have a valid allocation, we may well end
up with a negative value, NaN or Infinity.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/654
2019-08-06 14:52:41 +02:00
9aa06e3001 Update Indonesian translation 2019-08-06 10:49:56 +00:00
00ec8ca989 allView: Redisplay on folder changes
Now that redisplaying is a lightweight operation that only
adds and removes what changed, we can not be concerned about
redisplaying on folder changes.

Redisplaying will be necessary when custom order in the app
grid is implemented, in order to update not only which icons
are hidden, but also their position.

Call _redisplay() in AllView when folders change.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/645
2019-08-05 21:20:31 -03:00
9c6f558c9e baseAppView: Remove unused BaseAppView.addItem
Now that BaseAppView does not allow for subclasses to add
and remove items directly, the addItem() method can be
removed.

Remove BaseAppView.addItem().

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/645
2019-08-05 21:20:31 -03:00
1c172955ee allView, folderView: Only add icons once
FolderView and AllView currently check if the item is
present in the BaseAppView._items map, in order to avoid
adding the same icon multiple times.

Now that BaseAppView._loadApps() has a different role --
it returns a list with all app icons, and BaseAppView
diffs with the current list of app icons -- checking the
BaseAppView._items map is wrong.

Make sure there are no duplicated items in the temporary
array returned by all _loadApps() implementations. Remove
the now unused BaseAppView.hasItem() method.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/645
2019-08-05 21:20:31 -03:00
1d44bf7ce6 baseAppView: Only add and remove when necessary
BaseAppView currently removes all icons, and readds them, every
time the list of app icons needs to be redisplayed. In order to
allow animating app icon positions in the future, however, we
cannot destroy the actors of the app icons.

Previous commits paved the way for us to do differential loading,
i.e. add only the icons that were added, and remove only what was
removed.

Make the BaseAppView effectively implement differential loading.
The BaseAppView.removeAll() method is removed, since we do not
remove all icons anymore. BaseAppView._loadApps() now returns an
array with the new apps, instead of putting them directly at the
BaseAppView lists.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/645
2019-08-05 21:20:29 -03:00
036e41621d baseAppView: Move _loadGrid() into _redisplay()
Next commit will introduce differential loading of
app icons, and will reorganize this part of the
codebase.

When doing that, the ideal symmetry of the new code
would be:

 * Update BaseAppView._allItems array
 * Update BaseAppView._items map
 * Update BaseAppView._grid actor

Move the code in _loadGrid() into _redisplay() so that
we can check in-place which new icons need to be added.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/645
2019-08-05 21:19:26 -03:00
3003e9091d baseAppView: Call loadGrid() directly
Now that the three views follow the exact same loading routine
(remove all + load apps + load grid), we don't need each view
call loadGrid() directly anymore.

This is an important step in order to animate adding and removing
icons, since now we can diff old and new app icons properly.

Move all calls to BaseAppView.loadGrid() to a single one after
BaseAppView._loadApps(). Also add the underscore prefix, since
this is now considered a protected function.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/645
2019-08-05 21:19:25 -03:00
8d9da10710 frequentView: Use BaseAppView.addItem() and loadGrid()
FrequentView is another view that is slightly not unified with how
BaseAppView expects subclasses to load app icons. Instead of using
BaseAppView.addItem() and then calling BaseAppview.loadGrid(), it
adds the app icons directly to the icon grid.

Make FrequentView add icons using BaseAppview.addItem(), and load
the icons using BaseAppView.loadGrid().

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/645
2019-08-05 21:19:25 -03:00
4d23c12028 folderIcon: Move app icon loading to FolderView
Future patches will diff the old and new icons of views, in order to
animate them when necessary (e.g. adding an app icon to a folder, or
from a folder back to the app grid). In order to do that, all views
must be streamlined in how they load app icons.

Currently, FrequentView and AllView are already following the behavior
expected by BaseAppView, but FolderView isn't. Its icons are loaded by
FolderIcon, and FolderView doesn't implement BaseView._loadApps(),
which makes it impossible to diff old and new apps.

Move the app icon loading routine from FolderIcon to FolderView, by
implementing the _loadApps() method.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/645
2019-08-05 21:19:24 -03:00
476816732f cleanup: Use milliseconds for animation times
The different units - seconds for Tweener and milliseconds for
timeouts - are not a big issue currently, as there is little
overlap. However this will change when we start using Clutter's
own animation framework (which uses milliseconds as well), in
particular where constants are shared between modules.

In order to prepare for the transition, define all animation times
as milliseconds and adjust them when passing them to Tweener.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/663
2019-08-05 21:55:20 +00:00
31968ea53c meson: Avoid using relative paths to po directory
Replace the relative path to the po directory with a toplevel definition
of po_dir and use that instead.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/507
2019-08-05 21:38:51 +00:00
7e00d22bfa data: Update systemd units
The existing units were never used as the corresponding support was
never merged into gnome-session.

This commits updates units to be usable with the newer gnome-session
unit definitions. Also added is appropriate failure/restart logic
including the ability to disable extensions. Note that extensions will
only be disabled if a failure happens in the first two minutes after
login.

https://gitlab.gnome.org/GNOME/gnome-session/merge_requests/13
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/507
2019-08-05 21:38:51 +00:00
50055004f5 data: Flag in desktop file that gnome-shell can be started using systemd
As gnome-shell is a required component for GNOME sessions, gnome-session
will currently always try to autostart it. However as we are moving towards
using systemd's user instance for session startup, gnome-session should only
be responsible for launching the shell when either not running under systemd
or when we were built without systemd support.

gnome-session can detect the former but not the latter, so communicate this
via the newly added X-GNOME-HiddenUnderSystemd key in the .desktop file.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/507
https://gitlab.gnome.org/GNOME/gnome-session/merge_requests/13
2019-08-05 21:38:51 +00:00
abe2f07779 Update Swedish translation 2019-08-05 20:41:06 +00:00
277f0d77f3 Update Romanian translation 2019-08-04 13:09:14 +00:00
01d2ad760a Updated Spanish translation 2019-08-02 09:14:52 +02:00
25f118bf2c Update Karbi translation 2019-08-02 07:12:56 +00:00
12b8fb15b1 st: Move slow-down-factor into settings
Now that we have a Settings singleton, we have a better place for the
slow-down-factor than an awkward extern variable.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/656
2019-08-01 21:13:28 +00:00
5295866eff st: Fix property type
Commit 8f3554ff accidentally changed the type of the StWidget:track-hover
property from boolean to object.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1482
2019-08-01 12:44:33 +02:00
02b47f4640 weather: Fix property name
It's g-*name*-owner. As the condition covers the unlikely case where
Weather is running before gnome-shell, this slipped through.
2019-08-01 01:50:13 +02:00
108ac7cf20 ci: Make eslint wrapper script usable outside gitlab's CI
The eslint wrapper script is useful for development as well, but it currently
fails on systems where bash is not installed in /usr/bin.

Make it useful there as well by changing the shebang to use /usr/bin/env

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/655
2019-07-31 23:28:43 +02:00
933c037c6e weather: Stop accessing app settings directly
Our current Weather integration depends on poking around the app's
settings, which we cannot do when the app is sandboxed (as its
filesystem is "hidden away" in a container in that case).

So instead, use our own GSettings schema for the settings, and sync
it with GNOME Weather via a custom D-Bus interface.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1158
2019-07-31 19:04:41 +00:00
8f3554ff3e st: Use g_object_notify_by_pspec()
`g_object_notify()` actually takes a global lock to look up the property
by its name, which means there is a performance hit (albeit tiny) every
time this function is called. For this reason, always try to use
`g_object_notify_by_pspec()` instead.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/652
2019-07-31 17:35:03 +00:00
668128f8c9 lightbox: Remove previously added tweens from target
Since commit 007d30573 we use an actor effect to apply the radial effect and
we pass the effect to the tweener in order to animate it.

However, we always still remove the previously added tween from the actor,
instead that from the actual target.

So, depending the radial effect state, remove the tweens from the proper target

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/653
2019-07-31 19:19:28 +02:00
28ab1f4af4 Ensure G_PARAM_STATIC_STRINGS for all properties
In `st`, we can do this by using `ST_PARAM_*`. In the other code files,
just use `G_PARAM_STATIC_STRINGS` directly.

This is just a minor convenience to prevent a few unnecessary string
copies.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/646
2019-07-30 13:58:44 +00:00
d360114226 st: Cleanup ST_PARAM_* and add WRITABLE version
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/646
2019-07-30 13:58:44 +00:00
5fc456d9d9 userWidget: Handle non-square icons gracefully
We currently assume that user icons are square, which is the case when
set by the users settings panel, but not enforced by AccountsService.

Handle that case by moving the pixel size back to the actor and using
an appropriate background-size style property of 'cover' (which means
the smallest dimension of the image is scaled to fit the desired size).

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1144
2019-07-29 16:16:22 +00:00
007d305736 shell: Actually make GLSLEffect an effect
A generic, introspectable Shader effect is not only more flexible
than a shader actor, it will also make it much easier to turn
Lightbox into an actor subclass and replace Tweener with Clutter's
own animation support.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/651
2019-07-29 17:51:14 +02:00
ae7ec648b2 shell: Properly rename type
This is just a find-and-replace name change to make the actual code
changes in the follow-up commit more apparent.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/651
2019-07-29 17:51:14 +02:00
99a2fad311 shell: Rename GLSLQuad to GLSLEffect
You can guess where this is going ...

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/651
2019-07-29 17:51:14 +02:00
82d466598c lightbox: Remove unnecessary params
Actors have a default origin of (0, 0), so there's no need of
setting those explicitly.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/651
2019-07-29 17:51:14 +02:00
3a748fe737 theme: Fix focus style of icons in the calendar popup
https://gitlab.gnome.org/GNOME/gnome-shell/issues/1474
2019-07-28 15:43:56 +00:00
89ce53e3ff shellDBus: Use floats instead of percentages for OSD levels
Due to typecasting being done when converting floats to integers in
gnome-settings-daemon, a volume of 0.9% in g-s-d will end up as 0% in
gnome-shell. This can lead to a mismatch of icons between the volume OSD
(the icon to use is determined by g-s-d itself) and the shells own
volume indicator (the icon to use is determined by the shell using the
volume received from g-s-d).

To fix this, simply get rid of the conversion from float to percentage
in g-s-d and back to floats in the shell and just send a float/double
value on DBus.

https://gitlab.gnome.org/GNOME/gnome-settings-daemon/merge_requests/78
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/385
2019-07-28 17:27:53 +02:00
2f29081667 osdWindow: Remove LevelBar and use BarLevel directly
LevelBar is not really used, all the checks are implemented inside
BarLevel as well and the accessible name is wrong because the osdWindow
doesn't only show the volume, but also the brightness and other things.

The workaround for updating the bars width is also no longer needed now
that we have BarLevel.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/385
2019-07-28 17:27:53 +02:00
cb0d28770f osdWindow: Use float values as input for osdWindow
Using the same type/interval as BarLevel means we can cut out the intermediate
LevelBar class in a follow-up cleanup.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/385
2019-07-28 17:27:53 +02:00
86c3909908 barLevel: Return when trying to set value to already used value
There are some cases (for example when tweening value changes), where
the level will be set to the same value it already is at. Avoid those
unnecessary repaints by checking whether the value is already used and
returning if it is.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/385
2019-07-28 17:27:53 +02:00
b970ee7293 barLevel: Use setters instead of methods
Switching to getters/setters make the code suitable for using
with Tweener and as GObject properties, both of which we'll
do soon enough.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/385
2019-07-28 17:27:53 +02:00
5545e84430 volume: Remove unused method
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/385
2019-07-28 17:27:53 +02:00
85d9f39417 Update Friulian translation 2019-07-28 07:37:16 +00:00
a81450df17 Update Catalan translation 2019-07-28 00:49:20 +02:00
a7c94b2cd2 Update Indonesian translation 2019-07-26 11:24:57 +00:00
9d65c8b2ec ci: Remove path limitation of js60 check
The check is supposed to run for all merge requests that touch javascript
files, but for some reason I see it's skipped quite often. Better have
the test run unnecessarily some times than let bugs through, so remove
the limitation.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/647
2019-07-25 22:37:22 +02:00
1b7ff76092 ci: Fix identifying commits in MR
When a MR uses a private namespace, "origin" refers to that, and its
master branch may be outdated with regard to upstream master.

We are really only interested in checking the line changes from the commits
in the MR, so figure out the correct branch point instead.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/647
2019-07-25 22:31:31 +02:00
17e32bf16d docs: Fix interface prefix for D-Bus docs
The --interface-prefix= gtk-doc argument was being passed a stringified
array instead of a simple string.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/644
2019-07-25 15:56:20 +02:00
74905f3edc Update Basque translation 2019-07-25 06:43:46 +00:00
8e1b13ca96 appDisplay: Trivial code style improvement
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/643
2019-07-24 18:55:50 -03:00
c0e90807e0 baseIcon: Align labels to the center
Ensure icon labels are aligned to the center, both
vertically and horizontally. This will be required
for using BaseIcons as drag actors.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/643
2019-07-24 18:55:50 -03:00
3db1058c2c appIcon: End running drag operations on destroy
AppIcon makes itself draggable, and handles the various DnD
routines such as 'drag-begin' and 'drag-end' by making the
Overview emit the appropriate signals.

However, when destroyed, the AppIcon does not try to finish
any drag operations that started. That causes the event
blocker in AllView not to be updated correctly when dragging
icons to outside folders.

Make AppIcon emit 'item-drag-end' when a drag operation
started and it's destroyed.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/643
2019-07-24 18:11:25 -03:00
a57c4c580e Update Karbi translation 2019-07-24 18:42:40 +00:00
33bbbdc322 Updated Spanish translation 2019-07-24 09:51:21 +02:00
5826336a77 Update Karbi translation 2019-07-24 03:11:21 +00:00
3b5d13a0b2 ci: Add eslint job to review stage
We have now reduced the number of eslint errors enough to add it to
the CI pipeline. There are still plenty of errors left though, so we
cannot simply run eslint and fail on any errors. So instead, run it
through a fancy script that:

 - generates an eslint report using the "regular" configuration
 - generates an eslint report using the "legacy" configuration
 - creates a combined report with errors common to both configurations

When the pipeline is running for a branch or tag, the final report is
printed out and the job succeeds (we know there are errors left);
when the pipelne is running for a merge request, we fail if any errors
are reported for the lines modified/added by the MR.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:35:25 +02:00
ed37ba1d9b ci: Merge 'source_check' stage into 'review'
GNOME apps use 'review' for the CI stage that generates and exports a
flatpak bundle after a successful build, so they need some other name
for anything that is checked before building; that's how we ended up
with the somewhat awkward 'source_check' stage (inherited from Polari).

But since the commit log check added a 'review' stage that runs pre-build,
use that for all checks that are performed before the build stage.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:34:55 +02:00
8ea6fd1925 ci: Include git in extension-ci image
It's useful for anything that does not want to limit itself to the
checked out branch, like comparing source and target branch.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:28:45 +02:00
93a461f3f7 perf: Shut up another eslint error
The functions here are asynchronous to handle control back to the
mainloop while waiting for an action to complete, not to run operations
in parallel. That is, the race condition the rule is protecting against
isn't an issue here, so disable the error.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:28:45 +02:00
fda7c9b06e perf: Shut up an eslint error
The while(true) loop is intentional here and not a bug, so disable
the corresponding eslint rule.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:28:45 +02:00
1e13f32cea style: Disable camelcase rule for GObject properties
For GObject properties, we follow the convention of all-lowercase,
dash-separated names. Those translate to underscores in getters/setters,
so exempt them from the newly added "camelcase" rule.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:28:45 +02:00
e357559582 cleanup: Mark globals used from other modules as exported
eslint cannot figure out that those symbols are used from other modules
via imports, so they trigger unused-variable errors. To fix, explicitly
mark those symbols as exported.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:28:45 +02:00
71759a0769 cleanup: Mark unused (but useful) variables as ignored
While we aren't using those destructured variables, they are still useful
to document the meaning of those elements. We don't want eslint to keep
warning about them though, so mark them accordingly.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:28:45 +02:00
11b116cb9d cleanup: Remove some unhelpful unused variables in destructuring
We aren't using them, and they don't add much in terms of clarity,
so drop them to fix a couple of eslint errors.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:28:45 +02:00
2f97a1a55d cleanup: Mark unused arguments as unused
This will stop eslint from warning about them, while keeping their
self-documenting benefit.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:28:45 +02:00
79cf3a6dd0 cleanup: Remove some unhelpful unused arguments
Those unused arguments aren't bugs - unbeknownst to eslint, they all
correspond to valid signal parameters - but they don't contribute
anything to clarity, so just remove them anyway.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:28:45 +02:00
0257de1b7e lint: Allow marking variables/arguments as unused
Unused variables or arguments can indicate bugs, but they can also
help document the code, in particular in case of signal handlers
and destructuring.

Account for this by keeping the error, but set up patterns that allow
us to opt out of if for individual variables/arguments. For arguments
we pick a '_' prefix, while for variables we go with a suffix instead,
to not accidentally exempt private module-scope variables.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
2019-07-24 00:28:45 +02:00
466dc8da8f build: Automatically bump mutter API version each cycle
Mutter will now bump the API version automatically at the
beginning of a new development cycle; do the same here.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/641
2019-07-23 21:57:21 +00:00
2c61badc02 Update Esperanto translation 2019-07-23 19:08:49 +00:00
3f8d3a7ee2 panel: Remove unused constant
This was left-over in commit 2743f18af, and probably is the real reason
why the busy spinner wasn't using the shared AnimatedIcon.Spinner class:
The animation there was much slower.

Still, let's keep the code as-is for now, if we really need a different
animation time, we can add an optional constructor parameter to the
Spinner class.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/640
2019-07-23 20:05:19 +02:00
a455860978 appDisplay: Indent with spaces rather than tabs
Another style nit that sneaked in with commit eaa32090b9.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/640
2019-07-23 20:05:19 +02:00
0ecf135a4b appDisplay: Add missing semicolon
This slipped through in commit eaa320 ...

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/640
2019-07-23 20:05:19 +02:00
cebb6d40df appDisplay: Keep popup open on refresh
If the list of applications is refreshed we currently close
the open app folder.

This commit adds logic to reopen the app folder on reload.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628
2019-07-23 14:53:24 +00:00
0ee13672ee appDisplay: Add open method to FolderIcon
At the moment the only way to open a folder icon is to click on it;
there's no API to open the icon programmatically.

This commits adds an open method and makes the click handler use
it.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628
2019-07-23 14:53:24 +00:00
49260a85ad appDisplay: Stop watching FolderIcon parent view when destroyed
When a FolderIcon is opened, it asks the parent view to allocate
space for it, which takes time.  Eventually, the space-ready
signal is emitted on the view and the icon can make use of the new
space with its popup.  If the icon gets destroyed in the
interim, though, space-ready signal handler still fires.

This commit disconnects the signal handler so it doesn't get called
on a destroyed icon.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628
2019-07-23 14:53:24 +00:00
da9f37e629 appDisplay: Add destroy handler for FolderIcon
It is important that the FolderView of a FolderIcon always
gets destroyed before the AppFolderPopup, since the view
may or may not be in the popup, and the view should
get cleaned up exactly once in either case.

This commit adds a destroy handler on FolderIcon to ensure
things get taken down in the right order, and to make sure
the view isn't leaked if it's not yet part of the popup.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628
2019-07-23 14:53:24 +00:00
164f3fa3fd appDisplay: Clear AllView reference to current popup when destroyed
AllView contains a reference to the current popup that lingers after
the popup is destroyed.

This commit fixes that, by explicitly nullifying when appropriate.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628
2019-07-23 14:53:24 +00:00
8e75d81a44 appDisplay: Add AppFolderPopup destroy handler
At the moment AppFolderPopup calls popdown on destruction,
which leads to open-state-changed getting emitted after
the actor associated with the popup is destroyed.

This commit handles ungrabbing and closing from an
actor destroy handler to side-step the open-state-changed
signal.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628
2019-07-23 14:53:24 +00:00
1d60c4d9d4 iconGrid: Clear meta_later callback on destruction
The IconGrid code sometimes sets up a callback to be invoked
later right before being destroyed.

This commit adds a destroy handler to cancel the callback.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628
2019-07-23 14:53:24 +00:00
eaa32090b9 appDisplay: Don't leak duplicate items in AppView
If an icon already exists in an app view with the same id, the
duplicate is not added on a call to addItem.  Unfortunately,
since it's not added, the icon actor gets orphaned and leaked.

This commit address the problem by introducing a new hasItem
method and disallowing callers to call addItem with a duplicate
in the first place.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628
2019-07-23 14:53:24 +00:00
32ddb6f739 iconGrid: Add missing import for GLib
To silence warnings:

JS ERROR: ReferenceError: GLib is not defined
_updateIconSizes@resource:///org/gnome/shell/ui/iconGrid.js:771:9

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1462
2019-07-23 09:29:56 +00:00
2653402c5c js: Add missing return values to later_add() handlers
meta_later_add() is modelled after g_idle_add() and friends, and
the handler's boolean return value determines whether it should
be scheduled again or removed. There are some places where we omit
the return value, add them (although the implicit return value of
"undefined" already gives us the intended result).

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/637
2019-07-22 20:48:16 +02:00
2743f18af4 panel: Use dedicated Spinner class
The startup/busy indication in the app menu was left out of commit
22e21ad7d1 because it doesn't use a hard-coded image, but as the
image in the CSS is actually the same used by the spinner class,
drop the "custom" styling and use the regular spinner.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/636
2019-07-22 17:50:52 +02:00
dd1fdf88ff extensionPrefs: Add missing return value
When commit a7ec7583aa merged two functions into one, it left
out the expected return value from one branch.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/635
2019-07-22 13:18:55 +00:00
2a041e9d8d lint: Enforce consistent return
While all javascript functions have a return value - either an explicit
one from a return statement, or an implicit "undefined" - mixing both in
the same function is almost certainly an oversight, and more often than
not a bug.

Enable the corresponding eslint rule to catch those errors.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/635
2019-07-22 13:18:55 +00:00
1117f4760c Update Brazilian Portuguese translation 2019-07-22 02:49:59 +00:00
7dda7abf5e Update Basque translation 2019-07-21 18:27:36 +00:00
7ca3cca306 build: Bump gsettings-desktop-schemas requirement
... for the new disable-hot-corners setting.
2019-07-20 18:25:29 +02:00
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
361 changed files with 77166 additions and 28875 deletions

3
.eslintrc.yml Normal file
View File

@ -0,0 +1,3 @@
extends:
- ./lint/eslintrc-gjs.yml
- ./lint/eslintrc-shell.yml

View File

@ -1,6 +1,5 @@
stages:
- review
- source_check
- build
- test
@ -15,7 +14,7 @@ variables:
- merge_requests
check_commit_log:
image: registry.gitlab.gnome.org/gnome/mutter/master:v2
image: registry.gitlab.gnome.org/gnome/mutter/master:v3
stage: review
variables:
GIT_DEPTH: "100"
@ -26,28 +25,36 @@ check_commit_log:
js_check:
image: registry.gitlab.gnome.org/gnome/gnome-shell/extension-ci:v1
stage: source_check
stage: review
script:
- find js -name '*.js' -exec js60 -c -s '{}' ';' 2>&1 | tee $JS_LOG
- (! grep -q . $JS_LOG)
<<: *only_default
only:
changes:
- js/**/*
artifacts:
paths:
- ${JS_LOG}
when: on_failure
eslint:
image: registry.gitlab.gnome.org/gnome/gnome-shell/extension-ci:v1
stage: review
script:
- ./.gitlab-ci/run-eslint.sh
<<: *only_default
artifacts:
paths:
- reports
when: always
build:
image: registry.gitlab.gnome.org/gnome/mutter/master:v2
image: registry.gitlab.gnome.org/gnome/mutter/master:v3
stage: build
before_script:
- .gitlab-ci/checkout-mutter.sh
- meson mutter mutter/build --prefix=/usr -Dtests=false
- ninja -C mutter/build install
script:
- meson . build -Dbuiltype=debugoptimized
- meson . build -Dbuiltype=debugoptimized -Dman=false --werror
- ninja -C build
- ninja -C build install
<<: *only_default
@ -58,12 +65,15 @@ build:
- build
test:
image: registry.gitlab.gnome.org/gnome/mutter/master:v2
image: registry.gitlab.gnome.org/gnome/mutter/master:v3
stage: test
variables:
XDG_RUNTIME_DIR: "$CI_PROJECT_DIR/runtime-dir"
NO_AT_BRIDGE: "1"
before_script:
- ninja -C mutter/build install
script:
- xvfb-run meson test -C build --no-rebuild
- dbus-run-session -- xvfb-run meson test -C build --no-rebuild
<<: *only_default
artifacts:
expire_in: 1 day
@ -72,7 +82,7 @@ test:
when: on_failure
test-pot:
image: registry.gitlab.gnome.org/gnome/mutter/master:v2
image: registry.gitlab.gnome.org/gnome/mutter/master:v3
stage: test
before_script:
- ninja -C mutter/build install

View File

@ -1,7 +1,7 @@
FROM registry.fedoraproject.org/fedora:latest
RUN dnf -y update && dnf -y upgrade && \
dnf install -y 'dnf-command(copr)' && \
dnf install -y 'dnf-command(copr)' git && \
# For syntax checks with `find . -name '*.js' -exec js60 -c -s '{}' ';'`
dnf install -y findutils mozjs60-devel && \

View File

@ -1,6 +1,5 @@
#!/usr/bin/bash
shell_branch=$(git describe --contains --all HEAD)
mutter_target=
git clone https://gitlab.gnome.org/GNOME/mutter.git
@ -26,8 +25,7 @@ if [ "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" ]; then
fi
if [ -z "$mutter_target" ]; then
mutter_target=$(git branch -r -l origin/$shell_branch)
mutter_target=${mutter_target:-$(git branch -r -l ${shell_branch#remotes/})}
mutter_target=$(git branch -r -l origin/$CI_COMMIT_REF_NAME)
mutter_target=${mutter_target:-origin/master}
echo Using $mutter_target instead
fi

114
.gitlab-ci/run-eslint.sh Executable file
View File

@ -0,0 +1,114 @@
#!/usr/bin/env bash
OUTPUT_REGULAR=reports/lint-regular-report.txt
OUTPUT_LEGACY=reports/lint-legacy-report.txt
OUTPUT_FINAL=reports/lint-common-report.txt
OUTPUT_MR=reports/lint-mr-report.txt
LINE_CHANGES=changed-lines.txt
is_empty() {
(! grep -q . $1)
}
run_eslint() {
ARGS_LEGACY='--config lint/eslintrc-legacy.yml'
local extra_args=ARGS_$1
local output_var=OUTPUT_$1
local output=${!output_var}
# ensure output exists even if eslint doesn't report any errors
mkdir -p $(dirname $output)
touch $output
eslint -f unix ${!extra_args} -o $output js
}
list_commit_range_additions() {
# Turn raw context-less git-diff into a list of
# filename:lineno pairs of new (+) lines
git diff -U0 "$@" -- js |
awk '
BEGIN { file=""; }
/^+++ b/ { file=substr($0,7); }
/^@@ / {
len = split($3,a,",")
start=a[1]
count=(len > 1) ? a[2] : 1
for (line=start; line<start+count; line++)
printf "%s/%s:%d:\n",ENVIRON["PWD"],file,line;
}'
}
copy_matched_lines() {
local source=$1
local matches=$2
local target=$3
echo -n > $target
for l in $(<$matches); do
grep $l $source >> $target
done
}
create_common() {
# comm requires sorted input;
# we also strip the error message to make the following a "common" error:
# regular:
# file.js:42:23 Indentation of 55, expected 42
# legacy:
# file.js:42:23 Indentation of 55, extected 24
prepare() {
sed 's: .*::' $1 | sort
}
comm -12 <(prepare $OUTPUT_REGULAR) <(prepare $OUTPUT_LEGACY) >$OUTPUT_FINAL.tmp
# Now add back the stripped error messages
copy_matched_lines $OUTPUT_REGULAR $OUTPUT_FINAL.tmp $OUTPUT_FINAL
rm $OUTPUT_FINAL.tmp
}
# Disable MR handling for now. We aren't ready to enforce
# non-legacy style just yet ...
unset CI_MERGE_REQUEST_TARGET_BRANCH_NAME
REMOTE=${1:-$CI_MERGE_REQUEST_PROJECT_URL.git}
BRANCH_NAME=${2:-$CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
if [ "$BRANCH_NAME" ]; then
git fetch $REMOTE $BRANCH_NAME
branch_point=$(git merge-base HEAD FETCH_HEAD)
commit_range=$branch_point...HEAD
list_commit_range_additions $commit_range > $LINE_CHANGES
# Don't bother with running lint when no JS changed
if is_empty $LINE_CHANGES; then
exit 0
fi
fi
echo Generating lint report using regular configuration
run_eslint REGULAR
echo Generating lint report using legacy configuration
run_eslint LEGACY
echo Done.
create_common
if ! is_empty $OUTPUT_FINAL; then
cat $OUTPUT_FINAL
exit 1
fi
# Just show the report and succeed when not testing a MR
if [ -z "$BRANCH_NAME" ]; then
exit 0
fi
copy_matched_lines $OUTPUT_REGULAR $LINE_CHANGES $OUTPUT_MR
cat $OUTPUT_MR
is_empty $OUTPUT_MR

View File

@ -84,7 +84,6 @@ don't use.
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
const Util = imports.misc.util;
```
The alphabetical ordering should be done independently of the location of the
@ -164,11 +163,17 @@ you to inherit from a type to use it, you can do so:
return [100, 100];
}
vfunc_paint() {
vfunc_paint(paintContext) {
let framebuffer = paintContext.get_framebuffer();
let coglContext = framebuffer.get_context();
let alloc = this.get_allocation_box();
Cogl.set_source_color4ub(255, 0, 0, 255);
Cogl.rectangle(alloc.x1, alloc.y1,
alloc.x2, alloc.y2);
let pipeline = new Cogl.Pipeline(coglContext);
pipeline.set_color4ub(255, 0, 0, 255);
framebuffer.draw_rectangle(pipeline,
alloc.x1, alloc.y1,
alloc.x2, alloc.y2);
}
});
```
@ -187,15 +192,27 @@ and "double quotes" for strings that the user may see. This allows us to
quickly find untranslated or mistranslated strings by grepping through the
sources for double quotes without a gettext call around them.
## `actor` and `_delegate`
## `actor` (deprecated) and `_delegate`
gjs allows us to set so-called "expando properties" on introspected objects,
allowing us to treat them like any other. Because the Shell was built before
you could inherit from GTypes natively in JS, we usually have a wrapper class
that has a property called `actor`. We call this wrapper class the "delegate".
you could inherit from GTypes natively in JS, in some cases we have a wrapper
class that has a property called `actor` (now deprecated). We call this
wrapper class the "delegate".
We sometimes use expando properties to set a property called `_delegate` on
the actor itself:
```javascript
var MyActor = GObject.registerClass(
class MyActor extends Clutter.Actor {
_init(params) {
super._init(params);
this._delegate = this;
}
});
```
Or using the deprecated `actor`:
```javascript
var MyClass = class {
constructor() {
@ -216,6 +233,7 @@ delegate object from an associated actor. For instance, the drag and drop
system calls the `handleDragOver` function on the delegate of a "drop target"
when the user drags an item over it. If you do not set the `_delegate`
property, your actor will not be able to be dropped onto.
In case the class is an actor itself, the `_delegate` can be just set to `this`.
## Functional style
@ -277,34 +295,49 @@ If your usage of an object is like a hash table (and thus conceptually the keys
can have special chars in them), don't use quotes, but use brackets: `{ bar: 42
}`, `foo['bar']`.
## Getters, setters, and Tweener
## Animations
Most objects that are animated are actors, and most properties used in animations
are animatable, which means they can use implicit animations:
Getters and setters should be used when you are dealing with an API that is
designed around setting properties, like Tweener. If you want to animate an
arbitrary property, create a getter and setter, and use Tweener to animate the
property.
```javascript
var ANIMATION_TIME = 2000;
var MyClass = class {
constructor() {
this.actor = new St.BoxLayout();
this._position = 0;
}
get position() {
return this._position;
}
set position(value) {
this._position = value;
this.actor.set_position(value, value);
}
};
let myThing = new MyClass();
Tweener.addTween(myThing,
{ position: 100,
time: ANIMATION_TIME,
transition: 'easeOutQuad' });
moveActor(actor, x, y) {
actor.ease({
x,
y,
duration: 500, // ms
mode: Clutter.AnimationMode.EASE_OUT_QUAD
});
}
```
The above is a convenience wrapper around the actual Clutter API, and should generally
be preferred over the more verbose:
```javascript
moveActor(actor, x, y) {
actor.save_easing_state();
actor.set_easing_duration(500);
actor.set_easing_mode(Clutter.AnimationMode.EASE_OUT_QUAD);
actor.set({
x,
y
});
actor.restore_easing_state();
}
```
There is a similar convenience API around Clutter.PropertyTransition to animate
actor (or actor meta) properties that cannot use implicit animations:
```javascript
desaturateActor(actor, desaturate) {
let factor = desaturate ? 1.0 : 0.0;
actor.ease_property('@effects.desaturate.factor', factor, {
duration: 500, // ms
mode: Clutter.AnimationMode.EASE_OUT_QUAD
});
}
```

235
NEWS
View File

@ -1,3 +1,238 @@
3.35.3
======
* Add discrete GPU support for NVidia drivers [Bastien; #1810]
* Fix DND of window previews with tablet devices [Carlos; !897]
* Update pad OSD actions dynamically on mode changes [Carlos; !898]
* st: Add dedicated PasswordEntry widget [Umang; !619]
* Allow stand-alone builds of gnome-extensions tool [Florian; !877]
* extension-tool: Don't treat missing .js handler as error [Chuck; !905]
* Disallow top bar menus without top bar [Florian; #2002]
* Misc. bug fixes and cleanups [Georges, Florian, Robert, Umang; !901,
#789937, !909, !910, !911, !913, !916]
Contributors:
Michael Catanzaro, Chuck, Carlos Garnacho, Umang Jain, Robert Mader,
Florian Müllner, Georges Basile Stavracas Neto, Bastien Nocera
Translators:
Fabio Tomat [fur], Fran Dieguez [gl], Jordi Mas [ca], Daniel Mustieles [es]
3.35.2
======
* Fix unredirection after cancelled animations [Florian; #1788]
* Include shadow in window screenshots [Robert; !762]
* Show indicator when microphone is active [Florian; !729]
* Use inheritance instead of delegate pattern [Marco; !559]
* Use cached coordinates for window sorting in overview [Andrew; !763]
* Wiggle login/unlock password entries on failure [Georges; !769]
* Update window titles in app menu [Florian; #1830]
* Fix window animations getting stuck by workspace switches [Jonas D.; !784]
* Fix not-responding dialog size when using geometry scaling [Jonas D.; !783]
* Handle buggy MPRIS clients more gracefully [Philip; #1362]
* Deprecate StBoxLayout's child properties [Florian; !780]
* Remove StBin's align properties [Florian; !803]
* Use correct timezones for events [Milan, Florian; !806, #1895]
* Reduce overhead of tracking stylesheet changes [Carlos; !779]
* Replace action icons in system menu with regular menu items [Florian; #270]
* Refine polkit dialogs [Jonas D.; !788]
* Fix battery icon glitch in "100% but charging" case [Philip; !814]
* Fix windows getting stuck on screen if closed while animating [Florian; !815]
* Use font from interface settings [Florian; #688288]
* Show polkit confirmation dialog for users with no password
[Joaquim, Jonas D.; !829]
* Use better OSK layout fallback for unsupported variants [Florian; #1907]
* Hide stopped spinner in top bar [Joonas; !832]
* Reuse existing icons when updating the app picker grid [Georges; !841]
* Show switcher popups immediately on second key press [Florian; #1928]
* Add position-based animation to page indicators [Alexander; !843]
* Improve modifier-less keyboard navigation of switcher popups [Florian; #1883]
* Improve weather integration [Florian; #1927, #1926]
* Add back sound feedback when scrolling volume indicator [Florian; #53]
* Fix creating app folders with no pre-existing folders [Jonas D.; #1652]
* Improve DND page switching in app picker [Florian, Jonas D.; #1693]
* Fix disable command of gnome-extensions tool [Florian; #1946]
* Tweak styling of notifications/media constrols [Joonas; !855, !865]
* Enable clean session shutdown after gnome-shell failure [Benjamin; !858]
* Also remove scaled keys when texture cache is cleared [Daniel M.; !567]
* Don't show overflow indicator in switchers that fit screen [Florian; #1834]
* Move libcroco dependency in-tree [Federico; !861]
* Move to app folder location when it is created/renamed [Georges; !883]
* Dismiss switcher popups when a system modal dialogs opens [Florian; #1536]
* Fix weather forecasts for automatic location when Weather is not sandboxed
[Florian; #1823]
* Place launched applications into a systemd scope [Benjamin; !863]
* Fixed crashes [Jonas D., Carlos; !787, !813]
* Misc. bug fixes and cleanups [Marco, Georges, Daniel V., Florian, Robert,
Kalev, Philip, Jonas D., Will, Carlos, Jonas Å., cunidev, Joonas, Federico;
!747, !765, !421, !759, !749, !730, !770, #1799, !774, !773, !776, !777,
!782, !794, !778, !792, !790, !190, !796, !795, !797, !798, !800, !804, !808,
!807, !810, !811, !563, !809, !805, !817, !818, !822, !830, !828, !823, !835,
!840, !842, !833, !845, !846, !847, !851, #1916, !862, !866, #1979, !827,
#1976, !884, !873, !885, !799, !887, !891, !816]
Contributors:
Marco Trevisan (Treviño), Benjamin Berg, Philip Chimento, Milan Crha,
Jonas Dreßler, Carlos Garnacho, Joonas Henriksson, Kalev Lember, Robert Mader,
Alexander Mikhaylenko, Daniel García Moreno, Florian Müllner,
Georges Basile Stavracas Neto, Federico Mena Quintero, Joaquim Rocha,
Will Thompson, Daniel van Vugt, Andrew Watson, cunidev, Jonas Ådahl
Translators:
Daniel Mustieles [es], Goran Vidović [hr], Fabio Tomat [fur],
Danial Behzadi [fa], Andika Triwidada [id], Efstathios Iosifidis [el],
Ricardo Silva Veloso [pt_BR]
3.35.1
======
* Misc. bug fixes and cleanups [Marco; Matthias; !758, #701212]
Contributors:
Marco Trevisan (Treviño)
3.34.1
======
* Fix "Frequent" view icons disappearing on hover [Jonas D.; #1502]
* Allow editing app folder names [Georges, Marco; !675, !720]
* Skip property transitions while hidden [Florian; !708]
* Make menu animations more consistent [Florian, GB_2; #1595, !717]
* Improve performance when enabling/disabling all extensions [Jonas D.; !96]
* Fix extra icons appearing in "Frequent" view animation [Georges; !696]
* Fix fading out desktop icons [Harshula; #1616]
* Fix box-shadow glitch with prerendered resources [Daniel; #1186]
* Fix accidentally skipped animations [Florian; #1572]
* Fix screenshots and window animations when scaled [Robert; !728]
* Don't leak NOTIFY_SOCKET environment variable to applications [Benjamin; !741]
* Fix lock-up on X11 when ibus is already running on startup [Marco; #1712]
* Fix screen dimming on idle [Marco; #1683]
* Do not notify systemd before initialization is complete [Iain; !750]
* Support SAE secrets in network agent [Lubomir; !751]
* Fix various regressions with dynamic workspaces [Florian; #1497]
* Fixed crashes [Florian, Marco; #1678, !746]
* Misc. bug fixes and cleanups [Marco, Jonas D., Florian, Iain, Georges,
Jonas Å., Martin, Takao, Carlos; !700, !705, !709, !711, !707, #1538, !710,
!713, !699, !715, !718, !716, !719, !721, #1243, !725, !731, #1614, !683,
!732, !121, !735, !736, !740, #573, #1641, #1571]
Contributors:
Marco Trevisan (Treviño), Benjamin Berg, Jonas Dreßler, Takao Fujiwara, GB_2,
Carlos Garnacho, Harshula Jayasuriya, Iain Lane, Robert Mader,
Daniel García Moreno, Florian Müllner, Georges Basile Stavracas Neto,
Lubomir Rintel, Martin Zurowietz, Jonas Ådahl
Translators:
Rafael Fontenelle [pt_BR], Fran Dieguez [gl], Balázs Úr [hu],
Milo Casagrande [it], Daniel Șerbănescu [ro], Kukuh Syafaat [id],
Jiri Grönroos [fi], Daniel Mustieles [es], Piotr Drąg [pl],
Anders Jonsson [sv], Marek Černocký [cs], Jordi Mas [ca],
Aurimas Černius [lt], Christian Kirbach [de], Emin Tufan Çetin [tr],
Enrico Nicoletto [pt_BR], Danial Behzadi [fa], Марко Костић [sr],
Alexandre Franke [fr], Charles Monzat [fr], Kjartan Maraas [nb],
Ryuta Fujii [ja], Nathan Follens [nl], Dušan Kazik [sk], Fabio Tomat [fur],
Matej Urbančič [sl], Ask Hjorth Larsen [da], Alan Mortensen [da]
3.34.0
======
* Handle startup/shutdown of misc X11 services [Carlos; !680]
* Fix sound volume mute/unmute [Iain; #1557]
* Correctly terminate pasted text [Carlos; #1570]
Contributors:
Carlos Garnacho, Iain Lane
Translators:
Tom Tryfonidis [el], Milo Casagrande [it], Ryuta Fujii [ja],
Efstathios Iosifidis [el], Carmen Bianca BAKKER [eo], Sabri Ünal [tr],
Dušan Kazik [sk], Balázs Meskó [hu], Claude Paroz [fr]
3.33.92
=======
* Animate pointer a11y pie timer [Jonas D.; !688]
* Fix restarting shell in systemd user session [Benjamin; !690]
* Misc. bug fixes and cleanups [Florian, Jonas D., Jonas Å., Will;
!691, !689, !692, #1552, !698]
Contributors:
Jonas Ådahl, Benjamin Berg, Piotr Drąg, Jonas Dreßler, Florian Müllner,
Will Thompson
Translators:
Daniel Șerbănescu [ro], Danial Behzadi [fa], Daniel Mustieles [es],
Jiri Grönroos [fi], Asier Sarasua Garmendia [eu], Piotr Drąg [pl],
Rūdolfs Mazurs [lv], Anders Jonsson [sv], Fran Dieguez [gl], Jordi Mas [ca],
Matej Urbančič [sl], Zander Brown [en_GB], Ryuta Fujii [ja], Tim Sabsch [de],
Fabio Tomat [fur], Pawan Chitrakar [ne], A S Alam [pa], Changwoo Ryu [ko],
Aurimas Černius [lt], Daniel Rusek [cs], Marek Černocký [cs],
Kukuh Syafaat [id], Goran Vidović [hr], Rafael Fontenelle [pt_BR]
3.33.91
=======
* Fix regression when adjusting brightness [Florian; #1500]
* Fix pointer a11y timeout animation [Jonas D.; #1533]
* Add new extensions CLI tool [Florian; #1234]
* Only track top-level windows [Carlos; #556]
* Misc. bug fixes and cleanups [Jonas D., Jonas Å., Piotr, Florian;
!678, !682, !686]
Contributors:
Jonas Ådahl, Jonas Dreßler, Carlos Garnacho, Florian Müllner
Translators:
Asier Sarasua Garmendia [eu], Sveinn í Felli [is], Anders Jonsson [sv],
Jordi Mas [ca], Kukuh Syafaat [id], Florentina Mușat [ro], Jiri Grönroos [fi],
Aurimas Černius [lt], Daniel Mustieles [es], Piotr Drąg [pl],
Danial Behzadi [fa]
3.33.90
=======
* Implement DND app picker folder management [Georges; !643, !645, !664, !671]
* Make Clocks/Weather integration work with sandboxed apps [Florian; #1158]
* Support startup via systemd user instance [Benjamin; !507]
* Replace Tweener with Clutter animations [Florian; !663, !22, !666, !668, !669]
* Minimize travel distance in overview animation [Sergey; !267]
* Rescan icon theme when installed apps changed [Georges; !661]
* Consistently animate new window actions [Jonas; !662, !673]
* Misc. bug fixes and cleanups [Florian, Daniel, Ray, Bastien, Jonas, Niels,
Marco, Georges; !635, !636, !637, #1462, !628, !640, !641, !627, !644, !647,
!385, #1474, !651, #1144, !646, !653, !652, !655, #1482, !656, $654, !665,
!667, !670, #1357, !672, !657, #1507, !674, !677]
Contributors:
Benjamin Berg, Sergey Bugaev, Jonas Dreßler, Niels De Graef, Florian Müllner,
Georges Basile Stavracas Neto, Bastien Nocera, Ray Strode,
Marco Trevisan (Treviño), verdre, Daniel van Vugt
Translators:
Asier Sarasua Garmendia [eu], Rafael Fontenelle [pt_BR],
Kristjan SCHMIDT [eo], Jor Teron [mjw], Daniel Mustieles [es],
Kukuh Syafaat [id], Jordi Mas [ca], Fabio Tomat [fur], Daniel Șerbănescu [ro],
Anders Jonsson [sv]
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

@ -1,5 +1,46 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<!--
net.hadess.SwitcherooControl:
@short_description: D-Bus proxy to access dual-GPU controls.
After checking the availability of two switchable GPUs in the machine,
check the value of net.hadess.SwitcherooControl.HasDualGpu to see
if running applications on the discrete GPU should be offered.
The object path will be "/net/hadess/SwitcherooControl".
-->
<interface name="net.hadess.SwitcherooControl">
<!--
HasDualGpu:
Whether two switchable GPUs are present on the system. This property
has been obsoleted in favour of the "NumGPUs" property.
-->
<property name="HasDualGpu" type="b" access="read"/>
<!--
NumGPUs:
The number of GPUs available on the system. Note that while having no
GPUs is unlikely, consumers of this API should probably not throw errors
if that were the case.
-->
<property name="NumGPUs" type="u" access="read"/>
<!--
GPUs:
An array of key-pair values representing each GPU. The key named "Name" (s)
will contain a user-facing name for the GPU, the "Environment" (as) key will
contain an array of even number of strings, each being an environment
variable to set to use the GPU, followed by its value, the "Default" (b) key
will tag the default (usually integrated) GPU.
-->
<property name="GPUs" type="aa{sv}" access="read"/>
</interface>
</node>

View File

@ -0,0 +1,15 @@
<node>
<!--
org.gnome.Shell.ClocksIntegration:
@short_description: Clocks integration interface
The interface used for exporting location settings to GNOME Shell's
world clocks integration.
-->
<interface name="org.gnome.Shell.ClocksIntegration">
<property name="Locations" type="av" access="read"/>
</interface>
</node>

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

@ -0,0 +1,16 @@
<node>
<!--
org.gnome.Shell.WeatherIntegration:
@short_description: Weather integration interface
The interface used for exporting location settings to GNOME Shell's
weather integration.
-->
<interface name="org.gnome.Shell.WeatherIntegration">
<property name="AutomaticLocation" type="b" access="read"/>
<property name="Locations" type="av" access="read"/>
</interface>
</node>

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

@ -40,6 +40,7 @@
<file preprocess="xml-stripblanks">org.gnome.SettingsDaemon.Wacom.xml</file>
<file preprocess="xml-stripblanks">org.gnome.Shell.AudioDeviceSelection.xml</file>
<file preprocess="xml-stripblanks">org.gnome.Shell.CalendarServer.xml</file>
<file preprocess="xml-stripblanks">org.gnome.Shell.ClocksIntegration.xml</file>
<file preprocess="xml-stripblanks">org.gnome.Shell.Extensions.xml</file>
<file preprocess="xml-stripblanks">org.gnome.Shell.Introspect.xml</file>
<file preprocess="xml-stripblanks">org.gnome.Shell.HotplugSniffer.xml</file>
@ -48,6 +49,7 @@
<file preprocess="xml-stripblanks">org.gnome.Shell.Screencast.xml</file>
<file preprocess="xml-stripblanks">org.gnome.Shell.Screenshot.xml</file>
<file preprocess="xml-stripblanks">org.gnome.Shell.Wacom.PadOsd.xml</file>
<file preprocess="xml-stripblanks">org.gnome.Shell.WeatherIntegration.xml</file>
<file preprocess="xml-stripblanks">org.gnome.Shell.xml</file>
<file preprocess="xml-stripblanks">org.Gtk.MountOperationHandler.xml</file>
<file preprocess="xml-stripblanks">org.gtk.Notifications.xml</file>

View File

@ -0,0 +1,15 @@
[Unit]
Description=Disable GNOME Shell extensions after failure
# Note that this unit must not conflict with anything, and must
# be able to run in parallel with the gnome-session-shutdown.target.
DefaultDependencies=no
# We want to disable extensions only if gnome-shell has flagged the extensions
# to be a likely cause of trouble.
ConditionPathExists=%t/gnome-shell-disable-extensions
[Service]
Type=simple
# Disable extensions
ExecStart=gsettings set org.gnome.shell disable-user-extensions true
Restart=no

View File

@ -20,6 +20,8 @@
<file>no-notifications.svg</file>
<file>noise-texture.png</file>
<file>pad-osd.css</file>
<file alias="icons/eye-open-negative-filled-symbolic.svg">eye-open-negative-filled-symbolic.svg</file>
<file alias="icons/eye-not-looking-symbolic.svg">eye-not-looking-symbolic.svg</file>
<file alias="icons/pointer-double-click-symbolic.svg">pointer-double-click-symbolic.svg</file>
<file alias="icons/pointer-drag-symbolic.svg">pointer-drag-symbolic.svg</file>
<file alias="icons/pointer-primary-click-symbolic.svg">pointer-primary-click-symbolic.svg</file>

View File

@ -0,0 +1,27 @@
[Unit]
Description=GNOME Shell on Wayland
# On wayland, force a session shutdown
OnFailure=gnome-shell-disable-extensions.service gnome-session-shutdown.target
OnFailureJobMode=replace-irreversibly
CollectMode=inactive-or-failed
RefuseManualStart=on
RefuseManualStop=on
After=gnome-session-manager.target
Requisite=gnome-session-initialized.target
PartOf=gnome-session-initialized.target
Before=gnome-session-initialized.target
# The units already conflict because they use the same BusName
#Conflicts=gnome-shell-x11.service
[Service]
Type=notify
ExecStart=@bindir@/gnome-shell
# Exit code 1 means we are probably *not* dealing with an extension failure
SuccessExitStatus=1
# On wayland we cannot restart
Restart=no
# Kill any stubborn child processes after this long
TimeoutStopSec=5

View File

@ -1,5 +1,10 @@
[Unit]
Description=GNOME Shell (wayland sync point)
After=gnome-shell.service
BindsTo=gnome-shell.service
Conflicts=gnome-shell-x11.target
Description=GNOME Shell on Wayland
DefaultDependencies=no
Requisite=gnome-session-initialized.target
PartOf=gnome-session-initialized.target
Before=gnome-session-initialized.target
Requires=gnome-shell-wayland.service
After=gnome-shell-wayland.service

View File

@ -0,0 +1,33 @@
[Unit]
Description=GNOME Shell on X11
# On X11, try to show the GNOME Session Failed screen
OnFailure=gnome-shell-disable-extensions.service gnome-session-failed.target
OnFailureJobMode=replace
CollectMode=inactive-or-failed
RefuseManualStart=on
RefuseManualStop=on
After=gnome-session-manager.target
Requisite=gnome-session-initialized.target
PartOf=gnome-session-initialized.target
Before=gnome-session-initialized.target
# The units already conflict because they use the same BusName
#Conflicts=gnome-shell-wayland.service
# Limit startup frequency more than the default
StartLimitIntervalSec=15s
StartLimitBurst=3
[Service]
Type=notify
ExecStart=@bindir@/gnome-shell
# Exit code 1 means we are probably *not* dealing with an extension failure
SuccessExitStatus=1
# On X11 we want to restart on-success (Alt+F2 + r) and on-failure.
Restart=always
# Do not wait before restarting the shell
RestartSec=0ms
# Kill any stubborn child processes after this long
TimeoutStopSec=5

View File

@ -1,5 +1,10 @@
[Unit]
Description=GNOME Shell (x11 sync point)
After=gnome-shell.service
BindsTo=gnome-shell.service
Conflicts=gnome-shell-wayland.target
Description=GNOME Shell on X11
DefaultDependencies=no
Requisite=gnome-session-initialized.target
PartOf=gnome-session-initialized.target
Before=gnome-session-initialized.target
Requires=gnome-shell-x11.service
After=gnome-shell-x11.service

View File

@ -1,11 +0,0 @@
[Unit]
Description=GNOME Shell
Wants=gnome-session.service
After=graphical-session-pre.target gnome-session-bus.target
PartOf=graphical-session.target
[Service]
Type=dbus
ExecStart=@bindir@/gnome-shell
Restart=on-failure
BusName=org.gnome.Shell

View File

@ -14,6 +14,8 @@ desktopconf = configuration_data()
# file when built in a non-system prefix
desktopconf.set('bindir', bindir)
desktopconf.set('VERSION', meson.project_version())
desktopconf.set('systemd_hidden', have_systemd ? 'true' : 'false')
foreach desktop_file : desktop_files
i18n.merge_file('desktop',
input: configure_file(
@ -22,7 +24,7 @@ foreach desktop_file : desktop_files
configuration: desktopconf
),
output: desktop_file,
po_dir: '../po',
po_dir: po_dir,
install: true,
install_dir: desktopdir,
type: 'desktop'
@ -98,15 +100,23 @@ if have_systemd
unitconf = configuration_data()
unitconf.set('bindir', bindir)
unit = configure_file(
input: 'gnome-shell.service.in',
output: 'gnome-shell.service',
configure_file(
input: 'gnome-shell-x11.service.in',
output: 'gnome-shell-x11.service',
configuration: unitconf,
install_dir: systemduserunitdir
)
units = files('gnome-shell-wayland.target',
'gnome-shell-x11.target')
configure_file(
input: 'gnome-shell-wayland.service.in',
output: 'gnome-shell-wayland.service',
configuration: unitconf,
install_dir: systemduserunitdir
)
units = files('gnome-shell-x11.target',
'gnome-shell-wayland.target',
'gnome-shell-disable-extensions.service')
install_data(units, install_dir: systemduserunitdir)
endif

View File

@ -14,3 +14,4 @@ X-GNOME-Autostart-Phase=DisplayServer
X-GNOME-Provides=panel;windowmanager;
X-GNOME-Autostart-Notify=true
X-GNOME-AutoRestart=false
X-GNOME-HiddenUnderSystemd=@systemd_hidden@

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>
@ -39,7 +50,7 @@
</description>
</key>
<key name="favorite-apps" type="as">
<default>[ 'epiphany.desktop', 'evolution.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop' ]</default>
<default>[ 'org.gnome.Epiphany.desktop', 'org.gnome.Evolution.desktop', 'rhythmbox.desktop', 'org.gnome.Shotwell.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop' ]</default>
<summary>List of desktop file IDs for favorite applications</summary>
<description>
The applications corresponding to these identifiers
@ -139,11 +150,6 @@
Keybinding to focus the active notification.
</description>
</key>
<key name="pause-resume-tweens" type="as">
<default>[]</default>
<summary>Keybinding that pauses and resumes all running tweens, for debugging purposes</summary>
<description></description>
</key>
<key name="switch-to-application-1" type="as">
<default>["&lt;Super&gt;1"]</default>
<summary>Switch to application 1</summary>
@ -222,6 +228,36 @@
</key>
</schema>
<schema id="org.gnome.shell.world-clocks" path="/org/gnome/shell/world-clocks/"
gettext-domain="@GETTEXT_PACKAGE@">
<key name="locations" type="av">
<summary>Locations</summary>
<description>
The locations to show in world clocks
</description>
<default>[]</default>
</key>
</schema>
<schema id="org.gnome.shell.weather" path="/org/gnome/shell/weather/"
gettext-domain="@GETTEXT_PACKAGE@">
<key name="automatic-location" type="b">
<summary>Automatic location</summary>
<description>
Whether to fetch the current location or not
</description>
<default>false</default>
</key>
<key name="locations" type="av">
<summary>Location</summary>
<description>
The location for which to show a forecast
</description>
<default>[]</default>
</key>
</schema>
<!-- unused, change 00_org.gnome.shell.gschema.override instead -->
<schema id="org.gnome.shell.overrides" path="/org/gnome/shell/overrides/"
gettext-domain="@GETTEXT_PACKAGE@">

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path d="M13.98 1.99a1 1 0 0 0-.687.303l-.984.984A8 8 0 0 0 8 2 8 8 0 0 0 .262 8.01a8 8 0 0 0 2.943 4.37l-.912.913a1 1 0 1 0 1.414 1.414l11-11a1 1 0 0 0-.727-1.717zM8 4a4 4 0 0 1 2.611.974l-1.42 1.42A2 2 0 0 0 8 6a2 2 0 0 0-2 2 2 2 0 0 0 .396 1.19l-1.42 1.42A4 4 0 0 1 4 8a4 4 0 0 1 4-4zm7.03 2.209l-3.344 3.343a4 4 0 0 1-2.127 2.127l-2.28 2.28a8 8 0 0 0 .721.04 8 8 0 0 0 7.738-6.01 8 8 0 0 0-.709-1.78zm-7.53.79a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#2e3436"/>
</svg>

After

Width:  |  Height:  |  Size: 572 B

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="16" viewBox="0 0 16 16" version="1.1" id="svg7384" height="16">
<metadata id="metadata90">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title>Gnome Symbolic Icon Theme</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<title id="title9167">Gnome Symbolic Icon Theme</title>
<defs id="defs7386">
<linearGradient osb:paint="solid" id="linearGradient7212">
<stop style="stop-color:#000000;stop-opacity:1;" offset="0" id="stop7214"/>
</linearGradient>
</defs>
<g transform="translate(-341.0002,-13.000323)" style="display:inline" id="layer9"/>
<g transform="translate(-100,-380.00032)" id="layer1"/>
<g transform="translate(-100,-380.00032)" style="display:inline" id="layer10">
<path d="m 108,382 a 8,8 0 0 0 -7.73828,6.00977 A 8,8 0 0 0 108,394 8,8 0 0 0 115.73828,387.99023 8,8 0 0 0 108,382 Z m 0,2 a 4,4 0 0 1 4,4 4,4 0 0 1 -4,4 4,4 0 0 1 -4,-4 4,4 0 0 1 4,-4 z" id="path2314" style="opacity:1;vector-effect:none;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal"/>
<path id="path2318" d="m 110,388.00003 a 2,2 0 0 1 -2,2 2,2 0 0 1 -2,-2 2,2 0 0 1 2,-2 2,2 0 0 1 2,2 z" style="vector-effect:none;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
</g>
<g transform="translate(-100,-380.00032)" id="g6387"/>
<g transform="translate(-100,-380.00032)" id="layer11"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -36,10 +36,8 @@ $_hover_bg_color: lighten($bg_color,if($variant=='light', 5%, 3%));
$_active_bg_color: if($variant == 'light', darken($bg_color, 14%), darken($bg_color, 9%));
$font-size: 11;
$font-family: Cantarell, Sans-Serif;
stage {
font-family: $font-family;
@include fontsize($font-size);
color: $fg_color;
}
@ -99,6 +97,11 @@ StEntry {
warning-color: $warning_color;
padding: 0 4px;
}
StIcon.peek-password {
icon-size: 1.09em;
padding: 0 4px;
}
}
@ -406,6 +409,11 @@ StScrollBar {
padding-bottom: 8px;
}
.prompt-dialog-caps-lock-warning {
@extend .prompt-dialog-error-label;
padding-left: 6.2em;
}
.prompt-dialog-info-label {
font-size: 10pt;
padding-bottom: 8px;
@ -610,6 +618,13 @@ StScrollBar {
border-bottom-style: solid;
}
// Rename popup
.rename-folder-popup {
.rename-folder-popup-item {
spacing: 6px;
&:ltr, &:rtl { padding: 0, 12px; }
}
}
// Background menu
.background-menu { -boxpointer-gap: 4px; -arrow-rise: 0px; }
@ -619,6 +634,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,8 +755,9 @@ StScrollBar {
spacing: 8px;
}
.ws-switcher-active-up, .ws-switcher-active-down {
height: 50px;
.ws-switcher-active-up, .ws-switcher-active-down,
.ws-switcher-active-left, .ws-switcher-active-right {
height: 52px;
background-color: $selected_bg_color;
color: $selected_fg_color;
background-size: 32px;
@ -929,7 +957,7 @@ StScrollBar {
.world-clocks-button,
.weather-button,
.events-section-title {
&:hover, focus { background-color: $_hover_bg_color }
&:hover, &:focus { background-color: $_hover_bg_color }
&:active { background-color: $_active_bg_color }
}
@ -959,6 +987,7 @@ StScrollBar {
spacing-columns: 0.8em;
}
.weather-header-box,
.weather-box {
spacing: 0.4em;
}
@ -1000,7 +1029,7 @@ StScrollBar {
background-color: transparent;
width: 32px;
border-radius: 4px;
&:hover, focus { background-color: $_hover_bg_color; }
&:hover, &:focus { background-color: $_hover_bg_color; }
&:active { background-color: transparentize($fg_color, 0.84); }
}
@ -1016,7 +1045,7 @@ StScrollBar {
margin: 2px;
border-radius: 1.4em;
font-feature-settings: "tnum";
&:hover, focus { background-color: $_hover_bg_color; }
&:hover, &:focus { background-color: $_hover_bg_color; }
&:active,&:selected {
color: lighten($selected_fg_color,5%);
background-color: $selected_bg_color;
@ -1041,9 +1070,9 @@ StScrollBar {
}
.calendar-today {
font-weight: bold;
//color: lighten($fg_color,10%);
//background-color: darken($bg_color,5%);
border: 1px solid $_bubble_borders_color;
color: lighten($fg_color,5%);
background-color: darken($bg_color,5%);
// border: 1px solid lighten($_bubble_borders_color,20%);
}
.calendar-day-with-events {
color: lighten($fg_color,10%);
@ -1133,14 +1162,21 @@ StScrollBar {
padding: 10px;
}
.message-close-button {
color: lighten($fg_color, 15%);
&:hover { color: if($variant=='light', lighten($fg_color, 30%), darken($fg_color, 10%)); }
&:active { color: if($variant=='light', lighten($fg_color, 40%), darken($fg_color, 20%)); }
}
.message-media-control {
padding: 12px;
color: lighten($fg_color, 15%);
&:last-child:ltr { padding-right: 18px; }
&:last-child:rtl { padding-left: 18px; }
&:hover { color: $fg_color; }
&:insensitive { color: darken($fg_color,40%); }
&:hover { color: if($variant=='light', lighten($fg_color, 30%), darken($fg_color, 10%)); }
&:active { color: if($variant=='light', lighten($fg_color, 40%), darken($fg_color, 20%)); }
&:insensitive { color: if($variant=='light', lighten($fg_color, 50%), darken($fg_color, 40%)); }
}
.media-message-cover-icon {
@ -1158,7 +1194,6 @@ StScrollBar {
// a little unstructured mess:
#appMenu {
spinner-image: url("resource:///org/gnome/shell/theme/process-working.svg");
spacing: 4px;
.label-shadow { color: transparent; }
@ -1171,7 +1206,8 @@ StScrollBar {
.aggregate-menu {
min-width: 21em;
.popup-menu-icon { padding: 0 4px; }
.popup-menu-icon { padding: 0 4px;
-st-icon-style: symbolic; }
.popup-sub-menu .popup-menu-item > :first-child {
&:ltr { /* 12px spacing + 2*4px padding */
padding-left: 20px; margin-left: 1.09em; }
@ -1180,27 +1216,6 @@ StScrollBar {
}
}
.system-menu-action {
-st-icon-style: symbolic;
color: $fg_color;
border-radius: 32px; /* wish we could do 50% */
padding: 13px;
border: 1px solid $_bubble_borders_color;
&:hover, &:focus {
background-color: $_hover_bg_color;
color: $fg_color;
border: none;
padding: 14px;
}
&:active {
background-color: $selected_bg_color;
color: $selected_fg_color;
}
& > StIcon { icon-size: 16px; }
}
// Activities Ripples
.ripple-box {
width: 52px;
@ -1326,8 +1341,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 +1379,8 @@ StScrollBar {
//search results
#searchResultsBin {
max-width: 1000px;
}
#searchResultsContent {
max-width: 1000px;
padding-left: 20px;
padding-right: 20px;
spacing: 16px;
@ -1484,11 +1496,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 +1509,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 +1517,13 @@ StScrollBar {
border-image: none;
background-image: none;
}
&:drop .overview-icon {
background-color: transparentize($selected_bg_color,.15);
}
&:active .overview-icon,
&:checked .overview-icon {
background-color: transparentize(darken($osd_bg_color,10%), 0.5);
}
}
.app-well-app-running-dot { //running apps indicator
@ -1553,20 +1567,14 @@ StScrollBar {
}
.page-indicator {
padding: 15px 20px;
padding: 7px 16px;
.page-indicator-icon {
width: 12px;
height: 12px;
background-color: transparent;
border: 2px solid rgba(255, 255, 255, 0.4);
border-radius: 12px;
background-color: white;
border-radius: 6px;
}
&:hover .page-indicator-icon { border-color: white; }
&:active .page-indicator-icon { border: none; margin: 2px; background-color: white; }
&:checked .page-indicator-icon,
&:checked:active .page-indicator-icon { background-color: white;}
}
.no-frequent-applications-label { @extend %status_text; }
@ -1599,7 +1607,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
@ -1767,8 +1774,8 @@ StScrollBar {
padding: 4px 4px;
.page-indicator-icon {
width: 6px;
height: 6px
width: 8px;
height: 8px;
}
}
}

View File

@ -28,7 +28,7 @@ foreach iface : ifaces
output: 'doc-gen-' + iface[1],
command: [
'gdbus-codegen',
'--interface-prefix=@0@.'.format(iface),
'--interface-prefix=@0@.'.format(iface[0]),
'--generate-docbook', 'doc-gen',
'--output-directory', '@OUTDIR@',
'@INPUT@'

View File

@ -31,34 +31,34 @@ its dependencies to build from tarballs.</description>
<programming-language>JavaScript</programming-language>
<programming-language>C</programming-language>
<maintainer>
<author>
<foaf:Person>
<foaf:name>William Jon McCann</foaf:name>
<foaf:mbox rdf:resource="mailto:jmccann@redhat.com" />
<gnome:userid>mccann</gnome:userid>
</foaf:Person>
</maintainer>
<maintainer>
</author>
<author>
<foaf:Person>
<foaf:name>Owen Taylor</foaf:name>
<foaf:mbox rdf:resource="mailto:otaylor@redhat.com" />
<gnome:userid>otaylor</gnome:userid>
</foaf:Person>
</maintainer>
<maintainer>
</author>
<author>
<foaf:Person>
<foaf:name>Colin Walters</foaf:name>
<foaf:mbox rdf:resource="mailto:walters@verbum.org" />
<gnome:userid>walters</gnome:userid>
</foaf:Person>
</maintainer>
<maintainer>
</author>
<author>
<foaf:Person>
<foaf:name>Marina Zhurakhinskaya</foaf:name>
<foaf:mbox rdf:resource="mailto:marinaz@redhat.com" />
<gnome:userid>marinaz</gnome:userid>
</foaf:Person>
</maintainer>
</author>
<maintainer>
<foaf:Person>
<foaf:name>Florian Müllner</foaf:name>
@ -66,4 +66,11 @@ its dependencies to build from tarballs.</description>
<gnome:userid>fmuellner</gnome:userid>
</foaf:Person>
</maintainer>
<maintainer>
<foaf:Person>
<foaf:name>Georges Basile Stavracas Neto</foaf:name>
<foaf:mbox rdf:resource="mailto:gbsneto@gnome.org" />
<gnome:userid>gbsneto</gnome:userid>
</foaf:Person>
</maintainer>
</Project>

View File

@ -1,3 +1,7 @@
/* exported main */
imports.gi.versions.Gdk = '3.0';
imports.gi.versions.Gtk = '3.0';
const Gettext = imports.gettext;
const { Gdk, GLib, Gio, GObject, Gtk, Pango } = imports.gi;
const Format = imports.format;
@ -8,6 +12,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 +23,53 @@ function stripPrefix(string, prefix) {
return string;
}
var Application = class {
constructor() {
var Application = GObject.registerClass(
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
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;
@ -96,25 +81,27 @@ var Application = class {
dialog.set_default_size(600, 400);
dialog.add(widget);
dialog.show();
return true;
}
_buildErrorUI(extension, exc) {
_buildErrorUI(row, exc) {
let scroll = new Gtk.ScrolledWindow({
hscrollbar_policy: Gtk.PolicyType.NEVER,
propagate_natural_height: true
propagate_natural_height: true,
});
let box = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 12,
margin: 100,
margin_bottom: 60
margin_bottom: 60,
});
scroll.add(box);
let label = new Gtk.Label({
label: '<span size="x-large">%s</span>'.format(_("Somethings gone wrong")),
use_markup: true
use_markup: true,
});
label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
box.add(label);
@ -122,13 +109,13 @@ var Application = class {
label = new Gtk.Label({
label: _("Were very sorry, but theres been a problem: the settings for this extension cant be displayed. We recommend that you report the issue to the extension authors."),
justify: Gtk.Justification.CENTER,
wrap: true
wrap: true,
});
box.add(label);
let expander = new Expander({
label: _("Technical Details"),
margin_top: 12
margin_top: 12,
});
box.add(expander);
@ -139,14 +126,14 @@ var Application = class {
let buffer = new Gtk.TextBuffer({ text: errortext });
let textview = new Gtk.TextView({
buffer: buffer,
buffer,
wrap_mode: Gtk.WrapMode.WORD,
monospace: true,
editable: false,
top_margin: 12,
bottom_margin: 12,
left_margin: 12,
right_margin: 12
right_margin: 12,
});
let toolbar = new Gtk.Toolbar();
@ -162,7 +149,7 @@ var Application = class {
let copyButton = new Gtk.ToolButton({
icon_name: 'edit-copy-symbolic',
tooltip_text: _("Copy Error")
tooltip_text: _("Copy Error"),
});
toolbar.add(copyButton);
@ -170,16 +157,16 @@ 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:`,
'```', // '`' (xgettext throws up on odd number of backticks)
`${exc}`,
'```',
'```', // '`'
'',
'Stack trace:',
'```',
'```', // '`'
exc.stack.replace(/\n$/, ''), // stack without trailing newline
'```',
''
'```', // '`'
'',
];
clipboard.set_text(lines.join('\n'), -1);
});
@ -192,17 +179,17 @@ 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({
orientation: Gtk.Orientation.VERTICAL
orientation: Gtk.Orientation.VERTICAL,
});
expandedBox.add(textview);
expandedBox.add(toolbar);
@ -213,8 +200,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);
@ -232,7 +219,7 @@ var Application = class {
Gio.SettingsBindFlags.INVERT_BOOLEAN);
this._mainStack = new Gtk.Stack({
transition_type: Gtk.StackTransitionType.CROSSFADE
transition_type: Gtk.StackTransitionType.CROSSFADE,
});
this._window.add(this._mainStack);
@ -248,18 +235,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 +253,48 @@ 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 extension = ExtensionUtils.deserializeExtension(newState);
let row = this._findExtensionRow(uuid);
if (row) {
if (extension.state === ExtensionState.UNINSTALLED)
row.destroy();
return; // we only deal with new and deleted extensions here
}
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 +307,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 +337,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: {
@ -340,20 +352,20 @@ var Expander = GObject.registerClass({
'label', 'label', 'label',
GObject.ParamFlags.READWRITE,
null
)
}
),
},
}, class Expander extends Gtk.Box {
_init(params = {}) {
this._labelText = null;
super._init(Object.assign(params, {
orientation: Gtk.Orientation.VERTICAL,
spacing: 0
spacing: 0,
}));
this._frame = new Gtk.Frame({
shadow_type: Gtk.ShadowType.IN,
hexpand: true
hexpand: true,
});
let eventBox = new Gtk.EventBox();
@ -361,12 +373,12 @@ var Expander = GObject.registerClass({
let hbox = new Gtk.Box({
spacing: 6,
margin: 12
margin: 12,
});
eventBox.add(hbox);
this._arrow = new Gtk.Image({
icon_name: 'pan-end-symbolic'
icon_name: 'pan-end-symbolic',
});
hbox.add(this._arrow);
@ -376,7 +388,7 @@ var Expander = GObject.registerClass({
this._revealer = new Gtk.Revealer();
this._childBin = new Gtk.Frame({
shadow_type: Gtk.ShadowType.IN
shadow_type: Gtk.ShadowType.IN,
});
this._revealer.add(this._childBin);
@ -394,7 +406,7 @@ var Expander = GObject.registerClass({
this._gesture = new Gtk.GestureMultiPress({
widget: this._frame,
button: 0,
exclusive: true
exclusive: true,
});
this._gesture.connect('released', (gesture, nPress) => {
if (nPress == 1)
@ -439,7 +451,7 @@ class EmptyPlaceholder extends Gtk.Box {
super._init({
orientation: Gtk.Orientation.VERTICAL,
spacing: 6,
margin: 32
margin: 32,
});
let image = new Gtk.Image({
@ -447,15 +459,15 @@ class EmptyPlaceholder extends Gtk.Box {
pixel_size: 96,
visible: true,
vexpand: true,
valign: Gtk.Align.END
valign: Gtk.Align.END,
});
image.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
this.add(image);
let label = new Gtk.Label({
label: `<b><span size="x-large">${_("No Extensions Installed" )}</span></b>`,
label: `<b><span size="x-large">${_("No Extensions Installed")}</span></b>`,
use_markup: true,
visible: true
visible: true,
});
label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
this.add(label);
@ -470,9 +482,9 @@ class EmptyPlaceholder extends Gtk.Box {
visible: true,
max_width_chars: 50,
hexpand: true,
vexpand: (appInfo == null),
vexpand: appInfo == null,
halign: Gtk.Align.CENTER,
valign: Gtk.Align.START
valign: Gtk.Align.START,
});
this.add(desc);
@ -480,14 +492,14 @@ class EmptyPlaceholder extends Gtk.Box {
let button = new Gtk.Button({
label: _("Browse in Software"),
image: new Gtk.Image({
icon_name: "org.gnome.Software-symbolic"
icon_name: "org.gnome.Software-symbolic",
}),
always_show_image: true,
margin_top: 12,
visible: true,
halign: Gtk.Align.CENTER,
valign: Gtk.Align.START,
vexpand: true
vexpand: true,
});
this.add(button);
@ -499,6 +511,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 +552,59 @@ 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.connect('destroy', this._onDestroy.bind(this));
this._buildUI();
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.block_signal_handler(this._notifyActiveId);
this._switch.state = state;
this._switch.unblock_signal_handler(this._notifyActiveId);
this._switch.sensitive = this._canToggle();
});
}
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 extension = ExtensionUtils.extensions[this.uuid];
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
hexpand: true, margin_end: 24, spacing: 24,
margin: 12 });
@ -544,19 +614,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 +637,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.connect('notify::active', () => {
this._switch = new Gtk.Switch({
valign: Gtk.Align.CENTER,
sensitive: this._canToggle(),
state: this._extension.state === ExtensionState.ENABLED,
});
this._notifyActiveId = 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() {
// give extension prefs access to their own extension object
ExtensionUtils.getCurrentExtension = () => this._extension;
_enable() {
let extensions = this._settings.get_strv('enabled-extensions');
if (extensions.includes(this.uuid))
return;
if (!this._prefsModule) {
ExtensionUtils.installImporter(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;
}
});
@ -626,7 +683,7 @@ function initEnvironment() {
log(`ERROR: ${s}`);
},
userdatadir: GLib.build_filenamev([GLib.get_user_data_dir(), 'gnome-shell'])
userdatadir: GLib.build_filenamev([GLib.get_user_data_dir(), 'gnome-shell']),
};
String.prototype.format = Format.format;
@ -638,6 +695,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

@ -1,41 +1,58 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported AuthPrompt */
const { Clutter, Pango, Shell, St } = imports.gi;
const Signals = imports.signals;
const { Clutter, GObject, Pango, Shell, St } = imports.gi;
const Animation = imports.ui.animation;
const Batch = imports.gdm.batch;
const GdmUtil = imports.gdm.util;
const Util = imports.misc.util;
const Params = imports.misc.params;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener;
const UserWidget = imports.ui.userWidget;
var DEFAULT_BUTTON_WELL_ICON_SIZE = 16;
var DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0;
var DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3;
var DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1000;
var DEFAULT_BUTTON_WELL_ANIMATION_TIME = 300;
var MESSAGE_FADE_OUT_ANIMATION_TIME = 0.5;
var MESSAGE_FADE_OUT_ANIMATION_TIME = 500;
const WIGGLE_OFFSET = 6;
const WIGGLE_DURATION = 65;
const N_WIGGLES = 3;
var AuthPromptMode = {
UNLOCK_ONLY: 0,
UNLOCK_OR_LOG_IN: 1
UNLOCK_OR_LOG_IN: 1,
};
var AuthPromptStatus = {
NOT_VERIFYING: 0,
VERIFYING: 1,
VERIFICATION_FAILED: 2,
VERIFICATION_SUCCEEDED: 3
VERIFICATION_SUCCEEDED: 3,
};
var BeginRequestType = {
PROVIDE_USERNAME: 0,
DONT_PROVIDE_USERNAME: 1
DONT_PROVIDE_USERNAME: 1,
};
var AuthPrompt = class {
constructor(gdmClient, mode) {
var AuthPrompt = GObject.registerClass({
Signals: {
'cancelled': {},
'failed': {},
'next': {},
'prompted': {},
'reset': { param_types: [GObject.TYPE_UINT] },
},
}, class AuthPrompt extends St.BoxLayout {
_init(gdmClient, mode) {
super._init({
style_class: 'login-dialog-prompt-layout',
vertical: true,
});
this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
this._gdmClient = gdmClient;
@ -47,7 +64,7 @@ var AuthPrompt = class {
else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN)
reauthenticationOnly = false;
this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly });
this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly });
this._userVerifier.connect('ask-question', this._onAskQuestion.bind(this));
this._userVerifier.connect('show-message', this._onShowMessage.bind(this));
@ -61,70 +78,80 @@ var AuthPrompt = class {
this.connect('next', () => {
this.updateSensitivity(false);
this.startSpinning();
if (this._queryingService) {
if (this._queryingService)
this._userVerifier.answerQuery(this._queryingService, this._entry.text);
} else {
else
this._preemptiveAnswer = this._entry.text;
}
});
this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
vertical: true });
this.actor.connect('destroy', this._onDestroy.bind(this));
this.actor.connect('key-press-event', (actor, event) => {
if (event.get_key_symbol() == Clutter.KEY_Escape)
this.cancel();
return Clutter.EVENT_PROPAGATE;
this.connect('destroy', this._onDestroy.bind(this));
this._userWell = new St.Bin({
x_expand: true,
y_expand: true,
});
this.add_child(this._userWell);
this._label = new St.Label({
style_class: 'login-dialog-prompt-label',
x_expand: false,
y_expand: true,
});
this._userWell = new St.Bin({ x_fill: true,
x_align: St.Align.START });
this.actor.add(this._userWell,
{ x_align: St.Align.START,
x_fill: true,
y_fill: true,
expand: true });
this._label = new St.Label({ style_class: 'login-dialog-prompt-label' });
this.add_child(this._label);
this.actor.add(this._label,
{ expand: true,
x_fill: false,
y_fill: true,
x_align: St.Align.START });
this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
can_focus: true });
ShellEntry.addContextMenu(this._entry, { isPassword: true, actionMode: Shell.ActionMode.NONE });
let entryParams = {
style_class: 'login-dialog-prompt-entry',
can_focus: true,
x_expand: false,
y_expand: true,
};
this.actor.add(this._entry,
{ expand: true,
x_fill: true,
y_fill: false,
x_align: St.Align.START });
this._entry = null;
this._textEntry = new St.Entry(entryParams);
ShellEntry.addContextMenu(this._textEntry, { actionMode: Shell.ActionMode.NONE });
this._passwordEntry = new St.PasswordEntry(entryParams);
ShellEntry.addContextMenu(this._passwordEntry, { actionMode: Shell.ActionMode.NONE });
this._entry = this._passwordEntry;
this.add_child(this._entry);
this._entry.grab_key_focus();
this._message = new St.Label({ opacity: 0,
styleClass: 'login-dialog-message' });
this._capsLockWarningLabel = new ShellEntry.CapsLockWarning();
this.add_child(this._capsLockWarningLabel);
this._message = new St.Label({
opacity: 0,
styleClass: 'login-dialog-message',
x_expand: false,
y_expand: true,
y_align: Clutter.ActorAlign.START,
});
this._message.clutter_text.line_wrap = true;
this._message.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this.actor.add(this._message, { x_fill: false, x_align: St.Align.START, y_align: St.Align.START });
this.add_child(this._message);
this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box',
vertical: false });
this.actor.add(this._buttonBox,
{ expand: true,
x_align: St.Align.MIDDLE,
y_align: St.Align.END });
this._buttonBox = new St.BoxLayout({
style_class: 'login-dialog-button-box',
vertical: false,
y_align: Clutter.ActorAlign.END,
});
this.add_child(this._buttonBox);
this._defaultButtonWell = new St.Widget({ layout_manager: new Clutter.BinLayout() });
this._defaultButtonWellActor = null;
this._defaultButtonWell = new St.Widget({
layout_manager: new Clutter.BinLayout(),
x_align: Clutter.ActorAlign.END,
y_align: Clutter.ActorAlign.CENTER,
});
this._initButtons();
this._spinner = new Animation.Spinner(DEFAULT_BUTTON_WELL_ICON_SIZE);
this._spinner.actor.opacity = 0;
this._spinner.actor.show();
this._defaultButtonWell.add_child(this._spinner.actor);
this._spinner.opacity = 0;
this._spinner.show();
this._defaultButtonWell.add_child(this._spinner);
}
_onDestroy() {
@ -132,39 +159,39 @@ var AuthPrompt = class {
this._userVerifier = null;
}
_initButtons() {
this.cancelButton = new St.Button({ style_class: 'modal-dialog-button button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
label: _("Cancel") });
this.cancelButton.connect('clicked', () => this.cancel());
this._buttonBox.add(this.cancelButton,
{ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.END });
vfunc_key_press_event(keyPressEvent) {
if (keyPressEvent.keyval == Clutter.KEY_Escape)
this.cancel();
return Clutter.EVENT_PROPAGATE;
}
this._buttonBox.add(this._defaultButtonWell,
{ expand: true,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
this.nextButton = new St.Button({ style_class: 'modal-dialog-button button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
label: _("Next") });
_initButtons() {
this.cancelButton = new St.Button({
style_class: 'modal-dialog-button button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
label: _("Cancel"),
x_expand: true,
x_align: Clutter.ActorAlign.START,
y_align: Clutter.ActorAlign.END,
});
this.cancelButton.connect('clicked', () => this.cancel());
this._buttonBox.add_child(this.cancelButton);
this._buttonBox.add_child(this._defaultButtonWell);
this.nextButton = new St.Button({
style_class: 'modal-dialog-button button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
label: _("Next"),
x_align: Clutter.ActorAlign.END,
y_align: Clutter.ActorAlign.END,
});
this.nextButton.connect('clicked', () => this.emit('next'));
this.nextButton.add_style_pseudo_class('default');
this._buttonBox.add(this.nextButton,
{ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.END });
this._buttonBox.add_child(this.nextButton);
this._updateNextButtonSensitivity(this._entry.text.length > 0);
@ -180,7 +207,18 @@ var AuthPrompt = class {
});
}
_onAskQuestion(verifier, serviceName, question, passwordChar) {
_updateEntry(secret) {
if (secret && (this._entry != this._passwordEntry)) {
this.replace_child(this._entry, this._passwordEntry);
this._entry = this._passwordEntry;
} else if (!secret && (this._entry != this._textEntry)) {
this.replace_child(this._entry, this._textEntry);
this._entry = this._textEntry;
}
this._capsLockWarningLabel.visible = secret;
}
_onAskQuestion(verifier, serviceName, question, secret) {
if (this._queryingService)
this.clear();
@ -190,10 +228,11 @@ var AuthPrompt = class {
this._preemptiveAnswer = null;
return;
}
this.setPasswordChar(passwordChar);
this._updateEntry(secret);
this.setQuestion(question);
if (passwordChar) {
if (secret) {
if (this._userVerifier.reauthenticating)
this.nextButton.label = _("Unlock");
else
@ -242,6 +281,12 @@ var AuthPrompt = class {
this.updateSensitivity(canRetry);
this.setActorInDefaultButtonWell(null);
this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED;
Util.wiggle(this._entry, {
offset: WIGGLE_OFFSET,
duration: WIGGLE_DURATION,
wiggleCount: N_WIGGLES,
});
}
_onVerificationComplete() {
@ -267,16 +312,16 @@ var AuthPrompt = class {
let oldActor = this._defaultButtonWellActor;
if (oldActor)
Tweener.removeTweens(oldActor);
oldActor.remove_all_transitions();
let wasSpinner;
if (oldActor == this._spinner.actor)
if (oldActor == this._spinner)
wasSpinner = true;
else
wasSpinner = false;
let isSpinner;
if (actor == this._spinner.actor)
if (actor == this._spinner)
isSpinner = true;
else
isSpinner = false;
@ -290,18 +335,18 @@ var AuthPrompt = class {
this._spinner.stop();
}
} else {
Tweener.addTween(oldActor,
{ opacity: 0,
time: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
transition: 'linear',
onComplete: () => {
if (wasSpinner) {
if (this._spinner)
this._spinner.stop();
}
}
});
oldActor.ease({
opacity: 0,
duration: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
mode: Clutter.AnimationMode.LINEAR,
onComplete: () => {
if (wasSpinner) {
if (this._spinner)
this._spinner.stop();
}
},
});
}
}
@ -309,21 +354,23 @@ var AuthPrompt = class {
if (isSpinner)
this._spinner.play();
if (!animate)
if (!animate) {
actor.opacity = 255;
else
Tweener.addTween(actor,
{ opacity: 255,
time: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
transition: 'linear' });
} else {
actor.ease({
opacity: 255,
duration: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
mode: Clutter.AnimationMode.LINEAR,
});
}
}
this._defaultButtonWellActor = actor;
}
startSpinning() {
this.setActorInDefaultButtonWell(this._spinner.actor, true);
this.setActorInDefaultButtonWell(this._spinner, true);
}
stopSpinning() {
@ -335,11 +382,6 @@ var AuthPrompt = class {
this.stopSpinning();
}
setPasswordChar(passwordChar) {
this._entry.clutter_text.set_password_char(passwordChar);
this._entry.menu.isPassword = passwordChar != '';
}
setQuestion(question) {
this._label.set_text(question);
@ -365,12 +407,12 @@ var AuthPrompt = class {
_fadeOutMessage() {
if (this._message.opacity == 0)
return;
Tweener.removeTweens(this._message);
Tweener.addTween(this._message,
{ opacity: 0,
time: MESSAGE_FADE_OUT_ANIMATION_TIME,
transition: 'easeOutQuad'
});
this._message.remove_all_transitions();
this._message.ease({
opacity: 0,
duration: MESSAGE_FADE_OUT_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
setMessage(message, type) {
@ -385,7 +427,7 @@ var AuthPrompt = class {
this._message.remove_style_class_name('login-dialog-message-hint');
if (message) {
Tweener.removeTweens(this._message);
this._message.remove_all_transitions();
this._message.text = message;
this._message.opacity = 255;
} else {
@ -404,9 +446,9 @@ var AuthPrompt = class {
this._entry.clutter_text.editable = sensitive;
}
hide() {
vfunc_hide() {
this.setActorInDefaultButtonWell(null, true);
this.actor.hide();
super.vfunc_hide();
this._message.opacity = 0;
this.setUser(null);
@ -422,7 +464,8 @@ var AuthPrompt = class {
if (user) {
let userWidget = new UserWidget.UserWidget(user);
this._userWell.set_child(userWidget.actor);
userWidget.x_align = Clutter.ActorAlign.START;
this._userWell.set_child(userWidget);
}
}
@ -501,11 +544,10 @@ var AuthPrompt = class {
}
cancel() {
if (this.verificationStatus == AuthPromptStatus.VERIFICATION_SUCCEEDED) {
if (this.verificationStatus == AuthPromptStatus.VERIFICATION_SUCCEEDED)
return;
}
this.reset();
this.emit('cancelled');
}
};
Signals.addSignalMethods(AuthPrompt.prototype);
});

View File

@ -20,7 +20,7 @@
* In order for transformation animations to look good, they need to be
* incremental and have some order to them (e.g., fade out hidden items,
* then shrink to close the void left over). Chaining animations in this way can
* be error-prone and wordy using just Tweener callbacks.
* be error-prone and wordy using just ease() callbacks.
*
* The classes in this file help with this:
*
@ -112,13 +112,12 @@ var Batch = class extends Task {
for (let i = 0; i < tasks.length; i++) {
let task;
if (tasks[i] instanceof Task) {
if (tasks[i] instanceof Task)
task = tasks[i];
} else if (typeof tasks[i] == 'function') {
else if (typeof tasks[i] == 'function')
task = new Task(scope, tasks[i]);
} else {
else
throw new Error('Batch tasks must be functions or Task, Hold or Batch objects');
}
this.tasks.push(task);
}
@ -129,9 +128,8 @@ var Batch = class extends Task {
}
runTask() {
if (!(this._currentTaskIndex in this.tasks)) {
if (!(this._currentTaskIndex in this.tasks))
return null;
}
return this.tasks[this._currentTaskIndex].run();
}
@ -179,9 +177,8 @@ var ConcurrentBatch = class extends Batch {
process() {
let hold = this.runTask();
if (hold) {
if (hold)
this.hold.acquireUntilAfter(hold);
}
// Regardless of the state of the just run task,
// fire off the next one, so all the tasks can run
@ -202,7 +199,6 @@ var ConsecutiveBatch = class extends Batch {
hold.disconnect(signalId);
this.nextTask();
});
return;
} else {
// This task finished, process the next one
this.nextTask();

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported FprintManager */
const Gio = imports.gi.Gio;
@ -19,7 +20,7 @@ function FprintManager() {
g_interface_info: FprintManagerInfo,
g_name: 'net.reactivated.Fprint',
g_object_path: '/net/reactivated/Fprint/Manager',
g_flags: (Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES });
try {
self.init(null);

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported LoginDialog */
/*
* Copyright 2011 Red Hat, Inc
*
@ -18,7 +19,6 @@
const { AccountsService, Atk, Clutter, Gdm, Gio,
GLib, GObject, Meta, Pango, Shell, St } = imports.gi;
const Signals = imports.signals;
const AuthPrompt = imports.gdm.authPrompt;
const Batch = imports.gdm.batch;
@ -30,81 +30,89 @@ const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Realmd = imports.gdm.realmd;
const Tweener = imports.ui.tweener;
const UserWidget = imports.ui.userWidget;
const _FADE_ANIMATION_TIME = 0.25;
const _SCROLL_ANIMATION_TIME = 0.5;
const _FADE_ANIMATION_TIME = 250;
const _SCROLL_ANIMATION_TIME = 500;
const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;
const _LOGO_ICON_HEIGHT = 48;
const _MAX_BOTTOM_MENU_ITEMS = 5;
var UserListItem = class {
constructor(user) {
var UserListItem = GObject.registerClass({
Signals: { 'activate': {} },
}, class UserListItem extends St.Button {
_init(user) {
let layout = new St.BoxLayout({
vertical: true,
x_align: Clutter.ActorAlign.START,
});
super._init({
style_class: 'login-dialog-user-list-item',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
can_focus: true,
x_expand: true,
child: layout,
reactive: true,
});
this.user = user;
this._userChangedId = this.user.connect('changed',
this._onUserChanged.bind(this));
let layout = new St.BoxLayout({ vertical: true });
this.actor = new St.Button({ style_class: 'login-dialog-user-list-item',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
can_focus: true,
child: layout,
reactive: true,
x_align: St.Align.START,
x_fill: true });
this.actor.connect('destroy', this._onDestroy.bind(this));
this.actor.connect('key-focus-in', () => {
this._setSelected(true);
});
this.actor.connect('key-focus-out', () => {
this._setSelected(false);
});
this.actor.connect('notify::hover', () => {
this._setSelected(this.actor.hover);
this.connect('destroy', this._onDestroy.bind(this));
this.connect('notify::hover', () => {
this._setSelected(this.hover);
});
this._userWidget = new UserWidget.UserWidget(this.user);
layout.add(this._userWidget.actor);
layout.add(this._userWidget);
this._userWidget.actor.bind_property('label-actor', this.actor, 'label-actor',
GObject.BindingFlags.SYNC_CREATE);
this._userWidget.bind_property('label-actor', this, 'label-actor',
GObject.BindingFlags.SYNC_CREATE);
this._timedLoginIndicator = new St.Bin({ style_class: 'login-dialog-timed-login-indicator',
scale_x: 0,
visible: false });
layout.add(this._timedLoginIndicator);
this.actor.connect('clicked', this._onClicked.bind(this));
this._onUserChanged();
}
vfunc_key_focus_in() {
super.vfunc_key_focus_in();
this._setSelected(true);
}
vfunc_key_focus_out() {
super.vfunc_key_focus_out();
this._setSelected(false);
}
_onUserChanged() {
this._updateLoggedIn();
}
_updateLoggedIn() {
if (this.user.is_logged_in())
this.actor.add_style_pseudo_class('logged-in');
this.add_style_pseudo_class('logged-in');
else
this.actor.remove_style_pseudo_class('logged-in');
this.remove_style_pseudo_class('logged-in');
}
_onDestroy() {
this.user.disconnect(this._userChangedId);
}
_onClicked() {
vfunc_clicked() {
this.emit('activate');
}
_setSelected(selected) {
if (selected) {
this.actor.add_style_pseudo_class('selected');
this.actor.grab_key_focus();
this.add_style_pseudo_class('selected');
this.grab_key_focus();
} else {
this.actor.remove_style_pseudo_class('selected');
this.remove_style_pseudo_class('selected');
}
}
@ -117,7 +125,7 @@ var UserListItem = class {
let startTime = GLib.get_monotonic_time();
this._timedLoginTimeoutId = GLib.timeout_add (GLib.PRIORITY_DEFAULT, 33,
this._timedLoginTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 33,
() => {
let currentTime = GLib.get_monotonic_time();
let elapsedTime = (currentTime - startTime) / GLib.USEC_PER_SEC;
@ -145,23 +153,33 @@ var UserListItem = class {
this._timedLoginIndicator.visible = false;
this._timedLoginIndicator.scale_x = 0.;
}
};
Signals.addSignalMethods(UserListItem.prototype);
});
var UserList = class {
constructor() {
this.actor = new St.ScrollView({ style_class: 'login-dialog-user-list-view' });
this.actor.set_policy(St.PolicyType.NEVER,
St.PolicyType.AUTOMATIC);
var UserList = GObject.registerClass({
Signals: {
'activate': { param_types: [UserListItem.$gtype] },
'item-added': { param_types: [UserListItem.$gtype] },
},
}, class UserList extends St.ScrollView {
_init() {
super._init({
style_class: 'login-dialog-user-list-view',
x_expand: true,
y_expand: true,
});
this.set_policy(St.PolicyType.NEVER,
St.PolicyType.AUTOMATIC);
this._box = new St.BoxLayout({ vertical: true,
style_class: 'login-dialog-user-list',
pseudo_class: 'expanded' });
this.actor.add_actor(this._box);
this.add_actor(this._box);
this._items = {};
}
this.actor.connect('key-focus-in', this._moveFocusToItems.bind(this));
vfunc_key_focus_in() {
this._moveFocusToItems();
}
_moveFocusToItems() {
@ -170,10 +188,10 @@ var UserList = class {
if (!hasItems)
return;
if (global.stage.get_key_focus() != this.actor)
if (global.stage.get_key_focus() != this)
return;
let focusSet = this.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
let focusSet = this.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
if (!focusSet) {
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
this._moveFocusToItems();
@ -194,27 +212,26 @@ var UserList = class {
for (let userName in this._items) {
let item = this._items[userName];
item.actor.sync_hover();
item.sync_hover();
}
}
scrollToItem(item) {
let box = item.actor.get_allocation_box();
let box = item.get_allocation_box();
let adjustment = this.actor.get_vscroll_bar().get_adjustment();
let adjustment = this.get_vscroll_bar().get_adjustment();
let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);
Tweener.removeTweens(adjustment);
Tweener.addTween (adjustment,
{ value: value,
time: _SCROLL_ANIMATION_TIME,
transition: 'easeOutQuad' });
adjustment.ease(value, {
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
duration: _SCROLL_ANIMATION_TIME,
});
}
jumpToItem(item) {
let box = item.actor.get_allocation_box();
let box = item.get_allocation_box();
let adjustment = this.actor.get_vscroll_bar().get_adjustment();
let adjustment = this.get_vscroll_bar().get_adjustment();
let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);
@ -252,14 +269,14 @@ var UserList = class {
this.removeUser(user);
let item = new UserListItem(user);
this._box.add(item.actor, { x_fill: true });
this._box.add_child(item);
this._items[userName] = item;
item.connect('activate', this._onItemActivated.bind(this));
// Try to keep the focused item front-and-center
item.actor.connect('key-focus-in', () => this.scrollToItem(item));
item.connect('key-focus-in', () => this.scrollToItem(item));
this._moveFocusToItems();
@ -280,33 +297,37 @@ var UserList = class {
if (!item)
return;
item.actor.destroy();
item.destroy();
delete this._items[userName];
}
numItems() {
return Object.keys(this._items).length;
}
};
Signals.addSignalMethods(UserList.prototype);
});
var SessionMenuButton = class {
constructor() {
var SessionMenuButton = GObject.registerClass({
Signals: { 'session-activated': { param_types: [GObject.TYPE_STRING] } },
}, class SessionMenuButton extends St.Bin {
_init() {
let gearIcon = new St.Icon({ icon_name: 'emblem-system-symbolic' });
this._button = new St.Button({ style_class: 'login-dialog-session-list-button',
reactive: true,
track_hover: true,
can_focus: true,
accessible_name: _("Choose Session"),
accessible_role: Atk.Role.MENU,
child: gearIcon });
let button = new St.Button({
style_class: 'login-dialog-session-list-button',
reactive: true,
track_hover: true,
can_focus: true,
accessible_name: _("Choose Session"),
accessible_role: Atk.Role.MENU,
child: gearIcon,
});
this.actor = new St.Bin({ child: this._button });
super._init({ child: button });
this._button = button;
let side = St.Side.TOP;
let align = 0;
if (Gdm.get_session_ids().length > _MAX_BOTTOM_MENU_ITEMS) {
if (this.actor.text_direction == Clutter.TextDirection.RTL)
if (this.text_direction == Clutter.TextDirection.RTL)
side = St.Side.RIGHT;
else
side = St.Side.LEFT;
@ -372,7 +393,7 @@ var SessionMenuButton = class {
}
for (let i = 0; i < ids.length; i++) {
let [sessionName, sessionDescription] = Gdm.get_session_name_and_description(ids[i]);
let [sessionName, sessionDescription_] = Gdm.get_session_name_and_description(ids[i]);
let id = ids[i];
let item = new PopupMenu.PopupMenuItem(sessionName);
@ -385,15 +406,13 @@ var SessionMenuButton = class {
});
}
}
};
Signals.addSignalMethods(SessionMenuButton.prototype);
});
var LoginDialog = GObject.registerClass({
Signals: { 'failed': {} },
}, class LoginDialog extends St.Widget {
_init(parentActor) {
super._init({ style_class: 'login-dialog',
visible: false });
super._init({ style_class: 'login-dialog', visible: false });
this.get_accessible().set_role(Atk.Role.WINDOW);
@ -427,38 +446,35 @@ var LoginDialog = GObject.registerClass({
this.add_child(this._userSelectionBox);
this._userList = new UserList();
this._userSelectionBox.add(this._userList.actor,
{ expand: true,
x_fill: true,
y_fill: true });
this._userSelectionBox.add_child(this._userList);
this._authPrompt = new AuthPrompt.AuthPrompt(this._gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN);
this._authPrompt.connect('prompted', this._onPrompted.bind(this));
this._authPrompt.connect('reset', this._onReset.bind(this));
this._authPrompt.hide();
this.add_child(this._authPrompt.actor);
this.add_child(this._authPrompt);
// translators: this message is shown below the user list on the
// login screen. It can be activated to reveal an entry for
// manually entering the username.
let notListedLabel = new St.Label({ text: _("Not listed?"),
style_class: 'login-dialog-not-listed-label' });
this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
can_focus: true,
child: notListedLabel,
reactive: true,
x_align: St.Align.START,
x_fill: true });
let notListedLabel = new St.Label({
text: _("Not listed?"),
style_class: 'login-dialog-not-listed-label',
x_align: Clutter.ActorAlign.START,
});
this._notListedButton = new St.Button({
style_class: 'login-dialog-not-listed-button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
can_focus: true,
child: notListedLabel,
reactive: true,
});
this._notListedButton.connect('clicked', this._hideUserListAskForUsernameAndBeginVerification.bind(this));
this._notListedButton.hide();
this._userSelectionBox.add(this._notListedButton,
{ expand: false,
x_align: St.Align.START,
x_fill: true });
this._userSelectionBox.add_child(this._notListedButton);
this._bannerView = new St.ScrollView({ style_class: 'login-dialog-banner-view',
opacity: 0,
@ -493,11 +509,11 @@ var LoginDialog = GObject.registerClass({
this._sessionMenuButton = new SessionMenuButton();
this._sessionMenuButton.connect('session-activated',
(list, sessionId) => {
this._greeter.call_select_session_sync (sessionId, null);
this._greeter.call_select_session_sync(sessionId, null);
});
this._sessionMenuButton.actor.opacity = 0;
this._sessionMenuButton.actor.show();
this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor);
this._sessionMenuButton.opacity = 0;
this._sessionMenuButton.show();
this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton);
this._disableUserList = undefined;
this._userListLoaded = false;
@ -518,7 +534,7 @@ var LoginDialog = GObject.registerClass({
_getBannerAllocation(dialogBox) {
let actorBox = new Clutter.ActorBox();
let [minWidth, minHeight, natWidth, natHeight] = this._bannerView.get_preferred_size();
let [, , natWidth, natHeight] = this._bannerView.get_preferred_size();
let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2;
actorBox.x1 = Math.floor(centerX - natWidth / 2);
@ -532,7 +548,7 @@ var LoginDialog = GObject.registerClass({
_getLogoBinAllocation(dialogBox) {
let actorBox = new Clutter.ActorBox();
let [minWidth, minHeight, natWidth, natHeight] = this._logoBin.get_preferred_size();
let [, , natWidth, natHeight] = this._logoBin.get_preferred_size();
let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2;
actorBox.x1 = Math.floor(centerX - natWidth / 2);
@ -546,7 +562,7 @@ var LoginDialog = GObject.registerClass({
_getCenterActorAllocation(dialogBox, actor) {
let actorBox = new Clutter.ActorBox();
let [minWidth, minHeight, natWidth, natHeight] = actor.get_preferred_size();
let [, , natWidth, natHeight] = actor.get_preferred_size();
let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2;
let centerY = dialogBox.y1 + (dialogBox.y2 - dialogBox.y1) / 2;
@ -580,8 +596,8 @@ var LoginDialog = GObject.registerClass({
let authPromptAllocation = null;
let authPromptWidth = 0;
if (this._authPrompt.actor.visible) {
authPromptAllocation = this._getCenterActorAllocation(dialogBox, this._authPrompt.actor);
if (this._authPrompt.visible) {
authPromptAllocation = this._getCenterActorAllocation(dialogBox, this._authPrompt);
authPromptWidth = authPromptAllocation.x2 - authPromptAllocation.x1;
}
@ -648,7 +664,7 @@ var LoginDialog = GObject.registerClass({
// figure out how tall it would like to be and try to accommodate
// but don't let it get too close to the logo
let [wideMinHeight, wideBannerHeight] = this._bannerView.get_preferred_height(wideBannerWidth);
let [, wideBannerHeight] = this._bannerView.get_preferred_height(wideBannerWidth);
let maxWideHeight = dialogHeight - 3 * logoHeight;
wideBannerHeight = Math.min(maxWideHeight, wideBannerHeight);
@ -686,12 +702,11 @@ var LoginDialog = GObject.registerClass({
}
// Finally hand out the allocations
if (bannerAllocation) {
if (bannerAllocation)
this._bannerView.allocate(bannerAllocation, flags);
}
if (authPromptAllocation)
this._authPrompt.actor.allocate(authPromptAllocation, flags);
this._authPrompt.allocate(authPromptAllocation, flags);
if (userSelectionAllocation)
this._userSelectionBox.allocate(userSelectionAllocation, flags);
@ -758,14 +773,15 @@ var LoginDialog = GObject.registerClass({
_fadeInBannerView() {
this._bannerView.show();
Tweener.addTween(this._bannerView,
{ opacity: 255,
time: _FADE_ANIMATION_TIME,
transition: 'easeOutQuad' });
this._bannerView.ease({
opacity: 255,
duration: _FADE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
_hideBannerView() {
Tweener.removeTweens(this._bannerView);
this._bannerView.remove_all_transitions();
this._bannerView.opacity = 0;
this._bannerView.hide();
}
@ -794,7 +810,7 @@ var LoginDialog = GObject.registerClass({
_onPrompted() {
if (this._shouldShowSessionMenuButton()) {
this._sessionMenuButton.updateSensitivity(true);
this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor);
this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton);
} else {
this._sessionMenuButton.updateSensitivity(false);
}
@ -803,9 +819,9 @@ var LoginDialog = GObject.registerClass({
_resetGreeterProxy() {
if (GLib.getenv('GDM_GREETER_TEST') != '1') {
if (this._greeter) {
if (this._greeter)
this._greeter.run_dispose();
}
this._greeter = this._gdmClient.get_greeter_sync(null);
this._defaultSessionChangedId = this._greeter.connect('default-session-name-changed',
@ -854,14 +870,15 @@ var LoginDialog = GObject.registerClass({
}
_showPrompt() {
if (this._authPrompt.actor.visible)
if (this._authPrompt.visible)
return;
this._authPrompt.actor.opacity = 0;
this._authPrompt.actor.show();
Tweener.addTween(this._authPrompt.actor,
{ opacity: 255,
time: _FADE_ANIMATION_TIME,
transition: 'easeOutQuad' });
this._authPrompt.opacity = 0;
this._authPrompt.show();
this._authPrompt.ease({
opacity: 255,
duration: _FADE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
this._fadeInBannerView();
}
@ -879,7 +896,6 @@ var LoginDialog = GObject.registerClass({
}
_askForUsernameAndBeginVerification() {
this._authPrompt.setPasswordChar('');
this._authPrompt.setQuestion(_("Username: "));
this._showRealmLoginHint(this._realmManager.loginFormat);
@ -905,26 +921,31 @@ var LoginDialog = GObject.registerClass({
this._showPrompt();
}
_bindOpacity() {
this._bindings = Main.layoutManager.uiGroup.get_children()
.filter(c => c != Main.layoutManager.screenShieldGroup)
.map(c => this.bind_property('opacity', c, 'opacity', 0));
}
_unbindOpacity() {
this._bindings.forEach(b => b.unbind());
}
_loginScreenSessionActivated() {
if (this.opacity == 255 && this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING)
return;
Tweener.addTween(this,
{ opacity: 255,
time: _FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onUpdate: () => {
let children = Main.layoutManager.uiGroup.get_children();
for (let i = 0; i < children.length; i++) {
if (children[i] != Main.layoutManager.screenShieldGroup)
children[i].opacity = this.opacity;
}
},
onComplete: () => {
if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.NOT_VERIFYING)
this._authPrompt.reset();
} });
this._bindOpacity();
this.ease({
opacity: 255,
duration: _FADE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.NOT_VERIFYING)
this._authPrompt.reset();
this._unbindOpacity();
},
});
}
_gotGreeterSessionProxy(proxy) {
@ -937,21 +958,16 @@ var LoginDialog = GObject.registerClass({
}
_startSession(serviceName) {
Tweener.addTween(this,
{ opacity: 0,
time: _FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onUpdate: () => {
let children = Main.layoutManager.uiGroup.get_children();
for (let i = 0; i < children.length; i++) {
if (children[i] != Main.layoutManager.screenShieldGroup)
children[i].opacity = this.opacity;
}
},
onComplete: () => {
this._greeter.call_start_session_when_ready_sync(serviceName, true, null);
} });
this._bindOpacity();
this.ease({
opacity: 0,
duration: _FADE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this._greeter.call_start_session_when_ready_sync(serviceName, true, null);
this._unbindOpacity();
},
});
}
_onSessionOpened(client, serviceName) {
@ -967,7 +983,7 @@ var LoginDialog = GObject.registerClass({
let hold = new Batch.Hold();
let signalId = this._userList.connect('item-added',
() => {
let item = this._userList.getItemFromUserName(userName);
item = this._userList.getItemFromUserName(userName);
if (item)
hold.release();
@ -1021,9 +1037,8 @@ var LoginDialog = GObject.registerClass({
() => {
// If we're just starting out, start on the right item.
if (!this._userManager.is_loaded) {
if (!this._userManager.is_loaded)
this._userList.jumpToItem(loginItem);
}
},
() => {
@ -1044,12 +1059,12 @@ var LoginDialog = GObject.registerClass({
() => {
// If idle timeout is done, make sure the timed login indicator is shown
if (delay > _TIMED_LOGIN_IDLE_THRESHOLD &&
this._authPrompt.actor.visible)
this._authPrompt.visible)
this._authPrompt.cancel();
if (delay > _TIMED_LOGIN_IDLE_THRESHOLD || firstRun) {
this._userList.scrollToItem(loginItem);
loginItem.actor.grab_key_focus();
loginItem.grab_key_focus();
}
},
@ -1074,9 +1089,8 @@ var LoginDialog = GObject.registerClass({
// Restart timed login on user interaction
global.stage.connect('captured-event', (actor, event) => {
if (event.type() == Clutter.EventType.KEY_PRESS ||
event.type() == Clutter.EventType.BUTTON_PRESS) {
event.type() == Clutter.EventType.BUTTON_PRESS)
this._startTimedLogin(userName, seconds);
}
return Clutter.EVENT_PROPAGATE;
});
@ -1110,7 +1124,7 @@ var LoginDialog = GObject.registerClass({
this._sessionMenuButton.close();
this._setUserListExpanded(true);
this._notListedButton.show();
this._userList.actor.grab_key_focus();
this._userList.grab_key_focus();
}
_beginVerificationForItem(item) {
@ -1119,8 +1133,7 @@ var LoginDialog = GObject.registerClass({
let userName = item.user.get_user_name();
let hold = new Batch.Hold();
this._authPrompt.begin({ userName: userName,
hold: hold });
this._authPrompt.begin({ userName, hold });
return hold;
}
@ -1183,9 +1196,8 @@ var LoginDialog = GObject.registerClass({
let users = this._userManager.list_users();
for (let i = 0; i < users.length; i++) {
for (let i = 0; i < users.length; i++)
this._userList.addUser(users[i]);
}
this._updateDisableUserList();
@ -1218,16 +1230,17 @@ var LoginDialog = GObject.registerClass({
_("Login Window"),
'dialog-password-symbolic',
{ sortGroup: CtrlAltTab.SortGroup.MIDDLE });
this._userList.actor.grab_key_focus();
this._userList.grab_key_focus();
this.show();
this.opacity = 0;
Main.pushModal(this, { actionMode: Shell.ActionMode.LOGIN_SCREEN });
Tweener.addTween(this,
{ opacity: 255,
time: 1,
transition: 'easeInQuad' });
this.ease({
opacity: 255,
duration: 1000,
mode: Clutter.AnimationMode.EASE_IN_QUAD,
});
return true;
}
@ -1241,7 +1254,7 @@ var LoginDialog = GObject.registerClass({
this._authPrompt.cancel();
}
addCharacter(unichar) {
addCharacter(_unichar) {
// Don't allow type ahead at the login screen
}

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getOVirtCredentialsManager */
const Gio = imports.gi.Gio;
const Signals = imports.signals;
@ -22,7 +23,7 @@ function OVirtCredentials() {
g_interface_info: OVirtCredentialsInfo,
g_name: 'org.ovirt.vdsm.Credentials',
g_object_path: '/org/ovirt/vdsm/Credentials',
g_flags: (Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES });
self.init(null);
return self;
}

View File

@ -15,12 +15,13 @@ const RealmIface = loadInterfaceXML("org.freedesktop.realmd.Realm");
const Realm = Gio.DBusProxy.makeProxyWrapper(RealmIface);
var Manager = class {
constructor(parentActor) {
constructor() {
this._aggregateProvider = Provider(Gio.DBus.system,
'org.freedesktop.realmd',
'/org/freedesktop/realmd',
this._reloadRealms.bind(this));
this._realms = {};
this._loginFormat = null;
this._signalId = this._aggregateProvider.connect('g-properties-changed',
(proxy, properties) => {
@ -86,7 +87,7 @@ var Manager = class {
}
get loginFormat() {
if (this._loginFormat !== undefined)
if (this._loginFormat)
return this._loginFormat;
this._updateLoginFormat();

View File

@ -1,4 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported BANNER_MESSAGE_KEY, BANNER_MESSAGE_TEXT_KEY, LOGO_KEY,
DISABLE_USER_LIST_KEY, fadeInActor, fadeOutActor, cloneAndFadeOutActor */
const { Clutter, Gio, GLib } = imports.gi;
const Signals = imports.signals;
@ -9,14 +11,13 @@ const OVirt = imports.gdm.oVirt;
const Main = imports.ui.main;
const Params = imports.misc.params;
const SmartcardManager = imports.misc.smartcardManager;
const Tweener = imports.ui.tweener;
var PASSWORD_SERVICE_NAME = 'gdm-password';
var FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
var SMARTCARD_SERVICE_NAME = 'gdm-smartcard';
var OVIRT_SERVICE_NAME = 'gdm-ovirtcred';
var FADE_ANIMATION_TIME = 0.16;
var CLONE_FADE_ANIMATION_TIME = 0.25;
var FADE_ANIMATION_TIME = 160;
var CLONE_FADE_ANIMATION_TIME = 250;
var LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
var PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication';
@ -36,7 +37,7 @@ var MessageType = {
NONE: 0,
ERROR: 1,
INFO: 2,
HINT: 3
HINT: 3,
};
function fadeInActor(actor) {
@ -45,20 +46,20 @@ function fadeInActor(actor) {
let hold = new Batch.Hold();
actor.show();
let [minHeight, naturalHeight] = actor.get_preferred_height(-1);
let [, naturalHeight] = actor.get_preferred_height(-1);
actor.opacity = 0;
actor.set_height(0);
Tweener.addTween(actor,
{ opacity: 255,
height: naturalHeight,
time: FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete() {
this.set_height(-1);
hold.release();
},
});
actor.ease({
opacity: 255,
height: naturalHeight,
duration: FADE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this.set_height(-1);
hold.release();
},
});
return hold;
}
@ -71,17 +72,17 @@ function fadeOutActor(actor) {
}
let hold = new Batch.Hold();
Tweener.addTween(actor,
{ opacity: 0,
height: 0,
time: FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete() {
this.hide();
this.set_height(-1);
hold.release();
},
});
actor.ease({
opacity: 0,
height: 0,
duration: FADE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this.hide();
this.set_height(-1);
hold.release();
},
});
return hold;
}
@ -101,15 +102,15 @@ function cloneAndFadeOutActor(actor) {
clone.set_position(x, y);
let hold = new Batch.Hold();
Tweener.addTween(clone,
{ opacity: 0,
time: CLONE_FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete() {
clone.destroy();
hold.release();
}
});
clone.ease({
opacity: 0,
duration: CLONE_FADE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
clone.destroy();
hold.release();
},
});
return hold;
}
@ -271,7 +272,7 @@ var ShellUserVerifier = class {
let interval = this._getIntervalForMessage(message);
this.hasPendingMessages = true;
this._messageQueue.push({ text: message, type: messageType, interval: interval });
this._messageQueue.push({ text: message, type: messageType, interval });
this._queueMessageTimeout();
}
@ -303,7 +304,7 @@ var ShellUserVerifier = class {
});
}
_oVirtUserAuthenticated(token) {
_oVirtUserAuthenticated(_token) {
this._preemptingService = OVIRT_SERVICE_NAME;
this.emit('ovirt-user-authenticated');
}
@ -484,7 +485,7 @@ var ShellUserVerifier = class {
if (!this.serviceIsForeground(serviceName))
return;
this.emit('ask-question', serviceName, question, '');
this.emit('ask-question', serviceName, question, false);
}
_onSecretInfoQuery(client, serviceName, secretQuestion) {
@ -497,7 +498,7 @@ var ShellUserVerifier = class {
return;
}
this.emit('ask-question', serviceName, secretQuestion, '\u25cf');
this.emit('ask-question', serviceName, secretQuestion, true);
}
_onReset() {
@ -543,6 +544,7 @@ var ShellUserVerifier = class {
});
}
} else {
// eslint-disable-next-line no-lonely-if
if (!this.hasPendingMessages) {
this._cancelAndReset();
} else {
@ -570,9 +572,8 @@ var ShellUserVerifier = class {
// if the password service fails, then cancel everything.
// But if, e.g., fingerprint fails, still give
// password authentication a chance to succeed
if (this.serviceIsForeground(serviceName)) {
if (this.serviceIsForeground(serviceName))
this._verificationFailed(true);
}
}
};
Signals.addSignalMethods(ShellUserVerifier.prototype);

View File

@ -1,31 +1,46 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ExtensionState, ExtensionType, getCurrentExtension,
getSettings, initTranslations, isOutOfDate, installImporter,
serializeExtension, deserializeExtension */
// 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
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:
*
* Returns the current extension, or null if not called from an extension.
* @returns {?object} - The current extension, or null if not called from
* an extension.
*/
function getCurrentExtension() {
let stack = (new Error()).stack.split('\n');
let stack = new Error().stack.split('\n');
let extensionStackLine;
// Search for an occurrence of an extension stack frame
@ -49,13 +64,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();
@ -66,7 +85,7 @@ function getCurrentExtension() {
/**
* initTranslations:
* @domain: (optional): the gettext domain to use
* @param {string=} domain - the gettext domain to use
*
* Initialize Gettext to load translations from extensionsdir/locale.
* If @domain is not provided, it will be taken from metadata['gettext-domain']
@ -90,7 +109,8 @@ function initTranslations(domain) {
/**
* getSettings:
* @schema: (optional): the GSettings schema id
* @param {string=} schema - the GSettings schema id
* @returns {Gio.Settings} - a new settings object for @schema
*
* Builds and returns a GSettings schema for @schema, using schema files
* in extensionsdir/schemas. If @schema is omitted, it is taken from
@ -110,12 +130,13 @@ function getSettings(schema) {
// SYSTEM extension that has been installed in the same prefix as the shell
let schemaDir = extension.dir.get_child('schemas');
let schemaSource;
if (schemaDir.query_exists(null))
if (schemaDir.query_exists(null)) {
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
GioSSS.get_default(),
false);
else
} else {
schemaSource = GioSSS.get_default();
}
let schemaObj = schemaSource.lookup(schema, true);
if (!schemaObj)
@ -126,8 +147,9 @@ function getSettings(schema) {
/**
* versionCheck:
* @required: an array of versions we're compatible with
* @current: the version we have
* @param {string[]} required - an array of versions we're compatible with
* @param {string} current - the version we have
* @returns {bool} - true if @current is compatible with @required
*
* Check if a component is compatible for an extension.
* @required is an array, and at least one version must match.
@ -147,8 +169,8 @@ function versionCheck(required, current) {
let requiredArray = required[i].split('.');
if (requiredArray[0] == major &&
requiredArray[1] == minor &&
(requiredArray[2] == point ||
(requiredArray[2] == undefined && parseInt(minor) % 2 == 0)))
((requiredArray[2] === undefined && parseInt(minor) % 2 == 0) ||
requiredArray[2] == point))
return true;
}
return false;
@ -161,52 +183,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 +237,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

@ -1,4 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported collectFromDatadirs, recursivelyDeleteDir,
recursivelyMoveDir, loadInterfaceXML */
const { Gio, GLib } = imports.gi;
const Config = imports.misc.config;
@ -27,11 +29,6 @@ function collectFromDatadirs(subdir, includeUserDir, processFile) {
}
}
function deleteGFile(file) {
// Work around 'delete' being a keyword in JS.
return file['delete'](null);
}
function recursivelyDeleteDir(dir, deleteParent) {
let children = dir.enumerate_children('standard::name,standard::type',
Gio.FileQueryInfoFlags.NONE, null);
@ -41,13 +38,13 @@ function recursivelyDeleteDir(dir, deleteParent) {
let type = info.get_file_type();
let child = dir.get_child(info.get_name());
if (type == Gio.FileType.REGULAR)
deleteGFile(child);
child.delete(null);
else if (type == Gio.FileType.DIRECTORY)
recursivelyDeleteDir(child, true);
}
if (deleteParent)
deleteGFile(dir);
dir.delete(null);
}
function recursivelyMoveDir(srcDir, destDir) {
@ -73,18 +70,18 @@ let _ifaceResource = null;
function loadInterfaceXML(iface) {
if (!_ifaceResource) {
// don't use global.datadir so the method is usable from tests/tools
let dir = GLib.getenv ('GNOME_SHELL_DATADIR') || Config.PKGDATADIR;
let path = dir + '/gnome-shell-dbus-interfaces.gresource';
let dir = GLib.getenv('GNOME_SHELL_DATADIR') || Config.PKGDATADIR;
let path = `${dir}/gnome-shell-dbus-interfaces.gresource`;
_ifaceResource = Gio.Resource.load(path);
_ifaceResource._register();
}
let xml = null;
let uri = 'resource:///org/gnome/shell/dbus-interfaces/' + iface + '.xml';
let uri = `resource:///org/gnome/shell/dbus-interfaces/${iface}.xml`;
let f = Gio.File.new_for_uri(uri);
try {
let [ok, bytes] = f.load_contents(null);
let [ok_, bytes] = f.load_contents(null);
if (bytes instanceof Uint8Array)
xml = imports.byteArray.toString(bytes);
else

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported PresenceStatus, Presence, Inhibitor, SessionManager */
const Gio = imports.gi.Gio;
@ -10,7 +11,7 @@ var PresenceStatus = {
AVAILABLE: 0,
INVISIBLE: 1,
BUSY: 2,
IDLE: 3
IDLE: 3,
};
var PresenceProxy = Gio.DBusProxy.makeProxyWrapper(PresenceIface);

View File

@ -28,7 +28,7 @@ var HistoryManager = class {
this._entry = params.entry;
if (this._entry) {
this._entry.connect('key-press-event',
this._entry.connect('key-press-event',
this._onEntryKeyPress.bind(this));
}
}
@ -82,11 +82,11 @@ var HistoryManager = class {
_onEntryKeyPress(entry, event) {
let symbol = event.get_key_symbol();
if (symbol == Clutter.KEY_Up) {
if (symbol == Clutter.KEY_Up)
return this._setPrevItem(entry.get_text());
} else if (symbol == Clutter.KEY_Down) {
else if (symbol == Clutter.KEY_Down)
return this._setNextItem(entry.get_text());
}
return Clutter.EVENT_PROPAGATE;
}

View File

@ -1,7 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getIBusManager */
const { Gio, GLib, IBus } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const IBusCandidatePopup = imports.ui.ibusCandidatePopup;
@ -18,9 +18,9 @@ function _checkIBusVersion(requiredMajor, requiredMinor, requiredMicro) {
IBus.MICRO_VERSION >= requiredMicro))
return;
throw "Found IBus version %d.%d.%d but required is %d.%d.%d".
format(IBus.MAJOR_VERSION, IBus.MINOR_VERSION, IBus.MINOR_VERSION,
requiredMajor, requiredMinor, requiredMicro);
throw "Found IBus version %d.%d.%d but required is %d.%d.%d"
.format(IBus.MAJOR_VERSION, IBus.MINOR_VERSION, IBus.MINOR_VERSION,
requiredMajor, requiredMinor, requiredMicro);
}
function getIBusManager() {
@ -58,16 +58,30 @@ var IBusManager = class {
this._spawn();
}
_spawn() {
_spawn(extraArgs = []) {
try {
Gio.Subprocess.new(['ibus-daemon', '--xim', '--panel', 'disable'],
Gio.SubprocessFlags.NONE);
let cmdLine = ['ibus-daemon', '--panel', 'disable', ...extraArgs];
Gio.Subprocess.new(cmdLine, Gio.SubprocessFlags.NONE);
} catch (e) {
log(`Failed to launch ibus-daemon: ${e.message}`);
}
}
restartDaemon(extraArgs = []) {
this._spawn(['-r', ...extraArgs]);
}
_clear() {
if (this._cancellable) {
this._cancellable.cancel();
this._cancellable = null;
}
if (this._preloadEnginesId) {
GLib.source_remove(this._preloadEnginesId);
this._preloadEnginesId = 0;
}
if (this._panelService)
this._panelService.destroy();
@ -79,33 +93,44 @@ var IBusManager = class {
this._currentEngineName = null;
this.emit('ready', false);
this._spawn();
}
_onConnected() {
this._ibus.list_engines_async(-1, null, this._initEngines.bind(this));
this._cancellable = new Gio.Cancellable();
this._ibus.list_engines_async(-1, this._cancellable,
this._initEngines.bind(this));
this._ibus.request_name_async(IBus.SERVICE_PANEL,
IBus.BusNameFlag.REPLACE_EXISTING,
-1, null,
this._initPanelService.bind(this));
IBus.BusNameFlag.REPLACE_EXISTING, -1, this._cancellable,
this._initPanelService.bind(this));
}
_initEngines(ibus, result) {
let enginesList = this._ibus.list_engines_async_finish(result);
if (enginesList) {
try {
let enginesList = this._ibus.list_engines_async_finish(result);
for (let i = 0; i < enginesList.length; ++i) {
let name = enginesList[i].get_name();
this._engines.set(name, enginesList[i]);
}
this._updateReadiness();
} else {
} catch (e) {
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
return;
logError(e);
this._clear();
}
}
_initPanelService(ibus, result) {
let success = this._ibus.request_name_async_finish(result);
let success = false;
try {
success = !!this._ibus.request_name_async_finish(result);
} catch (e) {
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
return;
logError(e);
}
if (success) {
this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(),
object_path: IBus.PATH_PANEL });
@ -117,7 +142,7 @@ var IBusManager = class {
});
this._panelService.connect('focus-in', (panel, path) => {
if (!GLib.str_has_suffix(path, '/InputContext_1'))
this.emit ('focus-in');
this.emit('focus-in');
});
this._panelService.connect('focus-out', () => this.emit('focus-out'));
@ -132,10 +157,10 @@ var IBusManager = class {
} catch (e) {
}
// If an engine is already active we need to get its properties
this._ibus.get_global_engine_async(-1, null, (i, result) => {
this._ibus.get_global_engine_async(-1, this._cancellable, (_bus, res) => {
let engine;
try {
engine = this._ibus.get_global_engine_async_finish(result);
engine = this._ibus.get_global_engine_async_finish(res);
if (!engine)
return;
} catch (e) {
@ -204,8 +229,18 @@ var IBusManager = class {
return;
}
this._ibus.set_global_engine_async(id, this._MAX_INPUT_SOURCE_ACTIVATION_TIME,
null, callback || null);
this._ibus.set_global_engine_async(id,
this._MAX_INPUT_SOURCE_ACTIVATION_TIME,
this._cancellable, (_bus, res) => {
try {
this._ibus.set_global_engine_async_finish(res);
} catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
logError(e);
}
if (callback)
callback();
});
}
preloadEngines(ids) {
@ -213,21 +248,23 @@ var IBusManager = class {
return;
if (this._preloadEnginesId != 0) {
Mainloop.source_remove(this._preloadEnginesId);
GLib.source_remove(this._preloadEnginesId);
this._preloadEnginesId = 0;
}
this._preloadEnginesId =
Mainloop.timeout_add_seconds(this._PRELOAD_ENGINES_DELAY_TIME,
() => {
this._ibus.preload_engines_async(
ids,
-1,
null,
null);
this._preloadEnginesId = 0;
return GLib.SOURCE_REMOVE;
});
GLib.timeout_add_seconds(
GLib.PRIORITY_DEFAULT,
this._PRELOAD_ENGINES_DELAY_TIME,
() => {
this._ibus.preload_engines_async(
ids,
-1,
this._cancellable,
null);
this._preloadEnginesId = 0;
return GLib.SOURCE_REMOVE;
});
}
};
Signals.addSignalMethods(IBusManager.prototype);

View File

@ -1,5 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const { Clutter, GLib, GObject, IBus } = imports.gi;
/* exported InputMethod */
const { Clutter, GLib, Gio, GObject, IBus } = imports.gi;
const Keyboard = imports.ui.status.keyboard;
@ -35,15 +36,7 @@ class InputMethod extends Clutter.InputMethod {
}
_updateCapabilities() {
let caps = 0;
if (this.can_show_preedit)
caps |= IBus.Capabilite.PREEDIT_TEXT;
if (this._currentFocus)
caps |= IBus.Capabilite.FOCUS | IBus.Capabilite.SURROUNDING_TEXT;
else
caps |= IBus.Capabilite.PREEDIT_TEXT | IBus.Capabilite.AUXILIARY_TEXT | IBus.Capabilite.LOOKUP_TABLE | IBus.Capabilite.PROPERTY;
let caps = IBus.Capabilite.PREEDIT_TEXT | IBus.Capabilite.FOCUS | IBus.Capabilite.SURROUNDING_TEXT;
if (this._context)
this._context.set_capabilities(caps);
@ -54,23 +47,39 @@ class InputMethod extends Clutter.InputMethod {
}
_onConnected() {
this._ibus.create_input_context_async ('gnome-shell', -1, null,
this._setContext.bind(this));
this._cancellable = new Gio.Cancellable();
this._ibus.create_input_context_async('gnome-shell', -1,
this._cancellable, this._setContext.bind(this));
}
_setContext(bus, res) {
this._context = this._ibus.create_input_context_async_finish(res);
try {
this._context = this._ibus.create_input_context_async_finish(res);
} catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
logError(e);
this._clear();
}
return;
}
this._context.connect('commit-text', this._onCommitText.bind(this));
this._context.connect('delete-surrounding-text', this._onDeleteSurroundingText.bind(this));
this._context.connect('update-preedit-text', this._onUpdatePreeditText.bind(this));
this._context.connect('show-preedit-text', this._onShowPreeditText.bind(this));
this._context.connect('hide-preedit-text', this._onHidePreeditText.bind(this));
this._context.connect('forward-key-event', this._onForwardKeyEvent.bind(this));
this._context.connect('destroy', this._clear.bind(this));
this._updateCapabilities();
}
_clear() {
if (this._cancellable) {
this._cancellable.cancel();
this._cancellable = null;
}
this._context = null;
this._hints = 0;
this._purpose = 0;
@ -84,15 +93,15 @@ class InputMethod extends Clutter.InputMethod {
this.emit('request-surrounding');
}
_onCommitText(context, text) {
_onCommitText(_context, text) {
this.commit(text.get_text());
}
_onDeleteSurroundingText(context) {
_onDeleteSurroundingText() {
this.delete_surrounding();
}
_onUpdatePreeditText(context, text, pos, visible) {
_onUpdatePreeditText(_context, text, pos, visible) {
if (text == null)
return;
@ -108,19 +117,19 @@ class InputMethod extends Clutter.InputMethod {
this._preeditVisible = visible;
}
_onShowPreeditText(context) {
_onShowPreeditText() {
this._preeditVisible = true;
this.set_preedit_text(this._preeditStr, this._preeditPos);
}
_onHidePreeditText(context) {
_onHidePreeditText() {
this.set_preedit_text(null, this._preeditPos);
this._preeditVisible = false;
}
_onForwardKeyEvent(context, keyval, keycode, state) {
_onForwardKeyEvent(_context, keyval, keycode, state) {
let press = (state & IBus.ModifierType.RELEASE_MASK) == 0;
state &= ~(IBus.ModifierType.RELEASE_MASK);
state &= ~IBus.ModifierType.RELEASE_MASK;
let curEvent = Clutter.get_current_event();
let time;
@ -136,7 +145,6 @@ class InputMethod extends Clutter.InputMethod {
this._currentFocus = focus;
if (this._context) {
this._context.focus_in();
this._updateCapabilities();
this._emitRequestSurrounding();
}
@ -148,10 +156,8 @@ class InputMethod extends Clutter.InputMethod {
vfunc_focus_out() {
this._currentFocus = null;
if (this._context) {
if (this._context)
this._context.focus_out();
this._updateCapabilities();
}
if (this._preeditStr) {
// Unset any preedit text
@ -254,17 +260,22 @@ class InputMethod extends Clutter.InputMethod {
if (event.type() == Clutter.EventType.KEY_RELEASE)
state |= IBus.ModifierType.RELEASE_MASK;
this._context.process_key_event_async(event.get_key_symbol(),
event.get_key_code() - 8, // Convert XKB keycodes to evcodes
state, -1, null,
(context, res) => {
try {
let retval = context.process_key_event_async_finish(res);
this.notify_key_event(event, retval);
} catch (e) {
log(`Error processing key on IM: ${e.message}`);
}
});
this._context.process_key_event_async(
event.get_key_symbol(),
event.get_key_code() - 8, // Convert XKB keycodes to evcodes
state, -1, this._cancellable,
(context, res) => {
if (context != this._context)
return;
try {
let retval = context.process_key_event_async_finish(res);
this.notify_key_event(event, retval);
} catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
log(`Error processing key on IM: ${e.message}`);
}
});
return true;
}
});

View File

@ -1,3 +1,4 @@
/* exported IntrospectService */
const { Gio, GLib, Meta, Shell } = imports.gi;
const INTROSPECT_SCHEMA = 'org.gnome.shell';
@ -39,6 +40,15 @@ var IntrospectService = class {
});
this._syncRunningApplications();
this._whitelistMap = new Map();
APP_WHITELIST.forEach(appName => {
Gio.DBus.watch_name(Gio.BusType.SESSION,
appName,
Gio.BusNameWatcherFlags.NONE,
(conn, name, owner) => this._whitelistMap.set(name, owner),
(conn, name) => this._whitelistMap.delete(name));
});
}
_isStandaloneApp(app) {
@ -50,7 +60,7 @@ var IntrospectService = class {
}
_isSenderWhitelisted(sender) {
return APP_WHITELIST.includes(sender);
return [...this._whitelistMap.values()].includes(sender);
}
_getSandboxedAppId(app) {
@ -69,7 +79,7 @@ var IntrospectService = class {
for (let app of apps) {
let appInfo = {};
let isAppActive = (focusedApp == app);
let isAppActive = focusedApp == app;
if (!this._isStandaloneApp(app))
continue;
@ -103,10 +113,10 @@ var IntrospectService = class {
return false;
let type = window.get_window_type();
return (type == Meta.WindowType.NORMAL ||
return type == Meta.WindowType.NORMAL ||
type == Meta.WindowType.DIALOG ||
type == Meta.WindowType.MODAL_DIALOG ||
type == Meta.WindowType.UTILITY);
type == Meta.WindowType.UTILITY;
}
GetRunningApplicationsAsync(params, invocation) {
@ -126,7 +136,8 @@ var IntrospectService = class {
let apps = this._appSystem.get_running();
let windowsList = {};
if (!this._isIntrospectEnabled()) {
if (!this._isIntrospectEnabled() &&
!this._isSenderWhitelisted(invocation.get_sender())) {
invocation.return_error_literal(Gio.DBusError,
Gio.DBusError.ACCESS_DENIED,
'App introspection not allowed');
@ -150,9 +161,9 @@ var IntrospectService = class {
'app-id': GLib.Variant.new('s', app.get_id()),
'client-type': GLib.Variant.new('u', window.get_client_type()),
'is-hidden': GLib.Variant.new('b', window.is_hidden()),
'has-focus': GLib.Variant.new('b', (window == focusWindow)),
'has-focus': GLib.Variant.new('b', window == focusWindow),
'width': GLib.Variant.new('u', frameRect.width),
'height': GLib.Variant.new('u', frameRect.height)
'height': GLib.Variant.new('u', frameRect.height),
};
// These properties may not be available for all windows:
@ -162,9 +173,10 @@ var IntrospectService = class {
if (wmClass != null)
windowsList[windowId]['wm-class'] = GLib.Variant.new('s', wmClass);
if (sandboxedAppId != null)
if (sandboxedAppId != null) {
windowsList[windowId]['sandboxed-app-id'] =
GLib.Variant.new('s', sandboxedAppId);
}
}
}
invocation.return_value(new GLib.Variant('(a{ta{sv}})', [windowsList]));

View File

@ -1,4 +1,5 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/* exported getCompletions, getCommonPrefix, getDeclaredConstants */
// Returns a list of potential completions for text. Completions either
// follow a dot (e.g. foo.ba -> bar) or they are picked from globalCompletionList (e.g. fo -> foo)
@ -8,11 +9,10 @@
// This function is likely the one you want to call from external modules
function getCompletions(text, commandHeader, globalCompletionList) {
let methods = [];
let expr, base;
let expr_, base;
let attrHead = '';
if (globalCompletionList == null) {
if (globalCompletionList == null)
globalCompletionList = [];
}
let offset = getExpressionOffset(text, text.length - 1);
if (offset >= 0) {
@ -21,7 +21,7 @@ function getCompletions(text, commandHeader, globalCompletionList) {
// Look for expressions like "Main.panel.foo" and match Main.panel and foo
let matches = text.match(/(.*)\.(.*)/);
if (matches) {
[expr, base, attrHead] = matches;
[expr_, base, attrHead] = matches;
methods = getPropertyNamesFromExpression(base, commandHeader).filter(
attr => attr.slice(0, attrHead.length) == attrHead
@ -32,7 +32,7 @@ function getCompletions(text, commandHeader, globalCompletionList) {
// not proceeded by a dot and match them against global constants
matches = text.match(/^(\w*)$/);
if (text == '' || matches) {
[expr, attrHead] = matches;
[expr_, attrHead] = matches;
methods = globalCompletionList.filter(
attr => attr.slice(0, attrHead.length) == attrHead
);
@ -58,9 +58,8 @@ function isStopChar(c) {
function findMatchingQuote(expr, offset) {
let quoteChar = expr.charAt(offset);
for (let i = offset - 1; i >= 0; --i) {
if (expr.charAt(i) == quoteChar && expr.charAt(i - 1) != '\\') {
if (expr.charAt(i) == quoteChar && expr.charAt(i - 1) != '\\')
return i;
}
}
return -1;
}
@ -68,9 +67,8 @@ function findMatchingQuote(expr, offset) {
// Given the ending position of a regex, find where it starts
function findMatchingSlash(expr, offset) {
for (let i = offset - 1; i >= 0; --i) {
if (expr.charAt(i) == '/' && expr.charAt(i - 1) != '\\') {
if (expr.charAt(i) == '/' && expr.charAt(i - 1) != '\\')
return i;
}
}
return -1;
}
@ -81,31 +79,30 @@ function findMatchingSlash(expr, offset) {
// findMatchingBrace("[(])", 3) returns 1.
function findMatchingBrace(expr, offset) {
let closeBrace = expr.charAt(offset);
let openBrace = ({ ')': '(', ']': '[' })[closeBrace];
let openBrace = { ')': '(', ']': '[' }[closeBrace];
function findTheBrace(expr, offset) {
if (offset < 0) {
return -1;
}
return findTheBrace(expr, offset - 1, openBrace, closeBrace);
}
if (expr.charAt(offset) == openBrace) {
return offset;
}
if (expr.charAt(offset).match(/['"]/)) {
return findTheBrace(expr, findMatchingQuote(expr, offset) - 1);
}
if (expr.charAt(offset) == '/') {
return findTheBrace(expr, findMatchingSlash(expr, offset) - 1);
}
if (expr.charAt(offset) == closeBrace) {
return findTheBrace(expr, findTheBrace(expr, offset - 1) - 1);
}
function findTheBrace(expr, offset, ...braces) {
let [openBrace, closeBrace] = braces;
return findTheBrace(expr, offset - 1);
if (offset < 0)
return -1;
}
if (expr.charAt(offset) == openBrace)
return offset;
return findTheBrace(expr, offset - 1);
if (expr.charAt(offset).match(/['"]/))
return findTheBrace(expr, findMatchingQuote(expr, offset) - 1, ...braces);
if (expr.charAt(offset) == '/')
return findTheBrace(expr, findMatchingSlash(expr, offset) - 1, ...braces);
if (expr.charAt(offset) == closeBrace)
return findTheBrace(expr, findTheBrace(expr, offset - 1, ...braces) - 1, ...braces);
return findTheBrace(expr, offset - 1, ...braces);
}
// Walk expr backwards from offset looking for the beginning of an
@ -117,13 +114,11 @@ function getExpressionOffset(expr, offset) {
while (offset >= 0) {
let currChar = expr.charAt(offset);
if (isStopChar(currChar)) {
if (isStopChar(currChar))
return offset + 1;
}
if (currChar.match(/[)\]]/)) {
if (currChar.match(/[)\]]/))
offset = findMatchingBrace(expr, offset);
}
--offset;
}
@ -140,10 +135,10 @@ function isValidPropertyName(w) {
// To get all properties (enumerable and not), we need to walk
// the prototype chain ourselves
function getAllProps(obj) {
if (obj === null || obj === undefined) {
if (obj === null || obj === undefined)
return [];
}
return Object.getOwnPropertyNames(obj).concat( getAllProps(Object.getPrototypeOf(obj)) );
return Object.getOwnPropertyNames(obj).concat(getAllProps(Object.getPrototypeOf(obj)));
}
// Given a string _expr_, returns all methods
@ -151,11 +146,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 {
@ -171,11 +162,11 @@ function getPropertyNamesFromExpression(expr, commandHeader) {
if (typeof obj === 'object') {
let allProps = getAllProps(obj);
// Get only things we are allowed to complete following a '.'
allProps = allProps.filter( isValidPropertyName );
allProps = allProps.filter(isValidPropertyName);
// Make sure propsUnique contains one key for every
// property so we end up with a unique list of properties
allProps.map(p => propsUnique[p] = null);
allProps.map(p => (propsUnique[p] = null));
}
return Object.keys(propsUnique).sort();
}
@ -192,24 +183,26 @@ function getCommonPrefix(words) {
return word;
}
// Remove any blocks that are quoted or are in a regex
function removeLiterals(str) {
if (str.length == 0)
return '';
let currChar = str.charAt(str.length - 1);
if (currChar == '"' || currChar == '\'') {
return removeLiterals(
str.slice(0, findMatchingQuote(str, str.length - 1)));
} else if (currChar == '/') {
return removeLiterals(
str.slice(0, findMatchingSlash(str, str.length - 1)));
}
return removeLiterals(str.slice(0, str.length - 1)) + currChar;
}
// Returns true if there is reason to think that eval(str)
// will modify the global scope
function isUnsafeExpression(str) {
// Remove any blocks that are quoted or are in a regex
function removeLiterals(str) {
if (str.length == 0) {
return '';
}
let currChar = str.charAt(str.length - 1);
if (currChar == '"' || currChar == '\'') {
return removeLiterals(str.slice(0, findMatchingQuote(str, str.length - 1)));
} else if (currChar == '/') {
return removeLiterals(str.slice(0, findMatchingSlash(str, str.length - 1)));
}
return removeLiterals(str.slice(0, str.length - 1)) + currChar;
}
// Check for any sort of assignment
// The strategy used is dumb: remove any quotes
@ -217,10 +210,10 @@ function isUnsafeExpression(str) {
// If there is, it might be an unsafe assignment.
let prunedStr = removeLiterals(str);
prunedStr = prunedStr.replace(/[=!]==/g, ''); //replace === and !== with nothing
prunedStr = prunedStr.replace(/[=<>!]=/g, ''); //replace ==, <=, >=, != with nothing
prunedStr = prunedStr.replace(/[=!]==/g, ''); // replace === and !== with nothing
prunedStr = prunedStr.replace(/[=<>!]=/g, ''); // replace ==, <=, >=, != with nothing
if (prunedStr.match(/=/)) {
if (prunedStr.match(/[=]/)) {
return true;
} else if (prunedStr.match(/;/)) {
// If we contain a semicolon not inside of a quote/regex, assume we're unsafe as well
@ -234,10 +227,10 @@ function isUnsafeExpression(str) {
function getDeclaredConstants(str) {
let ret = [];
str.split(';').forEach(s => {
let base, keyword;
let base_, keyword;
let match = s.match(/const\s+(\w+)\s*=/);
if (match) {
[base, keyword] = match;
[base_, keyword] = match;
ret.push(keyword);
}
});

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getKeyboardManager, holdKeyboard, releaseKeyboard */
const { GLib, GnomeDesktop, Meta } = imports.gi;

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported canLock, getLoginManager, registerSessionWithGDM */
const { GLib, Gio } = imports.gi;
const Signals = imports.signals;
@ -73,8 +74,9 @@ function registerSessionWithGDM() {
let _loginManager = null;
/**
* LoginManager:
* getLoginManager:
* An abstraction over systemd/logind and ConsoleKit.
* @returns {object} - the LoginManager singleton
*
*/
function getLoginManager() {
@ -102,7 +104,7 @@ var LoginManagerSystemd = class {
getCurrentSessionProxy(callback) {
if (this._currentSession) {
callback (this._currentSession);
callback(this._currentSession);
return;
}
@ -116,7 +118,7 @@ var LoginManagerSystemd = class {
} else {
log('Failed to find "Display" session; are we the greeter?');
for (let [session, objectPath] of this._userProxy.Sessions) {
for ([session, objectPath] of this._userProxy.Sessions) {
let sessionProxy = new SystemdLoginSession(Gio.DBus.system,
'org.freedesktop.login1',
objectPath);
@ -182,9 +184,9 @@ var LoginManagerSystemd = class {
(proxy, result) => {
let fd = -1;
try {
let [outVariant, fdList] = proxy.call_with_unix_fd_list_finish(result);
let [outVariant_, fdList] = proxy.call_with_unix_fd_list_finish(result);
fd = fdList.steal_fds()[0];
callback(new Gio.UnixInputStream({ fd: fd }));
callback(new Gio.UnixInputStream({ fd }));
} catch (e) {
logError(e, "Error getting systemd inhibitor");
callback(null);
@ -199,7 +201,7 @@ var LoginManagerSystemd = class {
Signals.addSignalMethods(LoginManagerSystemd.prototype);
var LoginManagerDummy = class {
getCurrentSessionProxy(callback) {
getCurrentSessionProxy(_callback) {
// we could return a DummySession object that fakes whatever callers
// expect (at the time of writing: connect() and connectSignal()
// methods), but just never calling the callback should be safer

View File

@ -1,7 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ModemBase, ModemGsm, ModemCdma, BroadbandModem */
const { Gio, NMA } = imports.gi;
const Signals = imports.signals;
const { Gio, GObject, NM, NMA } = imports.gi;
const { loadInterfaceXML } = imports.misc.fileUtils;
@ -84,9 +84,9 @@ function _findProviderForSid(sid) {
}
//------------------------------------------------------------------------------
// Support for the old ModemManager interface (MM < 0.7)
//------------------------------------------------------------------------------
// ----------------------------------------------------- //
// Support for the old ModemManager interface (MM < 0.7) //
// ----------------------------------------------------- //
// The following are not the complete interfaces, just the methods we need
@ -98,21 +98,46 @@ const ModemGsmNetworkProxy = Gio.DBusProxy.makeProxyWrapper(ModemGsmNetworkInter
const ModemCdmaInterface = loadInterfaceXML('org.freedesktop.ModemManager.Modem.Cdma');
const ModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(ModemCdmaInterface);
var ModemGsm = class {
constructor(path) {
this._proxy = new ModemGsmNetworkProxy(Gio.DBus.system, 'org.freedesktop.ModemManager', path);
var ModemBase = GObject.registerClass({
GTypeFlags: GObject.TypeFlags.ABSTRACT,
Properties: {
'operator-name': GObject.ParamSpec.string(
'operator-name', 'operator-name', 'operator-name',
GObject.ParamFlags.READABLE,
null),
'signal-quality': GObject.ParamSpec.int(
'signal-quality', 'signal-quality', 'signal-quality',
GObject.ParamFlags.READABLE,
0, 100, 0),
},
}, class ModemBase extends GObject.Object {
_setOperatorName(operatorName) {
if (this.operator_name == operatorName)
return;
this.operator_name = operatorName;
this.notify('operator-name');
}
this.signal_quality = 0;
this.operator_name = null;
_setSignalQuality(signalQuality) {
if (this.signal_quality == signalQuality)
return;
this.signal_quality = signalQuality;
this.notify('signal-quality');
}
});
var ModemGsm = GObject.registerClass(
class ModemGsm extends ModemBase {
_init(path) {
super._init();
this._proxy = new ModemGsmNetworkProxy(Gio.DBus.system, 'org.freedesktop.ModemManager', path);
// Code is duplicated because the function have different signatures
this._proxy.connectSignal('SignalQuality', (proxy, sender, [quality]) => {
this.signal_quality = quality;
this.emit('notify::signal-quality');
this._setSignalQuality(quality);
});
this._proxy.connectSignal('RegistrationInfo', (proxy, sender, [status, code, name]) => {
this.operator_name = _findProviderForMccMnc(name, code);
this.emit('notify::operator-name');
this._proxy.connectSignal('RegistrationInfo', (proxy, sender, [_status, code, name]) => {
this._setOperatorName(_findProviderForMccMnc(name, code));
});
this._proxy.GetRegistrationInfoRemote(([result], err) => {
if (err) {
@ -120,33 +145,29 @@ var ModemGsm = class {
return;
}
let [status, code, name] = result;
this.operator_name = _findProviderForMccMnc(name, code);
this.emit('notify::operator-name');
let [status_, code, name] = result;
this._setOperatorName(_findProviderForMccMnc(name, code));
});
this._proxy.GetSignalQualityRemote((result, err) => {
if (err) {
// it will return an error if the device is not connected
this.signal_quality = 0;
this._setSignalQuality(0);
} else {
let [quality] = result;
this.signal_quality = quality;
this._setSignalQuality(quality);
}
this.emit('notify::signal-quality');
});
}
};
Signals.addSignalMethods(ModemGsm.prototype);
});
var ModemCdma = class {
constructor(path) {
var ModemCdma = GObject.registerClass(
class ModemCdma extends ModemBase {
_init(path) {
super._init();
this._proxy = new ModemCdmaProxy(Gio.DBus.system, 'org.freedesktop.ModemManager', path);
this.signal_quality = 0;
this.operator_name = null;
this._proxy.connectSignal('SignalQuality', (proxy, sender, params) => {
this.signal_quality = params[0];
this.emit('notify::signal-quality');
this._setSignalQuality(params[0]);
// receiving this signal means the device got activated
// and we can finally call GetServingSystem
@ -156,12 +177,11 @@ var ModemCdma = class {
this._proxy.GetSignalQualityRemote((result, err) => {
if (err) {
// it will return an error if the device is not connected
this.signal_quality = 0;
this._setSignalQuality(0);
} else {
let [quality] = result;
this.signal_quality = quality;
this._setSignalQuality(quality);
}
this.emit('notify::signal-quality');
});
}
@ -169,22 +189,19 @@ var ModemCdma = class {
this._proxy.GetServingSystemRemote(([result], err) => {
if (err) {
// it will return an error if the device is not connected
this.operator_name = null;
this._setOperatorName(null);
} else {
let [bandClass, band, sid] = result;
this.operator_name = _findProviderForSid(sid);
let [bandClass_, band_, sid] = result;
this._setOperatorName(_findProviderForSid(sid));
}
this.emit('notify::operator-name');
});
}
};
Signals.addSignalMethods(ModemCdma.prototype);
});
//------------------------------------------------------------------------------
// Support for the new ModemManager1 interface (MM >= 0.7)
//------------------------------------------------------------------------------
// ------------------------------------------------------- //
// Support for the new ModemManager1 interface (MM >= 0.7) //
// ------------------------------------------------------- //
const BroadbandModemInterface = loadInterfaceXML('org.freedesktop.ModemManager1.Modem');
const BroadbandModemProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemInterface);
@ -195,12 +212,20 @@ const BroadbandModem3gppProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModem3gp
const BroadbandModemCdmaInterface = loadInterfaceXML('org.freedesktop.ModemManager1.Modem.ModemCdma');
const BroadbandModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemCdmaInterface);
var BroadbandModem = class {
constructor(path, capabilities) {
this._proxy = new BroadbandModemProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
var BroadbandModem = GObject.registerClass({
Properties: {
'capabilities': GObject.ParamSpec.flags(
'capabilities', 'capabilities', 'capabilities',
GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
NM.DeviceModemCapabilities.$gtype,
NM.DeviceModemCapabilities.NONE),
},
}, class BroadbandModem extends ModemBase {
_init(path, capabilities) {
super._init({ capabilities });
this._proxy = new BroadbandModemProxy(Gio.DBus.system, 'org.freedesktop.ModemManager', path);
this._proxy_3gpp = new BroadbandModem3gppProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
this._proxy_cdma = new BroadbandModemCdmaProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
this._capabilities = capabilities;
this._proxy.connect('g-properties-changed', (proxy, properties) => {
if ('SignalQuality' in properties.deep_unpack())
@ -224,9 +249,8 @@ var BroadbandModem = class {
}
_reloadSignalQuality() {
let [quality, recent] = this._proxy.SignalQuality;
this.signal_quality = quality;
this.emit('notify::signal-quality');
let [quality, recent_] = this.SignalQuality;
this._setSignalQuality(quality);
}
_reloadOperatorName() {
@ -240,8 +264,7 @@ var BroadbandModem = class {
newName += this.operator_name_cdma;
}
this.operator_name = newName;
this.emit('notify::operator-name');
this._setOperatorName(newName);
}
_reload3gppOperatorName() {
@ -256,5 +279,4 @@ var BroadbandModem = class {
this.operator_name_cdma = _findProviderForSid(sid);
this._reloadOperatorName();
}
};
Signals.addSignalMethods(BroadbandModem.prototype);
});

View File

@ -192,9 +192,8 @@ var ObjectManager = class {
_onNameAppeared() {
this._managerProxy.GetManagedObjectsRemote((result, error) => {
if (!result) {
if (error) {
if (error)
logError(error, `could not get remote objects for service ${this._serviceName} path ${this._managerPath}`);
}
this._tryToCompleteLoad();
return;

View File

@ -1,6 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
/* exported parse */
// parse:
// @params: caller-provided parameter object, or %null
@ -18,12 +17,12 @@ const Lang = imports.lang;
// @params and @defaults
function parse(params = {}, defaults, allowExtras) {
if (!allowExtras) {
for (let prop in params)
for (let prop in params) {
if (!(prop in defaults))
throw new Error(`Unrecognized parameter "${prop}"`);
}
}
let defaultsCopy = {};
Lang.copyProperties(defaults, defaultsCopy);
let defaultsCopy = Object.assign({}, defaults);
return Object.assign(defaultsCopy, params);
}

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported PermissionStore */
const Gio = imports.gi.Gio;

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getSmartcardManager */
const Gio = imports.gi.Gio;
const Signals = imports.signals;
@ -71,11 +72,10 @@ var SmartcardManager = class {
if ('IsInserted' in properties.deep_unpack()) {
this._updateToken(token);
if (token.IsInserted) {
if (token.IsInserted)
this.emit('smartcard-inserted', token);
} else {
else
this.emit('smartcard-removed', token);
}
}
});

View File

@ -1,3 +1,4 @@
/* exported getDefault */
const { AccountsService, Clutter, Gdm, Gio, GLib, GObject, Meta } = imports.gi;
const GnomeSession = imports.misc.gnomeSession;
@ -73,8 +74,8 @@ const SystemActions = GObject.registerClass({
'orientation-lock-icon',
'orientation-lock-icon',
GObject.ParamFlags.READWRITE,
null)
}
null),
},
}, class SystemActions extends GObject.Object {
_init() {
super._init();
@ -89,7 +90,7 @@ const SystemActions = GObject.registerClass({
iconName: 'system-shutdown-symbolic',
// Translators: A list of keywords that match the power-off action, separated by semicolons
keywords: _("power off;shutdown;reboot;restart").split(/[; ]/),
available: false
available: false,
});
this._actions.set(LOCK_SCREEN_ACTION_ID, {
// Translators: The name of the lock screen action in search
@ -97,7 +98,7 @@ const SystemActions = GObject.registerClass({
iconName: 'system-lock-screen-symbolic',
// Translators: A list of keywords that match the lock screen action, separated by semicolons
keywords: _("lock screen").split(/[; ]/),
available: false
available: false,
});
this._actions.set(LOGOUT_ACTION_ID, {
// Translators: The name of the logout action in search
@ -105,7 +106,7 @@ const SystemActions = GObject.registerClass({
iconName: 'application-exit-symbolic',
// Translators: A list of keywords that match the logout action, separated by semicolons
keywords: _("logout;log out;sign off").split(/[; ]/),
available: false
available: false,
});
this._actions.set(SUSPEND_ACTION_ID, {
// Translators: The name of the suspend action in search
@ -113,7 +114,7 @@ const SystemActions = GObject.registerClass({
iconName: 'media-playback-pause-symbolic',
// Translators: A list of keywords that match the suspend action, separated by semicolons
keywords: _("suspend;sleep").split(/[; ]/),
available: false
available: false,
});
this._actions.set(SWITCH_USER_ACTION_ID, {
// Translators: The name of the switch user action in search
@ -121,7 +122,7 @@ const SystemActions = GObject.registerClass({
iconName: 'system-switch-user-symbolic',
// Translators: A list of keywords that match the switch user action, separated by semicolons
keywords: _("switch user").split(/[; ]/),
available: false
available: false,
});
this._actions.set(LOCK_ORIENTATION_ACTION_ID, {
// Translators: The name of the lock orientation action in search
@ -129,7 +130,7 @@ const SystemActions = GObject.registerClass({
iconName: '',
// Translators: A list of keywords that match the lock orientation action, separated by semicolons
keywords: _("lock orientation;screen;rotation").split(/[; ]/),
available: false
available: false,
});
this._loginScreenSettings = new Gio.Settings({ schema_id: LOGIN_SCREEN_SCHEMA });
@ -173,13 +174,21 @@ const SystemActions = GObject.registerClass({
});
Main.layoutManager.connect('monitors-changed',
() => this._updateOrientationLock());
Gio.DBus.system.watch_name(SENSOR_BUS_NAME,
Gio.BusNameWatcherFlags.NONE,
() => this._sensorProxyAppeared(),
() => {
this._sensorProxy = null;
this._updateOrientationLock();
});
this._sensorProxy = new SensorProxy(Gio.DBus.system,
SENSOR_BUS_NAME,
SENSOR_OBJECT_PATH,
(proxy, error) => {
if (error)
log(error.message);
},
null,
Gio.DBusProxyFlags.DO_NOT_AUTO_START);
this._sensorProxy.connect('g-properties-changed', () => {
this._updateOrientationLock();
});
this._sensorProxy.connect('notify::g-name-owner', () => {
this._updateOrientationLock();
});
this._updateOrientationLock();
this._updateOrientationLockIcon();
@ -187,52 +196,47 @@ const SystemActions = GObject.registerClass({
this._sessionUpdated();
}
// eslint-disable-next-line camelcase
get can_power_off() {
return this._actions.get(POWER_OFF_ACTION_ID).available;
}
// eslint-disable-next-line camelcase
get can_suspend() {
return this._actions.get(SUSPEND_ACTION_ID).available;
}
// eslint-disable-next-line camelcase
get can_lock_screen() {
return this._actions.get(LOCK_SCREEN_ACTION_ID).available;
}
// eslint-disable-next-line camelcase
get can_switch_user() {
return this._actions.get(SWITCH_USER_ACTION_ID).available;
}
// eslint-disable-next-line camelcase
get can_logout() {
return this._actions.get(LOGOUT_ACTION_ID).available;
}
// eslint-disable-next-line camelcase
get can_lock_orientation() {
return this._actions.get(LOCK_ORIENTATION_ACTION_ID).available;
}
// eslint-disable-next-line camelcase
get orientation_lock_icon() {
return this._actions.get(LOCK_ORIENTATION_ACTION_ID).iconName;
}
_sensorProxyAppeared() {
this._sensorProxy = new SensorProxy(Gio.DBus.system, SENSOR_BUS_NAME, SENSOR_OBJECT_PATH,
(proxy, error) => {
if (error) {
log(error.message);
return;
}
this._sensorProxy.connect('g-properties-changed',
() => this._updateOrientationLock());
this._updateOrientationLock();
});
}
_updateOrientationLock() {
let available = false;
if (this._sensorProxy)
if (this._sensorProxy.g_name_owner) {
available = this._sensorProxy.HasAccelerometer &&
this._monitorManager.get_is_builtin_display_on();
}
this._actions.get(LOCK_ORIENTATION_ACTION_ID).available = available;
@ -241,8 +245,9 @@ const SystemActions = GObject.registerClass({
_updateOrientationLockIcon() {
let locked = this._orientationSettings.get_boolean('orientation-lock');
let iconName = locked ? 'rotation-locked-symbolic'
: 'rotation-allowed-symbolic';
let iconName = locked
? 'rotation-locked-symbolic'
: 'rotation-allowed-symbolic';
this._actions.get(LOCK_ORIENTATION_ACTION_ID).iconName = iconName;
this.notify('orientation-lock-icon');
@ -265,13 +270,14 @@ const SystemActions = GObject.registerClass({
getMatchingActions(terms) {
// terms is a list of strings
terms = terms.map((term) => term.toLowerCase());
terms = terms.map(term => term.toLowerCase());
let results = [];
for (let [key, { available, keywords }] of this._actions)
for (let [key, { available, keywords }] of this._actions) {
if (available && terms.every(t => keywords.some(k => k.startsWith(t))))
results.push(key);
}
return results;
}

View File

@ -1,20 +1,20 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported findUrls, spawn, spawnCommandLine, spawnApp, trySpawnCommandLine,
formatTime, formatTimeSpan, createTimeLabel, insertSorted,
makeCloseButton, ensureActorVisibleInScrollView, wiggle */
const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi;
const { Clutter, Gio, GLib, GObject, Shell, St, GnomeDesktop } = imports.gi;
const Gettext = imports.gettext;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const Params = imports.misc.params;
var SCROLL_TIME = 0.1;
var SCROLL_TIME = 100;
// http://daringfireball.net/2010/07/improved_regex_for_matching_urls
const _balancedParens = '\\([^\\s()<>]+\\)';
const _leadingJunk = '[\\s`(\\[{\'\\"<\u00AB\u201C\u2018]';
const _notTrailingJunk = '[^\\s`!()\\[\\]{};:\'\\".,<>?\u00AB\u00BB\u201C\u201D\u2018\u2019]';
const _notTrailingJunk = '[^\\s`!()\\[\\]{};:\'\\".,<>?\u00AB\u00BB\u200E\u200F\u201C\u201D\u2018\u2019\u202A\u202C]';
const _urlRegexp = new RegExp(
`(^|${_leadingJunk})` +
@ -75,7 +75,7 @@ function spawn(argv) {
// occur when trying to parse or start the program.
function spawnCommandLine(commandLine) {
try {
let [success, argv] = GLib.shell_parse_argv(commandLine);
let [success_, argv] = GLib.shell_parse_argv(commandLine);
trySpawn(argv);
} catch (err) {
_handleSpawnError(commandLine, err);
@ -104,11 +104,11 @@ function spawnApp(argv) {
// Runs @argv in the background. If launching @argv fails,
// this will throw an error.
function trySpawn(argv) {
var success, pid;
var success_, pid;
try {
[success, pid] = GLib.spawn_async(null, argv, null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
null);
[success_, pid] = GLib.spawn_async(null, argv, null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
null);
} catch (err) {
/* Rewrite the error in case of ENOENT */
if (err.matches(GLib.SpawnError, GLib.SpawnError.NOENT)) {
@ -121,12 +121,15 @@ function trySpawn(argv) {
// We are only interested in the part in the parentheses. (And
// we can't pattern match the text, since it gets localized.)
let message = err.message.replace(/.*\((.+)\)/, '$1');
throw new (err.constructor)({ code: err.code,
message: message });
throw new err.constructor({ code: err.code, message });
} else {
throw err;
}
}
// Async call, we don't need the reply though
GnomeDesktop.start_systemd_scope(argv[0], pid, null, null, null, () => {});
// Dummy child watch; we don't want to double-fork internally
// because then we lose the parent-child relationship, which
// can break polkit. See https://bugzilla.redhat.com//show_bug.cgi?id=819275
@ -139,10 +142,10 @@ function trySpawn(argv) {
// Runs @commandLine in the background. If launching @commandLine
// fails, this will throw an error.
function trySpawnCommandLine(commandLine) {
let success, argv;
let success_, argv;
try {
[success, argv] = GLib.shell_parse_argv(commandLine);
[success_, argv] = GLib.shell_parse_argv(commandLine);
} catch (err) {
// Replace "Error invoking GLib.shell_parse_argv: " with
// something nicer
@ -172,23 +175,28 @@ function formatTimeSpan(date) {
if (minutesAgo < 5)
return _("Just now");
if (hoursAgo < 1)
if (hoursAgo < 1) {
return Gettext.ngettext("%d minute ago",
"%d minutes ago", minutesAgo).format(minutesAgo);
if (daysAgo < 1)
}
if (daysAgo < 1) {
return Gettext.ngettext("%d hour ago",
"%d hours ago", hoursAgo).format(hoursAgo);
}
if (daysAgo < 2)
return _("Yesterday");
if (daysAgo < 15)
if (daysAgo < 15) {
return Gettext.ngettext("%d day ago",
"%d days ago", daysAgo).format(daysAgo);
if (weeksAgo < 8)
}
if (weeksAgo < 8) {
return Gettext.ngettext("%d week ago",
"%d weeks ago", weeksAgo).format(weeksAgo);
if (yearsAgo < 1)
}
if (yearsAgo < 1) {
return Gettext.ngettext("%d month ago",
"%d months ago", monthsAgo).format(monthsAgo);
}
return Gettext.ngettext("%d year ago",
"%d years ago", yearsAgo).format(yearsAgo);
}
@ -213,7 +221,10 @@ function formatTime(time, params) {
_desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
let clockFormat = _desktopSettings.get_string('clock-format');
params = Params.parse(params, { timeOnly: false });
params = Params.parse(params, {
timeOnly: false,
ampm: true,
});
if (clockFormat == '24h') {
// Show only the time if date is on today
@ -246,7 +257,7 @@ function formatTime(time, params) {
format = N_("%B %-d %Y, %H\u2236%M");
} else {
// Show only the time if date is on today
if (daysAgo < 1 || params.timeOnly)
if (daysAgo < 1 || params.timeOnly) // eslint-disable-line no-lonely-if
/* Translators: Time in 12h format */
format = N_("%l\u2236%M %p");
// Show the word "Yesterday" and time if date is on yesterday
@ -275,6 +286,11 @@ function formatTime(time, params) {
format = N_("%B %-d %Y, %l\u2236%M %p");
}
// Time in short 12h format, without the equivalent of "AM" or "PM"; used
// when it is clear from the context
if (!params.ampm)
format = format.replace(/\s*%p/g, '');
let formattedTime = date.format(Shell.util_translate_time_string(format));
// prepend LTR-mark to colon/ratio to force a text direction on times
return formattedTime.replace(/([:\u2236])/g, '\u200e$1');
@ -313,7 +329,8 @@ function lowerBound(array, val, cmp) {
if (array.length == 0)
return 0;
min = 0; max = array.length;
min = 0;
max = array.length;
while (min < (max - 1)) {
mid = Math.floor((min + max) / 2);
v = cmp(array[mid], val);
@ -324,7 +341,7 @@ function lowerBound(array, val, cmp) {
max = mid;
}
return (min == max || cmp(array[min], val) < 0) ? max : min;
return min == max || cmp(array[min], val) < 0 ? max : min;
}
// insertSorted:
@ -345,19 +362,13 @@ function insertSorted(array, val, cmp) {
var CloseButton = GObject.registerClass(
class CloseButton extends St.Button {
_init(boxpointer) {
super._init({ style_class: 'notification-close' });
// This is a bit tricky. St.Bin has its own x-align/y-align properties
// that compete with Clutter's properties. This should be fixed for
// Clutter 2.0. Since St.Bin doesn't define its own setters, the
// setters are a workaround to get Clutter's version.
this.set_x_align(Clutter.ActorAlign.END);
this.set_y_align(Clutter.ActorAlign.START);
// XXX Clutter 2.0 workaround: ClutterBinLayout needs expand
// to respect the alignments.
this.set_x_expand(true);
this.set_y_expand(true);
super._init({
style_class: 'notification-close',
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.END,
y_align: Clutter.ActorAlign.START,
});
this._boxPointer = boxpointer;
if (boxpointer)
@ -395,7 +406,7 @@ function makeCloseButton(boxpointer) {
function ensureActorVisibleInScrollView(scrollView, actor) {
let adjustment = scrollView.vscroll.adjustment;
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
let [value, lower_, upper, stepIncrement_, pageIncrement_, pageSize] = adjustment.get_values();
let offset = 0;
let vfade = scrollView.get_effect("fade");
@ -410,7 +421,7 @@ function ensureActorVisibleInScrollView(scrollView, actor) {
if (!parent)
throw new Error("actor not in scroll view");
let box = parent.get_allocation_box();
box = parent.get_allocation_box();
y1 += box.y1;
y2 += box.y1;
parent = parent.get_parent();
@ -423,97 +434,42 @@ function ensureActorVisibleInScrollView(scrollView, actor) {
else
return;
Tweener.addTween(adjustment,
{ value: value,
time: SCROLL_TIME,
transition: 'easeOutQuad' });
adjustment.ease(value, {
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
duration: SCROLL_TIME,
});
}
var AppSettingsMonitor = class {
constructor(appId, schemaId) {
this._appId = appId;
this._schemaId = schemaId;
function wiggle(actor, params) {
params = Params.parse(params, {
offset: 0,
duration: 0,
wiggleCount: 0,
});
actor.translation_x = 0;
this._app = null;
this._settings = null;
this._handlers = [];
this._schemaSource = Gio.SettingsSchemaSource.get_default();
this._appSystem = Shell.AppSystem.get_default();
this._appSystem.connect('installed-changed',
this._onInstalledChanged.bind(this));
this._onInstalledChanged();
}
get available() {
return this._app != null && this._settings != null;
}
activateApp() {
if (this._app)
this._app.activate();
}
watchSetting(key, callback) {
let handler = { id: 0, key: key, callback: callback };
this._handlers.push(handler);
this._connectHandler(handler);
}
_connectHandler(handler) {
if (!this._settings || handler.id > 0)
return;
handler.id = this._settings.connect(`changed::${handler.key}`,
handler.callback);
handler.callback(this._settings, handler.key);
}
_disconnectHandler(handler) {
if (this._settings && handler.id > 0)
this._settings.disconnect(handler.id);
handler.id = 0;
}
_onInstalledChanged() {
let hadApp = (this._app != null);
this._app = this._appSystem.lookup_app(this._appId);
let haveApp = (this._app != null);
if (hadApp == haveApp)
return;
if (haveApp)
this._checkSettings();
else
this._setSettings(null);
}
_setSettings(settings) {
this._handlers.forEach((handler) => this._disconnectHandler(handler));
let hadSettings = (this._settings != null);
this._settings = settings;
let haveSettings = (this._settings != null);
this._handlers.forEach((handler) => this._connectHandler(handler));
if (hadSettings != haveSettings)
this.emit('available-changed');
}
_checkSettings() {
let schema = this._schemaSource.lookup(this._schemaId, true);
if (schema) {
this._setSettings(new Gio.Settings({ settings_schema: schema }));
} else if (this._app) {
Mainloop.timeout_add_seconds(1, () => {
this._checkSettings();
return GLib.SOURCE_REMOVE;
// Accelerate before wiggling
actor.ease({
translation_x: -params.offset,
duration: params.duration,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
// Wiggle
actor.ease({
translation_x: params.offset,
duration: params.duration,
mode: Clutter.AnimationMode.LINEAR,
repeatCount: params.wiggleCount,
autoReverse: true,
onComplete: () => {
// Decelerate and return to the original position
actor.ease({
translation_x: 0,
duration: params.duration,
mode: Clutter.AnimationMode.EASE_IN_QUAD,
});
},
});
}
}
};
Signals.addSignalMethods(AppSettingsMonitor.prototype);
},
});
}

View File

@ -1,10 +1,19 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const { Geoclue, Gio, GLib, GWeather } = imports.gi;
const { Geoclue, Gio, GLib, GWeather, Shell } = imports.gi;
const Signals = imports.signals;
const PermissionStore = imports.misc.permissionStore;
const Util = imports.misc.util;
const { loadInterfaceXML } = imports.misc.fileUtils;
const WeatherIntegrationIface = loadInterfaceXML('org.gnome.Shell.WeatherIntegration');
const WEATHER_BUS_NAME = 'org.gnome.Weather';
const WEATHER_OBJECT_PATH = '/org/gnome/Weather';
const WEATHER_INTEGRATION_IFACE = 'org.gnome.Shell.WeatherIntegration';
const WEATHER_APP_ID = 'org.gnome.Weather.desktop';
// Minimum time between updates to show loading indication
var UPDATE_THRESHOLD = 10 * GLib.TIME_SPAN_MINUTE;
@ -23,6 +32,7 @@ var WeatherClient = class {
this._gclueStarting = false;
this._gclueLocationChangedId = 0;
this._needsAuth = true;
this._weatherAuthorized = false;
this._permStore = new PermissionStore.PermissionStore((proxy, error) => {
if (error) {
@ -38,11 +48,11 @@ var WeatherClient = class {
return;
}
this._permStore.LookupRemote('gnome', 'geolocation', (res, error) => {
if (error)
log(`Error looking up permission: ${error.message}`);
this._permStore.LookupRemote('gnome', 'geolocation', (res, err) => {
if (err)
log(`Error looking up permission: ${err.message}`);
let [perms, data] = error ? [{}, null] : res;
let [perms, data] = err ? [{}, null] : res;
let params = ['gnome', 'geolocation', false, data, perms];
this._onPermStoreChanged(this._permStore, '', params);
});
@ -66,17 +76,38 @@ var WeatherClient = class {
this.emit('changed');
});
this._weatherAppMon = new Util.AppSettingsMonitor('org.gnome.Weather.desktop',
'org.gnome.Weather');
this._weatherAppMon.connect('available-changed', () => this.emit('changed'));
this._weatherAppMon.watchSetting('automatic-location',
this._onAutomaticLocationChanged.bind(this));
this._weatherAppMon.watchSetting('locations',
this._onLocationsChanged.bind(this));
this._weatherApp = null;
this._weatherProxy = null;
let nodeInfo = Gio.DBusNodeInfo.new_for_xml(WeatherIntegrationIface);
Gio.DBusProxy.new(
Gio.DBus.session,
Gio.DBusProxyFlags.DO_NOT_AUTO_START | Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES,
nodeInfo.lookup_interface(WEATHER_INTEGRATION_IFACE),
WEATHER_BUS_NAME,
WEATHER_OBJECT_PATH,
WEATHER_INTEGRATION_IFACE,
null,
this._onWeatherProxyReady.bind(this));
this._settings = new Gio.Settings({
schema_id: 'org.gnome.shell.weather',
});
this._settings.connect('changed::automatic-location',
this._onAutomaticLocationChanged.bind(this));
this._onAutomaticLocationChanged();
this._settings.connect('changed::locations',
this._onLocationsChanged.bind(this));
this._onLocationsChanged();
this._appSystem = Shell.AppSystem.get_default();
this._appSystem.connect('installed-changed',
this._onInstalledChanged.bind(this));
this._onInstalledChanged();
}
get available() {
return this._weatherAppMon.available;
return this._weatherApp != null;
}
get loading() {
@ -92,7 +123,8 @@ var WeatherClient = class {
}
activateApp() {
this._weatherAppMon.activateApp();
if (this._weatherApp)
this._weatherApp.activate();
}
update() {
@ -111,7 +143,46 @@ var WeatherClient = class {
get _useAutoLocation() {
return this._autoLocationRequested &&
this._locationSettings.get_boolean('enabled') &&
this._weatherAuthorized;
(!this._needsAuth || this._weatherAuthorized);
}
_onWeatherProxyReady(o, res) {
try {
this._weatherProxy = Gio.DBusProxy.new_finish(res);
} catch (e) {
log(`Failed to create GNOME Weather proxy: ${e}`);
return;
}
this._weatherProxy.connect('g-properties-changed',
this._onWeatherPropertiesChanged.bind(this));
this._onWeatherPropertiesChanged();
}
_onWeatherPropertiesChanged() {
if (this._weatherProxy.g_name_owner == null)
return;
this._settings.set_boolean('automatic-location',
this._weatherProxy.AutomaticLocation);
this._settings.set_value('locations',
new GLib.Variant('av', this._weatherProxy.Locations));
}
_onInstalledChanged() {
let hadApp = this._weatherApp != null;
this._weatherApp = this._appSystem.lookup_app(WEATHER_APP_ID);
let haveApp = this._weatherApp != null;
if (hadApp !== haveApp)
this.emit('changed');
let neededAuth = this._needsAuth;
this._needsAuth = this._weatherApp === null ||
this._weatherApp.app_info.has_key('X-Flatpak');
if (neededAuth !== this._needsAuth)
this._updateAutoLocation();
}
_loadInfo() {
@ -142,7 +213,7 @@ var WeatherClient = class {
this._weatherInfo.abort();
this._weatherInfo.set_location(location);
this._locationValid = (location != null);
this._locationValid = location != null;
this._weatherInfo.set_enabled_providers(location ? this._providers : 0);
@ -198,8 +269,8 @@ var WeatherClient = class {
this._setLocation(location);
}
_onAutomaticLocationChanged(settings, key) {
let useAutoLocation = settings.get_boolean(key);
_onAutomaticLocationChanged() {
let useAutoLocation = this._settings.get_boolean('automatic-location');
if (this._autoLocationRequested == useAutoLocation)
return;
@ -217,8 +288,9 @@ var WeatherClient = class {
this._setLocation(this._mostRecentLocation);
}
_onLocationsChanged(settings, key) {
let serialized = settings.get_value(key).deep_unpack().shift();
_onLocationsChanged() {
let locations = this._settings.get_value('locations').deep_unpack();
let serialized = locations.shift();
let mostRecentLocation = null;
if (serialized)
@ -234,7 +306,7 @@ var WeatherClient = class {
}
_onPermStoreChanged(proxy, sender, params) {
let [table, id, deleted, data, perms] = params;
let [table, id, deleted_, data_, perms] = params;
if (table != 'gnome' || id != 'geolocation')
return;

View File

@ -1,4 +1,9 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported run, script_overviewShowStart, script_overviewShowDone,
script_applicationsShowStart, script_applicationsShowDone,
script_afterShowHide, malloc_usedSize, glx_swapComplete,
clutter_stagePaintDone */
/* eslint camelcase: ["error", { properties: "never", allow: ["^script_", "^malloc", "^glx", "^clutter"] }] */
const System = imports.system;
@ -52,7 +57,7 @@ var METRICS = {
units: "us" },
applicationsShowTimeSubsequent:
{ description: "Time to switch to applications view, second time",
units: "us" }
units: "us" },
};
let WINDOW_CONFIGS = [
@ -62,7 +67,7 @@ let WINDOW_CONFIGS = [
{ width: 640, height: 480, alpha: false, maximized: true, count: 5, metric: 'overviewFps5Maximized' },
{ width: 640, height: 480, alpha: false, maximized: true, count: 10, metric: 'overviewFps10Maximized' },
{ width: 640, height: 480, alpha: true, maximized: false, count: 5, metric: 'overviewFps5Alpha' },
{ width: 640, height: 480, alpha: true, maximized: false, count: 10, metric: 'overviewFps10Alpha' }
{ width: 640, height: 480, alpha: true, maximized: false, count: 10, metric: 'overviewFps10Alpha' },
];
function *run() {
@ -89,11 +94,12 @@ function *run() {
let config = WINDOW_CONFIGS[i / 2];
yield Scripting.destroyTestWindows();
for (let k = 0; k < config.count; k++)
for (let k = 0; k < config.count; k++) {
yield Scripting.createTestWindow({ width: config.width,
height: config.height,
alpha: config.alpha,
maximized: config.maximized });
}
yield Scripting.waitTestWindows();
yield Scripting.sleep(1000);
@ -121,10 +127,12 @@ function *run() {
for (let i = 0; i < 2; i++) {
Scripting.scriptEvent('applicationsShowStart');
Main.overview._dash.showAppsButton.checked = true;
// eslint-disable-next-line require-atomic-updates
Main.overview.dash.showAppsButton.checked = true;
yield Scripting.waitLeisure();
Scripting.scriptEvent('applicationsShowDone');
Main.overview._dash.showAppsButton.checked = false;
// eslint-disable-next-line require-atomic-updates
Main.overview.dash.showAppsButton.checked = false;
yield Scripting.waitLeisure();
}
}
@ -147,7 +155,7 @@ function script_overviewShowStart(time) {
overviewFrames = 0;
}
function script_overviewShowDone(time) {
function script_overviewShowDone(_time) {
// We've set up the state at the end of the zoom out, but we
// need to wait for one more frame to paint before we count
// ourselves as done.
@ -166,12 +174,11 @@ function script_applicationsShowDone(time) {
METRICS.applicationsShowTimeSubsequent.value = time - applicationsShowStart;
}
function script_afterShowHide(time) {
if (overviewShowCount == 1) {
function script_afterShowHide(_time) {
if (overviewShowCount == 1)
METRICS.usedAfterOverview.value = mallocUsedSize;
} else {
else
METRICS.leakedAfterOverview.value = mallocUsedSize - METRICS.usedAfterOverview.value;
}
}
function malloc_usedSize(time, bytes) {

View File

@ -1,3 +1,12 @@
/* exported run, script_desktopShown, script_overviewShowStart,
script_overviewShowDone, script_applicationsShowStart,
script_applicationsShowDone, script_mainViewDrawStart,
script_mainViewDrawDone, script_overviewDrawStart,
script_overviewDrawDone, script_redrawTestStart,
script_redrawTestDone, script_collectTimings,
script_geditLaunch, script_geditFirstFrame,
clutter_stagePaintStart, clutter_paintCompletedTimestamp */
/* eslint camelcase: ["error", { properties: "never", allow: ["^script_", "^clutter"] }] */
const { Clutter, Gio, Shell } = imports.gi;
const Main = imports.ui.main;
const Scripting = imports.ui.scripting;
@ -38,7 +47,7 @@ function waitAndDraw(milliseconds) {
let timeline = new Clutter.Timeline({ duration: milliseconds });
timeline.start();
timeline.connect('new-frame', (timeline, frame) => {
timeline.connect('new-frame', (_timeline, _frame) => {
global.stage.queue_redraw();
});
@ -48,7 +57,7 @@ function waitAndDraw(milliseconds) {
cb();
});
return callback => cb = callback;
return callback => (cb = callback);
}
function waitSignal(object, signal) {
@ -60,7 +69,7 @@ function waitSignal(object, signal) {
cb();
});
return callback => cb = callback;
return callback => (cb = callback);
}
function extractBootTimestamp() {
@ -73,8 +82,8 @@ function extractBootTimestamp() {
let result = null;
let datastream = Gio.DataInputStream.new(sp.get_stdout_pipe());
while (true) {
let [line, length] = datastream.read_line_utf8(null);
while (true) { // eslint-disable-line no-constant-condition
let [line, length_] = datastream.read_line_utf8(null);
if (line === null)
break;
@ -105,7 +114,7 @@ function *run() {
Scripting.scriptEvent('desktopShown');
let interfaceSettings = new Gio.Settings({
schema_id: 'org.gnome.desktop.interface'
schema_id: 'org.gnome.desktop.interface',
});
interfaceSettings.set_boolean('enable-animations', false);
@ -117,7 +126,8 @@ function *run() {
yield Scripting.sleep(1000);
Scripting.scriptEvent('applicationsShowStart');
Main.overview._dash.showAppsButton.checked = true;
// eslint-disable-next-line require-atomic-updates
Main.overview.dash.showAppsButton.checked = true;
yield Scripting.waitLeisure();
Scripting.scriptEvent('applicationsShowDone');
@ -127,9 +137,9 @@ function *run() {
Main.overview.hide();
yield Scripting.waitLeisure();
////////////////////////////////////////
// Tests of redraw speed
////////////////////////////////////////
// --------------------- //
// Tests of redraw speed //
// --------------------- //
global.frame_timestamps = true;
global.frame_finish_timestamp = true;
@ -176,8 +186,6 @@ function *run() {
yield Scripting.sleep(1000);
////////////////////////////////////////
let appSys = Shell.AppSystem.get_default();
let app = appSys.lookup_app('org.gnome.gedit.desktop');
@ -234,31 +242,31 @@ function script_applicationsShowDone(time) {
METRICS.applicationsShowTime.value = time - applicationsShowStart;
}
function script_mainViewDrawStart(time) {
function script_mainViewDrawStart(_time) {
redrawTiming = 'mainView';
}
function script_mainViewDrawDone(time) {
function script_mainViewDrawDone(_time) {
redrawTiming = null;
}
function script_overviewDrawStart(time) {
function script_overviewDrawStart(_time) {
redrawTiming = 'overview';
}
function script_overviewDrawDone(time) {
function script_overviewDrawDone(_time) {
redrawTiming = null;
}
function script_redrawTestStart(time) {
function script_redrawTestStart(_time) {
redrawTiming = 'application';
}
function script_redrawTestDone(time) {
function script_redrawTestDone(_time) {
redrawTiming = null;
}
function script_collectTimings(time) {
function script_collectTimings(_time) {
for (let timing in redrawTimes) {
let times = redrawTimes[timing];
times.sort((a, b) => a - b);

View File

@ -1,3 +1,4 @@
/* exported main */
const Format = imports.format;
const Gettext = imports.gettext;
const { Gio, GLib, GObject, Gtk, Pango, Soup, WebKit2: WebKit } = imports.gi;
@ -10,17 +11,17 @@ const { loadInterfaceXML } = imports.misc.fileUtils;
const PortalHelperResult = {
CANCELLED: 0,
COMPLETED: 1,
RECHECK: 2
RECHECK: 2,
};
const PortalHelperSecurityLevel = {
NOT_YET_DETERMINED: 0,
SECURE: 1,
INSECURE: 2
INSECURE: 2,
};
const CONNECTIVITY_CHECK_HOST = 'nmcheck.gnome.org';
const CONNECTIVITY_CHECK_URI = 'http://' + CONNECTIVITY_CHECK_HOST;
const CONNECTIVITY_CHECK_URI = `http://${CONNECTIVITY_CHECK_HOST}`;
const CONNECTIVITY_RECHECK_RATELIMIT_TIMEOUT = 30 * GLib.USEC_PER_SEC;
const HelperDBusInterface = loadInterfaceXML('org.gnome.Shell.PortalHelper');
@ -91,7 +92,7 @@ class PortalHeaderBar extends Gtk.HeaderBar {
var PortalWindow = GObject.registerClass(
class PortalWindow extends Gtk.ApplicationWindow {
_init(application, url, timestamp, doneCallback) {
super._init({ application: application });
super._init({ application });
this.connect('delete-event', this.destroyWindow.bind(this));
this._headerBar = new PortalHeaderBar();
@ -151,7 +152,7 @@ class PortalWindow extends Gtk.ApplicationWindow {
this._webView.load_uri(this._originalUrl);
}
vfunc_delete_event(event) {
vfunc_delete_event(_event) {
if (this._recheckAtExit)
this._doneCallback(PortalHelperResult.RECHECK);
else
@ -177,7 +178,7 @@ class PortalWindow extends Gtk.ApplicationWindow {
this._headerBar.setSecurityIcon(PortalHelperSecurityLevel.INSECURE);
}
_onLoadFailedWithTlsErrors(view, failingURI, certificate, errors) {
_onLoadFailedWithTlsErrors(view, failingURI, certificate, _errors) {
this._headerBar.setSecurityIcon(PortalHelperSecurityLevel.INSECURE);
let uri = new Soup.URI(failingURI);
this._webContext.allow_tls_certificate_for_host(certificate, uri.get_host());
@ -286,7 +287,7 @@ class WebPortalHelper extends Gtk.Application {
}
Authenticate(connection, url, timestamp) {
this._queue.push({ connection: connection, url: url, timestamp: timestamp });
this._queue.push({ connection, url, timestamp });
this._processQueue();
}

View File

@ -1,3 +1,4 @@
/* exported AccessDialogDBus */
const { Clutter, Gio, GLib, GObject, Shell } = imports.gi;
const CheckBox = imports.ui.checkBox;
@ -12,7 +13,7 @@ const AccessIface = loadInterfaceXML('org.freedesktop.impl.portal.Access');
var DialogResponse = {
OK: 0,
CANCEL: 1,
CLOSED: 2
CLOSED: 2,
};
var AccessDialog = GObject.registerClass(
@ -34,7 +35,7 @@ class AccessDialog extends ModalDialog.ModalDialog {
_buildLayout(title, subtitle, body, options) {
// No support for non-modal system dialogs, so ignore the option
//let modal = options['modal'] || true;
// let modal = options['modal'] || true;
let denyLabel = options['deny_label'] || _("Deny Access");
let grantLabel = options['grant_label'] || _("Grant Access");
let iconName = options['icon'] || null;
@ -55,8 +56,8 @@ class AccessDialog extends ModalDialog.ModalDialog {
let check = new CheckBox.CheckBox();
check.getLabelActor().text = name;
check.actor.checked = selected == "true";
content.insertBeforeBody(check.actor);
check.checked = selected == "true";
content.insertBeforeBody(check);
this._choices.set(id, check);
}
@ -79,7 +80,7 @@ class AccessDialog extends ModalDialog.ModalDialog {
this._requestExported = this._request.export(connection, this._handle);
}
CloseAsync(invocation, params) {
CloseAsync(invocation, _params) {
if (this._invocation.get_sender() != invocation.get_sender()) {
invocation.return_error_literal(Gio.DBusError,
Gio.DBusError.ACCESS_DENIED,
@ -98,7 +99,7 @@ class AccessDialog extends ModalDialog.ModalDialog {
let results = {};
if (response == DialogResponse.OK) {
for (let [id, check] of this._choices) {
let checked = check.actor.checked ? 'true' : 'false';
let checked = check.checked ? 'true' : 'false';
results[id] = new GLib.Variant('s', checked);
}
}
@ -132,7 +133,7 @@ var AccessDialogDBus = class {
return;
}
let [handle, appId, parentWindow, title, subtitle, body, options] = params;
let [handle, appId, parentWindow_, title, subtitle, body, options] = params;
// We probably want to use parentWindow and global.display.focus_window
// for this check in the future
if (appId && `${appId}.desktop` != this._windowTracker.focus_app.id) {
@ -146,7 +147,7 @@ var AccessDialogDBus = class {
subtitle, body, options);
dialog.open();
dialog.connect('closed', () => this._accessDialog = null);
dialog.connect('closed', () => (this._accessDialog = null));
this._accessDialog = dialog;
}

View File

@ -1,17 +1,17 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported AppSwitcherPopup, GroupCyclerPopup, WindowSwitcherPopup,
WindowCyclerPopup */
const { Atk, Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Main = imports.ui.main;
const SwitcherPopup = imports.ui.switcherPopup;
const Tweener = imports.ui.tweener;
var APP_ICON_HOVER_TIMEOUT = 200; // milliseconds
var THUMBNAIL_DEFAULT_SIZE = 256;
var THUMBNAIL_POPUP_TIME = 500; // milliseconds
var THUMBNAIL_FADE_TIME = 0.1; // seconds
var THUMBNAIL_FADE_TIME = 100; // milliseconds
var WINDOW_PREVIEW_SIZE = 128;
var APP_ICON_SIZE = 96;
@ -62,7 +62,7 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
this.thumbnailsVisible = false;
let apps = Shell.AppSystem.get_default().get_running ();
let apps = Shell.AppSystem.get_default().get_running();
if (apps.length == 0)
return;
@ -87,9 +87,9 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
let hPadding = leftPadding + rightPadding;
let icon = this._items[this._selectedIndex];
let [posX, posY] = icon.get_transformed_position();
let [posX] = icon.get_transformed_position();
let thumbnailCenter = posX + icon.width / 2;
let [childMinWidth, childNaturalWidth] = this._thumbnails.get_preferred_width(-1);
let [, childNaturalWidth] = this._thumbnails.get_preferred_width(-1);
childBox.x1 = Math.max(primary.x + leftPadding, Math.floor(thumbnailCenter - childNaturalWidth / 2));
if (childBox.x1 + childNaturalWidth > primary.x + primary.width - hPadding) {
let offset = childBox.x1 + childNaturalWidth - primary.width + hPadding;
@ -103,7 +103,7 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
childBox.x2 = primary.x + primary.width - rightPadding;
childBox.y1 = this._switcherList.allocation.y2 + spacing;
this._thumbnails.addClones(primary.y + primary.height - bottomPadding - childBox.y1);
let [childMinHeight, childNaturalHeight] = this._thumbnails.get_preferred_height(-1);
let [, childNaturalHeight] = this._thumbnails.get_preferred_height(-1);
childBox.y2 = childBox.y1 + childNaturalHeight;
this._thumbnails.allocate(childBox, flags);
}
@ -111,14 +111,12 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
_initialSelection(backward, binding) {
if (binding == 'switch-group') {
if (backward) {
if (backward)
this._select(0, this._items[0].cachedWindows.length - 1);
} else {
if (this._items[0].cachedWindows.length > 1)
this._select(0, 1);
else
this._select(0, 0);
}
else if (this._items[0].cachedWindows.length > 1)
this._select(0, 1);
else
this._select(0, 0);
} else if (binding == 'switch-group-backward') {
this._select(0, this._items[0].cachedWindows.length - 1);
} else if (binding == 'switch-applications-backward') {
@ -180,28 +178,27 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
this._select(this._next());
} else if (action == Meta.KeyBindingAction.SWITCH_APPLICATIONS_BACKWARD) {
this._select(this._previous());
} else if (keysym == Clutter.q) {
} else if (keysym === Clutter.KEY_q) {
this._quitApplication(this._selectedIndex);
} else if (this._thumbnailsFocused) {
if (keysym == Clutter.Left)
if (keysym === Clutter.KEY_Left)
this._select(this._selectedIndex, this._previousWindow());
else if (keysym == Clutter.Right)
else if (keysym === Clutter.KEY_Right)
this._select(this._selectedIndex, this._nextWindow());
else if (keysym == Clutter.Up)
else if (keysym === Clutter.KEY_Up)
this._select(this._selectedIndex, null, true);
else if (keysym == Clutter.w || keysym == Clutter.F4)
else if (keysym === Clutter.KEY_w || keysym === Clutter.KEY_F4)
this._closeAppWindow(this._selectedIndex, this._currentWindow);
else
return Clutter.EVENT_PROPAGATE;
} else if (keysym == Clutter.KEY_Left) {
this._select(this._previous());
} else if (keysym == Clutter.KEY_Right) {
this._select(this._next());
} else if (keysym == Clutter.KEY_Down) {
this._select(this._selectedIndex, 0);
} else {
if (keysym == Clutter.Left)
this._select(this._previous());
else if (keysym == Clutter.Right)
this._select(this._next());
else if (keysym == Clutter.Down)
this._select(this._selectedIndex, 0);
else
return Clutter.EVENT_PROPAGATE;
return Clutter.EVENT_PROPAGATE;
}
return Clutter.EVENT_STOP;
@ -291,14 +288,14 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
if (this._thumbnails)
this._destroyThumbnails();
if (this._thumbnailTimeoutId != 0)
Mainloop.source_remove(this._thumbnailTimeoutId);
GLib.source_remove(this._thumbnailTimeoutId);
}
/**
* _select:
* @app: index of the app to select
* @window: (optional) index of which of @app's windows to select
* @forceAppFocus: optional flag, see below
* @param {number} app: index of the app to select
* @param {number=} window: index of which of @app's windows to select
* @param {bool} forceAppFocus: optional flag, see below
*
* Selects the indicated @app, and optional @window, and sets
* this._thumbnailsFocused appropriately to indicate whether the
@ -326,7 +323,7 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
}
if (this._thumbnailTimeoutId != 0) {
Mainloop.source_remove(this._thumbnailTimeoutId);
GLib.source_remove(this._thumbnailTimeoutId);
this._thumbnailTimeoutId = 0;
}
@ -343,7 +340,8 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
this._thumbnails.highlight(window, forceAppFocus);
} else if (this._items[this._selectedIndex].cachedWindows.length > 1 &&
!forceAppFocus) {
this._thumbnailTimeoutId = Mainloop.timeout_add (
this._thumbnailTimeoutId = GLib.timeout_add(
GLib.PRIORITY_DEFAULT,
THUMBNAIL_POPUP_TIME,
this._timeoutPopupThumbnails.bind(this));
GLib.Source.set_name_by_id(this._thumbnailTimeoutId, '[gnome-shell] this._timeoutPopupThumbnails');
@ -360,22 +358,22 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
_destroyThumbnails() {
let thumbnailsActor = this._thumbnails;
Tweener.addTween(thumbnailsActor,
{ opacity: 0,
time: THUMBNAIL_FADE_TIME,
transition: 'easeOutQuad',
onComplete: () => {
thumbnailsActor.destroy();
this.thumbnailsVisible = false;
}
});
this._thumbnails.ease({
opacity: 0,
duration: THUMBNAIL_FADE_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
thumbnailsActor.destroy();
this.thumbnailsVisible = false;
},
});
this._thumbnails = null;
if (this._switcherList._items[this._selectedIndex])
this._switcherList._items[this._selectedIndex].remove_accessible_state (Atk.StateType.EXPANDED);
this._switcherList._items[this._selectedIndex].remove_accessible_state(Atk.StateType.EXPANDED);
}
_createThumbnails() {
this._thumbnails = new ThumbnailList (this._items[this._selectedIndex].cachedWindows);
this._thumbnails = new ThumbnailList(this._items[this._selectedIndex].cachedWindows);
this._thumbnails.connect('item-activated', this._windowActivated.bind(this));
this._thumbnails.connect('item-entered', this._windowEntered.bind(this));
this._thumbnails.connect('item-removed', this._windowRemoved.bind(this));
@ -391,38 +389,39 @@ class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
this._thumbnails.get_allocation_box();
this._thumbnails.opacity = 0;
Tweener.addTween(this._thumbnails,
{ opacity: 255,
time: THUMBNAIL_FADE_TIME,
transition: 'easeOutQuad',
onComplete: () => this.thumbnailsVisible = true
});
this._thumbnails.ease({
opacity: 255,
duration: THUMBNAIL_FADE_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this.thumbnailsVisible = true;
},
});
this._switcherList._items[this._selectedIndex].add_accessible_state (Atk.StateType.EXPANDED);
this._switcherList._items[this._selectedIndex].add_accessible_state(Atk.StateType.EXPANDED);
}
});
class CyclerHighlight {
constructor() {
var CyclerHighlight = GObject.registerClass(
class CyclerHighlight extends St.Widget {
_init() {
super._init({ layout_manager: new Clutter.BinLayout() });
this._window = null;
this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout() });
this._clone = new Clutter.Clone();
this.actor.add_actor(this._clone);
this.add_actor(this._clone);
this._highlight = new St.Widget({ style_class: 'cycler-highlight' });
this.actor.add_actor(this._highlight);
this.add_actor(this._highlight);
let coordinate = Clutter.BindCoordinate.ALL;
let constraint = new Clutter.BindConstraint({ coordinate: coordinate });
let constraint = new Clutter.BindConstraint({ coordinate });
this._clone.bind_property('source', constraint, 'source', 0);
this.actor.add_constraint(constraint);
this.add_constraint(constraint);
this.actor.connect('notify::allocation',
this._onAllocationChanged.bind(this));
this.actor.connect('destroy', this._onDestroy.bind(this));
this.connect('notify::allocation', this._onAllocationChanged.bind(this));
this.connect('destroy', this._onDestroy.bind(this));
}
set window(w) {
@ -434,8 +433,8 @@ class CyclerHighlight {
if (this._clone.source)
this._clone.source.sync_visibility();
let windowActor = this._window ? this._window.get_compositor_private()
: null;
let windowActor = this._window
? this._window.get_compositor_private() : null;
if (windowActor)
windowActor.hide();
@ -448,7 +447,7 @@ class CyclerHighlight {
this._highlight.set_size(0, 0);
this._highlight.hide();
} else {
let [x, y] = this.actor.allocation.get_origin();
let [x, y] = this.allocation.get_origin();
let rect = this._window.get_frame_rect();
this._highlight.set_size(rect.width, rect.height);
this._highlight.set_position(rect.x - x, rect.y - y);
@ -459,7 +458,7 @@ class CyclerHighlight {
_onDestroy() {
this.window = null;
}
}
});
// We don't show an actual popup, so just provide what SwitcherPopup
// expects instead of inheriting from SwitcherList
@ -469,13 +468,13 @@ var CyclerList = GObject.registerClass({
'item-removed': { param_types: [GObject.TYPE_INT] },
'item-highlighted': { param_types: [GObject.TYPE_INT] } },
}, class CyclerList extends St.Widget {
highlight(index, justOutline) {
highlight(index, _justOutline) {
this.emit('item-highlighted', index);
}
});
var CyclerPopup = GObject.registerClass({
GTypeFlags: GObject.TypeFlags.ABSTRACT
GTypeFlags: GObject.TypeFlags.ABSTRACT,
}, class CyclerPopup extends SwitcherPopup.SwitcherPopup {
_init() {
super._init();
@ -486,7 +485,7 @@ var CyclerPopup = GObject.registerClass({
return;
this._highlight = new CyclerHighlight();
global.window_group.add_actor(this._highlight.actor);
global.window_group.add_actor(this._highlight);
this._switcherList = new CyclerList();
this._switcherList.connect('item-highlighted', (list, index) => {
@ -494,9 +493,9 @@ var CyclerPopup = GObject.registerClass({
});
}
_highlightItem(index, justOutline) {
_highlightItem(index, _justOutline) {
this._highlight.window = this._items[index];
global.window_group.set_child_above_sibling(this._highlight.actor, null);
global.window_group.set_child_above_sibling(this._highlight, null);
}
_finish() {
@ -526,7 +525,7 @@ var CyclerPopup = GObject.registerClass({
}
_onDestroy() {
this._highlight.actor.destroy();
this._highlight.destroy();
super._onDestroy();
}
@ -589,20 +588,18 @@ class WindowSwitcherPopup extends SwitcherPopup.SwitcherPopup {
}
_keyPressHandler(keysym, action) {
if (action == Meta.KeyBindingAction.SWITCH_WINDOWS) {
if (action == Meta.KeyBindingAction.SWITCH_WINDOWS)
this._select(this._next());
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD) {
else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD)
this._select(this._previous());
} else {
if (keysym == Clutter.Left)
this._select(this._previous());
else if (keysym == Clutter.Right)
this._select(this._next());
else if (keysym == Clutter.w || keysym == Clutter.F4)
this._closeWindow(this._selectedIndex);
else
return Clutter.EVENT_PROPAGATE;
}
else if (keysym == Clutter.KEY_Left)
this._select(this._previous());
else if (keysym == Clutter.KEY_Right)
this._select(this._next());
else if (keysym == Clutter.KEY_w || keysym == Clutter.KEY_F4)
this._closeWindow(this._selectedIndex);
else
return Clutter.EVENT_PROPAGATE;
return Clutter.EVENT_STOP;
}
@ -653,13 +650,17 @@ class AppIcon extends St.BoxLayout {
this.app = app;
this.icon = null;
this._iconBin = new St.Bin({ x_fill: true, y_fill: true });
this._iconBin = new St.Bin();
this.add(this._iconBin, { x_fill: false, y_fill: false } );
this.label = new St.Label({ text: this.app.get_name() });
this.add(this.label, { x_fill: false });
this.add_child(this._iconBin);
this.label = new St.Label({
text: this.app.get_name(),
x_align: Clutter.ActorAlign.CENTER,
});
this.add_child(this.label);
}
// eslint-disable-next-line camelcase
set_size(size) {
this.icon = this.app.create_icon_texture(size);
this._iconBin.child = this.icon;
@ -692,7 +693,7 @@ class AppSwitcher extends SwitcherPopup.SwitcherList {
// Cache the window list now; we don't handle dynamic changes here,
// and we don't want to be continually retrieving it
appIcon.cachedWindows = allWindows.filter(
w => windowTracker.get_window_app (w) == appIcon.app
w => windowTracker.get_window_app(w) == appIcon.app
);
if (appIcon.cachedWindows.length > 0)
this._addIcon(appIcon);
@ -707,7 +708,7 @@ class AppSwitcher extends SwitcherPopup.SwitcherList {
_onDestroy() {
if (this._mouseTimeOutId != 0)
Mainloop.source_remove(this._mouseTimeOutId);
GLib.source_remove(this._mouseTimeOutId);
this.icons.forEach(icon => {
icon.app.disconnect(icon._stateChangedId);
@ -716,9 +717,9 @@ class AppSwitcher extends SwitcherPopup.SwitcherList {
_setIconSize() {
let j = 0;
while (this._items.length > 1 && this._items[j].style_class != 'item-box') {
while (this._items.length > 1 && this._items[j].style_class != 'item-box')
j++;
}
let themeNode = this._items[j].get_theme_node();
this._list.ensure_style();
@ -786,14 +787,16 @@ class AppSwitcher extends SwitcherPopup.SwitcherList {
// activation when the thumbnail list is open
_onItemEnter(index) {
if (this._mouseTimeOutId != 0)
Mainloop.source_remove(this._mouseTimeOutId);
GLib.source_remove(this._mouseTimeOutId);
if (this._altTabPopup.thumbnailsVisible) {
this._mouseTimeOutId = Mainloop.timeout_add(APP_ICON_HOVER_TIMEOUT,
() => {
this._enterItem(index);
this._mouseTimeOutId = 0;
return GLib.SOURCE_REMOVE;
});
this._mouseTimeOutId = GLib.timeout_add(
GLib.PRIORITY_DEFAULT,
APP_ICON_HOVER_TIMEOUT,
() => {
this._enterItem(index);
this._mouseTimeOutId = 0;
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(this._mouseTimeOutId, '[gnome-shell] this._enterItem');
} else {
this._itemEntered(index);
@ -801,7 +804,7 @@ class AppSwitcher extends SwitcherPopup.SwitcherList {
}
_enterItem(index) {
let [x, y, mask] = global.get_pointer();
let [x, y] = global.get_pointer();
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
if (this._items[index].contains(pickedActor))
this._itemEntered(index);
@ -850,7 +853,7 @@ class AppSwitcher extends SwitcherPopup.SwitcherList {
if (appIcon.cachedWindows.length == 1)
arrow.hide();
else
item.add_accessible_state (Atk.StateType.EXPANDABLE);
item.add_accessible_state(Atk.StateType.EXPANDABLE);
}
_removeIcon(app) {
@ -870,9 +873,9 @@ class ThumbnailList extends SwitcherPopup.SwitcherList {
_init(windows) {
super._init(false);
this._labels = new Array();
this._thumbnailBins = new Array();
this._clones = new Array();
this._labels = [];
this._thumbnailBins = [];
this._clones = [];
this._windows = windows;
for (let i = 0; i < windows.length; i++) {
@ -886,12 +889,13 @@ class ThumbnailList extends SwitcherPopup.SwitcherList {
let title = windows[i].get_title();
if (title) {
let name = new St.Label({ text: title });
// St.Label doesn't support text-align so use a Bin
let bin = new St.Bin({ x_align: St.Align.MIDDLE });
this._labels.push(bin);
bin.add_actor(name);
box.add_actor(bin);
let name = new St.Label({
text: title,
// St.Label doesn't support text-align
x_align: Clutter.ActorAlign.CENTER,
});
this._labels.push(name);
box.add_actor(name);
this.addItem(box, name);
} else {
@ -908,7 +912,7 @@ class ThumbnailList extends SwitcherPopup.SwitcherList {
return;
let totalPadding = this._items[0].get_theme_node().get_horizontal_padding() + this._items[0].get_theme_node().get_vertical_padding();
totalPadding += this.get_theme_node().get_horizontal_padding() + this.get_theme_node().get_vertical_padding();
let [labelMinHeight, labelNaturalHeight] = this._labels[0].get_preferred_height(-1);
let [, labelNaturalHeight] = this._labels[0].get_preferred_height(-1);
let spacing = this._items[0].child.get_theme_node().get_length('spacing');
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
let thumbnailSize = THUMBNAIL_DEFAULT_SIZE * scaleFactor;
@ -933,7 +937,7 @@ class ThumbnailList extends SwitcherPopup.SwitcherList {
}
// Make sure we only do this once
this._thumbnailBins = new Array();
this._thumbnailBins = [];
}
_removeThumbnail(source, clone) {
@ -970,7 +974,7 @@ class WindowIcon extends St.BoxLayout {
this._icon = new St.Widget({ layout_manager: new Clutter.BinLayout() });
this.add(this._icon, { x_fill: false, y_fill: false } );
this.add_child(this._icon);
this.label = new St.Label({ text: window.get_title() });
let tracker = Shell.WindowTracker.get_default();
@ -993,9 +997,10 @@ class WindowIcon extends St.BoxLayout {
size = WINDOW_PREVIEW_SIZE;
this._icon.add_actor(_createWindowClone(mutterWindow, size * scaleFactor));
if (this.app)
if (this.app) {
this._icon.add_actor(this._createAppIcon(this.app,
APP_ICON_SIZE_SMALL));
}
break;
case AppIconMode.APP_ICON_ONLY:
@ -1007,9 +1012,9 @@ class WindowIcon extends St.BoxLayout {
}
_createAppIcon(app, size) {
let appIcon = app ? app.create_icon_texture(size)
: new St.Icon({ icon_name: 'icon-missing',
icon_size: size });
let appIcon = app
? app.create_icon_texture(size)
: new St.Icon({ icon_name: 'icon-missing', icon_size: size });
appIcon.x_expand = appIcon.y_expand = true;
appIcon.x_align = appIcon.y_align = Clutter.ActorAlign.END;
@ -1036,7 +1041,7 @@ class WindowList extends SwitcherPopup.SwitcherList {
this.addItem(icon, icon.label);
this.icons.push(icon);
icon._unmanagedSignalId = icon.window.connect('unmanaged', (window) => {
icon._unmanagedSignalId = icon.window.connect('unmanaged', window => {
this._removeWindow(window);
});
}

View File

@ -1,21 +1,20 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Animation, AnimatedIcon, Spinner */
const { GLib, Gio, St } = imports.gi;
const Mainloop = imports.mainloop;
const { Clutter, GLib, GObject, Gio, St } = imports.gi;
const Tweener = imports.ui.tweener;
const Params = imports.misc.params;
var ANIMATED_ICON_UPDATE_TIMEOUT = 16;
var SPINNER_ANIMATION_TIME = 0.3;
var SPINNER_ANIMATION_DELAY = 1.0;
var SPINNER_ANIMATION_TIME = 300;
var SPINNER_ANIMATION_DELAY = 1000;
var Animation = class {
constructor(file, width, height, speed) {
this.actor = new St.Bin();
this.actor.set_size(width, height);
this.actor.connect('destroy', this._onDestroy.bind(this));
this.actor.connect('notify::size', this._syncAnimationSize.bind(this));
this.actor.connect('resource-scale-changed',
var Animation = GObject.registerClass(
class Animation extends St.Bin {
_init(file, width, height, speed) {
super._init({ width, height });
this.connect('destroy', this._onDestroy.bind(this));
this.connect('resource-scale-changed',
this._loadFile.bind(this, file, width, height));
let themeContext = St.ThemeContext.get_for_stage(global.stage);
@ -46,7 +45,7 @@ var Animation = class {
stop() {
if (this._timeoutId > 0) {
Mainloop.source_remove(this._timeoutId);
GLib.source_remove(this._timeoutId);
this._timeoutId = 0;
}
@ -54,20 +53,34 @@ var Animation = class {
}
_loadFile(file, width, height) {
let [validResourceScale, resourceScale] = this.actor.get_resource_scale();
let [validResourceScale, resourceScale] = this.get_resource_scale();
let wasPlaying = this._isPlaying;
if (this._isPlaying)
this.stop();
this._isLoaded = false;
this.actor.destroy_all_children();
this.destroy_all_children();
if (!validResourceScale)
if (!validResourceScale) {
if (wasPlaying)
this.play();
return;
}
let textureCache = St.TextureCache.get_default();
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
this._animations = textureCache.load_sliced_image(file, width, height,
scaleFactor, resourceScale,
this._animationsLoaded.bind(this));
this.actor.set_child(this._animations);
this._animations.set({
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
});
this.set_child(this._animations);
if (wasPlaying)
this.play();
}
_showFrame(frame) {
@ -75,7 +88,7 @@ var Animation = class {
if (oldFrameActor)
oldFrameActor.hide();
this._frame = (frame % this._animations.get_n_children());
this._frame = frame % this._animations.get_n_children();
let newFrameActor = this._animations.get_child_at_index(this._frame);
if (newFrameActor)
@ -91,7 +104,7 @@ var Animation = class {
if (!this._isLoaded)
return;
let [width, height] = this.actor.get_size();
let [width, height] = this.get_size();
for (let i = 0; i < this._animations.get_n_children(); ++i)
this._animations.get_child_at_index(i).set_size(width, height);
@ -114,21 +127,29 @@ var Animation = class {
themeContext.disconnect(this._scaleChangedId);
this._scaleChangedId = 0;
}
};
});
var AnimatedIcon = class extends Animation {
constructor(file, size) {
super(file, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
var AnimatedIcon = GObject.registerClass(
class AnimatedIcon extends Animation {
_init(file, size) {
super._init(file, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
}
};
});
var Spinner = class extends AnimatedIcon {
constructor(size, animate = false) {
var Spinner = GObject.registerClass(
class Spinner extends AnimatedIcon {
_init(size, params) {
params = Params.parse(params, {
animate: false,
hideOnStop: false,
});
let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/process-working.svg');
super(file, size);
super._init(file, size);
this.actor.opacity = 0;
this._animate = animate;
this.opacity = 0;
this._animate = params.animate;
this._hideOnStop = params.hideOnStop;
this.visible = !this._hideOnStop;
}
_onDestroy() {
@ -137,37 +158,43 @@ var Spinner = class extends AnimatedIcon {
}
play() {
Tweener.removeTweens(this.actor);
this.remove_all_transitions();
this.show();
if (this._animate) {
super.play();
Tweener.addTween(this.actor, {
this.ease({
opacity: 255,
delay: SPINNER_ANIMATION_DELAY,
time: SPINNER_ANIMATION_TIME,
transition: 'linear'
duration: SPINNER_ANIMATION_TIME,
mode: Clutter.AnimationMode.LINEAR,
});
} else {
this.actor.opacity = 255;
this.opacity = 255;
super.play();
}
}
stop() {
Tweener.removeTweens(this.actor);
this.remove_all_transitions();
if (this._animate) {
Tweener.addTween(this.actor, {
this.ease({
opacity: 0,
time: SPINNER_ANIMATION_TIME,
transition: 'linear',
duration: SPINNER_ANIMATION_TIME,
mode: Clutter.AnimationMode.LINEAR,
onComplete: () => {
super.stop();
}
if (this._hideOnStop)
this.hide();
},
});
} else {
this.actor.opacity = 0;
this.opacity = 0;
super.stop();
if (this._hideOnStop)
this.hide();
}
}
};
});

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getAppFavorites */
const Shell = imports.gi.Shell;
const Signals = imports.signals;
@ -54,6 +55,8 @@ const RENAMED_DESKTOP_IDS = {
'org.gnome.taquin.desktop': 'org.gnome.Taquin.desktop',
'org.gnome.Weather.Application.desktop': 'org.gnome.Weather.desktop',
'polari.desktop': 'org.gnome.Polari.desktop',
'seahorse.desktop': 'org.gnome.seahorse.Application.desktop',
'shotwell.desktop': 'org.gnome.Shotwell.desktop',
'tali.desktop': 'org.gnome.Tali.desktop',
'totem.desktop': 'org.gnome.Totem.desktop',
'evince.desktop': 'org.gnome.Evince.desktop',
@ -146,12 +149,11 @@ class AppFavorites {
let app = Shell.AppSystem.get_default().lookup_app(appId);
Main.overview.setMessage(_("%s has been added to your favorites.").format(app.get_name()),
{ forFeedback: true,
undoCallback: () => {
this._removeFavorite(appId);
}
});
let msg = _("%s has been added to your favorites.").format(app.get_name());
Main.overview.setMessage(msg, {
forFeedback: true,
undoCallback: () => this._removeFavorite(appId),
});
}
addFavorite(appId) {
@ -180,12 +182,11 @@ class AppFavorites {
if (!this._removeFavorite(appId))
return;
Main.overview.setMessage(_("%s has been removed from your favorites.").format(app.get_name()),
{ forFeedback: true,
undoCallback: () => {
this._addFavorite(appId, pos);
}
});
let msg = _("%s has been removed from your favorites.").format(app.get_name());
Main.overview.setMessage(msg, {
forFeedback: true,
undoCallback: () => this._addFavorite(appId, pos),
});
}
}
Signals.addSignalMethods(AppFavorites.prototype);

View File

@ -1,3 +1,4 @@
/* exported AudioDeviceSelectionDBus */
const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Main = imports.ui.main;
@ -8,13 +9,13 @@ const { loadInterfaceXML } = imports.misc.fileUtils;
var AudioDevice = {
HEADPHONES: 1 << 0,
HEADSET: 1 << 1,
MICROPHONE: 1 << 2
MICROPHONE: 1 << 2,
};
const AudioDeviceSelectionIface = loadInterfaceXML('org.gnome.Shell.AudioDeviceSelection');
var AudioDeviceSelectionDialog = GObject.registerClass({
Signals: { 'device-selected': { param_types: [GObject.TYPE_UINT] } }
Signals: { 'device-selected': { param_types: [GObject.TYPE_UINT] } },
}, class AudioDeviceSelectionDialog extends ModalDialog.ModalDialog {
_init(devices) {
super._init({ styleClass: 'audio-device-selection-dialog' });
@ -34,7 +35,7 @@ var AudioDeviceSelectionDialog = GObject.registerClass({
throw new Error('Too few devices for a selection');
}
_buildLayout(devices) {
_buildLayout() {
let title = new St.Label({ style_class: 'audio-selection-title',
text: _("Select Audio Device"),
x_align: Clutter.ActorAlign.CENTER });
@ -42,15 +43,19 @@ var AudioDeviceSelectionDialog = GObject.registerClass({
this.contentLayout.style_class = 'audio-selection-content';
this.contentLayout.add(title);
this._selectionBox = new St.BoxLayout({ style_class: 'audio-selection-box' });
this.contentLayout.add(this._selectionBox, { expand: true });
this._selectionBox = new St.BoxLayout({
style_class: 'audio-selection-box',
x_expand: true,
});
this.contentLayout.add_child(this._selectionBox);
if (Main.sessionMode.allowSettings)
if (Main.sessionMode.allowSettings) {
this.addButton({ action: this._openSettings.bind(this),
label: _("Sound Settings") });
}
this.addButton({ action: this.close.bind(this),
label: _("Cancel"),
key: Clutter.Escape });
key: Clutter.KEY_Escape });
}
_getDeviceLabel(device) {
@ -85,6 +90,7 @@ var AudioDeviceSelectionDialog = GObject.registerClass({
box.connect('notify::height', () => {
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
box.width = box.height;
return GLib.SOURCE_REMOVE;
});
});
@ -159,7 +165,7 @@ var AudioDeviceSelectionDBus = class AudioDeviceSelectionDBus {
let [deviceNames] = params;
let devices = 0;
deviceNames.forEach(n => devices |= AudioDevice[n.toUpperCase()]);
deviceNames.forEach(n => (devices |= AudioDevice[n.toUpperCase()]));
let dialog;
try {

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported SystemBackground */
// READ THIS FIRST
// Background handling is a maze of objects, both objects in this file, and
@ -93,13 +94,12 @@
// MetaBackgroundImage MetaBackgroundImage
// MetaBackgroundImage MetaBackgroundImage
const { Clutter, GDesktopEnums, Gio, GLib, GnomeDesktop, Meta } = imports.gi;
const { Clutter, GDesktopEnums, Gio, GLib, GObject, GnomeDesktop, Meta } = imports.gi;
const Signals = imports.signals;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
var DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
@ -110,7 +110,7 @@ const COLOR_SHADING_TYPE_KEY = 'color-shading-type';
const BACKGROUND_STYLE_KEY = 'picture-options';
const PICTURE_URI_KEY = 'picture-uri';
var FADE_ANIMATION_TIME = 1.0;
var FADE_ANIMATION_TIME = 1000;
// These parameters affect how often we redraw.
// The first is how different (percent crossfaded) the slide show
@ -145,7 +145,7 @@ var BackgroundCache = class BackgroundCache {
let monitor = file.monitor(Gio.FileMonitorFlags.NONE, null);
monitor.connect('changed',
(obj, file, otherFile, eventType) => {
(obj, theFile, otherFile, eventType) => {
// Ignore CHANGED and CREATED events, since in both cases
// we'll get a CHANGES_DONE_HINT event when done.
if (eventType != Gio.FileMonitorEvent.CHANGED &&
@ -221,16 +221,17 @@ function getBackgroundCache() {
return _backgroundCache;
}
var Background = class Background {
constructor(params) {
var Background = GObject.registerClass({
Signals: { 'loaded': {}, 'bg-changed': {} },
}, class Background extends Meta.Background {
_init(params) {
params = Params.parse(params, { monitorIndex: 0,
layoutManager: Main.layoutManager,
settings: null,
file: null,
style: null });
this.background = new Meta.Background({ meta_display: global.display });
this.background._delegate = this;
super._init({ meta_display: global.display });
this._settings = params.settings;
this._file = params.file;
@ -263,16 +264,14 @@ var Background = class Background {
}
destroy() {
this.background = null;
this._cancellable.cancel();
this._removeAnimationTimeout();
let i;
let keys = Object.keys(this._fileWatches);
for (i = 0; i < keys.length; i++) {
for (i = 0; i < keys.length; i++)
this._cache.disconnect(this._fileWatches[keys[i]]);
}
this._fileWatches = null;
if (this._timezoneChangedId != 0)
@ -301,9 +300,11 @@ var Background = class Background {
this._changedIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
this._changedIdleId = 0;
this.emit('changed');
this.emit('bg-changed');
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(this._changedIdleId,
'[gnome-shell] Background._emitChangedSignal');
}
updateResolution() {
@ -329,23 +330,23 @@ var Background = class Background {
this.emit('loaded');
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(id, '[gnome-shell] this.emit');
GLib.Source.set_name_by_id(id, '[gnome-shell] Background._setLoaded Idle');
}
_loadPattern() {
let colorString, res, color, secondColor;
let colorString, res_, color, secondColor;
colorString = this._settings.get_string(PRIMARY_COLOR_KEY);
[res, color] = Clutter.Color.from_string(colorString);
[res_, color] = Clutter.Color.from_string(colorString);
colorString = this._settings.get_string(SECONDARY_COLOR_KEY);
[res, secondColor] = Clutter.Color.from_string(colorString);
[res_, secondColor] = Clutter.Color.from_string(colorString);
let shadingType = this._settings.get_enum(COLOR_SHADING_TYPE_KEY);
if (shadingType == GDesktopEnums.BackgroundShading.SOLID)
this.background.set_color(color);
this.set_color(color);
else
this.background.set_gradient(shadingType, color, secondColor);
this.set_gradient(shadingType, color, secondColor);
}
_watchFile(file) {
@ -381,13 +382,13 @@ var Background = class Background {
let finish = () => {
this._setLoaded();
if (files.length > 1) {
this.background.set_blend(files[0], files[1],
this._animation.transitionProgress,
this._style);
this.set_blend(files[0], files[1],
this._animation.transitionProgress,
this._style);
} else if (files.length > 0) {
this.background.set_file(files[0], this._style);
this.set_file(files[0], this._style);
} else {
this.background.set_file(null, this._style);
this.set_file(null, this._style);
}
this._queueUpdateAnimation();
};
@ -402,6 +403,7 @@ var Background = class Background {
if (numPendingImages == 0)
finish();
} else {
// eslint-disable-next-line no-loop-func
let id = image.connect('loaded', () => {
image.disconnect(id);
numPendingImages--;
@ -442,24 +444,25 @@ var Background = class Background {
}
_loadAnimation(file) {
this._cache.getAnimation({ file: file,
settingsSchema: this._settings.schema_id,
onLoaded: animation => {
this._animation = animation;
this._cache.getAnimation({
file,
settingsSchema: this._settings.schema_id,
onLoaded: animation => {
this._animation = animation;
if (!this._animation || this._cancellable.is_cancelled()) {
this._setLoaded();
return;
}
if (!this._animation || this._cancellable.is_cancelled()) {
this._setLoaded();
return;
}
this._updateAnimation();
this._watchFile(file);
}
});
this._updateAnimation();
this._watchFile(file);
},
});
}
_loadImage(file) {
this.background.set_file(file, this._style);
this.set_file(file, this._style);
this._watchFile(file);
let cache = Meta.BackgroundImageCache.get_default();
@ -493,13 +496,14 @@ var Background = class Background {
this._loadFile(this._file);
}
};
Signals.addSignalMethods(Background.prototype);
});
let _systemBackground;
var SystemBackground = class SystemBackground {
constructor() {
var SystemBackground = GObject.registerClass({
Signals: { 'loaded': {} },
}, class SystemBackground extends Meta.BackgroundActor {
_init() {
let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/noise-texture.png');
if (_systemBackground == null) {
@ -508,9 +512,11 @@ var SystemBackground = class SystemBackground {
_systemBackground.set_file(file, GDesktopEnums.BackgroundStyle.WALLPAPER);
}
this.actor = new Meta.BackgroundActor({ meta_display: global.display,
monitor: 0,
background: _systemBackground });
super._init({
meta_display: global.display,
monitor: 0,
background: _systemBackground,
});
let cache = Meta.BackgroundImageCache.get_default();
let image = cache.load(file);
@ -529,8 +535,7 @@ var SystemBackground = class SystemBackground {
});
}
}
};
Signals.addSignalMethods(SystemBackground.prototype);
});
var BackgroundSource = class BackgroundSource {
constructor(layoutManager, settingsSchema) {
@ -566,7 +571,7 @@ var BackgroundSource = class BackgroundSource {
// We don't watch changes to settings here,
// instead we rely on Background to watch those
// and emit 'changed' at the right time
// and emit 'bg-changed' at the right time
if (this._overrideImage != null) {
file = Gio.File.new_for_path(this._overrideImage);
@ -588,14 +593,14 @@ var BackgroundSource = class BackgroundSource {
if (!(monitorIndex in this._backgrounds)) {
let background = new Background({
monitorIndex: monitorIndex,
monitorIndex,
layoutManager: this._layoutManager,
settings: this._settings,
file: file,
style: style
file,
style,
});
background._changedId = background.connect('changed', () => {
background._changedId = background.connect('bg-changed', () => {
background.disconnect(background._changedId);
background.destroy();
delete this._backgrounds[monitorIndex];
@ -621,11 +626,11 @@ var BackgroundSource = class BackgroundSource {
}
};
var Animation = class Animation {
constructor(params) {
params = Params.parse(params, { file: null });
var Animation = GObject.registerClass(
class Animation extends GnomeDesktop.BGSlideShow {
_init(params) {
super._init(params);
this.file = params.file;
this.keyFrameFiles = [];
this.transitionProgress = 0.0;
this.transitionDuration = 0.0;
@ -633,9 +638,7 @@ var Animation = class Animation {
}
load(callback) {
this._show = new GnomeDesktop.BGSlideShow({ filename: this.file.get_path() });
this._show.load_async(null, (object, result) => {
this.load_async(null, () => {
this.loaded = true;
if (callback)
callback();
@ -645,13 +648,11 @@ var Animation = class Animation {
update(monitor) {
this.keyFrameFiles = [];
if (!this._show)
if (this.get_num_slides() < 1)
return;
if (this._show.get_num_slides() < 1)
return;
let [progress, duration, isFixed, filename1, filename2] = this._show.get_current_slide(monitor.width, monitor.height);
let [progress, duration, isFixed_, filename1, filename2] =
this.get_current_slide(monitor.width, monitor.height);
this.transitionDuration = duration;
this.transitionProgress = progress;
@ -662,8 +663,7 @@ var Animation = class Animation {
if (filename2)
this.keyFrameFiles.push(Gio.File.new_for_path(filename2));
}
};
Signals.addSignalMethods(Animation.prototype);
});
var BackgroundManager = class BackgroundManager {
constructor(params) {
@ -710,14 +710,12 @@ var BackgroundManager = class BackgroundManager {
this._newBackgroundActor = null;
this.emit('changed');
Tweener.addTween(oldBackgroundActor,
{ opacity: 0,
time: FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete() {
oldBackgroundActor.destroy();
}
});
oldBackgroundActor.ease({
opacity: 0,
duration: FADE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => oldBackgroundActor.destroy(),
});
}
_updateBackgroundActor() {
@ -734,7 +732,7 @@ var BackgroundManager = class BackgroundManager {
this._newBackgroundActor = newBackgroundActor;
let background = newBackgroundActor.background._delegate;
let background = newBackgroundActor.background;
if (background.isLoaded) {
this._swapBackgroundActor();
@ -751,23 +749,24 @@ var BackgroundManager = class BackgroundManager {
_createBackgroundActor() {
let background = this._backgroundSource.getBackground(this._monitorIndex);
let backgroundActor = new Meta.BackgroundActor({ meta_display: global.display,
monitor: this._monitorIndex,
background: background.background,
vignette: this._vignette,
vignette_sharpness: 0.5,
brightness: 0.5,
});
let backgroundActor = new Meta.BackgroundActor({
meta_display: global.display,
monitor: this._monitorIndex,
background,
vignette: this._vignette,
vignette_sharpness: 0.5,
brightness: 0.5,
});
this._container.add_child(backgroundActor);
if (this._controlPosition) {
let monitor = this._layoutManager.monitors[this._monitorIndex];
backgroundActor.set_position(monitor.x, monitor.y);
backgroundActor.lower_bottom();
this._container.set_child_below_sibling(backgroundActor, null);
}
let changeSignalId = background.connect('changed', () => {
let changeSignalId = background.connect('bg-changed', () => {
background.disconnect(changeSignalId);
changeSignalId = null;
this._updateBackgroundActor();

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported addBackgroundMenu */
const { Clutter, St } = imports.gi;
@ -30,15 +31,16 @@ function addBackgroundMenu(actor, layoutManager) {
function openMenu(x, y) {
Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0);
actor._backgroundMenu.open(BoxPointer.PopupAnimation.NONE);
actor._backgroundMenu.open(BoxPointer.PopupAnimation.FULL);
}
let clickAction = new Clutter.ClickAction();
clickAction.connect('long-press', (action, actor, state) => {
if (state == Clutter.LongPressState.QUERY)
return ((action.get_button() == 0 ||
clickAction.connect('long-press', (action, theActor, state) => {
if (state == Clutter.LongPressState.QUERY) {
return (action.get_button() == 0 ||
action.get_button() == 1) &&
!actor._backgroundMenu.isOpen);
!actor._backgroundMenu.isOpen;
}
if (state == Clutter.LongPressState.ACTIVATE) {
let [x, y] = action.get_coords();
openMenu(x, y);

View File

@ -1,74 +1,107 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/* exported BarLevel */
const { Atk, Clutter, St } = imports.gi;
const Signals = imports.signals;
const { Atk, Clutter, GObject, St } = imports.gi;
var BarLevel = class {
constructor(value, params) {
if (isNaN(value))
// Avoid spreading NaNs around
throw TypeError('The bar level value must be a number');
var BarLevel = GObject.registerClass({
Properties: {
'value': GObject.ParamSpec.double(
'value', 'value', 'value',
GObject.ParamFlags.READWRITE,
0, 2, 0),
'maximum-value': GObject.ParamSpec.double(
'maximum-value', 'maximum-value', 'maximum-value',
GObject.ParamFlags.READWRITE,
1, 2, 1),
'overdrive-start': GObject.ParamSpec.double(
'overdrive-start', 'overdrive-start', 'overdrive-start',
GObject.ParamFlags.READWRITE,
1, 2, 1),
},
}, class BarLevel extends St.DrawingArea {
_init(params) {
this._maxValue = 1;
this._value = Math.max(Math.min(value, this._maxValue), 0);
this._value = 0;
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,
accessible_role: params['accessibleRole'] || Atk.Role.LEVEL_BAR });
this.actor.connect('repaint', this._barLevelRepaint.bind(this));
this.actor.connect('allocation-changed', (actor, box) => {
let defaultParams = {
style_class: 'barlevel',
accessible_role: Atk.Role.LEVEL_BAR,
};
super._init(Object.assign(defaultParams, params));
this.connect('allocation-changed', (actor, box) => {
this._barLevelWidth = box.get_width();
});
this._customAccessible = St.GenericAccessible.new_for_actor(this.actor);
this.actor.set_accessible(this._customAccessible);
this._customAccessible = St.GenericAccessible.new_for_actor(this);
this.set_accessible(this._customAccessible);
this._customAccessible.connect('get-current-value', this._getCurrentValue.bind(this));
this._customAccessible.connect('get-minimum-value', this._getMinimumValue.bind(this));
this._customAccessible.connect('get-maximum-value', this._getMaximumValue.bind(this));
this._customAccessible.connect('set-current-value', this._setCurrentValue.bind(this));
this.connect('value-changed', this._valueChanged.bind(this));
this.connect('notify::value', this._valueChanged.bind(this));
}
setValue(value) {
if (isNaN(value))
throw TypeError('The bar level value must be a number');
this._value = Math.max(Math.min(value, this._maxValue), 0);
this.actor.queue_repaint();
get value() {
return this._value;
}
setMaximumValue(value) {
if (isNaN(value))
throw TypeError('The bar level max value must be a number');
set value(value) {
value = Math.max(Math.min(value, this._maxValue), 0);
this._maxValue = Math.max(value, 1);
if (this._value == value)
return;
this._value = value;
this.notify('value');
this.queue_repaint();
}
// eslint-disable-next-line camelcase
get maximum_value() {
return this._maxValue;
}
// eslint-disable-next-line camelcase
set maximum_value(value) {
value = Math.max(value, 1);
if (this._maxValue == value)
return;
this._maxValue = value;
this._overdriveStart = Math.min(this._overdriveStart, this._maxValue);
this.actor.queue_repaint();
this.notify('maximum-value');
this.queue_repaint();
}
setOverdriveStart(value) {
if (isNaN(value))
throw TypeError('The overdrive limit value must be a number');
if (value > this._maxValue)
// eslint-disable-next-line camelcase
get overdrive_start() {
return this._overdriveStart;
}
// eslint-disable-next-line camelcase
set overdrive_start(value) {
if (this._overdriveStart == value)
return;
if (value > this._maxValue) {
throw new Error(`Tried to set overdrive value to ${value}, ` +
`which is a number greater than the maximum allowed value ${this._maxValue}`);
}
this._overdriveStart = value;
this._value = Math.max(Math.min(value, this._maxValue), 0);
this.actor.queue_repaint();
this.notify('overdrive-start');
this.queue_repaint();
}
_barLevelRepaint(area) {
let cr = area.get_context();
let themeNode = area.get_theme_node();
let [width, height] = area.get_surface_size();
vfunc_repaint() {
let cr = this.get_context();
let themeNode = this.get_theme_node();
let [width, height] = this.get_surface_size();
let barLevelHeight = themeNode.get_length('-barlevel-height');
let barLevelBorderRadius = Math.min(width, barLevelHeight) / 2;
@ -175,32 +208,27 @@ var BarLevel = class {
cr.$dispose();
}
_getCurrentValue(actor) {
_getCurrentValue() {
return this._value;
}
_getOverdriveStart(actor) {
_getOverdriveStart() {
return this._overdriveStart;
}
_getMinimumValue(actor) {
_getMinimumValue() {
return 0;
}
_getMaximumValue(actor) {
_getMaximumValue() {
return this._maxValue;
}
_setCurrentValue(actor, value) {
_setCurrentValue(_actor, value) {
this._value = value;
}
_valueChanged(barLevel, value, property) {
_valueChanged() {
this._customAccessible.notify("accessible-value");
}
get value() {
return this._value;
}
};
Signals.addSignalMethods(BarLevel.prototype);
});

View File

@ -1,9 +1,9 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported BoxPointer */
const { Clutter, GObject, Shell, St } = imports.gi;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
var PopupAnimation = {
NONE: 0,
@ -12,7 +12,7 @@ var PopupAnimation = {
FULL: ~0,
};
var POPUP_ANIMATION_TIME = 0.15;
var POPUP_ANIMATION_TIME = 150;
/**
* BoxPointer:
@ -44,14 +44,20 @@ var BoxPointer = GObject.registerClass({
this._border = new St.DrawingArea();
this._border.connect('repaint', this._drawBorder.bind(this));
this.add_actor(this._border);
this.bin.raise(this._border);
this.set_child_above_sibling(this.bin, this._border);
this._sourceAlignment = 0.5;
this._capturedEventId = 0;
this._muteInput();
this._muteInput = true;
this.connect('destroy', this._onDestroy.bind(this));
}
vfunc_captured_event() {
if (this._muteInput)
return Clutter.EVENT_STOP;
return Clutter.EVENT_PROPAGATE;
}
_onDestroy() {
if (this._sourceActorDestroyId) {
this._sourceActor.disconnect(this._sourceActorDestroyId);
@ -63,23 +69,10 @@ var BoxPointer = GObject.registerClass({
return this._arrowSide;
}
_muteInput() {
if (this._capturedEventId == 0)
this._capturedEventId = this.connect('captured-event',
() => Clutter.EVENT_STOP);
}
_unmuteInput() {
if (this._capturedEventId != 0) {
this.disconnect(this._capturedEventId);
this._capturedEventId = 0;
}
}
open(animate, onComplete) {
let themeNode = this.get_theme_node();
let rise = themeNode.get_length('-arrow-rise');
let animationTime = (animate & PopupAnimation.FULL) ? POPUP_ANIMATION_TIME : 0;
let animationTime = animate & PopupAnimation.FULL ? POPUP_ANIMATION_TIME : 0;
if (animate & PopupAnimation.FADE)
this.opacity = 0;
@ -105,16 +98,18 @@ var BoxPointer = GObject.registerClass({
}
}
Tweener.addTween(this, { opacity: 255,
translation_x: 0,
translation_y: 0,
transition: 'linear',
onComplete: () => {
this._unmuteInput();
if (onComplete)
onComplete();
},
time: animationTime });
this.ease({
opacity: 255,
translation_x: 0,
translation_y: 0,
duration: animationTime,
mode: Clutter.AnimationMode.LINEAR,
onComplete: () => {
this._muteInput = false;
if (onComplete)
onComplete();
},
});
}
close(animate, onComplete) {
@ -125,8 +120,8 @@ var BoxPointer = GObject.registerClass({
let translationY = 0;
let themeNode = this.get_theme_node();
let rise = themeNode.get_length('-arrow-rise');
let fade = (animate & PopupAnimation.FADE);
let animationTime = (animate & PopupAnimation.FULL) ? POPUP_ANIMATION_TIME : 0;
let fade = animate & PopupAnimation.FADE;
let animationTime = animate & PopupAnimation.FULL ? POPUP_ANIMATION_TIME : 0;
if (animate & PopupAnimation.SLIDE) {
switch (this._arrowSide) {
@ -145,23 +140,24 @@ var BoxPointer = GObject.registerClass({
}
}
this._muteInput();
this._muteInput = true;
Tweener.removeTweens(this);
Tweener.addTween(this, { opacity: fade ? 0 : 255,
translation_x: translationX,
translation_y: translationY,
transition: 'linear',
time: animationTime,
onComplete: () => {
this.hide();
this.opacity = 0;
this.translation_x = 0;
this.translation_y = 0;
if (onComplete)
onComplete();
}
});
this.remove_all_transitions();
this.ease({
opacity: fade ? 0 : 255,
translation_x: translationX,
translation_y: translationY,
duration: animationTime,
mode: Clutter.AnimationMode.LINEAR,
onComplete: () => {
this.hide();
this.opacity = 0;
this.translation_x = 0;
this.translation_y = 0;
if (onComplete)
onComplete();
},
});
}
_adjustAllocationForArrow(isWidth, minSize, natSize) {
@ -169,8 +165,8 @@ var BoxPointer = GObject.registerClass({
let borderWidth = themeNode.get_length('-arrow-border-width');
minSize += borderWidth * 2;
natSize += borderWidth * 2;
if ((!isWidth && (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM))
|| (isWidth && (this._arrowSide == St.Side.LEFT || this._arrowSide == St.Side.RIGHT))) {
if ((!isWidth && (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM)) ||
(isWidth && (this._arrowSide == St.Side.LEFT || this._arrowSide == St.Side.RIGHT))) {
let rise = themeNode.get_length('-arrow-rise');
minSize += rise;
natSize += rise;
@ -251,11 +247,10 @@ var BoxPointer = GObject.registerClass({
let [absX, absY] = this.get_transformed_position();
if (this._arrowSide == St.Side.TOP ||
this._arrowSide == St.Side.BOTTOM) {
this._arrowSide == St.Side.BOTTOM)
this._arrowOrigin = sourceX - absX + sourceWidth / 2;
} else {
else
this._arrowOrigin = sourceY - absY + sourceHeight / 2;
}
}
let borderWidth = themeNode.get_length('-arrow-border-width');
@ -270,20 +265,19 @@ var BoxPointer = GObject.registerClass({
let [width, height] = area.get_surface_size();
let [boxWidth, boxHeight] = [width, height];
if (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM) {
if (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM)
boxHeight -= rise;
} else {
else
boxWidth -= rise;
}
let cr = area.get_context();
// Translate so that box goes from 0,0 to boxWidth,boxHeight,
// with the arrow poking out of that
if (this._arrowSide == St.Side.TOP) {
if (this._arrowSide == St.Side.TOP)
cr.translate(0, rise);
} else if (this._arrowSide == St.Side.LEFT) {
else if (this._arrowSide == St.Side.LEFT)
cr.translate(rise, 0);
}
let [x1, y1] = [halfBorder, halfBorder];
let [x2, y2] = [boxWidth - halfBorder, boxHeight - halfBorder];
@ -469,7 +463,7 @@ var BoxPointer = GObject.registerClass({
let sourceAllocation = this._sourceAllocation;
let sourceCenterX = sourceAllocation.x1 + sourceContentBox.x1 + (sourceContentBox.x2 - sourceContentBox.x1) * this._sourceAlignment;
let sourceCenterY = sourceAllocation.y1 + sourceContentBox.y1 + (sourceContentBox.y2 - sourceContentBox.y1) * this._sourceAlignment;
let [minWidth, minHeight, natWidth, natHeight] = this.get_preferred_size();
let [, , natWidth, natHeight] = this.get_preferred_size();
// We also want to keep it onscreen, and separated from the
// edge by the same distance as the main part of the box is
@ -479,7 +473,7 @@ var BoxPointer = GObject.registerClass({
let borderWidth = themeNode.get_length('-arrow-border-width');
let arrowBase = themeNode.get_length('-arrow-base');
let borderRadius = themeNode.get_length('-arrow-border-radius');
let margin = (4 * borderRadius + borderWidth + arrowBase);
let margin = 4 * borderRadius + borderWidth + arrowBase;
let gap = themeNode.get_length('-boxpointer-gap');
let padding = themeNode.get_length('-arrow-rise');
@ -530,11 +524,11 @@ var BoxPointer = GObject.registerClass({
arrowOrigin = sourceCenterX - resX;
if (arrowOrigin <= (x1 + (borderRadius + halfBase))) {
if (arrowOrigin > x1)
resX += (arrowOrigin - x1);
resX += arrowOrigin - x1;
arrowOrigin = x1;
} else if (arrowOrigin >= (x2 - (borderRadius + halfBase))) {
if (arrowOrigin < x2)
resX -= (x2 - arrowOrigin);
resX -= x2 - arrowOrigin;
arrowOrigin = x2;
}
break;
@ -549,11 +543,11 @@ var BoxPointer = GObject.registerClass({
arrowOrigin = sourceCenterY - resY;
if (arrowOrigin <= (y1 + (borderRadius + halfBase))) {
if (arrowOrigin > y1)
resY += (arrowOrigin - y1);
resY += arrowOrigin - y1;
arrowOrigin = y1;
} else if (arrowOrigin >= (y2 - (borderRadius + halfBase))) {
if (arrowOrigin < y2)
resX -= (y2 - arrowOrigin);
resX -= y2 - arrowOrigin;
arrowOrigin = y2;
}
break;
@ -594,7 +588,7 @@ var BoxPointer = GObject.registerClass({
_calculateArrowSide(arrowSide) {
let sourceAllocation = this._sourceAllocation;
let [minWidth, minHeight, boxWidth, boxHeight] = this.get_preferred_size();
let [, , boxWidth, boxHeight] = this.get_preferred_size();
let workarea = this._workArea;
switch (arrowSide) {

View File

@ -1,7 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Calendar, CalendarMessageList, DBusEventSource */
const { Clutter, Gio, GLib, Shell, St } = imports.gi;
const Signals = imports.signals;
const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi;
const Main = imports.ui.main;
const MessageList = imports.ui.messageList;
@ -20,7 +20,7 @@ var MESSAGE_ICON_SIZE = -1; // pick up from CSS
var NC_ = (context, str) => `${context}\u0004${str}`;
function sameYear(dateA, dateB) {
return (dateA.getYear() == dateB.getYear());
return dateA.getYear() == dateB.getYear();
}
function sameMonth(dateA, dateB) {
@ -78,7 +78,7 @@ function _getCalendarDayAbbreviation(dayNumber) {
/* Translators: Calendar grid abbreviation for Friday */
NC_("grid friday", "F"),
/* Translators: Calendar grid abbreviation for Saturday */
NC_("grid saturday", "S")
NC_("grid saturday", "S"),
];
return Shell.util_translate_time_string(abbreviations[dayNumber]);
}
@ -98,30 +98,66 @@ var CalendarEvent = class CalendarEvent {
// Interface for appointments/events - e.g. the contents of a calendar
//
// First, an implementation with no events
var EmptyEventSource = class EmptyEventSource {
constructor() {
this.isLoading = false;
this.isDummy = true;
this.hasCalendars = false;
var EventSourceBase = GObject.registerClass({
GTypeFlags: GObject.TypeFlags.ABSTRACT,
Properties: {
'has-calendars': GObject.ParamSpec.boolean(
'has-calendars', 'has-calendars', 'has-calendars',
GObject.ParamFlags.READABLE,
false),
'is-loading': GObject.ParamSpec.boolean(
'is-loading', 'is-loading', 'is-loading',
GObject.ParamFlags.READABLE,
false),
},
Signals: { 'changed': {} },
}, class EventSourceBase extends GObject.Object {
get isLoading() {
throw new GObject.NotImplementedError(`isLoading in ${this.constructor.name}`);
}
get hasCalendars() {
throw new GObject.NotImplementedError(`hasCalendars in ${this.constructor.name}`);
}
destroy() {
}
requestRange(begin, end) {
requestRange(_begin, _end) {
throw new GObject.NotImplementedError(`requestRange in ${this.constructor.name}`);
}
getEvents(begin, end) {
getEvents(_begin, _end) {
throw new GObject.NotImplementedError(`getEvents in ${this.constructor.name}`);
}
hasEvents(_day) {
throw new GObject.NotImplementedError(`hasEvents in ${this.constructor.name}`);
}
});
var EmptyEventSource = GObject.registerClass(
class EmptyEventSource extends EventSourceBase {
get isLoading() {
return false;
}
get hasCalendars() {
return false;
}
requestRange(_begin, _end) {
}
getEvents(_begin, _end) {
let result = [];
return result;
}
hasEvents(day) {
hasEvents(_day) {
return false;
}
};
Signals.addSignalMethods(EmptyEventSource.prototype);
});
const CalendarServerIface = loadInterfaceXML('org.gnome.Shell.CalendarServer');
@ -153,11 +189,12 @@ function _dateIntervalsOverlap(a0, a1, b0, b1) {
}
// an implementation that reads data from a session bus service
var DBusEventSource = class DBusEventSource {
constructor() {
var DBusEventSource = GObject.registerClass(
class DBusEventSource extends EventSourceBase {
_init() {
super._init();
this._resetCache();
this.isLoading = false;
this.isDummy = false;
this._isLoading = false;
this._initialized = false;
this._dbusProxy = new CalendarServer();
@ -192,12 +229,12 @@ var DBusEventSource = class DBusEventSource {
});
this._dbusProxy.connect('g-properties-changed', () => {
this.emit('notify::has-calendars');
this.notify('has-calendars');
});
this._initialized = loaded;
if (loaded) {
this.emit('notify::has-calendars');
this.notify('has-calendars');
this._onNameAppeared();
}
});
@ -214,19 +251,23 @@ var DBusEventSource = class DBusEventSource {
return false;
}
get isLoading() {
return this._isLoading;
}
_resetCache() {
this._events = [];
this._lastRequestBegin = null;
this._lastRequestEnd = null;
}
_onNameAppeared(owner) {
_onNameAppeared() {
this._initialized = true;
this._resetCache();
this._loadEvents(true);
}
_onNameVanished(oldOwner) {
_onNameVanished() {
this._resetCache();
this.emit('changed');
}
@ -235,25 +276,23 @@ var DBusEventSource = class DBusEventSource {
this._loadEvents(false);
}
_onEventsReceived(results, error) {
_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;
this._isLoading = false;
this.emit('changed');
}
@ -273,7 +312,7 @@ var DBusEventSource = class DBusEventSource {
requestRange(begin, end) {
if (!(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) {
this.isLoading = true;
this._isLoading = true;
this._lastRequestBegin = begin;
this._lastRequestEnd = end;
this._curRequestBegin = begin;
@ -287,9 +326,8 @@ var DBusEventSource = class DBusEventSource {
for (let n = 0; n < this._events.length; n++) {
let event = this._events[n];
if (_dateIntervalsOverlap (event.date, event.end, begin, end)) {
if (_dateIntervalsOverlap(event.date, event.end, begin, end))
result.push(event);
}
}
result.sort((event1, event2) => {
// sort events by end time on ending day
@ -311,11 +349,12 @@ var DBusEventSource = class DBusEventSource {
return true;
}
};
Signals.addSignalMethods(DBusEventSource.prototype);
});
var Calendar = class Calendar {
constructor() {
var Calendar = GObject.registerClass({
Signals: { 'selected-date-changed': { param_types: [GLib.DateTime.$gtype] } },
}, class Calendar extends St.Widget {
_init() {
this._weekStart = Shell.util_get_week_start();
this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.calendar' });
@ -345,19 +384,19 @@ var Calendar = class Calendar {
this._shouldDateGrabFocus = false;
this.actor = new St.Widget({ style_class: 'calendar',
layout_manager: new Clutter.TableLayout(),
reactive: true });
super._init({
style_class: 'calendar',
layout_manager: new Clutter.GridLayout(),
reactive: true,
});
this.actor.connect('scroll-event',
this._onScroll.bind(this));
this._buildHeader ();
this._buildHeader();
}
// @eventSource: is an object implementing the EventSource API, e.g. the
// requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
setEventSource(eventSource) {
if (!(eventSource instanceof EventSourceBase))
throw new Error('Event source is not valid type');
this._eventSource = eventSource;
this._eventSource.connect('changed', () => {
this._rebuildCalendar();
@ -374,7 +413,10 @@ var Calendar = class Calendar {
this._selectedDate = date;
this._update();
this.emit('selected-date-changed', new Date(this._selectedDate));
let datetime = GLib.DateTime.new_from_unix_local(
this._selectedDate.getTime() / 1000);
this.emit('selected-date-changed', datetime);
}
updateTimeZone() {
@ -385,14 +427,13 @@ var Calendar = class Calendar {
}
_buildHeader() {
let layout = this.actor.layout_manager;
let layout = this.layout_manager;
let offsetCols = this._useWeekdate ? 1 : 0;
this.actor.destroy_all_children();
this.destroy_all_children();
// Top line of the calendar '<| September 2009 |>'
this._topBox = new St.BoxLayout();
layout.pack(this._topBox, 0, 0);
layout.set_span(this._topBox, offsetCols + 7, 1);
layout.attach(this._topBox, 0, 0, offsetCols + 7, 1);
this._backButton = new St.Button({ style_class: 'calendar-change-month-back pager-button',
accessible_name: _("Previous month"),
@ -401,9 +442,13 @@ var Calendar = class Calendar {
this._topBox.add(this._backButton);
this._backButton.connect('clicked', this._onPrevMonthButtonClicked.bind(this));
this._monthLabel = new St.Label({ style_class: 'calendar-month-label',
can_focus: true });
this._topBox.add(this._monthLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
this._monthLabel = new St.Label({
style_class: 'calendar-month-label',
can_focus: true,
x_align: Clutter.ActorAlign.CENTER,
x_expand: true,
});
this._topBox.add_child(this._monthLabel);
this._forwardButton = new St.Button({ style_class: 'calendar-change-month-forward pager-button',
accessible_name: _("Next month"),
@ -429,20 +474,20 @@ var Calendar = class Calendar {
can_focus: true });
label.accessible_name = iter.toLocaleFormat('%A');
let col;
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL)
if (this.get_text_direction() == Clutter.TextDirection.RTL)
col = 6 - (7 + iter.getDay() - this._weekStart) % 7;
else
col = offsetCols + (7 + iter.getDay() - this._weekStart) % 7;
layout.pack(label, col, 1);
layout.attach(label, col, 1, 1, 1);
iter.setTime(iter.getTime() + MSECS_IN_DAY);
}
// All the children after this are days, and get removed when we update the calendar
this._firstDayIndex = this.actor.get_n_children();
this._firstDayIndex = this.get_n_children();
}
_onScroll(actor, event) {
switch (event.get_scroll_direction()) {
vfunc_scroll_event(scrollEvent) {
switch (scrollEvent.direction) {
case Clutter.ScrollDirection.UP:
case Clutter.ScrollDirection.LEFT:
this._onPrevMonthButtonClicked();
@ -512,7 +557,7 @@ var Calendar = class Calendar {
let now = new Date();
// Remove everything but the topBox and the weekday labels
let children = this.actor.get_children();
let children = this.get_children();
for (let i = this._firstDayIndex; i < children.length; i++)
children[i].destroy();
@ -549,7 +594,7 @@ var Calendar = class Calendar {
beginDate.setTime(beginDate.getTime() - (weekPadding + daysToWeekStart) * MSECS_IN_DAY);
let layout = this.actor.layout_manager;
let layout = this.layout_manager;
let iter = new Date(beginDate);
let row = 2;
// nRows here means 6 weeks + one header + one navbar
@ -560,7 +605,7 @@ var Calendar = class Calendar {
can_focus: true });
let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;
if (this._eventSource.isDummy)
if (this._eventSource instanceof EmptyEventSource)
button.reactive = false;
button._date = new Date(iter);
@ -582,8 +627,9 @@ var Calendar = class Calendar {
if (row == 2)
styleClass = `calendar-day-top ${styleClass}`;
let leftMost = rtl ? iter.getDay() == (this._weekStart + 6) % 7
: iter.getDay() == this._weekStart;
let leftMost = rtl
? iter.getDay() == (this._weekStart + 6) % 7
: iter.getDay() == this._weekStart;
if (leftMost)
styleClass = `calendar-day-left ${styleClass}`;
@ -603,7 +649,7 @@ var Calendar = class Calendar {
col = 6 - (7 + iter.getDay() - this._weekStart) % 7;
else
col = offsetCols + (7 + iter.getDay() - this._weekStart) % 7;
layout.pack(button, col, row);
layout.attach(button, col, row, 1, 1);
this._buttons.push(button);
@ -613,7 +659,7 @@ var Calendar = class Calendar {
can_focus: true });
let weekFormat = Shell.util_translate_time_string(N_("Week %V"));
label.accessible_name = iter.toLocaleFormat(weekFormat);
layout.pack(label, rtl ? 7 : 0, row);
layout.attach(label, rtl ? 7 : 0, row, 1, 1);
}
iter.setTime(iter.getTime() + MSECS_IN_DAY);
@ -648,12 +694,12 @@ var Calendar = class Calendar {
}
});
}
};
Signals.addSignalMethods(Calendar.prototype);
});
var EventMessage = class EventMessage extends MessageList.Message {
constructor(event, date) {
super('', event.summary);
var EventMessage = GObject.registerClass(
class EventMessage extends MessageList.Message {
_init(event, date) {
super._init('', event.summary);
this._event = event;
this._date = date;
@ -662,18 +708,19 @@ var EventMessage = class EventMessage extends MessageList.Message {
this._icon = new St.Icon({ icon_name: 'x-office-calendar-symbolic' });
this.setIcon(this._icon);
}
this.actor.connect('style-changed', () => {
let iconVisible = this.actor.get_parent().has_style_pseudo_class('first-child');
this._icon.opacity = (iconVisible ? 255 : 0);
});
vfunc_style_changed() {
let iconVisible = this.get_parent().has_style_pseudo_class('first-child');
this._icon.opacity = iconVisible ? 255 : 0;
super.vfunc_style_changed();
}
_formatEventTime() {
let periodBegin = _getBeginningOfDay(this._date);
let periodEnd = _getEndOfDay(this._date);
let allDay = (this._event.allDay || (this._event.date <= periodBegin &&
this._event.end >= periodEnd));
let allDay = this._event.allDay || (this._event.date <= periodBegin &&
this._event.end >= periodEnd);
let title;
if (allDay) {
/* Translators: Shown in calendar event list for all day events
@ -681,32 +728,33 @@ var EventMessage = class EventMessage extends MessageList.Message {
*/
title = C_("event list time", "All Day");
} else {
let date = this._event.date >= periodBegin ? this._event.date
: this._event.end;
let date = this._event.date >= periodBegin
? this._event.date
: this._event.end;
title = Util.formatTime(date, { timeOnly: true });
}
let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;
if (this._event.date < periodBegin && !this._event.allDay) {
if (rtl)
title = title + ELLIPSIS_CHAR;
title = `${title}${ELLIPSIS_CHAR}`;
else
title = ELLIPSIS_CHAR + title;
title = `${ELLIPSIS_CHAR}${title}`;
}
if (this._event.end > periodEnd && !this._event.allDay) {
if (rtl)
title = ELLIPSIS_CHAR + title;
title = `${ELLIPSIS_CHAR}${title}`;
else
title = title + ELLIPSIS_CHAR;
title = `${title}${ELLIPSIS_CHAR}`;
}
return title;
}
};
});
var NotificationMessage =
var NotificationMessage = GObject.registerClass(
class NotificationMessage extends MessageList.Message {
constructor(notification) {
super(notification.title, notification.bannerBodyText);
_init(notification) {
super._init(notification.title, notification.bannerBodyText);
this.setUseBodyMarkup(notification.bannerBodyMarkup);
this.notification = notification;
@ -729,21 +777,22 @@ class NotificationMessage extends MessageList.Message {
}
_getIcon() {
if (this.notification.gicon)
if (this.notification.gicon) {
return new St.Icon({ gicon: this.notification.gicon,
icon_size: MESSAGE_ICON_SIZE });
else
} else {
return this.notification.source.createIcon(MESSAGE_ICON_SIZE);
}
}
_onUpdated(n, clear) {
_onUpdated(n, _clear) {
this.setIcon(this._getIcon());
this.setTitle(n.title);
this.setBody(n.bannerBodyText);
this.setUseBodyMarkup(n.bannerBodyMarkup);
}
_onClicked() {
vfunc_clicked() {
this.notification.activate();
}
@ -765,11 +814,12 @@ class NotificationMessage extends MessageList.Message {
canClose() {
return true;
}
};
});
var EventsSection = class EventsSection extends MessageList.MessageListSection {
constructor() {
super();
var EventsSection = GObject.registerClass(
class EventsSection extends MessageList.MessageListSection {
_init() {
super._init();
this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
this._desktopSettings.connect('changed', this._reloadEvents.bind(this));
@ -779,9 +829,9 @@ var EventsSection = class EventsSection extends MessageList.MessageListSection {
this._title = new St.Button({ style_class: 'events-section-title',
label: '',
x_align: St.Align.START,
can_focus: true });
this.actor.insert_child_below(this._title, null);
this._title.child.x_align = Clutter.ActorAlign.START;
this.insert_child_below(this._title, null);
this._title.connect('clicked', this._onTitleClicked.bind(this));
this._title.connect('key-focus-in', this._onKeyFocusIn.bind(this));
@ -792,6 +842,9 @@ var EventsSection = class EventsSection extends MessageList.MessageListSection {
}
setEventSource(eventSource) {
if (!(eventSource instanceof EventSourceBase))
throw new Error('Event source is not valid type');
this._eventSource = eventSource;
this._eventSource.connect('changed', this._reloadEvents.bind(this));
}
@ -808,14 +861,15 @@ var EventsSection = class EventsSection extends MessageList.MessageListSection {
let dayFormat;
let now = new Date();
if (sameYear(this._date, now))
if (sameYear(this._date, now)) {
/* Translators: Shown on calendar heading when selected day occurs on current year */
dayFormat = Shell.util_translate_time_string(NC_("calendar heading",
"%A, %B %-d"));
else
} else {
/* Translators: Shown on calendar heading when selected day occurs on different year */
dayFormat = Shell.util_translate_time_string(NC_("calendar heading",
"%A, %B %-d, %Y"));
}
this._title.label = this._date.toLocaleFormat(dayFormat);
}
@ -856,7 +910,7 @@ var EventsSection = class EventsSection extends MessageList.MessageListSection {
_appInstalledChanged() {
this._calendarApp = undefined;
this._title.reactive = (this._getCalendarApp() != null);
this._title.reactive = this._getCalendarApp() != null;
}
_getCalendarApp() {
@ -900,12 +954,29 @@ var EventsSection = class EventsSection extends MessageList.MessageListSection {
super._sync();
}
};
});
var NotificationSection =
var TimeLabel = GObject.registerClass(
class NotificationTimeLabel extends St.Label {
_init(datetime) {
super._init({
style_class: 'event-time',
x_align: Clutter.ActorAlign.START,
y_align: Clutter.ActorAlign.END,
});
this._datetime = datetime;
}
vfunc_map() {
this.text = Util.formatTimeSpan(this._datetime);
super.vfunc_map();
}
});
var NotificationSection = GObject.registerClass(
class NotificationSection extends MessageList.MessageListSection {
constructor() {
super();
_init() {
super._init();
this._sources = new Map();
this._nUrgent = 0;
@ -914,8 +985,6 @@ class NotificationSection extends MessageList.MessageListSection {
Main.messageTray.getSources().forEach(source => {
this._sourceAdded(Main.messageTray, source);
});
this.actor.connect('notify::mapped', this._onMapped.bind(this));
}
get allowed() {
@ -923,24 +992,13 @@ class NotificationSection extends MessageList.MessageListSection {
!Main.sessionMode.isGreeter;
}
_createTimeLabel(datetime) {
let label = new St.Label({ style_class: 'event-time',
x_align: Clutter.ActorAlign.START,
y_align: Clutter.ActorAlign.END });
label.connect('notify::mapped', () => {
if (label.mapped)
label.text = Util.formatTimeSpan(datetime);
});
return label;
}
_sourceAdded(tray, source) {
let obj = {
destroyId: 0,
notificationAddedId: 0,
};
obj.destroyId = source.connect('destroy', source => {
obj.destroyId = source.connect('destroy', () => {
this._onSourceDestroy(source, obj);
});
obj.notificationAddedId = source.connect('notification-added',
@ -951,13 +1009,13 @@ class NotificationSection extends MessageList.MessageListSection {
_onNotificationAdded(source, notification) {
let message = new NotificationMessage(notification);
message.setSecondaryActor(this._createTimeLabel(notification.datetime));
message.setSecondaryActor(new TimeLabel(notification.datetime));
let isUrgent = notification.urgency == MessageTray.Urgency.CRITICAL;
let updatedId = notification.connect('updated', () => {
message.setSecondaryActor(this._createTimeLabel(notification.datetime));
this.moveMessage(message, isUrgent ? 0 : this._nUrgent, this.actor.mapped);
message.setSecondaryActor(new TimeLabel(notification.datetime));
this.moveMessage(message, isUrgent ? 0 : this._nUrgent, this.mapped);
});
let destroyId = notification.connect('destroy', () => {
notification.disconnect(destroyId);
@ -977,7 +1035,7 @@ class NotificationSection extends MessageList.MessageListSection {
}
let index = isUrgent ? 0 : this._nUrgent;
this.addMessageAtIndex(message, index, this.actor.mapped);
this.addMessageAtIndex(message, index, this.mapped);
}
_onSourceDestroy(source, obj) {
@ -987,25 +1045,23 @@ class NotificationSection extends MessageList.MessageListSection {
this._sources.delete(source);
}
_onMapped() {
if (!this.actor.mapped)
return;
for (let message of this._messages.keys())
vfunc_map() {
this._messages.forEach(message => {
if (message.notification.urgency != MessageTray.Urgency.CRITICAL)
message.notification.acknowledged = true;
});
super.vfunc_map();
}
_shouldShow() {
return !this.empty && isToday(this._date);
}
};
var Placeholder = class Placeholder {
constructor() {
this.actor = new St.BoxLayout({ style_class: 'message-list-placeholder',
vertical: true });
});
var Placeholder = GObject.registerClass(
class Placeholder extends St.BoxLayout {
_init() {
super._init({ style_class: 'message-list-placeholder', vertical: true });
this._date = new Date();
let todayFile = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/no-notifications.svg');
@ -1014,10 +1070,10 @@ var Placeholder = class Placeholder {
this._otherIcon = new Gio.FileIcon({ file: otherFile });
this._icon = new St.Icon();
this.actor.add_actor(this._icon);
this.add_actor(this._icon);
this._label = new St.Label();
this.actor.add_actor(this._label);
this.add_actor(this._label);
this._sync();
}
@ -1044,44 +1100,56 @@ var Placeholder = class Placeholder {
this._label.text = _("No Events");
}
}
};
});
var CalendarMessageList = class CalendarMessageList {
constructor() {
this.actor = new St.Widget({ style_class: 'message-list',
layout_manager: new Clutter.BinLayout(),
x_expand: true, y_expand: true });
var CalendarMessageList = GObject.registerClass(
class CalendarMessageList extends St.Widget {
_init() {
super._init({
style_class: 'message-list',
layout_manager: new Clutter.BinLayout(),
x_expand: true,
y_expand: true,
});
this._placeholder = new Placeholder();
this.actor.add_actor(this._placeholder.actor);
this.add_actor(this._placeholder);
let box = new St.BoxLayout({ vertical: true,
x_expand: true, y_expand: true });
this.actor.add_actor(box);
this.add_actor(box);
this._scrollView = new St.ScrollView({ style_class: 'vfade',
overlay_scrollbars: true,
x_expand: true, y_expand: true,
x_fill: true, y_fill: true });
this._scrollView = new St.ScrollView({
style_class: 'vfade',
overlay_scrollbars: true,
x_expand: true, y_expand: true,
});
this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
box.add_actor(this._scrollView);
this._clearButton = new St.Button({ style_class: 'message-list-clear-button button',
label: _("Clear"),
can_focus: true });
this._clearButton.set_x_align(Clutter.ActorAlign.END);
this._clearButton = new St.Button({
style_class: 'message-list-clear-button button',
label: _('Clear'),
can_focus: true,
x_align: Clutter.ActorAlign.END,
});
this._clearButton.connect('clicked', () => {
let sections = [...this._sections.keys()];
sections.forEach((s) => s.clear());
this._sectionList.get_children().forEach(s => s.clear());
});
box.add_actor(this._clearButton);
this._placeholder.bind_property('visible',
this._clearButton, 'visible',
GObject.BindingFlags.INVERT_BOOLEAN);
this._sectionList = new St.BoxLayout({ style_class: 'message-list-sections',
vertical: true,
x_expand: true,
y_expand: true,
y_align: Clutter.ActorAlign.START });
this._sectionList.connect('actor-added', this._sync.bind(this));
this._sectionList.connect('actor-removed', this._sync.bind(this));
this._scrollView.add_actor(this._sectionList);
this._sections = new Map();
this._mediaSection = new Mpris.MediaSection();
this._addSection(this._mediaSection);
@ -1096,59 +1164,35 @@ var CalendarMessageList = class CalendarMessageList {
}
_addSection(section) {
let obj = {
destroyId: 0,
visibleId: 0,
emptyChangedId: 0,
canClearChangedId: 0,
keyFocusId: 0
};
obj.destroyId = section.actor.connect('destroy', () => {
this._removeSection(section);
});
obj.visibleId = section.actor.connect('notify::visible',
this._sync.bind(this));
obj.emptyChangedId = section.connect('empty-changed',
this._sync.bind(this));
obj.canClearChangedId = section.connect('can-clear-changed',
this._sync.bind(this));
obj.keyFocusId = section.connect('key-focus-in',
this._onKeyFocusIn.bind(this));
let connectionsIds = [];
this._sections.set(section, obj);
this._sectionList.add_actor(section.actor);
this._sync();
}
for (let prop of ['visible', 'empty', 'can-clear']) {
connectionsIds.push(
section.connect(`notify::${prop}`, this._sync.bind(this)));
}
connectionsIds.push(section.connect('message-focused', (_s, messageActor) => {
Util.ensureActorVisibleInScrollView(this._scrollView, messageActor);
}));
_removeSection(section) {
let obj = this._sections.get(section);
section.actor.disconnect(obj.destroyId);
section.actor.disconnect(obj.visibleId);
section.disconnect(obj.emptyChangedId);
section.disconnect(obj.canClearChangedId);
section.disconnect(obj.keyFocusId);
connectionsIds.push(section.connect('destroy', () => {
connectionsIds.forEach(id => section.disconnect(id));
this._sectionList.remove_actor(section);
}));
this._sections.delete(section);
this._sectionList.remove_actor(section.actor);
this._sync();
}
_onKeyFocusIn(section, actor) {
Util.ensureActorVisibleInScrollView(this._scrollView, actor);
this._sectionList.add_actor(section);
}
_sync() {
let sections = [...this._sections.keys()];
let sections = this._sectionList.get_children();
let visible = sections.some(s => s.allowed);
this.actor.visible = visible;
this.visible = visible;
if (!visible)
return;
let empty = sections.every(s => s.empty || !s.actor.visible);
this._placeholder.actor.visible = empty;
this._clearButton.visible = !empty;
let empty = sections.every(s => s.empty || !s.visible);
this._placeholder.visible = empty;
let canClear = sections.some(s => s.canClear && s.actor.visible);
let canClear = sections.some(s => s.canClear && s.visible);
this._clearButton.reactive = canClear;
}
@ -1157,8 +1201,7 @@ var CalendarMessageList = class CalendarMessageList {
}
setDate(date) {
for (let section of this._sections.keys())
section.setDate(date);
this._sectionList.get_children().forEach(s => s.setDate(date));
this._placeholder.setDate(date);
}
};
});

View File

@ -1,18 +1,22 @@
const { Clutter, Pango, St } = imports.gi;
/* exported CheckBox */
const { Clutter, GObject, Pango, St } = imports.gi;
var CheckBox = class CheckBox {
constructor(label) {
let container = new St.BoxLayout();
this.actor = new St.Button({ style_class: 'check-box',
child: container,
button_mask: St.ButtonMask.ONE,
toggle_mode: true,
can_focus: true,
x_fill: true,
y_fill: true });
var CheckBox = GObject.registerClass(
class CheckBox extends St.Button {
_init(label) {
let container = new St.BoxLayout({
x_expand: true,
y_expand: true,
});
super._init({
style_class: 'check-box',
child: container,
button_mask: St.ButtonMask.ONE,
toggle_mode: true,
can_focus: true,
});
this._box = new St.Bin();
this._box.set_y_align(Clutter.ActorAlign.START);
this._box = new St.Bin({ y_align: Clutter.ActorAlign.START });
container.add_actor(this._box);
this._label = new St.Label();
@ -31,4 +35,4 @@ var CheckBox = class CheckBox {
getLabelActor() {
return this._label;
}
};
});

View File

@ -1,19 +1,19 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported CloseDialog */
const { Clutter, Gio, GLib, GObject, Meta, Shell } = imports.gi;
const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Dialog = imports.ui.dialog;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
var FROZEN_WINDOW_BRIGHTNESS = -0.3;
var DIALOG_TRANSITION_TIME = 0.15;
var DIALOG_TRANSITION_TIME = 150;
var ALIVE_TIMEOUT = 5000;
var CloseDialog = GObject.registerClass({
Implements: [Meta.CloseDialog],
Properties: {
'window': GObject.ParamSpec.override('window', Meta.CloseDialog)
'window': GObject.ParamSpec.override('window', Meta.CloseDialog),
},
}, class CloseDialog extends GObject.Object {
_init(window) {
@ -46,6 +46,18 @@ var CloseDialog = GObject.registerClass({
return new Dialog.MessageDialogContent({ icon, title, subtitle });
}
_updateScale() {
// Since this is a child of MetaWindowActor (which, for Wayland clients,
// applies the geometry scale factor to its children itself, see
// meta_window_actor_set_geometry_scale()), make sure we don't apply
// the factor twice in the end.
if (this._window.get_client_type() !== Meta.WindowClientType.WAYLAND)
return;
let { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
this._dialog.set_scale(1 / scaleFactor, 1 / scaleFactor);
}
_initDialog() {
if (this._dialog)
return;
@ -61,9 +73,14 @@ var CloseDialog = GObject.registerClass({
default: true });
this._dialog.addButton({ label: _('Wait'),
action: this._onWait.bind(this),
key: Clutter.Escape });
key: Clutter.KEY_Escape });
global.focus_manager.add_group(this._dialog);
let themeContext = St.ThemeContext.get_for_stage(global.stage);
themeContext.connect('notify::scale-factor', this._updateScale.bind(this));
this._updateScale();
}
_addWindowEffect() {
@ -107,11 +124,12 @@ var CloseDialog = GObject.registerClass({
if (this._tracked === shouldTrack)
return;
if (shouldTrack)
if (shouldTrack) {
Main.layoutManager.trackChrome(this._dialog,
{ affectsInputRegion: true });
else
} else {
Main.layoutManager.untrackChrome(this._dialog);
}
// The buttons are broken when they aren't added to the input region,
// so disable them properly in that case
@ -145,15 +163,15 @@ var CloseDialog = GObject.registerClass({
this._addWindowEffect();
this._initDialog();
this._dialog.scale_y = 0;
this._dialog.set_pivot_point(0.5, 0.5);
this._dialog._dialog.scale_y = 0;
this._dialog._dialog.set_pivot_point(0.5, 0.5);
Tweener.addTween(this._dialog,
{ scale_y: 1,
transition: 'linear',
time: DIALOG_TRANSITION_TIME,
onComplete: this._onFocusChanged.bind(this)
});
this._dialog._dialog.ease({
scale_y: 1,
mode: Clutter.AnimationMode.LINEAR,
duration: DIALOG_TRANSITION_TIME,
onComplete: this._onFocusChanged.bind(this),
});
}
vfunc_hide() {
@ -175,14 +193,12 @@ var CloseDialog = GObject.registerClass({
this._dialog = null;
this._removeWindowEffect();
Tweener.addTween(dialog,
{ scale_y: 0,
transition: 'linear',
time: DIALOG_TRANSITION_TIME,
onComplete: () => {
dialog.destroy();
}
});
dialog._dialog.ease({
scale_y: 0,
mode: Clutter.AnimationMode.LINEAR,
duration: DIALOG_TRANSITION_TIME,
onComplete: () => dialog.destroy(),
});
}
vfunc_focus() {

View File

@ -1,3 +1,4 @@
/* exported ComponentManager */
const Main = imports.ui.main;
var ComponentManager = class {

View File

@ -1,7 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */
const { Gio, GLib } = imports.gi;
const Mainloop = imports.mainloop;
const Params = imports.misc.params;
const GnomeSession = imports.misc.gnomeSession;
@ -38,7 +38,7 @@ var AutomountManager = class {
this._driveDisconnectedId = this._volumeMonitor.connect('drive-disconnected', this._onDriveDisconnected.bind(this));
this._driveEjectButtonId = this._volumeMonitor.connect('drive-eject-button', this._onDriveEjectButton.bind(this));
this._mountAllId = Mainloop.idle_add(this._startupMountAll.bind(this));
this._mountAllId = GLib.idle_add(GLib.PRIORITY_DEFAULT, this._startupMountAll.bind(this));
GLib.Source.set_name_by_id(this._mountAllId, '[gnome-shell] this._startupMountAll');
}
@ -50,17 +50,16 @@ var AutomountManager = class {
this._volumeMonitor.disconnect(this._driveEjectButtonId);
if (this._mountAllId > 0) {
Mainloop.source_remove(this._mountAllId);
GLib.source_remove(this._mountAllId);
this._mountAllId = 0;
}
}
_InhibitorsChanged(object, senderName, [inhibtor]) {
_InhibitorsChanged(_object, _senderName, [_inhibitor]) {
this._session.IsInhibitedRemote(GNOME_SESSION_AUTOMOUNT_INHIBIT,
(result, error) => {
if (!error) {
if (!error)
this._inhibited = result[0];
}
});
}
@ -110,7 +109,7 @@ var AutomountManager = class {
// mount operation object
if (drive.can_stop()) {
drive.stop(Gio.MountUnmountFlags.FORCE, null, null,
(drive, res) => {
(o, res) => {
try {
drive.stop_finish(res);
} catch (e) {
@ -119,7 +118,7 @@ var AutomountManager = class {
});
} else if (drive.can_eject()) {
drive.eject_with_operation(Gio.MountUnmountFlags.FORCE, null, null,
(drive, res) => {
(o, res) => {
try {
drive.eject_with_operation_finish(res);
} catch (e) {
@ -156,7 +155,7 @@ var AutomountManager = class {
!volume.should_automount() ||
!volume.can_mount()) {
// allow the autorun to run anyway; this can happen if the
// mount gets added programmatically later, even if
// mount gets added programmatically later, even if
// should_automount() or can_mount() are false, like for
// blank optical media.
this._allowAutorun(volume);
@ -219,19 +218,19 @@ var AutomountManager = class {
_onVolumeRemoved(monitor, volume) {
if (volume._allowAutorunExpireId && volume._allowAutorunExpireId > 0) {
Mainloop.source_remove(volume._allowAutorunExpireId);
GLib.source_remove(volume._allowAutorunExpireId);
delete volume._allowAutorunExpireId;
}
this._volumeQueue =
this._volumeQueue.filter(element => (element != volume));
this._volumeQueue =
this._volumeQueue.filter(element => element != volume);
}
_reaskPassword(volume) {
let prevOperation = this._activeOperations.get(volume);
let existingDialog = prevOperation ? prevOperation.borrowDialog() : null;
let operation =
let operation =
new ShellMountOperation.ShellMountOperation(volume,
{ existingDialog: existingDialog });
{ existingDialog });
this._mountVolume(volume, operation);
}
@ -248,7 +247,7 @@ var AutomountManager = class {
}
_allowAutorunExpire(volume) {
let id = Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, () => {
let id = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, AUTORUN_EXPIRE_TIMEOUT_SECS, () => {
volume.allowAutorun = false;
delete volume._allowAutorunExpireId;
return GLib.SOURCE_REMOVE;

View File

@ -1,6 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */
const { Gio, St } = imports.gi;
const { Clutter, Gio, GObject, St } = imports.gi;
const GnomeSession = imports.misc.gnomeSession;
const Main = imports.ui.main;
@ -19,7 +20,7 @@ var AutorunSetting = {
RUN: 0,
IGNORE: 1,
FILES: 2,
ASK: 3
ASK: 3,
};
// misc utils
@ -40,7 +41,7 @@ function isMountRootHidden(root) {
let path = root.get_path();
// skip any mounts in hidden directory hierarchies
return (path.includes('/.'));
return path.includes('/.');
}
function isMountNonLocal(mount) {
@ -51,7 +52,7 @@ function isMountNonLocal(mount) {
if (volume == null)
return true;
return (volume.get_identifier("class") == "network");
return volume.get_identifier("class") == "network";
}
function startAppForMount(app, mount) {
@ -62,7 +63,7 @@ function startAppForMount(app, mount) {
files.push(root);
try {
retval = app.launch(files,
retval = app.launch(files,
global.create_app_launch_context(0, -1));
} catch (e) {
log(`Unable to launch the application ${app.get_name()}: ${e}`);
@ -71,8 +72,6 @@ function startAppForMount(app, mount) {
return retval;
}
/******************************************/
const HotplugSnifferIface = loadInterfaceXML('org.gnome.Shell.HotplugSniffer');
const HotplugSnifferProxy = Gio.DBusProxy.makeProxyWrapper(HotplugSnifferIface);
function HotplugSniffer() {
@ -116,19 +115,17 @@ var ContentTypeDiscoverer = class {
let hotplugSniffer = new HotplugSniffer();
hotplugSniffer.SniffURIRemote(root.get_uri(),
([contentTypes]) => {
this._emitCallback(mount, contentTypes);
});
result => {
[contentTypes] = result;
this._emitCallback(mount, contentTypes);
});
}
}
_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')
type => type != 'x-content/win32-software'
);
let apps = [];
@ -170,7 +167,7 @@ var AutorunManager = class {
if (!this._session.SessionIsActive)
return;
let discoverer = new ContentTypeDiscoverer((mount, apps, contentTypes) => {
let discoverer = new ContentTypeDiscoverer((m, apps, contentTypes) => {
this._dispatcher.addMount(mount, apps, contentTypes);
});
discoverer.guessContentTypes(mount);
@ -205,7 +202,7 @@ var AutorunDispatcher = class {
}
_getSourceForMount(mount) {
let filtered = this._sources.filter(source => (source.mount == mount));
let filtered = this._sources.filter(source => source.mount == mount);
// we always make sure not to add two sources for the same
// mount in addMount(), so it's safe to assume filtered.length
@ -217,11 +214,11 @@ var AutorunDispatcher = class {
}
_addSource(mount, apps) {
// if we already have a source showing for this
// if we already have a source showing for this
// mount, return
if (this._getSourceForMount(mount))
return;
// add a new source
this._sources.push(new AutorunSource(this._manager, mount, apps));
}
@ -249,11 +246,10 @@ var AutorunDispatcher = class {
let success = false;
let app = null;
if (setting == AutorunSetting.RUN) {
if (setting == AutorunSetting.RUN)
app = Gio.app_info_get_default_for_type(contentTypes[0], false);
} else if (setting == AutorunSetting.FILES) {
else if (setting == AutorunSetting.FILES)
app = Gio.app_info_get_default_for_type('inode/directory', false);
}
if (app)
success = startAppForMount(app, mount);
@ -266,7 +262,7 @@ var AutorunDispatcher = class {
removeMount(mount) {
let source = this._getSourceForMount(mount);
// if we aren't tracking this mount, don't do anything
if (!source)
return;
@ -276,9 +272,10 @@ var AutorunDispatcher = class {
}
};
var AutorunSource = class extends MessageTray.Source {
constructor(manager, mount, apps) {
super(mount.get_name());
var AutorunSource = GObject.registerClass(
class AutorunSource extends MessageTray.Source {
_init(manager, mount, apps) {
super._init(mount.get_name());
this._manager = manager;
this.mount = mount;
@ -288,7 +285,7 @@ var AutorunSource = class extends MessageTray.Source {
// add ourselves as a source, and popup the notification
Main.messageTray.add(this);
this.notify(this._notification);
this.showNotification(this._notification);
}
getIcon() {
@ -298,11 +295,12 @@ var AutorunSource = class extends MessageTray.Source {
_createPolicy() {
return new MessageTray.NotificationApplicationPolicy('org.gnome.Nautilus');
}
};
});
var AutorunNotification = class extends MessageTray.Notification {
constructor(manager, source) {
super(source, source.title);
var AutorunNotification = GObject.registerClass(
class AutorunNotification extends MessageTray.Notification {
_init(manager, source) {
super._init(source, source.title);
this._manager = manager;
this._mount = source.mount;
@ -322,20 +320,23 @@ var AutorunNotification = class extends MessageTray.Notification {
}
_buttonForApp(app) {
let box = new St.BoxLayout();
let box = new St.BoxLayout({
x_expand: true,
x_align: Clutter.ActorAlign.START,
});
let icon = new St.Icon({ gicon: app.get_icon(),
style_class: 'hotplug-notification-item-icon' });
box.add(icon);
let label = new St.Bin({ y_align: St.Align.MIDDLE,
child: new St.Label
({ text: _("Open with %s").format(app.get_name()) })
});
let label = new St.Bin({
child: new St.Label({
text: _("Open with %s").format(app.get_name()),
y_align: Clutter.ActorAlign.CENTER,
}),
});
box.add(label);
let button = new St.Button({ child: box,
x_fill: true,
x_align: St.Align.START,
x_expand: true,
button_mask: St.ButtonMask.ONE,
style_class: 'hotplug-notification-item button' });
@ -354,6 +355,6 @@ var AutorunNotification = class extends MessageTray.Notification {
let app = Gio.app_info_get_default_for_type('inode/directory', false);
startAppForMount(app, this._mount);
}
};
});
var Component = AutorunManager;

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */
const { Clutter, Gcr, Gio, GObject, Pango, Shell, St } = imports.gi;
@ -32,7 +33,7 @@ class KeyringDialog extends ModalDialog.ModalDialog {
this._cancelButton = this.addButton({ label: '',
action: this._onCancelButton.bind(this),
key: Clutter.Escape });
key: Clutter.KEY_Escape });
this._continueButton = this.addButton({ label: '',
action: this._onContinueButton.bind(this),
default: true });
@ -53,8 +54,12 @@ class KeyringDialog extends ModalDialog.ModalDialog {
_buildControlTable() {
let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
let table = new St.Widget({ style_class: 'keyring-dialog-control-table',
layout_manager: layout });
let table = new St.Widget({
style_class: 'keyring-dialog-control-table',
layout_manager: layout,
x_expand: true,
y_expand: true,
});
layout.hookup_style(table);
let rtl = table.get_text_direction() == Clutter.TextDirection.RTL;
let row = 0;
@ -65,24 +70,27 @@ class KeyringDialog extends ModalDialog.ModalDialog {
y_align: Clutter.ActorAlign.CENTER });
label.set_text(_("Password:"));
label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: '',
can_focus: true,
x_expand: true });
this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
this._passwordEntry = new St.PasswordEntry({
style_class: 'prompt-dialog-password-entry',
text: '',
can_focus: true,
x_expand: true,
});
ShellEntry.addContextMenu(this._passwordEntry);
this._passwordEntry.clutter_text.connect('activate', this._onPasswordActivate.bind(this));
this._workSpinner = new Animation.Spinner(WORK_SPINNER_ICON_SIZE, true);
this._workSpinner = new Animation.Spinner(WORK_SPINNER_ICON_SIZE, {
animate: true,
});
if (rtl) {
layout.attach(this._workSpinner.actor, 0, row, 1, 1);
layout.attach(this._workSpinner, 0, row, 1, 1);
layout.attach(this._passwordEntry, 1, row, 1, 1);
layout.attach(label, 2, row, 1, 1);
} else {
layout.attach(label, 0, row, 1, 1);
layout.attach(this._passwordEntry, 1, row, 1, 1);
layout.attach(this._workSpinner.actor, 2, row, 1, 1);
layout.attach(this._workSpinner, 2, row, 1, 1);
}
row++;
} else {
@ -91,16 +99,17 @@ class KeyringDialog extends ModalDialog.ModalDialog {
}
if (this.prompt.confirm_visible) {
var label = new St.Label(({ style_class: 'prompt-dialog-password-label',
x_align: Clutter.ActorAlign.START,
y_align: Clutter.ActorAlign.CENTER }));
var label = new St.Label({ style_class: 'prompt-dialog-password-label',
x_align: Clutter.ActorAlign.START,
y_align: Clutter.ActorAlign.CENTER });
label.set_text(_("Type again:"));
this._confirmEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: '',
can_focus: true,
x_expand: true });
this._confirmEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
ShellEntry.addContextMenu(this._confirmEntry, { isPassword: true });
this._confirmEntry = new St.PasswordEntry({
style_class: 'prompt-dialog-password-entry',
text: '',
can_focus: true,
x_expand: true,
});
ShellEntry.addContextMenu(this._confirmEntry);
this._confirmEntry.clutter_text.connect('activate', this._onConfirmActivate.bind(this));
if (rtl) {
layout.attach(this._confirmEntry, 0, row, 1, 1);
@ -117,11 +126,17 @@ class KeyringDialog extends ModalDialog.ModalDialog {
this.prompt.set_password_actor(this._passwordEntry ? this._passwordEntry.clutter_text : null);
this.prompt.set_confirm_actor(this._confirmEntry ? this._confirmEntry.clutter_text : null);
if (this._passwordEntry || this._confirmEntry) {
this._capsLockWarningLabel = new ShellEntry.CapsLockWarning();
layout.attach(this._capsLockWarningLabel, 1, row, 1, 1);
row++;
}
if (this.prompt.choice_visible) {
let choice = new CheckBox.CheckBox();
this.prompt.bind_property('choice-label', choice.getLabelActor(), 'text', GObject.BindingFlags.SYNC_CREATE);
this.prompt.bind_property('choice-chosen', choice.actor, 'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL);
layout.attach(choice.actor, rtl ? 0 : 1, row, 1, 1);
this.prompt.bind_property('choice-chosen', choice, 'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL);
layout.attach(choice, rtl ? 0 : 1, row, 1, 1);
row++;
}
@ -139,7 +154,7 @@ class KeyringDialog extends ModalDialog.ModalDialog {
}
this._controlTable = table;
this._content.messageBox.add(table, { x_fill: true, y_fill: true });
this._content.messageBox.add_child(table);
}
_updateSensitivity(sensitive) {
@ -176,21 +191,21 @@ class KeyringDialog extends ModalDialog.ModalDialog {
return false;
}
_onShowPassword(prompt) {
_onShowPassword() {
this._buildControlTable();
this._ensureOpen();
this._updateSensitivity(true);
this._passwordEntry.grab_key_focus();
}
_onShowConfirm(prompt) {
_onShowConfirm() {
this._buildControlTable();
this._ensureOpen();
this._updateSensitivity(true);
this._continueButton.grab_key_focus();
}
_onHidePrompt(prompt) {
_onHidePrompt() {
this.close();
}
@ -227,12 +242,14 @@ var KeyringDummyDialog = class {
}
};
var KeyringPrompter = class {
constructor() {
this._prompter = new Gcr.SystemPrompter();
this._prompter.connect('new-prompt', () => {
let dialog = this._enabled ? new KeyringDialog()
: new KeyringDummyDialog();
var KeyringPrompter = GObject.registerClass(
class KeyringPrompter extends Gcr.SystemPrompter {
_init() {
super._init();
this.connect('new-prompt', () => {
let dialog = this._enabled
? new KeyringDialog()
: new KeyringDummyDialog();
this._currentPrompt = dialog.prompt;
return this._currentPrompt;
});
@ -244,7 +261,7 @@ var KeyringPrompter = class {
enable() {
if (!this._registered) {
this._prompter.register(Gio.DBus.session);
this.register(Gio.DBus.session);
this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter',
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
this._registered = true;
@ -255,10 +272,10 @@ var KeyringPrompter = class {
disable() {
this._enabled = false;
if (this._prompter.prompting)
if (this.prompting)
this._currentPrompt.cancel();
this._currentPrompt = null;
}
};
});
var Component = KeyringPrompter;

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */
const { Clutter, Gio, GLib, GObject, NM, Pango, Shell, St } = imports.gi;
const Signals = imports.signals;
@ -53,12 +54,18 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog {
let reactive = secret.key != null;
secret.entry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: secret.value, can_focus: reactive,
reactive: reactive,
x_expand: true });
ShellEntry.addContextMenu(secret.entry,
{ isPassword: secret.password });
let entryParams = {
style_class: 'prompt-dialog-password-entry',
text: secret.value,
can_focus: reactive,
reactive,
x_expand: true,
};
if (secret.password)
secret.entry = new St.PasswordEntry(entryParams);
else
secret.entry = new St.Entry(entryParams);
ShellEntry.addContextMenu(secret.entry);
if (secret.validate)
secret.valid = secret.validate(secret);
@ -92,9 +99,14 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog {
layout.attach(secret.entry, 1, pos, 1, 1);
}
pos++;
}
if (secret.password)
secret.entry.clutter_text.set_password_char('\u25cf');
if (this._content.secrets.some(s => s.password)) {
this._capsLockWarningLabel = new ShellEntry.CapsLockWarning();
if (rtl)
layout.attach(this._capsLockWarningLabel, 0, pos, 1, 1);
else
layout.attach(this._capsLockWarningLabel, 1, pos, 1, 1);
}
contentBox.messageBox.add(secretTable);
@ -105,22 +117,20 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog {
descriptionLabel.clutter_text.line_wrap = true;
descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
contentBox.messageBox.add(descriptionLabel,
{ y_fill: true,
y_align: St.Align.START,
expand: true });
contentBox.messageBox.add_child(descriptionLabel);
}
this._okButton = { label: _("Connect"),
action: this._onOk.bind(this),
default: true
};
this._okButton = {
label: _("Connect"),
action: this._onOk.bind(this),
default: true,
};
this.setButtons([{ label: _("Cancel"),
action: this.cancel.bind(this),
key: Clutter.KEY_Escape,
},
this._okButton]);
this.setButtons([{
label: _("Cancel"),
action: this.cancel.bind(this),
key: Clutter.KEY_Escape,
}, this._okButton]);
this._updateOkButton();
}
@ -162,15 +172,15 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog {
if (value.length == 64) {
// must be composed of hexadecimal digits only
for (let i = 0; i < 64; i++) {
if (!((value[i] >= 'a' && value[i] <= 'f')
|| (value[i] >= 'A' && value[i] <= 'F')
|| (value[i] >= '0' && value[i] <= '9')))
if (!((value[i] >= 'a' && value[i] <= 'f') ||
(value[i] >= 'A' && value[i] <= 'F') ||
(value[i] >= '0' && value[i] <= '9')))
return false;
}
return true;
}
return (value.length >= 8 && value.length <= 63);
return value.length >= 8 && value.length <= 63;
}
_validateStaticWep(secret) {
@ -178,15 +188,15 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog {
if (secret.wep_key_type == NM.WepKeyType.KEY) {
if (value.length == 10 || value.length == 26) {
for (let i = 0; i < value.length; i++) {
if (!((value[i] >= 'a' && value[i] <= 'f')
|| (value[i] >= 'A' && value[i] <= 'F')
|| (value[i] >= '0' && value[i] <= '9')))
if (!((value[i] >= 'a' && value[i] <= 'f') ||
(value[i] >= 'A' && value[i] <= 'F') ||
(value[i] >= '0' && value[i] <= '9')))
return false;
}
} else if (value.length == 5 || value.length == 13) {
for (let i = 0; i < value.length; i++) {
if (!((value[i] >= 'a' && value[i] <= 'z')
|| (value[i] >= 'A' && value[i] <= 'Z')))
if (!((value[i] >= 'a' && value[i] <= 'z') ||
(value[i] >= 'A' && value[i] <= 'Z')))
return false;
}
} else {
@ -199,7 +209,7 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog {
return true;
}
_getWirelessSecrets(secrets, wirelessSetting) {
_getWirelessSecrets(secrets, _wirelessSetting) {
let wirelessSecuritySetting = this._connection.get_setting_wireless_security();
if (this._settingName == '802-1x') {
@ -211,6 +221,7 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog {
// First the easy ones
case 'wpa-none':
case 'wpa-psk':
case 'sae':
secrets.push({ label: _("Password: "), key: 'psk',
value: wirelessSecuritySetting.psk || '',
validate: this._validateWpaPsk, password: true });
@ -222,11 +233,12 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog {
validate: this._validateStaticWep, password: true });
break;
case 'ieee8021x':
if (wirelessSecuritySetting.auth_alg == 'leap') // Cisco LEAP
if (wirelessSecuritySetting.auth_alg == 'leap') { // Cisco LEAP
secrets.push({ label: _("Password: "), key: 'leap-password',
value: wirelessSecuritySetting.leap_password || '', password: true });
else // Dynamic (IEEE 802.1x) WEP
} else { // Dynamic (IEEE 802.1x) WEP
this._get8021xSecrets(secrets);
}
break;
case 'wpa-eap':
this._get8021xSecrets(secrets);
@ -241,15 +253,18 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog {
/* If hints were given we know exactly what we need to ask */
if (this._settingName == "802-1x" && this._hints.length) {
if (this._hints.includes('identity'))
if (this._hints.includes('identity')) {
secrets.push({ label: _("Username: "), key: 'identity',
value: ieee8021xSetting.identity || '', password: false });
if (this._hints.includes('password'))
}
if (this._hints.includes('password')) {
secrets.push({ label: _("Password: "), key: 'password',
value: ieee8021xSetting.password || '', password: true });
if (this._hints.includes('private-key-password'))
}
if (this._hints.includes('private-key-password')) {
secrets.push({ label: _("Private key password: "), key: 'private-key-password',
value: ieee8021xSetting.private_key_password || '', password: true });
}
return;
}
@ -385,7 +400,7 @@ var VPNRequestHandler = class {
this._newStylePlugin = authHelper.externalUIMode;
try {
let [success, pid, stdin, stdout, stderr] =
let [success_, pid, stdin, stdout, stderr] =
GLib.spawn_async_with_pipes(null, /* pwd */
argv,
null, /* envp */
@ -444,7 +459,7 @@ var VPNRequestHandler = class {
this._destroyed = true;
}
_vpnChildFinished(pid, status, requestObj) {
_vpnChildFinished(pid, status, _requestObj) {
this._childWatch = 0;
if (this._newStylePlugin) {
// For new style plugin, all work is done in the async reading functions
@ -486,7 +501,7 @@ var VPNRequestHandler = class {
_readStdoutOldStyle() {
this._dataStdout.read_line_async(GLib.PRIORITY_DEFAULT, null, (stream, result) => {
let [line, len] = this._dataStdout.read_line_finish_utf8(result);
let [line, len_] = this._dataStdout.read_line_finish_utf8(result);
if (line == null) {
// end of file
@ -541,7 +556,7 @@ var VPNRequestHandler = class {
message: keyfile.get_string(VPN_UI_GROUP, 'Description'),
secrets: [] };
let [groups, len] = keyfile.get_groups();
let [groups, len_] = keyfile.get_groups();
for (let i = 0; i < groups.length; i++) {
if (groups[i] == VPN_UI_GROUP)
continue;
@ -550,11 +565,12 @@ var VPNRequestHandler = class {
let shouldAsk = keyfile.get_boolean(groups[i], 'ShouldAsk');
if (shouldAsk) {
contentOverride.secrets.push({ label: keyfile.get_string(groups[i], 'Label'),
key: groups[i],
value: value,
password: keyfile.get_boolean(groups[i], 'IsSecret')
});
contentOverride.secrets.push({
label: keyfile.get_string(groups[i], 'Label'),
key: groups[i],
value,
password: keyfile.get_boolean(groups[i], 'IsSecret'),
});
} else {
if (!value.length) // Ignore empty secrets
continue;
@ -608,10 +624,11 @@ Signals.addSignalMethods(VPNRequestHandler.prototype);
var NetworkAgent = class {
constructor() {
this._native = new Shell.NetworkAgent({ identifier: 'org.gnome.Shell.NetworkAgent',
capabilities: NM.SecretAgentCapabilities.VPN_HINTS,
auto_register: false
});
this._native = new Shell.NetworkAgent({
identifier: 'org.gnome.Shell.NetworkAgent',
capabilities: NM.SecretAgentCapabilities.VPN_HINTS,
auto_register: false,
});
this._dialogs = { };
this._vpnRequests = { };
@ -620,7 +637,7 @@ var NetworkAgent = class {
this._pluginDir = Gio.file_new_for_path(Config.VPNDIR);
try {
let monitor = this._pluginDir.monitor(Gio.FileMonitorFlags.NONE, null);
monitor.connect('changed', () => this._vpnCacheBuilt = false);
monitor.connect('changed', () => (this._vpnCacheBuilt = false));
} catch (e) {
log(`Failed to create monitor for VPN plugin dir: ${e.message}`);
}
@ -729,7 +746,7 @@ var NetworkAgent = class {
});
Main.messageTray.add(source);
source.notify(notification);
source.showNotification(notification);
}
_newRequest(agent, requestId, connection, settingName, hints, flags) {

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */
const { AccountsService, Clutter, Gio, GLib,
GObject, Pango, PolkitAgent, Polkit, Shell, St } = imports.gi;
@ -10,12 +11,19 @@ const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry;
const UserWidget = imports.ui.userWidget;
const DialogMode = {
AUTH: 0,
CONFIRM: 1,
};
var DIALOG_ICON_SIZE = 48;
var WORK_SPINNER_ICON_SIZE = 16;
const DELAYED_RESET_TIMEOUT = 200;
var AuthenticationDialog = GObject.registerClass({
Signals: { 'done': { param_types: [GObject.TYPE_BOOLEAN] } }
Signals: { 'done': { param_types: [GObject.TYPE_BOOLEAN] } },
}, class AuthenticationDialog extends ModalDialog.ModalDialog {
_init(actionId, body, cookie, userNames) {
super._init({ styleClass: 'prompt-dialog' });
@ -23,7 +31,6 @@ var AuthenticationDialog = GObject.registerClass({
this.actionId = actionId;
this.message = body;
this.userNames = userNames;
this._wasDismissed = false;
this._sessionUpdatedId = Main.sessionMode.connect('updated', () => {
this.visible = !Main.sessionMode.isLocked;
@ -50,70 +57,65 @@ var AuthenticationDialog = GObject.registerClass({
userName = userNames[0];
this._user = AccountsService.UserManager.get_default().get_user(userName);
let userRealName = this._user.get_real_name();
this._userLoadedId = this._user.connect('notify::is_loaded',
this._onUserChanged.bind(this));
this._userChangedId = this._user.connect('changed',
this._onUserChanged.bind(this));
// Special case 'root'
let userIsRoot = false;
if (userName == 'root') {
userIsRoot = true;
userRealName = _("Administrator");
}
let userBox = new St.BoxLayout({
style_class: 'polkit-dialog-user-layout',
vertical: false,
});
content.messageBox.add(userBox);
if (userIsRoot) {
let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-root-label',
text: userRealName }));
content.messageBox.add(userLabel, { x_fill: false,
x_align: St.Align.START });
} else {
let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout',
vertical: false });
content.messageBox.add(userBox);
this._userAvatar = new UserWidget.Avatar(this._user,
{ iconSize: DIALOG_ICON_SIZE,
styleClass: 'polkit-dialog-user-icon' });
this._userAvatar.actor.hide();
userBox.add(this._userAvatar.actor,
{ x_fill: true,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.START });
let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-label',
text: userRealName }));
userBox.add(userLabel,
{ x_fill: true,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
}
this._userAvatar = new UserWidget.Avatar(this._user, {
iconSize: DIALOG_ICON_SIZE,
styleClass: 'polkit-dialog-user-icon',
});
userBox.add_child(this._userAvatar);
this._onUserChanged();
this._userLabel = new St.Label({
style_class: userName === 'root'
? 'polkit-dialog-user-root-label'
: 'polkit-dialog-user-label',
x_expand: true,
y_align: Clutter.ActorAlign.CENTER,
});
if (userName === 'root')
this._userLabel.text = _('Administrator');
userBox.add_child(this._userLabel);
this._passwordBox = new St.BoxLayout({ vertical: false, style_class: 'prompt-dialog-password-box' });
content.messageBox.add(this._passwordBox);
this._passwordLabel = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
this._passwordBox.add(this._passwordLabel, { y_fill: false, y_align: St.Align.MIDDLE });
this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: "",
can_focus: true });
ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
this._passwordLabel = new St.Label({
style_class: 'prompt-dialog-password-label',
y_align: Clutter.ActorAlign.CENTER,
});
this._passwordBox.add_child(this._passwordLabel);
this._passwordEntry = new St.PasswordEntry({
style_class: 'prompt-dialog-password-entry',
text: "",
can_focus: true,
x_expand: true,
});
ShellEntry.addContextMenu(this._passwordEntry);
this._passwordEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
this._passwordBox.add(this._passwordEntry,
{ expand: true });
this._passwordEntry.bind_property('reactive',
this._passwordEntry.clutter_text, 'editable',
GObject.BindingFlags.SYNC_CREATE);
this._passwordBox.add_child(this._passwordEntry);
this._workSpinner = new Animation.Spinner(WORK_SPINNER_ICON_SIZE, true);
this._passwordBox.add(this._workSpinner.actor);
this._workSpinner = new Animation.Spinner(WORK_SPINNER_ICON_SIZE, {
animate: true,
});
this._passwordBox.add(this._workSpinner);
this.setInitialKeyFocus(this._passwordEntry);
this._passwordBox.hide();
this._capsLockWarningLabel = new ShellEntry.CapsLockWarning({ style_class: 'prompt-dialog-caps-lock-warning' });
content.messageBox.add(this._capsLockWarningLabel);
this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label' });
this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._errorMessageLabel.clutter_text.line_wrap = true;
content.messageBox.add(this._errorMessageLabel, { x_fill: false, x_align: St.Align.START });
content.messageBox.add_child(this._errorMessageLabel);
this._errorMessageLabel.hide();
this._infoMessageLabel = new St.Label({ style_class: 'prompt-dialog-info-label' });
@ -136,15 +138,30 @@ var AuthenticationDialog = GObject.registerClass({
this._cancelButton = this.addButton({ label: _("Cancel"),
action: this.cancel.bind(this),
key: Clutter.Escape });
key: Clutter.KEY_Escape });
this._okButton = this.addButton({ label: _("Authenticate"),
action: this._onAuthenticateButtonPressed.bind(this),
default: true });
reactive: false });
this._okButton.bind_property('reactive',
this._okButton, 'can-focus',
GObject.BindingFlags.SYNC_CREATE);
this._passwordEntry.clutter_text.connect('text-changed', text => {
this._okButton.reactive = text.get_text().length > 0;
});
this._doneEmitted = false;
this._mode = -1;
this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
this._cookie = cookie;
this._userLoadedId = this._user.connect('notify::is-loaded',
this._onUserChanged.bind(this));
this._userChangedId = this._user.connect('changed',
this._onUserChanged.bind(this));
this._onUserChanged();
}
_setWorking(working) {
@ -154,8 +171,9 @@ var AuthenticationDialog = GObject.registerClass({
this._workSpinner.stop();
}
performAuthentication() {
this._destroySession();
_initiateSession() {
this._destroySession(DELAYED_RESET_TIMEOUT);
this._session = new PolkitAgent.Session({ identity: this._identityToAuth,
cookie: this._cookie });
this._sessionCompletedId = this._session.connect('completed', this._onSessionCompleted.bind(this));
@ -194,18 +212,15 @@ var AuthenticationDialog = GObject.registerClass({
}
}
_updateSensitivity(sensitive) {
this._passwordEntry.reactive = sensitive;
this._passwordEntry.clutter_text.editable = sensitive;
this._okButton.can_focus = sensitive;
this._okButton.reactive = sensitive;
this._setWorking(!sensitive);
}
_onEntryActivate() {
let response = this._passwordEntry.get_text();
this._updateSensitivity(false);
if (response.length === 0)
return;
this._passwordEntry.reactive = false;
this._okButton.reactive = false;
this._setWorking(true);
this._session.response(response);
// When the user responds, dismiss already shown info and
// error texts (if any)
@ -215,7 +230,10 @@ var AuthenticationDialog = GObject.registerClass({
}
_onAuthenticateButtonPressed() {
this._onEntryActivate();
if (this._mode === DialogMode.CONFIRM)
this._initiateSession();
else
this._onEntryActivate();
}
_onSessionCompleted(session, gainedAuthorization) {
@ -234,7 +252,7 @@ var AuthenticationDialog = GObject.registerClass({
* error providing authentication-method specific information),
* show "Sorry, that didn't work. Please try again."
*/
if (!this._errorMessageLabel.visible && !this._wasDismissed) {
if (!this._errorMessageLabel.visible) {
/* Translators: "that didn't work" refers to the fact that the
* requested authentication was not gained; this can happen
* because of an authentication error (like invalid password),
@ -246,27 +264,32 @@ var AuthenticationDialog = GObject.registerClass({
}
/* Try and authenticate again */
this.performAuthentication();
this._initiateSession();
}
}
_onSessionRequest(session, request, echoOn) {
if (this._sessionRequestTimeoutId) {
GLib.source_remove(this._sessionRequestTimeoutId);
this._sessionRequestTimeoutId = 0;
}
// Cheap localization trick
if (request == 'Password:' || request == 'Password: ')
this._passwordLabel.set_text(_("Password:"));
else
this._passwordLabel.set_text(request);
if (echoOn)
this._passwordEntry.clutter_text.set_password_char('');
else
this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
this._passwordEntry.password_visible = echoOn;
this._passwordBox.show();
this._passwordEntry.set_text('');
this._passwordEntry.grab_key_focus();
this._updateSensitivity(true);
this._passwordEntry.reactive = true;
this._okButton.reactive = false;
this._setWorking(false);
this._ensureOpen();
this._passwordEntry.grab_key_focus();
}
_onSessionShowError(session, text) {
@ -287,29 +310,78 @@ var AuthenticationDialog = GObject.registerClass({
this._ensureOpen();
}
_destroySession() {
_destroySession(delay = 0) {
if (this._session) {
if (!this._completed)
this._session.cancel();
this._completed = false;
this._session.disconnect(this._sessionCompletedId);
this._session.disconnect(this._sessionRequestId);
this._session.disconnect(this._sessionShowErrorId);
this._session.disconnect(this._sessionShowInfoId);
if (!this._completed)
this._session.cancel();
this._completed = false;
this._session = null;
}
if (this._sessionRequestTimeoutId) {
GLib.source_remove(this._sessionRequestTimeoutId);
this._sessionRequestTimeoutId = 0;
}
let resetDialog = () => {
if (this.state != ModalDialog.State.OPENED)
return;
this._passwordBox.hide();
this._cancelButton.grab_key_focus();
this._okButton.reactive = false;
};
if (delay) {
this._sessionRequestTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay, resetDialog);
GLib.Source.set_name_by_id(this._sessionRequestTimeoutId, '[gnome-shell] this._sessionRequestTimeoutId');
} else {
resetDialog();
}
}
_onUserChanged() {
if (this._user.is_loaded && this._userAvatar) {
this._userAvatar.update();
this._userAvatar.actor.show();
if (!this._user.is_loaded)
return;
let userName = this._user.get_user_name();
let realName = this._user.get_real_name();
if (userName !== 'root')
this._userLabel.set_text(realName);
this._userAvatar.update();
if (this._user.get_password_mode() === AccountsService.UserPasswordMode.NONE) {
if (this._mode === DialogMode.CONFIRM)
return;
this._mode = DialogMode.CONFIRM;
this._destroySession();
this._okButton.reactive = true;
/* We normally open the dialog when we get a "request" signal, but
* since in this case initiating a session would perform the
* authentication, only open the dialog and initiate the session
* when the user confirmed. */
this._ensureOpen();
} else {
if (this._mode === DialogMode.AUTH)
return;
this._mode = DialogMode.AUTH;
this._initiateSession();
}
}
cancel() {
this._wasDismissed = true;
this.close(global.get_current_time());
this._emitDone(true);
}
@ -317,7 +389,10 @@ var AuthenticationDialog = GObject.registerClass({
_onDialogClosed() {
if (this._sessionUpdatedId)
Main.sessionMode.disconnect(this._sessionUpdatedId);
this._sessionUpdatedId = 0;
if (this._sessionRequestTimeoutId)
GLib.source_remove(this._sessionRequestTimeoutId);
this._sessionRequestTimeoutId = 0;
if (this._user) {
this._user.disconnect(this._userLoadedId);
@ -329,19 +404,20 @@ var AuthenticationDialog = GObject.registerClass({
}
});
var AuthenticationAgent = class {
constructor() {
var AuthenticationAgent = GObject.registerClass(
class AuthenticationAgent extends Shell.PolkitAuthenticationAgent {
_init() {
super._init();
this._currentDialog = null;
this._handle = null;
this._native = new Shell.PolkitAuthenticationAgent();
this._native.connect('initiate', this._onInitiate.bind(this));
this._native.connect('cancel', this._onCancel.bind(this));
this.connect('initiate', this._onInitiate.bind(this));
this.connect('cancel', this._onCancel.bind(this));
this._sessionUpdatedId = 0;
}
enable() {
try {
this._native.register();
this.register();
} catch (e) {
log('Failed to register AuthenticationAgent');
}
@ -349,7 +425,7 @@ var AuthenticationAgent = class {
disable() {
try {
this._native.unregister();
this.unregister();
} catch (e) {
log('Failed to unregister AuthenticationAgent');
}
@ -368,26 +444,14 @@ var AuthenticationAgent = class {
}
this._currentDialog = new AuthenticationDialog(actionId, message, cookie, userNames);
// We actually don't want to open the dialog until we know for
// sure that we're going to interact with the user. For
// example, if the password for the identity to auth is blank
// (which it will be on a live CD) then there will be no
// conversation at all... of course, we don't *know* that
// until we actually try it.
//
// See https://bugzilla.gnome.org/show_bug.cgi?id=643062 for more
// discussion.
this._currentDialog.connect('done', this._onDialogDone.bind(this));
this._currentDialog.performAuthentication();
}
_onCancel(nativeAgent) {
_onCancel(_nativeAgent) {
this._completeRequest(false);
}
_onDialogDone(dialog, dismissed) {
_onDialogDone(_dialog, dismissed) {
this._completeRequest(dismissed);
}
@ -399,8 +463,8 @@ var AuthenticationAgent = class {
Main.sessionMode.disconnect(this._sessionUpdatedId);
this._sessionUpdatedId = 0;
this._native.complete(dismissed);
this.complete(dismissed);
}
};
});
var Component = AuthenticationAgent;

View File

@ -1,8 +1,8 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */
const { Clutter, Gio, GLib, GObject, St } = imports.gi;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
var Tpl = null;
var Tp = null;
@ -19,7 +19,7 @@ const MessageTray = imports.ui.messageTray;
const Params = imports.misc.params;
const Util = imports.misc.util;
const HAVE_TP = (Tp != null && Tpl != null);
const HAVE_TP = Tp != null && Tpl != null;
// See Notification.appendMessage
var SCROLLBACK_IMMEDIATE_TIME = 3 * 60; // 3 minutes
@ -37,11 +37,11 @@ var CHAT_EXPAND_LINES = 12;
var NotificationDirection = {
SENT: 'chat-sent',
RECEIVED: 'chat-received'
RECEIVED: 'chat-received',
};
function makeMessageFromTpMessage(tpMessage, direction) {
let [text, flags] = tpMessage.to_text();
let [text, flags_] = tpMessage.to_text();
let timestamp = tpMessage.get_sent_timestamp();
if (timestamp == 0)
@ -49,10 +49,10 @@ function makeMessageFromTpMessage(tpMessage, direction) {
return {
messageType: tpMessage.get_message_type(),
text: text,
text,
sender: tpMessage.sender.alias,
timestamp: timestamp,
direction: direction
timestamp,
direction,
};
}
@ -66,7 +66,7 @@ function makeMessageFromTplEvent(event) {
text: event.get_message(),
sender: event.get_sender().get_alias(),
timestamp: event.get_timestamp(),
direction: direction
direction,
};
}
@ -148,17 +148,17 @@ class TelepathyClient extends Tp.BaseClient {
}
vfunc_observe_channels(...args) {
let [account, conn, channels, dispatchOp, requests, context] = args;
let [account, conn, channels, dispatchOp_, requests_, context] = args;
let len = channels.length;
for (let i = 0; i < len; i++) {
let channel = channels[i];
let [targetHandle, targetHandleType] = channel.get_handle();
let [targetHandle_, targetHandleType] = channel.get_handle();
if (channel.get_invalidated())
continue;
/* Only observe contact text channels */
if ((!(channel instanceof Tp.TextChannel)) ||
if (!(channel instanceof Tp.TextChannel) ||
targetHandleType != Tp.HandleType.CONTACT)
continue;
@ -181,7 +181,7 @@ class TelepathyClient extends Tp.BaseClient {
}
vfunc_handle_channels(...args) {
let [account, conn, channels, requests, userActionTime, context] = args;
let [account, conn, channels, requests_, userActionTime_, context] = args;
this._handlingChannels(account, conn, channels, true);
context.accept();
}
@ -215,7 +215,7 @@ class TelepathyClient extends Tp.BaseClient {
// We are already handling the channel, display the source
let source = this._chatSources[channel.get_object_path()];
if (source)
source.notify();
source.showNotification();
}
}
}
@ -231,15 +231,16 @@ class TelepathyClient extends Tp.BaseClient {
return;
}
if (chanType == Tp.IFACE_CHANNEL_TYPE_TEXT)
if (chanType == Tp.IFACE_CHANNEL_TYPE_TEXT) {
this._approveTextChannel(account, conn, channel, dispatchOp, context);
else
} else {
context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT,
message: 'Unsupported channel type' }));
}
}
_approveTextChannel(account, conn, channel, dispatchOp, context) {
let [targetHandle, targetHandleType] = channel.get_handle();
let [targetHandle_, targetHandleType] = channel.get_handle();
if (targetHandleType != Tp.HandleType.CONTACT) {
context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT,
@ -248,7 +249,7 @@ class TelepathyClient extends Tp.BaseClient {
}
// Approve private text channels right away as we are going to handle it
dispatchOp.claim_with_async(this, (dispatchOp, result) => {
dispatchOp.claim_with_async(this, (o, result) => {
try {
dispatchOp.claim_with_finish(result);
this._handlingChannels(account, conn, [channel], false);
@ -260,15 +261,16 @@ class TelepathyClient extends Tp.BaseClient {
context.accept();
}
_delegatedChannelsCb(client, channels) {
_delegatedChannelsCb(_client, _channels) {
// Nothing to do as we don't make a distinction between observed and
// handled channels.
}
}) : null;
var ChatSource = class extends MessageTray.Source {
constructor(account, conn, channel, contact, client) {
super(contact.get_alias());
var ChatSource = HAVE_TP ? GObject.registerClass(
class ChatSource extends MessageTray.Source {
_init(account, conn, channel, contact, client) {
super._init(contact.get_alias());
this._account = account;
this._contact = contact;
@ -326,7 +328,7 @@ var ChatSource = class extends MessageTray.Source {
// We ack messages when the user expands the new notification
let id = this._banner.connect('expanded', this._ackMessages.bind(this));
this._banner.actor.connect('destroy', () => {
this._banner.connect('destroy', () => {
this._banner.disconnect(id);
this._banner = null;
});
@ -348,11 +350,10 @@ var ChatSource = class extends MessageTray.Source {
getIcon() {
let file = this._contact.get_avatar_file();
if (file) {
return new Gio.FileIcon({ file: file });
} else {
if (file)
return new Gio.FileIcon({ file });
else
return new Gio.ThemedIcon({ name: 'avatar-default' });
}
}
getSecondaryIcon() {
@ -386,10 +387,11 @@ var ChatSource = class extends MessageTray.Source {
_updateAvatarIcon() {
this.iconUpdated();
if (this._notifiction)
if (this._notifiction) {
this._notification.update(this._notification.title,
this._notification.bannerBodyText,
{ gicon: this.getIcon() });
}
}
open() {
@ -427,7 +429,7 @@ var ChatSource = class extends MessageTray.Source {
}
_displayPendingMessages(logManager, result) {
let [success, events] = logManager.get_filtered_events_finish(result);
let [success_, events] = logManager.get_filtered_events_finish(result);
let logMessages = events.map(makeMessageFromTplEvent);
this._ensureNotification();
@ -476,7 +478,7 @@ var ChatSource = class extends MessageTray.Source {
this._notification.appendMessage(pendingMessages[i], true);
if (pendingMessages.length > 0)
this.notify();
this.showNotification();
}
destroy(reason) {
@ -545,15 +547,15 @@ var ChatSource = class extends MessageTray.Source {
// Wait a bit before notifying for the received message, a handler
// could ack it in the meantime.
if (this._notifyTimeoutId != 0)
Mainloop.source_remove(this._notifyTimeoutId);
this._notifyTimeoutId = Mainloop.timeout_add(500,
GLib.source_remove(this._notifyTimeoutId);
this._notifyTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500,
this._notifyTimeout.bind(this));
GLib.Source.set_name_by_id(this._notifyTimeoutId, '[gnome-shell] this._notifyTimeout');
}
_notifyTimeout() {
if (this._pendingMessages.length != 0)
this.notify();
this.showNotification();
this._notifyTimeoutId = 0;
@ -562,14 +564,14 @@ var ChatSource = class extends MessageTray.Source {
// This is called for both messages we send from
// our client and other clients as well.
_messageSent(channel, message, flags, token) {
_messageSent(channel, message, _flags, _token) {
this._ensureNotification();
message = makeMessageFromTpMessage(message, NotificationDirection.SENT);
this._notification.appendMessage(message);
}
notify() {
super.notify(this._notification);
showNotification() {
super.showNotification(this._notification);
}
respond(text) {
@ -583,7 +585,7 @@ var ChatSource = class extends MessageTray.Source {
let msg = Tp.ClientMessage.new_text(type, text);
this._channel.send_message_async(msg, 0, (src, result) => {
this._channel.send_message_finish(result);
this._channel.send_message_finish(result);
});
}
@ -600,11 +602,12 @@ var ChatSource = class extends MessageTray.Source {
}
}
_presenceChanged(contact, presence, status, message) {
if (this._notification)
_presenceChanged(_contact, _presence, _status, _message) {
if (this._notification) {
this._notification.update(this._notification.title,
this._notification.bannerBodyText,
{ secondaryGIcon: this.getSecondaryIcon() });
}
}
_pendingRemoved(channel, message) {
@ -625,12 +628,18 @@ var ChatSource = class extends MessageTray.Source {
// 'pending-message-removed' for each one.
this._channel.ack_all_pending_messages_async(null);
}
};
}) : null;
var ChatNotification = class extends MessageTray.Notification {
constructor(source) {
super(source, source.title, null,
{ secondaryGIcon: source.getSecondaryIcon() });
var ChatNotification = HAVE_TP ? GObject.registerClass({
Signals: {
'message-removed': { param_types: [Tp.Message.$gtype] },
'message-added': { param_types: [Tp.Message.$gtype] },
'timestamp-changed': { param_types: [Tp.Message.$gtype] },
},
}, class ChatNotification extends MessageTray.Notification {
_init(source) {
super._init(source, source.title, null,
{ secondaryGIcon: source.getSecondaryIcon() });
this.setUrgency(MessageTray.Urgency.HIGH);
this.setResident(true);
@ -640,23 +649,23 @@ var ChatNotification = class extends MessageTray.Notification {
destroy(reason) {
if (this._timestampTimeoutId)
Mainloop.source_remove(this._timestampTimeoutId);
GLib.source_remove(this._timestampTimeoutId);
this._timestampTimeoutId = 0;
super.destroy(reason);
}
/**
* appendMessage:
* @message: An object with the properties:
* text: the body of the message,
* messageType: a #Tp.ChannelTextMessageType,
* sender: the name of the sender,
* timestamp: the time the message was sent
* direction: a #NotificationDirection
*
* @noTimestamp: Whether to add a timestamp. If %true, no timestamp
* will be added, regardless of the difference since the
* last timestamp
* @param {Object} message: An object with the properties
* {string} message.text: the body of the message,
* {Tp.ChannelTextMessageType} message.messageType: the type
* {string} message.sender: the name of the sender,
* {number} message.timestamp: the time the message was sent
* {NotificationDirection} message.direction: a #NotificationDirection
*
* @param {bool} noTimestamp: Whether to add a timestamp. If %true,
* no timestamp will be added, regardless of the difference since
* the last timestamp
*/
appendMessage(message, noTimestamp) {
let messageBody = GLib.markup_escape_text(message.text, -1);
@ -668,19 +677,20 @@ var ChatNotification = class extends MessageTray.Notification {
styles.push('chat-action');
}
if (message.direction == NotificationDirection.RECEIVED)
if (message.direction == NotificationDirection.RECEIVED) {
this.update(this.source.title, messageBody,
{ datetime: GLib.DateTime.new_from_unix_local (message.timestamp),
{ datetime: GLib.DateTime.new_from_unix_local(message.timestamp),
bannerMarkup: true });
}
let group = (message.direction == NotificationDirection.RECEIVED ?
'received' : 'sent');
let group = message.direction == NotificationDirection.RECEIVED
? 'received' : 'sent';
this._append({ body: messageBody,
group: group,
styles: styles,
group,
styles,
timestamp: message.timestamp,
noTimestamp: noTimestamp });
noTimestamp });
}
_filterMessages() {
@ -688,7 +698,7 @@ var ChatNotification = class extends MessageTray.Notification {
return;
let lastMessageTime = this.messages[0].timestamp;
let currentTime = (Date.now() / 1000);
let currentTime = Date.now() / 1000;
// Keep the scrollback from growing too long. If the most
// recent message (before the one we just added) is within
@ -696,8 +706,8 @@ var ChatNotification = class extends MessageTray.Notification {
// SCROLLBACK_RECENT_LENGTH previous messages. Otherwise
// we'll keep SCROLLBACK_IDLE_LENGTH messages.
let maxLength = (lastMessageTime < currentTime - SCROLLBACK_RECENT_TIME) ?
SCROLLBACK_IDLE_LENGTH : SCROLLBACK_RECENT_LENGTH;
let maxLength = lastMessageTime < currentTime - SCROLLBACK_RECENT_TIME
? SCROLLBACK_IDLE_LENGTH : SCROLLBACK_RECENT_LENGTH;
let filteredHistory = this.messages.filter(item => item.realMessage);
if (filteredHistory.length > maxLength) {
@ -710,16 +720,16 @@ var ChatNotification = class extends MessageTray.Notification {
/**
* _append:
* @props: An object with the properties:
* body: The text of the message.
* group: The group of the message, one of:
* @param {Object} props: An object with the properties:
* {string} props.body: The text of the message.
* {string} props.group: The group of the message, one of:
* 'received', 'sent', 'meta'.
* styles: Style class names for the message to have.
* timestamp: The timestamp of the message.
* noTimestamp: suppress timestamp signal?
* {string[]} props.styles: Style class names for the message to have.
* {number} props.timestamp: The timestamp of the message.
* {bool} props.noTimestamp: suppress timestamp signal?
*/
_append(props) {
let currentTime = (Date.now() / 1000);
let currentTime = Date.now() / 1000;
props = Params.parse(props, { body: null,
group: null,
styles: [],
@ -728,7 +738,7 @@ var ChatNotification = class extends MessageTray.Notification {
// Reset the old message timeout
if (this._timestampTimeoutId)
Mainloop.source_remove(this._timestampTimeoutId);
GLib.source_remove(this._timestampTimeoutId);
this._timestampTimeoutId = 0;
let message = { realMessage: props.group != 'meta',
@ -746,7 +756,8 @@ var ChatNotification = class extends MessageTray.Notification {
} else {
// Schedule a new timestamp in SCROLLBACK_IMMEDIATE_TIME
// from the timestamp of the message.
this._timestampTimeoutId = Mainloop.timeout_add_seconds(
this._timestampTimeoutId = GLib.timeout_add_seconds(
GLib.PRIORITY_DEFAULT,
SCROLLBACK_IMMEDIATE_TIME - (currentTime - timestamp),
this.appendTimestamp.bind(this));
GLib.Source.set_name_by_id(this._timestampTimeoutId, '[gnome-shell] this.appendTimestamp');
@ -781,7 +792,7 @@ var ChatNotification = class extends MessageTray.Notification {
this._filterMessages();
}
};
}) : null;
var ChatLineBox = GObject.registerClass(
class ChatLineBox extends St.BoxLayout {
@ -791,9 +802,10 @@ class ChatLineBox extends St.BoxLayout {
}
});
var ChatNotificationBanner = class extends MessageTray.NotificationBanner {
constructor(notification) {
super(notification);
var ChatNotificationBanner = GObject.registerClass(
class ChatNotificationBanner extends MessageTray.NotificationBanner {
_init(notification) {
super._init(notification);
this._responseEntry = new St.Entry({ style_class: 'chat-response',
x_expand: true,
@ -878,8 +890,7 @@ var ChatNotificationBanner = class extends MessageTray.NotificationBanner {
}
_addMessage(message) {
let highlighter = new MessageList.URLHighlighter(message.body, true, true);
let body = highlighter.actor;
let body = new MessageList.URLHighlighter(message.body, true, true);
let styles = message.styles;
for (let i = 0; i < styles.length; i++)
@ -951,14 +962,15 @@ var ChatNotificationBanner = class extends MessageTray.NotificationBanner {
// Remove composing timeout.
if (this._composingTimeoutId > 0) {
Mainloop.source_remove(this._composingTimeoutId);
GLib.source_remove(this._composingTimeoutId);
this._composingTimeoutId = 0;
}
if (text != '') {
this.notification.source.setChatState(Tp.ChannelChatState.COMPOSING);
this._composingTimeoutId = Mainloop.timeout_add_seconds(
this._composingTimeoutId = GLib.timeout_add_seconds(
GLib.PRIORITY_DEFAULT,
COMPOSING_STOP_TIMEOUT,
this._composingStopTimeout.bind(this));
GLib.Source.set_name_by_id(this._composingTimeoutId, '[gnome-shell] this._composingStopTimeout');
@ -966,6 +978,6 @@ var ChatNotificationBanner = class extends MessageTray.NotificationBanner {
this.notification.source.setChatState(Tp.ChannelChatState.ACTIVE);
}
}
};
});
var Component = TelepathyComponent;

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported CtrlAltTabManager */
const { Clutter, GObject, Meta, Shell, St } = imports.gi;
@ -11,7 +12,7 @@ var POPUP_APPICON_SIZE = 96;
var SortGroup = {
TOP: 0,
MIDDLE: 1,
BOTTOM: 2
BOTTOM: 2,
};
var CtrlAltTabManager = class CtrlAltTabManager {
@ -63,9 +64,8 @@ var CtrlAltTabManager = class CtrlAltTabManager {
if (a.sortGroup != b.sortGroup)
return a.sortGroup - b.sortGroup;
let ax, bx, y;
[ax, y] = a.proxy.get_transformed_position();
[bx, y] = b.proxy.get_transformed_position();
let [ax] = a.proxy.get_transformed_position();
let [bx] = b.proxy.get_transformed_position();
return ax - bx;
}
@ -86,25 +86,26 @@ var CtrlAltTabManager = class CtrlAltTabManager {
for (let i = 0; i < windows.length; i++) {
let icon = null;
let iconName = null;
if (windows[i].get_window_type () == Meta.WindowType.DESKTOP) {
if (windows[i].get_window_type() == Meta.WindowType.DESKTOP) {
iconName = 'video-display-symbolic';
} else {
let app = windowTracker.get_window_app(windows[i]);
if (app)
if (app) {
icon = app.create_icon_texture(POPUP_APPICON_SIZE);
else
} else {
icon = textureCache.bind_cairo_surface_property(windows[i],
'icon',
POPUP_APPICON_SIZE);
}
}
items.push({ name: windows[i].title,
proxy: windows[i].get_compositor_private(),
focusCallback: function(timestamp) {
Main.activateWindow(this, timestamp);
}.bind(windows[i]),
focusCallback: timestamp => {
Main.activateWindow(windows[i], timestamp);
},
iconActor: icon,
iconName: iconName,
iconName,
sortGroup: SortGroup.MIDDLE });
}
}
@ -143,9 +144,9 @@ class CtrlAltTabPopup extends SwitcherPopup.SwitcherPopup {
this._select(this._next());
else if (action == Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD)
this._select(this._previous());
else if (keysym == Clutter.Left)
else if (keysym == Clutter.KEY_Left)
this._select(this._previous());
else if (keysym == Clutter.Right)
else if (keysym == Clutter.KEY_Right)
this._select(this._next());
else
return Clutter.EVENT_PROPAGATE;
@ -177,10 +178,13 @@ class CtrlAltTabSwitcher extends SwitcherPopup.SwitcherList {
icon = new St.Icon({ icon_name: item.iconName,
icon_size: POPUP_APPICON_SIZE });
}
box.add(icon, { x_fill: false, y_fill: false } );
box.add_child(icon);
let text = new St.Label({ text: item.name });
box.add(text, { x_fill: false });
let text = new St.Label({
text: item.name,
x_align: Clutter.ActorAlign.CENTER,
});
box.add_child(text);
this.addItem(box, text);
}

View File

@ -1,36 +1,62 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Dash */
const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const { Clutter, GLib, GObject,
Graphene, Meta, Shell, St } = imports.gi;
const AppDisplay = imports.ui.appDisplay;
const AppFavorites = imports.ui.appFavorites;
const DND = imports.ui.dnd;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
var DASH_ANIMATION_TIME = 0.2;
var DASH_ITEM_LABEL_SHOW_TIME = 0.15;
var DASH_ITEM_LABEL_HIDE_TIME = 0.1;
var DASH_ANIMATION_TIME = 200;
var DASH_ITEM_LABEL_SHOW_TIME = 150;
var DASH_ITEM_LABEL_HIDE_TIME = 100;
var DASH_ITEM_HOVER_TIMEOUT = 300;
function getAppFromSource(source) {
if (source instanceof AppDisplay.AppIcon) {
if (source instanceof AppDisplay.AppIcon)
return source.app;
} else {
else
return null;
}
}
var DashIcon = GObject.registerClass(
class DashIcon extends AppDisplay.AppIcon {
_init(app) {
super._init(app, {
setSizeManually: true,
showLabel: false,
});
}
// Disable all DnD methods
_onDragBegin() {
}
_onDragEnd() {
}
handleDragOver() {
return DND.DragMotionResult.CONTINUE;
}
acceptDrop() {
return false;
}
});
// A container like StBin, but taking the child's scale into account
// when requesting a size
var DashItemContainer = GObject.registerClass(
class DashItemContainer extends St.Widget {
_init() {
super._init({ style_class: 'dash-item-container',
pivot_point: new Clutter.Point({ x: .5, y: .5 }),
pivot_point: new Graphene.Point({ x: .5, y: .5 }),
scale_x: 0,
scale_y: 0,
opacity: 0,
x_expand: true,
x_align: Clutter.ActorAlign.CENTER });
@ -41,10 +67,11 @@ class DashItemContainer extends St.Widget {
this.label_actor = this.label;
this.child = null;
this._childScale = 0;
this._childOpacity = 0;
this.animatingOut = false;
this.connect('notify::scale-x', () => this.queue_relayout());
this.connect('notify::scale-y', () => this.queue_relayout());
this.connect('destroy', () => {
if (this.child != null)
this.child.destroy();
@ -95,11 +122,11 @@ class DashItemContainer extends St.Widget {
x = stageX + this.get_width() + xOffset;
this.label.set_position(x, y);
Tweener.addTween(this.label,
{ opacity: 255,
time: DASH_ITEM_LABEL_SHOW_TIME,
transition: 'easeOutQuad',
});
this.label.ease({
opacity: 255,
duration: DASH_ITEM_LABEL_SHOW_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
setLabelText(text) {
@ -108,14 +135,12 @@ class DashItemContainer extends St.Widget {
}
hideLabel() {
Tweener.addTween(this.label,
{ opacity: 0,
time: DASH_ITEM_LABEL_HIDE_TIME,
transition: 'easeOutQuad',
onComplete: () => {
this.label.hide();
}
});
this.label.ease({
opacity: 0,
duration: DASH_ITEM_LABEL_HIDE_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this.label.hide(),
});
}
setChild(actor) {
@ -126,9 +151,6 @@ class DashItemContainer extends St.Widget {
this.child = actor;
this.add_actor(this.child);
this.set_scale(this._childScale, this._childScale);
this.set_opacity(this._childOpacity);
}
show(animate) {
@ -136,12 +158,13 @@ class DashItemContainer extends St.Widget {
return;
let time = animate ? DASH_ANIMATION_TIME : 0;
Tweener.addTween(this,
{ childScale: 1.0,
childOpacity: 255,
time: time,
transition: 'easeOutQuad'
});
this.ease({
scale_x: 1,
scale_y: 1,
opacity: 255,
duration: time,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
animateOutAndDestroy() {
@ -153,37 +176,14 @@ class DashItemContainer extends St.Widget {
}
this.animatingOut = true;
Tweener.addTween(this,
{ childScale: 0.0,
childOpacity: 0,
time: DASH_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: () => {
this.destroy();
}
});
}
set childScale(scale) {
this._childScale = scale;
this.set_scale(scale, scale);
this.queue_relayout();
}
get childScale() {
return this._childScale;
}
set childOpacity(opacity) {
this._childOpacity = opacity;
this.set_opacity(opacity);
this.queue_redraw();
}
get childOpacity() {
return this._childOpacity;
this.ease({
scale_x: 0,
scale_y: 0,
opacity: 0,
duration: DASH_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this.destroy(),
});
}
});
@ -241,14 +241,14 @@ class ShowAppsIcon extends DashItemContainer {
this.setLabelText(_("Show Applications"));
}
handleDragOver(source, actor, x, y, time) {
handleDragOver(source, _actor, _x, _y, _time) {
if (!this._canRemoveApp(getAppFromSource(source)))
return DND.DragMotionResult.NO_DROP;
return DND.DragMotionResult.MOVE_DROP;
}
acceptDrop(source, actor, x, y, time) {
acceptDrop(source, _actor, _x, _y, _time) {
let app = getAppFromSource(source);
if (!this._canRemoveApp(app))
return false;
@ -284,9 +284,12 @@ var DashActor = GObject.registerClass(
class DashActor extends St.Widget {
_init() {
let layout = new Clutter.BoxLayout({ orientation: Clutter.Orientation.VERTICAL });
super._init({ name: 'dash',
layout_manager: layout,
clip_to_allocation: true });
super._init({
name: 'dash',
layout_manager: layout,
clip_to_allocation: true,
y_align: Clutter.ActorAlign.CENTER,
});
}
vfunc_allocate(box, flags) {
@ -296,7 +299,7 @@ class DashActor extends St.Widget {
this.set_allocation(box, flags);
let [appIcons, showAppsButton] = this.get_children();
let [showAppsMinHeight, showAppsNatHeight] = showAppsButton.get_preferred_height(availWidth);
let [, showAppsNatHeight] = showAppsButton.get_preferred_height(availWidth);
let childBox = new Clutter.ActorBox();
childBox.x1 = contentBox.x1;
@ -330,8 +333,10 @@ class DashActor extends St.Widget {
const baseIconSizes = [16, 22, 24, 32, 48, 64];
var Dash = class Dash {
constructor() {
var Dash = GObject.registerClass({
Signals: { 'icon-size-changed': {} },
}, class Dash extends St.Bin {
_init() {
this._maxHeight = -1;
this.iconSize = 64;
this._shownInitially = false;
@ -351,8 +356,7 @@ var Dash = class Dash {
this._container.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
this._showAppsIcon = new ShowAppsIcon();
this._showAppsIcon.childScale = 1;
this._showAppsIcon.childOpacity = 255;
this._showAppsIcon.show(false);
this._showAppsIcon.icon.setIconSize(this.iconSize);
this._hookUpLabel(this._showAppsIcon);
@ -360,11 +364,11 @@ var Dash = class Dash {
this._container.add_actor(this._showAppsIcon);
this.actor = new St.Bin({ child: this._container });
this.actor.connect('notify::height', () => {
if (this._maxHeight != this.actor.height)
super._init({ child: this._container });
this.connect('notify::height', () => {
if (this._maxHeight != this.height)
this._queueRedisplay();
this._maxHeight = this.actor.height;
this._maxHeight = this.height;
});
this._workId = Main.initializeDeferredWork(this._box, this._redisplay.bind(this));
@ -387,13 +391,13 @@ var Dash = class Dash {
// Translators: this is the name of the dock/favorites area on
// the left of the overview
Main.ctrlAltTabManager.addGroup(this.actor, _("Dash"), 'user-bookmarks-symbolic');
Main.ctrlAltTabManager.addGroup(this, _("Dash"), 'user-bookmarks-symbolic');
}
_onDragBegin() {
this._dragCancelled = false;
this._dragMonitor = {
dragMotion: this._onDragMotion.bind(this)
dragMotion: this._onDragMotion.bind(this),
};
DND.addDragMonitor(this._dragMonitor);
@ -474,31 +478,19 @@ var Dash = class Dash {
}
_createAppItem(app) {
let appIcon = new AppDisplay.AppIcon(app,
{ setSizeManually: true,
showLabel: false });
if (appIcon._draggable) {
appIcon._draggable.connect('drag-begin',
() => {
appIcon.actor.opacity = 50;
});
appIcon._draggable.connect('drag-end',
() => {
appIcon.actor.opacity = 255;
});
}
let appIcon = new DashIcon(app);
appIcon.connect('menu-state-changed',
(appIcon, opened) => {
(o, opened) => {
this._itemMenuStateChanged(item, opened);
});
let item = new DashItemContainer();
item.setChild(appIcon.actor);
item.setChild(appIcon);
// Override default AppIcon label_actor, now the
// accessible_name is set at DashItemContainer.setLabelText
appIcon.actor.label_actor = null;
appIcon.label_actor = null;
item.setLabelText(app.get_name());
appIcon.icon.setIconSize(this.iconSize);
@ -512,7 +504,7 @@ var Dash = class Dash {
// that the notify::hover handler does everything we need to.
if (opened) {
if (this._showLabelTimeoutId > 0) {
Mainloop.source_remove(this._showLabelTimeoutId);
GLib.source_remove(this._showLabelTimeoutId);
this._showLabelTimeoutId = 0;
}
@ -526,7 +518,7 @@ var Dash = class Dash {
if (shouldShow) {
if (this._showLabelTimeoutId == 0) {
let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT;
this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
this._showLabelTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, timeout,
() => {
this._labelShowing = true;
item.showLabel();
@ -535,17 +527,17 @@ var Dash = class Dash {
});
GLib.Source.set_name_by_id(this._showLabelTimeoutId, '[gnome-shell] item.showLabel');
if (this._resetHoverTimeoutId > 0) {
Mainloop.source_remove(this._resetHoverTimeoutId);
GLib.source_remove(this._resetHoverTimeoutId);
this._resetHoverTimeoutId = 0;
}
}
} else {
if (this._showLabelTimeoutId > 0)
Mainloop.source_remove(this._showLabelTimeoutId);
GLib.source_remove(this._showLabelTimeoutId);
this._showLabelTimeoutId = 0;
item.hideLabel();
if (this._labelShowing) {
this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT,
this._resetHoverTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, DASH_ITEM_HOVER_TIMEOUT,
() => {
this._labelShowing = false;
this._resetHoverTimeoutId = 0;
@ -633,12 +625,12 @@ var Dash = class Dash {
icon.icon.set_size(icon.icon.width * scale,
icon.icon.height * scale);
Tweener.addTween(icon.icon,
{ width: targetWidth,
height: targetHeight,
time: DASH_ANIMATION_TIME,
transition: 'easeOutQuad',
});
icon.icon.ease({
width: targetWidth,
height: targetHeight,
duration: DASH_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
}
@ -716,8 +708,8 @@ var Dash = class Dash {
}
// App moved
let nextApp = newApps.length > newIndex + 1 ? newApps[newIndex + 1]
: null;
let nextApp = newApps.length > newIndex + 1
? newApps[newIndex + 1] : null;
let insertHere = nextApp && nextApp == oldApp;
let alreadyRemoved = removedActors.reduce((result, actor) => {
let removedApp = actor.child._delegate.app;
@ -736,9 +728,10 @@ var Dash = class Dash {
}
}
for (let i = 0; i < addedItems.length; i++)
for (let i = 0; i < addedItems.length; i++) {
this._box.insert_child_at_index(addedItems[i].item,
addedItems[i].pos);
}
for (let i = 0; i < removedActors.length; i++) {
let item = removedActors[i];
@ -762,9 +755,8 @@ var Dash = class Dash {
if (!this._shownInitially)
this._shownInitially = true;
for (let i = 0; i < addedItems.length; i++) {
for (let i = 0; i < addedItems.length; i++)
addedItems[i].item.show(animate);
}
// Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744
// Without it, StBoxLayout may use a stale size cache
@ -790,7 +782,7 @@ var Dash = class Dash {
}
}
handleDragOver(source, actor, x, y, time) {
handleDragOver(source, actor, x, y, _time) {
let app = getAppFromSource(source);
// Don't allow favoriting of transient apps
@ -844,8 +836,8 @@ var Dash = class Dash {
}
this._dragPlaceholder = new DragPlaceholderItem();
this._dragPlaceholder.child.set_width (this.iconSize);
this._dragPlaceholder.child.set_height (this.iconSize / 2);
this._dragPlaceholder.child.set_width(this.iconSize);
this._dragPlaceholder.child.set_height(this.iconSize / 2);
this._box.insert_child_at_index(this._dragPlaceholder,
this._dragPlaceholderPos);
this._dragPlaceholder.show(fadeIn);
@ -859,7 +851,7 @@ var Dash = class Dash {
if (!this._dragPlaceholder)
return DND.DragMotionResult.NO_DROP;
let srcIsFavorite = (favPos != -1);
let srcIsFavorite = favPos != -1;
if (srcIsFavorite)
return DND.DragMotionResult.MOVE_DROP;
@ -868,13 +860,12 @@ var Dash = class Dash {
}
// Draggable target interface
acceptDrop(source, actor, x, y, time) {
acceptDrop(source, _actor, _x, _y, _time) {
let app = getAppFromSource(source);
// Don't allow favoriting of transient apps
if (app == null || app.is_window_backed()) {
if (app == null || app.is_window_backed())
return false;
}
if (!global.settings.is_writable('favorite-apps'))
return false;
@ -883,7 +874,7 @@ var Dash = class Dash {
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
let srcIsFavorite = (id in favorites);
let srcIsFavorite = id in favorites;
let favPos = 0;
let children = this._box.get_children();
@ -915,5 +906,4 @@ var Dash = class Dash {
return true;
}
};
Signals.addSignalMethods(Dash.prototype);
});

View File

@ -1,7 +1,8 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported DateMenuButton */
const { Clutter, GLib, GnomeDesktop,
GObject, GWeather, Shell, St } = imports.gi;
const { Clutter, Gio, GLib, GnomeDesktop,
GObject, GWeather, Pango, Shell, St } = imports.gi;
const Util = imports.misc.util;
const Main = imports.ui.main;
@ -10,8 +11,13 @@ const Calendar = imports.ui.calendar;
const Weather = imports.misc.weather;
const System = imports.system;
const { loadInterfaceXML } = imports.misc.fileUtils;
const MAX_FORECASTS = 5;
const ClocksIntegrationIface = loadInterfaceXML('org.gnome.Shell.ClocksIntegration');
const ClocksProxy = Gio.DBusProxy.makeProxyWrapper(ClocksIntegrationIface);
function _isToday(date) {
let now = new Date();
return now.getYear() == date.getYear() &&
@ -19,22 +25,25 @@ function _isToday(date) {
now.getDate() == date.getDate();
}
var TodayButton = class TodayButton {
constructor(calendar) {
function _gDateTimeToDate(datetime) {
return new Date(datetime.to_unix() * 1000 + datetime.get_microsecond() / 1000);
}
var TodayButton = GObject.registerClass(
class TodayButton extends St.Button {
_init(calendar) {
// Having the ability to go to the current date if the user is already
// on the current date can be confusing. So don't make the button reactive
// until the selected date changes.
this.actor = new St.Button({ style_class: 'datemenu-today-button',
x_expand: true, x_align: St.Align.START,
can_focus: true,
reactive: false
});
this.actor.connect('clicked', () => {
this._calendar.setDate(new Date(), false);
super._init({
style_class: 'datemenu-today-button',
x_expand: true,
can_focus: true,
reactive: false,
});
let hbox = new St.BoxLayout({ vertical: true });
this.actor.add_actor(hbox);
this.add_actor(hbox);
this._dayLabel = new St.Label({ style_class: 'day-label',
x_align: Clutter.ActorAlign.START });
@ -44,13 +53,17 @@ var TodayButton = class TodayButton {
hbox.add_actor(this._dateLabel);
this._calendar = calendar;
this._calendar.connect('selected-date-changed', (calendar, date) => {
this._calendar.connect('selected-date-changed', (_calendar, datetime) => {
// Make the button reactive only if the selected date is not the
// current date.
this.actor.reactive = !_isToday(date);
this.reactive = !_isToday(_gDateTimeToDate(datetime));
});
}
vfunc_clicked() {
this._calendar.setDate(new Date(), false);
}
setDate(date) {
this._dayLabel.set_text(date.toLocaleFormat('%A'));
@ -59,65 +72,81 @@ var TodayButton = class TodayButton {
* "Tue 9:29 AM"). The string itself should become a full date, e.g.,
* "February 17 2015".
*/
let dateFormat = Shell.util_translate_time_string (N_("%B %-d %Y"));
let dateFormat = Shell.util_translate_time_string(N_("%B %-d %Y"));
this._dateLabel.set_text(date.toLocaleFormat(dateFormat));
/* Translators: This is the accessible name of the date button shown
* below the time in the shell; it should combine the weekday and the
* date, e.g. "Tuesday February 17 2015".
*/
dateFormat = Shell.util_translate_time_string (N_("%A %B %e %Y"));
this.actor.accessible_name = date.toLocaleFormat(dateFormat);
dateFormat = Shell.util_translate_time_string(N_("%A %B %e %Y"));
this.accessible_name = date.toLocaleFormat(dateFormat);
}
};
});
var WorldClocksSection = class WorldClocksSection {
constructor() {
var WorldClocksSection = GObject.registerClass(
class WorldClocksSection extends St.Button {
_init() {
super._init({
style_class: 'world-clocks-button',
can_focus: true,
x_expand: true,
});
this._clock = new GnomeDesktop.WallClock();
this._clockNotifyId = 0;
this._locations = [];
this.actor = new St.Button({ style_class: 'world-clocks-button',
x_fill: true,
can_focus: true });
this.actor.connect('clicked', () => {
this._clockAppMon.activateApp();
Main.overview.hide();
Main.panel.closeCalendar();
});
let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
this._grid = new St.Widget({ style_class: 'world-clocks-grid',
x_expand: true,
layout_manager: layout });
layout.hookup_style(this._grid);
this.actor.child = this._grid;
this.child = this._grid;
this._clockAppMon = new Util.AppSettingsMonitor('org.gnome.clocks.desktop',
'org.gnome.clocks');
this._clockAppMon.connect('available-changed',
this._sync.bind(this));
this._clockAppMon.watchSetting('world-clocks',
this._clocksChanged.bind(this));
this._clocksApp = null;
this._clocksProxy = new ClocksProxy(
Gio.DBus.session,
'org.gnome.clocks',
'/org/gnome/clocks',
this._onProxyReady.bind(this),
null /* cancellable */,
Gio.DBusProxyFlags.DO_NOT_AUTO_START | Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
this._settings = new Gio.Settings({
schema_id: 'org.gnome.shell.world-clocks',
});
this._settings.connect('changed', this._clocksChanged.bind(this));
this._clocksChanged();
this._appSystem = Shell.AppSystem.get_default();
this._appSystem.connect('installed-changed',
this._sync.bind(this));
this._sync();
}
_sync() {
this.actor.visible = this._clockAppMon.available;
vfunc_clicked() {
if (this._clocksApp)
this._clocksApp.activate();
Main.overview.hide();
Main.panel.closeCalendar();
}
_clocksChanged(settings) {
_sync() {
this._clocksApp = this._appSystem.lookup_app('org.gnome.clocks.desktop');
this.visible = this._clocksApp != null;
}
_clocksChanged() {
this._grid.destroy_all_children();
this._locations = [];
let world = GWeather.Location.get_world();
let clocks = settings.get_value('world-clocks').deep_unpack();
let clocks = this._settings.get_value('locations').deep_unpack();
for (let i = 0; i < clocks.length; i++) {
if (!clocks[i].location)
continue;
let l = world.deserialize(clocks[i].location);
let l = world.deserialize(clocks[i]);
if (l && l.get_timezone() != null)
this._locations.push({ location: l });
}
@ -128,13 +157,14 @@ var WorldClocksSection = class WorldClocksSection {
});
let layout = this._grid.layout_manager;
let title = (this._locations.length == 0) ? _("Add world clocks…")
: _("World Clocks");
let title = this._locations.length == 0
? _("Add world clocks")
: _("World Clocks");
let header = new St.Label({ style_class: 'world-clocks-header',
x_align: Clutter.ActorAlign.START,
text: title });
layout.attach(header, 0, 0, 2, 1);
this.actor.label_actor = header;
this.label_actor = header;
let localOffset = GLib.DateTime.new_now_local().get_utc_offset();
@ -152,8 +182,8 @@ var WorldClocksSection = class WorldClocksSection {
let otherOffset = this._getTimeAtLocation(l).get_utc_offset();
let offset = (otherOffset - localOffset) / GLib.TIME_SPAN_HOUR;
let fmt = (Math.trunc(offset) == offset) ? '%s%.0f' : '%s%.1f';
let prefix = (offset >= 0) ? '+' : '-';
let fmt = Math.trunc(offset) == offset ? '%s%.0f' : '%s%.1f';
let prefix = offset >= 0 ? '+' : '-';
let tz = new St.Label({ style_class: 'world-clocks-timezone',
text: fmt.format(prefix, Math.abs(offset)),
x_align: Clutter.ActorAlign.END,
@ -173,9 +203,10 @@ var WorldClocksSection = class WorldClocksSection {
}
if (this._grid.get_n_children() > 1) {
if (!this._clockNotifyId)
if (!this._clockNotifyId) {
this._clockNotifyId =
this._clock.connect('notify::clock', this._updateLabels.bind(this));
}
this._updateLabels();
} else {
if (this._clockNotifyId)
@ -196,45 +227,68 @@ var WorldClocksSection = class WorldClocksSection {
l.actor.text = Util.formatTime(now, { timeOnly: true });
}
}
};
var WeatherSection = class WeatherSection {
constructor() {
_onProxyReady(proxy, error) {
if (error) {
log(`Failed to create GNOME Clocks proxy: ${error}`);
return;
}
this._clocksProxy.connect('g-properties-changed',
this._onClocksPropertiesChanged.bind(this));
this._onClocksPropertiesChanged();
}
_onClocksPropertiesChanged() {
if (this._clocksProxy.g_name_owner == null)
return;
this._settings.set_value('locations',
new GLib.Variant('av', this._clocksProxy.Locations));
}
});
var WeatherSection = GObject.registerClass(
class WeatherSection extends St.Button {
_init() {
super._init({
style_class: 'weather-button',
can_focus: true,
x_expand: true,
});
this._weatherClient = new Weather.WeatherClient();
this.actor = new St.Button({ style_class: 'weather-button',
x_fill: true,
can_focus: true });
this.actor.connect('clicked', () => {
this._weatherClient.activateApp();
Main.overview.hide();
Main.panel.closeCalendar();
});
this.actor.connect('notify::mapped', () => {
if (this.actor.mapped)
this._weatherClient.update();
let box = new St.BoxLayout({
style_class: 'weather-box',
vertical: true,
x_expand: true,
});
let box = new St.BoxLayout({ style_class: 'weather-box',
vertical: true });
this.child = box;
this.actor.child = box;
let titleBox = new St.BoxLayout();
titleBox.add_child(new St.Label({ style_class: 'weather-header',
x_align: Clutter.ActorAlign.START,
x_expand: true,
text: _("Weather") }));
let titleBox = new St.BoxLayout({ style_class: 'weather-header-box' });
titleBox.add_child(new St.Label({
style_class: 'weather-header',
x_align: Clutter.ActorAlign.START,
x_expand: true,
y_align: Clutter.ActorAlign.END,
text: _('Weather'),
}));
box.add_child(titleBox);
this._titleLocation = new St.Label({ style_class: 'weather-header location',
x_align: Clutter.ActorAlign.END });
this._titleLocation = new St.Label({
style_class: 'weather-header location',
x_align: Clutter.ActorAlign.END,
y_align: Clutter.ActorAlign.END,
});
titleBox.add_child(this._titleLocation);
let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
this._forecastGrid = new St.Widget({ style_class: 'weather-grid',
layout_manager: layout });
this._forecastGrid = new St.Widget({
style_class: 'weather-grid',
layout_manager: layout,
});
layout.hookup_style(this._forecastGrid);
box.add_child(this._forecastGrid);
@ -242,26 +296,40 @@ var WeatherSection = class WeatherSection {
this._sync();
}
vfunc_map() {
this._weatherClient.update();
super.vfunc_map();
}
vfunc_clicked() {
this._weatherClient.activateApp();
Main.overview.hide();
Main.panel.closeCalendar();
}
_getInfos() {
let info = this._weatherClient.info;
let forecasts = info.get_forecast_list();
let forecasts = this._weatherClient.info.get_forecast_list();
let current = info;
let infos = [info];
let now = GLib.DateTime.new_now_local();
let current = GLib.DateTime.new_from_unix_local(0);
let infos = [];
for (let i = 0; i < forecasts.length; i++) {
let [ok, timestamp] = forecasts[i].get_value_update();
let datetime = new Date(timestamp * 1000);
if (!_isToday(datetime))
continue; // Ignore forecasts from other days
const [valid, timestamp] = forecasts[i].get_value_update();
if (!valid || timestamp === 0)
continue; // 0 means 'never updated'
[ok, timestamp] = current.get_value_update();
let currenttime = new Date(timestamp * 1000);
if (currenttime.getHours() == datetime.getHours())
const datetime = GLib.DateTime.new_from_unix_local(timestamp);
if (now.difference(datetime) > 0)
continue; // Ignore earlier forecasts
if (datetime.difference(current) < GLib.TIME_SPAN_HOUR)
continue; // Enforce a minimum interval of 1h
current = forecasts[i];
if (infos.push(current) == MAX_FORECASTS)
if (infos.push(forecasts[i]) == MAX_FORECASTS)
break; // Use a maximum of five forecasts
current = datetime;
}
return infos;
}
@ -275,21 +343,31 @@ var WeatherSection = class WeatherSection {
let col = 0;
infos.forEach(fc => {
let [ok, timestamp] = fc.get_value_update();
const [valid_, timestamp] = fc.get_value_update();
let timeStr = Util.formatTime(new Date(timestamp * 1000), {
timeOnly: true
timeOnly: true,
ampm: false,
});
let icon = new St.Icon({ style_class: 'weather-forecast-icon',
icon_name: fc.get_symbolic_icon_name(),
x_align: Clutter.ActorAlign.CENTER,
x_expand: true });
let temp = new St.Label({ style_class: 'weather-forecast-temp',
text: fc.get_temp_summary(),
x_align: Clutter.ActorAlign.CENTER });
let time = new St.Label({ style_class: 'weather-forecast-time',
text: timeStr,
x_align: Clutter.ActorAlign.CENTER });
let icon = new St.Icon({
style_class: 'weather-forecast-icon',
icon_name: fc.get_symbolic_icon_name(),
x_align: Clutter.ActorAlign.CENTER,
x_expand: true,
});
let temp = new St.Label({
style_class: 'weather-forecast-temp',
text: fc.get_temp_summary(),
x_align: Clutter.ActorAlign.CENTER,
});
let time = new St.Label({
style_class: 'weather-forecast-time',
text: timeStr,
x_align: Clutter.ActorAlign.CENTER,
});
temp.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
time.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
layout.attach(icon, col, 0, 1, 1);
layout.attach(temp, col, 1, 1, 1);
@ -313,7 +391,12 @@ var WeatherSection = class WeatherSection {
}
let info = this._weatherClient.info;
this._titleLocation.text = info.get_location().get_name();
let loc = info.get_location();
if (loc.get_level() !== GWeather.LocationLevel.CITY && loc.has_coords()) {
let world = GWeather.Location.get_world();
loc = world.find_nearest_city(...loc.get_coords());
}
this._titleLocation.text = loc.get_name();
if (this._weatherClient.loading) {
this._setStatusLabel(_("Loading…"));
@ -332,23 +415,27 @@ var WeatherSection = class WeatherSection {
}
_sync() {
this.actor.visible = this._weatherClient.available;
this.visible = this._weatherClient.available;
if (!this.actor.visible)
if (!this.visible)
return;
this._titleLocation.visible = this._weatherClient.hasLocation;
this._updateForecasts();
}
};
});
var MessagesIndicator = class MessagesIndicator {
constructor() {
this.actor = new St.Icon({ icon_name: 'message-indicator-symbolic',
icon_size: 16,
visible: false, y_expand: true,
y_align: Clutter.ActorAlign.CENTER });
var MessagesIndicator = GObject.registerClass(
class MessagesIndicator extends St.Icon {
_init() {
super._init({
icon_name: 'message-indicator-symbolic',
icon_size: 16,
visible: false,
y_expand: true,
y_align: Clutter.ActorAlign.CENTER,
});
this._sources = [];
@ -361,7 +448,7 @@ var MessagesIndicator = class MessagesIndicator {
}
_onSourceAdded(tray, source) {
source.connect('count-updated', this._updateCount.bind(this));
source.connect('notify::count', this._updateCount.bind(this));
this._sources.push(source);
this._updateCount();
}
@ -373,12 +460,12 @@ var MessagesIndicator = class MessagesIndicator {
_updateCount() {
let count = 0;
this._sources.forEach(source => count += source.unseenCount);
this._sources.forEach(source => (count += source.unseenCount));
count -= Main.messageTray.queueCount;
this.actor.visible = (count > 0);
this.visible = count > 0;
}
};
});
var IndicatorPad = GObject.registerClass(
class IndicatorPad extends St.Widget {
@ -471,13 +558,13 @@ class DateMenuButton extends PanelMenu.Button {
this._indicator = new MessagesIndicator();
let box = new St.BoxLayout();
box.add_actor(new IndicatorPad(this._indicator.actor));
box.add_actor(new IndicatorPad(this._indicator));
box.add_actor(this._clockDisplay);
box.add_actor(this._indicator.actor);
box.add_actor(this._indicator);
this.label_actor = this._clockDisplay;
this.add_actor(box);
this.add_style_class_name ('clock-display');
this.add_style_class_name('clock-display');
let layout = new FreezableBinLayout();
let bin = new St.Widget({ layout_manager: layout });
@ -489,11 +576,11 @@ class DateMenuButton extends PanelMenu.Button {
bin.add_actor(hbox);
this._calendar = new Calendar.Calendar();
this._calendar.connect('selected-date-changed',
(calendar, date) => {
layout.frozen = !_isToday(date);
this._messageList.setDate(date);
});
this._calendar.connect('selected-date-changed', (_calendar, datetime) => {
let date = _gDateTimeToDate(datetime);
layout.frozen = !_isToday(date);
this._messageList.setDate(date);
});
this.menu.connect('open-state-changed', (menu, isOpen) => {
// Whenever the menu is opened, select today
@ -507,35 +594,36 @@ class DateMenuButton extends PanelMenu.Button {
// Fill up the first column
this._messageList = new Calendar.CalendarMessageList();
hbox.add(this._messageList.actor, { expand: true, y_fill: false, y_align: St.Align.START });
hbox.add_child(this._messageList);
// Fill up the second column
let boxLayout = new CalendarColumnLayout(this._calendar.actor);
let boxLayout = new CalendarColumnLayout(this._calendar);
vbox = new St.Widget({ style_class: 'datemenu-calendar-column',
layout_manager: boxLayout });
boxLayout.hookup_style(vbox);
hbox.add(vbox);
this._date = new TodayButton(this._calendar);
vbox.add_actor(this._date.actor);
vbox.add_actor(this._date);
vbox.add_actor(this._calendar.actor);
vbox.add_actor(this._calendar);
this._displaysSection = new St.ScrollView({ style_class: 'datemenu-displays-section vfade',
x_expand: true, x_fill: true,
x_expand: true,
overlay_scrollbars: true });
this._displaysSection.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
vbox.add_actor(this._displaysSection);
let displaysBox = new St.BoxLayout({ vertical: true,
x_expand: true,
style_class: 'datemenu-displays-box' });
this._displaysSection.add_actor(displaysBox);
this._clocksItem = new WorldClocksSection();
displaysBox.add(this._clocksItem.actor, { x_fill: true });
displaysBox.add_child(this._clocksItem);
this._weatherItem = new WeatherSection();
displaysBox.add(this._weatherItem.actor, { x_fill: true });
displaysBox.add_child(this._weatherItem);
// Done with hbox for calendar and event list
@ -573,11 +661,11 @@ class DateMenuButton extends PanelMenu.Button {
_sessionUpdated() {
let eventSource;
let showEvents = Main.sessionMode.showCalendarEvents;
if (showEvents) {
if (showEvents)
eventSource = this._getEventSource();
} else {
else
eventSource = new Calendar.EmptyEventSource();
}
this._setEventSource(eventSource);
// Displays are not actually expected to launch Settings when activated

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Dialog, MessageDialogContent */
const { Clutter, Gio, GObject, Pango, St } = imports.gi;
@ -35,19 +36,15 @@ class Dialog extends St.Widget {
this._dialog.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
this._dialog.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
this.contentLayout = new St.BoxLayout({ vertical: true,
style_class: "modal-dialog-content-box" });
this._dialog.add(this.contentLayout,
{ expand: true,
x_fill: true,
y_fill: true,
x_align: St.Align.MIDDLE,
y_align: St.Align.START });
this.contentLayout = new St.BoxLayout({
vertical: true,
style_class: 'modal-dialog-content-box',
y_expand: true,
});
this._dialog.add_child(this.contentLayout);
this.buttonLayout = new St.Widget ({ layout_manager: new Clutter.BoxLayout({ homogeneous: true }) });
this._dialog.add(this.buttonLayout,
{ x_align: St.Align.MIDDLE,
y_align: St.Align.START });
this.buttonLayout = new St.Widget({ layout_manager: new Clutter.BoxLayout({ homogeneous: true }) });
this._dialog.add_child(this.buttonLayout);
}
_onDestroy() {
@ -99,7 +96,7 @@ class Dialog extends St.Widget {
}
addContent(actor) {
this.contentLayout.add (actor, { expand: true });
this.contentLayout.add(actor, { expand: true });
}
addButton(buttonInfo) {
@ -120,7 +117,7 @@ class Dialog extends St.Widget {
can_focus: true,
x_expand: true,
y_expand: true,
label: label });
label });
button.connect('clicked', action);
buttonInfo['button'] = button;
@ -162,8 +159,8 @@ var MessageDialogContent = GObject.registerClass({
'body': GObject.ParamSpec.string('body', 'body', 'body',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT,
null)
}
null),
},
}, class MessageDialogContent extends St.BoxLayout {
_init(params) {
this._icon = new St.Icon({ y_align: Clutter.ActorAlign.START });
@ -180,10 +177,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,
@ -216,7 +211,7 @@ var MessageDialogContent = GObject.registerClass({
set icon(icon) {
this._icon.set({
gicon: icon,
visible: icon != null
visible: icon != null,
});
this.notify('icon');
}
@ -236,7 +231,7 @@ var MessageDialogContent = GObject.registerClass({
_setLabel(label, prop, value) {
label.set({
text: value || '',
visible: value != null
visible: value != null,
});
this.notify(prop);
}

View File

@ -1,24 +1,24 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported addDragMonitor, removeDragMonitor, makeDraggable */
const { Clutter, GLib, Meta, Shell, St } = imports.gi;
const Signals = imports.signals;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
// Time to scale down to maxDragActorSize
var SCALE_ANIMATION_TIME = 0.25;
var SCALE_ANIMATION_TIME = 250;
// Time to animate to original position on cancel
var SNAP_BACK_ANIMATION_TIME = 0.25;
var SNAP_BACK_ANIMATION_TIME = 250;
// Time to animate to original position on success
var REVERT_ANIMATION_TIME = 0.75;
var REVERT_ANIMATION_TIME = 750;
var DragMotionResult = {
NO_DROP: 0,
COPY_DROP: 1,
MOVE_DROP: 2,
CONTINUE: 3
CONTINUE: 3,
};
var DragState = {
@ -30,13 +30,13 @@ var DragState = {
var DRAG_CURSOR_MAP = {
0: Meta.Cursor.DND_UNSUPPORTED_TARGET,
1: Meta.Cursor.DND_COPY,
2: Meta.Cursor.DND_MOVE
2: Meta.Cursor.DND_MOVE,
};
var DragDropResult = {
FAILURE: 0,
SUCCESS: 1,
CONTINUE: 2
CONTINUE: 2,
};
var dragMonitors = [];
@ -61,11 +61,12 @@ function addDragMonitor(monitor) {
}
function removeDragMonitor(monitor) {
for (let i = 0; i < dragMonitors.length; i++)
for (let i = 0; i < dragMonitors.length; i++) {
if (dragMonitors[i] == monitor) {
dragMonitors.splice(i, 1);
return;
}
}
}
var _Draggable = class _Draggable {
@ -111,9 +112,6 @@ var _Draggable = class _Draggable {
if (event.get_button() != 1)
return Clutter.EVENT_PROPAGATE;
if (Tweener.getTweenCount(actor))
return Clutter.EVENT_PROPAGATE;
this._buttonDown = true;
this._grabActor(event.get_device());
@ -139,9 +137,6 @@ var _Draggable = class _Draggable {
!global.display.is_pointer_emulating_sequence(event.get_event_sequence()))
return Clutter.EVENT_PROPAGATE;
if (Tweener.getTweenCount(actor))
return Clutter.EVENT_PROPAGATE;
this._buttonDown = true;
this._grabActor(event.get_device(), event.get_event_sequence());
@ -156,12 +151,12 @@ var _Draggable = class _Draggable {
if (touchSequence)
pointer.sequence_grab(touchSequence, actor);
else if (pointer)
pointer.grab (actor);
pointer.grab(actor);
this._grabbedDevice = pointer;
this._touchSequence = touchSequence;
this._capturedEventId = global.stage.connect('captured-event', (actor, event) => {
this._capturedEventId = global.stage.connect('captured-event', (o, event) => {
let device = event.get_device();
if (device != this._grabbedDevice &&
device.get_device_type() != Clutter.InputDeviceType.KEYBOARD_DEVICE)
@ -177,7 +172,7 @@ var _Draggable = class _Draggable {
}
if (this._touchSequence)
this._grabbedDevice.sequence_ungrab (this._touchSequence);
this._grabbedDevice.sequence_ungrab(this._touchSequence);
else
this._grabbedDevice.ungrab();
@ -218,9 +213,9 @@ var _Draggable = class _Draggable {
_eventIsRelease(event) {
if (event.type() == Clutter.EventType.BUTTON_RELEASE) {
let buttonMask = (Clutter.ModifierType.BUTTON1_MASK |
let buttonMask = Clutter.ModifierType.BUTTON1_MASK |
Clutter.ModifierType.BUTTON2_MASK |
Clutter.ModifierType.BUTTON3_MASK);
Clutter.ModifierType.BUTTON3_MASK;
/* We only obey the last button release from the device,
* other buttons may get pressed/released during the DnD op.
*/
@ -264,16 +259,16 @@ var _Draggable = class _Draggable {
} else if (event.type() == Clutter.EventType.MOTION ||
(event.type() == Clutter.EventType.TOUCH_UPDATE &&
global.display.is_pointer_emulating_sequence(event.get_event_sequence()))) {
if (this._dragActor && this._dragState == DragState.DRAGGING) {
if (this._dragActor && this._dragState == DragState.DRAGGING)
return this._updateDragPosition(event);
} else if (this._dragActor == null && this._dragState != DragState.CANCELLED) {
else if (this._dragActor == null && this._dragState != DragState.CANCELLED)
return this._maybeStartDrag(event);
}
// We intercept KEY_PRESS event so that we can process Esc key press to cancel
// dragging and ignore all other key presses.
} else if (event.type() == Clutter.EventType.KEY_PRESS && this._dragState == DragState.DRAGGING) {
let symbol = event.get_key_symbol();
if (symbol == Clutter.Escape) {
if (symbol == Clutter.KEY_Escape) {
this._cancelDrag(event.get_time());
return Clutter.EVENT_STOP;
}
@ -297,9 +292,11 @@ var _Draggable = class _Draggable {
/**
* startDrag:
* @stageX: X coordinate of event
* @stageY: Y coordinate of event
* @time: Event timestamp
* @param {number} stageX: X coordinate of event
* @param {number} stageY: Y coordinate of event
* @param {number} time: Event timestamp
* @param {Clutter.EventSequence=} sequence: Event sequence
* @param {Clutter.InputDevice=} device: device that originated the event
*
* Directly initiate a drag and drop operation from the given actor.
* This function is useful to call if you've specified manualMode
@ -344,7 +341,7 @@ var _Draggable = class _Draggable {
if (this.actor._delegate && this.actor._delegate.getDragActor) {
this._dragActor = this.actor._delegate.getDragActor();
Main.uiGroup.add_child(this._dragActor);
this._dragActor.raise_top();
Main.uiGroup.set_child_above_sibling(this._dragActor, null);
Shell.util_set_hidden_from_pick(this._dragActor, true);
// Drag actor does not always have to be the same as actor. For example drag actor
@ -394,7 +391,7 @@ var _Draggable = class _Draggable {
this._dragOrigParent.remove_actor(this._dragActor);
Main.uiGroup.add_child(this._dragActor);
this._dragActor.raise_top();
Main.uiGroup.set_child_above_sibling(this._dragActor, null);
Shell.util_set_hidden_from_pick(this._dragActor, true);
}
@ -428,19 +425,22 @@ var _Draggable = class _Draggable {
// to the final position because that tween would
// fight with updates as the user continues dragging
// the mouse; instead we do the position computations in
// an onUpdate() function.
Tweener.addTween(this._dragActor,
{ scale_x: scale * origScale,
scale_y: scale * origScale,
time: SCALE_ANIMATION_TIME,
transition: 'easeOutQuad',
onUpdate: () => {
let currentScale = this._dragActor.scale_x / origScale;
this._dragOffsetX = currentScale * origDragOffsetX;
this._dragOffsetY = currentScale * origDragOffsetY;
this._dragActor.set_position(this._dragX + this._dragOffsetX,
this._dragY + this._dragOffsetY);
} });
// a ::new-frame handler.
this._dragActor.ease({
scale_x: scale * origScale,
scale_y: scale * origScale,
duration: SCALE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
this._dragActor.get_transition('scale-x').connect('new-frame', () => {
let currentScale = this._dragActor.scale_x / origScale;
this._dragOffsetX = currentScale * origDragOffsetX;
this._dragOffsetY = currentScale * origDragOffsetY;
this._dragActor.set_position(
this._dragX + this._dragOffsetX,
this._dragY + this._dragOffsetY);
});
}
}
}
@ -475,7 +475,7 @@ var _Draggable = class _Draggable {
y: this._dragY,
dragActor: this._dragActor,
source: this.actor._delegate,
targetActor: target
targetActor: target,
};
let targetActorDestroyHandlerId;
@ -503,7 +503,7 @@ var _Draggable = class _Draggable {
while (target) {
if (target._delegate && target._delegate.handleDragOver) {
let [r, targX, targY] = target.transform_stage_point(this._dragX, this._dragY);
let [r_, targX, targY] = target.transform_stage_point(this._dragX, this._dragY);
// We currently loop through all parents on drag-over even if one of the children has handled it.
// We can check the return value of the function and break the loop if it's true if we don't want
// to continue checking the parents.
@ -554,11 +554,11 @@ var _Draggable = class _Draggable {
let dropEvent = {
dropActor: this._dragActor,
targetActor: target,
clutterEvent: event
clutterEvent: event,
};
for (let i = 0; i < dragMonitors.length; i++) {
let dropFunc = dragMonitors[i].dragDrop;
if (dropFunc)
if (dropFunc) {
switch (dropFunc(dropEvent)) {
case DragDropResult.FAILURE:
case DragDropResult.SUCCESS:
@ -566,6 +566,7 @@ var _Draggable = class _Draggable {
case DragDropResult.CONTINUE:
continue;
}
}
}
// At this point it is too late to cancel a drag by destroying
@ -575,12 +576,16 @@ var _Draggable = class _Draggable {
while (target) {
if (target._delegate && target._delegate.acceptDrop) {
let [r, targX, targY] = target.transform_stage_point(dropX, dropY);
if (target._delegate.acceptDrop(this.actor._delegate,
this._dragActor,
targX,
targY,
event.get_time())) {
let [r_, targX, targY] = target.transform_stage_point(dropX, dropY);
let accepted = false;
try {
accepted = target._delegate.acceptDrop(this.actor._delegate,
this._dragActor, targX, targY, event.get_time());
} catch (e) {
// On error, skip this target
logError(e, "Skipping drag target");
}
if (accepted) {
// If it accepted the drop without taking the actor,
// handle it ourselves.
if (this._dragActor && this._dragActor.get_parent() == Main.uiGroup) {
@ -613,15 +618,15 @@ var _Draggable = class _Draggable {
if (this._dragActorSource && this._dragActorSource.visible) {
// Snap the clone back to its source
[x, y] = this._dragActorSource.get_transformed_position();
let [sourceScaledWidth, sourceScaledHeight] = this._dragActorSource.get_transformed_size();
scale = sourceScaledWidth ? this._dragActor.width / sourceScaledWidth : 0;
let [sourceScaledWidth] = this._dragActorSource.get_transformed_size();
scale = sourceScaledWidth ? sourceScaledWidth / this._dragActor.width : 0;
} else if (this._dragOrigParent) {
// Snap the actor back to its original position within
// its parent, adjusting for the fact that the parent
// may have been moved or scaled
let [parentX, parentY] = this._dragOrigParent.get_transformed_position();
let [parentWidth, parentHeight] = this._dragOrigParent.get_size();
let [parentScaledWidth, parentScaledHeight] = this._dragOrigParent.get_transformed_size();
let [parentWidth] = this._dragOrigParent.get_size();
let [parentScaledWidth] = this._dragOrigParent.get_transformed_size();
let parentScale = 1.0;
if (parentWidth != 0)
parentScale = parentScaledWidth / parentWidth;
@ -641,7 +646,7 @@ var _Draggable = class _Draggable {
_cancelDrag(eventTime) {
this.emit('drag-cancelled', eventTime);
let wasCancelled = (this._dragState == DragState.CANCELLED);
let wasCancelled = this._dragState == DragState.CANCELLED;
this._dragState = DragState.CANCELLED;
if (this._actorDestroyed || wasCancelled) {
@ -657,13 +662,13 @@ var _Draggable = class _Draggable {
let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();
this._animateDragEnd(eventTime,
{ x: snapBackX,
y: snapBackY,
scale_x: snapBackScale,
scale_y: snapBackScale,
time: SNAP_BACK_ANIMATION_TIME,
});
this._animateDragEnd(eventTime, {
x: snapBackX,
y: snapBackY,
scale_x: snapBackScale,
scale_y: snapBackScale,
duration: SNAP_BACK_ANIMATION_TIME,
});
}
_restoreDragActor(eventTime) {
@ -675,21 +680,22 @@ var _Draggable = class _Draggable {
this._dragActor.set_scale(restoreScale, restoreScale);
this._dragActor.opacity = 0;
this._animateDragEnd(eventTime,
{ time: REVERT_ANIMATION_TIME });
this._animateDragEnd(eventTime, {
duration: REVERT_ANIMATION_TIME,
});
}
_animateDragEnd(eventTime, params) {
this._animationInProgress = true;
params['opacity'] = this._dragOrigOpacity;
params['transition'] = 'easeOutQuad';
params['onComplete'] = this._onAnimationComplete;
params['onCompleteScope'] = this;
params['onCompleteParams'] = [this._dragActor, eventTime];
// start the animation
Tweener.addTween(this._dragActor, params);
this._dragActor.ease(Object.assign(params, {
opacity: this._dragOrigOpacity,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this._onAnimationComplete(this._dragActor, eventTime);
},
}));
}
_finishAnimation() {
@ -742,8 +748,9 @@ Signals.addSignalMethods(_Draggable.prototype);
/**
* makeDraggable:
* @actor: Source actor
* @params: (optional) Additional parameters
* @param {Clutter.Actor} actor: Source actor
* @param {Object=} params: Additional parameters
* @returns {Object} a new Draggable
*
* Create an object which controls drag and drop for the given actor.
*

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported EdgeDragAction */
const { Clutter, GObject, Meta, St } = imports.gi;
@ -26,7 +27,7 @@ var EdgeDragAction = GObject.registerClass({
return global.display.get_monitor_geometry(monitorIndex);
}
vfunc_gesture_prepare(actor) {
vfunc_gesture_prepare(_actor) {
if (this.get_n_current_points() == 0)
return false;
@ -36,17 +37,17 @@ var EdgeDragAction = GObject.registerClass({
let [x, y] = this.get_press_coords(0);
let monitorRect = this._getMonitorRect(x, y);
return ((this._side == St.Side.LEFT && x < monitorRect.x + EDGE_THRESHOLD) ||
return (this._side == St.Side.LEFT && x < monitorRect.x + EDGE_THRESHOLD) ||
(this._side == St.Side.RIGHT && x > monitorRect.x + monitorRect.width - EDGE_THRESHOLD) ||
(this._side == St.Side.TOP && y < monitorRect.y + EDGE_THRESHOLD) ||
(this._side == St.Side.BOTTOM && y > monitorRect.y + monitorRect.height - EDGE_THRESHOLD));
(this._side == St.Side.BOTTOM && y > monitorRect.y + monitorRect.height - EDGE_THRESHOLD);
}
vfunc_gesture_progress(actor) {
vfunc_gesture_progress(_actor) {
let [startX, startY] = this.get_press_coords(0);
let [x, y] = this.get_motion_coords(0);
let offsetX = Math.abs (x - startX);
let offsetY = Math.abs (y - startY);
let offsetX = Math.abs(x - startX);
let offsetY = Math.abs(y - startY);
if (offsetX < EDGE_THRESHOLD && offsetY < EDGE_THRESHOLD)
return true;
@ -62,7 +63,7 @@ var EdgeDragAction = GObject.registerClass({
return true;
}
vfunc_gesture_end(actor) {
vfunc_gesture_end(_actor) {
let [startX, startY] = this.get_press_coords(0);
let [x, y] = this.get_motion_coords(0);
let monitorRect = this._getMonitorRect(startX, startY);

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported init, EndSessionDialog */
/*
* Copyright 2010-2016 Red Hat, Inc
*
@ -16,8 +17,6 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
const Mainloop = imports.mainloop;
const { AccountsService, Clutter, Gio,
GLib, GObject, Pango, Polkit, Shell, St } = imports.gi;
@ -129,7 +128,7 @@ const DialogType = {
SHUTDOWN: 1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */,
RESTART: 2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */,
UPDATE_RESTART: 3,
UPGRADE_RESTART: 4
UPGRADE_RESTART: 4,
};
const DialogContent = {
@ -137,7 +136,7 @@ const DialogContent = {
1 /* DialogType.SHUTDOWN */: shutdownDialogContent,
2 /* DialogType.RESTART */: restartDialogContent,
3 /* DialogType.UPDATE_RESTART */: restartUpdateDialogContent,
4 /* DialogType.UPGRADE_RESTART */: restartUpgradeDialogContent
4 /* DialogType.UPGRADE_RESTART */: restartUpgradeDialogContent,
};
var MAX_USERS_IN_SESSION_DIALOG = 5;
@ -208,10 +207,10 @@ function _setCheckBoxLabel(checkBox, text) {
if (text) {
label.set_text(text);
checkBox.actor.show();
checkBox.show();
} else {
label.set_text('');
checkBox.actor.hide();
checkBox.hide();
}
}
@ -219,7 +218,7 @@ function init() {
// This always returns the same singleton object
// By instantiating it initially, we register the
// bus object, etc.
(new EndSessionDialog());
new EndSessionDialog();
}
var EndSessionDialog = GObject.registerClass(
@ -264,42 +263,39 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
this._userLoadedId = this._user.connect('notify::is_loaded', this._sync.bind(this));
this._userChangedId = this._user.connect('changed', this._sync.bind(this));
let mainContentLayout = new St.BoxLayout({ vertical: false });
this.contentLayout.add(mainContentLayout,
{ x_fill: true,
y_fill: false });
let mainContentLayout = new St.BoxLayout({
vertical: false,
x_expand: true,
y_expand: false,
});
this.contentLayout.add_child(mainContentLayout);
this._iconBin = new St.Bin();
mainContentLayout.add(this._iconBin,
{ x_fill: true,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.START });
this._iconBin = new St.Bin({
x_expand: true,
x_align: Clutter.ActorAlign.END,
});
mainContentLayout.add_child(this._iconBin);
let messageLayout = new St.BoxLayout({ vertical: true,
style_class: 'end-session-dialog-layout' });
mainContentLayout.add(messageLayout,
{ y_align: St.Align.START });
mainContentLayout.add_child(messageLayout);
this._subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject' });
messageLayout.add(this._subjectLabel,
{ x_fill: false,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.START });
messageLayout.add_child(this._subjectLabel);
this._descriptionLabel = new St.Label({ style_class: 'end-session-dialog-description' });
this._descriptionLabel = new St.Label({
style_class: 'end-session-dialog-description',
y_expand: true,
});
this._descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._descriptionLabel.clutter_text.line_wrap = true;
messageLayout.add(this._descriptionLabel,
{ y_fill: true,
y_align: St.Align.START });
messageLayout.add_child(this._descriptionLabel);
this._checkBox = new CheckBox.CheckBox();
this._checkBox.actor.connect('clicked', this._sync.bind(this));
messageLayout.add(this._checkBox.actor);
this._checkBox.connect('clicked', this._sync.bind(this));
messageLayout.add(this._checkBox);
this._batteryWarning = new St.Label({ style_class: 'end-session-dialog-warning',
text: _("Running on battery power: please plug in before installing updates.") });
@ -307,11 +303,13 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
this._batteryWarning.clutter_text.line_wrap = true;
messageLayout.add(this._batteryWarning);
this._scrollView = new St.ScrollView({ style_class: 'end-session-dialog-list' });
this._scrollView = new St.ScrollView({
style_class: 'end-session-dialog-list',
x_expand: true,
y_expand: true,
});
this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
this.contentLayout.add(this._scrollView,
{ x_fill: true,
y_fill: true });
this.contentLayout.add_child(this._scrollView);
this._scrollView.hide();
this._inhibitorSection = new St.BoxLayout({ vertical: true,
@ -351,12 +349,15 @@ 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) {
log('No permission to trigger offline updates: %s'.format(e.toString()));
}
Polkit.Permission.new(
'org.freedesktop.packagekit.trigger-offline-update', null, null,
(source, res) => {
try {
this._updatesPermission = Polkit.Permission.new_finish(res);
} catch (e) {
log(`No permission to trigger offline updates: ${e}`);
}
});
}
_onDestroy() {
@ -365,7 +366,7 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
}
_sync() {
let open = (this.state == ModalDialog.State.OPENING || this.state == ModalDialog.State.OPENED);
let open = this.state == ModalDialog.State.OPENING || this.state == ModalDialog.State.OPENED;
if (!open)
return;
@ -374,12 +375,12 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
let subject = dialogContent.subject;
// Use different title when we are installing updates
if (dialogContent.subjectWithUpdates && this._checkBox.actor.checked)
if (dialogContent.subjectWithUpdates && this._checkBox.checked)
subject = dialogContent.subjectWithUpdates;
if (dialogContent.showBatteryWarning) {
// Warn when running on battery power
if (this._powerProxy.OnBattery && this._checkBox.actor.checked)
if (this._powerProxy.OnBattery && this._checkBox.checked)
this._batteryWarning.opacity = 255;
else
this._batteryWarning.opacity = 0;
@ -427,7 +428,7 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
let avatarWidget = new UserWidget.Avatar(this._user,
{ iconSize: _DIALOG_ICON_SIZE,
styleClass: dialogContent.iconStyleClass });
this._iconBin.child = avatarWidget.actor;
this._iconBin.child = avatarWidget;
avatarWidget.update();
}
@ -442,19 +443,21 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
let dialogContent = DialogContent[this._type];
let buttons = [{ action: this.cancel.bind(this),
label: _("Cancel"),
key: Clutter.Escape }];
key: Clutter.KEY_Escape }];
for (let i = 0; i < dialogContent.confirmButtons.length; i++) {
let signal = dialogContent.confirmButtons[i].signal;
let label = dialogContent.confirmButtons[i].label;
buttons.push({ action: () => {
this.close(true);
let signalId = this.connect('closed', () => {
this.disconnect(signalId);
this._confirm(signal);
});
},
label: label });
buttons.push({
action: () => {
this.close(true);
let signalId = this.connect('closed', () => {
this.disconnect(signalId);
this._confirm(signal);
});
},
label,
});
}
this.setButtons(buttons);
@ -481,13 +484,13 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
};
// Offline update not available; just emit the signal
if (!this._checkBox.actor.visible) {
if (!this._checkBox.visible) {
callback();
return;
}
// Trigger the offline update as requested
if (this._checkBox.actor.checked) {
if (this._checkBox.checked) {
switch (signal) {
case "ConfirmedReboot":
this._triggerOfflineUpdateReboot(callback);
@ -561,9 +564,9 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
let startTime = GLib.get_monotonic_time();
this._secondsLeft = this._totalSecondsToStayOpen;
this._timerId = Mainloop.timeout_add_seconds(1, () => {
this._timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
let currentTime = GLib.get_monotonic_time();
let secondsElapsed = ((currentTime - startTime) / 1000000);
let secondsElapsed = (currentTime - startTime) / 1000000;
this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed;
if (this._secondsLeft > 0) {
@ -583,7 +586,7 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
_stopTimer() {
if (this._timerId > 0) {
Mainloop.source_remove(this._timerId);
GLib.source_remove(this._timerId);
this._timerId = 0;
}
@ -652,7 +655,7 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
let actor = new St.BoxLayout({ style_class: 'end-session-dialog-session-list-item',
can_focus: true });
actor.add(avatar.actor);
actor.add(avatar);
let nameLabel = new St.Label({ text: userLabelText,
style_class: 'end-session-dialog-session-list-item-name',
@ -668,7 +671,7 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
this._loginManager.listSessions(result => {
let n = 0;
for (let i = 0; i < result.length; i++) {
let [id, uid, userName, seat, sessionPath] = result[i];
let [id_, uid_, userName, seat_, sessionPath] = result[i];
let proxy = new LogindSession(Gio.DBus.system, 'org.freedesktop.login1', sessionPath);
if (proxy.Class != 'user')
@ -678,11 +681,12 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
continue;
let sessionId = GLib.getenv('XDG_SESSION_ID');
if (!sessionId)
if (!sessionId) {
this._loginManager.getCurrentSessionProxy(currentSessionProxy => {
sessionId = currentSessionProxy.Id;
log(`endSessionDialog: No XDG_SESSION_ID, fetched from logind: ${sessionId}`);
});
}
if (proxy.Id == sessionId)
continue;
@ -734,7 +738,7 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
let dialogContent = DialogContent[this._type];
for (let i = 0; i < inhibitorObjectPaths.length; i++) {
let inhibitor = new GnomeSession.Inhibitor(inhibitorObjectPaths[i], (proxy, error) => {
let inhibitor = new GnomeSession.Inhibitor(inhibitorObjectPaths[i], proxy => {
this._onInhibitorLoaded(proxy);
});
@ -750,14 +754,14 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
let updatesAllowed = this._updatesPermission && this._updatesPermission.allowed;
_setCheckBoxLabel(this._checkBox, dialogContent.checkBoxText || '');
this._checkBox.actor.visible = (dialogContent.checkBoxText && updatePrepared && updatesAllowed);
this._checkBox.actor.checked = (updatePrepared && updateTriggered);
this._checkBox.visible = dialogContent.checkBoxText && updatePrepared && updatesAllowed;
this._checkBox.checked = updatePrepared && updateTriggered;
// We show the warning either together with the checkbox, or when
// updates have already been triggered, but the user doesn't have
// enough permissions to cancel them.
this._batteryWarning.visible = (dialogContent.showBatteryWarning &&
(this._checkBox.actor.visible || updatePrepared && updateTriggered && !updatesAllowed));
this._batteryWarning.visible = dialogContent.showBatteryWarning &&
(this._checkBox.visible || updatePrepared && updateTriggered && !updatesAllowed);
this._updateButtons();
@ -778,7 +782,7 @@ class EndSessionDialog extends ModalDialog.ModalDialog {
});
}
Close(parameters, invocation) {
Close(_parameters, _invocation) {
this.close();
}
});

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported init */
const Config = imports.misc.config;
@ -9,7 +10,7 @@ imports.gi.versions.Gtk = '3.0';
imports.gi.versions.TelepathyGLib = '0.12';
imports.gi.versions.TelepathyLogger = '0.2';
const { Clutter, GLib, Shell, St } = imports.gi;
const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi;
const Gettext = imports.gettext;
// We can't import shell JS modules yet, because they may have
@ -22,7 +23,7 @@ const Gettext = imports.gettext;
// of interfaces in Javascript
function _patchContainerClass(containerClass) {
// This one is a straightforward mapping of the C method
containerClass.prototype.child_set = function(actor, props) {
containerClass.prototype.child_set = function (actor, props) {
let meta = this.get_child_meta(actor);
for (let prop in props)
meta[prop] = props[prop];
@ -31,7 +32,7 @@ function _patchContainerClass(containerClass) {
// clutter_container_add() actually is a an add-many-actors
// method. We conveniently, but somewhat dubiously, take the
// this opportunity to make it do something more useful.
containerClass.prototype.add = function(actor, props) {
containerClass.prototype.add = function (actor, props) {
this.add_actor(actor);
if (props)
this.child_set(actor, props);
@ -39,8 +40,8 @@ function _patchContainerClass(containerClass) {
}
function _patchLayoutClass(layoutClass, styleProps) {
if (styleProps)
layoutClass.prototype.hookup_style = function(container) {
if (styleProps) {
layoutClass.prototype.hookup_style = function (container) {
container.connect('style-changed', () => {
let node = container.get_theme_node();
for (let prop in styleProps) {
@ -50,13 +51,157 @@ function _patchLayoutClass(layoutClass, styleProps) {
}
});
};
layoutClass.prototype.child_set = function(actor, props) {
let meta = this.get_child_meta(actor.get_parent(), actor);
for (let prop in props)
meta[prop] = props[prop];
}
}
function _makeEaseCallback(params, cleanup) {
let onComplete = params.onComplete;
delete params.onComplete;
let onStopped = params.onStopped;
delete params.onStopped;
return isFinished => {
cleanup();
if (onStopped)
onStopped(isFinished);
if (onComplete && isFinished)
onComplete();
};
}
function _getPropertyTarget(actor, propName) {
if (!propName.startsWith('@'))
return [actor, propName];
let [type, name, prop] = propName.split('.');
switch (type) {
case '@layout':
return [actor.layout_manager, name];
case '@actions':
return [actor.get_action(name), prop];
case '@constraints':
return [actor.get_constraint(name), prop];
case '@effects':
return [actor.get_effect(name), prop];
}
throw new Error(`Invalid property name ${propName}`);
}
function _easeActor(actor, params) {
actor.save_easing_state();
if (params.duration != undefined)
actor.set_easing_duration(params.duration);
delete params.duration;
if (params.delay != undefined)
actor.set_easing_delay(params.delay);
delete params.delay;
let repeatCount = 0;
if (params.repeatCount != undefined)
repeatCount = params.repeatCount;
delete params.repeatCount;
let autoReverse = false;
if (params.autoReverse != undefined)
autoReverse = params.autoReverse;
delete params.autoReverse;
if (params.mode != undefined)
actor.set_easing_mode(params.mode);
delete params.mode;
let cleanup = () => Meta.enable_unredirect_for_display(global.display);
let callback = _makeEaseCallback(params, cleanup);
// cancel overwritten transitions
let animatedProps = Object.keys(params).map(p => p.replace('_', '-', 'g'));
animatedProps.forEach(p => actor.remove_transition(p));
actor.set(params);
actor.restore_easing_state();
let transition = animatedProps.map(p => actor.get_transition(p))
.find(t => t !== null);
if (transition && transition.delay)
transition.connect('started', () => Meta.disable_unredirect_for_display(global.display));
else
Meta.disable_unredirect_for_display(global.display);
if (transition) {
transition.set({ repeatCount, autoReverse });
transition.connect('stopped', (t, finished) => callback(finished));
} else {
callback(true);
}
}
function _easeActorProperty(actor, propName, target, params) {
// Avoid pointless difference with ease()
if (params.mode)
params.progress_mode = params.mode;
delete params.mode;
if (params.duration)
params.duration = adjustAnimationTime(params.duration);
let duration = Math.floor(params.duration || 0);
let repeatCount = 0;
if (params.repeatCount != undefined)
repeatCount = params.repeatCount;
delete params.repeatCount;
let autoReverse = false;
if (params.autoReverse != undefined)
autoReverse = params.autoReverse;
delete params.autoReverse;
// Copy Clutter's behavior for implicit animations, see
// should_skip_implicit_transition()
if (actor instanceof Clutter.Actor && !actor.mapped)
duration = 0;
let cleanup = () => Meta.enable_unredirect_for_display(global.display);
let callback = _makeEaseCallback(params, cleanup);
// cancel overwritten transition
actor.remove_transition(propName);
if (duration == 0) {
let [obj, prop] = _getPropertyTarget(actor, propName);
obj[prop] = target;
Meta.disable_unredirect_for_display(global.display);
callback(true);
return;
}
let pspec = actor.find_property(propName);
let transition = new Clutter.PropertyTransition(Object.assign({
property_name: propName,
interval: new Clutter.Interval({ value_type: pspec.value_type }),
remove_on_complete: true,
repeat_count: repeatCount,
auto_reverse: autoReverse,
}, params));
actor.add_transition(propName, transition);
transition.set_to(target);
if (transition.delay)
transition.connect('started', () => Meta.disable_unredirect_for_display(global.display));
else
Meta.disable_unredirect_for_display(global.display);
transition.connect('stopped', (t, finished) => callback(finished));
}
function _loggingFunc(...args) {
let fields = { 'MESSAGE': args.join(', ') };
let domain = "GNOME Shell";
@ -84,16 +229,37 @@ function init() {
window.ngettext = Gettext.ngettext;
window.N_ = s => s;
GObject.gtypeNameBasedOnJSPath = true;
// Miscellaneous monkeypatching
_patchContainerClass(St.BoxLayout);
_patchLayoutClass(Clutter.TableLayout, { row_spacing: 'spacing-rows',
column_spacing: 'spacing-columns' });
_patchLayoutClass(Clutter.GridLayout, { row_spacing: 'spacing-rows',
column_spacing: 'spacing-columns' });
_patchLayoutClass(Clutter.BoxLayout, { spacing: 'spacing' });
Clutter.Actor.prototype.toString = function() {
let origSetEasingDuration = Clutter.Actor.prototype.set_easing_duration;
Clutter.Actor.prototype.set_easing_duration = function (msecs) {
origSetEasingDuration.call(this, adjustAnimationTime(msecs));
};
let origSetEasingDelay = Clutter.Actor.prototype.set_easing_delay;
Clutter.Actor.prototype.set_easing_delay = function (msecs) {
origSetEasingDelay.call(this, adjustAnimationTime(msecs));
};
Clutter.Actor.prototype.ease = function (props, easingParams) {
_easeActor(this, props, easingParams);
};
Clutter.Actor.prototype.ease_property = function (propName, target, params) {
_easeActorProperty(this, propName, target, params);
};
St.Adjustment.prototype.ease = function (target, params) {
// we're not an actor of course, but we implement the same
// transition API as Clutter.Actor, so this works anyway
_easeActorProperty(this, 'value', target, params);
};
Clutter.Actor.prototype.toString = function () {
return St.describe_actor(this);
};
// Deprecation warning for former JS classes turned into an actor subclass
@ -103,11 +269,17 @@ function init() {
let { stack } = new Error();
log(`Usage of object.actor is deprecated for ${klass}\n${stack}`);
return this;
}
},
});
St.set_slow_down_factor = function (factor) {
let { stack } = new Error();
log(`St.set_slow_down_factor() is deprecated, use St.Settings.slow_down_factor\n${stack}`);
St.Settings.get().slow_down_factor = factor;
};
let origToString = Object.prototype.toString;
Object.prototype.toString = function() {
Object.prototype.toString = function () {
let base = origToString.call(this);
try {
if ('actor' in this && this.actor instanceof Clutter.Actor)
@ -120,15 +292,16 @@ function init() {
};
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=508783
Date.prototype.toLocaleFormat = function(format) {
return Shell.util_format_date(format, this.getTime());
Date.prototype.toLocaleFormat = function (format) {
let dt = GLib.DateTime.new_from_unix_local(this.getTime() / 1000);
return dt ? dt.format(format) : '';
};
let slowdownEnv = GLib.getenv('GNOME_SHELL_SLOWDOWN_FACTOR');
if (slowdownEnv) {
let factor = parseFloat(slowdownEnv);
if (!isNaN(factor) && factor > 0.0)
St.set_slow_down_factor(factor);
St.Settings.get().slow_down_factor = factor;
}
// OK, now things are initialized enough that we can import shell JS
@ -138,3 +311,17 @@ function init() {
Tweener.init();
String.prototype.format = Format.format;
}
// adjustAnimationTime:
// @msecs: time in milliseconds
//
// Adjust @msecs to account for St's enable-animations
// and slow-down-factor settings
function adjustAnimationTime(msecs) {
let settings = St.Settings.get();
if (!settings.enable_animations)
return 1;
return settings.slow_down_factor * msecs;
}

View File

@ -1,12 +1,14 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported init, installExtension, uninstallExtension,
checkForUpdates, updateExtension */
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';
@ -17,14 +19,14 @@ var REPOSITORY_URL_UPDATE = `${REPOSITORY_URL_BASE}/update-info/`;
let _httpSession;
function installExtension(uuid, invocation) {
let params = { uuid: uuid,
let params = { uuid,
shell_version: Config.PACKAGE_VERSION };
let message = Soup.form_request_new_from_hash('GET', REPOSITORY_URL_INFO, params);
_httpSession.queue_message(message, (session, message) => {
_httpSession.queue_message(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 +35,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 +46,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 +54,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);
@ -88,7 +90,7 @@ function gotExtensionZipFile(session, message, uuid, dir, callback, errback) {
return;
}
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, (pid, status) => {
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, (o, status) => {
GLib.spawn_close_pid(pid);
if (status != 0)
@ -111,12 +113,12 @@ function updateExtension(uuid) {
let url = REPOSITORY_URL_DOWNLOAD.format(uuid);
let message = Soup.form_request_new_from_hash('GET', url, params);
_httpSession.queue_message(message, (session, message) => {
_httpSession.queue_message(message, session => {
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 +127,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,29 +140,29 @@ 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;
}
FileUtils.recursivelyDeleteDir(oldExtensionTmpDir, true);
}, (code, message) => {
log('Error while updating extension %s: %s (%s)'.format(uuid, code, message ? message : ''));
}, (code, msg) => {
log(`Error while updating extension ${uuid}: ${code} (${msg})`);
});
});
}
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) };
let url = REPOSITORY_URL_UPDATE;
let message = Soup.form_request_new_from_hash('GET', url, params);
_httpSession.queue_message(message, (session, message) => {
_httpSession.queue_message(message, () => {
if (message.status_code != Soup.KnownStatusCode.OK)
return;
@ -184,31 +186,32 @@ class InstallExtensionDialog extends ModalDialog.ModalDialog {
this._info = info;
this._invocation = invocation;
this.setButtons([{ label: _("Cancel"),
action: this._onCancelButtonPressed.bind(this),
key: Clutter.Escape
},
{ label: _("Install"),
action: this._onInstallButtonPressed.bind(this),
default: true
}]);
this.setButtons([{
label: _("Cancel"),
action: this._onCancelButtonPressed.bind(this),
key: Clutter.KEY_Escape,
}, {
label: _("Install"),
action: this._onInstallButtonPressed.bind(this),
default: true,
}]);
let content = new Dialog.MessageDialogContent({
title: _("Download and install “%s” from extensions.gnome.org?").format(info.name),
icon: new Gio.FileIcon({
file: Gio.File.new_for_uri(`${REPOSITORY_URL_BASE}${info.icon}`)
})
file: Gio.File.new_for_uri(`${REPOSITORY_URL_BASE}${info.icon}`),
}),
});
this.contentLayout.add(content);
}
_onCancelButtonPressed(button, event) {
_onCancelButtonPressed() {
this.close();
this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled']));
}
_onInstallButtonPressed(button, event) {
_onInstallButtonPressed() {
let params = { shell_version: Config.PACKAGE_VERSION };
let url = REPOSITORY_URL_DOWNLOAD.format(this._uuid);
@ -217,23 +220,17 @@ class InstallExtensionDialog extends ModalDialog.ModalDialog {
let uuid = this._uuid;
let dir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions', uuid]));
let invocation = this._invocation;
function errback(code, message) {
let msg = message ? message.toString() : '';
log('Error while installing %s: %s (%s)'.format(uuid, code, msg));
invocation.return_dbus_error(`org.gnome.Shell.${code}`, msg);
function errback(code, msg) {
log(`Error while installing ${uuid}: ${code} (${msg})`);
invocation.return_dbus_error(`org.gnome.Shell.${code}`, msg || '');
}
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);
@ -243,7 +240,7 @@ class InstallExtensionDialog extends ModalDialog.ModalDialog {
invocation.return_value(GLib.Variant.new('(s)', ['successful']));
}
_httpSession.queue_message(message, (session, message) => {
_httpSession.queue_message(message, session => {
gotExtensionZipFile(session, message, uuid, dir, callback, errback);
});

View File

@ -1,374 +1,533 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported init connect disconnect */
const { Gio, St } = imports.gi;
const { GLib, 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._initialized = 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.
// Example:
// order = [A, B, C, D, E]
// user disables C
// this should: disable E, disable D, disable C, enable D, enable E
let orderIdx = extensionOrder.indexOf(uuid);
let order = extensionOrder.slice(orderIdx + 1);
let orderReversed = order.slice().reverse();
for (let i = 0; i < orderReversed.length; i++) {
let uuid = orderReversed[i];
init() {
// The following file should exist for a period of time when extensions
// are enabled after start. If it exists, then the systemd unit will
// disable extensions should gnome-shell crash.
// Should the file already exist from a previous login, then this is OK.
let disableFilename = GLib.build_filenamev([GLib.get_user_runtime_dir(), 'gnome-shell-disable-extensions']);
let disableFile = Gio.File.new_for_path(disableFilename);
try {
ExtensionUtils.extensions[uuid].stateObj.disable();
disableFile.create(Gio.FileCreateFlags.REPLACE_DESTINATION, null);
} catch (e) {
logExtensionError(uuid, e);
log(`Failed to create file ${disableFilename}: ${e.message}`);
}
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 60, () => {
disableFile.delete(null);
return GLib.SOURCE_REMOVE;
});
this._sessionUpdated();
}
if (extension.stylesheet) {
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
theme.unload_stylesheet(extension.stylesheet);
delete extension.stylesheet;
lookup(uuid) {
return this._extensions.get(uuid);
}
try {
extension.stateObj.disable();
} catch (e) {
logExtensionError(uuid, e);
getUuids() {
return [...this._extensions.keys()];
}
for (let i = 0; i < order.length; i++) {
let uuid = order[i];
try {
ExtensionUtils.extensions[uuid].stateObj.enable();
} catch (e) {
logExtensionError(uuid, e);
}
}
extensionOrder.splice(orderIdx, 1);
if ( extension.state != ExtensionState.ERROR ) {
extension.state = ExtensionState.DISABLED;
_signals.emit('extension-state-changed', extension);
}
}
function enableExtension(uuid) {
let extension = ExtensionUtils.extensions[uuid];
if (!extension)
return;
if (extension.state == ExtensionState.INITIALIZED)
initExtension(uuid);
if (extension.state != ExtensionState.DISABLED)
return;
extensionOrder.push(uuid);
let stylesheetNames = [`${global.session_mode}.css`, 'stylesheet.css'];
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
for (let i = 0; i < stylesheetNames.length; i++) {
try {
let stylesheetFile = extension.dir.get_child(stylesheetNames[i]);
theme.load_stylesheet(stylesheetFile);
extension.stylesheet = stylesheetFile;
break;
} catch (e) {
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
continue; // not an error
log(`Failed to load stylesheet for extension ${uuid}: ${e.message}`);
_callExtensionDisable(uuid) {
let extension = this.lookup(uuid);
if (!extension)
return;
}
}
try {
extension.stateObj.enable();
extension.state = ExtensionState.ENABLED;
_signals.emit('extension-state-changed', extension);
return;
} catch (e) {
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 otherUuid = orderReversed[i];
try {
this.lookup(otherUuid).stateObj.disable();
} catch (e) {
this.logExtensionError(otherUuid, e);
}
}
if (extension.stylesheet) {
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
theme.unload_stylesheet(extension.stylesheet);
delete extension.stylesheet;
}
logExtensionError(uuid, e);
return;
}
}
function logExtensionError(uuid, error) {
let extension = ExtensionUtils.extensions[uuid];
if (!extension)
return;
try {
extension.stateObj.disable();
} catch (e) {
this.logExtensionError(uuid, e);
}
let message = `${error}`;
for (let i = 0; i < order.length; i++) {
let otherUuid = order[i];
try {
this.lookup(otherUuid).stateObj.enable();
} catch (e) {
this.logExtensionError(otherUuid, e);
}
}
extension.state = ExtensionState.ERROR;
if (!extension.errors)
extension.errors = [];
extension.errors.push(message);
this._extensionOrder.splice(orderIdx, 1);
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 (extension.state != ExtensionState.ERROR) {
extension.state = ExtensionState.DISABLED;
this.emit('extension-state-changed', extension);
}
}
_signals.emit('extension-state-changed', extension);
}
_callExtensionEnable(uuid) {
if (!Main.sessionMode.allowExtensions)
return;
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 extension = this.lookup(uuid);
if (!extension)
return;
extension.state = ExtensionState.UNINSTALLED;
_signals.emit('extension-state-changed', extension);
if (extension.state == ExtensionState.INITIALIZED)
this._callExtensionInit(uuid);
delete ExtensionUtils.extensions[extension.uuid];
return true;
}
if (extension.state != ExtensionState.DISABLED)
return;
function reloadExtension(oldExtension) {
// Grab the things we'll need to pass to createExtensionObject
// to reload it.
let { uuid: uuid, dir: dir, type: type } = oldExtension;
let stylesheetNames = [`${global.session_mode}.css`, 'stylesheet.css'];
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
for (let i = 0; i < stylesheetNames.length; i++) {
try {
let stylesheetFile = extension.dir.get_child(stylesheetNames[i]);
theme.load_stylesheet(stylesheetFile);
extension.stylesheet = stylesheetFile;
break;
} catch (e) {
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
continue; // not an error
this.logExtensionError(uuid, e);
return;
}
}
// 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;
}
loadExtension(newExtension);
}
function initExtension(uuid) {
let extension = ExtensionUtils.extensions[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)) {
logExtensionError(uuid, new Error('Missing extension.js'));
return false;
}
let extensionModule;
let extensionState = null;
ExtensionUtils.installImporter(extension);
try {
extensionModule = extension.imports.extension;
} catch (e) {
logExtensionError(uuid, e);
return false;
}
if (extensionModule.init) {
try {
extensionState = extensionModule.init(extension);
extension.stateObj.enable();
extension.state = ExtensionState.ENABLED;
this._extensionOrder.push(uuid);
this.emit('extension-state-changed', extension);
} catch (e) {
logExtensionError(uuid, e);
if (extension.stylesheet) {
theme.unload_stylesheet(extension.stylesheet);
delete extension.stylesheet;
}
this.logExtensionError(uuid, e);
}
}
enableExtension(uuid) {
if (!this._extensions.has(uuid))
return false;
let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
let disabledExtensions = global.settings.get_strv(DISABLED_EXTENSIONS_KEY);
if (disabledExtensions.includes(uuid)) {
disabledExtensions = disabledExtensions.filter(item => item !== uuid);
global.settings.set_strv(DISABLED_EXTENSIONS_KEY, disabledExtensions);
}
if (!enabledExtensions.includes(uuid)) {
enabledExtensions.push(uuid);
global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
}
return true;
}
disableExtension(uuid) {
if (!this._extensions.has(uuid))
return false;
let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
let disabledExtensions = global.settings.get_strv(DISABLED_EXTENSIONS_KEY);
if (enabledExtensions.includes(uuid)) {
enabledExtensions = enabledExtensions.filter(item => item !== uuid);
global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
}
if (!disabledExtensions.includes(uuid)) {
disabledExtensions.push(uuid);
global.settings.set_strv(DISABLED_EXTENSIONS_KEY, disabledExtensions);
}
return true;
}
logExtensionError(uuid, error) {
let extension = this.lookup(uuid);
if (!extension)
return;
let message = `${error}`;
extension.error = message;
extension.state = ExtensionState.ERROR;
if (!extension.errors)
extension.errors = [];
extension.errors.push(message);
logError(error, `Extension ${uuid}`);
this.emit('extension-state-changed', extension);
}
createExtensionObject(uuid, dir, type) {
let metadataFile = dir.get_child('metadata.json');
if (!metadataFile.query_exists(null))
throw new Error('Missing metadata.json');
let metadataContents, success_;
try {
[success_, metadataContents] = metadataFile.load_contents(null);
if (metadataContents instanceof Uint8Array)
metadataContents = imports.byteArray.toString(metadataContents);
} catch (e) {
throw new Error(`Failed to load metadata.json: ${e}`);
}
let meta;
try {
meta = JSON.parse(metadataContents);
} catch (e) {
throw new Error(`Failed to parse metadata.json: ${e}`);
}
let requiredProperties = ['uuid', 'name', 'description', 'shell-version'];
for (let i = 0; i < requiredProperties.length; i++) {
let prop = requiredProperties[i];
if (!meta[prop])
throw new Error(`missing "${prop}" property in metadata.json`);
}
if (uuid != meta.uuid)
throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`);
let extension = {
metadata: meta,
uuid: meta.uuid,
type,
dir,
path: dir.get_path(),
error: '',
hasPrefs: dir.get_child('prefs.js').query_exists(null),
canChange: false,
};
this._extensions.set(uuid, extension);
return extension;
}
loadExtension(extension) {
// Default to error, we set success as the last step
extension.state = ExtensionState.ERROR;
let checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY);
if (checkVersion && ExtensionUtils.isOutOfDate(extension)) {
extension.state = ExtensionState.OUT_OF_DATE;
} else {
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, dir, 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) {
if (!Main.sessionMode.allowExtensions)
return false;
let extension = this.lookup(uuid);
if (!extension)
throw new Error("Extension was not properly created. Call createExtensionObject first");
let dir = extension.dir;
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 (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._extensionOrder.filter(
uuid => !newEnabledExtensions.includes(uuid)
).reverse().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) {
_onVersionValidationChanged() {
// Disabling extensions modifies the order array, so use a copy
let extensionOrder = this._extensionOrder.slice();
// Disable enabled extensions in the reverse order first to avoid
// the "rebasing" done in _callExtensionDisable...
extensionOrder.slice().reverse().forEach(uuid => {
disableExtension(uuid);
this._callExtensionDisable(uuid);
});
// ...and then reload and enable extensions in the correct order again.
[...this._extensions.values()].sort((a, b) => {
return extensionOrder.indexOf(a.uuid) - extensionOrder.indexOf(b.uuid);
}).forEach(extension => this.reloadExtension(extension));
}
_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._initialized) {
this._loadExtensions();
this._initialized = 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._initialized) {
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) {
// Take care of added or removed sessionMode extensions
this._onEnabledExtensionsChanged();
this._enableAllExtensions();
} else {
this._disableAllExtensions();
}
}
};
Signals.addSignalMethods(ExtensionManager.prototype);

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported GrabHelper */
const { Clutter, St } = imports.gi;
@ -193,8 +194,17 @@ var GrabHelper = class GrabHelper {
return true;
}
grabAsync(params) {
return new Promise((resolve, reject) => {
params.onUngrab = resolve;
if (!this.grab(params))
reject(new Error('Grab failed'));
});
}
_takeModalGrab() {
let firstGrab = (this._modalCount == 0);
let firstGrab = this._modalCount == 0;
if (firstGrab) {
if (!Main.pushModal(this._owner, this._modalParams))
return false;
@ -291,7 +301,7 @@ var GrabHelper = class GrabHelper {
let touchEnd = type == Clutter.EventType.TOUCH_END;
let touch = touchUpdate || touchBegin || touchEnd;
if (touch && !global.display.is_pointer_emulating_sequence (event.get_event_sequence()))
if (touch && !global.display.is_pointer_emulating_sequence(event.get_event_sequence()))
return Clutter.EVENT_PROPAGATE;
if (this._ignoreUntilRelease && (motion || release || touch)) {

View File

@ -1,7 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported CandidatePopup */
const { Clutter, IBus, St } = imports.gi;
const Signals = imports.signals;
const { Clutter, GObject, IBus, St } = imports.gi;
const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
@ -11,11 +11,23 @@ var MAX_CANDIDATES_PER_PAGE = 16;
var DEFAULT_INDEX_LABELS = ['1', '2', '3', '4', '5', '6', '7', '8',
'9', '0', 'a', 'b', 'c', 'd', 'e', 'f'];
var CandidateArea = class CandidateArea {
constructor() {
this.actor = new St.BoxLayout({ vertical: true,
reactive: true,
visible: false });
var CandidateArea = GObject.registerClass({
Signals: {
'candidate-clicked': { param_types: [GObject.TYPE_UINT,
GObject.TYPE_UINT,
Clutter.ModifierType.$gtype] },
'cursor-down': {},
'cursor-up': {},
'next-page': {},
'previous-page': {},
},
}, class CandidateArea extends St.BoxLayout {
_init() {
super._init({
vertical: true,
reactive: true,
visible: false,
});
this._candidateBoxes = [];
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
let box = new St.BoxLayout({ style_class: 'candidate-box',
@ -23,10 +35,10 @@ var CandidateArea = class CandidateArea {
track_hover: true });
box._indexLabel = new St.Label({ style_class: 'candidate-index' });
box._candidateLabel = new St.Label({ style_class: 'candidate-label' });
box.add(box._indexLabel, { y_fill: false });
box.add(box._candidateLabel, { y_fill: false });
box.add_child(box._indexLabel);
box.add_child(box._candidateLabel);
this._candidateBoxes.push(box);
this.actor.add(box);
this.add(box);
let j = i;
box.connect('button-release-event', (actor, event) => {
@ -35,30 +47,23 @@ var CandidateArea = class CandidateArea {
});
}
this.actor.connect('scroll-event', (actor, event) => {
let direction = event.get_scroll_direction();
switch (direction) {
case Clutter.ScrollDirection.UP:
this.emit('cursor-up');
break;
case Clutter.ScrollDirection.DOWN:
this.emit('cursor-down');
break;
}
return Clutter.EVENT_PROPAGATE;
});
this._buttonBox = new St.BoxLayout({ style_class: 'candidate-page-button-box' });
this._previousButton = new St.Button({ style_class: 'candidate-page-button candidate-page-button-previous button' });
this._previousButton = new St.Button({
style_class: 'candidate-page-button candidate-page-button-previous button',
x_expand: true,
});
this._previousButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' });
this._buttonBox.add(this._previousButton, { expand: true });
this._buttonBox.add_child(this._previousButton);
this._nextButton = new St.Button({ style_class: 'candidate-page-button candidate-page-button-next button' });
this._nextButton = new St.Button({
style_class: 'candidate-page-button candidate-page-button-next button',
x_expand: true,
});
this._nextButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' });
this._buttonBox.add(this._nextButton, { expand: true });
this._buttonBox.add_child(this._nextButton);
this.actor.add(this._buttonBox);
this.add(this._buttonBox);
this._previousButton.connect('clicked', () => {
this.emit('previous-page');
@ -71,6 +76,18 @@ var CandidateArea = class CandidateArea {
this._cursorPosition = 0;
}
vfunc_scroll_event(scrollEvent) {
switch (scrollEvent.direction) {
case Clutter.ScrollDirection.UP:
this.emit('cursor-up');
break;
case Clutter.ScrollDirection.DOWN:
this.emit('cursor-down');
break;
}
return Clutter.EVENT_PROPAGATE;
}
setOrientation(orientation) {
if (this._orientation == orientation)
return;
@ -78,15 +95,15 @@ var CandidateArea = class CandidateArea {
this._orientation = orientation;
if (this._orientation == IBus.Orientation.HORIZONTAL) {
this.actor.vertical = false;
this.actor.remove_style_class_name('vertical');
this.actor.add_style_class_name('horizontal');
this.vertical = false;
this.remove_style_class_name('vertical');
this.add_style_class_name('horizontal');
this._previousButton.child.icon_name = 'go-previous-symbolic';
this._nextButton.child.icon_name = 'go-next-symbolic';
} else { // VERTICAL || SYSTEM
this.actor.vertical = true;
this.actor.add_style_class_name('vertical');
this.actor.remove_style_class_name('horizontal');
this.vertical = true;
this.add_style_class_name('vertical');
this.remove_style_class_name('horizontal');
this._previousButton.child.icon_name = 'go-up-symbolic';
this._nextButton.child.icon_name = 'go-down-symbolic';
}
@ -101,7 +118,7 @@ var CandidateArea = class CandidateArea {
if (!visible)
continue;
box._indexLabel.text = ((indexes && indexes[i]) ? indexes[i] : DEFAULT_INDEX_LABELS[i]);
box._indexLabel.text = indexes && indexes[i] ? indexes[i] : DEFAULT_INDEX_LABELS[i];
box._candidateLabel.text = candidates[i];
}
@ -120,19 +137,23 @@ var CandidateArea = class CandidateArea {
this._previousButton.reactive = wrapsAround || page > 0;
this._nextButton.reactive = wrapsAround || page < nPages - 1;
}
};
Signals.addSignalMethods(CandidateArea.prototype);
});
var CandidatePopup = class CandidatePopup {
constructor() {
this._boxPointer = new BoxPointer.BoxPointer(St.Side.TOP);
this._boxPointer.visible = false;
this._boxPointer.style_class = 'candidate-popup-boxpointer';
Main.layoutManager.addChrome(this._boxPointer);
var CandidatePopup = GObject.registerClass(
class IbusCandidatePopup extends BoxPointer.BoxPointer {
_init() {
super._init(St.Side.TOP);
this.visible = false;
this.style_class = 'candidate-popup-boxpointer';
this._dummyCursor = new St.Widget({ opacity: 0 });
Main.layoutManager.uiGroup.add_actor(this._dummyCursor);
Main.layoutManager.addChrome(this);
let box = new St.BoxLayout({ style_class: 'candidate-popup-content',
vertical: true });
this._boxPointer.bin.set_child(box);
this.bin.set_child(box);
this._preeditText = new St.Label({ style_class: 'candidate-popup-text',
visible: false });
@ -143,7 +164,7 @@ var CandidatePopup = class CandidatePopup {
box.add(this._auxText);
this._candidateArea = new CandidateArea();
box.add(this._candidateArea.actor);
box.add(this._candidateArea);
this._candidateArea.connect('previous-page', () => {
this._panelService.page_up();
@ -194,41 +215,42 @@ var CandidatePopup = class CandidatePopup {
this._preeditText.text = text.get_text();
let attrs = text.get_attributes();
if (attrs)
if (attrs) {
this._setTextAttributes(this._preeditText.clutter_text,
attrs);
}
});
panelService.connect('show-preedit-text', ps => {
panelService.connect('show-preedit-text', () => {
this._preeditText.show();
this._updateVisibility();
});
panelService.connect('hide-preedit-text', ps => {
panelService.connect('hide-preedit-text', () => {
this._preeditText.hide();
this._updateVisibility();
});
panelService.connect('update-auxiliary-text', (ps, text, visible) => {
panelService.connect('update-auxiliary-text', (_ps, text, visible) => {
this._auxText.visible = visible;
this._updateVisibility();
this._auxText.text = text.get_text();
});
panelService.connect('show-auxiliary-text', ps => {
panelService.connect('show-auxiliary-text', () => {
this._auxText.show();
this._updateVisibility();
});
panelService.connect('hide-auxiliary-text', ps => {
panelService.connect('hide-auxiliary-text', () => {
this._auxText.hide();
this._updateVisibility();
});
panelService.connect('update-lookup-table', (ps, lookupTable, visible) => {
this._candidateArea.actor.visible = visible;
panelService.connect('update-lookup-table', (_ps, lookupTable, visible) => {
this._candidateArea.visible = visible;
this._updateVisibility();
let nCandidates = lookupTable.get_number_of_candidates();
let cursorPos = lookupTable.get_cursor_pos();
let pageSize = lookupTable.get_page_size();
let nPages = Math.ceil(nCandidates / pageSize);
let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize));
let page = cursorPos == 0 ? 0 : Math.floor(cursorPos / pageSize);
let startIndex = page * pageSize;
let endIndex = Math.min((page + 1) * pageSize, nCandidates);
@ -256,45 +278,48 @@ var CandidatePopup = class CandidatePopup {
this._candidateArea.setOrientation(lookupTable.get_orientation());
this._candidateArea.updateButtons(lookupTable.is_round(), page, nPages);
});
panelService.connect('show-lookup-table', ps => {
this._candidateArea.actor.show();
panelService.connect('show-lookup-table', () => {
this._candidateArea.show();
this._updateVisibility();
});
panelService.connect('hide-lookup-table', ps => {
this._candidateArea.actor.hide();
panelService.connect('hide-lookup-table', () => {
this._candidateArea.hide();
this._updateVisibility();
});
panelService.connect('focus-out', ps => {
this._boxPointer.close(BoxPointer.PopupAnimation.NONE);
panelService.connect('focus-out', () => {
this.close(BoxPointer.PopupAnimation.NONE);
Main.keyboard.resetSuggestions();
});
}
_setDummyCursorGeometry(x, y, w, h) {
Main.layoutManager.setDummyCursorGeometry(x, y, w, h);
if (this._boxPointer.visible)
this._boxPointer.setPosition(Main.layoutManager.dummyCursor, 0);
this._dummyCursor.set_position(Math.round(x), Math.round(y));
this._dummyCursor.set_size(Math.round(w), Math.round(h));
if (this.visible)
this.setPosition(this._dummyCursor, 0);
}
_updateVisibility() {
let isVisible = (!Main.keyboard.visible &&
let isVisible = !Main.keyboard.visible &&
(this._preeditText.visible ||
this._auxText.visible ||
this._candidateArea.actor.visible));
this._candidateArea.visible);
if (isVisible) {
this._boxPointer.setPosition(Main.layoutManager.dummyCursor, 0);
this._boxPointer.open(BoxPointer.PopupAnimation.NONE);
this._boxPointer.raise_top();
this.setPosition(this._dummyCursor, 0);
this.open(BoxPointer.PopupAnimation.NONE);
this.get_parent().set_child_above_sibling(this, null);
} else {
this._boxPointer.close(BoxPointer.PopupAnimation.NONE);
this.close(BoxPointer.PopupAnimation.NONE);
}
}
_setTextAttributes(clutterText, ibusAttrList) {
let attr;
for (let i = 0; (attr = ibusAttrList.get(i)); ++i)
for (let i = 0; (attr = ibusAttrList.get(i)); ++i) {
if (attr.get_attr_type() == IBus.AttrType.BACKGROUND)
clutterText.set_selection(attr.get_start_index(), attr.get_end_index());
}
}
};
});

View File

@ -1,17 +1,17 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported BaseIcon, IconGrid, PaginatedIconGrid */
const { Clutter, GObject, Meta, St } = imports.gi;
const { Clutter, GLib, GObject, Graphene, Meta, St } = imports.gi;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
const Main = imports.ui.main;
var ICON_SIZE = 96;
var MIN_ICON_SIZE = 16;
var EXTRA_SPACE_ANIMATION_TIME = 0.25;
var EXTRA_SPACE_ANIMATION_TIME = 250;
var ANIMATION_TIME_IN = 0.350;
var ANIMATION_TIME_IN = 350;
var ANIMATION_TIME_OUT = 1 / 2 * ANIMATION_TIME_IN;
var ANIMATION_MAX_DELAY_FOR_ITEM = 2 / 3 * ANIMATION_TIME_IN;
var ANIMATION_BASE_DELAY_FOR_ITEM = 1 / 4 * ANIMATION_MAX_DELAY_FOR_ITEM;
@ -22,11 +22,11 @@ var ANIMATION_BOUNCE_ICON_SCALE = 1.1;
var AnimationDirection = {
IN: 0,
OUT: 1
OUT: 1,
};
var APPICON_ANIMATION_OUT_SCALE = 3;
var APPICON_ANIMATION_OUT_TIME = 0.25;
var APPICON_ANIMATION_OUT_TIME = 250;
var BaseIcon = GObject.registerClass(
class BaseIcon extends St.Bin {
@ -39,23 +39,28 @@ class BaseIcon extends St.Bin {
if (params.showLabel)
styleClass += ' overview-icon-with-label';
super._init({ style_class: styleClass,
x_fill: true,
y_fill: true });
super._init({ style_class: styleClass });
this.connect('destroy', this._onDestroy.bind(this));
this._box = new St.BoxLayout({ vertical: true });
this._box = new St.BoxLayout({
vertical: true,
x_expand: true,
y_expand: true,
});
this.set_child(this._box);
this.iconSize = ICON_SIZE;
this._iconBin = new St.Bin({ x_align: St.Align.MIDDLE,
y_align: St.Align.MIDDLE });
this._iconBin = new St.Bin({ x_align: Clutter.ActorAlign.CENTER });
this._box.add_actor(this._iconBin);
if (params.showLabel) {
this.label = new St.Label({ text: label });
this.label.clutter_text.set({
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
});
this._box.add_actor(this.label);
} else {
this.label = null;
@ -71,14 +76,14 @@ class BaseIcon extends St.Bin {
this._iconThemeChangedId = cache.connect('icon-theme-changed', this._onIconThemeChanged.bind(this));
}
vfunc_get_preferred_width(forHeight) {
vfunc_get_preferred_width(_forHeight) {
// Return the actual height to keep the squared aspect
return this.get_preferred_height(-1);
}
// This can be overridden by a subclass, or by the createIcon
// parameter to _init()
createIcon(size) {
createIcon(_size) {
throw new GObject.NotImplementedError(`createIcon in ${this.constructor.name}`);
}
@ -137,6 +142,14 @@ class BaseIcon extends St.Bin {
// animating.
zoomOutActor(this.child);
}
animateZoomOutAtPos(x, y) {
zoomOutActorAtPos(this.child, x, y);
}
update() {
this._createIconTexture(this.iconSize);
}
});
function clamp(value, min, max) {
@ -144,10 +157,15 @@ function clamp(value, min, max) {
}
function zoomOutActor(actor) {
let [x, y] = actor.get_transformed_position();
zoomOutActorAtPos(actor, x, y);
}
function zoomOutActorAtPos(actor, x, y) {
let actorClone = new Clutter.Clone({ source: actor,
reactive: false });
let [width, height] = actor.get_transformed_size();
let [x, y] = actor.get_transformed_position();
actorClone.set_size(width, height);
actorClone.set_position(x, y);
actorClone.opacity = 255;
@ -164,18 +182,16 @@ function zoomOutActor(actor) {
let containedX = clamp(scaledX, monitor.x, monitor.x + monitor.width - scaledWidth);
let containedY = clamp(scaledY, monitor.y, monitor.y + monitor.height - scaledHeight);
Tweener.addTween(actorClone,
{ time: APPICON_ANIMATION_OUT_TIME,
scale_x: APPICON_ANIMATION_OUT_SCALE,
scale_y: APPICON_ANIMATION_OUT_SCALE,
translation_x: containedX - scaledX,
translation_y: containedY - scaledY,
opacity: 0,
transition: 'easeOutQuad',
onComplete() {
actorClone.destroy();
}
});
actorClone.ease({
scale_x: APPICON_ANIMATION_OUT_SCALE,
scale_y: APPICON_ANIMATION_OUT_SCALE,
translation_x: containedX - scaledX,
translation_y: containedY - scaledY,
opacity: 0,
duration: APPICON_ANIMATION_OUT_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => actorClone.destroy(),
});
}
var IconGrid = GObject.registerClass({
@ -206,6 +222,8 @@ var IconGrid = GObject.registerClass({
this.rightPadding = 0;
this.leftPadding = 0;
this._updateIconSizesLaterId = 0;
this._items = [];
this._clonesAnimating = [];
// Pulled from CSS, but hardcode some defaults here
@ -214,15 +232,23 @@ var IconGrid = GObject.registerClass({
this._fixedHItemSize = this._fixedVItemSize = undefined;
this.connect('style-changed', this._onStyleChanged.bind(this));
// Cancel animations when hiding the overview, to avoid icons
// swarming into the void ...
this.connect('notify::mapped', () => {
if (!this.mapped)
this._cancelAnimation();
});
this.connect('actor-added', this._childAdded.bind(this));
this.connect('actor-removed', this._childRemoved.bind(this));
this.connect('destroy', this._onDestroy.bind(this));
}
vfunc_unmap() {
// Cancel animations when hiding the overview, to avoid icons
// swarming into the void ...
this._resetAnimationActors();
super.vfunc_unmap();
}
_onDestroy() {
if (this._updateIconSizesLaterId) {
Meta.later_remove(this._updateIconSizesLaterId);
this._updateIconSizesLaterId = 0;
}
}
_keyFocusIn(actor) {
@ -231,22 +257,35 @@ var IconGrid = GObject.registerClass({
_childAdded(grid, child) {
child._iconGridKeyFocusInId = child.connect('key-focus-in', this._keyFocusIn.bind(this));
child._paintVisible = child.opacity > 0;
child._opacityChangedId = child.connect('notify::opacity', () => {
let paintVisible = child._paintVisible;
child._paintVisible = child.opacity > 0;
if (paintVisible !== child._paintVisible)
this.queue_relayout();
});
}
_childRemoved(grid, child) {
child.disconnect(child._iconGridKeyFocusInId);
delete child._iconGridKeyFocusInId;
child.disconnect(child._opacityChangedId);
delete child._opacityChangedId;
delete child._paintVisible;
}
vfunc_get_preferred_width(forHeight) {
vfunc_get_preferred_width(_forHeight) {
if (this._fillParent)
// Ignore all size requests of children and request a size of 0;
// later we'll allocate as many children as fit the parent
return [0, 0];
let nChildren = this.get_n_children();
let nColumns = this._colLimit ? Math.min(this._colLimit,
nChildren)
: nChildren;
let nColumns = this._colLimit
? Math.min(this._colLimit, nChildren)
: nChildren;
let totalSpacing = Math.max(0, nColumns - 1) * this._getSpacing();
// Kind of a lie, but not really an issue right now. If
// we wanted to support some sort of hidden/overflow that would
@ -364,7 +403,7 @@ var IconGrid = GObject.registerClass({
let allocationBox = this.get_allocation_box();
let paintBox = themeNode.get_paint_box(allocationBox);
let origin = new Clutter.Vertex();
let origin = new Graphene.Point3D();
origin.x = paintBox.x1 - allocationBox.x1;
origin.y = paintBox.y1 - allocationBox.y1;
origin.z = 0.0;
@ -393,35 +432,35 @@ var IconGrid = GObject.registerClass({
return true;
}
/**
/*
* Intended to be override by subclasses if they need a different
* set of items to be animated.
*/
_getChildrenToAnimate() {
return this._getVisibleChildren();
return this._getVisibleChildren().filter(child => child.opacity > 0);
}
_cancelAnimation() {
this._clonesAnimating.forEach(clone => clone.destroy());
this._clonesAnimating = [];
}
_animationDone() {
_resetAnimationActors() {
this._clonesAnimating.forEach(clone => {
clone.source.reactive = true;
clone.source.opacity = 255;
clone.destroy();
});
this._clonesAnimating = [];
}
_animationDone() {
this._resetAnimationActors();
this.emit('animation-done');
}
animatePulse(animationDirection) {
if (animationDirection != AnimationDirection.IN)
if (animationDirection != AnimationDirection.IN) {
throw new GObject.NotImplementedError("Pulse animation only implements " +
"'in' animation direction");
}
this._cancelAnimation();
this._resetAnimationActors();
let actors = this._getChildrenToAnimate();
if (actors.length == 0) {
@ -444,30 +483,32 @@ var IconGrid = GObject.registerClass({
let delay = index / actors.length * maxDelay;
let bounceUpTime = ANIMATION_TIME_IN / 4;
let isLastItem = index == actors.length - 1;
Tweener.addTween(actor,
{ time: bounceUpTime,
transition: 'easeInOutQuad',
delay: delay,
scale_x: ANIMATION_BOUNCE_ICON_SCALE,
scale_y: ANIMATION_BOUNCE_ICON_SCALE,
onComplete: () => {
Tweener.addTween(actor,
{ time: ANIMATION_TIME_IN - bounceUpTime,
transition: 'easeInOutQuad',
scale_x: 1,
scale_y: 1,
onComplete: () => {
if (isLastItem)
this._animationDone();
}
});
}
});
actor.ease({
scale_x: ANIMATION_BOUNCE_ICON_SCALE,
scale_y: ANIMATION_BOUNCE_ICON_SCALE,
duration: bounceUpTime,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
delay,
onComplete: () => {
let duration = ANIMATION_TIME_IN - bounceUpTime;
actor.ease({
scale_x: 1,
scale_y: 1,
duration,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
onComplete: () => {
if (isLastItem)
this._animationDone();
actor.reactive = true;
},
});
},
});
}
}
animateSpring(animationDirection, sourceActor) {
this._cancelAnimation();
this._resetAnimationActors();
let actors = this._getChildrenToAnimate();
if (actors.length == 0) {
@ -521,21 +562,25 @@ var IconGrid = GObject.registerClass({
let delay = (1 - (actor._distance - minDist) / normalization) * ANIMATION_MAX_DELAY_FOR_ITEM;
let [finalX, finalY] = actor._transformedPosition;
movementParams = { time: ANIMATION_TIME_IN,
transition: 'easeInOutQuad',
delay: delay,
x: finalX,
y: finalY,
scale_x: 1,
scale_y: 1,
onComplete: () => {
if (isLastItem)
this._animationDone();
} };
fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
transition: 'easeInOutQuad',
delay: delay,
opacity: 255 };
movementParams = {
x: finalX,
y: finalY,
scale_x: 1,
scale_y: 1,
duration: ANIMATION_TIME_IN,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
delay,
};
if (isLastItem)
movementParams.onComplete = this._animationDone.bind(this);
fadeParams = {
opacity: 255,
duration: ANIMATION_FADE_IN_TIME_FOR_ITEM,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
delay,
};
} else {
let isLastItem = actor._distance == maxDist;
@ -543,26 +588,29 @@ var IconGrid = GObject.registerClass({
actorClone.set_position(startX, startY);
let delay = (actor._distance - minDist) / normalization * ANIMATION_MAX_DELAY_OUT_FOR_ITEM;
movementParams = { time: ANIMATION_TIME_OUT,
transition: 'easeInOutQuad',
delay: delay,
x: adjustedSourcePositionX,
y: adjustedSourcePositionY,
scale_x: scaleX,
scale_y: scaleY,
onComplete: () => {
if (isLastItem)
this._animationDone();
} };
fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
transition: 'easeInOutQuad',
delay: ANIMATION_TIME_OUT + delay - ANIMATION_FADE_IN_TIME_FOR_ITEM,
opacity: 0 };
movementParams = {
x: adjustedSourcePositionX,
y: adjustedSourcePositionY,
scale_x: scaleX,
scale_y: scaleY,
duration: ANIMATION_TIME_OUT,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
delay,
};
if (isLastItem)
movementParams.onComplete = this._animationDone.bind(this);
fadeParams = {
opacity: 0,
duration: ANIMATION_FADE_IN_TIME_FOR_ITEM,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
delay: ANIMATION_TIME_OUT + delay - ANIMATION_FADE_IN_TIME_FOR_ITEM,
};
}
Tweener.addTween(actorClone, movementParams);
Tweener.addTween(actorClone, fadeParams);
actorClone.ease(movementParams);
actorClone.ease(fadeParams);
}
}
@ -602,6 +650,8 @@ var IconGrid = GObject.registerClass({
}
_computeLayout(forWidth) {
this.ensure_style();
let nColumns = 0;
let usedWidth = this.leftPadding + this.rightPadding;
let spacing = this._getSpacing();
@ -628,8 +678,8 @@ var IconGrid = GObject.registerClass({
nRows(forWidth) {
let children = this._getVisibleChildren();
let nColumns = (forWidth < 0) ? children.length : this._computeLayout(forWidth)[0];
let nRows = (nColumns > 0) ? Math.ceil(children.length / nColumns) : 0;
let nColumns = forWidth < 0 ? children.length : this._computeLayout(forWidth)[0];
let nRows = nColumns > 0 ? Math.ceil(children.length / nColumns) : 0;
if (this._rowLimit)
nRows = Math.min(nRows, this._rowLimit);
return nRows;
@ -669,13 +719,13 @@ var IconGrid = GObject.registerClass({
this._items.push(item);
if (index !== undefined)
this.insert_child_at_index(item.actor, index);
this.insert_child_at_index(item, index);
else
this.add_actor(item.actor);
this.add_actor(item);
}
removeItem(item) {
this.remove_child(item.actor);
this.remove_child(item);
}
getItemAtIndex(index) {
@ -735,7 +785,7 @@ var IconGrid = GObject.registerClass({
this.topPadding = this.rightPadding = this.bottomPadding = this.leftPadding = spacing;
}
/**
/*
* This function must to be called before iconGrid allocation,
* to know how much spacing can the grid has
*/
@ -748,24 +798,29 @@ var IconGrid = GObject.registerClass({
let neededWidth = this.usedWidthForNColumns(this._minColumns) - availWidth;
let neededHeight = this.usedHeightForNRows(this._minRows) - availHeight;
let neededSpacePerItem = (neededWidth > neededHeight) ? Math.ceil(neededWidth / this._minColumns)
: Math.ceil(neededHeight / this._minRows);
let neededSpacePerItem = neededWidth > neededHeight
? Math.ceil(neededWidth / this._minColumns)
: Math.ceil(neededHeight / this._minRows);
this._fixedHItemSize = Math.max(this._hItemSize - neededSpacePerItem, MIN_ICON_SIZE);
this._fixedVItemSize = Math.max(this._vItemSize - neededSpacePerItem, MIN_ICON_SIZE);
this._updateSpacingForSize(availWidth, availHeight);
}
Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
this._updateIconSizes.bind(this));
if (!this._updateIconSizesLaterId) {
this._updateIconSizesLaterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
this._updateIconSizes.bind(this));
}
}
// Note that this is ICON_SIZE as used by BaseIcon, not elsewhere in IconGrid; it's a bit messed up
_updateIconSizes() {
this._updateIconSizesLaterId = 0;
let scale = Math.min(this._fixedHItemSize, this._fixedVItemSize) / Math.max(this._hItemSize, this._vItemSize);
let newIconSize = Math.floor(ICON_SIZE * scale);
for (let i in this._items) {
for (let i in this._items)
this._items[i].icon.setIconSize(newIconSize);
}
return GLib.SOURCE_REMOVE;
}
});
@ -782,7 +837,7 @@ var PaginatedIconGrid = GObject.registerClass({
this._childrenPerPage = 0;
}
vfunc_get_preferred_height(forWidth) {
vfunc_get_preferred_height(_forWidth) {
let height = (this._availableHeightPerPageForItems() + this.bottomPadding + this.topPadding) * this._nPages + this._spaceBetweenPages * this._nPages;
return [height, height];
}
@ -826,9 +881,9 @@ var PaginatedIconGrid = GObject.registerClass({
children[i].show();
columnIndex++;
if (columnIndex == nColumns) {
if (columnIndex == nColumns)
columnIndex = 0;
}
if (columnIndex == 0) {
y += this._getVItemSize() + spacing;
if ((i + 1) % this._childrenPerPage == 0)
@ -842,7 +897,7 @@ var PaginatedIconGrid = GObject.registerClass({
// Overridden from IconGrid
_getChildrenToAnimate() {
let children = this._getVisibleChildren();
let children = super._getChildrenToAnimate();
let firstIndex = this._childrenPerPage * this.currentPage;
let lastIndex = firstIndex + this._childrenPerPage;
@ -850,7 +905,7 @@ var PaginatedIconGrid = GObject.registerClass({
}
_computePages(availWidthPerPage, availHeightPerPage) {
let [nColumns, usedWidth] = this._computeLayout(availWidthPerPage);
let [nColumns, usedWidth_] = this._computeLayout(availWidthPerPage);
let nRows;
let children = this._getVisibleChildren();
if (nColumns > 0)
@ -903,15 +958,16 @@ var PaginatedIconGrid = GObject.registerClass({
/**
* openExtraSpace:
* @sourceItem: the item for which to create extra space
* @side: where @sourceItem should be located relative to the created space
* @nRows: the amount of space to create
* @param {Clutter.Actor} sourceItem: item for which to create extra space
* @param {St.Side} side: where @sourceItem should be located relative to
* the created space
* @param {number} nRows: the amount of space to create
*
* Pan view to create extra space for @nRows above or below @sourceItem.
*/
openExtraSpace(sourceItem, side, nRows) {
let children = this._getVisibleChildren();
let index = children.indexOf(sourceItem.actor);
let index = children.indexOf(sourceItem);
if (index == -1)
throw new Error('Item not found.');
@ -921,8 +977,7 @@ var PaginatedIconGrid = GObject.registerClass({
let childrenPerRow = this._childrenPerPage / this._rowsPerPage;
let sourceRow = Math.floor((index - pageOffset) / childrenPerRow);
let nRowsAbove = (side == St.Side.TOP) ? sourceRow + 1
: sourceRow;
let nRowsAbove = side == St.Side.TOP ? sourceRow + 1 : sourceRow;
let nRowsBelow = this._rowsPerPage - nRowsAbove;
let nRowsUp, nRowsDown;
@ -962,13 +1017,14 @@ var PaginatedIconGrid = GObject.registerClass({
for (let i = 0; i < children.length; i++) {
children[i].translation_y = 0;
let params = { translation_y: translationY,
time: EXTRA_SPACE_ANIMATION_TIME,
transition: 'easeInOutQuad'
};
let params = {
translation_y: translationY,
duration: EXTRA_SPACE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
};
if (i == (children.length - 1))
params.onComplete = () => this.emit('space-opened');
Tweener.addTween(children[i], params);
children[i].ease(params);
}
}
@ -981,12 +1037,12 @@ var PaginatedIconGrid = GObject.registerClass({
for (let i = 0; i < this._translatedChildren.length; i++) {
if (!this._translatedChildren[i].translation_y)
continue;
Tweener.addTween(this._translatedChildren[i],
{ translation_y: 0,
time: EXTRA_SPACE_ANIMATION_TIME,
transition: 'easeInOutQuad',
onComplete: () => this.emit('space-closed')
});
this._translatedChildren[i].ease({
translation_y: 0,
duration: EXTRA_SPACE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
onComplete: () => this.emit('space-closed'),
});
}
}
});

View File

@ -1,3 +1,4 @@
/* exported InhibitShortcutsDialog */
const { Clutter, Gio, GLib, GObject, Gtk, Meta, Shell } = imports.gi;
const Dialog = imports.ui.dialog;
@ -17,8 +18,8 @@ var DialogResponse = Meta.InhibitShortcutsDialogResponse;
var InhibitShortcutsDialog = GObject.registerClass({
Implements: [Meta.InhibitShortcutsDialog],
Properties: {
'window': GObject.ParamSpec.override('window', Meta.InhibitShortcutsDialog)
}
'window': GObject.ParamSpec.override('window', Meta.InhibitShortcutsDialog),
},
}, class InhibitShortcutsDialog extends GObject.Object {
_init(window) {
super._init();
@ -75,17 +76,19 @@ var InhibitShortcutsDialog = GObject.registerClass({
let name = this._app ? this._app.get_name() : this._window.title;
/* Translators: %s is an application name like "Settings" */
let title = name ? _("%s wants to inhibit shortcuts").format(name)
: _("Application wants to inhibit shortcuts");
let title = name
? _("%s wants to inhibit shortcuts").format(name)
: _("Application wants to inhibit shortcuts");
let icon = new Gio.ThemedIcon({ name: 'dialog-warning-symbolic' });
let contentParams = { icon, title };
let restoreAccel = this._getRestoreAccel();
if (restoreAccel)
if (restoreAccel) {
contentParams.subtitle =
/* Translators: %s is a keyboard shortcut like "Super+x" */
_("You can restore shortcuts by pressing %s.").format(restoreAccel);
}
let content = new Dialog.MessageDialogContent(contentParams);
this._dialog.contentLayout.add_actor(content);
@ -132,14 +135,14 @@ var InhibitShortcutsDialog = GObject.registerClass({
this._permStore.LookupRemote(APP_PERMISSIONS_TABLE,
APP_PERMISSIONS_ID,
(res, error) => {
if (error) {
(res, err) => {
if (err) {
this._dialog.open();
log(error.message);
log(err.message);
return;
}
let [permissions, data] = res;
let [permissions] = res;
if (permissions[appId] === undefined) // Not found
this._dialog.open();
else if (permissions[appId] == GRANTED)

View File

@ -1,3 +1,4 @@
/* exported KbdA11yDialog */
const { Clutter, Gio, GObject } = imports.gi;
const Dialog = imports.ui.dialog;
@ -26,24 +27,24 @@ class KbdA11yDialog extends GObject.Object {
if (whatChanged & Clutter.KeyboardA11yFlags.SLOW_KEYS_ENABLED) {
key = KEY_SLOW_KEYS_ENABLED;
enabled = (newFlags & Clutter.KeyboardA11yFlags.SLOW_KEYS_ENABLED) ? true : false;
title = enabled ?
_("Slow Keys Turned On") :
_("Slow Keys Turned Off");
enabled = (newFlags & Clutter.KeyboardA11yFlags.SLOW_KEYS_ENABLED) > 0;
title = enabled
? _("Slow Keys Turned On")
: _("Slow Keys Turned Off");
body = _("You just held down the Shift key for 8 seconds. This is the shortcut " +
"for the Slow Keys feature, which affects the way your keyboard works.");
} else if (whatChanged & Clutter.KeyboardA11yFlags.STICKY_KEYS_ENABLED) {
key = KEY_STICKY_KEYS_ENABLED;
enabled = (newFlags & Clutter.KeyboardA11yFlags.STICKY_KEYS_ENABLED) ? true : false;
title = enabled ?
_("Sticky Keys Turned On") :
_("Sticky Keys Turned Off");
body = enabled ?
_("You just pressed the Shift key 5 times in a row. This is the shortcut " +
"for the Sticky Keys feature, which affects the way your keyboard works.") :
_("You just pressed two keys at once, or pressed the Shift key 5 times in a row. " +
"This turns off the Sticky Keys feature, which affects the way your keyboard works.");
enabled = (newFlags & Clutter.KeyboardA11yFlags.STICKY_KEYS_ENABLED) > 0;
title = enabled
? _("Sticky Keys Turned On")
: _("Sticky Keys Turned Off");
body = enabled
? _("You just pressed the Shift key 5 times in a row. This is the shortcut " +
"for the Sticky Keys feature, which affects the way your keyboard works.")
: _("You just pressed two keys at once, or pressed the Shift key 5 times in a row. " +
"This turns off the Sticky Keys feature, which affects the way your keyboard works.");
} else {
return;
}
@ -60,7 +61,7 @@ class KbdA11yDialog extends GObject.Object {
dialog.close();
},
default: enabled,
key: !enabled ? Clutter.Escape : null });
key: !enabled ? Clutter.KEY_Escape : null });
dialog.addButton({ label: enabled ? _("Turn Off") : _("Leave Off"),
action: () => {
@ -68,7 +69,7 @@ class KbdA11yDialog extends GObject.Object {
dialog.close();
},
default: !enabled,
key: enabled ? Clutter.Escape : null });
key: enabled ? Clutter.KEY_Escape : null });
dialog.open();
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported MonitorConstraint, LayoutManager */
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;
@ -10,12 +11,11 @@ const LoginManager = imports.misc.loginManager;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
const Ripples = imports.ui.ripples;
var STARTUP_ANIMATION_TIME = 0.5;
var KEYBOARD_ANIMATION_TIME = 0.15;
var BACKGROUND_FADE_ANIMATION_TIME = 1.0;
var STARTUP_ANIMATION_TIME = 500;
var KEYBOARD_ANIMATION_TIME = 150;
var BACKGROUND_FADE_ANIMATION_TIME = 1000;
var HOT_CORNER_PRESSURE_THRESHOLD = 100; // pixels
var HOT_CORNER_PRESSURE_TIMEOUT = 1000; // ms
@ -44,7 +44,7 @@ var MonitorConstraint = GObject.registerClass({
'work-area': GObject.ParamSpec.boolean('work-area',
'Work-area', 'Track monitor\'s work-area',
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
false)
false),
},
}, class MonitorConstraint extends Clutter.Constraint {
_init(props) {
@ -80,10 +80,12 @@ var MonitorConstraint = GObject.registerClass({
this.notify('index');
}
// eslint-disable-next-line camelcase
get work_area() {
return this._workArea;
}
// eslint-disable-next-line camelcase
set work_area(v) {
if (v == this._workArea)
return;
@ -165,12 +167,12 @@ var Monitor = class Monitor {
const UiActor = GObject.registerClass(
class UiActor extends St.Widget {
vfunc_get_preferred_width (forHeight) {
vfunc_get_preferred_width(_forHeight) {
let width = global.stage.width;
return [width, width];
}
vfunc_get_preferred_height (forWidth) {
vfunc_get_preferred_height(_forWidth) {
let height = global.stage.height;
return [height, height];
}
@ -179,7 +181,7 @@ class UiActor extends St.Widget {
const defaultParams = {
trackFullscreen: false,
affectsStruts: false,
affectsInputRegion: true
affectsInputRegion: true,
};
var LayoutManager = GObject.registerClass({
@ -187,12 +189,13 @@ var LayoutManager = GObject.registerClass({
'startup-complete': {},
'startup-prepared': {},
'monitors-changed': {},
'system-modal-opened': {},
'keyboard-visible-changed': { param_types: [GObject.TYPE_BOOLEAN] } },
}, class LayoutManager extends GObject.Object {
_init() {
super._init();
this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL);
this._rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;
this.monitors = [];
this.primaryMonitor = null;
this.primaryIndex = -1;
@ -210,11 +213,6 @@ var LayoutManager = GObject.registerClass({
this._startingUp = true;
this._pendingLoadBackground = false;
// We don't want to paint the stage background color because either
// the SystemBackground we create or the MetaBackgroundActor inside
// global.window_group covers the entirety of the screen.
global.stage.no_clear_hint = true;
// Set up stage hierarchy to group all UI actors under one container.
this.uiGroup = new UiActor({ name: 'uiGroup' });
this.uiGroup.set_flags(Clutter.ActorFlags.NO_LAYOUT);
@ -236,11 +234,12 @@ var LayoutManager = GObject.registerClass({
reactive: true });
this.addChrome(this.overviewGroup);
this.screenShieldGroup = new St.Widget({ name: 'screenShieldGroup',
visible: false,
clip_to_allocation: true,
layout_manager: new Clutter.BinLayout(),
});
this.screenShieldGroup = new St.Widget({
name: 'screenShieldGroup',
visible: false,
clip_to_allocation: true,
layout_manager: new Clutter.BinLayout(),
});
this.addChrome(this.screenShieldGroup);
this.panelBox = new St.BoxLayout({ name: 'panelBox',
@ -271,9 +270,16 @@ var LayoutManager = GObject.registerClass({
this._backgroundGroup = new Meta.BackgroundGroup();
global.window_group.add_child(this._backgroundGroup);
this._backgroundGroup.lower_bottom();
global.window_group.set_child_below_sibling(this._backgroundGroup, null);
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',
@ -334,10 +340,11 @@ var LayoutManager = GObject.registerClass({
this.monitors = [];
let nMonitors = display.get_n_monitors();
for (let i = 0; i < nMonitors; i++)
for (let i = 0; i < nMonitors; i++) {
this.monitors.push(new Monitor(i,
display.get_monitor_geometry(i),
display.get_monitor_scale(i)));
}
if (nMonitors == 0) {
this.primaryIndex = this.bottomIndex = -1;
@ -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
@ -435,7 +447,7 @@ var LayoutManager = GObject.registerClass({
_createBackgroundManager(monitorIndex) {
let bgManager = new Background.BackgroundManager({ container: this._backgroundGroup,
layoutManager: this,
monitorIndex: monitorIndex });
monitorIndex });
bgManager.connect('changed', this._addBackgroundMenu.bind(this));
this._addBackgroundMenu(bgManager);
@ -449,17 +461,17 @@ var LayoutManager = GObject.registerClass({
let backgroundActor = this._bgManagers[i].backgroundActor;
backgroundActor.show();
backgroundActor.opacity = 0;
Tweener.addTween(backgroundActor,
{ opacity: 255,
time: BACKGROUND_FADE_ANIMATION_TIME,
transition: 'easeOutQuad' });
backgroundActor.ease({
opacity: 255,
duration: BACKGROUND_FADE_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
}
}
_updateBackgrounds() {
let i;
for (i = 0; i < this._bgManagers.length; i++)
for (let i = 0; i < this._bgManagers.length; i++)
this._bgManagers[i].destroy();
this._bgManagers = [];
@ -590,17 +602,17 @@ var LayoutManager = GObject.registerClass({
return;
}
this._systemBackground = new Background.SystemBackground();
this._systemBackground.actor.hide();
this._systemBackground.hide();
global.stage.insert_child_below(this._systemBackground.actor, null);
global.stage.insert_child_below(this._systemBackground, null);
let constraint = new Clutter.BindConstraint({ source: global.stage,
coordinate: Clutter.BindCoordinate.ALL });
this._systemBackground.actor.add_constraint(constraint);
this._systemBackground.add_constraint(constraint);
let signalId = this._systemBackground.connect('loaded', () => {
this._systemBackground.disconnect(signalId);
this._systemBackground.actor.show();
this._systemBackground.show();
global.stage.show();
this._prepareStartupAnimation();
@ -683,30 +695,30 @@ var LayoutManager = GObject.registerClass({
}
_startupAnimationGreeter() {
Tweener.addTween(this.panelBox,
{ translation_y: 0,
time: STARTUP_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._startupAnimationComplete,
onCompleteScope: this });
this.panelBox.ease({
translation_y: 0,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._startupAnimationComplete(),
});
}
_startupAnimationSession() {
Tweener.addTween(this.uiGroup,
{ scale_x: 1,
scale_y: 1,
opacity: 255,
time: STARTUP_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._startupAnimationComplete,
onCompleteScope: this });
this.uiGroup.ease({
scale_x: 1,
scale_y: 1,
opacity: 255,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._startupAnimationComplete(),
});
}
_startupAnimationComplete() {
this._coverPane.destroy();
this._coverPane = null;
this._systemBackground.actor.destroy();
this._systemBackground.destroy();
this._systemBackground = null;
this._startingUp = false;
@ -725,14 +737,15 @@ var LayoutManager = GObject.registerClass({
showKeyboard() {
this.keyboardBox.show();
Tweener.addTween(this.keyboardBox,
{ anchor_y: this.keyboardBox.height,
opacity: 255,
time: KEYBOARD_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._showKeyboardComplete,
onCompleteScope: this
});
this.keyboardBox.ease({
anchor_y: this.keyboardBox.height,
opacity: 255,
duration: KEYBOARD_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this._showKeyboardComplete();
},
});
this.emit('keyboard-visible-changed', true);
}
@ -751,14 +764,15 @@ var LayoutManager = GObject.registerClass({
this.keyboardBox.disconnect(this._keyboardHeightNotifyId);
this._keyboardHeightNotifyId = 0;
}
Tweener.addTween(this.keyboardBox,
{ anchor_y: 0,
opacity: 0,
time: immediate ? 0 : KEYBOARD_ANIMATION_TIME,
transition: 'easeInQuad',
onComplete: this._hideKeyboardComplete,
onCompleteScope: this
});
this.keyboardBox.ease({
anchor_y: 0,
opacity: 0,
duration: immediate ? 0 : KEYBOARD_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_IN_QUAD,
onComplete: () => {
this._hideKeyboardComplete();
},
});
this.emit('keyboard-visible-changed', false);
}
@ -829,7 +843,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) {
@ -837,14 +851,13 @@ var LayoutManager = GObject.registerClass({
index = this._findActor(ancestor);
}
let ancestorData = ancestor ? this._trackedActors[index]
: defaultParams;
if (!params)
params = {};
let ancestorData = ancestor
? this._trackedActors[index]
: defaultParams;
// We can't use Params.parse here because we want to drop
// the extra values like ancestorData.actor
for (let prop in defaultParams) {
if (!params.hasOwnProperty(prop))
if (!Object.prototype.hasOwnProperty.call(params, prop))
params[prop] = ancestorData[prop];
}
@ -944,7 +957,7 @@ var LayoutManager = GObject.registerClass({
findIndexForActor(actor) {
let [x, y] = actor.get_transformed_position();
let [w, h] = actor.get_transformed_size();
let rect = new Meta.Rectangle({ x: x, y: y, width: w, height: h });
let rect = new Meta.Rectangle({ x, y, width: w, height: h });
return global.display.get_monitor_index_for_rect(rect);
}
@ -959,9 +972,10 @@ var LayoutManager = GObject.registerClass({
if (this._startingUp)
return;
if (!this._updateRegionIdle)
if (!this._updateRegionIdle) {
this._updateRegionIdle = Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
this._updateRegions.bind(this));
}
}
_getWindowActorsForWorkspace(workspace) {
@ -998,11 +1012,6 @@ var LayoutManager = GObject.registerClass({
if (Main.modalCount > 0)
return GLib.SOURCE_REMOVE;
// Bug workaround - get_transformed_position()/get_transformed_size() don't work after
// a change in stage size until the first pick or paint.
// https://bugzilla.gnome.org/show_bug.cgi?id=761565
global.stage.get_actor_at_pos(Clutter.PickMode.ALL, 0, 0);
let rects = [], struts = [], i;
let isPopupMenuVisible = global.top_window_group.get_children().some(isPopupMetaWindow);
let wantsInputRegion = !isPopupMenuVisible;
@ -1020,7 +1029,7 @@ var LayoutManager = GObject.registerClass({
h = Math.round(h);
if (actorData.affectsInputRegion && wantsInputRegion && actorData.actor.get_paint_visibility())
rects.push(new Meta.Rectangle({ x: x, y: y, width: w, height: h }));
rects.push(new Meta.Rectangle({ x, y, width: w, height: h }));
let monitor = null;
if (actorData.affectsStruts)
@ -1058,19 +1067,20 @@ var LayoutManager = GObject.registerClass({
side = Meta.Side.RIGHT;
else
continue;
} else if (x1 <= monitor.x)
} else if (x1 <= monitor.x) {
side = Meta.Side.LEFT;
else if (y1 <= monitor.y)
} else if (y1 <= monitor.y) {
side = Meta.Side.TOP;
else if (x2 >= monitor.x + monitor.width)
} else if (x2 >= monitor.x + monitor.width) {
side = Meta.Side.RIGHT;
else if (y2 >= monitor.y + monitor.height)
} else if (y2 >= monitor.y + monitor.height) {
side = Meta.Side.BOTTOM;
else
} else {
continue;
}
let strutRect = new Meta.Rectangle({ x: x1, y: y1, width: x2 - x1, height: y2 - y1 });
let strut = new Meta.Strut({ rect: strutRect, side: side });
let strut = new Meta.Strut({ rect: strutRect, side });
struts.push(strut);
}
}
@ -1099,8 +1109,11 @@ var LayoutManager = GObject.registerClass({
//
// This class manages a "hot corner" that can toggle switching to
// overview.
var HotCorner = class HotCorner {
constructor(layoutManager, monitor, x, y) {
var HotCorner = GObject.registerClass(
class HotCorner extends Clutter.Actor {
_init(layoutManager, monitor, x, y) {
super._init();
// We use this flag to mark the case where the user has entered the
// hot corner and has not left both the hot corner and a surrounding
// guard area (the "environs"). This avoids triggering the hot corner
@ -1129,6 +1142,8 @@ var HotCorner = class HotCorner {
this._ripples = new Ripples.Ripples(px, py, 'ripple-box');
this._ripples.addTo(layoutManager.uiGroup);
this.connect('destroy', this._onDestroy.bind(this));
}
setBarrierSize(size) {
@ -1168,11 +1183,14 @@ var HotCorner = class HotCorner {
_setupFallbackCornerIfNeeded(layoutManager) {
if (!global.display.supports_extended_barriers()) {
this.actor = new Clutter.Actor({ name: 'hot-corner-environs',
x: this._x, y: this._y,
width: 3,
height: 3,
reactive: true });
this.set({
name: 'hot-corner-environs',
x: this._x,
y: this._y,
width: 3,
height: 3,
reactive: true,
});
this._corner = new Clutter.Actor({ name: 'hot-corner',
width: 1,
@ -1181,19 +1199,16 @@ var HotCorner = class HotCorner {
reactive: true });
this._corner._delegate = this;
this.actor.add_child(this._corner);
layoutManager.addChrome(this.actor);
this.add_child(this._corner);
layoutManager.addChrome(this);
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
this._corner.set_position(this.actor.width - this._corner.width, 0);
this.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
this._corner.set_position(this.width - this._corner.width, 0);
this.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
} else {
this._corner.set_position(0, 0);
}
this.actor.connect('leave-event',
this._onEnvironsLeft.bind(this));
this._corner.connect('enter-event',
this._onCornerEntered.bind(this));
this._corner.connect('leave-event',
@ -1201,13 +1216,12 @@ var HotCorner = class HotCorner {
}
}
destroy() {
_onDestroy() {
this.setBarrierSize(0);
this._pressureBarrier.destroy();
this._pressureBarrier = null;
if (this.actor)
this.actor.destroy();
this._ripples.destroy();
}
_toggleOverview() {
@ -1220,7 +1234,7 @@ var HotCorner = class HotCorner {
}
}
handleDragOver(source, actor, x, y, time) {
handleDragOver(source, _actor, _x, _y, _time) {
if (source != Main.xdndHandler)
return DND.DragMotionResult.CONTINUE;
@ -1238,18 +1252,18 @@ var HotCorner = class HotCorner {
}
_onCornerLeft(actor, event) {
if (event.get_related() != this.actor)
if (event.get_related() != this)
this._entered = false;
// Consume event, otherwise this will confuse onEnvironsLeft
return Clutter.EVENT_STOP;
}
_onEnvironsLeft(actor, event) {
if (event.get_related() != this._corner)
vfunc_leave_event(crossingEvent) {
if (crossingEvent.related != this._corner)
this._entered = false;
return Clutter.EVENT_PROPAGATE;
}
};
});
var PressureBarrier = class PressureBarrier {
constructor(threshold, timeout, actionMode) {
@ -1321,7 +1335,7 @@ var PressureBarrier = class PressureBarrier {
let threshold = this._lastTime - this._timeout;
while (i < this._barrierEvents.length) {
let [time, distance] = this._barrierEvents[i];
let [time, distance_] = this._barrierEvents[i];
if (time >= threshold)
break;
i++;
@ -1330,14 +1344,14 @@ var PressureBarrier = class PressureBarrier {
let firstNewEvent = i;
for (i = 0; i < firstNewEvent; i++) {
let [time, distance] = this._barrierEvents[i];
let [time_, distance] = this._barrierEvents[i];
this._currentPressure -= distance;
}
this._barrierEvents = this._barrierEvents.slice(firstNewEvent);
}
_onBarrierLeft(barrier, event) {
_onBarrierLeft(barrier, _event) {
barrier._isHit = false;
if (this._barriers.every(b => !b._isHit)) {
this._reset();

View File

@ -1,10 +1,9 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Lightbox */
const { Clutter, GObject, Shell, St } = imports.gi;
const Signals = imports.signals;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
var DEFAULT_FADE_FACTOR = 0.4;
var VIGNETTE_BRIGHTNESS = 0.2;
@ -23,16 +22,31 @@ t = clamp(t, 0.0, 1.0);\n\
float pixel_brightness = mix(1.0, 1.0 - vignette_sharpness, t);\n\
cogl_color_out.a = cogl_color_out.a * (1 - pixel_brightness * brightness);';
var RadialShaderQuad = GObject.registerClass(
class RadialShaderQuad extends Shell.GLSLQuad {
var RadialShaderEffect = GObject.registerClass({
Properties: {
'brightness': GObject.ParamSpec.float(
'brightness', 'brightness', 'brightness',
GObject.ParamFlags.READWRITE,
0, 1, 1
),
'sharpness': GObject.ParamSpec.float(
'sharpness', 'sharpness', 'sharpness',
GObject.ParamFlags.READWRITE,
0, 1, 0
),
},
}, class RadialShaderEffect extends Shell.GLSLEffect {
_init(params) {
this._brightness = undefined;
this._sharpness = undefined;
super._init(params);
this._brightnessLocation = this.get_uniform_location('brightness');
this._sharpnessLocation = this.get_uniform_location('vignette_sharpness');
this.brightness = 1.0;
this.vignetteSharpness = 0.0;
this.sharpness = 0.0;
}
vfunc_build_pipeline() {
@ -45,19 +59,25 @@ class RadialShaderQuad extends Shell.GLSLQuad {
}
set brightness(v) {
if (this._brightness == v)
return;
this._brightness = v;
this.set_uniform_float(this._brightnessLocation,
1, [this._brightness]);
this.notify('brightness');
}
get vignetteSharpness() {
get sharpness() {
return this._sharpness;
}
set vignetteSharpness(v) {
set sharpness(v) {
if (this._sharpness == v)
return;
this._sharpness = v;
this.set_uniform_float(this._sharpnessLocation,
1, [this._sharpness]);
this.notify('sharpness');
}
});
@ -68,8 +88,8 @@ class RadialShaderQuad extends Shell.GLSLQuad {
* - inhibitEvents: whether to inhibit events for @container
* - width: shade actor width
* - height: shade actor height
* - fadeInTime: seconds used to fade in
* - fadeOutTime: seconds used to fade out
* - fadeFactor: fading opacity factor
* - radialEffect: whether to enable the GLSL radial effect
*
* Lightbox creates a dark translucent "shade" actor to hide the
* contents of @container, and allows you to specify particular actors
@ -85,44 +105,49 @@ class RadialShaderQuad extends Shell.GLSLQuad {
* @container and will track any changes in its size. You can override
* this by passing an explicit width and height in @params.
*/
var Lightbox = class Lightbox {
constructor(container, params) {
params = Params.parse(params, { inhibitEvents: false,
width: null,
height: null,
fadeFactor: DEFAULT_FADE_FACTOR,
radialEffect: false,
});
var Lightbox = GObject.registerClass({
Properties: {
'active': GObject.ParamSpec.boolean(
'active', 'active', 'active', GObject.ParamFlags.READABLE, false),
},
}, class Lightbox extends St.Bin {
_init(container, params) {
params = Params.parse(params, {
inhibitEvents: false,
width: null,
height: null,
fadeFactor: DEFAULT_FADE_FACTOR,
radialEffect: false,
});
super._init({
reactive: params.inhibitEvents,
width: params.width,
height: params.height,
visible: false,
});
this._active = false;
this._container = container;
this._children = container.get_children();
this._fadeFactor = params.fadeFactor;
this._radialEffect = Clutter.feature_available(Clutter.FeatureFlags.SHADERS_GLSL) && params.radialEffect;
if (this._radialEffect)
this.actor = new RadialShaderQuad({ x: 0,
y: 0,
reactive: params.inhibitEvents });
this.add_effect(new RadialShaderEffect({ name: 'radial' }));
else
this.actor = new St.Bin({ x: 0,
y: 0,
opacity: 0,
style_class: 'lightbox',
reactive: params.inhibitEvents });
this.set({ opacity: 0, style_class: 'lightbox' });
container.add_actor(this.actor);
this.actor.raise_top();
this.actor.hide();
this.shown = false;
container.add_actor(this);
container.set_child_above_sibling(this, null);
this.actor.connect('destroy', this._onDestroy.bind(this));
this.connect('destroy', this._onDestroy.bind(this));
if (params.width && params.height) {
this.actor.width = params.width;
this.actor.height = params.height;
} else {
let constraint = new Clutter.BindConstraint({ source: container,
coordinate: Clutter.BindCoordinate.ALL });
this.actor.add_constraint(constraint);
if (!params.width || !params.height) {
this.add_constraint(new Clutter.BindConstraint({
source: container,
coordinate: Clutter.BindCoordinate.ALL,
}));
}
this._actorAddedSignalId = container.connect('actor-added', this._actorAdded.bind(this));
@ -131,16 +156,20 @@ var Lightbox = class Lightbox {
this._highlighted = null;
}
get active() {
return this._active;
}
_actorAdded(container, newChild) {
let children = this._container.get_children();
let myIndex = children.indexOf(this.actor);
let myIndex = children.indexOf(this);
let newChildIndex = children.indexOf(newChild);
if (newChildIndex > myIndex) {
// The child was added above the shade (presumably it was
// made the new top-most child). Move it below the shade,
// and add it to this._children as the new topmost actor.
newChild.lower(this.actor);
this._container.set_child_above_sibling(this, newChild);
this._children.push(newChild);
} else if (newChildIndex == 0) {
// Bottom of stack
@ -153,61 +182,55 @@ var Lightbox = class Lightbox {
}
}
show(fadeInTime) {
fadeInTime = fadeInTime || 0;
lightOn(fadeInTime) {
this.remove_all_transitions();
let easeProps = {
duration: fadeInTime || 0,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
};
let onComplete = () => {
this._active = true;
this.notify('active');
};
this.show();
Tweener.removeTweens(this.actor);
if (this._radialEffect) {
Tweener.addTween(this.actor,
{ brightness: VIGNETTE_BRIGHTNESS,
vignetteSharpness: VIGNETTE_SHARPNESS,
time: fadeInTime,
transition: 'easeOutQuad',
onComplete: () => {
this.shown = true;
this.emit('shown');
}
});
this.ease_property(
'@effects.radial.brightness', VIGNETTE_BRIGHTNESS, easeProps);
this.ease_property(
'@effects.radial.sharpness', VIGNETTE_SHARPNESS,
Object.assign({ onComplete }, easeProps));
} else {
Tweener.addTween(this.actor,
{ opacity: 255 * this._fadeFactor,
time: fadeInTime,
transition: 'easeOutQuad',
onComplete: () => {
this.shown = true;
this.emit('shown');
}
});
this.ease(Object.assign(easeProps, {
opacity: 255 * this._fadeFactor,
onComplete,
}));
}
this.actor.show();
}
hide(fadeOutTime) {
fadeOutTime = fadeOutTime || 0;
lightOff(fadeOutTime) {
this.remove_all_transitions();
this._active = false;
this.notify('active');
let easeProps = {
duration: fadeOutTime || 0,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
};
let onComplete = () => this.hide();
this.shown = false;
Tweener.removeTweens(this.actor);
if (this._radialEffect) {
Tweener.addTween(this.actor,
{ brightness: 1.0,
vignetteSharpness: 0.0,
opacity: 0,
time: fadeOutTime,
transition: 'easeOutQuad',
onComplete: () => {
this.actor.hide();
}
});
this.ease_property(
'@effects.radial.brightness', 1.0, easeProps);
this.ease_property(
'@effects.radial.sharpness', 0.0, Object.assign({ onComplete }, easeProps));
} else {
Tweener.addTween(this.actor,
{ opacity: 0,
time: fadeOutTime,
transition: 'easeOutQuad',
onComplete: () => {
this.actor.hide();
}
});
this.ease(Object.assign(easeProps, { opacity: 0, onComplete }));
}
}
@ -222,7 +245,7 @@ var Lightbox = class Lightbox {
/**
* highlight:
* @window: actor to highlight
* @param {Clutter.Actor=} window: actor to highlight
*
* Highlights the indicated actor and unhighlights any other
* currently-highlighted actor. With no arguments or a false/null
@ -238,12 +261,12 @@ var Lightbox = class Lightbox {
// case we may need to indicate some *other* actor as the new
// sibling of the to-be-lowered one.
let below = this.actor;
let below = this;
for (let i = this._children.length - 1; i >= 0; i--) {
if (this._children[i] == window)
this._children[i].raise_top();
this._container.set_child_above_sibling(this._children[i], null);
else if (this._children[i] == this._highlighted)
this._children[i].lower(below);
this._container.set_child_below_sibling(this._children[i], below);
else
below = this._children[i];
}
@ -251,15 +274,6 @@ var Lightbox = class Lightbox {
this._highlighted = window;
}
/**
* destroy:
*
* Destroys the lightbox.
*/
destroy() {
this.actor.destroy();
}
/**
* _onDestroy:
*
@ -267,10 +281,15 @@ var Lightbox = class Lightbox {
* by destroying its container or by explicitly calling this.destroy().
*/
_onDestroy() {
this._container.disconnect(this._actorAddedSignalId);
this._container.disconnect(this._actorRemovedSignalId);
if (this._actorAddedSignalId) {
this._container.disconnect(this._actorAddedSignalId);
this._actorAddedSignalId = 0;
}
if (this._actorRemovedSignalId) {
this._container.disconnect(this._actorRemovedSignalId);
this._actorRemovedSignalId = 0;
}
this.highlight(null);
}
};
Signals.addSignalMethods(Lightbox.prototype);
});

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported LocatePointer */
const { Gio } = imports.gi;
const Ripples = imports.ui.ripples;
@ -10,15 +11,29 @@ const LOCATE_POINTER_SCHEMA = "org.gnome.desktop.interface";
var LocatePointer = class {
constructor() {
this._settings = new Gio.Settings({ schema_id: LOCATE_POINTER_SCHEMA });
this._ripples = new Ripples.Ripples(0.5, 0.5, 'ripple-pointer-location');
this._ripples.addTo(Main.uiGroup);
this._settings.connect(`changed::${LOCATE_POINTER_KEY}`, () => this._syncEnabled());
this._syncEnabled();
}
_syncEnabled() {
let enabled = this._settings.get_boolean(LOCATE_POINTER_KEY);
if (enabled == !!this._ripples)
return;
if (enabled) {
this._ripples = new Ripples.Ripples(0.5, 0.5, 'ripple-pointer-location');
this._ripples.addTo(Main.uiGroup);
} else {
this._ripples.destroy();
this._ripples = null;
}
}
show() {
if (!this._settings.get_boolean(LOCATE_POINTER_KEY))
if (!this._ripples)
return;
let [x, y, mods] = global.get_pointer();
let [x, y] = global.get_pointer();
this._ripples.playAnimation(x, y);
}
};

View File

@ -1,26 +1,24 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported LookingGlass */
const { Clutter, Cogl, Gio, GLib,
GObject, Meta, Pango, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const { Clutter, Cogl, Gio, GLib, GObject,
Graphene, Meta, Pango, Shell, St } = imports.gi;
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 */
var commandHeader = 'const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; ' +
'const Main = imports.ui.main; ' +
'const Mainloop = imports.mainloop; ' +
'const Tweener = imports.ui.tweener; ' +
/* Utility functions...we should probably be able to use these
* in the shell core code too. */
'const stage = global.stage; ' +
@ -32,9 +30,11 @@ var commandHeader = 'const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = im
const HISTORY_KEY = 'looking-glass-history';
// Time between tabs for them to count as a double-tab event
var AUTO_COMPLETE_DOUBLE_TAB_DELAY = 500;
var AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION = 0.2;
var AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION = 200;
var AUTO_COMPLETE_GLOBAL_KEYWORDS = _getAutoCompleteGlobalKeywords();
const LG_ANIMATION_TIME = 500;
function _getAutoCompleteGlobalKeywords() {
const keywords = ['true', 'false', 'null', 'new'];
// Don't add the private properties of window (i.e., ones starting with '_')
@ -54,9 +54,9 @@ var AutoComplete = class AutoComplete {
}
_processCompletionRequest(event) {
if (event.completions.length == 0) {
if (event.completions.length == 0)
return;
}
// Unique match = go ahead and complete; multiple matches + single tab = complete the common starting string;
// multiple matches + double tab = emit a suggest event with all possible options
if (event.completions.length == 1) {
@ -78,20 +78,20 @@ var AutoComplete = class AutoComplete {
_entryKeyPressEvent(actor, event) {
let cursorPos = this._entry.clutter_text.get_cursor_position();
let text = this._entry.get_text();
if (cursorPos != -1) {
if (cursorPos != -1)
text = text.slice(0, cursorPos);
}
if (event.get_key_symbol() == Clutter.Tab) {
if (event.get_key_symbol() == Clutter.KEY_Tab) {
let [completions, attrHead] = JsParse.getCompletions(text, commandHeader, AUTO_COMPLETE_GLOBAL_KEYWORDS);
let currTime = global.get_current_time();
if ((currTime - this._lastTabTime) < AUTO_COMPLETE_DOUBLE_TAB_DELAY) {
this._processCompletionRequest({ tabType: 'double',
completions: completions,
attrHead: attrHead });
completions,
attrHead });
} else {
this._processCompletionRequest({ tabType: 'single',
completions: completions,
attrHead: attrHead });
completions,
attrHead });
}
this._lastTabTime = currTime;
}
@ -110,9 +110,14 @@ var AutoComplete = class AutoComplete {
Signals.addSignalMethods(AutoComplete.prototype);
var Notebook = class Notebook {
constructor() {
this.actor = new St.BoxLayout({ vertical: true });
var Notebook = GObject.registerClass({
Signals: { 'selection': { param_types: [Clutter.Actor.$gtype] } },
}, class Notebook extends St.BoxLayout {
_init() {
super._init({
vertical: true,
y_expand: true,
});
this.tabControls = new St.BoxLayout({ style_class: 'labels' });
@ -129,21 +134,21 @@ var Notebook = class Notebook {
this.selectChild(child);
return true;
});
labelBox.add(label, { expand: true });
labelBox.add_child(label);
this.tabControls.add(labelBox);
let scrollview = new St.ScrollView({ x_fill: true, y_fill: true });
let scrollview = new St.ScrollView({ y_expand: true });
scrollview.get_hscroll_bar().hide();
scrollview.add_actor(child);
let tabData = { child: child,
labelBox: labelBox,
label: label,
let tabData = { child,
labelBox,
label,
scrollView: scrollview,
_scrollToBottom: false };
this._tabs.push(tabData);
scrollview.hide();
this.actor.add(scrollview, { expand: true });
this.add_child(scrollview);
let vAdjust = scrollview.vscroll.adjustment;
vAdjust.connect('changed', () => this._onAdjustScopeChanged(tabData));
@ -174,7 +179,7 @@ var Notebook = class Notebook {
// Focus the new tab before unmapping the old one
let tabData = this._tabs[index];
if (!tabData.scrollView.navigate_focus(null, St.DirectionType.TAB_FORWARD, false))
this.actor.grab_key_focus();
this.grab_key_focus();
this._unselect();
@ -219,26 +224,23 @@ var Notebook = class Notebook {
nextTab() {
let nextIndex = this._selectedIndex;
if (nextIndex < this._tabs.length - 1) {
if (nextIndex < this._tabs.length - 1)
++nextIndex;
}
this.selectIndex(nextIndex);
}
prevTab() {
let prevIndex = this._selectedIndex;
if (prevIndex > 0) {
if (prevIndex > 0)
--prevIndex;
}
this.selectIndex(prevIndex);
}
};
Signals.addSignalMethods(Notebook.prototype);
});
function objectToString(o) {
if (typeof(o) == typeof(objectToString)) {
if (typeof o == typeof objectToString) {
// special case this since the default is way, way too verbose
return '<js function>';
} else {
@ -246,57 +248,63 @@ function objectToString(o) {
}
}
var ObjLink = class ObjLink {
constructor(lookingGlass, o, title) {
var ObjLink = GObject.registerClass(
class ObjLink extends St.Button {
_init(lookingGlass, o, title) {
let text;
if (title)
text = title;
else
text = objectToString(o);
text = GLib.markup_escape_text(text, -1);
super._init({
reactive: true,
track_hover: true,
style_class: 'shell-link',
label: text,
x_align: Clutter.ActorAlign.START,
});
this.get_child().single_line_mode = true;
this._obj = o;
this.actor = new St.Button({ reactive: true,
track_hover: true,
style_class: 'shell-link',
label: text });
this.actor.get_child().single_line_mode = true;
this.actor.connect('clicked', this._onClicked.bind(this));
this._lookingGlass = lookingGlass;
}
_onClicked(link) {
this._lookingGlass.inspectObject(this._obj, this.actor);
vfunc_clicked() {
this._lookingGlass.inspectObject(this._obj, this);
}
};
});
var Result = GObject.registerClass(
class Result extends St.BoxLayout {
_init(lookingGlass, command, o, index) {
super._init({ vertical: true });
var Result = class Result {
constructor(lookingGlass, command, o, index) {
this.index = index;
this.o = o;
this.actor = new St.BoxLayout({ vertical: true });
this._lookingGlass = lookingGlass;
let cmdTxt = new St.Label({ text: command });
cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
this.actor.add(cmdTxt);
this.add(cmdTxt);
let box = new St.BoxLayout({});
this.actor.add(box);
this.add(box);
let resultTxt = new St.Label({ text: `r(${index}) = ` });
resultTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
box.add(resultTxt);
let objLink = new ObjLink(this._lookingGlass, o);
box.add(objLink.actor);
box.add(objLink);
}
};
});
var WindowList = class WindowList {
constructor(lookingGlass) {
this.actor = new St.BoxLayout({ name: 'Windows', vertical: true, style: 'spacing: 8px' });
var WindowList = GObject.registerClass({
}, class WindowList extends St.BoxLayout {
_init(lookingGlass) {
super._init({ name: 'Windows', vertical: true, style: 'spacing: 8px' });
let tracker = Shell.WindowTracker.get_default();
this._updateId = Main.initializeDeferredWork(this.actor, this._updateWindowList.bind(this));
this._updateId = Main.initializeDeferredWork(this, this._updateWindowList.bind(this));
global.display.connect('window-created', this._updateWindowList.bind(this));
tracker.connect('tracked-windows-changed', this._updateWindowList.bind(this));
@ -304,7 +312,10 @@ var WindowList = class WindowList {
}
_updateWindowList() {
this.actor.destroy_all_children();
if (!this._lookingGlass.isOpen)
return;
this.destroy_all_children();
let windows = global.get_window_actors();
let tracker = Shell.WindowTracker.get_default();
for (let i = 0; i < windows.length; i++) {
@ -315,9 +326,9 @@ var WindowList = class WindowList {
metaWindow._lookingGlassManaged = true;
}
let box = new St.BoxLayout({ vertical: true });
this.actor.add(box);
this.add(box);
let windowLink = new ObjLink(this._lookingGlass, metaWindow, metaWindow.title);
box.add(windowLink.actor, { x_align: St.Align.START, x_fill: false });
box.add_child(windowLink);
let propsBox = new St.BoxLayout({ vertical: true, style: 'padding-left: 6px;' });
box.add(propsBox);
propsBox.add(new St.Label({ text: `wmclass: ${metaWindow.get_wm_class()}` }));
@ -326,32 +337,42 @@ var WindowList = class WindowList {
let icon = app.create_icon_texture(22);
let propBox = new St.BoxLayout({ style: 'spacing: 6px; ' });
propsBox.add(propBox);
propBox.add(new St.Label({ text: 'app: ' }), { y_fill: false });
propBox.add_child(new St.Label({ text: 'app: ' }));
let appLink = new ObjLink(this._lookingGlass, app, app.get_id());
propBox.add(appLink.actor, { y_fill: false });
propBox.add(icon, { y_fill: false });
propBox.add_child(appLink);
propBox.add_child(icon);
} else {
propsBox.add(new St.Label({ text: '<untracked>' }));
}
}
}
};
Signals.addSignalMethods(WindowList.prototype);
var ObjInspector = class ObjInspector {
constructor(lookingGlass) {
update() {
this._updateWindowList();
}
});
var ObjInspector = GObject.registerClass(
class ObjInspector extends St.ScrollView {
_init(lookingGlass) {
super._init({
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
});
this._obj = null;
this._previousObj = null;
this._parentList = [];
this.actor = new St.ScrollView({ pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }),
x_fill: true, y_fill: true });
this.actor.get_hscroll_bar().hide();
this._container = new St.BoxLayout({ name: 'LookingGlassPropertyInspector',
style_class: 'lg-dialog',
vertical: true });
this.actor.add_actor(this._container);
this.get_hscroll_bar().hide();
this._container = new St.BoxLayout({
name: 'LookingGlassPropertyInspector',
style_class: 'lg-dialog',
vertical: true,
x_expand: true,
y_expand: true,
});
this.add_actor(this._container);
this._lookingGlass = lookingGlass;
}
@ -367,10 +388,12 @@ var ObjInspector = class ObjInspector {
let hbox = new St.BoxLayout({ style_class: 'lg-obj-inspector-title' });
this._container.add_actor(hbox);
let label = new St.Label({ text: 'Inspecting: %s: %s'.format(typeof(obj),
objectToString(obj)) });
let label = new St.Label({
text: 'Inspecting: %s: %s'.format(typeof obj, objectToString(obj)),
x_expand: true,
});
label.single_line_mode = true;
hbox.add(label, { expand: true, y_fill: false });
hbox.add_child(label);
let button = new St.Button({ label: 'Insert', style_class: 'lg-obj-inspector-button' });
button.connect('clicked', this._onInsert.bind(this));
hbox.add(button);
@ -385,11 +408,10 @@ var ObjInspector = class ObjInspector {
button.add_actor(new St.Icon({ icon_name: 'window-close-symbolic' }));
button.connect('clicked', this.close.bind(this));
hbox.add(button);
if (typeof(obj) == typeof({})) {
if (typeof obj == typeof {}) {
let properties = [];
for (let propName in obj) {
for (let propName in obj)
properties.push(propName);
}
properties.sort();
for (let i = 0; i < properties.length; i++) {
@ -397,14 +419,14 @@ var ObjInspector = class ObjInspector {
let link;
try {
let prop = obj[propName];
link = new ObjLink(this._lookingGlass, prop).actor;
link = new ObjLink(this._lookingGlass, prop);
} catch (e) {
link = new St.Label({ text: '<error>' });
}
let hbox = new St.BoxLayout();
hbox.add(new St.Label({ text: `${propName}: ` }));
hbox.add(link);
this._container.add_actor(hbox);
let box = new St.BoxLayout();
box.add(new St.Label({ text: `${propName}: ` }));
box.add(link);
this._container.add_actor(box);
}
}
}
@ -414,14 +436,17 @@ var ObjInspector = class ObjInspector {
return;
this._previousObj = null;
this._open = true;
this.actor.show();
this.show();
if (sourceActor) {
this.actor.set_scale(0, 0);
Tweener.addTween(this.actor, { scale_x: 1, scale_y: 1,
transition: 'easeOutQuad',
time: 0.2 });
this.set_scale(0, 0);
this.ease({
scale_x: 1,
scale_y: 1,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
duration: 200,
});
} else {
this.actor.set_scale(1, 1);
this.set_scale(1, 1);
}
}
@ -429,7 +454,7 @@ var ObjInspector = class ObjInspector {
if (!this._open)
return;
this._open = false;
this.actor.hide();
this.hide();
this._previousObj = null;
this._obj = null;
}
@ -443,29 +468,44 @@ var ObjInspector = class ObjInspector {
_onBack() {
this.selectObject(this._previousObj, true);
}
};
});
var RedBorderEffect = GObject.registerClass(
class RedBorderEffect extends Clutter.Effect {
vfunc_paint() {
_init() {
super._init();
this._pipeline = null;
}
vfunc_paint(paintContext) {
let framebuffer = paintContext.get_framebuffer();
let coglContext = framebuffer.get_context();
let actor = this.get_actor();
actor.continue_paint();
actor.continue_paint(paintContext);
let color = new Cogl.Color();
color.init_from_4ub(0xff, 0, 0, 0xc4);
Cogl.set_source_color(color);
if (!this._pipeline) {
let color = new Cogl.Color();
color.init_from_4ub(0xff, 0, 0, 0xc4);
let geom = actor.get_allocation_geometry();
this._pipeline = new Cogl.Pipeline(coglContext);
this._pipeline.set_color(color);
}
let alloc = actor.get_allocation_box();
let width = 2;
// clockwise order
Cogl.rectangle(0, 0, geom.width, width);
Cogl.rectangle(geom.width - width, width,
geom.width, geom.height);
Cogl.rectangle(0, geom.height,
geom.width - width, geom.height - width);
Cogl.rectangle(0, geom.height - width,
width, width);
framebuffer.draw_rectangle(this._pipeline,
0, 0, alloc.get_width(), width);
framebuffer.draw_rectangle(this._pipeline,
alloc.get_width() - width, width,
alloc.get_width(), alloc.get_height());
framebuffer.draw_rectangle(this._pipeline,
0, alloc.get_height(),
alloc.get_width() - width, alloc.get_height() - width);
framebuffer.draw_rectangle(this._pipeline,
0, alloc.get_height() - width,
width, width);
}
});
@ -474,8 +514,7 @@ var Inspector = GObject.registerClass({
'target': { param_types: [Clutter.Actor.$gtype, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] } },
}, class Inspector extends Clutter.Actor {
_init(lookingGlass) {
super._init({ width: 0,
height: 0 });
super._init({ width: 0, height: 0 });
Main.uiGroup.add_actor(this);
@ -484,8 +523,8 @@ var Inspector = GObject.registerClass({
reactive: true });
this._eventHandler = eventHandler;
this.add_actor(eventHandler);
this._displayText = new St.Label();
eventHandler.add(this._displayText, { expand: true });
this._displayText = new St.Label({ x_expand: true });
eventHandler.add_child(this._displayText);
eventHandler.connect('key-press-event', this._onKeyPressEvent.bind(this));
eventHandler.connect('button-press-event', this._onButtonPressEvent.bind(this));
@ -518,7 +557,7 @@ var Inspector = GObject.registerClass({
let primary = Main.layoutManager.primaryMonitor;
let [minWidth, minHeight, natWidth, natHeight] =
let [, , natWidth, natHeight] =
this._eventHandler.get_preferred_size();
let childBox = new Clutter.ActorBox();
@ -538,7 +577,7 @@ var Inspector = GObject.registerClass({
}
_onKeyPressEvent(actor, event) {
if (event.get_key_symbol() == Clutter.Escape)
if (event.get_key_symbol() === Clutter.KEY_Escape)
this._close();
return Clutter.EVENT_STOP;
}
@ -610,28 +649,30 @@ var Inspector = GObject.registerClass({
}
});
var Extensions = class Extensions {
constructor(lookingGlass) {
var Extensions = GObject.registerClass({
}, class Extensions extends St.BoxLayout {
_init(lookingGlass) {
super._init({ vertical: true, name: 'lookingGlassExtensions' });
this._lookingGlass = lookingGlass;
this.actor = new St.BoxLayout({ vertical: true,
name: 'lookingGlassExtensions' });
this._noExtensions = new St.Label({ style_class: 'lg-extensions-none',
text: _("No extensions installed") });
this._numExtensions = 0;
this._extensionsList = new St.BoxLayout({ vertical: true,
style_class: 'lg-extensions-list' });
this._extensionsList.add(this._noExtensions);
this.actor.add(this._extensionsList);
this.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)
@ -641,7 +682,7 @@ var Extensions = class Extensions {
if (this._numExtensions == 0)
this._extensionsList.remove_actor(this._noExtensions);
this._numExtensions ++;
this._numExtensions++;
this._extensionsList.add(extensionDisplay);
}
@ -666,7 +707,7 @@ var Extensions = class Extensions {
let errors = extension.errors;
let errorDisplay = new St.BoxLayout({ vertical: true });
if (errors && errors.length) {
for (let i = 0; i < errors.length; i ++)
for (let i = 0; i < errors.length; i++)
errorDisplay.add(new St.Label({ text: errors[i] }));
} else {
/* Translators: argument is an extension UUID. */
@ -688,16 +729,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
@ -705,12 +746,18 @@ var Extensions = class Extensions {
_createExtensionDisplay(extension) {
let box = new St.BoxLayout({ style_class: 'lg-extension', vertical: true });
let name = new St.Label({ style_class: 'lg-extension-name',
text: extension.metadata.name });
box.add(name, { expand: true });
let description = new St.Label({ style_class: 'lg-extension-description',
text: extension.metadata.description || 'No description' });
box.add(description, { expand: true });
let name = new St.Label({
style_class: 'lg-extension-name',
text: extension.metadata.name,
x_expand: true,
});
box.add_child(name);
let description = new St.Label({
style_class: 'lg-extension-description',
text: extension.metadata.description || 'No description',
x_expand: true,
});
box.add_child(description);
let metaBox = new St.BoxLayout({ style_class: 'lg-extension-meta' });
box.add(metaBox);
@ -748,10 +795,19 @@ var Extensions = class Extensions {
return box;
}
};
});
var LookingGlass = GObject.registerClass(
class LookingGlass extends St.BoxLayout {
_init() {
super._init({
name: 'LookingGlassDialog',
style_class: 'lg-dialog',
vertical: true,
visible: false,
reactive: true,
});
var LookingGlass = class LookingGlass {
constructor() {
this._borderPaintTarget = null;
this._redBorderEffect = new RedBorderEffect();
@ -759,26 +815,18 @@ var LookingGlass = class LookingGlass {
this._it = null;
this._offset = 0;
this._results = [];
// Sort of magic, but...eh.
this._maxItems = 150;
this.actor = new St.BoxLayout({ name: 'LookingGlassDialog',
style_class: 'lg-dialog',
vertical: true,
visible: false,
reactive: true });
this.actor.connect('key-press-event', this._globalKeyPressEvent.bind(this));
this._interfaceSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
this._interfaceSettings.connect('changed::monospace-font-name',
this._updateFont.bind(this));
this._updateFont();
// We want it to appear to slide out from underneath the panel
Main.uiGroup.add_actor(this.actor);
Main.uiGroup.set_child_below_sibling(this.actor,
Main.uiGroup.add_actor(this);
Main.uiGroup.set_child_below_sibling(this,
Main.layoutManager.panelBox);
Main.layoutManager.panelBox.connect('allocation-changed',
this._queueResize.bind(this));
@ -786,11 +834,11 @@ var LookingGlass = class LookingGlass {
this._queueResize.bind(this));
this._objInspector = new ObjInspector(this);
Main.uiGroup.add_actor(this._objInspector.actor);
this._objInspector.actor.hide();
Main.uiGroup.add_actor(this._objInspector);
this._objInspector.hide();
let toolbar = new St.BoxLayout({ name: 'Toolbar' });
this.actor.add_actor(toolbar);
this.add_actor(toolbar);
let inspectIcon = new St.Icon({ icon_name: 'gtk-color-picker',
icon_size: 24 });
toolbar.add_actor(inspectIcon);
@ -801,10 +849,10 @@ var LookingGlass = class LookingGlass {
this._pushResult(`inspect(${Math.round(stageX)}, ${Math.round(stageY)})`, target);
});
inspector.connect('closed', () => {
this.actor.show();
this.show();
global.stage.set_key_focus(this._entry);
});
this.actor.hide();
this.hide();
return Clutter.EVENT_STOP;
});
@ -815,7 +863,7 @@ var LookingGlass = class LookingGlass {
gcIcon.connect('button-press-event', () => {
gcIcon.icon_name = 'user-trash';
System.gc();
this._timeoutId = Mainloop.timeout_add(500, () => {
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
gcIcon.icon_name = 'user-trash-full';
this._timeoutId = 0;
return GLib.SOURCE_REMOVE;
@ -826,35 +874,45 @@ var LookingGlass = class LookingGlass {
let notebook = new Notebook();
this._notebook = notebook;
this.actor.add(notebook.actor, { expand: true });
this.add_child(notebook);
let emptyBox = new St.Bin();
toolbar.add(emptyBox, { expand: true });
let emptyBox = new St.Bin({ x_expand: true });
toolbar.add_child(emptyBox);
toolbar.add_actor(notebook.tabControls);
this._evalBox = new St.BoxLayout({ name: 'EvalBox', vertical: true });
notebook.appendPage('Evaluator', this._evalBox);
this._resultsArea = new St.BoxLayout({ name: 'ResultsArea', vertical: true });
this._evalBox.add(this._resultsArea, { expand: true });
this._resultsArea = new St.BoxLayout({
name: 'ResultsArea',
vertical: true,
y_expand: true,
});
this._evalBox.add_child(this._resultsArea);
this._entryArea = new St.BoxLayout({ name: 'EntryArea' });
this._entryArea = new St.BoxLayout({
name: 'EntryArea',
y_align: Clutter.ActorAlign.END,
});
this._evalBox.add_actor(this._entryArea);
let label = new St.Label({ text: CHEVRON });
this._entryArea.add(label);
this._entry = new St.Entry({ can_focus: true });
this._entry = new St.Entry({
can_focus: true,
x_expand: true,
});
ShellEntry.addContextMenu(this._entry);
this._entryArea.add(this._entry, { expand: true });
this._entryArea.add_child(this._entry);
this._windowList = new WindowList(this);
notebook.appendPage('Windows', this._windowList.actor);
notebook.appendPage('Windows', this._windowList);
this._extensions = new Extensions(this);
notebook.appendPage('Extensions', this._extensions.actor);
notebook.appendPage('Extensions', this._extensions);
this._entry.clutter_text.connect('activate', (o, e) => {
this._entry.clutter_text.connect('activate', (o, _e) => {
// Hide any completions we are currently showing
this._hideCompletions();
@ -870,7 +928,7 @@ var LookingGlass = class LookingGlass {
return true;
});
this._history = new History.HistoryManager({ gsettingsKey: HISTORY_KEY,
this._history = new History.HistoryManager({ gsettingsKey: HISTORY_KEY,
entry: this._entry.clutter_text });
this._autoComplete = new AutoComplete(this._entry);
@ -894,7 +952,7 @@ var LookingGlass = class LookingGlass {
// monospace font to be bold/oblique/etc. Could easily be added here.
let size = fontDesc.get_size() / 1024.;
let unit = fontDesc.get_size_is_absolute() ? 'px' : 'pt';
this.actor.style = `
this.style = `
font-size: ${size}${unit};
font-family: "${fontDesc.get_family()}";`;
}
@ -908,17 +966,14 @@ var LookingGlass = class LookingGlass {
}
_pushResult(command, obj) {
let index = this._results.length + this._offset;
let index = this._resultsArea.get_n_children() + this._offset;
let result = new Result(this, CHEVRON + command, obj, index);
this._results.push(result);
this._resultsArea.add(result.actor);
this._resultsArea.add(result);
if (obj instanceof Clutter.Actor)
this.setBorderPaintTarget(obj);
let children = this._resultsArea.get_children();
if (children.length > this._maxItems) {
this._results.shift();
children[0].destroy();
if (this._resultsArea.get_n_children() > this._maxItems) {
this._resultsArea.get_first_child().destroy();
this._offset++;
}
this._it = obj;
@ -938,35 +993,41 @@ var LookingGlass = class LookingGlass {
this._completionActor.set_text(completions.join(', '));
// Setting the height to -1 allows us to get its actual preferred height rather than
// whatever was last given in set_height by Tweener.
// whatever was last set when animating
this._completionActor.set_height(-1);
let [minHeight, naturalHeight] = this._completionActor.get_preferred_height(this._resultsArea.get_width());
let [, naturalHeight] = this._completionActor.get_preferred_height(this._resultsArea.get_width());
// Don't reanimate if we are already visible
if (this._completionActor.visible) {
this._completionActor.height = naturalHeight;
} else {
let settings = St.Settings.get();
let duration = AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION / settings.slow_down_factor;
this._completionActor.show();
Tweener.removeTweens(this._completionActor);
Tweener.addTween(this._completionActor, { time: AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION / St.get_slow_down_factor(),
transition: 'easeOutQuad',
height: naturalHeight,
opacity: 255
});
this._completionActor.remove_all_transitions();
this._completionActor.ease({
height: naturalHeight,
opacity: 255,
duration,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
}
_hideCompletions() {
if (this._completionActor) {
Tweener.removeTweens(this._completionActor);
Tweener.addTween(this._completionActor, { time: AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION / St.get_slow_down_factor(),
transition: 'easeOutQuad',
height: 0,
opacity: 0,
onComplete: () => {
this._completionActor.hide();
}
});
let settings = St.Settings.get();
let duration = AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION / settings.slow_down_factor;
this._completionActor.remove_all_transitions();
this._completionActor.ease({
height: 0,
opacity: 0,
duration,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this._completionActor.hide();
},
});
}
}
@ -998,7 +1059,11 @@ var LookingGlass = class LookingGlass {
}
getResult(idx) {
return this._results[idx - this._offset].o;
try {
return this._resultsArea.get_child_at_index(idx - this._offset).o;
} catch (e) {
throw new Error(`Unknown result at index ${idx}`);
}
}
toggle() {
@ -1009,7 +1074,10 @@ var LookingGlass = class LookingGlass {
}
_queueResize() {
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => this._resize());
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
this._resize();
return GLib.SOURCE_REMOVE;
});
}
_resize() {
@ -1017,15 +1085,15 @@ var LookingGlass = class LookingGlass {
let myWidth = primary.width * 0.7;
let availableHeight = primary.height - Main.layoutManager.keyboardBox.height;
let myHeight = Math.min(primary.height * 0.7, availableHeight * 0.9);
this.actor.x = primary.x + (primary.width - myWidth) / 2;
this.x = primary.x + (primary.width - myWidth) / 2;
this._hiddenY = primary.y + Main.layoutManager.panelBox.height - myHeight;
this._targetY = this._hiddenY + myHeight;
this.actor.y = this._hiddenY;
this.actor.width = myWidth;
this.actor.height = myHeight;
this._objInspector.actor.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8));
this._objInspector.actor.set_position(this.actor.x + Math.floor(myWidth * 0.1),
this._targetY + Math.floor(myHeight * 0.1));
this.y = this._hiddenY;
this.width = myWidth;
this.height = myHeight;
this._objInspector.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8));
this._objInspector.set_position(this.x + Math.floor(myWidth * 0.1),
this._targetY + Math.floor(myHeight * 0.1));
}
insertObject(obj) {
@ -1038,24 +1106,21 @@ var LookingGlass = class LookingGlass {
}
// Handle key events which are relevant for all tabs of the LookingGlass
_globalKeyPressEvent(actor, event) {
let symbol = event.get_key_symbol();
let modifierState = event.get_state();
if (symbol == Clutter.Escape) {
if (this._objInspector.actor.visible) {
vfunc_key_press_event(keyPressEvent) {
let symbol = keyPressEvent.keyval;
if (symbol == Clutter.KEY_Escape) {
if (this._objInspector.visible)
this._objInspector.close();
} else {
else
this.close();
}
return Clutter.EVENT_STOP;
}
// Ctrl+PgUp and Ctrl+PgDown switches tabs in the notebook view
if (modifierState & Clutter.ModifierType.CONTROL_MASK) {
if (symbol == Clutter.KEY_Page_Up) {
if (keyPressEvent.modifier_state & Clutter.ModifierType.CONTROL_MASK) {
if (symbol == Clutter.KEY_Page_Up)
this._notebook.prevTab();
} else if (symbol == Clutter.KEY_Page_Down) {
else if (symbol == Clutter.KEY_Page_Down)
this._notebook.nextTab();
}
}
return Clutter.EVENT_PROPAGATE;
}
@ -1068,40 +1133,49 @@ var LookingGlass = class LookingGlass {
return;
this._notebook.selectIndex(0);
this.actor.show();
this.show();
this._open = true;
this._history.lastItem();
Tweener.removeTweens(this.actor);
this.remove_all_transitions();
// We inverse compensate for the slow-down so you can change the factor
// through LookingGlass without long waits.
Tweener.addTween(this.actor, { time: 0.5 / St.get_slow_down_factor(),
transition: 'easeOutQuad',
y: this._targetY
});
let duration = LG_ANIMATION_TIME / St.Settings.get().slow_down_factor;
this.ease({
y: this._targetY,
duration,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
this._windowList.update();
}
close() {
if (!this._open)
return;
this._objInspector.actor.hide();
this._objInspector.hide();
this._open = false;
Tweener.removeTweens(this.actor);
this.remove_all_transitions();
this.setBorderPaintTarget(null);
Main.popModal(this._entry);
Tweener.addTween(this.actor, { time: Math.min(0.5 / St.get_slow_down_factor(), 0.5),
transition: 'easeOutQuad',
y: this._hiddenY,
onComplete: () => {
this.actor.hide();
}
});
let settings = St.Settings.get();
let duration = Math.min(LG_ANIMATION_TIME / settings.slow_down_factor,
LG_ANIMATION_TIME);
this.ease({
y: this._hiddenY,
duration,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this.hide(),
});
}
};
Signals.addSignalMethods(LookingGlass.prototype);
get isOpen() {
return this._open;
}
});

View File

@ -2,7 +2,6 @@
const { Atspi, Clutter, GDesktopEnums,
Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const Background = imports.ui.background;
@ -56,7 +55,7 @@ var MouseSpriteContent = GObject.registerClass({
return [true, this._texture.get_width(), this._texture.get_height()];
}
vfunc_paint_content(actor, node) {
vfunc_paint_content(actor, node, _paintContext) {
if (!this._texture)
return;
@ -107,8 +106,7 @@ var Magnifier = class Magnifier {
// Create the first ZoomRegion and initialize it according to the
// magnification settings.
let mask;
[this.xMouse, this.yMouse, mask] = global.get_pointer();
[this.xMouse, this.yMouse] = global.get_pointer();
let aZoomRegion = new ZoomRegion(this, this._cursorRoot);
this._zoomRegions.push(aZoomRegion);
@ -120,7 +118,7 @@ var Magnifier = class Magnifier {
});
// Export to dbus.
(new MagnifierDBus.ShellMagnifier());
new MagnifierDBus.ShellMagnifier();
this.setActive(St.Settings.get().magnifier_active);
}
@ -143,12 +141,12 @@ var Magnifier = class Magnifier {
/**
* setActive:
* Show/hide all the zoom regions.
* @activate: Boolean to activate or de-activate the magnifier.
* @param {bool} activate: Boolean to activate or de-activate the magnifier.
*/
setActive(activate) {
let isActive = this.isActive();
this._zoomRegions.forEach((zoomRegion, index, array) => {
this._zoomRegions.forEach(zoomRegion => {
zoomRegion.setActive(activate);
});
@ -179,7 +177,7 @@ var Magnifier = class Magnifier {
/**
* isActive:
* @return Whether the magnifier is active (boolean).
* @returns {bool} Whether the magnifier is active.
*/
isActive() {
// Sufficient to check one ZoomRegion since Magnifier's active
@ -214,7 +212,7 @@ var Magnifier = class Magnifier {
/**
* isTrackingMouse:
* Is the magnifier tracking the mouse currently?
* @returns {bool} whether the magnifier is currently tracking the mouse
*/
isTrackingMouse() {
return !!this._mouseTrackingId;
@ -224,17 +222,17 @@ var Magnifier = class Magnifier {
* scrollToMousePos:
* Position all zoom regions' ROI relative to the current location of the
* system pointer.
* @return true.
* @returns {bool} true.
*/
scrollToMousePos() {
let [xMouse, yMouse, mask] = global.get_pointer();
let [xMouse, yMouse] = global.get_pointer();
if (xMouse != this.xMouse || yMouse != this.yMouse) {
this.xMouse = xMouse;
this.yMouse = yMouse;
let sysMouseOverAny = false;
this._zoomRegions.forEach((zoomRegion, index, array) => {
this._zoomRegions.forEach(zoomRegion => {
if (zoomRegion.scrollToMousePos())
sysMouseOverAny = true;
});
@ -249,24 +247,24 @@ var Magnifier = class Magnifier {
/**
* createZoomRegion:
* Create a ZoomRegion instance with the given properties.
* @xMagFactor: The power to set horizontal magnification of the
* ZoomRegion. A value of 1.0 means no magnification. A
* value of 2.0 doubles the size.
* @yMagFactor: The power to set the vertical magnification of the
* ZoomRegion.
* @roi Object in the form { x, y, width, height } that
* defines the region to magnify. Given in unmagnified
* coordinates.
* @viewPort Object in the form { x, y, width, height } that defines
* the position of the ZoomRegion on screen.
* @return The newly created ZoomRegion.
* @param {number} xMagFactor:
* The power to set horizontal magnification of the ZoomRegion. A value
* of 1.0 means no magnification, a value of 2.0 doubles the size.
* @param {number} yMagFactor:
* The power to set the vertical magnification of the ZoomRegion.
* @param {{x: number, y: number, width: number, height: number}} roi:
* The reg Object that defines the region to magnify, given in
* unmagnified coordinates.
* @param {{x: number, y: number, width: number, height: number}} viewPort:
* Object that defines the position of the ZoomRegion on screen.
* @returns {ZoomRegion} the newly created ZoomRegion.
*/
createZoomRegion(xMagFactor, yMagFactor, roi, viewPort) {
let zoomRegion = new ZoomRegion(this, this._cursorRoot);
zoomRegion.setViewPort(viewPort);
// We ignore the redundant width/height on the ROI
let fixedROI = new Object(roi);
let fixedROI = Object.create(roi);
fixedROI.width = viewPort.width / xMagFactor;
fixedROI.height = viewPort.height / yMagFactor;
zoomRegion.setROI(fixedROI);
@ -279,7 +277,7 @@ var Magnifier = class Magnifier {
* addZoomRegion:
* Append the given ZoomRegion to the list of currently defined ZoomRegions
* for this Magnifier instance.
* @zoomRegion: The zoomRegion to add.
* @param {ZoomRegion} zoomRegion: The zoomRegion to add.
*/
addZoomRegion(zoomRegion) {
if (zoomRegion) {
@ -292,7 +290,7 @@ var Magnifier = class Magnifier {
/**
* getZoomRegions:
* Return a list of ZoomRegion's for this Magnifier.
* @return: The Magnifier's zoom region list (array).
* @returns {number[]} The Magnifier's zoom region list.
*/
getZoomRegions() {
return this._zoomRegions;
@ -332,7 +330,7 @@ var Magnifier = class Magnifier {
this.setCrosshairsClip(clip);
let theCrossHairs = this._crossHairs;
this._zoomRegions.forEach ((zoomRegion, index, array) => {
this._zoomRegions.forEach(zoomRegion => {
zoomRegion.addCrosshairs(theCrossHairs);
});
}
@ -340,7 +338,7 @@ var Magnifier = class Magnifier {
/**
* setCrosshairsVisible:
* Show or hide the cross hair.
* @visible Flag that indicates show (true) or hide (false).
* @param {bool} visible: Flag that indicates show (true) or hide (false).
*/
setCrosshairsVisible(visible) {
if (visible) {
@ -348,6 +346,7 @@ var Magnifier = class Magnifier {
this.addCrosshairs();
this._crossHairs.show();
} else {
// eslint-disable-next-line no-lonely-if
if (this._crossHairs)
this._crossHairs.hide();
}
@ -356,11 +355,11 @@ var Magnifier = class Magnifier {
/**
* setCrosshairsColor:
* Set the color of the crosshairs for all ZoomRegions.
* @color: The color as a string, e.g. '#ff0000ff' or 'red'.
* @param {string} color: The color as a string, e.g. '#ff0000ff' or 'red'.
*/
setCrosshairsColor(color) {
if (this._crossHairs) {
let [res, clutterColor] = Clutter.Color.from_string(color);
let [res_, clutterColor] = Clutter.Color.from_string(color);
this._crossHairs.setColor(clutterColor);
}
}
@ -368,7 +367,7 @@ var Magnifier = class Magnifier {
/**
* getCrosshairsColor:
* Get the color of the crosshairs.
* @return: The color as a string, e.g. '#0000ffff' or 'blue'.
* @returns {string} The color as a string, e.g. '#0000ffff' or 'blue'.
*/
getCrosshairsColor() {
if (this._crossHairs) {
@ -382,8 +381,8 @@ var Magnifier = class Magnifier {
/**
* setCrosshairsThickness:
* Set the crosshairs thickness for all ZoomRegions.
* @thickness: The width of the vertical and horizontal lines of the
* crosshairs.
* @param {number} thickness: The width of the vertical and
* horizontal lines of the crosshairs.
*/
setCrosshairsThickness(thickness) {
if (this._crossHairs)
@ -393,8 +392,8 @@ var Magnifier = class Magnifier {
/**
* getCrosshairsThickness:
* Get the crosshairs thickness.
* @return: The width of the vertical and horizontal lines of the
* crosshairs.
* @returns {number} The width of the vertical and horizontal
* lines of the crosshairs.
*/
getCrosshairsThickness() {
if (this._crossHairs)
@ -405,7 +404,8 @@ var Magnifier = class Magnifier {
/**
* setCrosshairsOpacity:
* @opacity: Value between 0.0 (transparent) and 1.0 (fully opaque).
* @param {number} opacity: Value between 0.0 (transparent)
* and 1.0 (fully opaque).
*/
setCrosshairsOpacity(opacity) {
if (this._crossHairs)
@ -414,7 +414,7 @@ var Magnifier = class Magnifier {
/**
* getCrosshairsOpacity:
* @return: Value between 0.0 (transparent) and 1.0 (fully opaque).
* @returns {number} Value between 0.0 (transparent) and 1.0 (fully opaque).
*/
getCrosshairsOpacity() {
if (this._crossHairs)
@ -426,8 +426,8 @@ var Magnifier = class Magnifier {
/**
* setCrosshairsLength:
* Set the crosshairs length for all ZoomRegions.
* @length: The length of the vertical and horizontal lines making up the
* crosshairs.
* @param {number} length: The length of the vertical and horizontal
* lines making up the crosshairs.
*/
setCrosshairsLength(length) {
if (this._crossHairs)
@ -437,8 +437,8 @@ var Magnifier = class Magnifier {
/**
* getCrosshairsLength:
* Get the crosshairs length.
* @return: The length of the vertical and horizontal lines making up the
* crosshairs.
* @returns {number} The length of the vertical and horizontal
* lines making up the crosshairs.
*/
getCrosshairsLength() {
if (this._crossHairs)
@ -450,35 +450,31 @@ var Magnifier = class Magnifier {
/**
* setCrosshairsClip:
* Set whether the crosshairs are clipped at their intersection.
* @clip: Flag to indicate whether to clip the crosshairs.
* @param {bool} clip: Flag to indicate whether to clip the crosshairs.
*/
setCrosshairsClip(clip) {
if (clip) {
if (this._crossHairs)
this._crossHairs.setClip(CROSSHAIRS_CLIP_SIZE);
} else {
// Setting no clipping on crosshairs means a zero sized clip
// rectangle.
if (this._crossHairs)
this._crossHairs.setClip([0, 0]);
}
if (!this._crossHairs)
return;
// Setting no clipping on crosshairs means a zero sized clip rectangle.
this._crossHairs.setClip(clip ? CROSSHAIRS_CLIP_SIZE : [0, 0]);
}
/**
* getCrosshairsClip:
* Get whether the crosshairs are clipped by the mouse image.
* @return: Whether the crosshairs are clipped.
* @returns {bool} Whether the crosshairs are clipped.
*/
getCrosshairsClip() {
if (this._crossHairs) {
let [clipWidth, clipHeight] = this._crossHairs.getClip();
return (clipWidth > 0 && clipHeight > 0);
return clipWidth > 0 && clipHeight > 0;
} else {
return false;
}
}
//// Private methods ////
// Private methods //
_updateMouseSprite() {
this._updateSpriteTexture();
@ -629,9 +625,8 @@ var Magnifier = class Magnifier {
_updateLensMode() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
if (this._zoomRegions.length)
this._zoomRegions[0].setLensMode(this._settings.get_boolean(LENS_MODE_KEY));
}
}
_updateClampMode() {
@ -778,15 +773,16 @@ var ZoomRegion = class ZoomRegion {
}
_updateScreenPosition() {
if (this._screenPosition == GDesktopEnums.MagnifierScreenPosition.NONE)
if (this._screenPosition == GDesktopEnums.MagnifierScreenPosition.NONE) {
this._setViewPort({
x: this._viewPortX,
y: this._viewPortY,
width: this._viewPortWidth,
height: this._viewPortHeight
height: this._viewPortHeight,
});
else
} else {
this.setScreenPosition(this._screenPosition);
}
}
_updateFocus(caller, event) {
@ -824,7 +820,7 @@ var ZoomRegion = class ZoomRegion {
/**
* setActive:
* @activate: Boolean to show/hide the ZoomRegion.
* @param {bool} activate: Boolean to show/hide the ZoomRegion.
*/
setActive(activate) {
if (activate == this.isActive())
@ -850,7 +846,7 @@ var ZoomRegion = class ZoomRegion {
/**
* isActive:
* @return Whether this ZoomRegion is active (boolean).
* @returns {bool} Whether this ZoomRegion is active
*/
isActive() {
return this._magView != null;
@ -858,24 +854,24 @@ var ZoomRegion = class ZoomRegion {
/**
* setMagFactor:
* @xMagFactor: The power to set the horizontal magnification factor to
* of the magnified view. A value of 1.0 means no
* magnification. A value of 2.0 doubles the size.
* @yMagFactor: The power to set the vertical magnification factor to
* of the magnified view.
* @param {number} xMagFactor: The power to set the horizontal
* magnification factor to of the magnified view. A value of 1.0
* means no magnification. A value of 2.0 doubles the size.
* @param {number} yMagFactor: The power to set the vertical
* magnification factor to of the magnified view.
*/
setMagFactor(xMagFactor, yMagFactor) {
this._changeROI({ xMagFactor: xMagFactor,
yMagFactor: yMagFactor,
this._changeROI({ xMagFactor,
yMagFactor,
redoCursorTracking: this._followingCursor });
}
/**
* getMagFactor:
* @return an array, [xMagFactor, yMagFactor], containing the horizontal
* and vertical magnification powers. A value of 1.0 means no
* magnification. A value of 2.0 means the contents are doubled
* in size, and so on.
* @returns {number[]} an array, [xMagFactor, yMagFactor], containing
* the horizontal and vertical magnification powers. A value of
* 1.0 means no magnification. A value of 2.0 means the contents
* are doubled in size, and so on.
*/
getMagFactor() {
return [this._xMagFactor, this._yMagFactor];
@ -883,7 +879,7 @@ var ZoomRegion = class ZoomRegion {
/**
* setMouseTrackingMode
* @mode: One of the enum MouseTrackingMode values.
* @param {GDesktopEnums.MagnifierMouseTrackingMode} mode: the new mode
*/
setMouseTrackingMode(mode) {
if (mode >= GDesktopEnums.MagnifierMouseTrackingMode.NONE &&
@ -893,7 +889,7 @@ var ZoomRegion = class ZoomRegion {
/**
* getMouseTrackingMode
* @return: One of the enum MouseTrackingMode values.
* @returns {GDesktopEnums.MagnifierMouseTrackingMode} the current mode
*/
getMouseTrackingMode() {
return this._mouseTrackingMode;
@ -901,7 +897,7 @@ var ZoomRegion = class ZoomRegion {
/**
* setFocusTrackingMode
* @mode: One of the enum FocusTrackingMode values.
* @param {GDesktopEnums.MagnifierFocusTrackingMode} mode: the new mode
*/
setFocusTrackingMode(mode) {
this._focusTrackingMode = mode;
@ -910,7 +906,7 @@ var ZoomRegion = class ZoomRegion {
/**
* setCaretTrackingMode
* @mode: One of the enum CaretTrackingMode values.
* @param {GDesktopEnums.MagnifierCaretTrackingMode} mode: the new mode
*/
setCaretTrackingMode(mode) {
this._caretTrackingMode = mode;
@ -940,9 +936,9 @@ var ZoomRegion = class ZoomRegion {
/**
* setViewPort
* Sets the position and size of the ZoomRegion on screen.
* @viewPort: Object defining the position and size of the view port.
* It has members x, y, width, height. The values are in
* stage coordinate space.
* @param {{x: number, y: number, width: number, height: number}} viewPort:
* Object defining the position and size of the view port.
* The values are in stage coordinate space.
*/
setViewPort(viewPort) {
this._setViewPort(viewPort);
@ -952,9 +948,9 @@ var ZoomRegion = class ZoomRegion {
/**
* setROI
* Sets the "region of interest" that the ZoomRegion is magnifying.
* @roi: Object that defines the region of the screen to magnify. It
* has members x, y, width, height. The values are in
* screen (unmagnified) coordinate space.
* @param {{x: number, y: number, width: number, height: number}} roi:
* Object that defines the region of the screen to magnify.
* The values are in screen (unmagnified) coordinate space.
*/
setROI(roi) {
if (roi.width <= 0 || roi.height <= 0)
@ -972,8 +968,8 @@ var ZoomRegion = class ZoomRegion {
* Retrieves the "region of interest" -- the rectangular bounds of that part
* of the desktop that the magnified view is showing (x, y, width, height).
* The bounds are given in non-magnified coordinates.
* @return an array, [x, y, width, height], representing the bounding
* rectangle of what is shown in the magnified view.
* @returns {number[]} an array, [x, y, width, height], representing
* the bounding rectangle of what is shown in the magnified view.
*/
getROI() {
let roiWidth = this._viewPortWidth / this._xMagFactor;
@ -988,18 +984,18 @@ var ZoomRegion = class ZoomRegion {
* setLensMode:
* Turn lens mode on/off. In full screen mode, lens mode does nothing since
* a lens the size of the screen is pointless.
* @lensMode: A boolean to set the sense of lens mode.
* @param {bool} lensMode: Whether lensMode should be active
*/
setLensMode(lensMode) {
this._lensMode = lensMode;
if (!this._lensMode)
this.setScreenPosition (this._screenPosition);
this.setScreenPosition(this._screenPosition);
}
/**
* isLensMode:
* Is lens mode on or off?
* @return The lens mode state as a boolean.
* @returns {bool} The lens mode state.
*/
isLensMode() {
return this._lensMode;
@ -1009,7 +1005,7 @@ var ZoomRegion = class ZoomRegion {
* setClampScrollingAtEdges:
* Stop vs. allow scrolling of the magnified contents when it scroll beyond
* the edges of the screen.
* @clamp: Boolean to turn on/off clamping.
* @param {bool} clamp: Boolean to turn on/off clamping.
*/
setClampScrollingAtEdges(clamp) {
this._clampScrollingAtEdges = clamp;
@ -1093,9 +1089,7 @@ var ZoomRegion = class ZoomRegion {
* setScreenPosition:
* Positions the zoom region to one of the enumerated positions on the
* screen.
* @position: one of Magnifier.FULL_SCREEN, Magnifier.TOP_HALF,
* Magnifier.BOTTOM_HALF,Magnifier.LEFT_HALF, or
* Magnifier.RIGHT_HALF.
* @param {GDesktopEnums.MagnifierScreenPosition} inPosition: the position
*/
setScreenPosition(inPosition) {
switch (inPosition) {
@ -1121,7 +1115,7 @@ var ZoomRegion = class ZoomRegion {
* getScreenPosition:
* Tell the outside world what the current mode is -- magnifiying the
* top half, bottom half, etc.
* @return: the current mode.
* @returns {GDesktopEnums.MagnifierScreenPosition}: the current position.
*/
getScreenPosition() {
return this._screenPosition;
@ -1130,7 +1124,8 @@ var ZoomRegion = class ZoomRegion {
/**
* scrollToMousePos:
* Set the region of interest based on the position of the system pointer.
* @return: Whether the system mouse pointer is over the magnified view.
* @returns {bool}: Whether the system mouse pointer is over the
* magnified view.
*/
scrollToMousePos() {
this._followingCursor = true;
@ -1145,7 +1140,7 @@ var ZoomRegion = class ZoomRegion {
_clearScrollContentsTimer() {
if (this._scrollContentsTimerId != 0) {
Mainloop.source_remove(this._scrollContentsTimerId);
GLib.source_remove(this._scrollContentsTimerId);
this._scrollContentsTimerId = 0;
}
}
@ -1157,7 +1152,7 @@ var ZoomRegion = class ZoomRegion {
}
this._clearScrollContentsTimer();
this._scrollContentsTimerId = Mainloop.timeout_add(POINTER_REST_TIME, () => {
this._scrollContentsTimerId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, POINTER_REST_TIME, () => {
this._scrollContentsToDelayed(x, y);
return GLib.SOURCE_REMOVE;
});
@ -1167,8 +1162,8 @@ var ZoomRegion = class ZoomRegion {
* scrollContentsTo:
* Shift the contents of the magnified view such it is centered on the given
* coordinate.
* @x: The x-coord of the point to center on.
* @y: The y-coord of the point to center on.
* @param {number} x: The x-coord of the point to center on.
* @param {number} y: The y-coord of the point to center on.
*/
scrollContentsTo(x, y) {
this._clearScrollContentsTimer();
@ -1181,22 +1176,21 @@ var ZoomRegion = class ZoomRegion {
/**
* addCrosshairs:
* Add crosshairs centered on the magnified mouse.
* @crossHairs: Crosshairs instance
* @param {Crosshairs} crossHairs: Crosshairs instance
*/
addCrosshairs(crossHairs) {
this._crossHairs = crossHairs;
// If the crossHairs is not already within a larger container, add it
// to this zoom region. Otherwise, add a clone.
if (crossHairs && this.isActive()) {
if (crossHairs && this.isActive())
this._crossHairsActor = crossHairs.addToZoomRegion(this, this._mouseActor);
}
}
/**
* setInvertLightness:
* Set whether to invert the lightness of the magnified view.
* @flag Boolean to either invert brightness (true), or not (false).
* @param {bool} flag: whether brightness should be inverted
*/
setInvertLightness(flag) {
this._invertLightness = flag;
@ -1207,7 +1201,7 @@ var ZoomRegion = class ZoomRegion {
/**
* getInvertLightness:
* Retrieve whether the lightness is inverted.
* @return Boolean indicating inversion (true), or not (false).
* @returns {bool} whether brightness should be inverted
*/
getInvertLightness() {
return this._invertLightness;
@ -1216,9 +1210,9 @@ var ZoomRegion = class ZoomRegion {
/**
* setColorSaturation:
* Set the color saturation of the magnified view.
* @sauration A value from 0.0 to 1.0 that defines the color
* saturation, with 0.0 defining no color (grayscale),
* and 1.0 defining full color.
* @param {number} saturation: A value from 0.0 to 1.0 that defines
* the color saturation, with 0.0 defining no color (grayscale),
* and 1.0 defining full color.
*/
setColorSaturation(saturation) {
this._colorSaturation = saturation;
@ -1229,6 +1223,7 @@ var ZoomRegion = class ZoomRegion {
/**
* getColorSaturation:
* Retrieve the color saturation of the magnified view.
* @returns {number} the color saturation
*/
getColorSaturation() {
return this._colorSaturation;
@ -1237,10 +1232,14 @@ var ZoomRegion = class ZoomRegion {
/**
* setBrightness:
* Alter the brightness of the magnified view.
* @brightness Object containing the contrast for the red, green,
* and blue channels. Values of 0.0 represent "standard"
* brightness (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed brightness, respectively.
* @param {Object} brightness: Object containing the contrast for the
* red, green, and blue channels. Values of 0.0 represent "standard"
* brightness (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed brightness, respectively.
*
* {number} brightness.r - the red component
* {number} brightness.g - the green component
* {number} brightness.b - the blue component
*/
setBrightness(brightness) {
this._brightness.r = brightness.r;
@ -1253,10 +1252,14 @@ var ZoomRegion = class ZoomRegion {
/**
* setContrast:
* Alter the contrast of the magnified view.
* @contrast Object containing the contrast for the red, green,
* and blue channels. Values of 0.0 represent "standard"
* contrast (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed contrast, respectively.
* @param {Object} contrast: Object containing the contrast for the
* red, green, and blue channels. Values of 0.0 represent "standard"
* contrast (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed contrast, respectively.
*
* {number} contrast.r - the red component
* {number} contrast.g - the green component
* {number} contrast.b - the blue component
*/
setContrast(contrast) {
this._contrast.r = contrast.r;
@ -1269,8 +1272,8 @@ var ZoomRegion = class ZoomRegion {
/**
* getContrast:
* Retrieve the contrast of the magnified view.
* @return Object containing the contrast for the red, green,
* and blue channels.
* @returns {{r: number, g: number, b: number}}: Object containing
* the contrast for the red, green, and blue channels.
*/
getContrast() {
let contrast = {};
@ -1280,15 +1283,15 @@ var ZoomRegion = class ZoomRegion {
return contrast;
}
//// Private methods ////
// Private methods //
_createActors() {
// The root actor for the zoom region
this._magView = new St.Bin({ style_class: 'magnifier-zoom-region', x_fill: true, y_fill: true });
this._magView = new St.Bin({ style_class: 'magnifier-zoom-region' });
global.stage.add_actor(this._magView);
// hide the magnified region from CLUTTER_PICK_ALL
Shell.util_set_hidden_from_pick (this._magView, true);
Shell.util_set_hidden_from_pick(this._magView, true);
// Add a group to clip the contents of the magnified view.
let mainGroup = new Clutter.Actor({ clip_to_allocation: true });
@ -1296,7 +1299,7 @@ var ZoomRegion = class ZoomRegion {
// Add a background for when the magnified uiGroup is scrolled
// out of view (don't want to see desktop showing through).
this._background = (new Background.SystemBackground()).actor;
this._background = new Background.SystemBackground();
mainGroup.add_actor(this._background);
// Clone the group that contains all of UI on the screen. This is the
@ -1328,7 +1331,7 @@ var ZoomRegion = class ZoomRegion {
_destroyActors() {
if (this._mouseActor == this._mouseSourceActor)
this._mouseActor.get_parent().remove_actor (this._mouseActor);
this._mouseActor.get_parent().remove_actor(this._mouseActor);
if (this._crossHairs)
this._crossHairs.removeFromParent(this._crossHairsActor);
@ -1408,11 +1411,12 @@ var ZoomRegion = class ZoomRegion {
// If in lens mode, move the magnified view such that it is centered
// over the actual mouse. However, in full screen mode, the "lens" is
// the size of the screen -- pointless to move such a large lens around.
if (this._lensMode && !this._isFullScreen())
if (this._lensMode && !this._isFullScreen()) {
this._setViewPort({ x: this._xCenter - this._viewPortWidth / 2,
y: this._yCenter - this._viewPortHeight / 2,
width: this._viewPortWidth,
height: this._viewPortHeight }, true);
}
this._updateCloneGeometry();
this._updateMousePosition();
@ -1426,10 +1430,9 @@ var ZoomRegion = class ZoomRegion {
let xMouse = this._magnifier.xMouse;
let yMouse = this._magnifier.yMouse;
mouseIsOver = (
mouseIsOver =
xMouse >= this._viewPortX && xMouse < (this._viewPortX + this._viewPortWidth) &&
yMouse >= this._viewPortY && yMouse < (this._viewPortY + this._viewPortHeight)
);
yMouse >= this._viewPortY && yMouse < (this._viewPortY + this._viewPortHeight);
}
return mouseIsOver;
}
@ -1454,13 +1457,12 @@ var ZoomRegion = class ZoomRegion {
let xMouse = this._magnifier.xMouse;
let yMouse = this._magnifier.yMouse;
if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) {
if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL)
return this._centerFromPointProportional(xMouse, yMouse);
} else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) {
else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH)
return this._centerFromPointPush(xMouse, yMouse);
} else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) {
else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED)
return this._centerFromPointCentered(xMouse, yMouse);
}
return null; // Should never be hit
}
@ -1502,20 +1504,20 @@ var ZoomRegion = class ZoomRegion {
let yRoiBottom = yRoi + heightRoi - cursorHeight;
if (xPoint < xRoi)
xPos -= (xRoi - xPoint);
xPos -= xRoi - xPoint;
else if (xPoint > xRoiRight)
xPos += (xPoint - xRoiRight);
xPos += xPoint - xRoiRight;
if (yPoint < yRoi)
yPos -= (yRoi - yPoint);
yPos -= yRoi - yPoint;
else if (yPoint > yRoiBottom)
yPos += (yPoint - yRoiBottom);
yPos += yPoint - yRoiBottom;
return [xPos, yPos];
}
_centerFromPointProportional(xPoint, yPoint) {
let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
let [xRoi_, yRoi_, widthRoi, heightRoi] = this.getROI();
let halfScreenWidth = global.screen_width / 2;
let halfScreenHeight = global.screen_height / 2;
// We want to pad with a constant distance after zooming, so divide
@ -1593,8 +1595,9 @@ var ZoomRegion = class ZoomRegion {
}
};
var Crosshairs = class Crosshairs {
constructor() {
var Crosshairs = GObject.registerClass(
class Crosshairs extends Clutter.Actor {
_init() {
// Set the group containing the crosshairs to three times the desktop
// size in case the crosshairs need to appear to be infinite in
@ -1602,19 +1605,19 @@ var Crosshairs = class Crosshairs {
let groupWidth = global.screen_width * 3;
let groupHeight = global.screen_height * 3;
this._actor = new Clutter.Actor({
super._init({
clip_to_allocation: false,
width: groupWidth,
height: groupHeight
height: groupHeight,
});
this._horizLeftHair = new Clutter.Actor();
this._horizRightHair = new Clutter.Actor();
this._vertTopHair = new Clutter.Actor();
this._vertBottomHair = new Clutter.Actor();
this._actor.add_actor(this._horizLeftHair);
this._actor.add_actor(this._horizRightHair);
this._actor.add_actor(this._vertTopHair);
this._actor.add_actor(this._vertBottomHair);
this.add_actor(this._horizLeftHair);
this.add_actor(this._horizRightHair);
this.add_actor(this._vertTopHair);
this.add_actor(this._vertBottomHair);
this._clipSize = [0, 0];
this._clones = [];
this.reCenter();
@ -1624,7 +1627,7 @@ var Crosshairs = class Crosshairs {
}
_monitorsChanged() {
this._actor.set_size(global.screen_width * 3, global.screen_height * 3);
this.set_size(global.screen_width * 3, global.screen_height * 3);
this.reCenter();
}
@ -1634,26 +1637,30 @@ var Crosshairs = class Crosshairs {
* already part of some other ZoomRegion, create a clone of the crosshairs
* actor, and add the clone instead. Returns either the original or the
* clone.
* @zoomRegion: The container to add the crosshairs group to.
* @magnifiedMouse: The mouse actor for the zoom region -- used to
* position the crosshairs and properly layer them below
* the mouse.
* @return The crosshairs actor, or its clone.
* @param {ZoomRegion} zoomRegion: The container to add the crosshairs
* group to.
* @param {Clutter.Actor} magnifiedMouse: The mouse actor for the
* zoom region -- used to position the crosshairs and properly
* layer them below the mouse.
* @returns {Clutter.Actor} The crosshairs actor, or its clone.
*/
addToZoomRegion(zoomRegion, magnifiedMouse) {
let crosshairsActor = null;
if (zoomRegion && magnifiedMouse) {
let container = magnifiedMouse.get_parent();
if (container) {
crosshairsActor = this._actor;
if (this._actor.get_parent() != null) {
crosshairsActor = new Clutter.Clone({ source: this._actor });
crosshairsActor = this;
if (this.get_parent() != null) {
crosshairsActor = new Clutter.Clone({ source: this });
this._clones.push(crosshairsActor);
// Clones don't share visibility.
this.bind_property('visible', crosshairsActor, 'visible',
GObject.BindingFlags.SYNC_CREATE);
}
crosshairsActor.visible = this._actor.visible;
container.add_actor(crosshairsActor);
container.raise_child(magnifiedMouse, crosshairsActor);
container.set_child_above_sibling(magnifiedMouse, crosshairsActor);
let [xMouse, yMouse] = magnifiedMouse.get_position();
let [crosshairsWidth, crosshairsHeight] = crosshairsActor.get_size();
crosshairsActor.set_position(xMouse - crosshairsWidth / 2, yMouse - crosshairsHeight / 2);
@ -1664,12 +1671,13 @@ var Crosshairs = class Crosshairs {
/**
* removeFromParent:
* @childActor: the actor returned from addToZoomRegion
* @param {Clutter.Actor} childActor: the actor returned from
* addToZoomRegion
* Remove the crosshairs actor from its parent container, or destroy the
* child actor if it was just a clone of the crosshairs actor.
*/
removeFromParent(childActor) {
if (childActor == this._actor)
if (childActor == this)
childActor.get_parent().remove_actor(childActor);
else
childActor.destroy();
@ -1678,7 +1686,7 @@ var Crosshairs = class Crosshairs {
/**
* setColor:
* Set the color of the crosshairs.
* @clutterColor: The color as a Clutter.Color.
* @param {Clutter.Color} clutterColor: The color
*/
setColor(clutterColor) {
this._horizLeftHair.background_color = clutterColor;
@ -1690,7 +1698,7 @@ var Crosshairs = class Crosshairs {
/**
* getColor:
* Get the color of the crosshairs.
* @color: The color as a Clutter.Color.
* @returns {ClutterColor} the crosshairs color
*/
getColor() {
return this._horizLeftHair.get_color();
@ -1699,7 +1707,7 @@ var Crosshairs = class Crosshairs {
/**
* setThickness:
* Set the width of the vertical and horizontal lines of the crosshairs.
* @thickness
* @param {number} thickness: the new thickness value
*/
setThickness(thickness) {
this._horizLeftHair.set_height(thickness);
@ -1712,7 +1720,7 @@ var Crosshairs = class Crosshairs {
/**
* getThickness:
* Get the width of the vertical and horizontal lines of the crosshairs.
* @return: The thickness of the crosshairs.
* @returns {number} The thickness of the crosshairs.
*/
getThickness() {
return this._horizLeftHair.get_height();
@ -1721,7 +1729,8 @@ var Crosshairs = class Crosshairs {
/**
* setOpacity:
* Set how opaque the crosshairs are.
* @opacity: Value between 0 (fully transparent) and 255 (full opaque).
* @param {number} opacity: Value between 0 (fully transparent)
* and 255 (full opaque).
*/
setOpacity(opacity) {
// set_opacity() throws an exception for values outside the range
@ -1740,7 +1749,7 @@ var Crosshairs = class Crosshairs {
/**
* setLength:
* Set the length of the vertical and horizontal lines in the crosshairs.
* @length: The length of the crosshairs.
* @param {number} length: The length of the crosshairs.
*/
setLength(length) {
this._horizLeftHair.set_width(length);
@ -1753,7 +1762,7 @@ var Crosshairs = class Crosshairs {
/**
* getLength:
* Get the length of the vertical and horizontal lines in the crosshairs.
* @return: The length of the crosshairs.
* @returns {number} The length of the crosshairs.
*/
getLength() {
return this._horizLeftHair.get_width();
@ -1763,8 +1772,8 @@ var Crosshairs = class Crosshairs {
* setClip:
* Set the width and height of the rectangle that clips the crosshairs at
* their intersection
* @size: Array of [width, height] defining the size of the clip
* rectangle.
* @param {number[]} size: Array of [width, height] defining the size
* of the clip rectangle.
*/
setClip(size) {
if (size) {
@ -1779,37 +1788,15 @@ var Crosshairs = class Crosshairs {
}
}
/**
* show:
* Show the crosshairs.
*/
show() {
this._actor.show();
// Clones don't share visibility.
for (let i = 0; i < this._clones.length; i++)
this._clones[i].show();
}
/**
* hide:
* Hide the crosshairs.
*/
hide() {
this._actor.hide();
// Clones don't share visibility.
for (let i = 0; i < this._clones.length; i++)
this._clones[i].hide();
}
/**
* reCenter:
* Reposition the horizontal and vertical hairs such that they cross at
* the center of crosshairs group. If called with the dimensions of
* the clip rectangle, these are used to update the size of the clip.
* @clipSize: Optional. If present, an array of the form [width, height].
* @param {number[]=} clipSize: If present, the clip's [width, height].
*/
reCenter(clipSize) {
let [groupWidth, groupHeight] = this._actor.get_size();
let [groupWidth, groupHeight] = this.get_size();
let leftLength = this._horizLeftHair.get_width();
let topLength = this._vertTopHair.get_height();
let thickness = this._horizLeftHair.get_height();
@ -1831,7 +1818,7 @@ var Crosshairs = class Crosshairs {
this._vertTopHair.set_position((groupWidth - thickness) / 2, top);
this._vertBottomHair.set_position((groupWidth - thickness) / 2, bottom);
}
};
});
var MagShaderEffects = class MagShaderEffects {
constructor(uiGroupClone) {
@ -1864,7 +1851,7 @@ var MagShaderEffects = class MagShaderEffects {
/**
* setInvertLightness:
* Enable/disable invert lightness effect.
* @invertFlag: Enabled flag.
* @param {bool} invertFlag: Enabled flag.
*/
setInvertLightness(invertFlag) {
this._inverse.set_enabled(invertFlag);
@ -1877,11 +1864,14 @@ var MagShaderEffects = class MagShaderEffects {
/**
* setBrightness:
* Set the brightness of the magnified view.
* @brightness: Object containing the brightness for the red, green,
* and blue channels. Values of 0.0 represent "standard"
* brightness (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed brightness,
* respectively.
* @param {Object} brightness: Object containing the contrast for the
* red, green, and blue channels. Values of 0.0 represent "standard"
* brightness (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed brightness, respectively.
*
* {number} brightness.r - the red component
* {number} brightness.g - the green component
* {number} brightness.b - the blue component
*/
setBrightness(brightness) {
let bRed = brightness.r;
@ -1893,17 +1883,21 @@ var MagShaderEffects = class MagShaderEffects {
// it modifies the brightness and/or contrast.
let [cRed, cGreen, cBlue] = this._brightnessContrast.get_contrast();
this._brightnessContrast.set_enabled(
(bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != NO_CHANGE ||
cRed != NO_CHANGE || cGreen != NO_CHANGE || cBlue != NO_CHANGE)
bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != NO_CHANGE ||
cRed != NO_CHANGE || cGreen != NO_CHANGE || cBlue != NO_CHANGE
);
}
/**
* Set the contrast of the magnified view.
* @contrast: Object containing the contrast for the red, green,
* and blue channels. Values of 0.0 represent "standard"
* contrast (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed contrast, respectively.
* @param {Object} contrast: Object containing the contrast for the
* red, green, and blue channels. Values of 0.0 represent "standard"
* contrast (no change), whereas values less or greater than
* 0.0 indicate decreased or incresaed contrast, respectively.
*
* {number} contrast.r - the red component
* {number} contrast.g - the green component
* {number} contrast.b - the blue component
*/
setContrast(contrast) {
let cRed = contrast.r;

View File

@ -1,4 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ShellMagnifier */
const Gio = imports.gi.Gio;
const Main = imports.ui.main;
@ -31,7 +32,7 @@ var ShellMagnifier = class ShellMagnifier {
/**
* setActive:
* @activate: Boolean to activate or de-activate the magnifier.
* @param {bool} activate: activate or de-activate the magnifier.
*/
setActive(activate) {
Main.magnifier.setActive(activate);
@ -39,7 +40,7 @@ var ShellMagnifier = class ShellMagnifier {
/**
* isActive:
* @return Whether the magnifier is active (boolean).
* @returns {bool} Whether the magnifier is active.
*/
isActive() {
return Main.magnifier.isActive();
@ -64,22 +65,25 @@ var ShellMagnifier = class ShellMagnifier {
/**
* createZoomRegion:
* Create a new ZoomRegion and return its object path.
* @xMagFactor: The power to set horizontal magnification of the
* ZoomRegion. A value of 1.0 means no magnification. A
* value of 2.0 doubles the size.
* @yMagFactor: The power to set the vertical magnification of the
* ZoomRegion.
* @roi Array of integers defining the region of the
* screen/desktop to magnify. The array has the form
* [left, top, right, bottom].
* @viewPort Array of integers, [left, top, right, bottom] that defines
* the position of the ZoomRegion on screen.
* @param {number} xMagFactor:
* The power to set horizontal magnification of the ZoomRegion.
* A value of 1.0 means no magnification. A value of 2.0 doubles
* the size.
* @param {number} yMagFactor:
* The power to set the vertical magnification of the
* ZoomRegion.
* @param {number[]} roi
* Array of integers defining the region of the screen/desktop
* to magnify. The array has the form [left, top, right, bottom].
* @param {number[]} viewPort
* Array of integers, [left, top, right, bottom] that defines
* the position of the ZoomRegion on screen.
*
* FIXME: The arguments here are redundant, since the width and height of
* the ROI are determined by the viewport and magnification factors.
* We ignore the passed in width and height.
*
* @return The newly created ZoomRegion.
* @returns {ZoomRegion} The newly created ZoomRegion.
*/
createZoomRegion(xMagFactor, yMagFactor, roi, viewPort) {
let ROI = { x: roi[0], y: roi[1], width: roi[2] - roi[0], height: roi[3] - roi[1] };
@ -99,7 +103,9 @@ var ShellMagnifier = class ShellMagnifier {
/**
* addZoomRegion:
* Append the given ZoomRegion to the magnifier's list of ZoomRegions.
* @zoomerObjectPath: The object path for the zoom region proxy.
* @param {string} zoomerObjectPath: The object path for the zoom
* region proxy.
* @returns {bool} whether the region was added successfully
*/
addZoomRegion(zoomerObjectPath) {
let proxyAndZoomRegion = this._zoomers[zoomerObjectPath];
@ -114,8 +120,8 @@ var ShellMagnifier = class ShellMagnifier {
/**
* getZoomRegions:
* Return a list of ZoomRegion object paths for this Magnifier.
* @return: The Magnifier's zoom region list as an array of DBus object
* paths.
* @returns {string[]}: The Magnifier's zoom region list as an array
* of DBus object paths.
*/
getZoomRegions() {
// There may be more ZoomRegions in the magnifier itself than have
@ -124,7 +130,7 @@ var ShellMagnifier = class ShellMagnifier {
let zoomRegions = Main.magnifier.getZoomRegions();
let objectPaths = [];
let thoseZoomers = this._zoomers;
zoomRegions.forEach ((aZoomRegion, index, array) => {
zoomRegions.forEach(aZoomRegion => {
let found = false;
for (let objectPath in thoseZoomers) {
let proxyAndZoomRegion = thoseZoomers[objectPath];
@ -136,7 +142,7 @@ var ShellMagnifier = class ShellMagnifier {
}
if (!found) {
// Got a ZoomRegion with no DBus proxy, make one.
let newPath = ZOOM_SERVICE_PATH + '/zoomer' + _zoomRegionInstanceCount;
let newPath = `${ZOOM_SERVICE_PATH}/zoomer${_zoomRegionInstanceCount}`;
_zoomRegionInstanceCount++;
let zoomRegionProxy = new ShellMagnifierZoomRegion(newPath, aZoomRegion);
let proxyAndZoomer = {};
@ -168,7 +174,7 @@ var ShellMagnifier = class ShellMagnifier {
/**
* fullScreenCapable:
* Consult if the Magnifier can magnify in full-screen mode.
* @return Always return true.
* @returns {bool} Always return true.
*/
fullScreenCapable() {
return true;
@ -177,7 +183,7 @@ var ShellMagnifier = class ShellMagnifier {
/**
* setCrosswireSize:
* Set the crosswire size of all ZoomRegions.
* @size: The thickness of each line in the cross wire.
* @param {number} size: The thickness of each line in the cross wire.
*/
setCrosswireSize(size) {
Main.magnifier.setCrosshairsThickness(size);
@ -186,7 +192,7 @@ var ShellMagnifier = class ShellMagnifier {
/**
* getCrosswireSize:
* Get the crosswire size of all ZoomRegions.
* @return: The thickness of each line in the cross wire.
* @returns {number}: The thickness of each line in the cross wire.
*/
getCrosswireSize() {
return Main.magnifier.getCrosshairsThickness();
@ -195,16 +201,16 @@ var ShellMagnifier = class ShellMagnifier {
/**
* setCrosswireLength:
* Set the crosswire length of all zoom-regions..
* @size: The length of each line in the cross wire.
* @param {number} length: The length of each line in the cross wire.
*/
setCrosswireLength(length) {
Main.magnifier.setCrosshairsLength(length);
}
/**
* setCrosswireSize:
* Set the crosswire size of all zoom-regions.
* @size: The thickness of each line in the cross wire.
* getCrosswireSize:
* Get the crosswire length of all zoom-regions.
* @returns {number} size: The length of each line in the cross wire.
*/
getCrosswireLength() {
return Main.magnifier.getCrosshairsLength();
@ -213,7 +219,7 @@ var ShellMagnifier = class ShellMagnifier {
/**
* setCrosswireClip:
* Set if the crosswire will be clipped by the cursor image..
* @clip: Flag to indicate whether to clip the crosswire.
* @param {bool} clip: Flag to indicate whether to clip the crosswire.
*/
setCrosswireClip(clip) {
Main.magnifier.setCrosshairsClip(clip);
@ -222,7 +228,7 @@ var ShellMagnifier = class ShellMagnifier {
/**
* getCrosswireClip:
* Get the crosswire clip value.
* @return: Whether the crosswire is clipped by the cursor image.
* @returns {bool}: Whether the crosswire is clipped by the cursor image.
*/
getCrosswireClip() {
return Main.magnifier.getCrosshairsClip();
@ -231,7 +237,7 @@ var ShellMagnifier = class ShellMagnifier {
/**
* setCrosswireColor:
* Set the crosswire color of all ZoomRegions.
* @color: Unsigned int of the form rrggbbaa.
* @param {number} color: Unsigned int of the form rrggbbaa.
*/
setCrosswireColor(color) {
Main.magnifier.setCrosshairsColor('#%08x'.format(color));
@ -240,7 +246,8 @@ var ShellMagnifier = class ShellMagnifier {
/**
* getCrosswireClip:
* Get the crosswire color of all ZoomRegions.
* @return: The crosswire color as an unsigned int in the form rrggbbaa.
* @returns {number}: The crosswire color as an unsigned int in
* the form rrggbbaa.
*/
getCrosswireColor() {
let colorString = Main.magnifier.getCrosshairsColor();
@ -265,11 +272,11 @@ var ShellMagnifierZoomRegion = class ShellMagnifierZoomRegion {
/**
* setMagFactor:
* @xMagFactor: The power to set the horizontal magnification factor to
* of the magnified view. A value of 1.0 means no
* magnification. A value of 2.0 doubles the size.
* @yMagFactor: The power to set the vertical magnification factor to
* of the magnified view.
* @param {number} xMagFactor: The power to set the horizontal
* magnification factor to of the magnified view. A value of
* 1.0 means no magnification. A value of 2.0 doubles the size.
* @param {number} yMagFactor: The power to set the vertical
* magnification factor to of the magnified view.
*/
setMagFactor(xMagFactor, yMagFactor) {
this._zoomRegion.setMagFactor(xMagFactor, yMagFactor);
@ -277,7 +284,7 @@ var ShellMagnifierZoomRegion = class ShellMagnifierZoomRegion {
/**
* getMagFactor:
* @return an array, [xMagFactor, yMagFactor], containing the horizontal
* @returns {number[]}: [xMagFactor, yMagFactor], containing the horizontal
* and vertical magnification powers. A value of 1.0 means no
* magnification. A value of 2.0 means the contents are doubled
* in size, and so on.
@ -289,9 +296,9 @@ var ShellMagnifierZoomRegion = class ShellMagnifierZoomRegion {
/**
* setRoi:
* Sets the "region of interest" that the ZoomRegion is magnifying.
* @roi Array, [left, top, right, bottom], defining the region of the
* screen to magnify. The values are in screen (unmagnified)
* coordinate space.
* @param {number[]} roi: [left, top, right, bottom], defining the
* region of the screen to magnify.
* The values are in screen (unmagnified) coordinate space.
*/
setRoi(roi) {
let roiObject = { x: roi[0], y: roi[1], width: roi[2] - roi[0], height: roi[3] - roi[1] };
@ -303,7 +310,7 @@ var ShellMagnifierZoomRegion = class ShellMagnifierZoomRegion {
* Retrieves the "region of interest" -- the rectangular bounds of that part
* of the desktop that the magnified view is showing (x, y, width, height).
* The bounds are given in non-magnified coordinates.
* @return an array, [left, top, right, bottom], representing the bounding
* @returns {Array}: [left, top, right, bottom], representing the bounding
* rectangle of what is shown in the magnified view.
*/
getRoi() {
@ -316,10 +323,11 @@ var ShellMagnifierZoomRegion = class ShellMagnifierZoomRegion {
/**
* Set the "region of interest" by centering the given screen coordinate
* within the zoom region.
* @x The x-coord of the point to place at the center of the zoom region.
* @y The y-coord.
* @return Whether the shift was successful (for GS-mag, this is always
* true).
* @param {number} x: The x-coord of the point to place at the
* center of the zoom region.
* @param {number} y: The y-coord.
* @returns {bool} Whether the shift was successful (for GS-mag, this
* is always true).
*/
shiftContentsTo(x, y) {
this._zoomRegion.scrollContentsTo(x, y);
@ -329,8 +337,8 @@ var ShellMagnifierZoomRegion = class ShellMagnifierZoomRegion {
/**
* moveResize
* Sets the position and size of the ZoomRegion on screen.
* @viewPort Array, [left, top, right, bottom], defining the position and
* size on screen to place the zoom region.
* @param {number[]} viewPort: [left, top, right, bottom], defining
* the position and size on screen to place the zoom region.
*/
moveResize(viewPort) {
let viewRect = { x: viewPort[0], y: viewPort[1], width: viewPort[2] - viewPort[0], height: viewPort[3] - viewPort[1] };

View File

@ -1,7 +1,14 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported componentManager, notificationDaemon, windowAttentionHandler,
ctrlAltTabManager, padOsdService, osdWindowManager,
osdMonitorLabeler, shellMountOpDBusService, shellDBusService,
shellAccessDialogDBusService, shellAudioSelectionDBusService,
screenSaverDBus, screencastService, uiGroup, magnifier,
xdndHandler, keyboard, kbdA11yDialog, introspectService,
start, pushModal, popModal, activateWindow, createLookingGlass,
initializeDeferredWork, getThemeStylesheet, setThemeStylesheet */
const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const AccessDialog = imports.ui.accessDialog;
const AudioDeviceSelection = imports.ui.audioDeviceSelection;
@ -46,6 +53,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;
@ -181,7 +189,7 @@ function _initializeUI() {
messageTray = new MessageTray.MessageTray();
panel = new Panel.Panel();
keyboard = new Keyboard.Keyboard();
keyboard = new Keyboard.KeyboardManager();
notificationDaemon = new NotificationDaemon.NotificationDaemon();
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
componentManager = new Components.ComponentManager();
@ -191,12 +199,12 @@ function _initializeUI() {
layoutManager.init();
overview.init();
(new PointerA11yTimeout.PointerA11yTimeout());
new PointerA11yTimeout.PointerA11yTimeout();
_a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA });
global.display.connect('overlay-key', () => {
if (!_a11ySettings.get_boolean (STICKY_KEYS_ENABLE))
if (!_a11ySettings.get_boolean(STICKY_KEYS_ENABLE))
overview.toggle();
});
@ -221,12 +229,17 @@ function _initializeUI() {
EndSessionDialog.init();
// We're ready for the session manager to move to the next phase
Meta.register_with_session();
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Shell.util_sd_notify();
Meta.register_with_session();
return GLib.SOURCE_REMOVE;
});
_startDate = new Date();
ExtensionDownloader.init();
ExtensionSystem.init();
extensionManager = new ExtensionSystem.ExtensionManager();
extensionManager.init();
if (sessionMode.isGreeter && screenShield) {
layoutManager.connect('startup-prepared', () => {
@ -235,20 +248,33 @@ function _initializeUI() {
}
layoutManager.connect('startup-complete', () => {
if (actionMode == Shell.ActionMode.NONE) {
if (actionMode == Shell.ActionMode.NONE)
actionMode = Shell.ActionMode.NORMAL;
}
if (screenShield) {
if (screenShield)
screenShield.lockIfWasLocked();
}
if (sessionMode.currentMode != 'gdm' &&
sessionMode.currentMode != 'initial-setup') {
GLib.log_structured(LOG_DOMAIN, GLib.LogLevelFlags.LEVEL_MESSAGE, {
'MESSAGE': `GNOME Shell started at ${_startDate}`,
'MESSAGE_ID': GNOMESHELL_STARTED_MESSAGE_ID
'MESSAGE_ID': GNOMESHELL_STARTED_MESSAGE_ID,
});
}
let credentials = new Gio.Credentials();
if (credentials.get_unix_user() === 0) {
notify(_('Logged in as a privileged user'),
_('Running a session as a privileged user should be avoided for security reasons. If possible, you should log in as a normal user.'));
}
if (sessionMode.currentMode !== 'gdm' &&
sessionMode.currentMode !== 'initial-setup' &&
screenShield === null) {
notify(_('Screen Lock disabled'),
_('Screen Locking requires the GNOME display manager.'));
}
LoginManager.registerSessionWithGDM();
let perfModuleName = GLib.getenv("SHELL_PERF_MODULE");
@ -263,19 +289,19 @@ function _initializeUI() {
function _getStylesheet(name) {
let stylesheet;
stylesheet = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/' + name);
stylesheet = Gio.File.new_for_uri(`resource:///org/gnome/shell/theme/${name}`);
if (stylesheet.query_exists(null))
return stylesheet;
let dataDirs = GLib.get_system_data_dirs();
for (let i = 0; i < dataDirs.length; i++) {
let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'theme', name]);
let stylesheet = Gio.file_new_for_path(path);
stylesheet = Gio.file_new_for_path(path);
if (stylesheet.query_exists(null))
return stylesheet;
}
stylesheet = Gio.File.new_for_path(global.datadir + '/theme/' + name);
stylesheet = Gio.File.new_for_path(`${global.datadir}/theme/${name}`);
if (stylesheet.query_exists(null))
return stylesheet;
@ -311,7 +337,7 @@ function _loadDefaultStylesheet() {
*
* Get the theme CSS file that the shell will load
*
* Returns: A #GFile that contains the theme CSS,
* @returns {?Gio.File}: A #GFile that contains the theme CSS,
* null if using the default
*/
function getThemeStylesheet() {
@ -320,8 +346,8 @@ function getThemeStylesheet() {
/**
* setThemeStylesheet:
* @cssStylesheet: A file path that contains the theme CSS,
* set it to null to use the default
* @param {string=} cssStylesheet: A file path that contains the theme CSS,
* set it to null to use the default
*
* Set the theme CSS file that the shell will load
*/
@ -333,12 +359,12 @@ function reloadThemeResource() {
if (_themeResource)
_themeResource._unregister();
_themeResource = Gio.Resource.load(global.datadir + '/gnome-shell-theme.gresource');
_themeResource = Gio.Resource.load(`${global.datadir}/gnome-shell-theme.gresource`);
_themeResource._register();
}
function _loadOskLayouts() {
_oskResource = Gio.Resource.load(global.datadir + '/gnome-shell-osk-layouts.gresource');
_oskResource = Gio.Resource.load(`${global.datadir}/gnome-shell-osk-layouts.gresource`);
_oskResource._register();
}
@ -348,11 +374,13 @@ function _loadOskLayouts() {
* Reloads the theme CSS file
*/
function loadTheme() {
let themeContext = St.ThemeContext.get_for_stage (global.stage);
let themeContext = St.ThemeContext.get_for_stage(global.stage);
let previousTheme = themeContext.get_theme();
let theme = new St.Theme ({ application_stylesheet: _cssStylesheet,
default_stylesheet: _defaultCssStylesheet });
let theme = new St.Theme({
application_stylesheet: _cssStylesheet,
default_stylesheet: _defaultCssStylesheet,
});
if (theme.default_stylesheet == null)
throw new Error("No valid stylesheet found for '%s'".format(sessionMode.stylesheetName));
@ -364,26 +392,26 @@ function loadTheme() {
theme.load_stylesheet(customStylesheets[i]);
}
themeContext.set_theme (theme);
themeContext.set_theme(theme);
}
/**
* notify:
* @msg: A message
* @details: Additional information
* @param {string} msg: A message
* @param {string} details: Additional information
*/
function notify(msg, details) {
let source = new MessageTray.SystemNotificationSource();
messageTray.add(source);
let notification = new MessageTray.Notification(source, msg, details);
notification.setTransient(true);
source.notify(notification);
source.showNotification(notification);
}
/**
* notifyError:
* @msg: An error message
* @details: Additional information
* @param {string} msg: An error message
* @param {string} details: Additional information
*
* See shell_global_notify_problem().
*/
@ -407,8 +435,8 @@ function _findModal(actor) {
/**
* pushModal:
* @actor: #ClutterActor which will be given keyboard focus
* @params: optional parameters
* @param {Clutter.Actor} actor: actor which will be given keyboard focus
* @param {Object=} params: optional parameters
*
* Ensure we are in a mode where all keyboard and mouse input goes to
* the stage, and focus @actor. Multiple calls to this function act in
@ -431,7 +459,7 @@ function _findModal(actor) {
* global keybindings; the default of NONE will filter
* out all keybindings
*
* Returns: true iff we successfully acquired a grab or already had one
* @returns {bool}: true iff we successfully acquired a grab or already had one
*/
function pushModal(actor, params) {
params = Params.parse(params, { timestamp: global.get_current_time(),
@ -462,11 +490,11 @@ function pushModal(actor, params) {
modalActorFocusStack[index].prevFocus = null;
});
}
modalActorFocusStack.push({ actor: actor,
modalActorFocusStack.push({ actor,
destroyId: actorDestroyId,
prevFocus: prevFocus,
prevFocusDestroyId: prevFocusDestroyId,
actionMode: actionMode });
prevFocus,
prevFocusDestroyId,
actionMode });
actionMode = params.actionMode;
global.stage.set_key_focus(actor);
@ -475,8 +503,9 @@ function pushModal(actor, params) {
/**
* popModal:
* @actor: #ClutterActor passed to original invocation of pushModal()
* @timestamp: optional timestamp
* @param {Clutter.Actor} actor: the actor passed to original invocation
* of pushModal()
* @param {number=} timestamp: optional timestamp
*
* Reverse the effect of pushModal(). If this invocation is undoing
* the topmost invocation, then the focus will be restored to the
@ -547,24 +576,24 @@ function popModal(actor, timestamp) {
}
function createLookingGlass() {
if (lookingGlass == null) {
if (lookingGlass == null)
lookingGlass = new LookingGlass.LookingGlass();
}
return lookingGlass;
}
function openRunDialog() {
if (runDialog == null) {
if (runDialog == null)
runDialog = new RunDialog.RunDialog();
}
runDialog.open();
}
/**
* activateWindow:
* @window: the Meta.Window to activate
* @time: (optional) current event time
* @workspaceNum: (optional) window's workspace number
* @param {Meta.Window} window: the window to activate
* @param {number=} time: current event time
* @param {number=} workspaceNum: window's workspace number
*
* Activates @window, switching to its workspace first if necessary,
* and switching out of the overview if it's currently active
@ -572,7 +601,7 @@ function openRunDialog() {
function activateWindow(window, time, workspaceNum) {
let workspaceManager = global.workspace_manager;
let activeWorkspaceNum = workspaceManager.get_active_workspace_index();
let windowWorkspaceNum = (workspaceNum !== undefined) ? workspaceNum : window.get_workspace().index();
let windowWorkspaceNum = workspaceNum !== undefined ? workspaceNum : window.get_workspace().index();
if (!time)
time = global.get_current_time();
@ -610,7 +639,7 @@ function _runDeferredWork(workId) {
_deferredWorkQueue.splice(index, 1);
_deferredWorkData[workId].callback();
if (_deferredWorkQueue.length == 0 && _deferredTimeoutId > 0) {
Mainloop.source_remove(_deferredTimeoutId);
GLib.source_remove(_deferredTimeoutId);
_deferredTimeoutId = 0;
}
}
@ -640,8 +669,8 @@ function _queueBeforeRedraw(workId) {
/**
* initializeDeferredWork:
* @actor: A #ClutterActor
* @callback: Function to invoke to perform work
* @param {Clutter.Actor} actor: an actor
* @param {callback} callback: Function to invoke to perform work
*
* This function sets up a callback to be invoked when either the
* given actor is mapped, or after some period of time when the machine
@ -654,13 +683,13 @@ function _queueBeforeRedraw(workId) {
* initialization as well, under the assumption that new actors
* will need it.
*
* Returns: A string work identifier
* @returns {string}: A string work identifier
*/
function initializeDeferredWork(actor, callback, props) {
function initializeDeferredWork(actor, callback) {
// Turn into a string so we can use as an object property
let workId = `${(++_deferredWorkSequence)}`;
_deferredWorkData[workId] = { 'actor': actor,
'callback': callback };
let workId = `${++_deferredWorkSequence}`;
_deferredWorkData[workId] = { actor,
callback };
actor.connect('notify::mapped', () => {
if (!(actor.mapped && _deferredWorkQueue.includes(workId)))
return;
@ -678,7 +707,7 @@ function initializeDeferredWork(actor, callback, props) {
/**
* queueDeferredWork:
* @workId: work identifier
* @param {string} workId: work identifier
*
* Ensure that the work identified by @workId will be
* run on map or timeout. You should call this function
@ -696,9 +725,8 @@ function queueDeferredWork(workId) {
_deferredWorkQueue.push(workId);
if (data.actor.mapped) {
_queueBeforeRedraw(workId);
return;
} else if (_deferredTimeoutId == 0) {
_deferredTimeoutId = Mainloop.timeout_add_seconds(DEFERRED_TIMEOUT_SECONDS, () => {
_deferredTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, DEFERRED_TIMEOUT_SECONDS, () => {
_runAllDeferredWork();
_deferredTimeoutId = 0;
return GLib.SOURCE_REMOVE;
@ -715,12 +743,13 @@ class RestartMessage extends ModalDialog.ModalDialog {
shouldFadeIn: false,
destroyOnClose: true });
let label = new St.Label({ text: message });
let label = new St.Label({
text: message,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
});
this.contentLayout.add(label, { x_fill: false,
y_fill: false,
x_align: St.Align.MIDDLE,
y_align: St.Align.MIDDLE });
this.contentLayout.add_child(label);
this.buttonLayout.hide();
}
});

Some files were not shown because too many files have changed in this diff Show More