Compare commits

..

7 Commits

Author SHA1 Message Date
d4802861ed status/keyboard: Adapt to changes in the settings schema 2012-05-15 02:24:23 +02:00
a8fa0b8146 candidatePanel: Use the Lang.Class framework 2012-05-03 13:20:01 +02:00
cf0bb62f40 candidatePanel: Remove the PopupSeparatorMenuItem copy
No reason to not use the real deal.
2012-05-03 13:20:01 +02:00
de8106967d candidatePanel: Remove hardcoded styling
The styling should be done in the CSS file.
2012-05-03 13:19:58 +02:00
1a24f061cf candidatePanel: A candidate popup for IBus input methods
This is an implementation of the org.freedesktop.IBus.Panel API which
shows a shell style popup (BoxPointer) when using an IBus input
method.

The original code is from the ibus-gjs project[1] with minor
modifications.

[1] https://github.com/fujiwarat/ibus-gjs
2012-05-03 13:19:53 +02:00
e038845458 status/keyboard: Add the input source switcher keybindings 2012-05-03 13:19:53 +02:00
b03273c765 status/keyboard: Initial port to the new input sources settings 2012-05-03 13:19:53 +02:00
271 changed files with 57544 additions and 66684 deletions

5
.gitignore vendored
View File

@ -16,7 +16,6 @@ config.log
config.status config.status
config config
configure configure
data/50-gnome-shell-*.xml
data/gnome-shell.desktop data/gnome-shell.desktop
data/gnome-shell.desktop.in data/gnome-shell.desktop.in
data/gnome-shell-extension-prefs.desktop data/gnome-shell-extension-prefs.desktop
@ -45,14 +44,12 @@ intltool-merge.in
intltool-update.in intltool-update.in
libtool libtool
m4/ m4/
man/gnome-shell.1
omf.make omf.make
po/*.gmo po/*.gmo
po/gnome-shell.pot po/gnome-shell.pot
po/*.header po/*.header
po/*.sed po/*.sed
po/*.sin po/*.sin
po/.intltool-merge-cache
po/Makefile.in.in po/Makefile.in.in
po/Makevars.template po/Makevars.template
po/POTFILES po/POTFILES
@ -65,8 +62,6 @@ src/*-enum-types.[ch]
src/*-marshal.[ch] src/*-marshal.[ch]
src/Makefile src/Makefile
src/Makefile.in src/Makefile.in
src/calendar-server/evolution-calendar.desktop
src/calendar-server/evolution-calendar.desktop.in
src/calendar-server/org.gnome.Shell.CalendarServer.service src/calendar-server/org.gnome.Shell.CalendarServer.service
src/gnome-shell src/gnome-shell
src/gnome-shell-calendar-server src/gnome-shell-calendar-server

331
HACKING
View File

@ -1,331 +0,0 @@
Coding guide
============
Our goal is to have all JavaScript code in GNOME follow a consistent style. In
a dynamic language like JavaScript, it is essential to be rigorous about style
(and unit tests), or you rapidly end up with a spaghetti-code mess.
A quick note
------------
Life isn't fun if you can't break the rules. If a rule seems unnecessarily
restrictive while you're coding, ignore it, and let the patch reviewer decide
what to do.
Indentation and whitespace
--------------------------
Use four-space indents. Braces are on the same line as their associated
statements. You should only omit braces if *both* sides of the statement are
on one line.
* One space after the `function` keyword. No space between the function name
* in a declaration or a call. One space before the parens in the `if`
* statements, or `while`, or `for` loops.
function foo(a, b) {
let bar;
if (a > b)
bar = do_thing(a);
else
bar = do_thing(b);
if (var == 5) {
for (let i = 0; i < 10; i++) {
print(i);
}
} else {
print(20);
}
}
Semicolons
----------
JavaScript allows omitting semicolons at the end of lines, but don't. Always
end statements with a semicolon.
js2-mode
--------
If using Emacs, do not use js2-mode. It is outdated and hasn't worked for a
while. emacs now has a built-in JavaScript mode, js-mode, based on
espresso-mode. It is the de facto emacs mode for JavaScript.
File naming and creation
------------------------
For JavaScript files, use lowerCamelCase-style names, with a `.js` extension.
We only use C where gjs/gobject-introspection is not available for the task, or
where C would be cleaner. To work around limitations in
gjs/gobject-introspection itself, add a new method in `shell-util.[ch]`.
Like many other GNOME projects, we prefix our C source filenames with the
library name followed by a dash, e.g. `shell-app-system.c`. Create a
`-private.h` header when you want to share code internally in the
library. These headers are not installed, distributed or introspected.
Imports
-------
Use UpperCamelCase when importing modules to distinguish them from ordinary
variables, e.g.
const GLib = imports.gi.GLib;
Imports should be categorized into one of two places. The top-most import block
should contain only "environment imports". These are either modules from
gobject-introspection or modules added by gjs itself.
The second block of imports should contain only "application imports". These
are the JS code that is in the gnome-shell codebase,
e.g. `imports.ui.popupMenu`.
Each import block should be sorted alphabetically. Don't import modules you
don't use.
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const St = imports.gi.St;
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
location. Never reference `imports` in actual code.
Constants
---------
We use CONSTANTS_CASE to define constants. All constants should be directly
under the imports:
const MY_DBUS_INTERFACE = 'org.my.Interface';
Variable declaration
--------------------
Always use either `const` or `let` when defining a variable.
// Iterating over an array
for (let i = 0; i < arr.length; ++i) {
let item = arr[i];
}
// Iterating over an object's properties
for (let prop in someobj) {
...
}
If you use "var" then the variable is added to function scope, not block scope.
See [What's new in JavaScript 1.7](https://developer.mozilla.org/en/JavaScript/New_in_JavaScript/1.7#Block_scope_with_let_%28Merge_into_let_Statement%29)
Classes
-------
There are many approaches to classes in JavaScript. We use our own class framework
(sigh), which is built in gjs. The advantage is that it supports inheriting from
GObjects, although this feature isn't used very often in the Shell itself.
const IconLabelMenuItem = new Lang.Class({
Name: 'IconLabelMenuItem',
Extends: PopupMenu.PopupMenuBaseItem,
_init: function(icon, label) {
this.parent({ reactive: false });
this.addActor(icon);
this.addActor(label);
},
open: function() {
log("menu opened!");
}
});
* 'Name' is required. 'Extends' is optional. If you leave it out, you will
automatically inherit from Object.
* Leave a blank line between the "class header" (Name, Extends, and other
things) and the "class body" (methods). Leave a blank line between each
method.
* No space before the colon, one space after.
* No trailing comma after the last item.
* Make sure to use a semicolon after the closing paren to the class. It's
still a giant function call, even though it may resemble a more
conventional syntax.
GObject Introspection
---------------------
GObject Introspection is a powerful feature that allows us to have native
bindings for almost any library built around GObject. If a library requires
you to inherit from a type to use it, you can do so:
const MyClutterActor = new Lang.Class({
Name: 'MyClutterActor',
Extends: Clutter.Actor,
vfunc_get_preferred_width: function(actor, forHeight) {
return [100, 100];
},
vfunc_get_preferred_height: function(actor, forWidth) {
return [100, 100];
},
vfunc_paint: function(actor) {
let alloc = this.get_allocation_box();
Cogl.set_source_color4ub(255, 0, 0, 255);
Cogl.rectangle(alloc.x1, alloc.y1,
alloc.x2, alloc.y2);
}
});
Translatable strings, `environment.js`
--------------------------------------
We use gettext to translate the GNOME Shell into all the languages that GNOME
supports. The `gettext` function is aliased globally as `_`, you do not need to
explicitly import it. This is done through some magic in the
[environment.js](http://git.gnome.org/browse/gnome-shell/tree/js/ui/environment.js)
file. If you can't find a method that's used, it's probably either in gjs itself
or installed on the global object from the Environment.
Use 'single quotes' for programming strings that should not be translated
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`
-----------------------
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".
We sometimes use expando properties to set a property called `_delegate` on
the actor itself:
const MyClass = new Lang.Class({
Name: 'MyClass',
_init: function() {
this.actor = new St.Button({ text: "This is a button" });
this.actor._delegate = this;
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
},
_onClicked: function(actor) {
actor.set_label("You clicked the button!");
}
});
The 'delegate' property is important for anything which trying to get the
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.
Functional style
----------------
JavaScript Array objects offer a lot of common functional programming
capabilities such as forEach, map, filter and so on. You can use these when
they make sense, but please don't have a spaghetti mess of function programming
messed in a procedural style. Use your best judgment.
Closures
--------
`this` will not be captured in a closure, it is relative to how the closure is
invoked, not to the value of this where the closure is created, because "this"
is a keyword with a value passed in at function invocation time, it is not a
variable that can be captured in closures.
All closures should be wrapped with a Lang.bind.
const Lang = imports.lang;
let closure = Lang.bind(this, function() { this._fnorbate(); });
A more realistic example would be connecting to a signal on a method of a
prototype:
const Lang = imports.lang;
const FnorbLib = imports.fborbLib;
const MyClass = new Lang.Class({
_init: function() {
let fnorb = new FnorbLib.Fnorb();
fnorb.connect('frobate', Lang.bind(this, this._onFnorbFrobate));
},
_onFnorbFrobate: function(fnorb) {
this._updateFnorb();
}
});
Object literal syntax
---------------------
In JavaScript, these are equivalent:
foo = { 'bar': 42 };
foo = { bar: 42 };
and so are these:
var b = foo['bar'];
var b = foo.bar;
If your usage of an object is like an object, then you're defining "member
variables." For member variables, use the no-quotes no-brackets syntax: `{ bar:
42 }` `foo.bar`.
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
-----------------------------
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.
const ANIMATION_TIME = 2000;
const MyClass = new Lang.Class({
Name: 'MyClass',
_init: function() {
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' });

View File

@ -1,11 +1,7 @@
# Point to our macro directory and pick up user flags from the environment # Point to our macro directory and pick up user flags from the environment
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
SUBDIRS = data js src browser-plugin tests po docs SUBDIRS = data js src browser-plugin tests po man docs
if ENABLE_MAN
SUBDIRS += man
endif
EXTRA_DIST = \ EXTRA_DIST = \
.project \ .project \
@ -17,7 +13,6 @@ EXTRA_DIST = \
DIST_EXCLUDE = \ DIST_EXCLUDE = \
.gitignore \ .gitignore \
gnome-shell.doap \ gnome-shell.doap \
HACKING \
MAINTAINERS \ MAINTAINERS \
tools/build/* tools/build/*
@ -25,4 +20,4 @@ distcheck-hook:
@echo "Checking disted files against files in git" @echo "Checking disted files against files in git"
@$(srcdir)/tools/check-for-missing.py $(srcdir) $(distdir) $(DIST_EXCLUDE) @$(srcdir)/tools/check-for-missing.py $(srcdir) $(distdir) $(DIST_EXCLUDE)
DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc --enable-man DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc

525
NEWS
View File

@ -1,528 +1,3 @@
3.6.3.1
=======
* Fix regression in reentrancy fix for 3.6.3 [Giovanni; #689295]
Contributors:
Giovanni Campagna
Translations:
Rafael Ferreira [pt_BR]
3.6.3
=====
* recorder: Set frame duration to fix broken video headers [Adel; #688487]
* Block cancellation of unlock dialog during drags [Giovanni; #686800]
* Honor lock-delay GSettings key [Giovanni, Matthias; #690766, #691170]
* Fix reentrancy problem causing the tray to disappear [Giovanni; #683986]
Contributors:
Giovanni Campagna, Matthias Clasen, Adel Gadllah,
Translations:
Mattias Põldaru [et], Sandeep Sheshrao Shedmake [mr], Wouter Bolsterlee [nl],
Rajesh Ranjan [hi], Nilamdyuti Goswami [as], Ani Peter [ml],
Andika Triwidada [id], Krishnababu Krothapalli [te], Shankar Prasad [kn],
Dr.T.Vasudevan [ta], Runa Bhattacharjee [bn_IN], Rafael Ferreira [pt_BR],
Balázs Úr [hu], Gheyret Kenji [ug], Yasumichi Akahoshi [ja], Yoji TOYODA [ja],
Alexandre Franke [fr]
3.6.2
=====
* Implement org.gnome.ScreenSaver.GetActiveTime [Giovanni; #686064]
* Explicitly load gnome-screensaver when not running GDM [Tim; #683060]
* Set Empathy as preferred handler when delegating channels [Xavier; #686296]
* Tweak screenShield animations [Rui; #686745]
* Restore Fittsability of summary items in message tray [Florian; #686474]
* App search: Match GenericName too [Matthias; #687121]
* Add setting to force log out menuitem [Matthias; #686057]
* Fix auto-scroll to bottom in chat notifications [Sjoerd; #686571]
* Update man page [Matthias; #680601]
* Hide notifications when closed button is clicked [Jasper, Florian; #682237]
* Fix screenshots on Big Endian [Owen; #685915]
* Implement 'disable-user-list' in login screen [Ray; #660660]
* Misc bugfixes [Jasper, Florian, Giovanni, Matthew, Matthias, Alejandro,
Sebastian; #686233, #686484, #686487, #686630, #686728, #686574, #686574,
#686574, #686879, #686763, #685239, #687189, #686736, #686583, #687132,
#687958, #687020, #687708, #686888, #686530, #684810, #688181]
Contributors:
Matthew Barnes, Giovanni Campagna, Xavier Claessens, Matthias Clasen,
Sebastian Keller, Tim Lunn, Rui Matos, Florian Müllner, Alejandro Piñeiro,
Sjoerd Simons, Jasper St. Pierre, Ray Strode, Owen Taylor
Translations:
Takayuki KUSANO [ja], Dušan Kazik [sk], Mattias Põldaru [et],
Tom Tryfonidis [el], Sweta Kothari [gu], Matej Urbančič [sl], Piotr Drąg [pl],
Fran Diéguez [gl], Aurimas Černius [lt], Kjartan Maraas [nb],
Ihar Hrachyshka [be], Мирослав Николић [sr, sr@latin], Gil Forcada [ca],
A S Alam [pa], Yuri Myasoedov [ru], Marek Černocký [cs], Sweta Kothari [gu],
Wolfgang Stöggl [de], Daniel Mustieles [es]
3.6.1
=====
* dash: Make padding even on the top/bottom of the dash [Jasper; #684619]
* Fix a crash when dragging search results [Jasper; #684888]
* workspaceThumbnail: Fix dragging with static workspaces [Jasper; #684641]
* Really hide 'Show Keyboard Layout' on the lock screen [Matthias]
* Misc. improvements to jhbuild setup [Owen; #685352, #685353, #685354, #685355]
* Show message tray in Ctrl+Alt+Tab outside of the overview [Jasper, Florian;
#684633, #685914]
* Disable hotplug sniffer on remote filesystems [Jasper; #684093]
* userMenu: Remove 'Switch Session' item [Florian; #685062]
* unlockDialog: Make prompt entry insensitive while logging in [Jasper; #685444]
* messageTray: Don't animate desktop clone for failed grabs [Jasper; #685342]
* Fix crash on dragging windows between workspaces [Ryan; #681399]
* userMenu: Ignore 'lock-enabled' setting for user switching [Florian; #685536]
* gdm: Fix key-focus on first user [Adel; #684650]
* Make grid button insensitive when dragging non-favorites [Jasper; #685313]
* Calendar: hide all actions when on the login screen [Matthias; #685142]
* Adapt unlock dialog layout for the login screen [Florian; #685201]
* Make focus-follows-mouse work better with Shell UI [Florian; #678169]
* Improve look of screen shield [Jasper; #685919]
* Fix keynav in the login screen [Florian; #684730]
* dateMenu: Hide "Open Calendar" item if calendar unavailable [Florian; #686050]
* unlockDialog: Reset UI on verification failure [Giovanni; #685441]
* Show unlock dialog on primary monitor when using keynav [Giovanni; #685855]
* Fix height changes of entries when entering text [Florian; #685534]
* Fix show-apps label after successful drags [Florian; #684627]
* Misc. bugfixes and cleanups [Jasper, Olivier, Florian, Owen, Adel, Tanner, Tim, Matthias; #685434, #685511, #685466, #685341, #685156, #681159, #673189, #686016, 684869, #686079, #686063
Contributors:
Jasper St. Pierre
Matthias Clasen
Owen Taylor
Olivier Blin
Florian Müllner
Ryan Lortie
Adel Gadllah
Tanner Doshier
Tim Lunn
Giovanni Campagna
Translations:
Tobias Endrigkeit [de], Rudolfs Mazurs [lv], Ask H. Larsen [da],
Shankar Prasad [kn], Changwoo Ryu [ko], Chris Leonard [en_GB],
Arash Mousavi [fa], Theppitak Karoonboonyanan [th], Seán de Búrca [ga],
Yaron Shahrabani [he], Alexander Shopov [bg], Žygimantas Beručka [lt],
Milo Casagrande [it], Kjartan Maraas [nb], Kris Thomsen [da],
Aurimas Černius [lt], Yuri Myasoedov [ru], Мирослав Николић [sr],
Marek Černocký [cs], Gabor Kelemen [hu], Ihar Hrachyshka [be],
Chao-Hsiung Liao [zh_HK, zh_TW], Eleanor Chen [zh_CN],
Carles Ferrando [ca@valencia], Vicent Cubells [ca], Daniel Korostil [uk],
Alexandre Franke [fr], Piotr Drąg [pl]
3.6.0
=====
* keyboard: Make input source items accessible [Florian; #684462]
* Don't show network dialogs in the lock screen [Giovanni; #684384]
* popupMenu: Fix initial visibility of settings items [Florian; #684473]
* userMenu: Close menu immediately on user/session switch [Florian; #684459]
* Fix alignment of search section headers in RTL locales [Florian; #684379]
* screenShield: Fix unlock animation [Florian; #684591]
* Don't open the tray from a dwell while in a modal grab [Jasper; #684458]
* userMenu: Fix texture updates on icon changes [Florian; #679268]
* Fix a11y support in the login screen [Florian, Ray; #684727, #684728, #684748]
* Make On-Screen-Keyboard usable with new message tray [Giovanni, Florian;
#683546]
* Fix initial visibility of input volume in lock-screen [Florian; #684611]
Contributors:
Giovanni Campagna, Florian Müllner, Jasper St. Pierre, Ray Strode
Translations:
Matej Urbančič [sl], Dr.T.Vasudevan [ta], Piotr Drąg [pl], A S Alam [pa],
Alexander Shopov [bg], Nilamdyuti Goswami [as], Chandan Kumar [hi],
Khaled Hosny [ar], Ibrahim Saed [ar], Sandeep Sheshrao Shedmake [mr],
Tom Tryfonidis [el], Theppitak Karoonboonyanan [th], Alexandre Franke [fr],
Fran Diéguez [gl], Gabor Kelemen [hu], Ani Peter [ml], Daniel Mustieles [es],
Мирослав Николић [sr, sr@latin], Duarte Loreto [pt], ManojKumar Giri [or],
Ihar Hrachyshka [be], Aurimas Černius [lt], Djavan Fagundes [pt_BR],
Changwoo Ryu [ko], Bruce Cowan [en_GB], Kris Thomsen [da], Gil Forcada [ca],
Yaron Shahrabani [he], Milo Casagrande [it], Ville-Pekka Vainio [fi],
YunQiang Su [zh_CN], Carles Ferrando [ca@valencia], Mario Blättermann [de],
Rajesh Ranjan [hi], Yuri Myasoedov [ru], Rūdolfs Mazurs [lv],
Jiro Matsuzawa [ja], Mattias Põldaru [et], Timur Zhamakeev [ky],
Petr Kovar [cs], Chao-Hsiung Liao [zh_HK,zh_TW], Andika Triwidada [id]
3.5.92
======
* Login/UnlockDialog: Don't reset immediately if auth fails [Giovanni; #682544]
* Allow changing session mode at runtime [Jasper, Giovanni; #683156]
* Add zoom out animation on login [Jasper; #683170]
* Bluetooth: don't restrict the length of non numeric PINs [Giovanni; #683356]
* Force chat notification to stay open when focusing entry [Debarshi; #682236]
* Make sure the screen is fully locked before suspending [Giovanni; #683448]
* st-texture-cache: Fix a case of distorted textures [Florian; #683483]
* popupSubMenu: Fix padding for non-scrolled submenus [Florian; #683009]
* popupMenu: Fix width changes on submenu open/close [Florian; #683485]
* boxpointer: Avoid malformed boxpointer arrow [Debarshi; #680077]
* Change stage background color to grey [Adel; #683514]
* messageTray: Update style of summary counters [Debarshi; #682891]
* Don't fail if a legacy tray icon has no WM_CLASS [Giovanni; #683724]
* PolkitAgent: Fix a crash if there is no avatar [Giovanni; #683707]
* Hide the a11y menu in the lock screen, but show it in the login screen
[Giovanni; #682542]
* Fix show-apps button dropping off the dash [Florian; #683340]
* Fix committing strings to shell entries from input method [Florian; #658325]
* Make IBus display strings consistent with control-center [Rui; #683124]
* Fix missing short codes for some input sources [Rui; #683613]
* Remove support for long-press from entry context menus [Jasper; #683509]
* screenShield: Add box-shadow to the shield [Florian]
* Don't show a right-click menu for the hotplug source [Jasper; #683438]
* Fix extension styling [Giovanni; #682128]
* Fix on-screen keyboard not working with system-modal dialogs
[Giovanni; #664309]
* Fix insensitive styling for popup menu items [Giovanni; #683988]
* Disable the message tray dwell when the user is interacting [Owen; #683811]
* Animate going from the unlock dialog to the lock screen [Giovanni; #681143]
* Autostart fprintd when necessary [Ray; #683131]
* UnlockDialog: Allow typing before the first PAM question [Giovanni; #681576]
* Make Return key dismiss screenshield [Ray; #683889]
* Fix keyboard navigation in the message tray [Florian; #682243]
* Remove the places & devices search provider [Giovanni; #683506]
* Enable hot corner while the message tray is up [Florian; #682255]
* Port screen recorder to new GStreamer vp8enc API [Adel; #684206]
* Fix fish flickering [Giovanni; #684154]
* Fix extension ordering with !important [Jasper; #684163]
* Allow the shell to run without the screenshield [Giovanni; #683060]
* Add menu items for IBus Anthy's InputMode, TypingMode [Rui; #682314]
* Improve transition to the login dialog [Jasper; #682428]
* Keep unlock dialog around until shield animation ends [Florian; #684342]
* Expose shell keybindings in System Settings [Florian; #671010]
* Misc. bugfixes and cleanups [Debarshi, Florian, Giovanni, Jasper, Rico, Rui;
#672790, #677434, #683305, #683357, #683369, #683377, #683378, #683400,
#683449, #683472, #683482, #683487, #683488, #683526, #683529, #683546,
#683583, #683628, #683705, #683982, #683989, #684035, #684036, #684040,
#684162, #684214, #684343]
Contributors:
Giovanni Campagna, Adel Gadllah, Rui Matos, Florian Müllner, Debarshi Ray,
Jasper St. Pierre, Ray Strode, Owen Taylor, Rico Tzschichholz
Translations:
Gabor Kelemen [hu], Piotr Drąg [pl], Khaled Hosny [ar],
Мирослав Николић [sr, sr@latin], Chao-Hsiung Liao [zh_HK, zh_TW],
Bruce Cowan [en_GB], Dirgita [id], Tom Tryfonidis [el], Timo Jyrinki [fi],
Adorilson Bezerra [pt_BR], Arash Mousavi [fa], Matej Urbančič [sl],
Christian Kirbach [de], Yaron Shahrabani [he], Ihar Hrachyshka [be],
Changwoo Ryu [ko], Duarte Loreto [pt], Theppitak Karoonboonyanan [th],
Nilamdyuti Goswami [as], Sandeep Sheshrao Shedmake [mr],
Alexandre Franke [fr], Ivaylo Valkov [bg], tuhaihe [zh_CN],
Yuri Myasoedov [ru], Aurimas Černius [lt], Andika Triwidada [id],
Rajesh Ranjan [hi], Sweta Kothari [gu], Daniel Mustieles [es],
Fran Diéguez [gl], Praveen Illa [te]
3.5.91
======
* Improve modal dialog styling of network secret prompts [Jasper; #682412]
* Fix visibility of non-active workspaces during overview transition
[Florian; #682002]
* Improve scrollbar theming [Cosimo; #682476]
* Make sure the app menu remains hidden in locked state [Florian; #682475]
* Add tooltip to show-applications icon [Jasper; #682445]
* Do not add duplicate remote search providers [Florian; #682470]
* Handle 'popup-menu' signal on summary items [Florian; #682486]
* Fix dwelling during mouse-down [Owen; #682385]
* Set label actor for endSessionDialog.ListItem [Alejandro; #677503]
* Don't match on comments when searching applications [Florian; #682529]
* Make workspace selector more similar to the mockup [Stefano; #662087]
* Fix extension installation and reloading [Jasper; #682578]
* Hide removable devices in the lock screen [Giovanni; #681143]
* Reset cancellable after hitting Escape on login screen [Alban; #681537]
* Fix suspend from the user menu [Giovanni; #682746]
* Set label actor for summary items in message tray [Alejandro; #677229]
* Set label for the "Show applications" dash button [Alejandro; #682366]
* Load extensions as late as possible [Jasper; #682822]
* Improve mount operation dialogs [Jon; #682645]
* Remove "Connect to ..." item from places search [Florian; #682817]
* Don't auto-expand notifications with actions [Giovanni; #682738]
* Add a new lock screen menu to combine volume network and power
[Giovanni; #682540]
* Add support for pre-edit to StIMText [Daiki; #664041]
* Remove StIconType [Jasper, Florian, Rui, Giovanni, Debarshi; #682540]
* Use monitor geometry for dwelling [Florian; #683044]
* Add support for surrounding-text to StIMText [Daiki; #683015]
* Improve the placement and style of the "No results" text [Jasper; #683135]
* Remove broken network device activation policy [Giovanni; #683136]
* Hide power status icon when no battery is present [Tim; #683080]
* Ensure summary items are square and have spacing [Debarshi; #682248]
* Fix close buttons overlapping screen edge [Debarshi; #682343]
* Escape the tray when a legacy icon is clicked [Giovanni; #682244]
* Update arrow in the screen shield to match latest mockups [Giovanni; #682285]
* Allow lifting the screen shield with the mouse wheel [Giovanni; #683164]
* Make sure to show the app menu after unlocking the screen [Jasper; #683154]
* Misc bug fixes and cleanups [Debarshi, Florian, Giovanni, Jasper, Rui;
#582650, #667439, #682238, #682268, #682429, #682455, #682544, #682546,
#682683, #682710, #682998, #683073, #683137, #683156]
Contributors:
Alban Browaeys, Giovanni Campagna, Cosimo Cecchi, Stefano Facchini,
Adel Gadllah, Tim Lunn, Rui Matos, William Jon McCann, Florian Müllner,
Alejandro Piñeiro, Debarshi Ray, Jasper St. Pierre, Owen Taylor, Daiki Ueno
Translations:
Piotr Drąg [pl], Takayuki KUSANO [ja], Kjartan Maraas [nb],
Aurimas Černius [lt], Daniel Mustieles [es], Yuri Myasoedov [ru],
Khaled Hosny [ar], Yaron Shahrabani [he], Tom Tryfonidis [el],
Nilamdyuti Goswami [as], Fran Diéguez [gl], Nguyễn Thái Ngọc Duy [vi],
A S Alam [pa], Dr.T.Vasudevan [ta], Luca Ferretti [it]
3.5.90
======
* Use symbolic icons for workspace switch OSD [Jon; #680738]
* Lock screen improvements:
- Hide user menu and a11y menu in the screen lock [Giovanni; #681143]
- Bump the lock screen slightly when pressing a key [Giovanni; #681143]
- Constrain vertical movement of the screen shield [Giovanni; #681143]
- Return to lock screen on idle [Giovanni; #682041]
- Unlock screen automatically after fast-user switching [Giovanni; #682096]
- Fix "other user" label [Ray; #681750]
* Constrain content of system modals to primary monitor [#681743]
* Respect automatic lock setting on suspend/user-switch [Giovanni; #680231]
* Improve styling of keyring prompt [Jasper; #681821]
* Do not hard-code <super> as overlay-key [Florian; #665547]
* Update style of attached modal dialogs [Florian; #681601]
* a11y: allow navigation on non reactive items [Alejandro; #667439, #667439]
* Implement mode-less overview design [Joost, Florian; #682109]
* Implement message-tray redesign:
- Restyle the message tray [Ana, Allan, Florian; #677213, #682342]
- Move the desktop upwards when showing the tray [Debarshi; #681392]
- Add a close button to notifications [Ana, Jasper; #682253]
- Add a keybinding to toggle the tray [Debarshi; #681392]
- Make the tray keyboard navigable [Debarshi; #681519]
- Add dwelling at the bottom of the screen to open the tray [Owen; #682310]
- Don't time out banners when the user is inactive [Marina, Jasper]
- Misc fixes and cleanups [Jasper, Marina]
* Fix showing "Next Week" on Sundays [Sebastian; #682198]
* Delay restoring IM presence until the network comes up [Florian; #677982]
* Display enterprise login hint [Ray; #681975]
* Ignore unrecognized/irrelevant network devices/connections [Dan; #682364]
* Misc bug fixes and cleanups: [Dan, Florian, Jasper, Jiro, Piotr, Rico;
#643687, #682045, #682189]
Contributors:
Giovanni Campagna, Allan Day, Piotr Drąg, William Jon McCann,
Sebastian Keller, Jiro Matsuzawa, Florian Müllner, Alejandro Piñeiro,
Debarshi Ray, Ana Risteska, Jasper St. Pierre, Ray Strode, Owen Taylor,
Rico Tzschichholz, Joost Verdoorn, Dan Winship, Marina Zhurakhinskaya
Translations:
Nilamdyuti Goswami [as], Daniel Mustieles [es], Yaron Shahrabani [he],
Chao-Hsiung Liao [zh_HK, zh_TW], Tobias Endrigkeit [de], A S Alam [pa],
Sandeep Sheshrao Shedmake [mr], Fran Diéguez [gl],
Мирослав Николић [sr, sr@latin]
3.5.5
=====
* Update style to match mockups [Allan]
- improve calendar layout and legibility
- update notifications and menus
- use a common style for entries
- update scrollbars to match GTK+
- improve clock/unlock button in lock screen
- update polkit dialogs [Jasper]
* Fix login dialog growing when selecting different users [Florian; #675076]
* Implement screen lock in the shell [Giovanni]
- restructure login code to be shared with session unlock [#619955]
- add initial screen shield / unlock dialog implementation [#619955]
- implement (optional) notification list on lock shield [#619955]
- update login dialog style to match lock screen [#619955]
- filter notifications to only show new ones on the screen lock [#681143]
- make notifications scrollable if necessary [#681143]
- use correct application names in notifications [#681143]
- allow to return to the shield by pressing Escape [#681143]
* Minor login dialog improvements [Florian]
- update style to match the overall visuals [#660913]
- indicate whether users are logged in [#658185]
* Add support for background-repeat CSS property [Jasper; #680801]
* Add :active pseudo class on scroll handles [Florian]
* Remove markup from translated strings [Matthias; #681270]
* Misc bug fixes and cleanups: [Alban, Florian, Giovanni, Jasper, Jeremy,
Matthias, Piotr; #677893, #679944, #680064, #680170, #680216, #680426,
#681101, #681382]
Contributors:
Jeremy Bicha, Alban Browaeys, Giovanni Campagna, Matthias Clasen, Allan Day,
Piotr Drąg , Florian Müllner, Jasper St. Pierre
Translations:
Matej Urbančič [sl], Tom Tryfonidis [el], Yaron Shahrabani [he],
Kjartan Maraas [nb], Baurzhan Muftakhidinov [kk], Praveen Illa [te],
Khaled Hosny [ar], Daniel Mustieles [es], Gabor Kelemen [hu],
Fran Diéguez [gl], Sweta Kothari [gu], Aleksej Kabanov [ru],
Nilamdyuti Goswami [as], Arash Mousavi [fa], Мирослав Николић [sr, sr@latin]
3.5.4
=====
* Fix wrong result handling of remote calls [Florian; #678852]
* dateMenu: Fix regression that caused no date to be displayed [Colin]
* WindowTracker: Fix refcounting bug in get_app_for_window() [Giovanni; #678992]
* Show the workspace switcher for move-to-workspace keybinding
[Giovanni, Jasper; #674104, #660839, #679005]
* userMenu: Move "Power off" item to the bottom [Florian; #678887]
* Remove contacts search provider [Florian, Rui; #677442]
* network: don't ask for always-ask secrets when interaction isn't allowed
[Dan; #679091]
* PolkitAgent: Look for the right password prompt [Matthias; #675300]
* Implement extension updates [Jasper; #679099]
* userMenu: Don't disconnect account signals when disabled [Guillaume; #669112]
* Fix startup notification when opening calendar [Florian; #677907]
* networkAgent: use absolute path if configured [Clemens; #679212]
* recorder: Port to GStreamer-1.0 API [Florian; #679445]
* telepathyClient: don't add log messages on presence changes [Ana; #669508]
* lookingGlass: Don't use a signal callback on 'paint' to draw the border
[Jasper; #679464]
* Add support for inhibiting automount [Hans; #678597]
* Implemented banner support for the login screen [Matthias, Marius; #665346]
* boxpointer: Flip side if we would end outside the monitor [Rui; #678164]
* boxpointer: Change 'animate' parameter on show/hide to a bitmask
[Rui; #678337]
* Add a grayscale effect [Matthias, Jasper, Florian: #676782, #674499]
* UserMenu: show "Install Updates & Restart" when appropriate
[Giovanni; #677394, #680080]
* messageTray: don't show the message tray when a new notification is shown
[Ana; #677210]
* panel: don't break when indicator has no menu [Jean-Philippe; #678694]
* appMenu: Disable app menu during startup animations [Florian; #672322]
* autorun: Add a notification when unmounting drives [Cosimo; #676125]
* st-icon: Fix potential crash involving shadows [Jasper; #679776]
* Remove manual garbage collection on tweeners end [Cosimo; #679832]
* dash: hide tooltips when overview begins hiding [Stefano; #674241]
* Update modal dialog animation for new centered position [Florian; #674499]
* calendar: Fix grid lines in RTL locales [Florian; #679879]
* Integrate IBus with keyboard indicator [Rui; #641531]
* Move ibus status icon under keyboard [Matthias]
* gdm: port from libgdmgreeter to libgdm [Ray; #676401]
* Misc bug fixes and cleanups [Antoine, Cosimo, Giovanni, Jasper, Rico;
#678978, #672790, #679847, #679944]
Contributors:
Jean-Philippe Braun, Clemens Buchacher, Giovanni Campagna, Cosimo Cecchi,
Matthias Clasen, Hans de Goede, Guillaume Desmottes, Stefano Facchini,
Antoine Jacoutot, Rui Matos, Florian Müllner, Marius Rieder, Ana Risteska,
Jasper St. Pierre, Rico Tzschichholz, Colin Walters, Dan Williams
Translations:
Matej Urbančič [sl], Khaled Hosny [ar], Nguyễn Thái Ngọc Duy [vi],
Nilamdyuti Goswami [as], Alexander Shopov [bg], Ivaylo Valkov [bg],
Daniel Mustieles [es], Kjartan Maraas [nb,nn], Yaron Shahrabani [he],
Nilamdyuti Goswami [as], Chao-Hsiung Liao [zh_HK, zh_TW], Ihar Hrachyshka [be],
Praveen Illa [te]
3.5.3
=====
* calendar: Adapt to Evolution-Data-Server API changes [Matthew; #677402]
* messageTray: Don't show non urgent notifications while in fullscreen
[Adel; #677590]
* modalDialog: show dialogs on monitor with the mouse pointer [Tim; #642591]
* extensionSystem: Prepare for extension updating system [Jasper; #677586]
* appDisplay: Don't show apps in NoDisplay categories in the All view
[Jasper; #658176]
* st: Trigger theme updates on resolution changes [Florian; #677975]
* Always enable a11y [Bastien; #678095]
* telepathyClient: ignore invalidated channels [Guillaume; #677457]
* shell-app: Update app menu if necessary [Florian; #676238]
* Enable the Screen Reader menu item [Matthias; #663256]
* Disable unredirection when a modal operation is active [Giovanni]
* Make folks optional [Colin]
* Improve mount-operation support [Cosimo]
- Fix exception when showing password entry [#678428]
- Close the password entry on operation abort [#673787]
- autorun: Don't allow autorun for things we mount on startup [#660595]
- Turn passphrase prompt into a dialog [#674962]
- Implement org.Gtk.MountOperationHandler [#678516]
* Network menu improvements
- Sort Wifi networks by strength [Giovanni; #658946]
- Prefer wifi/3g over VPN in the panel [Cosimo; #672591]
* clock: Switch to using GnomeWallClock [Colin; #657074]
* remoteSearch: Allow to reference .desktop file for Title/Icon
[Florian; #678816]
* Fix memory leaks [Jasper, Pavel; #678079, #678406, #678737]
* Misc fixes [Florian, Giovanni, Guillaume, Jasper, Kjartan, Piotr, Rui;
#658955, #677497, #678396, #678502]
* Misc cleanups [Bastien, Florian, Jasper; #677426, #677515, #678096, #678416]
Contributors:
Matthew Barnes, Giovanni Campagna, Cosimo Cecchi, Matthias Clasen,
Guillaume Desmottes, Piotr Drąg, Adel Gadllah, Tim L, Kjartan Maraas,
Rui Matos, Florian Müllner, Bastien Nocera, Jasper St. Pierre, Colin Walters
Translations:
Matej Urbančič [sl], Yuri Kozlov [ru], Tom Tryfonidis [el],
Kjartan Maraas [nb], Žygimantas Beručka [lt], Luca Ferretti [it],
Khaled Hosny [ar], Daniel Mustieles [es], Fran Diéguez [gl], A S Alam [pa]
3.5.2
=====
* main: Move 'toggle-recording' binding into the shell [Florian; #674377]
* popupMenu: make sure to break the grab when the slider is not visible
[Stefano; #672713]
* st-theme-node-drawing: Don't use GL types [Neil; #672711]
* Mirror Evolution calendar settings into our own schema [Owen; #674424]
* shell-network-agent: don't crash if a request isn't found [Dan; #674961]
* notificationDaemon: Match app based on WM_CLASS [Jasper; #673761]
* NetworkMenu: use network-offline while loading [Giovanni; #674426]
* lookingGlass: Remove the Errors tab [Jasper; #675104]
* searchDisplay: Reset keyboard focus after displaying async results
[Rui; #675078]
* gdm: don't fail if fprintd is unavailable [Ray; #675006]
* messageTray: Fix scrolling up [Jasper; #661615]
* main: Close the recorder instead of pausing it [Rui; #675128]
* Accessibility [Alejandro]
- Use the proper label_actor for date menu on top panel [#675307]
- Set the proper role/label_actor for SearchResult.content [#672242]
- do not expose a label text if 'hidden' style class is used [#675341]
* Magnifier: Add brightness and contrast functionality [Joseph; #639851]
* theme: use a smaller border-radius for top bar [Jakub; #672430]
* placeDisplay: use new bookmark file location [Matthias; #675443]
* port all synchronous search providers to the async API [Jasper, Rui; #675328]
* NetworkAgent: disallow multiple requests for the same connection/setting
[Giovanni; #674961]
* userMenu: Update to latest mockups [Florian; #675802]
* util: Don't double-fork when spawning from Alt-F2 [Colin; #675789]
* messageTray: Make Source usable without subclassing [Jasper; #661236]
* panel: Check for appMenu button's reactivity before opening [Florian; #676316]
* Fix formatting of bluetooth passkey [Florian; #651251]
* notificationDaemon: Filter out file-transfer notifications [Jasper; #676175]
* Don't use global.log() [Jasper; #675790]
* Fix broken extension loading in some distributions [Owen, Alexandre; #670477]
* shell-app: Raise windows in reverse order to preserve the stacking
[Rui; #676371]
* Generalize gdm-mode [Florian; #676156]
* Switch string formatting to the one inside gjs [Jasper; #675479]
* extensionUtils: Support subdirectories in getCurrentExtension
[Jasper; #677001]
* panel: Refuse to add (legacy) status icons not part of the session mode
[Florian; #677058]
* Add an initial-setup mode [Matthias; #676697]
* status/keyboard: Port to the new input sources settings [Rui; #641531]
* NetworkMenu: show notifications for failed VPN connections [Giovanni; #676330]
* userMenu: Indicate progress on status changes [Florian; #659067]
* recorder: Honor "disable-save-to-disk" lockdown key [Rūdolfs; #673630]
* searchDisplay: Use the rowLimit we pass to the IconGrid [Christian; #675527]
* endSessionDialog: Factor out _updateDescription from _updateContent
[Alejandro; #674210]
* Fix empathy's appMenu freezing the shell [Alban; #676447]
* Code cleanups [Florian, Giovanni, Jasper; #672807, #672413, #676837, #676850,
#672272]
* Misc bug fixes [Alban, Florian, Giovanni, Guillaume, Jasper, Piotr, Rico,
Ron, Rui, Stefano; #659968, #672192, #673177, #673198, #674323, #675301,
#675370, #676347, #676806, #677097]
Contributors:
Alban Browaeys, Giovanni Campagna, Matthias Clasen, Guillaume Desmottes,
Piotr Drąg, Stefano Facchini, Rui Matos, Rūdolfs Mazurs, Florian Müllner,
Alejandro Piñeiro, Neil Roberts, Alexandre Rostovtsev, Joseph Scheuhammer,
Jakub Steiner, Jasper St. Pierre, Ray Strode, Owen Taylor, Rico Tzschichholz,
Colin Walters, Dan Winship, Ron Yorston
Translations:
OKANO Takayoshi [ja], Daniel Mustieles [es], Changwoo Ryu [ko],
Yaron Shahrabani [he], Fran Diéguez [gl], Jonh Wendell [pt_BR],
Kjartan Maraas [nb], Luca Ferretti [it], Tom Tryfonidis [el],
Sandeep Sheshrao Shedmake [mr], Takanori MATSUURA [ja], Dirgita [id],
Mantas Kriaučiūnas [lt], Matej Urbančič [sl], Jiro Matsuzawa [ja]
3.4.1 3.4.1
===== =====
* Fix crash that occurred when an icon theme change caused unexpected * Fix crash that occurred when an icon theme change caused unexpected

View File

@ -41,7 +41,7 @@
"It can be used only by extensions.gnome.org" "It can be used only by extensions.gnome.org"
#define PLUGIN_MIME_STRING "application/x-gnome-shell-integration::Gnome Shell Integration Dummy Content-Type"; #define PLUGIN_MIME_STRING "application/x-gnome-shell-integration::Gnome Shell Integration Dummy Content-Type";
#define PLUGIN_API_VERSION 5 #define PLUGIN_API_VERSION 3
typedef struct { typedef struct {
GDBusProxy *proxy; GDBusProxy *proxy;
@ -163,7 +163,6 @@ NP_Initialize(NPNetscapeFuncs *pfuncs, NPPluginFuncs *plugin)
plugin->newp = NPP_New; plugin->newp = NPP_New;
plugin->destroy = NPP_Destroy; plugin->destroy = NPP_Destroy;
plugin->getvalue = NPP_GetValue; plugin->getvalue = NPP_GetValue;
plugin->setwindow = NPP_SetWindow;
return NPERR_NO_ERROR; return NPERR_NO_ERROR;
} }
@ -225,7 +224,7 @@ NPP_New(NPMIMEType mimetype,
NULL, /* interface info */ NULL, /* interface info */
"org.gnome.Shell", "org.gnome.Shell",
"/org/gnome/Shell", "/org/gnome/Shell",
"org.gnome.Shell.Extensions", "org.gnome.Shell",
NULL, /* GCancellable */ NULL, /* GCancellable */
&error); &error);
if (!data->proxy) if (!data->proxy)
@ -268,7 +267,6 @@ typedef struct {
NPObject parent; NPObject parent;
NPP instance; NPP instance;
GDBusProxy *proxy; GDBusProxy *proxy;
GSettings *settings;
NPObject *listener; NPObject *listener;
NPObject *restart_listener; NPObject *restart_listener;
gint signal_id; gint signal_id;
@ -325,9 +323,6 @@ on_shell_appeared (GDBusConnection *connection,
} }
} }
#define SHELL_SCHEMA "org.gnome.shell"
#define ENABLED_EXTENSIONS_KEY "enabled-extensions"
static NPObject * static NPObject *
plugin_object_allocate (NPP instance, plugin_object_allocate (NPP instance,
NPClass *klass) NPClass *klass)
@ -337,7 +332,6 @@ plugin_object_allocate (NPP instance,
obj->instance = instance; obj->instance = instance;
obj->proxy = g_object_ref (data->proxy); obj->proxy = g_object_ref (data->proxy);
obj->settings = g_settings_new (SHELL_SCHEMA);
obj->signal_id = g_signal_connect (obj->proxy, "g-signal", obj->signal_id = g_signal_connect (obj->proxy, "g-signal",
G_CALLBACK (on_shell_signal), obj); G_CALLBACK (on_shell_signal), obj);
@ -373,14 +367,39 @@ plugin_object_deallocate (NPObject *npobj)
g_slice_free (PluginObject, obj); g_slice_free (PluginObject, obj);
} }
static NPIdentifier api_version_id;
static NPIdentifier shell_version_id;
static NPIdentifier get_info_id;
static NPIdentifier list_extensions_id;
static NPIdentifier enable_extension_id;
static NPIdentifier install_extension_id;
static NPIdentifier uninstall_extension_id;
static NPIdentifier onextension_changed_id;
static NPIdentifier onrestart_id;
static NPIdentifier get_errors_id;
static NPIdentifier launch_extension_prefs_id;
static bool
plugin_object_has_method (NPObject *npobj,
NPIdentifier name)
{
return (name == get_info_id ||
name == list_extensions_id ||
name == enable_extension_id ||
name == install_extension_id ||
name == uninstall_extension_id ||
name == get_errors_id ||
name == launch_extension_prefs_id);
}
static inline gboolean static inline gboolean
uuid_is_valid (NPString string) uuid_is_valid (const gchar *uuid)
{ {
gsize i; gsize i;
for (i = 0; i < string.UTF8Length; i++) for (i = 0; uuid[i]; i ++)
{ {
gchar c = string.UTF8Characters[i]; gchar c = uuid[i];
if (c < 32 || c >= 127) if (c < 32 || c >= 127)
return FALSE; return FALSE;
@ -444,73 +463,8 @@ jsonify_variant (GVariant *variant,
} }
static gboolean static gboolean
parse_args (const gchar *format_str, plugin_list_extensions (PluginObject *obj,
uint32_t argc, NPVariant *result)
const NPVariant *argv,
...)
{
va_list args;
gsize i;
gboolean ret = FALSE;
if (strlen (format_str) != argc)
return FALSE;
va_start (args, argv);
for (i = 0; format_str[i]; i++)
{
gpointer arg_location;
const NPVariant arg = argv[i];
arg_location = va_arg (args, gpointer);
switch (format_str[i])
{
case 'u':
{
NPString string;
if (!NPVARIANT_IS_STRING (arg))
goto out;
string = NPVARIANT_TO_STRING (arg);
if (!uuid_is_valid (string))
goto out;
*(gchar **) arg_location = g_strndup (string.UTF8Characters, string.UTF8Length);
}
break;
case 'b':
if (!NPVARIANT_IS_BOOLEAN (arg))
goto out;
*(gboolean *) arg_location = NPVARIANT_TO_BOOLEAN (arg);
break;
case 'o':
if (!NPVARIANT_IS_OBJECT (arg))
goto out;
*(NPObject **) arg_location = NPVARIANT_TO_OBJECT (arg);
}
}
ret = TRUE;
out:
va_end (args);
return ret;
}
static gboolean
plugin_list_extensions (PluginObject *obj,
uint32_t argc,
const NPVariant *args,
NPVariant *result)
{ {
GError *error = NULL; GError *error = NULL;
GVariant *res; GVariant *res;
@ -534,159 +488,91 @@ plugin_list_extensions (PluginObject *obj,
} }
static gboolean static gboolean
plugin_enable_extension (PluginObject *obj, plugin_enable_extension (PluginObject *obj,
uint32_t argc, NPString uuid,
const NPVariant *argv, gboolean enabled)
NPVariant *result)
{ {
gboolean ret; gchar *uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
gchar *uuid; if (!uuid_is_valid (uuid_str))
gboolean enabled;
gsize length;
gchar **uuids;
const gchar **new_uuids;
if (!parse_args ("ub", argc, argv, &uuid, &enabled))
return FALSE;
uuids = g_settings_get_strv (obj->settings, ENABLED_EXTENSIONS_KEY);
length = g_strv_length (uuids);
if (enabled)
{ {
new_uuids = g_new (const gchar *, length + 2); /* New key, NULL */ g_free (uuid_str);
memcpy (new_uuids, uuids, length * sizeof (*new_uuids)); return FALSE;
new_uuids[length] = uuid;
new_uuids[length + 1] = NULL;
} }
else
{
gsize i = 0, j = 0;
new_uuids = g_new (const gchar *, length);
for (i = 0; i < length; i ++)
{
if (g_str_equal (uuids[i], uuid))
continue;
new_uuids[j] = uuids[i];
j++;
}
new_uuids[j] = NULL;
}
ret = g_settings_set_strv (obj->settings,
ENABLED_EXTENSIONS_KEY,
new_uuids);
g_strfreev (uuids);
g_free (new_uuids);
g_free (uuid);
return ret;
}
typedef struct _AsyncClosure AsyncClosure;
struct _AsyncClosure {
PluginObject *obj;
NPObject *callback;
NPObject *errback;
};
static void
install_extension_cb (GObject *proxy,
GAsyncResult *async_res,
gpointer user_data)
{
AsyncClosure *async_closure = (AsyncClosure *) user_data;
GError *error = NULL;
GVariant *res;
NPVariant args[1];
NPVariant result = { NPVariantType_Void };
NPObject *callback;
res = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), async_res, &error);
if (res == NULL)
{
if (g_dbus_error_is_remote_error (error))
g_dbus_error_strip_remote_error (error);
STRINGZ_TO_NPVARIANT (error->message, args[0]);
callback = async_closure->errback;
}
else
{
char *string_result;
g_variant_get (res, "(&s)", &string_result);
STRINGZ_TO_NPVARIANT (string_result, args[0]);
callback = async_closure->callback;
}
funcs.invokeDefault (async_closure->obj->instance,
callback, args, 1, &result);
funcs.releasevariantvalue (&result);
funcs.releaseobject (async_closure->callback);
funcs.releaseobject (async_closure->errback);
g_slice_free (AsyncClosure, async_closure);
}
static gboolean
plugin_install_extension (PluginObject *obj,
uint32_t argc,
const NPVariant *argv,
NPVariant *result)
{
gchar *uuid;
NPObject *callback, *errback;
AsyncClosure *async_closure;
if (!parse_args ("uoo", argc, argv, &uuid, &callback, &errback))
return FALSE;
async_closure = g_slice_new (AsyncClosure);
async_closure->obj = obj;
async_closure->callback = funcs.retainobject (callback);
async_closure->errback = funcs.retainobject (errback);
g_dbus_proxy_call (obj->proxy, g_dbus_proxy_call (obj->proxy,
"InstallRemoteExtension", (enabled ? "EnableExtension" : "DisableExtension"),
g_variant_new ("(s)", uuid), g_variant_new ("(s)", uuid_str),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
install_extension_cb, NULL, /* callback */
async_closure); NULL /* user_data */);
g_free (uuid); g_free (uuid_str);
return TRUE; return TRUE;
} }
static gboolean static gboolean
plugin_uninstall_extension (PluginObject *obj, plugin_install_extension (PluginObject *obj,
uint32_t argc, NPString uuid,
const NPVariant *argv, NPString version_tag)
NPVariant *result) {
gchar *uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
gchar *version_tag_str;
if (!uuid_is_valid (uuid_str))
{
g_free (uuid_str);
return FALSE;
}
version_tag_str = g_strndup (version_tag.UTF8Characters,
version_tag.UTF8Length);
g_dbus_proxy_call (obj->proxy,
"InstallRemoteExtension",
g_variant_new ("(ss)",
uuid_str,
version_tag_str),
G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */
NULL, /* cancellable */
NULL, /* callback */
NULL /* user_data */);
g_free (uuid_str);
g_free (version_tag_str);
return TRUE;
}
static gboolean
plugin_uninstall_extension (PluginObject *obj,
NPString uuid,
NPVariant *result)
{ {
GError *error = NULL; GError *error = NULL;
GVariant *res; GVariant *res;
gchar *uuid; gchar *uuid_str;
if (!parse_args ("u", argc, argv, &uuid)) uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
return FALSE; if (!uuid_is_valid (uuid_str))
{
g_free (uuid_str);
return FALSE;
}
res = g_dbus_proxy_call_sync (obj->proxy, res = g_dbus_proxy_call_sync (obj->proxy,
"UninstallExtension", "UninstallExtension",
g_variant_new ("(s)", uuid), g_variant_new ("(s)",
uuid_str),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
&error); &error);
g_free (uuid); g_free (uuid_str);
if (!res) if (!res)
{ {
@ -699,27 +585,30 @@ plugin_uninstall_extension (PluginObject *obj,
} }
static gboolean static gboolean
plugin_get_info (PluginObject *obj, plugin_get_info (PluginObject *obj,
uint32_t argc, NPString uuid,
const NPVariant *argv, NPVariant *result)
NPVariant *result)
{ {
GError *error = NULL; GError *error = NULL;
GVariant *res; GVariant *res;
gchar *uuid; gchar *uuid_str;
if (!parse_args ("u", argc, argv, &uuid)) uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
return FALSE; if (!uuid_is_valid (uuid_str))
{
g_free (uuid_str);
return FALSE;
}
res = g_dbus_proxy_call_sync (obj->proxy, res = g_dbus_proxy_call_sync (obj->proxy,
"GetExtensionInfo", "GetExtensionInfo",
g_variant_new ("(s)", uuid), g_variant_new ("(s)", uuid_str),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
&error); &error);
g_free (uuid); g_free (uuid_str);
if (!res) if (!res)
{ {
@ -732,26 +621,31 @@ plugin_get_info (PluginObject *obj,
} }
static gboolean static gboolean
plugin_get_errors (PluginObject *obj, plugin_get_errors (PluginObject *obj,
uint32_t argc, NPString uuid,
const NPVariant *argv, NPVariant *result)
NPVariant *result)
{ {
GError *error = NULL; GError *error = NULL;
GVariant *res; GVariant *res;
gchar *uuid; gchar *uuid_str;
if (!parse_args ("u", argc, argv, &uuid)) uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
return FALSE; if (!uuid_is_valid (uuid_str))
{
g_free (uuid_str);
return FALSE;
}
res = g_dbus_proxy_call_sync (obj->proxy, res = g_dbus_proxy_call_sync (obj->proxy,
"GetExtensionErrors", "GetExtensionErrors",
g_variant_new ("(s)", uuid), g_variant_new ("(s)", uuid_str),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
&error); &error);
g_free (uuid_str);
if (!res) if (!res)
{ {
g_warning ("Failed to retrieve errors: %s", error->message); g_warning ("Failed to retrieve errors: %s", error->message);
@ -763,25 +657,29 @@ plugin_get_errors (PluginObject *obj,
} }
static gboolean static gboolean
plugin_launch_extension_prefs (PluginObject *obj, plugin_launch_extension_prefs (PluginObject *obj,
uint32_t argc, NPString uuid,
const NPVariant *argv, NPVariant *result)
NPVariant *result)
{ {
gchar *uuid; gchar *uuid_str;
if (!parse_args ("u", argc, argv, &uuid)) uuid_str = g_strndup (uuid.UTF8Characters, uuid.UTF8Length);
return FALSE; if (!uuid_is_valid (uuid_str))
{
g_free (uuid_str);
return FALSE;
}
g_dbus_proxy_call (obj->proxy, g_dbus_proxy_call (obj->proxy,
"LaunchExtensionPrefs", "LaunchExtensionPrefs",
g_variant_new ("(s)", uuid), g_variant_new ("(s)", uuid_str),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
NULL, /* callback */ NULL, /* callback */
NULL /* user_data */); NULL /* user_data */);
g_free (uuid_str);
return TRUE; return TRUE;
} }
@ -835,40 +733,10 @@ plugin_get_shell_version (PluginObject *obj,
return ret; return ret;
} }
#define METHODS \
METHOD (list_extensions) \
METHOD (get_info) \
METHOD (enable_extension) \
METHOD (install_extension) \
METHOD (uninstall_extension) \
METHOD (get_errors) \
METHOD (launch_extension_prefs) \
/* */
#define METHOD(x) \
static NPIdentifier x##_id;
METHODS
#undef METHOD
static NPIdentifier api_version_id;
static NPIdentifier shell_version_id;
static NPIdentifier onextension_changed_id;
static NPIdentifier onrestart_id;
static bool
plugin_object_has_method (NPObject *npobj,
NPIdentifier name)
{
#define METHOD(x) (name == (x##_id)) ||
/* expands to (name == list_extensions_id) || FALSE; */
return METHODS FALSE;
#undef METHOD
}
static bool static bool
plugin_object_invoke (NPObject *npobj, plugin_object_invoke (NPObject *npobj,
NPIdentifier name, NPIdentifier name,
const NPVariant *argv, const NPVariant *args,
uint32_t argc, uint32_t argc,
NPVariant *result) NPVariant *result)
{ {
@ -880,13 +748,61 @@ plugin_object_invoke (NPObject *npobj,
VOID_TO_NPVARIANT (*result); VOID_TO_NPVARIANT (*result);
#define METHOD(x) \ if (!plugin_object_has_method (npobj, name))
if (name == x##_id) \ return FALSE;
return plugin_##x (obj, argc, argv, result);
METHODS
#undef METHOD
return FALSE; if (name == list_extensions_id)
return plugin_list_extensions (obj, result);
else if (name == get_info_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
return plugin_get_info (obj, NPVARIANT_TO_STRING(args[0]), result);
}
else if (name == enable_extension_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
if (!NPVARIANT_IS_BOOLEAN(args[1])) return FALSE;
return plugin_enable_extension (obj,
NPVARIANT_TO_STRING(args[0]),
NPVARIANT_TO_BOOLEAN(args[1]));
}
else if (name == install_extension_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
if (!NPVARIANT_IS_STRING(args[1])) return FALSE;
return plugin_install_extension (obj,
NPVARIANT_TO_STRING(args[0]),
NPVARIANT_TO_STRING(args[1]));
}
else if (name == uninstall_extension_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
return plugin_uninstall_extension (obj,
NPVARIANT_TO_STRING(args[0]),
result);
}
else if (name == get_errors_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
return plugin_get_errors (obj,
NPVARIANT_TO_STRING(args[0]),
result);
}
else if (name == launch_extension_prefs_id)
{
if (!NPVARIANT_IS_STRING(args[0])) return FALSE;
return plugin_launch_extension_prefs (obj,
NPVARIANT_TO_STRING(args[0]),
result);
}
return TRUE;
} }
static bool static bool
@ -1030,12 +946,3 @@ NPP_GetValue(NPP instance,
return NPERR_NO_ERROR; return NPERR_NO_ERROR;
} }
/* Opera tries to call NPP_SetWindow without checking the
* NULL pointer beforehand. */
NPError
NPP_SetWindow(NPP instance,
NPWindow *window)
{
return NPERR_NO_ERROR;
}

View File

@ -1,5 +1,5 @@
AC_PREREQ(2.63) AC_PREREQ(2.63)
AC_INIT([gnome-shell],[3.6.3.1],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell]) AC_INIT([gnome-shell],[3.4.1],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
AC_CONFIG_SRCDIR([src/shell-global.c]) AC_CONFIG_SRCDIR([src/shell-global.c])
@ -36,8 +36,6 @@ AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE",
PKG_PROG_PKG_CONFIG([0.22]) PKG_PROG_PKG_CONFIG([0.22])
AC_PATH_PROG([XSLTPROC], [xsltproc])
GLIB_GSETTINGS GLIB_GSETTINGS
# Get a value to substitute into gnome-shell.in # Get a value to substitute into gnome-shell.in
@ -46,15 +44,15 @@ AC_SUBST(PYTHON)
# We need at least this, since gst_plugin_register_static() was added # We need at least this, since gst_plugin_register_static() was added
# in 0.10.16, but nothing older than 0.10.21 has been tested. # in 0.10.16, but nothing older than 0.10.21 has been tested.
GSTREAMER_MIN_VERSION=0.11.92 GSTREAMER_MIN_VERSION=0.10.16
recorder_modules= recorder_modules=
build_recorder=false build_recorder=false
AC_MSG_CHECKING([for GStreamer (needed for recording functionality)]) AC_MSG_CHECKING([for GStreamer (needed for recording functionality)])
if $PKG_CONFIG --exists gstreamer-1.0 '>=' $GSTREAMER_MIN_VERSION ; then if $PKG_CONFIG --exists gstreamer-0.10 '>=' $GSTREAMER_MIN_VERSION ; then
AC_MSG_RESULT(yes) AC_MSG_RESULT(yes)
build_recorder=true build_recorder=true
recorder_modules="gstreamer-1.0 gstreamer-base-1.0 x11" recorder_modules="gstreamer-0.10 gstreamer-base-0.10 x11"
PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0 xfixes gl) PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0 xfixes gl)
else else
AC_MSG_RESULT(no) AC_MSG_RESULT(no)
@ -62,32 +60,30 @@ fi
AM_CONDITIONAL(BUILD_RECORDER, $build_recorder) AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
CLUTTER_MIN_VERSION=1.11.11 CLUTTER_MIN_VERSION=1.9.16
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1 GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
GJS_MIN_VERSION=1.33.2 GJS_MIN_VERSION=1.29.18
MUTTER_MIN_VERSION=3.6.2 MUTTER_MIN_VERSION=3.4.1
FOLKS_MIN_VERSION=0.5.2
GTK_MIN_VERSION=3.3.9 GTK_MIN_VERSION=3.3.9
GIO_MIN_VERSION=2.31.6 GIO_MIN_VERSION=2.31.6
LIBECAL_MIN_VERSION=3.5.3 LIBECAL_MIN_VERSION=2.32.0
LIBEDATASERVER_MIN_VERSION=3.5.3 LIBEDATASERVER_MIN_VERSION=1.2.0
LIBEDATASERVERUI_MIN_VERSION=3.5.3 LIBEDATASERVERUI_MIN_VERSION=2.91.6
TELEPATHY_GLIB_MIN_VERSION=0.17.5 TELEPATHY_GLIB_MIN_VERSION=0.17.5
TELEPATHY_LOGGER_MIN_VERSION=0.2.4 TELEPATHY_LOGGER_MIN_VERSION=0.2.4
POLKIT_MIN_VERSION=0.100 POLKIT_MIN_VERSION=0.100
STARTUP_NOTIFICATION_MIN_VERSION=0.11 STARTUP_NOTIFICATION_MIN_VERSION=0.11
GCR_MIN_VERSION=3.3.90 GCR_MIN_VERSION=3.3.90
GNOME_DESKTOP_REQUIRED_VERSION=3.5.1
GNOME_MENUS_REQUIRED_VERSION=3.5.3
# Collect more than 20 libraries for a prize! # Collect more than 20 libraries for a prize!
PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
libxml-2.0 libxml-2.0
gtk+-3.0 >= $GTK_MIN_VERSION gtk+-3.0 >= $GTK_MIN_VERSION
atk-bridge-2.0 folks >= $FOLKS_MIN_VERSION
libmutter >= $MUTTER_MIN_VERSION libmutter >= $MUTTER_MIN_VERSION
gjs-internals-1.0 >= $GJS_MIN_VERSION gjs-internals-1.0 >= $GJS_MIN_VERSION
libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION libgnome-menu-3.0 $recorder_modules
$recorder_modules
gdk-x11-3.0 libsoup-2.4 gdk-x11-3.0 libsoup-2.4
gl gl
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
@ -99,8 +95,7 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
telepathy-logger-0.2 >= $TELEPATHY_LOGGER_MIN_VERSION telepathy-logger-0.2 >= $TELEPATHY_LOGGER_MIN_VERSION
polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes
libnm-glib libnm-util gnome-keyring-1 libnm-glib libnm-util gnome-keyring-1
gcr-3 >= $GCR_MIN_VERSION gcr-3 >= $GCR_MIN_VERSION)
gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VERSION)
PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0) PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0)
@ -108,8 +103,9 @@ PKG_CHECK_MODULES(SHELL_HOTPLUG_SNIFFER, gio-2.0 gdk-pixbuf-2.0)
PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2) PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2)
GNOME_KEYBINDINGS_KEYSDIR=`$PKG_CONFIG --variable keysdir gnome-keybindings` GJS_VERSION=`$PKG_CONFIG --modversion gjs-internals-1.0`
AC_SUBST([GNOME_KEYBINDINGS_KEYSDIR]) AC_DEFINE_UNQUOTED([GJS_VERSION], ["$GJS_VERSION"], [The version of GJS we're linking to])
AC_SUBST([GJS_VERSION], ["$GJS_VERSION"])
GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION]) GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION])
@ -125,7 +121,7 @@ PKG_CHECK_MODULES(GNOME_SHELL_JS, gio-2.0 gjs-internals-1.0 >= $GJS_MIN_VERSION)
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.2 x11) PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.2 x11)
PKG_CHECK_MODULES(TRAY, gtk+-3.0) PKG_CHECK_MODULES(TRAY, gtk+-3.0)
PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0) PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0)
PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.5.4) PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 0.1.7)
AC_MSG_CHECKING([for bluetooth support]) AC_MSG_CHECKING([for bluetooth support])
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0], PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0],
@ -141,7 +137,7 @@ PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0],
AC_SUBST([HAVE_BLUETOOTH],[0]) AC_SUBST([HAVE_BLUETOOTH],[0])
AC_MSG_RESULT([no])]) AC_MSG_RESULT([no])])
PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION gio-2.0) PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION libedataserverui-3.0 >= $LIBEDATASERVERUI_MIN_VERSION gio-2.0)
AC_SUBST(CALENDAR_SERVER_CFLAGS) AC_SUBST(CALENDAR_SERVER_CFLAGS)
AC_SUBST(CALENDAR_SERVER_LIBS) AC_SUBST(CALENDAR_SERVER_LIBS)
@ -210,18 +206,6 @@ AC_SUBST(TYPELIBDIR)
GTK_DOC_CHECK([1.15], [--flavour no-tmpl]) GTK_DOC_CHECK([1.15], [--flavour no-tmpl])
AC_ARG_ENABLE(man,
[AS_HELP_STRING([--enable-man],
[generate man pages [default=yes]])],,
enable_man=yes)
if test "$enable_man" != no; then
AC_PATH_PROG([XSLTPROC], [xsltproc])
if test -z "$XSLTPROC"; then
AC_MSG_ERROR([xsltproc is required for --enable-man])
fi
fi
AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no)
# Stay command-line compatible with the gnome-common configure option. Here # Stay command-line compatible with the gnome-common configure option. Here
# minimum/yes/maximum are the same, however. # minimum/yes/maximum are the same, however.
AC_ARG_ENABLE(compile_warnings, AC_ARG_ENABLE(compile_warnings,
@ -253,6 +237,31 @@ AC_ARG_ENABLE(jhbuild-wrapper-script,
AS_HELP_STRING([--enable-jhbuild-wrapper-script],[Make "gnome-shell" script work for jhbuild]),,enable_jhbuild_wrapper_script=no) AS_HELP_STRING([--enable-jhbuild-wrapper-script],[Make "gnome-shell" script work for jhbuild]),,enable_jhbuild_wrapper_script=no)
AM_CONDITIONAL(USE_JHBUILD_WRAPPER_SCRIPT, test "x$enable_jhbuild_wrapper_script" = xyes) AM_CONDITIONAL(USE_JHBUILD_WRAPPER_SCRIPT, test "x$enable_jhbuild_wrapper_script" = xyes)
AC_MSG_CHECKING([location of system Certificate Authority list])
AC_ARG_WITH(ca-certificates,
[AC_HELP_STRING([--with-ca-certificates=@<:@path@:>@],
[path to system Certificate Authority list])])
if test "$with_ca_certificates" = "no"; then
AC_MSG_RESULT([disabled])
else
if test -z "$with_ca_certificates"; then
for f in /etc/pki/tls/certs/ca-bundle.crt \
/etc/ssl/certs/ca-certificates.crt; do
if test -f "$f"; then
with_ca_certificates="$f"
fi
done
if test -z "$with_ca_certificates"; then
AC_MSG_ERROR([could not find. Use --with-ca-certificates=path to set, or --without-ca-certificates to disable])
fi
fi
AC_MSG_RESULT($with_ca_certificates)
AC_DEFINE_UNQUOTED(SHELL_SYSTEM_CA_FILE, ["$with_ca_certificates"], [The system TLS CA list])
fi
AC_SUBST(SHELL_SYSTEM_CA_FILE,["$with_ca_certificates"])
BROWSER_PLUGIN_DIR="${BROWSER_PLUGIN_DIR:-"\${libdir}/mozilla/plugins"}" BROWSER_PLUGIN_DIR="${BROWSER_PLUGIN_DIR:-"\${libdir}/mozilla/plugins"}"
AC_ARG_VAR([BROWSER_PLUGIN_DIR],[Where to install the plugin to]) AC_ARG_VAR([BROWSER_PLUGIN_DIR],[Where to install the plugin to])
@ -266,7 +275,6 @@ AC_CONFIG_FILES([
docs/reference/st/Makefile docs/reference/st/Makefile
docs/reference/st/st-docs.sgml docs/reference/st/st-docs.sgml
js/Makefile js/Makefile
src/calendar-server/evolution-calendar.desktop.in
src/Makefile src/Makefile
browser-plugin/Makefile browser-plugin/Makefile
tests/Makefile tests/Makefile

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<KeyListEntries schema="org.gnome.shell.keybindings"
group="system"
_name="Screenshots"
wm_name="GNOME Shell"
package="gnome-shell">
<KeyListEntry name="toggle-recording"
_description="Record a screencast"/>
</KeyListEntries>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<KeyListEntries schema="org.gnome.shell.keybindings"
group="system"
_name="System"
wm_name="GNOME Shell"
package="gnome-shell">
<KeyListEntry name="toggle-message-tray"
_description="Show the message tray"/>
</KeyListEntries>

View File

@ -8,7 +8,14 @@ desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
-e "s|@VERSION[@]|$(VERSION)|" \ -e "s|@VERSION[@]|$(VERSION)|" \
$< > $@ || rm $@ $< > $@ || rm $@
@INTLTOOL_DESKTOP_RULE@ # Placeholder until we add intltool
%.desktop:%.desktop.in
$(AM_V_GEN) sed s/^_// < $< > $@ || rm $@
searchprovidersdir = $(pkgdatadir)/open-search-providers
dist_searchproviders_DATA = \
open-search-providers/google.xml \
open-search-providers/wikipedia.xml
introspectiondir = $(datadir)/dbus-1/interfaces introspectiondir = $(datadir)/dbus-1/interfaces
introspection_DATA = org.gnome.ShellSearchProvider.xml introspection_DATA = org.gnome.ShellSearchProvider.xml
@ -29,39 +36,26 @@ dist_theme_DATA = \
theme/dash-placeholder.svg \ theme/dash-placeholder.svg \
theme/filter-selected-ltr.svg \ theme/filter-selected-ltr.svg \
theme/filter-selected-rtl.svg \ theme/filter-selected-rtl.svg \
theme/gdm.css \
theme/gnome-shell.css \ theme/gnome-shell.css \
theme/logged-in-indicator.svg \
theme/message-tray-background.png \
theme/noise-texture.png \
theme/panel-button-border.svg \ theme/panel-button-border.svg \
theme/panel-button-highlight-narrow.svg \ theme/panel-button-highlight-narrow.svg \
theme/panel-button-highlight-wide.svg \ theme/panel-button-highlight-wide.svg \
theme/process-working.svg \ theme/process-working.svg \
theme/running-indicator.svg \ theme/running-indicator.svg \
theme/scroll-hhandle.svg \
theme/scroll-vhandle.svg \
theme/source-button-border.svg \ theme/source-button-border.svg \
theme/summary-counter.svg \
theme/toggle-off-us.svg \ theme/toggle-off-us.svg \
theme/toggle-off-intl.svg \ theme/toggle-off-intl.svg \
theme/toggle-on-us.svg \ theme/toggle-on-us.svg \
theme/toggle-on-intl.svg \ theme/toggle-on-intl.svg \
theme/ws-switch-arrow-up.png \ theme/ws-switch-arrow-up.svg \
theme/ws-switch-arrow-down.png theme/ws-switch-arrow-down.svg
keysdir = @GNOME_KEYBINDINGS_KEYSDIR@ gsettings_SCHEMAS = org.gnome.shell.gschema.xml org.gnome.shell.evolution.calendar.gschema.xml
keys_in_files = \
50-gnome-shell-screenshot.xml.in \
50-gnome-shell-system.xml.in \
$(NULL)
keys_DATA = $(keys_in_files:.xml.in=.xml)
gsettings_SCHEMAS = org.gnome.shell.gschema.xml
@INTLTOOL_XML_NOMERGE_RULE@ @INTLTOOL_XML_NOMERGE_RULE@
%.gschema.xml.in: %.gschema.xml.in.in Makefile
$(AM_V_GEN) sed -e 's|@GETTEXT_PACKAGE[@]|$(GETTEXT_PACKAGE)|g' \
$< > $@ || rm $@
@GSETTINGS_RULES@ @GSETTINGS_RULES@
# We need to compile schemas at make time # We need to compile schemas at make time
@ -74,21 +68,25 @@ all-local: gschemas.compiled
convertdir = $(datadir)/GConf/gsettings convertdir = $(datadir)/GConf/gsettings
convert_DATA = gnome-shell-overrides.convert convert_DATA = gnome-shell-overrides.convert
shadersdir = $(pkgdatadir)/shaders
shaders_DATA = \
shaders/dim-window.glsl
EXTRA_DIST = \ EXTRA_DIST = \
gnome-shell.desktop.in.in \ gnome-shell.desktop.in.in \
gnome-shell-extension-prefs.desktop.in.in \ gnome-shell-extension-prefs.desktop.in.in \
$(introspection_DATA) \ $(introspection_DATA) \
$(menu_DATA) \ $(menu_DATA) \
$(shaders_DATA) \
$(convert_DATA) \ $(convert_DATA) \
$(keys_in_files) \ org.gnome.shell.evolution.calendar.gschema.xml.in \
org.gnome.shell.gschema.xml.in.in org.gnome.shell.gschema.xml.in
CLEANFILES = \ CLEANFILES = \
gnome-shell.desktop.in \ gnome-shell.desktop.in \
gnome-shell-extension-prefs.in \ gnome-shell-extension-prefs.in \
$(desktop_DATA) \ $(desktop_DATA) \
$(keys_DATA) \
$(gsettings_SCHEMAS) \ $(gsettings_SCHEMAS) \
gschemas.compiled \ gschemas.compiled
org.gnome.shell.gschema.valid \
org.gnome.shell.gschema.xml.in

View File

@ -0,0 +1,7 @@
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Google</ShortName>
<Description>Google Search</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA</Image>
<Url type="text/html" method="GET" template="http://www.google.com/search?q={searchTerms}"/>
</OpenSearchDescription>

View File

@ -0,0 +1,44 @@
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Wikipedia</ShortName>
<Description>Wikipedia, the free encyclopedia</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAEAgQAhIOEAMjHyABIR0gA6ejpAGlqaQCpqKkAKCgoAPz9%2FAAZGBkAmJiYANjZ2ABXWFcAent6ALm6uQA8OjwAiIiIiIiIiIiIiI4oiL6IiIiIgzuIV4iIiIhndo53KIiIiB%2FWvXoYiIiIfEZfWBSIiIEGi%2FfoqoiIgzuL84i9iIjpGIoMiEHoiMkos3FojmiLlUipYliEWIF%2BiDe0GoRa7D6GPbjcu1yIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
<Url type="text/html" method="GET" template="http://{language}.wikipedia.org/wiki/Special:Search?search={searchTerms}"/>
<!-- The criterion for being below is being listed with more than 100,000
articles on http://meta.wikimedia.org/wiki/List_of_Wikipedias -->
<Language>ar</Language>
<Language>bg</Language>
<Language>ca</Language>
<Language>cs</Language>
<Language>da</Language>
<Language>de</Language>
<Language>en</Language>
<Language>eo</Language>
<Language>es</Language>
<Language>fa</Language>
<Language>fi</Language>
<Language>fr</Language>
<Language>he</Language>
<Language>hu</Language>
<Language>id</Language>
<Language>it</Language>
<Language>ja</Language>
<Language>ko</Language>
<Language>lt</Language>
<Language>nl</Language>
<Language>no</Language>
<Language>pl</Language>
<Language>pt</Language>
<Language>ro</Language>
<Language>ru</Language>
<Language>sk</Language>
<Language>sl</Language>
<Language>sr</Language>
<Language>sv</Language>
<Language>tr</Language>
<Language>uk</Language>
<Language>vi</Language>
<Language>vo</Language>
<Language>war</Language>
<Language>zh</Language>
</OpenSearchDescription>

View File

@ -108,7 +108,7 @@
</doc:summary> </doc:summary>
</doc:doc> </doc:doc>
</arg> </arg>
<arg type="aa{sv}" direction="out"> <arg type="a{sv}" direction="out">
<doc:doc> <doc:doc>
<doc:summary> <doc:summary>
<doc:para> <doc:para>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- NOTE: This schema is a GNOME 3.4 workaround - it uses the same path
as org.gnome.evolution.calendar, but avoids us requiring Evolution
be installed. In GNOME 3.6 the selected state will become a flag
on the calendar. Because the translations are in Evolution,
this is untranslated and in POTFILES.skip.
-->
<schemalist>
<schema path="/org/gnome/evolution/calendar/" id="org.gnome.shell.evolution.calendar" gettext-domain="evolution">
<key type="as" name="selected-calendars">
<default>[]</default>
<summary>List of selected calendars</summary>
<description>List of calendars to load</description>
</key>
<key type="as" name="selected-tasks">
<default>[]</default>
<summary>List of selected task lists</summary>
<description>List of task lists to load</description>
</key>
</schema>
</schemalist>

View File

@ -39,6 +39,10 @@
will be displayed in the favorites area. will be displayed in the favorites area.
</_description> </_description>
</key> </key>
<key name="disabled-open-search-providers" type="as">
<default>[]</default>
<_summary>disabled OpenSearch providers</_summary>
</key>
<key name="command-history" type="as"> <key name="command-history" type="as">
<default>[]</default> <default>[]</default>
<_summary>History for command (Alt-F2) dialog</_summary> <_summary>History for command (Alt-F2) dialog</_summary>
@ -57,14 +61,7 @@ value here is from the TpConnectionPresenceType enumeration.</_summary>
<_summary>Internally used to store the last session presence status for the user. The <_summary>Internally used to store the last session presence status for the user. The
value here is from the GsmPresenceStatus enumeration.</_summary> value here is from the GsmPresenceStatus enumeration.</_summary>
</key> </key>
<key name="always-show-log-out" type="b"> <child name="clock" schema="org.gnome.shell.clock"/>
<default>false</default>
<_summary>Always show the 'Log out' menuitem in the user menu.</_summary>
<_description>
This key overrides the automatic hiding of the 'Log out'
menuitem in single-user, single-session situations.
</_description>
</key>
<child name="calendar" schema="org.gnome.shell.calendar"/> <child name="calendar" schema="org.gnome.shell.calendar"/>
<child name="recorder" schema="org.gnome.shell.recorder"/> <child name="recorder" schema="org.gnome.shell.recorder"/>
<child name="keybindings" schema="org.gnome.shell.keybindings"/> <child name="keybindings" schema="org.gnome.shell.keybindings"/>
@ -91,13 +88,6 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
Keybinding to open the application menu. Keybinding to open the application menu.
</_description> </_description>
</key> </key>
<key name="toggle-message-tray" type="as">
<default>["&lt;Super&gt;m"]</default>
<_summary>Keybinding to toggle the visibility of the message tray</_summary>
<_description>
Keybinding to toggle the visibility of the message tray.
</_description>
</key>
<key name="toggle-recording" type="as"> <key name="toggle-recording" type="as">
<default><![CDATA[['<Control><Shift><Alt>r']]]></default> <default><![CDATA[['<Control><Shift><Alt>r']]]></default>
<_summary>Keybinding to toggle the screen recorder</_summary> <_summary>Keybinding to toggle the screen recorder</_summary>
@ -118,6 +108,24 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
</key> </key>
</schema> </schema>
<schema id="org.gnome.shell.clock" path="/org/gnome/shell/clock/"
gettext-domain="@GETTEXT_PACKAGE@">
<key name="show-seconds" type="b">
<default>false</default>
<_summary>Show time with seconds</_summary>
<_description>
If true, display seconds in time.
</_description>
</key>
<key name="show-date" type="b">
<default>false</default>
<_summary>Show date in clock</_summary>
<_description>
If true, display date in the clock, in addition to time.
</_description>
</key>
</schema>
<schema id="org.gnome.shell.recorder" path="/org/gnome/shell/recorder/" <schema id="org.gnome.shell.recorder" path="/org/gnome/shell/recorder/"
gettext-domain="@GETTEXT_PACKAGE@"> gettext-domain="@GETTEXT_PACKAGE@">
<key name="framerate" type="i"> <key name="framerate" type="i">
@ -140,7 +148,7 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
take care of its own output - this might be used to send the output take care of its own output - this might be used to send the output
to an icecast server via shout2send or similar. When unset or set to an icecast server via shout2send or similar. When unset or set
to an empty value, the default pipeline will be used. This is currently to an empty value, the default pipeline will be used. This is currently
'vp8enc min_quantizer=13 max_quantizer=13 cpu-used=5 deadline=1000000 threads=%T ! queue ! webmmux' 'vp8enc quality=8 speed=6 threads=%T ! queue ! webmmux'
and records to WEBM using the VP8 codec. %T is used as a placeholder and records to WEBM using the VP8 codec. %T is used as a placeholder
for a guess at the optimal thread count on the system. for a guess at the optimal thread count on the system.
</_description> </_description>
@ -198,13 +206,5 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
This key overrides the key in org.gnome.mutter when running GNOME Shell. This key overrides the key in org.gnome.mutter when running GNOME Shell.
</description> </description>
</key> </key>
<key name="focus-change-on-pointer-rest" type="b">
<default>true</default>
<summary>Delay focus changes in mouse mode until the pointer stops moving</summary>
<description>
This key overrides the key in org.gnome.mutter when running GNOME Shell.
</description>
</key>
</schema> </schema>
</schemalist> </schemalist>

View File

@ -0,0 +1,27 @@
#version 110
uniform sampler2D tex;
uniform float fraction;
uniform float height;
const float c = -0.2;
const float border_max_height = 60.0;
mat4 contrast = mat4 (1.0 + c, 0.0, 0.0, 0.0,
0.0, 1.0 + c, 0.0, 0.0,
0.0, 0.0, 1.0 + c, 0.0,
0.0, 0.0, 0.0, 1.0);
vec4 off = vec4(0.633, 0.633, 0.633, 0);
void main()
{
vec4 color = texture2D(tex, cogl_tex_coord_in[0].xy);
float y = height * cogl_tex_coord_in[0].y;
// To reduce contrast, blend with a mid gray
cogl_color_out = color * contrast - off * c * color.a;
// We only fully dim at a distance of BORDER_MAX_HEIGHT from the top and
// when the fraction is 1.0. For other locations and fractions we linearly
// interpolate back to the original undimmed color, so the top of the window
// is at full color.
cogl_color_out = color + (cogl_color_out - color) * max(min(y / border_max_height, 1.0), 0.0);
cogl_color_out = color + (cogl_color_out - color) * fraction;
}

View File

@ -10,11 +10,11 @@
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="29" width="28"
height="29" height="25"
id="svg10621" id="svg10621"
version="1.1" version="1.1"
inkscape:version="0.48.2 r9819" inkscape:version="0.48.1 r9760"
sodipodi:docname="calendar-today.svg"> sodipodi:docname="calendar-today.svg">
<defs <defs
id="defs10623"> id="defs10623">
@ -118,17 +118,6 @@
fx="51" fx="51"
fy="30" fy="30"
r="42" /> r="42" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient34508-1-3"
id="radialGradient3113"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.72146227,0,0,0.27484277,14.205424,21.754717)"
cx="51"
cy="30"
fx="51"
fy="30"
r="42" />
</defs> </defs>
<sodipodi:namedview <sodipodi:namedview
id="base" id="base"
@ -138,29 +127,20 @@
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="15.839192" inkscape:zoom="15.839192"
inkscape:cx="20.652108" inkscape:cx="8.3750933"
inkscape:cy="11.839084" inkscape:cy="8.0837211"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="true" showgrid="false"
fit-margin-top="0" fit-margin-top="0"
fit-margin-left="0" fit-margin-left="0"
fit-margin-right="0" fit-margin-right="0"
fit-margin-bottom="0" fit-margin-bottom="0"
inkscape:window-width="1280" inkscape:window-width="1440"
inkscape:window-height="741" inkscape:window-height="843"
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="27" inkscape:window-y="26"
inkscape:window-maximized="1" inkscape:window-maximized="1" />
borderlayer="true">
<inkscape:grid
type="xygrid"
id="grid3109"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata <metadata
id="metadata10626"> id="metadata10626">
<rdf:RDF> <rdf:RDF>
@ -177,28 +157,31 @@
inkscape:label="Layer 1" inkscape:label="Layer 1"
inkscape:groupmode="layer" inkscape:groupmode="layer"
id="layer1" id="layer1"
transform="translate(-469.08263,-532.99307)"> transform="translate(-469.08263,-536.99307)">
<path <g
sodipodi:type="arc" id="g3003">
style="opacity:0.4625;color:#000000;fill:url(#radialGradient3113);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" <path
id="path34506-3" inkscape:export-ydpi="90"
sodipodi:cx="51" inkscape:export-xdpi="90"
sodipodi:cy="30" inkscape:export-filename="/home/jimmac/src/cvs/gnome/gnome-shell-design/mockups/motion/textures/panel.png"
sodipodi:rx="42" transform="matrix(0.43692393,0,0,1.3783114,460.60467,517.48289)"
sodipodi:ry="16" sodipodi:end="6.2831853"
d="M 9,29.999999 A 42,16 0 0 1 93,30 l -42,0 z" sodipodi:start="3.1415927"
sodipodi:start="3.1415927" d="M 9,29.999999 C 9.0000011,21.163443 27.804042,14 51.000002,14 74.195961,14 93,21.163444 93,30 l -42,0 z"
sodipodi:end="6.2831853" sodipodi:ry="16"
transform="matrix(0.43692393,0,0,1.3783114,461.29951,517.6437)" sodipodi:rx="42"
inkscape:export-filename="/home/jimmac/src/cvs/gnome/gnome-shell-design/mockups/motion/textures/panel.png" sodipodi:cy="30"
inkscape:export-xdpi="90" sodipodi:cx="51"
inkscape:export-ydpi="90" /> id="path34506-3"
<rect style="opacity:0.4625;color:#000000;fill:url(#radialGradient2997);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
style="fill:#ffffff;fill-opacity:0.50196078;stroke-width:0.43599999;stroke-miterlimit:4;stroke-dasharray:none" sodipodi:type="arc" />
id="rect2996" <rect
width="31" y="558.85046"
height="3" x="468.96878"
x="468.08264" height="3.1425927"
y="558.99304" /> width="28.149134"
id="rect2996"
style="fill:#ffffff;fill-opacity:0.50196078;stroke-width:0.43599999;stroke-miterlimit:4;stroke-dasharray:none" />
</g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

180
data/theme/gdm.css Normal file
View File

@ -0,0 +1,180 @@
/* Copyright 2011, Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Login Dialog */
.login-dialog-title {
font-size: 14pt;
font-weight: bold;
color: #666666;
padding-bottom: 2em;
}
.login-dialog {
border-radius: 16px;
min-height: 150px;
max-height: 700px;
min-width: 350px;
}
.login-dialog-prompt-fingerprint-message {
font-size: 10.5pt;
}
.login-dialog-user-list-view {
-st-vfade-offset: 1em;
}
.login-dialog-user-list {
spacing: 12px;
}
.login-dialog-user-list-item {
color: #666666;
}
.login-dialog-user-list-item:ltr {
padding-right: 1em;
}
.login-dialog-user-list-item:rtl {
padding-left: 1em;
}
.login-dialog-user-list-item .login-dialog-user-list-item-name {
font-size: 20pt;
padding-left: 1em;
color: #666666;
}
.login-dialog-user-list-item:hover .login-dialog-user-list-item-name {
color: white;
}
.login-dialog-user-list-item:focus .login-dialog-user-list-item-name {
color: white;
text-shadow: black 0px 2px 2px;
}
.login-dialog-user-list-item-vertical-layout {
spacing: 2px;
}
.login-dialog-user-list-item .login-dialog-user-list-item-focus-bin {
background-color: rgba(0,0,0,0.0);
height: 2px;
}
.login-dialog-user-list-item:focus .login-dialog-user-list-item-focus-bin {
background-color: #666666;
}
.login-dialog-user-list-item-icon {
border: 2px solid #8b8b8b;
border-radius: 8px;
width: 64px;
height: 64px;
}
.login-dialog-not-listed-button {
padding-top: 2em;
}
.login-dialog-not-listed-label {
font-size: 14pt;
font-weight: bold;
color: #666666;
}
.login-dialog-not-listed-button:hover .login-dialog-not-listed-label {
color: white;
}
.login-dialog-prompt-layout {
padding-bottom: 32px;
}
.login-dialog-prompt-label {
color: white;
font-size: 20pt;
}
.login-dialog-prompt-entry {
padding: 4px;
border-radius: 4px;
border: 2px solid #5656cc;
color: black;
background-color: white;
caret-color: black;
caret-size: 1px;
width: 15em;
}
.login-dialog-prompt-entry .capslock-warning {
icon-size: 16px;
warning-color: #999;
}
.login-dialog-prompt-entry:insensitive {
color: rgba(0,0,0,0.7);
border: 2px solid #565656;
}
.login-dialog-session-list {
color: #ffffff;
font-size: 10.5pt;
}
.login-dialog-session-list-button {
padding: 4px;
}
.login-dialog-session-list-button:focus {
background-color: #4c4c4c;
}
.login-dialog-session-list-button:active {
background-color: #4c4c4c;
}
.login-dialog-session-list-button:hover {
font-weight: bold;
}
.login-dialog-session-list-scroll-view {
background-gradient-start: rgba(80,80,80,0.3);
background-gradient-end: rgba(80,80,80,0.7);
background-gradient-direction: vertical;
box-shadow: inset 0px 2px 4px rgba(0,0,0,0.9);
border-radius: 8px;
border: 1px solid rgba(80,80,80,1.0);
padding: .5em;
}
.login-dialog-session-list-item:focus {
background-color: #666666;
}
.login-dialog-session-list-triangle {
padding-right: .5em;
}
.login-dialog-session-list-item-box {
spacing: .25em;
}
.login-dialog-session-list-item-dot {
width: .75em;
height: .75em;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,130 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
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"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="300"
height="80"
id="svg7355"
version="1.1"
inkscape:version="0.48.2 r9819"
sodipodi:docname="logged-in-indicator.svg">
<metadata
id="metadata4175">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#2c1cff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:window-width="1440"
inkscape:window-height="843"
id="namedview4173"
showgrid="false"
inkscape:zoom="2.8760889"
inkscape:cx="106.00403"
inkscape:cy="80.68078"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="g30864" />
<defs
id="defs7357">
<radialGradient
xlink:href="#linearGradient36429"
id="radialGradient7461"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.5919312,0,0,0.57582113,-20.687059,48.400487)"
cx="47.428951"
cy="167.16817"
fx="47.428951"
fy="167.16817"
r="37" />
<linearGradient
id="linearGradient36429">
<stop
id="stop36431"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop36433"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<radialGradient
xlink:href="#linearGradient36471"
id="radialGradient7463"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1891549,0,0,0.55513246,-9.281289,36.12653)"
cx="49.067139"
cy="242.50381"
fx="49.067139"
fy="242.50381"
r="37.00671" />
<linearGradient
id="linearGradient36471">
<stop
id="stop36473"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop36475"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<radialGradient
r="37.00671"
fy="242.50381"
fx="49.067139"
cy="242.50381"
cx="49.067139"
gradientTransform="matrix(3.4218418,0,0,0.03365337,-61.309005,138.5071)"
gradientUnits="userSpaceOnUse"
id="radialGradient7488"
xlink:href="#linearGradient36471" />
</defs>
<g
id="layer1"
transform="matrix(1.6213276,0,0,1.6213276,-431.6347,-272.5745)">
<g
style="display:inline"
id="g30864"
transform="translate(255.223,70.118091)">
<rect
ry="3.4593496"
rx="8.8641119"
y="76.159348"
x="12.596948"
height="71.116341"
width="182.22595"
id="rect14000"
style="opacity:0.371875;fill:url(#radialGradient7461);fill-opacity:1;stroke:none" />
<path
id="rect34520"
d="m 194.80022,146.83551 -182.559919,0"
style="opacity:0.35;fill:none;stroke:url(#radialGradient7488);stroke-width:0.61184424;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
connector-curvature="0"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

View File

@ -9,7 +9,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="17" width="21"
height="10" height="10"
id="svg2" id="svg2"
version="1.1" version="1.1"
@ -66,9 +66,9 @@
<rect <rect
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke-width:0.43599999;stroke-miterlimit:4;stroke-dasharray:none" style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke-width:0.43599999;stroke-miterlimit:4;stroke-dasharray:none"
id="rect3796" id="rect3796"
width="7" width="3"
height="2" height="2"
x="5" x="9"
y="8" /> y="8" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
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"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="10"
height="4"
id="svg2"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="scroll-hhandle.svg">
<defs
id="defs4">
</defs>
<metadata
id="metadata7">
<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 />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="rect3592"
width="2"
height="4"
x="0"
y="0"
rx="0"
ry="0" />
<use
x="0"
y="0"
xlink:href="#rect3592"
id="use2825"
transform="translate(8,0)"
width="10"
height="4" />
<use
x="0"
y="0"
xlink:href="#use2825"
id="use2827"
transform="translate(-4,0)"
width="10"
height="4" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
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"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="4"
height="10"
id="svg2"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="scroll-hhandle.svg">
<metadata
id="metadata7">
<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></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="rect3592"
width="2"
height="4"
x="0"
y="-4"
rx="0"
ry="0"
transform="matrix(0,1,-1,0,0,0)" />
<use
x="0"
y="0"
xlink:href="#rect3592"
id="use3705"
transform="translate(0,4)"
width="4"
height="10" />
<use
x="0"
y="0"
xlink:href="#use3705"
id="use3707"
transform="translate(0,4)"
width="4"
height="10" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,120 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<svg
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"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
id="Foreground"
x="0px"
y="0px"
width="32"
height="32"
viewBox="0 0 23.272727 23.272727"
enable-background="new 0 0 16 16"
xml:space="preserve"
sodipodi:version="0.32"
inkscape:version="0.48.2 r9819"
sodipodi:docname="summary-counter.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
id="metadata2399"><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 /></cc:Work></rdf:RDF></metadata><defs
id="defs2397"><linearGradient
id="linearGradient3173"><stop
style="stop-color:#c4c4c4;stop-opacity:1;"
offset="0"
id="stop3175" /><stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop3177" /></linearGradient><inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 8 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="16 : 8 : 1"
inkscape:persp3d-origin="8 : 5.3333333 : 1"
id="perspective2401" /><filter
color-interpolation-filters="sRGB"
inkscape:collect="always"
id="filter16494-4"
x="-0.20989846"
width="1.4197969"
y="-0.20903821"
height="1.4180764"><feGaussianBlur
inkscape:collect="always"
stdDeviation="1.3282637"
id="feGaussianBlur16496-8" /></filter><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient16498-6"
id="radialGradient16504-1"
cx="7.6582627"
cy="5.8191104"
fx="7.6582627"
fy="5.8191104"
r="8.6928644"
gradientTransform="matrix(1.0474339,0,0,1.0517402,-0.3632615,-0.42032492)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
id="linearGradient16498-6"><stop
style="stop-color:#9FD0FF;stop-opacity:1"
offset="0"
id="stop16500-8" /><stop
style="stop-color:#3465A4;stop-opacity:1"
offset="1"
id="stop16502-0" /></linearGradient></defs><sodipodi:namedview
inkscape:window-height="709"
inkscape:window-width="1366"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10.0"
gridtolerance="10.0"
objecttolerance="10.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#000000"
id="base"
showgrid="false"
inkscape:zoom="11.313708"
inkscape:cx="15.386407"
inkscape:cy="13.739577"
inkscape:window-x="0"
inkscape:window-y="1179"
inkscape:current-layer="g16402-8"
showguides="true"
inkscape:guide-bbox="true"
borderlayer="true"
inkscape:showpageshadow="false"
inkscape:window-maximized="1"><inkscape:grid
type="xygrid"
id="grid11246"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" /></sodipodi:namedview><g
style="display:inline"
id="g16402-8"
transform="translate(4.7533483,2.8238929)"><g
id="g3175-4"
transform="translate(-0.89995416,0.94028614)"><path
sodipodi:type="inkscape:offset"
inkscape:radius="0"
inkscape:original="M 7.65625 0.125 C 3.2589349 0.125 -0.3125 3.7070002 -0.3125 8.125 C -0.3125 12.543001 3.2589349 16.125 7.65625 16.125 C 12.053566 16.125 15.625 12.543001 15.625 8.125 C 15.625 3.7070002 12.053566 0.125 7.65625 0.125 z "
xlink:href="#path2394-32"
style="opacity:0.52994014;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:2.18181825;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter16494-4);enable-background:accumulate"
id="path16480-5"
inkscape:href="#path2394-32"
d="m 7.65625,0.125 c -4.3973151,0 -7.96875,3.5820002 -7.96875,8 0,4.418001 3.5714349,8 7.96875,8 4.397316,0 7.96875,-3.581999 7.96875,-8 0,-4.4179998 -3.571434,-8 -7.96875,-8 z"
transform="translate(0,1.028519)" /><path
clip-rule="evenodd"
d="m -0.30428257,8.1237596 c 0,-4.4179998 3.56522987,-7.9999996 7.96254497,-7.9999996 4.3973156,0 7.9625456,3.5819998 7.9625456,7.9999996 0,4.4180014 -3.56523,8.0000004 -7.9625456,8.0000004 -4.3973151,0 -7.96254497,-3.581999 -7.96254497,-8.0000004 z"
id="path2394-32"
style="color:#000000;fill:url(#radialGradient16504-1);fill-opacity:1;fill-rule:nonzero;stroke:#eeeeec;stroke-width:1.4545455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
sodipodi:nodetypes="csssc"
inkscape:connector-curvature="0" /><g
id="g3172-6" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

View File

@ -0,0 +1,376 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
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"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="96"
height="96"
id="svg25070"
inkscape:version="0.48.0 r9654"
sodipodi:docname="ws-switch-arrow-down.svg">
<metadata
id="metadata3353">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="718"
inkscape:window-height="480"
id="namedview3351"
showgrid="false"
inkscape:zoom="2.6979167"
inkscape:cx="48"
inkscape:cy="48"
inkscape:window-x="0"
inkscape:window-y="26"
inkscape:window-maximized="0"
inkscape:current-layer="svg25070" />
<defs
id="defs25072">
<linearGradient
x1="-86.552246"
y1="185.439"
x2="-83.37072"
y2="197.31261"
id="linearGradient24957"
xlink:href="#linearGradient4034-0-4"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(6,0)" />
<linearGradient
id="linearGradient4034-0-4">
<stop
id="stop4036-5-7"
style="stop-color:#eeeeec;stop-opacity:1"
offset="0" />
<stop
id="stop4038-9-6"
style="stop-color:#babdb6;stop-opacity:1"
offset="1" />
</linearGradient>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter24765">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix24767" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix24769" />
</filter>
<linearGradient
x1="-74.520325"
y1="169.06032"
x2="-74.520325"
y2="205.94189"
id="linearGradient24955"
xlink:href="#linearGradient4632-1-3-9-3-2"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-5,0)" />
<linearGradient
id="linearGradient4632-1-3-9-3-2">
<stop
id="stop4634-1-8-3-9-0"
style="stop-color:#eeeeec;stop-opacity:1"
offset="0" />
<stop
id="stop4636-1-9-9-8-8"
style="stop-color:#ffffff;stop-opacity:1"
offset="0.0274937" />
<stop
id="stop4638-8-3-9-6-6"
style="stop-color:#f2f2f2;stop-opacity:1"
offset="0.274937" />
<stop
id="stop4640-8-5-7-8-9"
style="stop-color:#eeeeec;stop-opacity:1"
offset="0.38707438" />
<stop
id="stop4642-5-41-9-6-9"
style="stop-color:#d9dad8;stop-opacity:1"
offset="0.66528589" />
<stop
id="stop4644-5-2-7-9-2"
style="stop-color:#dfe0dd;stop-opacity:1"
offset="0.76745707" />
<stop
id="stop4646-3-2-3-7-3"
style="stop-color:#f0f0f0;stop-opacity:1"
offset="1" />
</linearGradient>
<radialGradient
cx="-33.412369"
cy="185.74171"
r="2.3554697"
fx="-33.412369"
fy="185.74171"
id="radialGradient24959"
xlink:href="#linearGradient4869-4-1"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0075,0,0,1.0075,-5.4544,-1.25141)" />
<linearGradient
id="linearGradient4869-4-1">
<stop
id="stop4871-6-2"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop4879-7-4"
style="stop-color:#eeeeec;stop-opacity:1"
offset="0.31807542" />
<stop
id="stop4877-6-1"
style="stop-color:#c8c9c6;stop-opacity:1"
offset="0.74691135" />
<stop
id="stop4873-1-0"
style="stop-color:#d3d7cf;stop-opacity:1"
offset="1" />
</linearGradient>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25011">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25013" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25015" />
</filter>
<radialGradient
cx="-33.412369"
cy="185.74171"
r="2.3554697"
fx="-33.412369"
fy="185.74171"
id="radialGradient24961"
xlink:href="#linearGradient4869-4-0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0075,0,0,1.0075,-5.4544,-1.25141)" />
<linearGradient
id="linearGradient4869-4-0">
<stop
id="stop4871-6-8"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop4879-7-5"
style="stop-color:#eeeeec;stop-opacity:1"
offset="0.31807542" />
<stop
id="stop4877-6-5"
style="stop-color:#c8c9c6;stop-opacity:1"
offset="0.74691135" />
<stop
id="stop4873-1-4"
style="stop-color:#d3d7cf;stop-opacity:1"
offset="1" />
</linearGradient>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25023">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25025" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25027" />
</filter>
<linearGradient
x1="-39.858727"
y1="184.61784"
x2="-38.244785"
y2="188.84898"
id="linearGradient24963"
xlink:href="#linearGradient4941"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient4941">
<stop
id="stop4943"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop4945"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25033">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25035" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25037" />
</filter>
<linearGradient
x1="-39.858727"
y1="184.61784"
x2="-38.244785"
y2="188.84898"
id="linearGradient24965"
xlink:href="#linearGradient4941-7"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient4941-7">
<stop
id="stop4943-2"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop4945-5"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25043">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25045" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25047" />
</filter>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25049">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25051" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25053" />
</filter>
<filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter25055">
<feColorMatrix
result="fbSourceGraphic"
values="1"
type="saturate"
id="feColorMatrix25057" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
in="fbSourceGraphic"
id="feColorMatrix25059" />
</filter>
</defs>
<g
transform="matrix(0,1,-1,0,48.0003,4.1307112e-7)"
id="layer1">
<g
transform="matrix(-2,0,0,2,-97.2497,-374.967)"
id="g4030-1-8"
style="stroke:#000000;stroke-opacity:1;display:inline">
<path
d="m -72.5,173.5 -14,14 14,14"
id="path3165-7-3"
style="color:#000000;fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
</g>
<path
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
transform="matrix(-3.34328,0,0,3.34328,-89.2797,-623.176)"
id="path4050-2-7-9-4"
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52343899;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
<path
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
transform="matrix(-3.34328,0,0,3.34328,-111.2797,-623.176)"
id="path4050-2-7-9-4-8"
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52343899;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
<path
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
transform="matrix(-2.86565,0,0,2.86565,-70.8457,-534.143)"
id="path4050-2-7-9-4-0"
style="color:#000000;fill:none;stroke:#000000;stroke-width:0.69792098;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
<path
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
transform="matrix(-2.86565,0,0,2.86565,-92.8457,-534.143)"
id="path4050-2-7-9-4-0-9"
style="color:#000000;fill:none;stroke:#000000;stroke-width:0.69792098;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
<path
d="m 47.87528,-34.0295 c 1.53896,0.0448 3.0511,0.70928 4.125,1.8125 l 32.25,32.25 -32.25,32.25 c -2.2253,2.2253 -6.2747,2.2253 -8.5,0 -2.2253,-2.22528 -2.2253,-6.2747 0,-8.5 l 23.75,-23.75 -23.75,-23.75 c -1.73168,-1.6731 -2.295,-4.44228 -1.3546,-6.65894 0.94042,-2.21668 3.32312,-3.73604 5.7296,-3.65356 z"
id="path3165-7-3-1"
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0pt;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;text-anchor:start;opacity:0.35;color:#000000;fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans"
inkscape:connector-curvature="0" />
<path
d="m 41.8316,28.09418 c -0.014,-1.58898 0.54158,-3.18406 1.66868,-4.31118 l 23.75,-23.75 m -25.1046,-30.40894 c 0.94042,-2.21668 3.32312,-3.73604 5.7296,-3.65356 1.53896,0.0448 3.0511,0.70928 4.125,1.8125 l 32.25,32.25"
id="path3165-7-3-1-9"
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0pt;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;text-anchor:start;color:#000000;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 841 B

View File

@ -0,0 +1,447 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
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"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg25070"
version="1.1"
inkscape:version="0.48.0 r9654"
sodipodi:docname="ws-switch-arrow-up.svg">
<defs
id="defs25072">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective25078" />
<inkscape:perspective
id="perspective24985"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4034-0-4"
id="linearGradient24957"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(6)"
x1="-86.552246"
y1="185.439"
x2="-83.37072"
y2="197.31261" />
<linearGradient
inkscape:collect="always"
id="linearGradient4034-0-4">
<stop
style="stop-color: rgb(238, 238, 236); stop-opacity: 1;"
offset="0"
id="stop4036-5-7" />
<stop
style="stop-color: rgb(186, 189, 182); stop-opacity: 1;"
offset="1"
id="stop4038-9-6" />
</linearGradient>
<filter
id="filter24765"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix24767"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix24769"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4632-1-3-9-3-2"
id="linearGradient24955"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-5)"
x1="-74.520325"
y1="169.06032"
x2="-74.520325"
y2="205.94189" />
<linearGradient
id="linearGradient4632-1-3-9-3-2">
<stop
style="stop-color: rgb(238, 238, 236); stop-opacity: 1;"
offset="0"
id="stop4634-1-8-3-9-0" />
<stop
id="stop4636-1-9-9-8-8"
offset="0.0274937"
style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" />
<stop
id="stop4638-8-3-9-6-6"
offset="0.274937"
style="stop-color: rgb(242, 242, 242); stop-opacity: 1;" />
<stop
id="stop4640-8-5-7-8-9"
offset="0.38707438"
style="stop-color: rgb(238, 238, 236); stop-opacity: 1;" />
<stop
id="stop4642-5-41-9-6-9"
offset="0.66528589"
style="stop-color: rgb(217, 218, 216); stop-opacity: 1;" />
<stop
id="stop4644-5-2-7-9-2"
offset="0.76745707"
style="stop-color: rgb(223, 224, 221); stop-opacity: 1;" />
<stop
style="stop-color: rgb(240, 240, 240); stop-opacity: 1;"
offset="1"
id="stop4646-3-2-3-7-3" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4869-4-1"
id="radialGradient24959"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0075, 0, 0, 1.0075, -5.4544, -1.25141)"
cx="-33.412369"
cy="185.74171"
fx="-33.412369"
fy="185.74171"
r="2.3554697" />
<linearGradient
id="linearGradient4869-4-1">
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 1;"
offset="0"
id="stop4871-6-2" />
<stop
id="stop4879-7-4"
offset="0.31807542"
style="stop-color: rgb(238, 238, 236); stop-opacity: 1;" />
<stop
id="stop4877-6-1"
offset="0.74691135"
style="stop-color: rgb(200, 201, 198); stop-opacity: 1;" />
<stop
style="stop-color: rgb(211, 215, 207); stop-opacity: 1;"
offset="1"
id="stop4873-1-0" />
</linearGradient>
<filter
id="filter25011"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25013"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25015"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4869-4-0"
id="radialGradient24961"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0075, 0, 0, 1.0075, -5.4544, -1.25141)"
cx="-33.412369"
cy="185.74171"
fx="-33.412369"
fy="185.74171"
r="2.3554697" />
<linearGradient
id="linearGradient4869-4-0">
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 1;"
offset="0"
id="stop4871-6-8" />
<stop
id="stop4879-7-5"
offset="0.31807542"
style="stop-color: rgb(238, 238, 236); stop-opacity: 1;" />
<stop
id="stop4877-6-5"
offset="0.74691135"
style="stop-color: rgb(200, 201, 198); stop-opacity: 1;" />
<stop
style="stop-color: rgb(211, 215, 207); stop-opacity: 1;"
offset="1"
id="stop4873-1-4" />
</linearGradient>
<filter
id="filter25023"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25025"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25027"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4941"
id="linearGradient24963"
gradientUnits="userSpaceOnUse"
x1="-39.858727"
y1="184.61784"
x2="-38.244785"
y2="188.84898" />
<linearGradient
inkscape:collect="always"
id="linearGradient4941">
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 1;"
offset="0"
id="stop4943" />
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 0;"
offset="1"
id="stop4945" />
</linearGradient>
<filter
id="filter25033"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25035"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25037"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4941-7"
id="linearGradient24965"
gradientUnits="userSpaceOnUse"
x1="-39.858727"
y1="184.61784"
x2="-38.244785"
y2="188.84898" />
<linearGradient
inkscape:collect="always"
id="linearGradient4941-7">
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 1;"
offset="0"
id="stop4943-2" />
<stop
style="stop-color: rgb(255, 255, 255); stop-opacity: 0;"
offset="1"
id="stop4945-5" />
</linearGradient>
<filter
id="filter25043"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25045"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25047"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<filter
id="filter25049"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25051"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25053"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
<filter
id="filter25055"
inkscape:label="Invert"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Invert colors"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix25057"
type="saturate"
values="1"
result="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix25059"
in="fbSourceGraphic"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8284271"
inkscape:cx="-12.356322"
inkscape:cy="57.536221"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1200"
inkscape:window-height="840"
inkscape:window-x="0"
inkscape:window-y="26"
inkscape:window-maximized="0" />
<metadata
id="metadata25075">
<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 />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(0, 48)">
<g
id="g3181"
transform="matrix(0,1,-1,0,48.0003,-48)">
<g
style="stroke:#000000;stroke-opacity:1;display:inline"
transform="matrix(2,0,0,2,193.25,-374.967)"
id="g4030-1-8">
<path
style="color:#000000;fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
d="m -72.5,173.5 -14,14 14,14"
id="path3165-7-3"
sodipodi:nodetypes="ccc"
inkscape:connector-curvature="0" />
</g>
<path
transform="matrix(3.34328,0,0,3.34328,185.28,-623.176)"
d="m -36.5,186.40625 c 0,1.15635 -0.937404,2.09375 -2.09375,2.09375 -1.156346,0 -2.09375,-0.9374 -2.09375,-2.09375 0,-1.15635 0.937404,-2.09375 2.09375,-2.09375 1.156346,0 2.09375,0.9374 2.09375,2.09375 z"
sodipodi:ry="2.09375"
sodipodi:rx="2.09375"
sodipodi:cy="186.40625"
sodipodi:cx="-38.59375"
id="path4050-2-7-9-4"
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52343899;marker:none;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
<path
transform="matrix(3.34328,0,0,3.34328,207.28,-623.176)"
d="m -36.5,186.40625 c 0,1.15635 -0.937404,2.09375 -2.09375,2.09375 -1.156346,0 -2.09375,-0.9374 -2.09375,-2.09375 0,-1.15635 0.937404,-2.09375 2.09375,-2.09375 1.156346,0 2.09375,0.9374 2.09375,2.09375 z"
sodipodi:ry="2.09375"
sodipodi:rx="2.09375"
sodipodi:cy="186.40625"
sodipodi:cx="-38.59375"
id="path4050-2-7-9-4-8"
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52343899;marker:none;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
<path
transform="matrix(2.86565,0,0,2.86565,166.846,-534.143)"
d="m -36.5,186.40625 c 0,1.15635 -0.937404,2.09375 -2.09375,2.09375 -1.156346,0 -2.09375,-0.9374 -2.09375,-2.09375 0,-1.15635 0.937404,-2.09375 2.09375,-2.09375 1.156346,0 2.09375,0.9374 2.09375,2.09375 z"
sodipodi:ry="2.09375"
sodipodi:rx="2.09375"
sodipodi:cy="186.40625"
sodipodi:cx="-38.59375"
id="path4050-2-7-9-4-0"
style="color:#000000;fill:none;stroke:#000000;stroke-width:0.69792098;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
<path
transform="matrix(2.86565,0,0,2.86565,188.846,-534.143)"
d="m -36.5,186.40625 c 0,1.15635 -0.937404,2.09375 -2.09375,2.09375 -1.156346,0 -2.09375,-0.9374 -2.09375,-2.09375 0,-1.15635 0.937404,-2.09375 2.09375,-2.09375 1.156346,0 2.09375,0.9374 2.09375,2.09375 z"
sodipodi:ry="2.09375"
sodipodi:rx="2.09375"
sodipodi:cy="186.40625"
sodipodi:cx="-38.59375"
id="path4050-2-7-9-4-0-9"
style="color:#000000;fill:none;stroke:#000000;stroke-width:0.69792098;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
<path
transform="matrix(2,0,0,2,-586,-765.967)"
sodipodi:nodetypes="ccccscccsc"
id="path3165-7-3-1"
d="m 317.06251,365.96875 c -0.76948,0.0224 -1.52555,0.35464 -2.0625,0.90625 l -16.125,16.125 16.125,16.125 c 1.11265,1.11265 3.13735,1.11265 4.25,0 1.11265,-1.11264 1.11265,-3.13735 0,-4.25 l -11.875,-11.875 11.875,-11.875 c 0.86584,-0.83655 1.1475,-2.22114 0.6773,-3.32947 -0.47021,-1.10834 -1.66156,-1.86802 -2.8648,-1.82678 z"
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0pt;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;text-anchor:start;opacity:0.35;color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans"
inkscape:connector-curvature="0" />
<path
transform="matrix(2,0,0,2,-586,-765.967)"
sodipodi:nodetypes="ccccccc"
id="path3165-7-3-1-9"
d="m 320.08435,397.03059 c 0.007,-0.79449 -0.27079,-1.59203 -0.83434,-2.15559 L 307.37501,383 m 12.5523,-15.20447 c -0.47021,-1.10834 -1.66156,-1.86802 -2.8648,-1.82678 -0.76948,0.0224 -1.52555,0.35464 -2.0625,0.90625 L 298.87501,383"
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0pt;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;text-anchor:start;color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -68,10 +68,6 @@ IGNORE_HFILES= \
gactionobserver.h \ gactionobserver.h \
shell-recorder-src.h shell-recorder-src.h
if !BUILD_RECORDER
IGNORE_HFILES += shell-recorder.h
endif
# Images to copy into HTML directory. # Images to copy into HTML directory.
# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png # e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
HTML_IMAGES= HTML_IMAGES=

View File

@ -29,6 +29,8 @@
<chapter> <chapter>
<title>Search</title> <title>Search</title>
<xi:include href="xml/shell-app-system.xml"/> <xi:include href="xml/shell-app-system.xml"/>
<xi:include href="xml/shell-contact-system.xml"/>
<xi:include href="xml/shell-doc-system.xml"/>
</chapter> </chapter>
<chapter> <chapter>
<title>Tray Icons</title> <title>Tray Icons</title>
@ -40,6 +42,7 @@
<chapter> <chapter>
<title>Recorder</title> <title>Recorder</title>
<xi:include href="xml/shell-recorder.xml"/> <xi:include href="xml/shell-recorder.xml"/>
<xi:include href="xml/shell-recorder-src.xml"/>
</chapter> </chapter>
<chapter> <chapter>
<title>Integration helpers and utilities</title> <title>Integration helpers and utilities</title>

View File

@ -1,4 +1,3 @@
NULL =
EXTRA_DIST = misc/config.js.in EXTRA_DIST = misc/config.js.in
CLEANFILES = misc/config.js CLEANFILES = misc/config.js
@ -7,7 +6,9 @@ misc/config.js: misc/config.js.in Makefile
[ -d $(@D) ] || $(mkdir_p) $(@D) ; \ [ -d $(@D) ] || $(mkdir_p) $(@D) ; \
sed -e "s|[@]PACKAGE_NAME@|$(PACKAGE_NAME)|g" \ sed -e "s|[@]PACKAGE_NAME@|$(PACKAGE_NAME)|g" \
-e "s|[@]PACKAGE_VERSION@|$(PACKAGE_VERSION)|g" \ -e "s|[@]PACKAGE_VERSION@|$(PACKAGE_VERSION)|g" \
-e "s|[@]GJS_VERSION@|$(GJS_VERSION)|g" \
-e "s|[@]HAVE_BLUETOOTH@|$(HAVE_BLUETOOTH)|g" \ -e "s|[@]HAVE_BLUETOOTH@|$(HAVE_BLUETOOTH)|g" \
-e "s|[@]SHELL_SYSTEM_CA_FILE@|$(SHELL_SYSTEM_CA_FILE)|g" \
-e "s|[@]GETTEXT_PACKAGE@|$(GETTEXT_PACKAGE)|g" \ -e "s|[@]GETTEXT_PACKAGE@|$(GETTEXT_PACKAGE)|g" \
-e "s|[@]datadir@|$(datadir)|g" \ -e "s|[@]datadir@|$(datadir)|g" \
-e "s|[@]libexecdir@|$(libexecdir)|g" \ -e "s|[@]libexecdir@|$(libexecdir)|g" \
@ -18,75 +19,79 @@ jsdir = $(pkgdatadir)/js
nobase_dist_js_DATA = \ nobase_dist_js_DATA = \
gdm/batch.js \ gdm/batch.js \
gdm/consoleKit.js \
gdm/fingerprint.js \ gdm/fingerprint.js \
gdm/loginDialog.js \ gdm/loginDialog.js \
gdm/powerMenu.js \ gdm/powerMenu.js \
gdm/realmd.js \ gdm/systemd.js \
gdm/util.js \
extensionPrefs/main.js \ extensionPrefs/main.js \
misc/config.js \ misc/config.js \
misc/extensionUtils.js \ misc/extensionUtils.js \
misc/fileUtils.js \ misc/fileUtils.js \
misc/format.js \
misc/gnomeSession.js \ misc/gnomeSession.js \
misc/history.js \ misc/history.js \
misc/jsParse.js \ misc/jsParse.js \
misc/loginManager.js \
misc/modemManager.js \ misc/modemManager.js \
misc/params.js \ misc/params.js \
misc/screenSaver.js \
misc/util.js \ misc/util.js \
perf/core.js \ perf/core.js \
ui/altTab.js \ ui/altTab.js \
ui/appDisplay.js \ ui/appDisplay.js \
ui/appFavorites.js \ ui/appFavorites.js \
ui/automountManager.js \
ui/autorunManager.js \
ui/boxpointer.js \ ui/boxpointer.js \
ui/calendar.js \ ui/calendar.js \
ui/checkBox.js \ ui/checkBox.js \
ui/contactDisplay.js \
ui/ctrlAltTab.js \ ui/ctrlAltTab.js \
ui/dash.js \ ui/dash.js \
ui/dateMenu.js \ ui/dateMenu.js \
ui/dnd.js \ ui/dnd.js \
ui/endSessionDialog.js \ ui/endSessionDialog.js \
ui/extensionSystem.js \
ui/extensionDownloader.js \
ui/environment.js \ ui/environment.js \
ui/extensionSystem.js \
ui/flashspot.js \ ui/flashspot.js \
ui/ibusCandidatePopup.js\
ui/grabHelper.js \
ui/iconGrid.js \ ui/iconGrid.js \
ui/keyboard.js \ ui/keyboard.js \
ui/keyringPrompt.js \
ui/layout.js \ ui/layout.js \
ui/lightbox.js \ ui/lightbox.js \
ui/link.js \
ui/lookingGlass.js \ ui/lookingGlass.js \
ui/magnifier.js \ ui/magnifier.js \
ui/magnifierDBus.js \ ui/magnifierDBus.js \
ui/main.js \ ui/main.js \
ui/messageTray.js \ ui/messageTray.js \
ui/modalDialog.js \ ui/modalDialog.js \
ui/sessionMode.js \ ui/networkAgent.js \
ui/shellEntry.js \ ui/shellEntry.js \
ui/shellMountOperation.js \ ui/shellMountOperation.js \
ui/notificationDaemon.js \ ui/notificationDaemon.js \
ui/overview.js \ ui/overview.js \
ui/panel.js \ ui/panel.js \
ui/panelMenu.js \ ui/panelMenu.js \
ui/pointerWatcher.js \ ui/placeDisplay.js \
ui/polkitAuthenticationAgent.js \
ui/popupMenu.js \ ui/popupMenu.js \
ui/remoteSearch.js \ ui/remoteSearch.js \
ui/runDialog.js \ ui/runDialog.js \
ui/screenShield.js \
ui/scripting.js \ ui/scripting.js \
ui/search.js \ ui/search.js \
ui/searchDisplay.js \ ui/searchDisplay.js \
ui/shellDBus.js \ ui/shellDBus.js \
ui/statusIconDispatcher.js \
ui/status/accessibility.js \ ui/status/accessibility.js \
ui/status/candidatePanel.js \
ui/status/keyboard.js \ ui/status/keyboard.js \
ui/status/lockScreenMenu.js \
ui/status/network.js \ ui/status/network.js \
ui/status/power.js \ ui/status/power.js \
ui/status/volume.js \ ui/status/volume.js \
ui/status/bluetooth.js \ ui/status/bluetooth.js \
ui/telepathyClient.js \
ui/tweener.js \ ui/tweener.js \
ui/unlockDialog.js \
ui/userMenu.js \ ui/userMenu.js \
ui/viewSelector.js \ ui/viewSelector.js \
ui/wanda.js \ ui/wanda.js \
@ -96,13 +101,4 @@ nobase_dist_js_DATA = \
ui/workspaceThumbnail.js \ ui/workspaceThumbnail.js \
ui/workspacesView.js \ ui/workspacesView.js \
ui/workspaceSwitcherPopup.js \ ui/workspaceSwitcherPopup.js \
ui/xdndHandler.js \ ui/xdndHandler.js
ui/components/__init__.js \
ui/components/autorunManager.js \
ui/components/automountManager.js \
ui/components/networkAgent.js \
ui/components/polkitAgent.js \
ui/components/recorder.js \
ui/components/telepathyClient.js \
ui/components/keyring.js \
$(NULL)

View File

@ -6,14 +6,15 @@ const GObject = imports.gi.GObject;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Pango = imports.gi.Pango; const Pango = imports.gi.Pango;
const Format = imports.format;
const _ = Gettext.gettext; const _ = Gettext.gettext;
const Config = imports.misc.config; const Config = imports.misc.config;
const Format = imports.misc.format;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const GnomeShellIface = <interface name="org.gnome.Shell.Extensions">
const GnomeShellIface = <interface name="org.gnome.Shell">
<signal name="ExtensionStatusChanged"> <signal name="ExtensionStatusChanged">
<arg type="s" name="uuid"/> <arg type="s" name="uuid"/>
<arg type="i" name="state"/> <arg type="i" name="state"/>
@ -161,7 +162,7 @@ const Application = new Lang.Class({
vbox.add(toolbar); vbox.add(toolbar);
let toolitem; let toolitem;
let label = new Gtk.Label({ label: '<b>' + _("Extension") + '</b>', let label = new Gtk.Label({ label: _("<b>Extension</b>"),
use_markup: true }); use_markup: true });
toolitem = new Gtk.ToolItem({ child: label }); toolitem = new Gtk.ToolItem({ child: label });
toolbar.add(toolitem); toolbar.add(toolitem);
@ -174,7 +175,7 @@ const Application = new Lang.Class({
let renderer = new Gtk.CellRendererText(); let renderer = new Gtk.CellRendererText();
this._extensionSelector.pack_start(renderer, true); this._extensionSelector.pack_start(renderer, true);
this._extensionSelector.add_attribute(renderer, 'text', 1); this._extensionSelector.add_attribute(renderer, 'text', 1);
this._extensionSelector.set_cell_data_func(renderer, Lang.bind(this, this._setExtensionInsensitive)); this._extensionSelector.set_cell_data_func(renderer, Lang.bind(this, this._setExtensionInsensitive), null);
this._extensionSelector.connect('changed', Lang.bind(this, this._extensionSelected)); this._extensionSelector.connect('changed', Lang.bind(this, this._extensionSelected));
toolitem = new Gtk.ToolItem({ child: this._extensionSelector }); toolitem = new Gtk.ToolItem({ child: this._extensionSelector });
@ -201,17 +202,23 @@ const Application = new Lang.Class({
}, },
_scanExtensions: function() { _scanExtensions: function() {
let finder = new ExtensionUtils.ExtensionFinder(); ExtensionUtils.scanExtensions(Lang.bind(this, function(uuid, dir, type) {
finder.connect('extension-found', Lang.bind(this, this._extensionFound)); if (ExtensionUtils.extensions[uuid] !== undefined)
finder.scanExtensions(); return;
},
_extensionFound: function(signals, extension) { let extension;
let iter = this._model.append(); try {
this._model.set(iter, [0, 1], [extension.uuid, extension.metadata.name]); extension = ExtensionUtils.createExtensionObject(uuid, dir, type);
this._extensionIters[extension.uuid] = iter; } catch(e) {
}, global.logError('' + e);
return;
}
let iter = this._model.append();
this._model.set(iter, [0, 1], [uuid, extension.metadata.name]);
this._extensionIters[uuid] = iter;
}));
},
_onActivate: function() { _onActivate: function() {
this._window.present(); this._window.present();
@ -250,7 +257,7 @@ function initEnvironment() {
}, },
logError: function(s) { logError: function(s) {
log('ERROR: ' + s); global.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'])
@ -261,6 +268,7 @@ function initEnvironment() {
function main(argv) { function main(argv) {
initEnvironment(); initEnvironment();
ExtensionUtils.init();
Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR); Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
Gettext.textdomain(Config.GETTEXT_PACKAGE); Gettext.textdomain(Config.GETTEXT_PACKAGE);

22
js/gdm/consoleKit.js Normal file
View File

@ -0,0 +1,22 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const ConsoleKitManagerIface = <interface name='org.freedesktop.ConsoleKit.Manager'>
<method name='CanRestart'>
<arg type='b' direction='out'/>
</method>
<method name='CanStop'>
<arg type='b' direction='out'/>
</method>
<method name='Restart' />
<method name='Stop' />
</interface>;
const ConsoleKitProxy = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface);
function ConsoleKitManager() {
return new ConsoleKitProxy(Gio.DBus.system,
'org.freedesktop.ConsoleKit',
'/org/freedesktop/ConsoleKit/Manager');
};

View File

@ -19,7 +19,8 @@ function FprintManager() {
g_interface_info: FprintManagerInfo, g_interface_info: FprintManagerInfo,
g_name: 'net.reactivated.Fprint', g_name: 'net.reactivated.Fprint',
g_object_path: '/net/reactivated/Fprint/Manager', g_object_path: '/net/reactivated/Fprint/Manager',
g_flags: (Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) }); g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START |
Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
self.init(null); self.init(null);
return self; return self;

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,8 @@
const Lang = imports.lang; const Lang = imports.lang;
const UPowerGlib = imports.gi.UPowerGlib; const UPowerGlib = imports.gi.UPowerGlib;
const LoginManager = imports.misc.loginManager; const ConsoleKit = imports.gdm.consoleKit;
const Systemd = imports.gdm.systemd;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
@ -31,11 +32,13 @@ const PowerMenuButton = new Lang.Class({
Extends: PanelMenu.SystemStatusButton, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
/* Translators: accessible name of the power menu in the login screen */ this.parent('system-shutdown', null);
this.parent('system-shutdown-symbolic', _("Power"));
this._upClient = new UPowerGlib.Client(); this._upClient = new UPowerGlib.Client();
this._loginManager = LoginManager.getLoginManager(); if (Systemd.haveSystemd())
this._systemdLoginManager = new Systemd.SystemdLoginManager();
else
this._consoleKitManager = new ConsoleKit.ConsoleKitManager();
this._createSubMenu(); this._createSubMenu();
@ -62,19 +65,57 @@ const PowerMenuButton = new Lang.Class({
}, },
_updateHaveShutdown: function() { _updateHaveShutdown: function() {
this._loginManager.canPowerOff(Lang.bind(this, function(result) {
this._haveShutdown = result; if (Systemd.haveSystemd()) {
this._powerOffItem.actor.visible = this._haveShutdown; this._systemdLoginManager.CanPowerOffRemote(Lang.bind(this,
this._updateVisibility(); function(result, error) {
})); if (!error)
this._haveShutdown = result != 'no';
else
this._haveShutdown = false;
this._powerOffItem.actor.visible = this._haveShutdown;
this._updateVisibility();
}));
} else {
this._consoleKitManager.CanStopRemote(Lang.bind(this,
function(result, error) {
if (!error)
this._haveShutdown = result;
else
this._haveShutdown = false;
this._powerOffItem.actor.visible = this._haveShutdown;
this._updateVisibility();
}));
}
}, },
_updateHaveRestart: function() { _updateHaveRestart: function() {
this._loginManager.canReboot(Lang.bind(this, function(result) {
this._haveRestart = result; if (Systemd.haveSystemd()) {
this._restartItem.actor.visible = this._haveRestart; this._systemdLoginManager.CanRebootRemote(Lang.bind(this,
this._updateVisibility(); function(result, error) {
})); if (!error)
this._haveRestart = result != 'no';
else
this._haveRestart = false;
this._restartItem.actor.visible = this._haveRestart;
this._updateVisibility();
}));
} else {
this._consoleKitManager.CanRestartRemote(Lang.bind(this,
function(result, error) {
if (!error)
this._haveRestart = result;
else
this._haveRestart = false;
this._restartItem.actor.visible = this._haveRestart;
this._updateVisibility();
}));
}
}, },
_updateHaveSuspend: function() { _updateHaveSuspend: function() {
@ -111,13 +152,19 @@ const PowerMenuButton = new Lang.Class({
if (!this._haveRestart) if (!this._haveRestart)
return; return;
this._loginManager.reboot(); if (Systemd.haveSystemd())
this._systemdLoginManager.RebootRemote(true);
else
this._consoleKitManager.RestartRemote();
}, },
_onActivatePowerOff: function() { _onActivatePowerOff: function() {
if (!this._haveShutdown) if (!this._haveShutdown)
return; return;
this._loginManager.powerOff(); if (Systemd.haveSystemd())
this._systemdLoginManager.PowerOffRemote(true);
else
this._consoleKitManager.StopRemote();
} }
}); });

View File

@ -1,139 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const ProviderIface = <interface name='org.freedesktop.realmd.Provider'>
<property name="Name" type="s" access="read"/>
<property name="Version" type="s" access="read"/>
<property name="Realms" type="ao" access="read"/>
<method name="Discover">
<arg name="string" type="s" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
<arg name="relevance" type="i" direction="out"/>
<arg name="realm" type="ao" direction="out"/>
</method>
</interface>;
const Provider = Gio.DBusProxy.makeProxyWrapper(ProviderIface);
const ServiceIface = <interface name="org.freedesktop.realmd.Service">
<method name="Cancel">
<arg name="operation" type="s" direction="in"/>
</method>
<method name="Release" />
<method name="SetLocale">
<arg name="locale" type="s" direction="in"/>
</method>
<signal name="Diagnostics">
<arg name="data" type="s"/>
<arg name="operation" type="s"/>
</signal>
</interface>;
const Service = Gio.DBusProxy.makeProxyWrapper(ServiceIface);
const RealmIface = <interface name="org.freedesktop.realmd.Realm">
<property name="Name" type="s" access="read"/>
<property name="Configured" type="s" access="read"/>
<property name="Details" type="a(ss)" access="read"/>
<property name="LoginFormats" type="as" access="read"/>
<property name="LoginPolicy" type="s" access="read"/>
<property name="PermittedLogins" type="as" access="read"/>
<property name="SupportedInterfaces" type="as" access="read"/>
<method name="ChangeLoginPolicy">
<arg name="login_policy" type="s" direction="in"/>
<arg name="permitted_add" type="as" direction="in"/>
<arg name="permitted_remove" type="as" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
</method>
<method name="Deconfigure">
<arg name="options" type="a{sv}" direction="in"/>
</method>
</interface>;
const Realm = Gio.DBusProxy.makeProxyWrapper(RealmIface);
const Manager = new Lang.Class({
Name: 'Manager',
_init: function(parentActor) {
this._aggregateProvider = Provider(Gio.DBus.system,
'org.freedesktop.realmd',
'/org/freedesktop/realmd',
Lang.bind(this, this._reloadRealms))
this._realms = {};
this._aggregateProvider.connect('g-properties-changed',
Lang.bind(this, function(proxy, properties) {
if ('Realms' in properties.deep_unpack())
this._reloadRealms();
}));
},
_reloadRealms: function() {
let realmPaths = this._aggregateProvider.Realms;
if (!realmPaths)
return;
for (let i = 0; i < realmPaths.length; i++) {
let realm = Realm(Gio.DBus.system,
'org.freedesktop.realmd',
realmPaths[i],
Lang.bind(this, this._onRealmLoaded));
}
},
_reloadRealm: function(realm) {
if (!realm.Configured) {
if (this._realms[realm.get_object_path()])
delete this._realms[realm.get_object_path()];
return;
}
this._realms[realm.get_object_path()] = realm;
this._updateLoginFormat();
},
_onRealmLoaded: function(realm, error) {
if (error)
return;
this._reloadRealm(realm);
realm.connect('g-properties-changed',
Lang.bind(this, function(proxy, properties) {
if ('Configured' in properties.deep_unpack())
this._reloadRealm();
}));
},
_updateLoginFormat: function() {
let newLoginFormat;
for (let realmPath in this._realms) {
let realm = this._realms[realmPath];
if (realm.LoginFormats && realm.LoginFormats.length > 0) {
newLoginFormat = realm.LoginFormats[0];
break;
}
}
if (this._loginFormat != newLoginFormat) {
this._loginFormat = newLoginFormat;
this.emit('login-format-changed', newLoginFormat);
}
},
get loginFormat() {
if (this._loginFormat !== undefined)
return this._loginFormat;
this._updateLoginFormat();
return this._loginFormat;
}
});
Signals.addSignalMethods(Manager.prototype)

31
js/gdm/systemd.js Normal file
View File

@ -0,0 +1,31 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager'>
<method name='PowerOff'>
<arg type='b' direction='in'/>
</method>
<method name='Reboot'>
<arg type='b' direction='in'/>
</method>
<method name='CanPowerOff'>
<arg type='s' direction='out'/>
</method>
<method name='CanReboot'>
<arg type='s' direction='out'/>
</method>
</interface>;
const SystemdLoginManagerProxy = Gio.DBusProxy.makeProxyWrapper(SystemdLoginManagerIface);
function SystemdLoginManager() {
return new SystemdLoginManagerProxy(Gio.DBus.system,
'org.freedesktop.login1',
'/org/freedesktop/login1');
};
function haveSystemd() {
return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0;
}

View File

@ -1,372 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const Batch = imports.gdm.batch;
const Fprint = imports.gdm.fingerprint;
const Realmd = imports.gdm.realmd;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
const PASSWORD_SERVICE_NAME = 'gdm-password';
const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
const FADE_ANIMATION_TIME = 0.16;
const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
const BANNER_MESSAGE_KEY = 'banner-message-enable';
const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text';
const ALLOWED_FAILURES_KEY = 'allowed-failures';
const LOGO_KEY = 'logo';
const DISABLE_USER_LIST_KEY = 'disable-user-list';
function fadeInActor(actor) {
if (actor.opacity == 255 && actor.visible)
return null;
let hold = new Batch.Hold();
actor.show();
let [minHeight, 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: function() {
this.set_height(-1);
hold.release();
},
});
return hold;
}
function fadeOutActor(actor) {
if (!actor.visible || actor.opacity == 0) {
actor.opacity = 0;
actor.hide();
return null;
}
let hold = new Batch.Hold();
Tweener.addTween(actor,
{ opacity: 0,
height: 0,
time: FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: function() {
this.hide();
this.set_height(-1);
hold.release();
},
});
return hold;
}
const ShellUserVerifier = new Lang.Class({
Name: 'ShellUserVerifier',
_init: function(client, params) {
params = Params.parse(params, { reauthenticationOnly: false });
this._reauthOnly = params.reauthenticationOnly;
this._client = client;
this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA });
this._fprintManager = new Fprint.FprintManager();
this._realmManager = new Realmd.Manager();
this._failCounter = 0;
},
begin: function(userName, hold) {
this._cancellable = new Gio.Cancellable();
this._hold = hold;
this._userName = userName;
this._checkForFingerprintReader();
if (userName) {
// If possible, reauthenticate an already running session,
// so any session specific credentials get updated appropriately
this._client.open_reauthentication_channel(userName, this._cancellable,
Lang.bind(this, this._reauthenticationChannelOpened));
} else {
this._client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot));
}
},
cancel: function() {
if (this._cancellable)
this._cancellable.cancel();
if (this._userVerifier)
this._userVerifier.call_cancel_sync(null);
},
clear: function() {
if (this._cancellable) {
this._cancellable.cancel();
this._cancellable = null;
}
if (this._userVerifier) {
this._userVerifier.run_dispose();
this._userVerifier = null;
}
},
answerQuery: function(serviceName, answer) {
// Clear any previous message
this.emit('show-message', null, null);
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
},
_checkForFingerprintReader: function() {
this._haveFingerprintReader = false;
if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY))
return;
this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this,
function(device, error) {
if (!error && device)
this._haveFingerprintReader = true;
}));
},
_reportInitError: function(where, error) {
logError(error, where);
this._hold.release();
this.emit('show-message', _("Authentication error"), 'login-dialog-message-warning');
this._verificationFailed(false);
},
_reauthenticationChannelOpened: function(client, result) {
try {
this._userVerifier = client.open_reauthentication_channel_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
} catch(e if e.matches(Gio.DBusError, Gio.DBusError.ACCESS_DENIED) &&
!this._reauthOnly) {
// Gdm emits org.freedesktop.DBus.Error.AccessDenied when there is
// no session to reauthenticate. Fall back to performing verification
// from this login session
client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot));
return;
} catch(e) {
this._reportInitError('Failed to open reauthentication channel', e);
return;
}
this._connectSignals();
this._beginVerification();
this._hold.release();
},
_userVerifierGot: function(client, result) {
try {
this._userVerifier = client.get_user_verifier_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
} catch(e) {
this._reportInitError('Failed to obtain user verifier', e);
return;
}
this._connectSignals();
this._beginVerification();
this._hold.release();
},
_connectSignals: function() {
this._userVerifier.connect('info', Lang.bind(this, this._onInfo));
this._userVerifier.connect('problem', Lang.bind(this, this._onProblem));
this._userVerifier.connect('info-query', Lang.bind(this, this._onInfoQuery));
this._userVerifier.connect('secret-info-query', Lang.bind(this, this._onSecretInfoQuery));
this._userVerifier.connect('conversation-stopped', Lang.bind(this, this._onConversationStopped));
this._userVerifier.connect('reset', Lang.bind(this, this._onReset));
this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
},
_beginVerification: function() {
this._hold.acquire();
if (this._userName) {
this._userVerifier.call_begin_verification_for_user(PASSWORD_SERVICE_NAME,
this._userName,
this._cancellable,
Lang.bind(this, function(obj, result) {
try {
obj.call_begin_verification_for_user_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
} catch(e) {
this._reportInitError('Failed to start verification for user', e);
return;
}
this._hold.release();
}));
if (this._haveFingerprintReader) {
this._hold.acquire();
this._userVerifier.call_begin_verification_for_user(FINGERPRINT_SERVICE_NAME,
this._userName,
this._cancellable,
Lang.bind(this, function(obj, result) {
try {
obj.call_begin_verification_for_user_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
} catch(e) {
this._reportInitError('Failed to start fingerprint verification for user', e);
return;
}
this._hold.release();
}));
}
} else {
this._userVerifier.call_begin_verification(PASSWORD_SERVICE_NAME,
this._cancellable,
Lang.bind(this, function(obj, result) {
try {
obj.call_begin_verification_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
} catch(e) {
this._reportInitError('Failed to start verification', e);
return;
}
this._hold.release();
}));
}
},
_onInfo: function(client, serviceName, info) {
// We don't display fingerprint messages, because they
// have words like UPEK in them. Instead we use the messages
// as a cue to display our own message.
if (serviceName == FINGERPRINT_SERVICE_NAME &&
this._haveFingerprintReader) {
// Translators: this message is shown below the password entry field
// to indicate the user can swipe their finger instead
this.emit('show-login-hint', _("(or swipe finger)"));
} else if (serviceName == PASSWORD_SERVICE_NAME) {
this.emit('show-message', info, 'login-dialog-message-info');
}
},
_onProblem: function(client, serviceName, problem) {
// we don't want to show auth failed messages to
// users who haven't enrolled their fingerprint.
if (serviceName != PASSWORD_SERVICE_NAME)
return;
this.emit('show-message', problem, 'login-dialog-message-warning');
},
_showRealmLoginHint: function() {
if (this._realmManager.loginFormat) {
let hint = this._realmManager.loginFormat;
hint = hint.replace(/%U/g, 'user');
hint = hint.replace(/%D/g, 'DOMAIN');
hint = hint.replace(/%[^UD]/g, '');
// Translators: this message is shown below the username entry field
// to clue the user in on how to login to the local network realm
this.emit('show-login-hint',
_("(e.g., user or %s)").format(hint));
}
},
_onInfoQuery: function(client, serviceName, question) {
// We only expect questions to come from the main auth service
if (serviceName != PASSWORD_SERVICE_NAME)
return;
this._showRealmLoginHint();
this._realmLoginHintSignalId = this._realmManager.connect('login-format-changed',
Lang.bind(this, this._showRealmLoginHint));
this.emit('ask-question', serviceName, question, '');
},
_onSecretInfoQuery: function(client, serviceName, secretQuestion) {
// We only expect secret requests to come from the main auth service
if (serviceName != PASSWORD_SERVICE_NAME)
return;
this.emit('ask-question', serviceName, secretQuestion, '\u25cf');
},
_onReset: function() {
this.clear();
// Clear previous attempts to authenticate
this._failCounter = 0;
this.emit('reset');
},
_onVerificationComplete: function() {
this.emit('verification-complete');
},
_verificationFailed: function(retry) {
// For Not Listed / enterprise logins, immediately reset
// the dialog
// Otherwise, we allow ALLOWED_FAILURES attempts. After that, we
// go back to the welcome screen.
this._failCounter++;
let canRetry = retry && this._userName &&
this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY);
if (canRetry) {
this.clear();
this.begin(this._userName, new Batch.Hold());
} else {
// Allow some time to see the message, then reset everything
Mainloop.timeout_add(3000, Lang.bind(this, function() {
this.cancel();
this._onReset();
}));
}
this.emit('verification-failed');
},
_onConversationStopped: function(client, serviceName) {
// if the password service fails, then cancel everything.
// But if, e.g., fingerprint fails, still give
// password authentication a chance to succeed
if (serviceName == PASSWORD_SERVICE_NAME) {
this._verificationFailed(true);
}
this.emit('hide-login-hint');
if (this._realmLoginHintSignalId) {
this._realmManager.disconnect(this._realmLoginHintSignalId);
this._realmLoginHintSignalId = 0;
}
},
});
Signals.addSignalMethods(ShellUserVerifier.prototype);

View File

@ -4,8 +4,12 @@
const PACKAGE_NAME = '@PACKAGE_NAME@'; const PACKAGE_NAME = '@PACKAGE_NAME@';
/* The version of this package */ /* The version of this package */
const PACKAGE_VERSION = '@PACKAGE_VERSION@'; const PACKAGE_VERSION = '@PACKAGE_VERSION@';
/* The version of GJS we're linking to */
const GJS_VERSION = '@GJS_VERSION@';
/* 1 if gnome-bluetooth is available, 0 otherwise */ /* 1 if gnome-bluetooth is available, 0 otherwise */
const HAVE_BLUETOOTH = @HAVE_BLUETOOTH@; const HAVE_BLUETOOTH = @HAVE_BLUETOOTH@;
/* The system TLS CA list */
const SHELL_SYSTEM_CA_FILE = '@SHELL_SYSTEM_CA_FILE@';
/* gettext package */ /* gettext package */
const GETTEXT_PACKAGE = '@GETTEXT_PACKAGE@'; const GETTEXT_PACKAGE = '@GETTEXT_PACKAGE@';
/* locale dir */ /* locale dir */

View File

@ -3,9 +3,6 @@
// Common utils for the extension system and the extension // Common utils for the extension system and the extension
// preferences tool // preferences tool
const Lang = imports.lang;
const Signals = imports.signals;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const ShellJS = imports.gi.ShellJS; const ShellJS = imports.gi.ShellJS;
@ -17,6 +14,9 @@ const ExtensionType = {
PER_USER: 2 PER_USER: 2
}; };
// GFile for user extensions
var userExtensionsDir = null;
// Maps uuid -> metadata object // Maps uuid -> metadata object
const extensions = {}; const extensions = {};
@ -40,18 +40,13 @@ function getCurrentExtension() {
throw new Error('Could not find current extension'); throw new Error('Could not find current extension');
let path = match[1]; let path = match[1];
let file = Gio.File.new_for_path(path); let uuid = GLib.path_get_basename(GLib.path_get_dirname(path));
// Walk up the directory tree, looking for an extesion with let extension = extensions[uuid];
// the same UUID as a directory name. if (extension === undefined)
while (file != null) { throw new Error('Could not find current extension');
let extension = extensions[file.get_basename()];
if (extension !== undefined)
return extension;
file = file.get_parent();
}
throw new Error('Could not find current extension'); return extension;
} }
/** /**
@ -88,6 +83,9 @@ function isOutOfDate(extension) {
if (!versionCheck(extension.metadata['shell-version'], Config.PACKAGE_VERSION)) if (!versionCheck(extension.metadata['shell-version'], Config.PACKAGE_VERSION))
return true; return true;
if (extension.metadata['js-version'] && !versionCheck(extension.metadata['js-version'], Config.GJS_VERSION))
return true;
return false; return false;
} }
@ -120,6 +118,11 @@ function createExtensionObject(uuid, dir, type) {
} }
} }
// Encourage people to add this
if (!meta.url) {
global.log('Warning: Missing "url" property in metadata.json');
}
if (uuid != meta.uuid) { if (uuid != meta.uuid) {
throw new Error('uuid "' + meta.uuid + '" from metadata.json does not match directory name "' + uuid + '"'); throw new Error('uuid "' + meta.uuid + '" from metadata.json does not match directory name "' + uuid + '"');
} }
@ -147,56 +150,45 @@ function installImporter(extension) {
_extension = null; _extension = null;
} }
const ExtensionFinder = new Lang.Class({ function init() {
Name: 'ExtensionFinder', let userExtensionsPath = GLib.build_filenamev([global.userdatadir, 'extensions']);
userExtensionsDir = Gio.file_new_for_path(userExtensionsPath);
_scanExtensionsInDirectory: function(dir, type) { try {
let fileEnum; if (!userExtensionsDir.query_exists(null))
let file, info; userExtensionsDir.make_directory_with_parents(null);
try { } catch (e) {
fileEnum = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null); global.logError('' + e);
} catch(e) {
if (e.domain != Gio.io_error_quark() || e.code != Gio.IOErrorEnum.NOT_FOUND)
logError(e, 'Could not enumerate extensions directory');
return;
}
while ((info = fileEnum.next_file(null)) != null) {
let fileType = info.get_file_type();
if (fileType != Gio.FileType.DIRECTORY)
continue;
let uuid = info.get_name();
let extensionDir = dir.get_child(uuid);
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()));
continue;
}
let extension;
try {
extension = createExtensionObject(uuid, extensionDir, type);
} catch(e) {
logError(e, 'Could not load extension %s'.format(uuid));
continue;
}
this.emit('extension-found', extension);
}
fileEnum.close(null);
},
scanExtensions: function() {
let userExtensionsDir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions']));
this._scanExtensionsInDirectory(userExtensionsDir, ExtensionType.PER_USER);
let systemDataDirs = GLib.get_system_data_dirs();
for (let i = 0; i < systemDataDirs.length; i++) {
let dirPath = GLib.build_filenamev([systemDataDirs[i], 'gnome-shell', 'extensions']);
let dir = Gio.file_new_for_path(dirPath);
if (dir.query_exists(null))
this._scanExtensionsInDirectory(dir, ExtensionType.SYSTEM);
}
} }
}); }
Signals.addSignalMethods(ExtensionFinder.prototype);
function scanExtensionsInDirectory(callback, dir, type) {
let fileEnum;
let file, info;
try {
fileEnum = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
} catch(e) {
global.logError('' + e);
return;
}
while ((info = fileEnum.next_file(null)) != null) {
let fileType = info.get_file_type();
if (fileType != Gio.FileType.DIRECTORY)
continue;
let uuid = info.get_name();
let extensionDir = dir.get_child(uuid);
callback(uuid, extensionDir, type);
}
fileEnum.close(null);
}
function scanExtensions(callback) {
let systemDataDirs = GLib.get_system_data_dirs();
scanExtensionsInDirectory(callback, userExtensionsDir, ExtensionType.PER_USER);
for (let i = 0; i < systemDataDirs.length; i++) {
let dirPath = GLib.build_filenamev([systemDataDirs[i], 'gnome-shell', 'extensions']);
let dir = Gio.file_new_for_path(dirPath);
if (dir.query_exists(null))
scanExtensionsInDirectory(callback, dir, ExtensionType.SYSTEM);
}
}

View File

@ -28,7 +28,7 @@ function deleteGFile(file) {
return file['delete'](null); return file['delete'](null);
} }
function recursivelyDeleteDir(dir, deleteParent) { function recursivelyDeleteDir(dir) {
let children = dir.enumerate_children('standard::name,standard::type', let children = dir.enumerate_children('standard::name,standard::type',
Gio.FileQueryInfoFlags.NONE, null); Gio.FileQueryInfoFlags.NONE, null);
@ -39,29 +39,8 @@ function recursivelyDeleteDir(dir, deleteParent) {
if (type == Gio.FileType.REGULAR) if (type == Gio.FileType.REGULAR)
deleteGFile(child); deleteGFile(child);
else if (type == Gio.FileType.DIRECTORY) else if (type == Gio.FileType.DIRECTORY)
recursivelyDeleteDir(child, true); recursivelyDeleteDir(child);
} }
if (deleteParent) deleteGFile(dir);
deleteGFile(dir);
}
function recursivelyMoveDir(srcDir, destDir) {
let children = srcDir.enumerate_children('standard::name,standard::type',
Gio.FileQueryInfoFlags.NONE, null);
if (!destDir.query_exists(null))
destDir.make_directory_with_parents(null);
let info, child;
while ((info = children.next_file(null)) != null) {
let type = info.get_file_type();
let srcChild = srcDir.get_child(info.get_name());
let destChild = destDir.get_child(info.get_name());
log([srcChild.get_path(), destChild.get_path()]);
if (type == Gio.FileType.REGULAR)
srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null);
else if (type == Gio.FileType.DIRECTORY)
recursivelyMoveDir(srcChild, destChild);
}
} }

71
js/misc/format.js Normal file
View File

@ -0,0 +1,71 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const ShellJS = imports.gi.ShellJS;
/*
* This function is intended to extend the String object and provide
* an String.format API for string formatting.
* It has to be set up using String.prototype.format = Format.format;
* Usage:
* "somestring %s %d".format('hello', 5);
* It supports %s, %d, %x and %f, for %f it also support precisions like
* "%.2f".format(1.526). All specifiers can be prefixed with a minimum
* field width, e.g. "%5s".format("foo"). Unless the width is prefixed
* with '0', the formatted string will be padded with spaces.
*/
function format() {
let str = this;
let i = 0;
let args = arguments;
return str.replace(/%(I+)?([0-9]+)?(?:\.([0-9]+))?(.)/g, function (str, flagsGroup, widthGroup, precisionGroup, genericGroup) {
if (precisionGroup != '' && genericGroup != 'f')
throw new Error("Precision can only be specified for 'f'");
let hasAlternativeIntFlag = (flagsGroup.indexOf('I') != -1);
if (hasAlternativeIntFlag && genericGroup != 'd')
throw new Error("Alternative output digits can only be specfied for 'd'");
let fillChar = (widthGroup[0] == '0') ? '0' : ' ';
let width = parseInt(widthGroup, 10) || 0;
function fillWidth(s, c, w) {
let fill = '';
for (let i = 0; i < w; i++)
fill += c;
return fill.substr(s.length) + s;
}
let s = '';
switch (genericGroup) {
case '%':
return '%';
break;
case 's':
s = args[i++].toString();
break;
case 'd':
let intV = parseInt(args[i++]);
if (hasAlternativeIntFlag)
s = ShellJS.format_int_alternative_output(intV);
else
s = intV.toString();
break;
case 'x':
s = parseInt(args[i++]).toString(16);
break;
case 'f':
if (precisionGroup == '')
s = parseFloat(args[i++]).toString();
else
s = parseFloat(args[i++]).toFixed(parseInt(precisionGroup));
break;
default:
throw new Error('Unsupported conversion character %' + genericGroup);
}
return fillWidth(s, fillChar, width);
});
}

View File

@ -50,20 +50,9 @@ const SessionManagerIface = <interface name="org.gnome.SessionManager">
<arg type="u" direction="in" /> <arg type="u" direction="in" />
</method> </method>
<method name="Shutdown" /> <method name="Shutdown" />
<method name="Reboot" />
<method name="CanShutdown"> <method name="CanShutdown">
<arg type="b" direction="out" /> <arg type="b" direction="out" />
</method> </method>
<method name="IsInhibited">
<arg type="u" direction="in" />
<arg type="b" direction="out" />
</method>
<signal name="InhibitorAdded">
<arg type="o" direction="out"/>
</signal>
<signal name="InhibitorRemoved">
<arg type="o" direction="out"/>
</signal>
</interface>; </interface>;
var SessionManagerProxy = Gio.DBusProxy.makeProxyWrapper(SessionManagerIface); var SessionManagerProxy = Gio.DBusProxy.makeProxyWrapper(SessionManagerIface);

View File

@ -41,26 +41,24 @@ const HistoryManager = new Lang.Class({
this._historyIndex = this._history.length; this._historyIndex = this._history.length;
}, },
_setPrevItem: function(text) { prevItem: function(text) {
if (this._historyIndex <= 0) if (this._historyIndex <= 0)
return false; return text;
if (text) if (text)
this._history[this._historyIndex] = text; this._history[this._historyIndex] = text;
this._historyIndex--; this._historyIndex--;
this._indexChanged(); return this._indexChanged();
return true;
}, },
_setNextItem: function(text) { nextItem: function(text) {
if (this._historyIndex >= this._history.length) if (this._historyIndex >= this._history.length)
return false; return text;
if (text) if (text)
this._history[this._historyIndex] = text; this._history[this._historyIndex] = text;
this._historyIndex++; this._historyIndex++;
this._indexChanged(); return this._indexChanged();
return true;
}, },
lastItem: function() { lastItem: function() {
@ -85,9 +83,11 @@ const HistoryManager = new Lang.Class({
_onEntryKeyPress: function(entry, event) { _onEntryKeyPress: function(entry, event) {
let symbol = event.get_key_symbol(); let symbol = event.get_key_symbol();
if (symbol == Clutter.KEY_Up) { if (symbol == Clutter.KEY_Up) {
return this._setPrevItem(entry.get_text()); this.prevItem(entry.get_text());
return true;
} else if (symbol == Clutter.KEY_Down) { } else if (symbol == Clutter.KEY_Down) {
return this._setNextItem(entry.get_text()); this.nextItem(entry.get_text());
return true;
} }
return false; return false;
}, },
@ -98,6 +98,8 @@ const HistoryManager = new Lang.Class({
if (this._entry) if (this._entry)
this._entry.set_text(current); this._entry.set_text(current);
return current;
}, },
_save: function() { _save: function() {

View File

@ -1,197 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager'>
<method name='PowerOff'>
<arg type='b' direction='in'/>
</method>
<method name='Reboot'>
<arg type='b' direction='in'/>
</method>
<method name='CanPowerOff'>
<arg type='s' direction='out'/>
</method>
<method name='CanReboot'>
<arg type='s' direction='out'/>
</method>
</interface>;
const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'>
<signal name='Lock' />
<signal name='Unlock' />
</interface>;
const SystemdLoginManager = Gio.DBusProxy.makeProxyWrapper(SystemdLoginManagerIface);
const SystemdLoginSession = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface);
const ConsoleKitManagerIface = <interface name='org.freedesktop.ConsoleKit.Manager'>
<method name='CanRestart'>
<arg type='b' direction='out'/>
</method>
<method name='CanStop'>
<arg type='b' direction='out'/>
</method>
<method name='Restart' />
<method name='Stop' />
<method name='GetCurrentSession'>
<arg type='o' direction='out' />
</method>
</interface>;
const ConsoleKitSessionIface = <interface name='org.freedesktop.ConsoleKit.Session'>
<method name='IsActive'>
<arg type='b' direction='out' />
</method>
<signal name='ActiveChanged'>
<arg type='b' direction='out' />
</signal>
<signal name='Lock' />
<signal name='Unlock' />
</interface>;
const ConsoleKitSession = Gio.DBusProxy.makeProxyWrapper(ConsoleKitSessionIface);
const ConsoleKitManager = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface);
function haveSystemd() {
return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0;
}
let _loginManager = null;
/**
* LoginManager:
* An abstraction over systemd/logind and ConsoleKit.
*
*/
function getLoginManager() {
if (_loginManager == null) {
if (haveSystemd())
_loginManager = new LoginManagerSystemd();
else
_loginManager = new LoginManagerConsoleKit();
}
return _loginManager;
}
const LoginManagerSystemd = new Lang.Class({
Name: 'LoginManagerSystemd',
_init: function() {
this._proxy = new SystemdLoginManager(Gio.DBus.system,
'org.freedesktop.login1',
'/org/freedesktop/login1');
},
// Having this function is a bit of a hack since the Systemd and ConsoleKit
// session objects have different interfaces - but in both cases there are
// Lock/Unlock signals, and that's all we count upon at the moment.
getCurrentSessionProxy: function() {
if (!this._currentSession) {
this._currentSession = new SystemdLoginSession(Gio.DBus.system,
'org.freedesktop.login1',
'/org/freedesktop/login1/session/' +
GLib.getenv('XDG_SESSION_ID'));
}
return this._currentSession;
},
get sessionActive() {
return Shell.session_is_active_for_systemd();
},
canPowerOff: function(asyncCallback) {
this._proxy.CanPowerOffRemote(function(result, error) {
if (error)
asyncCallback(false);
else
asyncCallback(result[0] != 'no');
});
},
canReboot: function(asyncCallback) {
this._proxy.CanRebootRemote(function(result, error) {
if (error)
asyncCallback(false);
else
asyncCallback(result[0] != 'no');
});
},
powerOff: function() {
this._proxy.PowerOffRemote(true);
},
reboot: function() {
this._proxy.RebootRemote(true);
}
});
const LoginManagerConsoleKit = new Lang.Class({
Name: 'LoginManagerConsoleKit',
_init: function() {
this._proxy = new ConsoleKitManager(Gio.DBus.system,
'org.freedesktop.ConsoleKit',
'/org/freedesktop/ConsoleKit/Manager');
},
// Having this function is a bit of a hack since the Systemd and ConsoleKit
// session objects have different interfaces - but in both cases there are
// Lock/Unlock signals, and that's all we count upon at the moment.
getCurrentSessionProxy: function() {
if (!this._currentSession) {
let [currentSessionId] = this._proxy.GetCurrentSessionSync();
this._currentSession = new ConsoleKitSession(Gio.DBus.system,
'org.freedesktop.ConsoleKit',
currentSessionId);
}
return this._currentSession;
},
get sessionActive() {
if (this._sessionActive !== undefined)
return this._sessionActive;
let session = this.getCurrentSessionProxy();
session.connectSignal('ActiveChanged', Lang.bind(this, function(object, senderName, [isActive]) {
this._sessionActive = isActive;
}));
[this._sessionActive] = session.IsActiveSync();
return this._sessionActive;
},
canPowerOff: function(asyncCallback) {
this._proxy.CanStopRemote(function(result, error) {
if (error)
asyncCallback(false);
else
asyncCallback(result[0]);
});
},
canReboot: function(asyncCallback) {
this._proxy.CanRestartRemote(function(result, error) {
if (error)
asyncCallback(false);
else
asyncCallback(result[0]);
});
},
powerOff: function() {
this._proxy.StopRemote();
},
reboot: function() {
this._proxy.RestartRemote();
}
});

48
js/misc/screenSaver.js Normal file
View File

@ -0,0 +1,48 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const ScreenSaverIface = <interface name="org.gnome.ScreenSaver">
<method name="GetActive">
<arg type="b" direction="out" />
</method>
<method name="Lock" />
<method name="SetActive">
<arg type="b" direction="in" />
</method>
<signal name="ActiveChanged">
<arg type="b" direction="out" />
</signal>
</interface>;
const ScreenSaverInfo = Gio.DBusInterfaceInfo.new_for_xml(ScreenSaverIface);
function ScreenSaverProxy() {
var self = new Gio.DBusProxy({ g_connection: Gio.DBus.session,
g_interface_name: ScreenSaverInfo.name,
g_interface_info: ScreenSaverInfo,
g_name: 'org.gnome.ScreenSaver',
g_object_path: '/org/gnome/ScreenSaver',
g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START |
Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
self.init(null);
self.screenSaverActive = false;
self.connectSignal('ActiveChanged', function(proxy, senderName, [isActive]) {
self.screenSaverActive = isActive;
});
self.connect('notify::g-name-owner', function() {
if (self.g_name_owner) {
self.GetActiveRemote(function(result, excp) {
if (result) {
let [isActive] = result;
self.screenSaverActive = isActive;
}
});
} else
self.screenSaverActive = false;
});
return self;
}

View File

@ -1,6 +1,9 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gdk = imports.gi.Gdk;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Shell = imports.gi.Shell;
const Main = imports.ui.main; const Main = imports.ui.main;
@ -80,33 +83,24 @@ function spawnCommandLine(command_line) {
// this will throw an error. // this will throw an error.
function trySpawn(argv) function trySpawn(argv)
{ {
var success, pid;
try { try {
[success, pid] = GLib.spawn_async(null, argv, null, GLib.spawn_async(null, argv, null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD, GLib.SpawnFlags.SEARCH_PATH,
null); null, null);
} catch (err) { } catch (err) {
/* Rewrite the error in case of ENOENT */ if (err.code == GLib.SpawnError.G_SPAWN_ERROR_NOENT) {
if (err.matches(GLib.SpawnError, GLib.SpawnError.NOENT)) { err.message = _("Command not found");
throw new GLib.SpawnError({ code: GLib.SpawnError.NOENT, } else {
message: _("Command not found") });
} else if (err instanceof GLib.Error) {
// The exception from gjs contains an error string like: // The exception from gjs contains an error string like:
// Error invoking GLib.spawn_command_line_async: Failed to // Error invoking GLib.spawn_command_line_async: Failed to
// execute child process "foo" (No such file or directory) // execute child process "foo" (No such file or directory)
// We are only interested in the part in the parentheses. (And // We are only interested in the part in the parentheses. (And
// we can't pattern match the text, since it gets localized.) // we can't pattern match the text, since it gets localized.)
let message = err.message.replace(/.*\((.+)\)/, '$1'); err.message = err.message.replace(/.*\((.+)\)/, '$1');
throw new (err.constructor)({ code: err.code,
message: message });
} else {
throw err;
} }
throw err;
} }
// 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
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, function () {}, null);
} }
// trySpawnCommandLine: // trySpawnCommandLine:
@ -150,7 +144,7 @@ function killall(processName) {
// whatever... // whatever...
let argv = ['pkill', '-f', '^([^ ]*/)?' + processName + '($| )']; let argv = ['pkill', '-f', '^([^ ]*/)?' + processName + '($| )'];
GLib.spawn_sync(null, argv, null, GLib.SpawnFlags.SEARCH_PATH, null); GLib.spawn_sync(null, argv, null, GLib.SpawnFlags.SEARCH_PATH, null, null);
// It might be useful to return success/failure, but we'd need // It might be useful to return success/failure, but we'd need
// a wrapper around WIFEXITED and WEXITSTATUS. Since none of // a wrapper around WIFEXITED and WEXITSTATUS. Since none of
// the current callers care, we don't bother. // the current callers care, we don't bother.

View File

@ -1,7 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const System = imports.system;
const Main = imports.ui.main; const Main = imports.ui.main;
const Scripting = imports.ui.scripting; const Scripting = imports.ui.scripting;
@ -101,7 +99,7 @@ function run() {
Main.overview.hide(); Main.overview.hide();
yield Scripting.waitLeisure(); yield Scripting.waitLeisure();
System.gc(); global.gc();
yield Scripting.sleep(1000); yield Scripting.sleep(1000);
Scripting.collectStatistics(); Scripting.collectStatistics();
Scripting.scriptEvent('afterShowHide'); Scripting.scriptEvent('afterShowHide');
@ -115,10 +113,10 @@ function run() {
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
Scripting.scriptEvent('applicationsShowStart'); Scripting.scriptEvent('applicationsShowStart');
Main.overview._dash.showAppsButton.checked = true; Main.overview._viewSelector.switchTab('applications');
yield Scripting.waitLeisure(); yield Scripting.waitLeisure();
Scripting.scriptEvent('applicationsShowDone'); Scripting.scriptEvent('applicationsShowDone');
Main.overview._dash.showAppsButton.checked = false; Main.overview._viewSelector.switchTab('windows');
yield Scripting.waitLeisure(); yield Scripting.waitLeisure();
} }
} }

View File

@ -22,7 +22,6 @@ const Search = imports.ui.search;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const Workspace = imports.ui.workspace; const Workspace = imports.ui.workspace;
const Params = imports.misc.params; const Params = imports.misc.params;
const Util = imports.misc.util;
const MAX_APPLICATION_WORK_MILLIS = 75; const MAX_APPLICATION_WORK_MILLIS = 75;
const MENU_POPUP_TIMEOUT = 600; const MENU_POPUP_TIMEOUT = 600;
@ -37,7 +36,6 @@ const AlphabeticalView = new Lang.Class({
this._pendingAppLaterId = 0; this._pendingAppLaterId = 0;
this._appIcons = {}; // desktop file id this._appIcons = {}; // desktop file id
this._allApps = [];
let box = new St.BoxLayout({ vertical: true }); let box = new St.BoxLayout({ vertical: true });
box.add(this._grid.actor, { y_align: St.Align.START, expand: true }); box.add(this._grid.actor, { y_align: St.Align.START, expand: true });
@ -62,22 +60,16 @@ const AlphabeticalView = new Lang.Class({
})); }));
}, },
removeAll: function() { _removeAll: function() {
this._grid.removeAll(); this._grid.removeAll();
this._appIcons = {}; this._appIcons = {};
this._allApps = [];
}, },
addApp: function(app) { _addApp: function(app) {
var id = app.get_id(); var id = app.get_id();
if (this._appIcons[id] !== undefined)
return;
let appIcon = new AppWellIcon(app); let appIcon = new AppWellIcon(app);
let pos = Util.insertSorted(this._allApps, app, function(a, b) {
return a.compare_by_name(b); this._grid.addItem(appIcon.actor);
});
this._grid.addItem(appIcon.actor, pos);
appIcon.actor.connect('key-focus-in', Lang.bind(this, this._ensureIconVisible)); appIcon.actor.connect('key-focus-in', Lang.bind(this, this._ensureIconVisible));
this._appIcons[id] = appIcon; this._appIcons[id] = appIcon;
@ -128,6 +120,14 @@ const AlphabeticalView = new Lang.Class({
icon.actor.visible = true; icon.actor.visible = true;
} }
} }
},
setAppList: function(apps) {
this._removeAll();
for (var i = 0; i < apps.length; i++) {
var app = apps[i];
this._addApp(app);
}
} }
}); });
@ -147,6 +147,7 @@ const ViewByCategories = new Lang.Class({
// (used only before the actor is mapped the first time) // (used only before the actor is mapped the first time)
this._currentCategory = -2; this._currentCategory = -2;
this._categories = []; this._categories = [];
this._apps = null;
this._categoryBox = new St.BoxLayout({ vertical: true, this._categoryBox = new St.BoxLayout({ vertical: true,
reactive: true, reactive: true,
@ -203,19 +204,16 @@ const ViewByCategories = new Lang.Class({
if (nextType == GMenu.TreeItemType.ENTRY) { if (nextType == GMenu.TreeItemType.ENTRY) {
var entry = iter.get_entry(); var entry = iter.get_entry();
var app = this._appSystem.lookup_app_by_tree_entry(entry); var app = this._appSystem.lookup_app_by_tree_entry(entry);
if (!entry.get_app_info().get_nodisplay()) { if (!entry.get_app_info().get_nodisplay())
this._view.addApp(app);
appList.push(app); appList.push(app);
}
} else if (nextType == GMenu.TreeItemType.DIRECTORY) { } else if (nextType == GMenu.TreeItemType.DIRECTORY) {
var itemDir = iter.get_directory(); if (!dir.get_is_nodisplay())
if (!itemDir.get_is_nodisplay()) this._loadCategory(iter.get_directory(), appList);
this._loadCategory(itemDir, appList);
} }
} }
}, },
_addCategory: function(name, index, dir) { _addCategory: function(name, index, dir, allApps) {
let button = new St.Button({ label: GLib.markup_escape_text (name, -1), let button = new St.Button({ label: GLib.markup_escape_text (name, -1),
style_class: 'app-filter', style_class: 'app-filter',
x_align: St.Align.START, x_align: St.Align.START,
@ -227,6 +225,7 @@ const ViewByCategories = new Lang.Class({
var apps; var apps;
if (dir == null) { if (dir == null) {
apps = allApps;
this._allCategoryButton = button; this._allCategoryButton = button;
} else { } else {
apps = []; apps = [];
@ -240,7 +239,6 @@ const ViewByCategories = new Lang.Class({
}, },
_removeAll: function() { _removeAll: function() {
this._view.removeAll();
this._categories = []; this._categories = [];
this._categoryBox.destroy_all_children(); this._categoryBox.destroy_all_children();
}, },
@ -248,8 +246,13 @@ const ViewByCategories = new Lang.Class({
refresh: function() { refresh: function() {
this._removeAll(); this._removeAll();
var allApps = Shell.AppSystem.get_default().get_all();
allApps.sort(function(a, b) {
return a.compare_by_name(b);
});
/* Translators: Filter to display all applications */ /* Translators: Filter to display all applications */
this._addCategory(_("All"), -1, null); this._addCategory(_("All"), -1, null, allApps);
var tree = this._appSystem.get_tree(); var tree = this._appSystem.get_tree();
var root = tree.get_root_directory(); var root = tree.get_root_directory();
@ -267,6 +270,7 @@ const ViewByCategories = new Lang.Class({
} }
} }
this._view.setAppList(allApps);
this._selectCategory(-1); this._selectCategory(-1);
if (this._focusDummy) { if (this._focusDummy) {
@ -308,10 +312,11 @@ const AppSearchProvider = new Lang.Class({
_init: function() { _init: function() {
this.parent(_("APPLICATIONS")); this.parent(_("APPLICATIONS"));
this._appSys = Shell.AppSystem.get_default(); this._appSys = Shell.AppSystem.get_default();
}, },
getResultMetas: function(apps, callback) { getResultMetas: function(apps) {
let metas = []; let metas = [];
for (let i = 0; i < apps.length; i++) { for (let i = 0; i < apps.length; i++) {
let app = apps[i]; let app = apps[i];
@ -322,15 +327,15 @@ const AppSearchProvider = new Lang.Class({
} }
}); });
} }
callback(metas); return metas;
}, },
getInitialResultSet: function(terms) { getInitialResultSet: function(terms) {
this.searchSystem.pushResults(this, this._appSys.initial_search(terms)); return this._appSys.initial_search(terms);
}, },
getSubsearchResultSet: function(previousResults, terms) { getSubsearchResultSet: function(previousResults, terms) {
this.searchSystem.pushResults(this, this._appSys.subsearch(previousResults, terms)); return this._appSys.subsearch(previousResults, terms);
}, },
activateResult: function(app, params) { activateResult: function(app, params) {
@ -373,7 +378,7 @@ const SettingsSearchProvider = new Lang.Class({
this._gnomecc = this._appSys.lookup_app('gnome-control-center.desktop'); this._gnomecc = this._appSys.lookup_app('gnome-control-center.desktop');
}, },
getResultMetas: function(prefs, callback) { getResultMetas: function(prefs) {
let metas = []; let metas = [];
for (let i = 0; i < prefs.length; i++) { for (let i = 0; i < prefs.length; i++) {
let pref = prefs[i]; let pref = prefs[i];
@ -384,15 +389,15 @@ const SettingsSearchProvider = new Lang.Class({
} }
}); });
} }
callback(metas); return metas;
}, },
getInitialResultSet: function(terms) { getInitialResultSet: function(terms) {
this.searchSystem.pushResults(this, this._appSys.search_settings(terms)); return this._appSys.search_settings(terms);
}, },
getSubsearchResultSet: function(previousResults, terms) { getSubsearchResultSet: function(previousResults, terms) {
this.searchSystem.pushResults(this, this._appSys.search_settings(terms)); return this._appSys.search_settings(terms);
}, },
activateResult: function(pref, params) { activateResult: function(pref, params) {

290
js/ui/automountManager.js Normal file
View File

@ -0,0 +1,290 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Params = imports.misc.params;
const Shell = imports.gi.Shell;
const Main = imports.ui.main;
const ShellMountOperation = imports.ui.shellMountOperation;
const ScreenSaver = imports.misc.screenSaver;
// GSettings keys
const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling';
const SETTING_ENABLE_AUTOMOUNT = 'automount';
const AUTORUN_EXPIRE_TIMEOUT_SECS = 10;
const ConsoleKitSessionIface = <interface name="org.freedesktop.ConsoleKit.Session">
<method name="IsActive">
<arg type="b" direction="out" />
</method>
<signal name="ActiveChanged">
<arg type="b" direction="out" />
</signal>
</interface>;
const ConsoleKitSessionProxy = Gio.DBusProxy.makeProxyWrapper(ConsoleKitSessionIface);
const ConsoleKitManagerIface = <interface name="org.freedesktop.ConsoleKit.Manager">
<method name="GetCurrentSession">
<arg type="o" direction="out" />
</method>
</interface>;
const ConsoleKitManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ConsoleKitManagerIface);
function ConsoleKitManager() {
var self = new Gio.DBusProxy({ g_connection: Gio.DBus.system,
g_interface_name: ConsoleKitManagerInfo.name,
g_interface_info: ConsoleKitManagerInfo,
g_name: 'org.freedesktop.ConsoleKit',
g_object_path: '/org/freedesktop/ConsoleKit/Manager',
g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START |
Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
self._updateSessionActive = function() {
if (self.g_name_owner) {
self.GetCurrentSessionRemote(function([session]) {
self._ckSession = new ConsoleKitSessionProxy(Gio.DBus.system, 'org.freedesktop.ConsoleKit', session);
self._ckSession.connectSignal('ActiveChanged', function(object, senderName, [isActive]) {
self.sessionActive = isActive;
});
self._ckSession.IsActiveRemote(function([isActive]) {
self.sessionActive = isActive;
});
});
} else {
self.sessionActive = true;
}
};
self.connect('notify::g-name-owner',
Lang.bind(self, self._updateSessionActive));
self._updateSessionActive();
self.init(null);
return self;
}
function haveSystemd() {
return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0;
}
const AutomountManager = new Lang.Class({
Name: 'AutomountManager',
_init: function() {
this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA });
this._volumeQueue = [];
if (!haveSystemd())
this.ckListener = new ConsoleKitManager();
this._ssProxy = new ScreenSaver.ScreenSaverProxy();
this._ssProxy.connectSignal('ActiveChanged',
Lang.bind(this, this._screenSaverActiveChanged));
this._volumeMonitor = Gio.VolumeMonitor.get();
this._volumeMonitor.connect('volume-added',
Lang.bind(this,
this._onVolumeAdded));
this._volumeMonitor.connect('volume-removed',
Lang.bind(this,
this._onVolumeRemoved));
this._volumeMonitor.connect('drive-connected',
Lang.bind(this,
this._onDriveConnected));
this._volumeMonitor.connect('drive-disconnected',
Lang.bind(this,
this._onDriveDisconnected));
this._volumeMonitor.connect('drive-eject-button',
Lang.bind(this,
this._onDriveEjectButton));
Mainloop.idle_add(Lang.bind(this, this._startupMountAll));
},
_screenSaverActiveChanged: function(object, senderName, [isActive]) {
if (!isActive) {
this._volumeQueue.forEach(Lang.bind(this, function(volume) {
this._checkAndMountVolume(volume);
}));
}
// clear the queue anyway
this._volumeQueue = [];
},
_startupMountAll: function() {
let volumes = this._volumeMonitor.get_volumes();
volumes.forEach(Lang.bind(this, function(volume) {
this._checkAndMountVolume(volume, { checkSession: false,
useMountOp: false });
}));
return false;
},
isSessionActive: function() {
// Return whether the current session is active, using the
// right mechanism: either systemd if available or ConsoleKit
// as fallback.
if (haveSystemd())
return Shell.session_is_active_for_systemd();
return this.ckListener.sessionActive;
},
_onDriveConnected: function() {
// if we're not in the current ConsoleKit session,
// or screensaver is active, don't play sounds
if (!this.isSessionActive())
return;
if (this._ssProxy.screenSaverActive)
return;
global.play_theme_sound(0, 'device-added-media');
},
_onDriveDisconnected: function() {
// if we're not in the current ConsoleKit session,
// or screensaver is active, don't play sounds
if (!this.isSessionActive())
return;
if (this._ssProxy.screenSaverActive)
return;
global.play_theme_sound(0, 'device-removed-media');
},
_onDriveEjectButton: function(monitor, drive) {
// TODO: this code path is not tested, as the GVfs volume monitor
// doesn't emit this signal just yet.
if (!this.isSessionActive())
return;
// we force stop/eject in this case, so we don't have to pass a
// mount operation object
if (drive.can_stop()) {
drive.stop
(Gio.MountUnmountFlags.FORCE, null, null,
Lang.bind(this, function(drive, res) {
try {
drive.stop_finish(res);
} catch (e) {
log("Unable to stop the drive after drive-eject-button " + e.toString());
}
}));
} else if (drive.can_eject()) {
drive.eject_with_operation
(Gio.MountUnmountFlags.FORCE, null, null,
Lang.bind(this, function(drive, res) {
try {
drive.eject_with_operation_finish(res);
} catch (e) {
log("Unable to eject the drive after drive-eject-button " + e.toString());
}
}));
}
},
_onVolumeAdded: function(monitor, volume) {
this._checkAndMountVolume(volume);
},
_checkAndMountVolume: function(volume, params) {
params = Params.parse(params, { checkSession: true,
useMountOp: true });
if (params.checkSession) {
// if we're not in the current ConsoleKit session,
// don't attempt automount
if (!this.isSessionActive())
return;
if (this._ssProxy.screenSaverActive) {
if (this._volumeQueue.indexOf(volume) == -1)
this._volumeQueue.push(volume);
return;
}
}
// Volume is already mounted, don't bother.
if (volume.get_mount())
return;
if (!this._settings.get_boolean(SETTING_ENABLE_AUTOMOUNT) ||
!volume.should_automount() ||
!volume.can_mount()) {
// allow the autorun to run anyway; this can happen if the
// mount gets added programmatically later, even if
// should_automount() or can_mount() are false, like for
// blank optical media.
this._allowAutorun(volume);
this._allowAutorunExpire(volume);
return;
}
if (params.useMountOp) {
let operation = new ShellMountOperation.ShellMountOperation(volume);
this._mountVolume(volume, operation.mountOp);
} else {
this._mountVolume(volume, null);
}
},
_mountVolume: function(volume, operation) {
this._allowAutorun(volume);
volume.mount(0, operation, null,
Lang.bind(this, this._onVolumeMounted));
},
_onVolumeMounted: function(volume, res) {
this._allowAutorunExpire(volume);
try {
volume.mount_finish(res);
} catch (e) {
let string = e.toString();
// FIXME: needs proper error code handling instead of this
// See https://bugzilla.gnome.org/show_bug.cgi?id=591480
if (string.indexOf('No key available with this passphrase') != -1)
this._reaskPassword(volume);
else
log('Unable to mount volume ' + volume.get_name() + ': ' + string);
}
},
_onVolumeRemoved: function(monitor, volume) {
this._volumeQueue =
this._volumeQueue.filter(function(element) {
return (element != volume);
});
},
_reaskPassword: function(volume) {
let operation = new ShellMountOperation.ShellMountOperation(volume, { reaskPassword: true });
this._mountVolume(volume, operation.mountOp);
},
_allowAutorun: function(volume) {
volume.allowAutorun = true;
},
_allowAutorunExpire: function(volume) {
Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, function() {
volume.allowAutorun = false;
return false;
});
}
});

View File

@ -4,7 +4,6 @@ const Lang = imports.lang;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const St = imports.gi.St; const St = imports.gi.St;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main; const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const ShellMountOperation = imports.ui.shellMountOperation; const ShellMountOperation = imports.ui.shellMountOperation;
@ -24,14 +23,12 @@ const AutorunSetting = {
}; };
// misc utils // misc utils
function shouldAutorunMount(mount, forTransient) { function ignoreAutorunForMount(mount) {
let root = mount.get_root(); let root = mount.get_root();
let volume = mount.get_volume(); let volume = mount.get_volume();
if (!volume || (!volume.allowAutorun && forTransient)) if ((root.is_native() && !isMountRootHidden(root)) ||
return false; (volume && volume.allowAutorun && volume.should_automount()))
if (!root.is_native() || isMountRootHidden(root))
return false; return false;
return true; return true;
@ -44,19 +41,6 @@ function isMountRootHidden(root) {
return (path.indexOf('/.') != -1); return (path.indexOf('/.') != -1);
} }
function isMountNonLocal(mount) {
// If the mount doesn't have an associated volume, that means it could
// be a remote filesystem. For certain kinds of local filesystems,
// like digital cameras and music players, there's no associated
// gvfs volume, so err on the side of caution and assume it's a local
// filesystem to allow the prompt.
let volume = mount.get_volume();
if (volume == null)
return false;
return (volume.get_identifier("class") == "network");
}
function startAppForMount(app, mount) { function startAppForMount(app, mount) {
let files = []; let files = [];
let root = mount.get_root(); let root = mount.get_root();
@ -96,21 +80,13 @@ const ContentTypeDiscoverer = new Lang.Class({
_init: function(callback) { _init: function(callback) {
this._callback = callback; this._callback = callback;
this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA });
}, },
guessContentTypes: function(mount) { guessContentTypes: function(mount) {
let autorunEnabled = !this._settings.get_boolean(SETTING_DISABLE_AUTORUN); // guess mount's content types using GIO
let shouldScan = autorunEnabled && !isMountNonLocal(mount); mount.guess_content_type(false, null,
Lang.bind(this,
if (shouldScan) { this._onContentTypeGuessed));
// guess mount's content types using GIO
mount.guess_content_type(false, null,
Lang.bind(this,
this._onContentTypeGuessed));
} else {
this._emitCallback(mount, []);
}
}, },
_onContentTypeGuessed: function(mount, res) { _onContentTypeGuessed: function(mount, res) {
@ -164,69 +140,55 @@ const AutorunManager = new Lang.Class({
Name: 'AutorunManager', Name: 'AutorunManager',
_init: function() { _init: function() {
this._loginManager = LoginManager.getLoginManager();
this._volumeMonitor = Gio.VolumeMonitor.get(); this._volumeMonitor = Gio.VolumeMonitor.get();
this._transDispatcher = new AutorunTransientDispatcher(this); this._volumeMonitor.connect('mount-added',
}, Lang.bind(this,
this._onMountAdded));
this._volumeMonitor.connect('mount-removed',
Lang.bind(this,
this._onMountRemoved));
_ensureResidentSource: function() { this._transDispatcher = new AutorunTransientDispatcher();
if (this._residentSource) this._createResidentSource();
return;
this._residentSource = new AutorunResidentSource(this);
let destroyId = this._residentSource.connect('destroy', Lang.bind(this, function() {
this._residentSource.disconnect(destroyId);
this._residentSource = null;
}));
},
enable: function() {
this._scanMounts();
this._mountAddedId = this._volumeMonitor.connect('mount-added', Lang.bind(this, this._onMountAdded));
this._mountRemovedId = this._volumeMonitor.connect('mount-removed', Lang.bind(this, this._onMountRemoved));
},
disable: function() {
if (this._residentSource)
this._residentSource.destroy();
this._volumeMonitor.disconnect(this._mountAddedId);
this._volumeMonitor.disconnect(this._mountRemovedId);
},
_processMount: function(mount, hotplug) {
let discoverer = new ContentTypeDiscoverer(Lang.bind(this, function(mount, apps, contentTypes) {
this._ensureResidentSource();
this._residentSource.addMount(mount, apps);
if (hotplug)
this._transDispatcher.addMount(mount, apps, contentTypes);
}));
discoverer.guessContentTypes(mount);
},
_scanMounts: function() {
let mounts = this._volumeMonitor.get_mounts(); let mounts = this._volumeMonitor.get_mounts();
mounts.forEach(Lang.bind(this, function(mount) {
this._processMount(mount, false); mounts.forEach(Lang.bind(this, function (mount) {
let discoverer = new ContentTypeDiscoverer(Lang.bind (this,
function (mount, apps) {
this._residentSource.addMount(mount, apps);
}));
discoverer.guessContentTypes(mount);
})); }));
}, },
_createResidentSource: function() {
this._residentSource = new AutorunResidentSource();
this._residentSource.connect('destroy',
Lang.bind(this,
this._createResidentSource));
},
_onMountAdded: function(monitor, mount) { _onMountAdded: function(monitor, mount) {
// don't do anything if our session is not the currently // don't do anything if our session is not the currently
// active one // active one
if (!this._loginManager.sessionActive) if (!Main.automountManager.isSessionActive())
return; return;
this._processMount(mount, true); let discoverer = new ContentTypeDiscoverer(Lang.bind (this,
function (mount, apps, contentTypes) {
this._transDispatcher.addMount(mount, apps, contentTypes);
this._residentSource.addMount(mount, apps);
}));
discoverer.guessContentTypes(mount);
}, },
_onMountRemoved: function(monitor, mount) { _onMountRemoved: function(monitor, mount) {
this._transDispatcher.removeMount(mount); this._transDispatcher.removeMount(mount);
if (this._residentSource) this._residentSource.removeMount(mount);
this._residentSource.removeMount(mount);
}, },
ejectMount: function(mount) { ejectMount: function(mount) {
@ -262,9 +224,11 @@ const AutorunManager = new Lang.Class({
try { try {
mount.unmount_with_operation_finish(res); mount.unmount_with_operation_finish(res);
} catch (e) { } catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED)) // FIXME: we need to ignore G_IO_ERROR_FAILED_HANDLED errors here
log('Unable to eject the mount ' + mount.get_name() // but we can't access the error code from JS.
+ ': ' + e.toString()); // See https://bugzilla.gnome.org/show_bug.cgi?id=591480
log('Unable to eject the mount ' + mount.get_name()
+ ': ' + e.toString());
} }
}, },
@ -272,9 +236,11 @@ const AutorunManager = new Lang.Class({
try { try {
source.eject_with_operation_finish(res); source.eject_with_operation_finish(res);
} catch (e) { } catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED)) // FIXME: we need to ignore G_IO_ERROR_FAILED_HANDLED errors here
log('Unable to eject the drive ' + source.get_name() // but we can't access the error code from JS.
+ ': ' + e.toString()); // See https://bugzilla.gnome.org/show_bug.cgi?id=591480
log('Unable to eject the drive ' + source.get_name()
+ ': ' + e.toString());
} }
}, },
@ -282,9 +248,11 @@ const AutorunManager = new Lang.Class({
try { try {
drive.stop_finish(res); drive.stop_finish(res);
} catch (e) { } catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED)) // FIXME: we need to ignore G_IO_ERROR_FAILED_HANDLED errors here
log('Unable to stop the drive ' + drive.get_name() // but we can't access the error code from JS.
+ ': ' + e.toString()); // See https://bugzilla.gnome.org/show_bug.cgi?id=591480
log('Unable to stop the drive ' + drive.get_name()
+ ': ' + e.toString());
} }
}, },
}); });
@ -293,22 +261,17 @@ const AutorunResidentSource = new Lang.Class({
Name: 'AutorunResidentSource', Name: 'AutorunResidentSource',
Extends: MessageTray.Source, Extends: MessageTray.Source,
_init: function(manager) { _init: function() {
this.parent(_("Removable Devices"), 'media-removable'); this.parent(_("Removable Devices"));
this.showInLockScreen = false;
this._mounts = []; this._mounts = [];
this._manager = manager; this._notification = new AutorunResidentNotification(this);
this._notification = new AutorunResidentNotification(this._manager, this); this._setSummaryIcon(this.createNotificationIcon());
},
buildRightClickMenu: function() {
return null;
}, },
addMount: function(mount, apps) { addMount: function(mount, apps) {
if (!shouldAutorunMount(mount, false)) if (ignoreAutorunForMount(mount))
return; return;
let filtered = this._mounts.filter(function (element) { let filtered = this._mounts.filter(function (element) {
@ -347,6 +310,12 @@ const AutorunResidentSource = new Lang.Class({
Main.messageTray.add(this); Main.messageTray.add(this);
this.pushNotification(this._notification); this.pushNotification(this._notification);
} }
},
createNotificationIcon: function() {
return new St.Icon ({ icon_name: 'media-removable',
icon_type: St.IconType.FULLCOLOR,
icon_size: this.ICON_SIZE });
} }
}); });
@ -354,7 +323,7 @@ const AutorunResidentNotification = new Lang.Class({
Name: 'AutorunResidentNotification', Name: 'AutorunResidentNotification',
Extends: MessageTray.Notification, Extends: MessageTray.Notification,
_init: function(manager, source) { _init: function(source) {
this.parent(source, source.title, null, { customContent: true }); this.parent(source, source.title, null, { customContent: true });
// set the notification as resident // set the notification as resident
@ -362,7 +331,6 @@ const AutorunResidentNotification = new Lang.Class({
this._layout = new St.BoxLayout ({ style_class: 'hotplug-resident-box', this._layout = new St.BoxLayout ({ style_class: 'hotplug-resident-box',
vertical: true }); vertical: true });
this._manager = manager;
this.addActor(this._layout, this.addActor(this._layout,
{ x_expand: true, { x_expand: true,
@ -410,7 +378,7 @@ const AutorunResidentNotification = new Lang.Class({
expand: true }); expand: true });
let ejectIcon = let ejectIcon =
new St.Icon({ icon_name: 'media-eject-symbolic', new St.Icon({ icon_name: 'media-eject',
style_class: 'hotplug-resident-eject-icon' }); style_class: 'hotplug-resident-eject-icon' });
let ejectButton = let ejectButton =
@ -425,7 +393,7 @@ const AutorunResidentNotification = new Lang.Class({
})); }));
ejectButton.connect('clicked', Lang.bind(this, function() { ejectButton.connect('clicked', Lang.bind(this, function() {
this._manager.ejectMount(mount); Main.autorunManager.ejectMount(mount);
})); }));
return item; return item;
@ -435,8 +403,7 @@ const AutorunResidentNotification = new Lang.Class({
const AutorunTransientDispatcher = new Lang.Class({ const AutorunTransientDispatcher = new Lang.Class({
Name: 'AutorunTransientDispatcher', Name: 'AutorunTransientDispatcher',
_init: function(manager) { _init: function() {
this._manager = manager;
this._sources = []; this._sources = [];
this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA }); this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA });
}, },
@ -479,7 +446,7 @@ const AutorunTransientDispatcher = new Lang.Class({
return; return;
// add a new source // add a new source
this._sources.push(new AutorunTransientSource(this._manager, mount, apps)); this._sources.push(new AutorunTransientSource(mount, apps));
}, },
addMount: function(mount, apps, contentTypes) { addMount: function(mount, apps, contentTypes) {
@ -488,7 +455,7 @@ const AutorunTransientDispatcher = new Lang.Class({
return; return;
// if the mount doesn't want to be autorun, return // if the mount doesn't want to be autorun, return
if (!shouldAutorunMount(mount, true)) if (ignoreAutorunForMount(mount))
return; return;
let setting = this._getAutorunSettingForType(contentTypes[0]); let setting = this._getAutorunSettingForType(contentTypes[0]);
@ -532,23 +499,23 @@ const AutorunTransientSource = new Lang.Class({
Name: 'AutorunTransientSource', Name: 'AutorunTransientSource',
Extends: MessageTray.Source, Extends: MessageTray.Source,
_init: function(manager, mount, apps) { _init: function(mount, apps) {
this._manager = manager; this.parent(mount.get_name());
this.mount = mount; this.mount = mount;
this.apps = apps; this.apps = apps;
this.parent(mount.get_name()); this._notification = new AutorunTransientNotification(this);
this._setSummaryIcon(this.createNotificationIcon());
this._notification = new AutorunTransientNotification(this._manager, this);
// add ourselves as a source, and popup the notification // add ourselves as a source, and popup the notification
Main.messageTray.add(this); Main.messageTray.add(this);
this.notify(this._notification); this.notify(this._notification);
}, },
createIcon: function(size) { createNotificationIcon: function() {
return new St.Icon({ gicon: this.mount.get_icon(), return new St.Icon({ gicon: this.mount.get_icon(),
icon_size: size }); icon_size: this.ICON_SIZE });
} }
}); });
@ -556,10 +523,9 @@ const AutorunTransientNotification = new Lang.Class({
Name: 'AutorunTransientNotification', Name: 'AutorunTransientNotification',
Extends: MessageTray.Notification, Extends: MessageTray.Notification,
_init: function(manager, source) { _init: function(source) {
this.parent(source, source.title, null, { customContent: true }); this.parent(source, source.title, null, { customContent: true });
this._manager = manager;
this._box = new St.BoxLayout({ style_class: 'hotplug-transient-box', this._box = new St.BoxLayout({ style_class: 'hotplug-transient-box',
vertical: true }); vertical: true });
this.addActor(this._box); this.addActor(this._box);
@ -611,7 +577,7 @@ const AutorunTransientNotification = new Lang.Class({
_buttonForEject: function() { _buttonForEject: function() {
let box = new St.BoxLayout(); let box = new St.BoxLayout();
let icon = new St.Icon({ icon_name: 'media-eject-symbolic', let icon = new St.Icon({ icon_name: 'media-eject',
style_class: 'hotplug-notification-item-icon' }); style_class: 'hotplug-notification-item-icon' });
box.add(icon); box.add(icon);
@ -628,11 +594,10 @@ const AutorunTransientNotification = new Lang.Class({
style_class: 'hotplug-notification-item' }); style_class: 'hotplug-notification-item' });
button.connect('clicked', Lang.bind(this, function() { button.connect('clicked', Lang.bind(this, function() {
this._manager.ejectMount(this._mount); Main.autorunManager.ejectMount(this._mount);
})); }));
return button; return button;
} }
}); });
const Component = AutorunManager;

View File

@ -9,13 +9,6 @@ const Shell = imports.gi.Shell;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const PopupAnimation = {
NONE: 0,
SLIDE: 1 << 0,
FADE: 1 << 1,
FULL: ~0,
};
const POPUP_ANIMATION_TIME = 0.15; const POPUP_ANIMATION_TIME = 0.15;
/** /**
@ -25,10 +18,7 @@ const POPUP_ANIMATION_TIME = 0.15;
* *
* An actor which displays a triangle "arrow" pointing to a given * An actor which displays a triangle "arrow" pointing to a given
* side. The .bin property is a container in which content can be * side. The .bin property is a container in which content can be
* placed. The arrow position may be controlled via * placed. The arrow position may be controlled via setArrowOrigin().
* setArrowOrigin(). The arrow side might be temporarily flipped
* depending on the box size and source position to keep the box
* totally inside the monitor if possible.
* *
*/ */
const BoxPointer = new Lang.Class({ const BoxPointer = new Lang.Class({
@ -36,7 +26,6 @@ const BoxPointer = new Lang.Class({
_init: function(arrowSide, binProperties) { _init: function(arrowSide, binProperties) {
this._arrowSide = arrowSide; this._arrowSide = arrowSide;
this._userArrowSide = arrowSide;
this._arrowOrigin = 0; this._arrowOrigin = 0;
this.actor = new St.Bin({ x_fill: true, this.actor = new St.Bin({ x_fill: true,
y_fill: true }); y_fill: true });
@ -76,16 +65,11 @@ const BoxPointer = new Lang.Class({
show: function(animate, onComplete) { show: function(animate, onComplete) {
let themeNode = this.actor.get_theme_node(); let themeNode = this.actor.get_theme_node();
let rise = themeNode.get_length('-arrow-rise'); let rise = themeNode.get_length('-arrow-rise');
let animationTime = (animate & PopupAnimation.FULL) ? POPUP_ANIMATION_TIME : 0;
if (animate & PopupAnimation.FADE)
this.opacity = 0;
else
this.opacity = 255;
this.opacity = 0;
this.actor.show(); this.actor.show();
if (animate & PopupAnimation.SLIDE) { if (animate) {
switch (this._arrowSide) { switch (this._arrowSide) {
case St.Side.TOP: case St.Side.TOP:
this.yOffset = -rise; this.yOffset = -rise;
@ -111,7 +95,7 @@ const BoxPointer = new Lang.Class({
if (onComplete) if (onComplete)
onComplete(); onComplete();
}), }),
time: animationTime }); time: POPUP_ANIMATION_TIME });
}, },
hide: function(animate, onComplete) { hide: function(animate, onComplete) {
@ -119,10 +103,8 @@ const BoxPointer = new Lang.Class({
let yOffset = 0; let yOffset = 0;
let themeNode = this.actor.get_theme_node(); let themeNode = this.actor.get_theme_node();
let rise = themeNode.get_length('-arrow-rise'); let rise = themeNode.get_length('-arrow-rise');
let fade = (animate & PopupAnimation.FADE);
let animationTime = (animate & PopupAnimation.FULL) ? POPUP_ANIMATION_TIME : 0;
if (animate & PopupAnimation.SLIDE) { if (animate) {
switch (this._arrowSide) { switch (this._arrowSide) {
case St.Side.TOP: case St.Side.TOP:
yOffset = rise; yOffset = rise;
@ -141,15 +123,13 @@ const BoxPointer = new Lang.Class({
this._muteInput(); this._muteInput();
Tweener.removeTweens(this); Tweener.addTween(this, { opacity: 0,
Tweener.addTween(this, { opacity: fade ? 0 : 255,
xOffset: xOffset, xOffset: xOffset,
yOffset: yOffset, yOffset: yOffset,
transition: 'linear', transition: 'linear',
time: animationTime, time: POPUP_ANIMATION_TIME,
onComplete: Lang.bind(this, function () { onComplete: Lang.bind(this, function () {
this.actor.hide(); this.actor.hide();
this.opacity = 0;
this.xOffset = 0; this.xOffset = 0;
this.yOffset = 0; this.yOffset = 0;
if (onComplete) if (onComplete)
@ -219,27 +199,8 @@ const BoxPointer = new Lang.Class({
} }
this.bin.allocate(childBox, flags); this.bin.allocate(childBox, flags);
if (this._sourceActor && this._sourceActor.mapped) { if (this._sourceActor && this._sourceActor.mapped)
this._reposition(this._sourceActor, this._arrowAlignment); this._reposition(this._sourceActor, this._arrowAlignment);
if (this._shouldFlip()) {
switch (this._arrowSide) {
case St.Side.TOP:
this._arrowSide = St.Side.BOTTOM;
break;
case St.Side.BOTTOM:
this._arrowSide = St.Side.TOP;
break;
case St.Side.LEFT:
this._arrowSide = St.Side.RIGHT;
break;
case St.Side.RIGHT:
this._arrowSide = St.Side.LEFT;
break;
}
this._reposition(this._sourceActor, this._arrowAlignment);
}
}
}, },
_drawBorder: function(area) { _drawBorder: function(area) {
@ -253,6 +214,7 @@ const BoxPointer = new Lang.Class({
let halfBorder = borderWidth / 2; let halfBorder = borderWidth / 2;
let halfBase = Math.floor(base/2); let halfBase = Math.floor(base/2);
let borderColor = themeNode.get_color('-arrow-border-color');
let backgroundColor = themeNode.get_color('-arrow-background-color'); let backgroundColor = themeNode.get_color('-arrow-background-color');
let [width, height] = area.get_surface_size(); let [width, height] = area.get_surface_size();
@ -263,6 +225,7 @@ const BoxPointer = new Lang.Class({
boxWidth -= rise; boxWidth -= rise;
} }
let cr = area.get_context(); let cr = area.get_context();
Clutter.cairo_set_source_color(cr, borderColor);
// Translate so that box goes from 0,0 to boxWidth,boxHeight, // Translate so that box goes from 0,0 to boxWidth,boxHeight,
// with the arrow poking out of that // with the arrow poking out of that
@ -275,51 +238,14 @@ const BoxPointer = new Lang.Class({
let [x1, y1] = [halfBorder, halfBorder]; let [x1, y1] = [halfBorder, halfBorder];
let [x2, y2] = [boxWidth - halfBorder, boxHeight - halfBorder]; let [x2, y2] = [boxWidth - halfBorder, boxHeight - halfBorder];
let skipTopLeft = false;
let skipTopRight = false;
let skipBottomLeft = false;
let skipBottomRight = false;
switch (this._arrowSide) {
case St.Side.TOP:
if (this._arrowOrigin == x1)
skipTopLeft = true;
else if (this._arrowOrigin == x2)
skipTopRight = true;
break;
case St.Side.RIGHT:
if (this._arrowOrigin == y1)
skipTopRight = true;
else if (this._arrowOrigin == y2)
skipBottomRight = true;
break;
case St.Side.BOTTOM:
if (this._arrowOrigin == x1)
skipBottomLeft = true;
else if (this._arrowOrigin == x2)
skipBottomRight = true;
break;
case St.Side.LEFT:
if (this._arrowOrigin == y1)
skipTopLeft = true;
else if (this._arrowOrigin == y2)
skipBottomLeft = true;
break;
}
cr.moveTo(x1 + borderRadius, y1); cr.moveTo(x1 + borderRadius, y1);
if (this._arrowSide == St.Side.TOP) { if (this._arrowSide == St.Side.TOP) {
if (skipTopLeft) { if (this._arrowOrigin < (x1 + (borderRadius + halfBase))) {
cr.moveTo(x1, y2 - borderRadius); cr.lineTo(this._arrowOrigin, y1 - rise);
cr.lineTo(x1, y1 - rise); cr.lineTo(Math.max(x1 + borderRadius, this._arrowOrigin) + halfBase, y1);
cr.lineTo(x1 + halfBase, y1); } else if (this._arrowOrigin > (x2 - (borderRadius + halfBase))) {
} else if (skipTopRight) { cr.lineTo(Math.min(x2 - borderRadius, this._arrowOrigin) - halfBase, y1);
cr.lineTo(x2 - halfBase, y1); cr.lineTo(this._arrowOrigin, y1 - rise);
cr.lineTo(x2, y1 - rise);
cr.lineTo(x2, y1 + borderRadius);
} else { } else {
cr.lineTo(this._arrowOrigin - halfBase, y1); cr.lineTo(this._arrowOrigin - halfBase, y1);
cr.lineTo(this._arrowOrigin, y1 - rise); cr.lineTo(this._arrowOrigin, y1 - rise);
@ -327,20 +253,19 @@ const BoxPointer = new Lang.Class({
} }
} }
if (!skipTopRight) { cr.lineTo(x2 - borderRadius, y1);
cr.lineTo(x2 - borderRadius, y1);
cr.arc(x2 - borderRadius, y1 + borderRadius, borderRadius, // top-right corner
3*Math.PI/2, Math.PI*2); cr.arc(x2 - borderRadius, y1 + borderRadius, borderRadius,
} 3*Math.PI/2, Math.PI*2);
if (this._arrowSide == St.Side.RIGHT) { if (this._arrowSide == St.Side.RIGHT) {
if (skipTopRight) { if (this._arrowOrigin < (y1 + (borderRadius + halfBase))) {
cr.lineTo(x2 + rise, y1); cr.lineTo(x2 + rise, this._arrowOrigin);
cr.lineTo(x2 + rise, y1 + halfBase); cr.lineTo(x2, Math.max(y1 + borderRadius, this._arrowOrigin) + halfBase);
} else if (skipBottomRight) { } else if (this._arrowOrigin > (y2 - (borderRadius + halfBase))) {
cr.lineTo(x2, y2 - halfBase); cr.lineTo(x2, Math.min(y2 - borderRadius, this._arrowOrigin) - halfBase);
cr.lineTo(x2 + rise, y2); cr.lineTo(x2 + rise, this._arrowOrigin);
cr.lineTo(x2 - borderRadius, y2);
} else { } else {
cr.lineTo(x2, this._arrowOrigin - halfBase); cr.lineTo(x2, this._arrowOrigin - halfBase);
cr.lineTo(x2 + rise, this._arrowOrigin); cr.lineTo(x2 + rise, this._arrowOrigin);
@ -348,20 +273,19 @@ const BoxPointer = new Lang.Class({
} }
} }
if (!skipBottomRight) { cr.lineTo(x2, y2 - borderRadius);
cr.lineTo(x2, y2 - borderRadius);
cr.arc(x2 - borderRadius, y2 - borderRadius, borderRadius, // bottom-right corner
0, Math.PI/2); cr.arc(x2 - borderRadius, y2 - borderRadius, borderRadius,
} 0, Math.PI/2);
if (this._arrowSide == St.Side.BOTTOM) { if (this._arrowSide == St.Side.BOTTOM) {
if (skipBottomLeft) { if (this._arrowOrigin < (x1 + (borderRadius + halfBase))) {
cr.lineTo(x1 + halfBase, y2); cr.lineTo(Math.max(x1 + borderRadius, this._arrowOrigin) + halfBase, y2);
cr.lineTo(x1, y2 + rise); cr.lineTo(this._arrowOrigin, y2 + rise);
cr.lineTo(x1, y2 - borderRadius); } else if (this._arrowOrigin > (x2 - (borderRadius + halfBase))) {
} else if (skipBottomRight) { cr.lineTo(this._arrowOrigin, y2 + rise);
cr.lineTo(x2, y2 + rise); cr.lineTo(Math.min(x2 - borderRadius, this._arrowOrigin) - halfBase, y2);
cr.lineTo(x2 - halfBase, y2);
} else { } else {
cr.lineTo(this._arrowOrigin + halfBase, y2); cr.lineTo(this._arrowOrigin + halfBase, y2);
cr.lineTo(this._arrowOrigin, y2 + rise); cr.lineTo(this._arrowOrigin, y2 + rise);
@ -369,20 +293,19 @@ const BoxPointer = new Lang.Class({
} }
} }
if (!skipBottomLeft) { cr.lineTo(x1 + borderRadius, y2);
cr.lineTo(x1 + borderRadius, y2);
cr.arc(x1 + borderRadius, y2 - borderRadius, borderRadius, // bottom-left corner
Math.PI/2, Math.PI); cr.arc(x1 + borderRadius, y2 - borderRadius, borderRadius,
} Math.PI/2, Math.PI);
if (this._arrowSide == St.Side.LEFT) { if (this._arrowSide == St.Side.LEFT) {
if (skipTopLeft) { if (this._arrowOrigin < (y1 + (borderRadius + halfBase))) {
cr.lineTo(x1, y1 + halfBase); cr.lineTo(x1, Math.max(y1 + borderRadius, this._arrowOrigin) + halfBase);
cr.lineTo(x1 - rise, y1); cr.lineTo(x1 - rise, this._arrowOrigin);
cr.lineTo(x1 + borderRadius, y1); } else if (this._arrowOrigin > (y2 - (borderRadius + halfBase))) {
} else if (skipBottomLeft) { cr.lineTo(x1 - rise, this._arrowOrigin);
cr.lineTo(x1 - rise, y2) cr.lineTo(x1, Math.min(y2 - borderRadius, this._arrowOrigin) - halfBase);
cr.lineTo(x1 - rise, y2 - halfBase);
} else { } else {
cr.lineTo(x1, this._arrowOrigin + halfBase); cr.lineTo(x1, this._arrowOrigin + halfBase);
cr.lineTo(x1 - rise, this._arrowOrigin); cr.lineTo(x1 - rise, this._arrowOrigin);
@ -390,26 +313,20 @@ const BoxPointer = new Lang.Class({
} }
} }
if (!skipTopLeft) { cr.lineTo(x1, y1 + borderRadius);
cr.lineTo(x1, y1 + borderRadius);
cr.arc(x1 + borderRadius, y1 + borderRadius, borderRadius, // top-left corner
Math.PI, 3*Math.PI/2); cr.arc(x1 + borderRadius, y1 + borderRadius, borderRadius,
} Math.PI, 3*Math.PI/2);
Clutter.cairo_set_source_color(cr, backgroundColor); Clutter.cairo_set_source_color(cr, backgroundColor);
cr.fillPreserve(); cr.fillPreserve();
Clutter.cairo_set_source_color(cr, borderColor);
if (borderWidth > 0) { cr.setLineWidth(borderWidth);
let borderColor = themeNode.get_color('-arrow-border-color'); cr.stroke();
Clutter.cairo_set_source_color(cr, borderColor);
cr.setLineWidth(borderWidth);
cr.stroke();
}
}, },
setPosition: function(sourceActor, alignment) { setPosition: function(sourceActor, alignment) {
this._arrowSide = this._userArrowSide;
// We need to show it now to force an allocation, // We need to show it now to force an allocation,
// so that we can query the correct size. // so that we can query the correct size.
this.actor.show(); this.actor.show();
@ -426,7 +343,11 @@ const BoxPointer = new Lang.Class({
if (!this._sourceActor) if (!this._sourceActor)
return; return;
this.setPosition(this._sourceActor, this._arrowAlignment); // We need to show it now to force an allocation,
// so that we can query the correct size.
this.actor.show();
this._reposition(this._sourceActor, this._arrowAlignment);
}, },
_reposition: function(sourceActor, alignment) { _reposition: function(sourceActor, alignment) {
@ -447,9 +368,10 @@ const BoxPointer = new Lang.Class({
let arrowBase = themeNode.get_length('-arrow-base'); let arrowBase = themeNode.get_length('-arrow-base');
let borderRadius = themeNode.get_length('-arrow-border-radius'); let borderRadius = themeNode.get_length('-arrow-border-radius');
let margin = (4 * borderRadius + borderWidth + arrowBase); let margin = (4 * borderRadius + borderWidth + arrowBase);
let halfMargin = margin / 2;
let themeNode = this.actor.get_theme_node();
let gap = themeNode.get_length('-boxpointer-gap'); let gap = themeNode.get_length('-boxpointer-gap');
let padding = themeNode.get_length('-arrow-rise');
let resX, resY; let resX, resY;
@ -468,66 +390,29 @@ const BoxPointer = new Lang.Class({
break; break;
} }
// Now align and position the pointing axis, making sure it fits on // Now align and position the pointing axis, making sure
// screen. If the arrowOrigin is so close to the edge that the arrow // it fits on screen
// will not be isosceles, we try to compensate as follows:
// - We skip the rounded corner and settle for a right angled arrow
// as shown below. See _drawBorder for further details.
// |\_____
// |
// |
// - If the arrow was going to be acute angled, we move the position
// of the box to maintain the arrow's accuracy.
let arrowOrigin;
let halfBase = Math.floor(arrowBase/2);
let halfBorder = borderWidth / 2;
let halfMargin = margin / 2;
let [x1, y1] = [halfBorder, halfBorder];
let [x2, y2] = [natWidth - halfBorder, natHeight - halfBorder];
switch (this._arrowSide) { switch (this._arrowSide) {
case St.Side.TOP: case St.Side.TOP:
case St.Side.BOTTOM: case St.Side.BOTTOM:
resX = sourceCenterX - (halfMargin + (natWidth - margin) * alignment); resX = sourceCenterX - (halfMargin + (natWidth - margin) * alignment);
resX = Math.max(resX, monitor.x + padding); resX = Math.max(resX, monitor.x + 10);
resX = Math.min(resX, monitor.x + monitor.width - (padding + natWidth)); resX = Math.min(resX, monitor.x + monitor.width - (10 + natWidth));
this.setArrowOrigin(sourceCenterX - resX);
arrowOrigin = sourceCenterX - resX;
if (arrowOrigin <= (x1 + (borderRadius + halfBase))) {
if (arrowOrigin > x1)
resX += (arrowOrigin - x1);
arrowOrigin = x1;
} else if (arrowOrigin >= (x2 - (borderRadius + halfBase))) {
if (arrowOrigin < x2)
resX -= (x2 - arrowOrigin);
arrowOrigin = x2;
}
break; break;
case St.Side.LEFT: case St.Side.LEFT:
case St.Side.RIGHT: case St.Side.RIGHT:
resY = sourceCenterY - (halfMargin + (natHeight - margin) * alignment); resY = sourceCenterY - (halfMargin + (natHeight - margin) * alignment);
resY = Math.max(resY, monitor.y + padding); resY = Math.max(resY, monitor.y + 10);
resY = Math.min(resY, monitor.y + monitor.height - (padding + natHeight)); resY = Math.min(resY, monitor.y + monitor.height - (10 + natHeight));
arrowOrigin = sourceCenterY - resY; this.setArrowOrigin(sourceCenterY - resY);
if (arrowOrigin <= (y1 + (borderRadius + halfBase))) {
if (arrowOrigin > y1)
resY += (arrowOrigin - y1);
arrowOrigin = y1;
} else if (arrowOrigin >= (y2 - (borderRadius + halfBase))) {
if (arrowOrigin < y2)
resX -= (y2 - arrowOrigin);
arrowOrigin = y2;
}
break; break;
} }
this.setArrowOrigin(arrowOrigin);
let parent = this.actor.get_parent(); let parent = this.actor.get_parent();
let success, x, y; let success, x, y;
while (!success) { while (!success) {
@ -561,39 +446,6 @@ const BoxPointer = new Lang.Class({
-(this._yPosition + this._yOffset)); -(this._yPosition + this._yOffset));
}, },
_shouldFlip: function() {
let sourceAllocation = Shell.util_get_transformed_allocation(this._sourceActor);
let boxAllocation = Shell.util_get_transformed_allocation(this.actor);
let boxWidth = boxAllocation.x2 - boxAllocation.x1;
let boxHeight = boxAllocation.y2 - boxAllocation.y1;
let monitor = Main.layoutManager.findMonitorForActor(this.actor);
switch (this._arrowSide) {
case St.Side.TOP:
if (boxAllocation.y2 > monitor.y + monitor.height &&
boxHeight < sourceAllocation.y1 - monitor.y)
return true;
break;
case St.Side.BOTTOM:
if (boxAllocation.y1 < monitor.y &&
boxHeight < monitor.y + monitor.height - sourceAllocation.y2)
return true;
break;
case St.Side.LEFT:
if (boxAllocation.x2 > monitor.x + monitor.width &&
boxWidth < sourceAllocation.x1 - monitor.x)
return true;
break;
case St.Side.RIGHT:
if (boxAllocation.x1 < monitor.x &&
boxWidth < monitor.x + monitor.width - sourceAllocation.x2)
return true;
break;
}
return false;
},
set xOffset(offset) { set xOffset(offset) {
this._xOffset = offset; this._xOffset = offset;
this._shiftActor(); this._shiftActor();

View File

@ -204,11 +204,12 @@ const CalendarServerInfo = Gio.DBusInterfaceInfo.new_for_xml(CalendarServerIfac
function CalendarServer() { function CalendarServer() {
var self = new Gio.DBusProxy({ g_connection: Gio.DBus.session, var self = new Gio.DBusProxy({ g_connection: Gio.DBus.session,
g_interface_name: CalendarServerInfo.name, g_interface_name: CalendarServerInfo.name,
g_interface_info: CalendarServerInfo, g_interface_info: CalendarServerInfo,
g_name: 'org.gnome.Shell.CalendarServer', g_name: 'org.gnome.Shell.CalendarServer',
g_object_path: '/org/gnome/Shell/CalendarServer', g_object_path: '/org/gnome/Shell/CalendarServer',
g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES }); g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START |
Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
self.init(null); self.init(null);
return self; return self;
@ -339,10 +340,22 @@ const DBusEventSource = new Lang.Class({
}); });
Signals.addSignalMethods(DBusEventSource.prototype); Signals.addSignalMethods(DBusEventSource.prototype);
// Calendar:
// @eventSource: is an object implementing the EventSource API, e.g. the
// requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
const Calendar = new Lang.Class({ const Calendar = new Lang.Class({
Name: 'Calendar', Name: 'Calendar',
_init: function() { _init: function(eventSource) {
if (eventSource) {
this._eventSource = eventSource;
this._eventSource.connect('changed', Lang.bind(this,
function() {
this._update(false);
}));
}
this._weekStart = Shell.util_get_week_start(); this._weekStart = Shell.util_get_week_start();
this._weekdate = NaN; this._weekdate = NaN;
this._digitWidth = NaN; this._digitWidth = NaN;
@ -379,24 +392,6 @@ const Calendar = new Lang.Class({
this._buildHeader (); this._buildHeader ();
}, },
// @eventSource: is an object implementing the EventSource API, e.g. the
// requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
setEventSource: function(eventSource) {
if (this._eventSource) {
this._eventSource.disconnect(this._eventSourceChangedId);
this._eventSource = null;
}
this._eventSource = eventSource;
if (this._eventSource) {
this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, function() {
this._update(false);
}));
this._update(true);
}
},
// Sets the calendar to show a specific date // Sets the calendar to show a specific date
setDate: function(date, forceReload) { setDate: function(date, forceReload) {
if (!_sameDay(date, this._selectedDate)) { if (!_sameDay(date, this._selectedDate)) {
@ -453,7 +448,7 @@ const Calendar = new Lang.Class({
} }
// All the children after this are days, and get removed when we update the calendar // 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.actor.get_children().length;
}, },
_onStyleChange: function(actor, event) { _onStyleChange: function(actor, event) {
@ -556,7 +551,6 @@ const Calendar = new Lang.Class({
let row = 2; let row = 2;
while (true) { while (true) {
let button = new St.Button({ label: iter.getDate().toString() }); let button = new St.Button({ label: iter.getDate().toString() });
let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;
if (!this._eventSource) if (!this._eventSource)
button.reactive = false; button.reactive = false;
@ -577,10 +571,7 @@ const Calendar = new Lang.Class({
// Hack used in lieu of border-collapse - see gnome-shell.css // Hack used in lieu of border-collapse - see gnome-shell.css
if (row == 2) if (row == 2)
styleClass = 'calendar-day-top ' + styleClass; styleClass = 'calendar-day-top ' + styleClass;
if (iter.getDay() == this._weekStart)
let leftMost = rtl ? iter.getDay() == (this._weekStart + 6) % 7
: iter.getDay() == this._weekStart;
if (leftMost)
styleClass = 'calendar-day-left ' + styleClass; styleClass = 'calendar-day-left ' + styleClass;
if (_sameDay(now, iter)) if (_sameDay(now, iter))
@ -627,25 +618,16 @@ Signals.addSignalMethods(Calendar.prototype);
const EventsList = new Lang.Class({ const EventsList = new Lang.Class({
Name: 'EventsList', Name: 'EventsList',
_init: function() { _init: function(eventSource) {
this.actor = new St.BoxLayout({ vertical: true, style_class: 'events-header-vbox'}); this.actor = new St.BoxLayout({ vertical: true, style_class: 'events-header-vbox'});
this._date = new Date(); this._date = new Date();
this._eventSource = eventSource;
this._eventSource.connect('changed', Lang.bind(this, this._update));
this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' }); this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
this._desktopSettings.connect('changed', Lang.bind(this, this._update)); this._desktopSettings.connect('changed', Lang.bind(this, this._update));
this._weekStart = Shell.util_get_week_start(); this._weekStart = Shell.util_get_week_start();
},
setEventSource: function(eventSource) { this._update();
if (this._eventSource) {
this._eventSource.disconnect(this._eventSourceChangedId);
this._eventSource = null;
}
this._eventSource = eventSource;
if (this._eventSource) {
this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, this._update));
}
}, },
_addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) { _addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) {
@ -731,15 +713,13 @@ const EventsList = new Lang.Class({
let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000); let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000);
this._addPeriod(_("Tomorrow"), tomorrowBegin, tomorrowEnd, false, true); this._addPeriod(_("Tomorrow"), tomorrowBegin, tomorrowEnd, false, true);
let dayInWeek = (dayEnd.getDay() - this._weekStart + 7) % 7; if (dayEnd.getDay() <= 4 + this._weekStart) {
if (dayInWeek < 5) {
/* If now is within the first 5 days we show "This week" and /* If now is within the first 5 days we show "This week" and
* include events up until and including Saturday/Sunday * include events up until and including Saturday/Sunday
* (depending on whether a week starts on Sunday/Monday). * (depending on whether a week starts on Sunday/Monday).
*/ */
let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000); let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
let thisWeekEnd = new Date(dayEnd.getTime() + (6 - dayInWeek) * 86400 * 1000); let thisWeekEnd = new Date(dayEnd.getTime() + (6 + this._weekStart - dayEnd.getDay()) * 86400 * 1000);
this._addPeriod(_("This week"), thisWeekBegin, thisWeekEnd, true, false); this._addPeriod(_("This week"), thisWeekBegin, thisWeekEnd, true, false);
} else { } else {
/* otherwise it's one of the two last days of the week ... show /* otherwise it's one of the two last days of the week ... show
@ -747,7 +727,7 @@ const EventsList = new Lang.Class({
* Saturday/Sunday * Saturday/Sunday
*/ */
let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000); let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
let nextWeekEnd = new Date(dayEnd.getTime() + (13 - dayInWeek) * 86400 * 1000); let nextWeekEnd = new Date(dayEnd.getTime() + (13 + this._weekStart - dayEnd.getDay()) * 86400 * 1000);
this._addPeriod(_("Next week"), nextWeekBegin, nextWeekEnd, true, false); this._addPeriod(_("Next week"), nextWeekBegin, nextWeekEnd, true, false);
} }
}, },

View File

@ -1,65 +0,0 @@
const Lang = imports.lang;
const Main = imports.ui.main;
const ComponentManager = new Lang.Class({
Name: 'ComponentManager',
_init: function() {
this._allComponents = {};
this._enabledComponents = [];
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
this._sessionUpdated();
},
_sessionUpdated: function() {
let newEnabledComponents = Main.sessionMode.components;
newEnabledComponents.filter(Lang.bind(this, function(name) {
return this._enabledComponents.indexOf(name) == -1;
})).forEach(Lang.bind(this, function(name) {
this._enableComponent(name);
}));
this._enabledComponents.filter(Lang.bind(this, function(name) {
return newEnabledComponents.indexOf(name) == -1;
})).forEach(Lang.bind(this, function(name) {
this._disableComponent(name);
}));
this._enabledComponents = newEnabledComponents;
},
_importComponent: function(name) {
let module = imports.ui.components[name];
return module.Component;
},
_ensureComponent: function(name) {
let component = this._allComponents[name];
if (component)
return component;
if (Main.sessionMode.isLocked)
return null;
let constructor = this._importComponent(name);
component = new constructor();
this._allComponents[name] = component;
return component;
},
_enableComponent: function(name) {
let component = this._ensureComponent(name);
if (component)
component.enable();
},
_disableComponent: function(name) {
let component = this._allComponents[name];
if (component == null)
return;
component.disable();
}
});

View File

@ -1,241 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Params = imports.misc.params;
const Shell = imports.gi.Shell;
const GnomeSession = imports.misc.gnomeSession;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;
const ShellMountOperation = imports.ui.shellMountOperation;
const GNOME_SESSION_AUTOMOUNT_INHIBIT = 16;
// GSettings keys
const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling';
const SETTING_ENABLE_AUTOMOUNT = 'automount';
const AUTORUN_EXPIRE_TIMEOUT_SECS = 10;
const AutomountManager = new Lang.Class({
Name: 'AutomountManager',
_init: function() {
this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA });
this._volumeQueue = [];
this._session = new GnomeSession.SessionManager();
this._session.connectSignal('InhibitorAdded',
Lang.bind(this, this._InhibitorsChanged));
this._session.connectSignal('InhibitorRemoved',
Lang.bind(this, this._InhibitorsChanged));
this._inhibited = false;
this._loginManager = LoginManager.getLoginManager();
this._volumeMonitor = Gio.VolumeMonitor.get();
},
enable: function() {
this._volumeAddedId = this._volumeMonitor.connect('volume-added', Lang.bind(this, this._onVolumeAdded));
this._volumeRemovedId = this._volumeMonitor.connect('volume-removed', Lang.bind(this, this._onVolumeRemoved));
this._driveConnectedId = this._volumeMonitor.connect('drive-connected', Lang.bind(this, this._onDriveConnected));
this._driveDisconnectedId = this._volumeMonitor.connect('drive-disconnected', Lang.bind(this, this._onDriveDisconnected));
this._driveEjectButtonId = this._volumeMonitor.connect('drive-eject-button', Lang.bind(this, this._onDriveEjectButton));
this._mountAllId = Mainloop.idle_add(Lang.bind(this, this._startupMountAll));
},
disable: function() {
this._volumeMonitor.disconnect(this._volumeAddedId);
this._volumeMonitor.disconnect(this._volumeRemovedId);
this._volumeMonitor.disconnect(this._driveConnectedId);
this._volumeMonitor.disconnect(this._driveDisconnectedId);
this._volumeMonitor.disconnect(this._driveEjectButtonId);
if (this._mountAllId > 0) {
Mainloop.source_remove(this._mountAllId);
this._mountAllId = 0;
}
},
_InhibitorsChanged: function(object, senderName, [inhibtor]) {
this._session.IsInhibitedRemote(GNOME_SESSION_AUTOMOUNT_INHIBIT,
Lang.bind(this,
function(result, error) {
if (!error) {
this._inhibited = result[0];
}
}));
},
_startupMountAll: function() {
let volumes = this._volumeMonitor.get_volumes();
volumes.forEach(Lang.bind(this, function(volume) {
this._checkAndMountVolume(volume, { checkSession: false,
useMountOp: false,
allowAutorun: false });
}));
this._mountAllId = 0;
return false;
},
_onDriveConnected: function() {
// if we're not in the current ConsoleKit session,
// or screensaver is active, don't play sounds
if (!this._loginManager.sessionActive)
return;
global.play_theme_sound(0, 'device-added-media');
},
_onDriveDisconnected: function() {
// if we're not in the current ConsoleKit session,
// or screensaver is active, don't play sounds
if (!this._loginManager.sessionActive)
return;
global.play_theme_sound(0, 'device-removed-media');
},
_onDriveEjectButton: function(monitor, drive) {
// TODO: this code path is not tested, as the GVfs volume monitor
// doesn't emit this signal just yet.
if (!this._loginManager.sessionActive)
return;
// we force stop/eject in this case, so we don't have to pass a
// mount operation object
if (drive.can_stop()) {
drive.stop
(Gio.MountUnmountFlags.FORCE, null, null,
Lang.bind(this, function(drive, res) {
try {
drive.stop_finish(res);
} catch (e) {
log("Unable to stop the drive after drive-eject-button " + e.toString());
}
}));
} else if (drive.can_eject()) {
drive.eject_with_operation
(Gio.MountUnmountFlags.FORCE, null, null,
Lang.bind(this, function(drive, res) {
try {
drive.eject_with_operation_finish(res);
} catch (e) {
log("Unable to eject the drive after drive-eject-button " + e.toString());
}
}));
}
},
_onVolumeAdded: function(monitor, volume) {
this._checkAndMountVolume(volume);
},
_checkAndMountVolume: function(volume, params) {
params = Params.parse(params, { checkSession: true,
useMountOp: true,
allowAutorun: true });
if (params.checkSession) {
// if we're not in the current ConsoleKit session,
// don't attempt automount
if (!this._loginManager.sessionActive)
return;
}
if (this._inhibited)
return;
// Volume is already mounted, don't bother.
if (volume.get_mount())
return;
if (!this._settings.get_boolean(SETTING_ENABLE_AUTOMOUNT) ||
!volume.should_automount() ||
!volume.can_mount()) {
// allow the autorun to run anyway; this can happen if the
// mount gets added programmatically later, even if
// should_automount() or can_mount() are false, like for
// blank optical media.
this._allowAutorun(volume);
this._allowAutorunExpire(volume);
return;
}
if (params.useMountOp) {
let operation = new ShellMountOperation.ShellMountOperation(volume);
this._mountVolume(volume, operation, params.allowAutorun);
} else {
this._mountVolume(volume, null, params.allowAutorun);
}
},
_mountVolume: function(volume, operation, allowAutorun) {
if (allowAutorun)
this._allowAutorun(volume);
let mountOp = operation ? operation.mountOp : null;
volume._operation = operation;
volume.mount(0, mountOp, null,
Lang.bind(this, this._onVolumeMounted));
},
_onVolumeMounted: function(volume, res) {
this._allowAutorunExpire(volume);
try {
volume.mount_finish(res);
this._closeOperation(volume);
} catch (e) {
// FIXME: we will always get G_IO_ERROR_FAILED from the gvfs udisks
// backend in this case, see
// https://bugs.freedesktop.org/show_bug.cgi?id=51271
if (e.message.indexOf('No key available with this passphrase') != -1) {
this._reaskPassword(volume);
} else {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED))
log('Unable to mount volume ' + volume.get_name() + ': ' + e.toString());
this._closeOperation(volume);
}
}
},
_onVolumeRemoved: function(monitor, volume) {
this._volumeQueue =
this._volumeQueue.filter(function(element) {
return (element != volume);
});
},
_reaskPassword: function(volume) {
let existingDialog = volume._operation ? volume._operation.borrowDialog() : null;
let operation =
new ShellMountOperation.ShellMountOperation(volume,
{ existingDialog: existingDialog });
this._mountVolume(volume, operation);
},
_closeOperation: function(volume) {
if (volume._operation)
volume._operation.close();
},
_allowAutorun: function(volume) {
volume.allowAutorun = true;
},
_allowAutorunExpire: function(volume) {
Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, function() {
volume.allowAutorun = false;
return false;
});
}
});
const Component = AutomountManager;

View File

@ -1,58 +0,0 @@
const Lang = imports.lang;
const Gio = imports.gi.Gio;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const Recorder = new Lang.Class({
Name: 'Recorder',
_init: function() {
this._recorderSettings = new Gio.Settings({ schema: 'org.gnome.shell.recorder' });
this._desktopLockdownSettings = new Gio.Settings({ schema: 'org.gnome.desktop.lockdown' });
this._bindingSettings = new Gio.Settings({ schema: 'org.gnome.shell.keybindings' });
this._recorder = null;
},
enable: function() {
global.display.add_keybinding('toggle-recording',
this._bindingSettings,
Meta.KeyBindingFlags.NONE, Lang.bind(this, this._toggleRecorder));
},
disable: function() {
global.display.remove_keybinding('toggle-recording');
},
_ensureRecorder: function() {
if (this._recorder == null)
this._recorder = new Shell.Recorder({ stage: global.stage });
return this._recorder;
},
_toggleRecorder: function() {
let recorder = this._ensureRecorder();
if (recorder.is_recording()) {
recorder.close();
Meta.enable_unredirect_for_screen(global.screen);
} else if (!this._desktopLockdownSettings.get_boolean('disable-save-to-disk')) {
// read the parameters from GSettings always in case they have changed
recorder.set_framerate(this._recorderSettings.get_int('framerate'));
/* Translators: this is a filename used for screencast recording */
// xgettext:no-c-format
recorder.set_filename(_("Screencast from %d %t") + '.' + this._recorderSettings.get_string('file-extension'));
let pipeline = this._recorderSettings.get_string('pipeline');
if (!pipeline.match(/^\s*$/))
recorder.set_pipeline(pipeline);
else
recorder.set_pipeline(null);
Meta.disable_unredirect_for_screen(global.screen);
recorder.record();
}
}
});
const Component = Recorder;

196
js/ui/contactDisplay.js Normal file
View File

@ -0,0 +1,196 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Folks = imports.gi.Folks
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Atk = imports.gi.Atk;
const Util = imports.misc.util;
const IconGrid = imports.ui.iconGrid;
const Search = imports.ui.search;
const SearchDisplay = imports.ui.searchDisplay;
const MAX_SEARCH_RESULTS_ROWS = 1;
const ICON_SIZE = 81;
function launchContact(id) {
Util.spawn(['gnome-contacts', '-i', id]);
}
/* This class represents a shown contact search result in the overview */
const Contact = new Lang.Class({
Name: 'Contact',
_init: function(id) {
this._contactSys = Shell.ContactSystem.get_default();
this.individual = this._contactSys.get_individual(id);
this.actor = new St.Bin({ style_class: 'contact',
reactive: true,
can_focus: true,
track_hover: true,
accessible_role: Atk.Role.PUSH_BUTTON });
let content = new St.BoxLayout( { style_class: 'contact-content',
vertical: false });
this.actor.set_child(content);
let icon = new St.Icon({ icon_type: St.IconType.FULLCOLOR,
icon_size: ICON_SIZE,
style_class: 'contact-icon' });
if (this.individual.avatar != null)
icon.gicon = this.individual.avatar;
else
icon.icon_name = 'avatar-default';
content.add(icon, { x_fill: true,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
let details = new St.BoxLayout({ style_class: 'contact-details',
vertical: true });
content.add(details, { x_fill: true,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
let email = this._contactSys.get_email_for_display(this.individual);
let aliasText = this.individual.alias ||
this.individual.full_name ||
this.individual.nickname ||
email ||
_("Unknown");
let aliasLabel = new St.Label({ text: aliasText,
style_class: 'contact-details-alias' });
details.add(aliasLabel, { x_fill: true,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.START });
this.actor.label_actor = aliasLabel;
let presence = this._createPresence(this.individual.presence_type);
details.add(presence, { x_fill: false,
y_fill: true,
x_align: St.Align.START,
y_align: St.Align.END });
},
_createPresence: function(presence) {
let text;
let iconName;
switch(presence) {
case Folks.PresenceType.AVAILABLE:
text = _("Available");
iconName = 'user-available';
break;
case Folks.PresenceType.AWAY:
case Folks.PresenceType.EXTENDED_AWAY:
text = _("Away");
iconName = 'user-away';
break;
case Folks.PresenceType.BUSY:
text = _("Busy");
iconName = 'user-busy';
break;
case Folks.PresenceType.OFFLINE:
text = _("Offline");
iconName = 'user-offline';
break;
default:
text = '';
iconName = null;
}
let box = new St.BoxLayout({ vertical: false,
style_class: 'contact-details-status' });
if (iconName) {
let icon = new St.Icon({ icon_name: iconName,
icon_type: St.IconType.FULLCOLOR,
icon_size: 16,
style_class: 'contact-details-status-icon' });
box.add(icon, { x_fill: true,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.START });
}
let label = new St.Label({ text: text });
box.add(label, { x_fill: true,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.START });
return box;
},
createIcon: function(size) {
let tc = St.TextureCache.get_default();
let icon = this.individual.avatar;
if (icon != null) {
return tc.load_gicon(null, icon, size);
} else {
return tc.load_icon_name(null, 'avatar-default', St.IconType.FULLCOLOR, size);
}
},
});
/* Searches for and returns contacts */
const ContactSearchProvider = new Lang.Class({
Name: 'ContactSearchProvider',
Extends: Search.SearchProvider,
_init: function() {
this.parent(_("CONTACTS"));
this._contactSys = Shell.ContactSystem.get_default();
},
getResultMetas: function(ids) {
let metas = [];
for (let i = 0; i < ids.length; i++) {
let contact = new Contact(ids[i]);
metas.push({ 'id': ids[i],
'name': contact.alias,
'createIcon': function(size) {
return contact.createIcon(size);
}
});
}
return metas;
},
getInitialResultSet: function(terms) {
return this._contactSys.initial_search(terms);
},
getSubsearchResultSet: function(previousResults, terms) {
return this._contactSys.subsearch(previousResults, terms);
},
createResultActor: function(resultMeta, terms) {
let contact = new Contact(resultMeta.id);
return contact.actor;
},
createResultContainerActor: function() {
let grid = new IconGrid.IconGrid({ rowLimit: MAX_SEARCH_RESULTS_ROWS,
xAlign: St.Align.START });
grid.actor.style_class = 'contact-grid';
let actor = new SearchDisplay.GridSearchResults(this, grid);
return actor;
},
activateResult: function(id, params) {
launchContact(id);
}
});

View File

@ -27,6 +27,7 @@ const CtrlAltTabManager = new Lang.Class({
_init: function() { _init: function() {
this._items = []; this._items = [];
this._focusManager = St.FocusManager.get_for_stage(global.stage);
}, },
addGroup: function(root, name, icon, params) { addGroup: function(root, name, icon, params) {
@ -40,11 +41,11 @@ const CtrlAltTabManager = new Lang.Class({
this._items.push(item); this._items.push(item);
root.connect('destroy', Lang.bind(this, function() { this.removeGroup(root); })); root.connect('destroy', Lang.bind(this, function() { this.removeGroup(root); }));
global.focus_manager.add_group(root); this._focusManager.add_group(root);
}, },
removeGroup: function(root) { removeGroup: function(root) {
global.focus_manager.remove_group(root); this._focusManager.remove_group(root);
for (let i = 0; i < this._items.length; i++) { for (let i = 0; i < this._items.length; i++) {
if (this._items[i].root == root) { if (this._items[i].root == root) {
this._items.splice(i, 1); this._items.splice(i, 1);
@ -54,17 +55,16 @@ const CtrlAltTabManager = new Lang.Class({
}, },
focusGroup: function(item) { focusGroup: function(item) {
if (item.window) { if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE ||
Main.activateWindow(item.window); global.stage_input_mode == Shell.StageInputMode.NORMAL)
} else if (item.focusCallback) { global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
item.focusCallback();
} else {
if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE ||
global.stage_input_mode == Shell.StageInputMode.NORMAL)
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
if (item.window)
Main.activateWindow(item.window);
else if (item.focusCallback)
item.focusCallback();
else
item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
}
}, },
// Sort the items into a consistent order; panel first, tray last, // Sort the items into a consistent order; panel first, tray last,
@ -319,6 +319,7 @@ const CtrlAltTabSwitcher = new Lang.Class({
let icon = item.iconActor; let icon = item.iconActor;
if (!icon) { if (!icon) {
icon = new St.Icon({ icon_name: item.iconName, icon = new St.Icon({ icon_name: item.iconName,
icon_type: St.IconType.SYMBOLIC,
icon_size: POPUP_APPICON_SIZE }); icon_size: POPUP_APPICON_SIZE });
} }
box.add(icon, { x_fill: false, y_fill: false } ); box.add(icon, { x_fill: false, y_fill: false } );

View File

@ -21,18 +21,6 @@ const DASH_ITEM_LABEL_SHOW_TIME = 0.15;
const DASH_ITEM_LABEL_HIDE_TIME = 0.1; const DASH_ITEM_LABEL_HIDE_TIME = 0.1;
const DASH_ITEM_HOVER_TIMEOUT = 300; const DASH_ITEM_HOVER_TIMEOUT = 300;
function getAppFromSource(source) {
if (source instanceof AppDisplay.AppWellIcon) {
let appSystem = Shell.AppSystem.get_default();
return appSystem.lookup_app(source.getId());
} else if (source.metaWindow) {
let tracker = Shell.WindowTracker.get_default();
return tracker.get_window_app(source.metaWindow);
} else {
return null;
}
}
// A container like StBin, but taking the child's scale into account // A container like StBin, but taking the child's scale into account
// when requesting a size // when requesting a size
const DashItemContainer = new Lang.Class({ const DashItemContainer = new Lang.Class({
@ -48,11 +36,7 @@ const DashItemContainer = new Lang.Class({
Lang.bind(this, this._allocate)); Lang.bind(this, this._allocate));
this.actor._delegate = this; this.actor._delegate = this;
this._labelText = ""; this.label = null;
this.label = new St.Label({ style_class: 'dash-label'});
this.label.hide();
Main.layoutManager.addChrome(this.label);
this.actor.label_actor = this.label;
this.child = null; this.child = null;
this._childScale = 1; this._childScale = 1;
@ -107,10 +91,9 @@ const DashItemContainer = new Lang.Class({
}, },
showLabel: function() { showLabel: function() {
if (!this._labelText) if (this.label == null)
return; return;
this.label.set_text(this._labelText);
this.label.opacity = 0; this.label.opacity = 0;
this.label.show(); this.label.show();
@ -141,11 +124,16 @@ const DashItemContainer = new Lang.Class({
}, },
setLabelText: function(text) { setLabelText: function(text) {
this._labelText = text; if (this.label == null)
this.child.accessible_name = text; this.label = new St.Label({ style_class: 'dash-label'});
this.label.set_text(text);
Main.layoutManager.addChrome(this.label);
this.label.hide();
}, },
hideLabel: function () { hideLabel: function () {
this.label.opacity = 255;
Tweener.addTween(this.label, Tweener.addTween(this.label,
{ opacity: 0, { opacity: 0,
time: DASH_ITEM_LABEL_HIDE_TIME, time: DASH_ITEM_LABEL_HIDE_TIME,
@ -239,76 +227,52 @@ const DashItemContainer = new Lang.Class({
} }
}); });
const ShowAppsIcon = new Lang.Class({ const RemoveFavoriteIcon = new Lang.Class({
Name: 'ShowAppsIcon', Name: 'RemoveFavoriteIcon',
Extends: DashItemContainer, Extends: DashItemContainer,
_init: function() { _init: function() {
this.parent(); this.parent();
this.toggleButton = new St.Button({ style_class: 'show-apps', this._iconBin = new St.Bin({ style_class: 'remove-favorite' });
track_hover: true,
can_focus: true,
toggle_mode: true });
this._iconActor = null; this._iconActor = null;
this.icon = new IconGrid.BaseIcon(_("Show Applications"), this.icon = new IconGrid.BaseIcon(_("Remove"),
{ setSizeManually: true, { setSizeManually: true,
showLabel: false, showLabel: false,
createIcon: Lang.bind(this, this._createIcon) }); createIcon: Lang.bind(this, this._createIcon) });
this.toggleButton.add_actor(this.icon.actor); this._iconBin.set_child(this.icon.actor);
this.toggleButton._delegate = this; this._iconBin._delegate = this;
this.setChild(this.toggleButton); this.setChild(this._iconBin);
this.setDragApp(null);
}, },
_createIcon: function(size) { _createIcon: function(size) {
this._iconActor = new St.Icon({ icon_name: 'view-grid-symbolic', this._iconActor = new St.Icon({ icon_name: 'user-trash',
icon_size: size, style_class: 'remove-favorite-icon',
style_class: 'show-apps-icon', icon_size: size });
track_hover: true });
return this._iconActor; return this._iconActor;
}, },
_canRemoveApp: function(app) { setHover: function(hovered) {
if (app == null) this._iconBin.set_hover(hovered);
return false;
let id = app.get_id();
let isFavorite = AppFavorites.getAppFavorites().isFavorite(id);
return isFavorite;
},
setDragApp: function(app) {
let canRemove = this._canRemoveApp(app);
this.toggleButton.set_hover(canRemove);
if (this._iconActor) if (this._iconActor)
this._iconActor.set_hover(canRemove); this._iconActor.set_hover(hovered);
if (canRemove)
this.setLabelText(_("Remove from Favorites"));
else
this.setLabelText(_("Show Applications"));
}, },
// Rely on the dragged item being a favorite
handleDragOver: function(source, actor, x, y, time) { handleDragOver: function(source, actor, x, y, time) {
let app = getAppFromSource(source);
if (app == null)
return DND.DragMotionResult.NO_DROP;
let id = app.get_id();
let isFavorite = AppFavorites.getAppFavorites().isFavorite(id);
if (!isFavorite)
return DND.DragMotionResult.NO_DROP;
return DND.DragMotionResult.MOVE_DROP; return DND.DragMotionResult.MOVE_DROP;
}, },
acceptDrop: function(source, actor, x, y, time) { acceptDrop: function(source, actor, x, y, time) {
let app = getAppFromSource(source); let app = null;
if (app == null) if (source instanceof AppDisplay.AppWellIcon) {
return false; let appSystem = Shell.AppSystem.get_default();
app = appSystem.lookup_app(source.getId());
} else if (source.metaWindow) {
let tracker = Shell.WindowTracker.get_default();
app = tracker.get_window_app(source.metaWindow);
}
let id = app.get_id(); let id = app.get_id();
@ -332,39 +296,6 @@ const DragPlaceholderItem = new Lang.Class({
} }
}); });
const DashActor = new Lang.Class({
Name: 'DashActor',
Extends: St.Widget,
_init: function() {
let layout = new Clutter.BoxLayout({ orientation: Clutter.Orientation.VERTICAL });
this.parent({ name: 'dash',
layout_manager: layout,
clip_to_allocation: true });
},
vfunc_allocate: function(box, flags) {
let contentBox = this.get_theme_node().get_content_box(box);
let availWidth = contentBox.x2 - contentBox.x1;
this.set_allocation(box, flags);
let [appIcons, showAppsButton] = this.get_children();
let [showAppsMinHeight, showAppsNatHeight] = showAppsButton.get_preferred_height(availWidth);
let childBox = new Clutter.ActorBox();
childBox.x1 = contentBox.x1;
childBox.y1 = contentBox.y1;
childBox.x2 = contentBox.x2;
childBox.y2 = contentBox.y2 - showAppsNatHeight;
appIcons.allocate(childBox, flags);
childBox.y1 = contentBox.y2 - showAppsNatHeight;
childBox.y2 = contentBox.y2;
showAppsButton.allocate(childBox, flags);
}
});
const Dash = new Lang.Class({ const Dash = new Lang.Class({
Name: 'Dash', Name: 'Dash',
@ -376,25 +307,17 @@ const Dash = new Lang.Class({
this._dragPlaceholder = null; this._dragPlaceholder = null;
this._dragPlaceholderPos = -1; this._dragPlaceholderPos = -1;
this._animatingPlaceholdersCount = 0; this._animatingPlaceholdersCount = 0;
this._favRemoveTarget = null;
this._showLabelTimeoutId = 0; this._showLabelTimeoutId = 0;
this._resetHoverTimeoutId = 0; this._resetHoverTimeoutId = 0;
this._labelShowing = false; this._labelShowing = false;
this._container = new DashActor(); this._box = new St.BoxLayout({ name: 'dash',
this._box = new St.BoxLayout({ vertical: true, vertical: true,
clip_to_allocation: true }); clip_to_allocation: true });
this._box._delegate = this; this._box._delegate = this;
this._container.add_actor(this._box);
this._showAppsIcon = new ShowAppsIcon(); this.actor = new St.Bin({ y_align: St.Align.START, child: this._box });
this._showAppsIcon.icon.setIconSize(this.iconSize);
this._hookUpLabel(this._showAppsIcon);
this.showAppsButton = this._showAppsIcon.toggleButton;
this._container.add_actor(this._showAppsIcon.actor);
this.actor = new St.Bin({ child: this._container });
this.actor.connect('notify::height', Lang.bind(this, this.actor.connect('notify::height', Lang.bind(this,
function() { function() {
if (this._maxHeight != this.actor.height) if (this._maxHeight != this.actor.height)
@ -404,6 +327,7 @@ const Dash = new Lang.Class({
this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay)); this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay));
this._tracker = Shell.WindowTracker.get_default();
this._appSystem = Shell.AppSystem.get_default(); this._appSystem = Shell.AppSystem.get_default();
this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay)); this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
@ -446,25 +370,54 @@ const Dash = new Lang.Class({
_endDrag: function() { _endDrag: function() {
this._clearDragPlaceholder(); this._clearDragPlaceholder();
this._showAppsIcon.setDragApp(null); if (this._favRemoveTarget) {
this._favRemoveTarget.animateOutAndDestroy();
this._favRemoveTarget.actor.connect('destroy', Lang.bind(this,
function() {
this._favRemoveTarget = null;
}));
this._adjustIconSize();
}
DND.removeDragMonitor(this._dragMonitor); DND.removeDragMonitor(this._dragMonitor);
}, },
_onDragMotion: function(dragEvent) { _onDragMotion: function(dragEvent) {
let app = getAppFromSource(dragEvent.source); let app = null;
if (app == null) if (dragEvent.source instanceof AppDisplay.AppWellIcon)
app = this._appSystem.lookup_app(dragEvent.source.getId());
else if (dragEvent.source.metaWindow)
app = this._tracker.get_window_app(dragEvent.source.metaWindow);
else
return DND.DragMotionResult.CONTINUE; return DND.DragMotionResult.CONTINUE;
let showAppsHovered = let id = app.get_id();
this._showAppsIcon.actor.contains(dragEvent.targetActor);
if (!this._box.contains(dragEvent.targetActor) || showAppsHovered) let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
let srcIsFavorite = (id in favorites);
if (srcIsFavorite &&
app.get_state() != Shell.AppState.RUNNING &&
dragEvent.source.actor &&
this.actor.contains (dragEvent.source.actor) &&
this._favRemoveTarget == null) {
this._favRemoveTarget = new RemoveFavoriteIcon();
this._favRemoveTarget.icon.setIconSize(this.iconSize);
this._box.add(this._favRemoveTarget.actor);
this._adjustIconSize();
this._favRemoveTarget.animateIn();
}
let favRemoveHovered = false;
if (this._favRemoveTarget)
favRemoveHovered =
this._favRemoveTarget.actor.contains(dragEvent.targetActor);
if (!this._box.contains(dragEvent.targetActor) || favRemoveHovered)
this._clearDragPlaceholder(); this._clearDragPlaceholder();
if (showAppsHovered) if (this._favRemoveTarget)
this._showAppsIcon.setDragApp(app); this._favRemoveTarget.setHover(favRemoveHovered);
else
this._showAppsIcon.setDragApp(null);
return DND.DragMotionResult.CONTINUE; return DND.DragMotionResult.CONTINUE;
}, },
@ -480,17 +433,6 @@ const Dash = new Lang.Class({
Main.queueDeferredWork(this._workId); Main.queueDeferredWork(this._workId);
}, },
_hookUpLabel: function(item) {
item.child.connect('notify::hover', Lang.bind(this, function() {
this._onHover(item);
}));
Main.overview.connect('hiding', Lang.bind(this, function() {
this._labelShowing = false;
item.hideLabel();
}));
},
_createAppItem: function(app) { _createAppItem: function(app) {
let display = new AppDisplay.AppWellIcon(app, let display = new AppDisplay.AppWellIcon(app,
{ setSizeManually: true, { setSizeManually: true,
@ -507,19 +449,21 @@ const Dash = new Lang.Class({
let item = new DashItemContainer(); let item = new DashItemContainer();
item.setChild(display.actor); item.setChild(display.actor);
// Override default AppWellIcon label_actor, now the
// accessible_name is set at DashItemContainer.setLabelText
display.actor.label_actor = null;
item.setLabelText(app.get_name()); item.setLabelText(app.get_name());
// Override default AppWellIcon label_actor
display.actor.label_actor = item.label;
display.icon.setIconSize(this.iconSize); display.icon.setIconSize(this.iconSize);
this._hookUpLabel(item); display.actor.connect('notify::hover',
Lang.bind(this, function() {
this._onHover(item, display)
}));
return item; return item;
}, },
_onHover: function (item) { _onHover: function (item, display) {
if (item.child.get_hover() && !item.child._delegate.isMenuUp) { if (display.actor.get_hover() && !display.isMenuUp) {
if (this._showLabelTimeoutId == 0) { if (this._showLabelTimeoutId == 0) {
let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT; let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT;
this._showLabelTimeoutId = Mainloop.timeout_add(timeout, this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
@ -530,7 +474,7 @@ const Dash = new Lang.Class({
})); }));
if (this._resetHoverTimeoutId > 0) { if (this._resetHoverTimeoutId > 0) {
Mainloop.source_remove(this._resetHoverTimeoutId); Mainloop.source_remove(this._resetHoverTimeoutId);
this._resetHoverTimeoutId = 0; this._resetHoverTimeoutId = 0;
} }
} }
} else { } else {
@ -560,12 +504,18 @@ const Dash = new Lang.Class({
!actor._delegate.animatingOut; !actor._delegate.animatingOut;
}); });
iconChildren.push(this._showAppsIcon.actor); if (iconChildren.length == 0) {
this._box.add_style_pseudo_class('empty');
return;
}
this._box.remove_style_pseudo_class('empty');
if (this._maxHeight == -1) if (this._maxHeight == -1)
return; return;
let themeNode = this._container.get_theme_node();
let themeNode = this._box.get_theme_node();
let maxAllocation = new Clutter.ActorBox({ x1: 0, y1: 0, let maxAllocation = new Clutter.ActorBox({ x1: 0, y1: 0,
x2: 42 /* whatever */, x2: 42 /* whatever */,
y2: this._maxHeight }); y2: this._maxHeight });
@ -591,6 +541,7 @@ const Dash = new Lang.Class({
[minHeight, natHeight] = iconChildren[0].get_preferred_height(-1); [minHeight, natHeight] = iconChildren[0].get_preferred_height(-1);
} }
// Subtract icon padding and box spacing from the available height // Subtract icon padding and box spacing from the available height
availHeight -= iconChildren.length * (natHeight - this.iconSize) + availHeight -= iconChildren.length * (natHeight - this.iconSize) +
(iconChildren.length - 1) * spacing; (iconChildren.length - 1) * spacing;
@ -621,10 +572,8 @@ const Dash = new Lang.Class({
icon.setIconSize(this.iconSize); icon.setIconSize(this.iconSize);
// Don't animate the icon size change when the overview // Don't animate the icon size change when the overview
// is transitioning, not visible or when initially filling // is not visible or when initially filling the dash
// the dash if (!Main.overview.visible || !this._shownInitially)
if (!Main.overview.visible || Main.overview.animationInProgress ||
!this._shownInitially)
continue; continue;
let [targetWidth, targetHeight] = icon.icon.get_size(); let [targetWidth, targetHeight] = icon.icon.get_size();
@ -748,9 +697,8 @@ const Dash = new Lang.Class({
for (let i = 0; i < removedActors.length; i++) { for (let i = 0; i < removedActors.length; i++) {
let item = removedActors[i]._delegate; let item = removedActors[i]._delegate;
// Don't animate item removal when the overview is transitioning // Don't animate item removal when the overview is hidden
// or hidden if (Main.overview.visible)
if (Main.overview.visible && !Main.overview.animationInProgress)
item.animateOutAndDestroy(); item.animateOutAndDestroy();
else else
item.destroy(); item.destroy();
@ -765,9 +713,8 @@ const Dash = new Lang.Class({
return; return;
} }
// Don't animate item addition when the overview is transitioning // Don't animate item addition when the overview is hidden
// or hidden if (!Main.overview.visible)
if (!Main.overview.visible || Main.overview.animationInProgress)
return; return;
for (let i = 0; i < addedItems.length; i++) for (let i = 0; i < addedItems.length; i++)
@ -783,7 +730,11 @@ const Dash = new Lang.Class({
}, },
handleDragOver : function(source, actor, x, y, time) { handleDragOver : function(source, actor, x, y, time) {
let app = getAppFromSource(source); let app = null;
if (source instanceof AppDisplay.AppWellIcon)
app = this._appSystem.lookup_app(source.getId());
else if (source.metaWindow)
app = this._tracker.get_window_app(source.metaWindow);
// Don't allow favoriting of transient apps // Don't allow favoriting of transient apps
if (app == null || app.is_window_backed()) if (app == null || app.is_window_backed())
@ -864,7 +815,12 @@ const Dash = new Lang.Class({
// Draggable target interface // Draggable target interface
acceptDrop : function(source, actor, x, y, time) { acceptDrop : function(source, actor, x, y, time) {
let app = getAppFromSource(source); let app = null;
if (source instanceof AppDisplay.AppWellIcon) {
app = this._appSystem.lookup_app(source.getId());
} else if (source.metaWindow) {
app = this._tracker.get_window_app(source.metaWindow);
}
// Don't allow favoriting of transient apps // Don't allow favoriting of transient apps
if (app == null || app.is_window_backed()) { if (app == null || app.is_window_backed()) {

View File

@ -2,7 +2,6 @@
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GnomeDesktop = imports.gi.GnomeDesktop;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Cairo = imports.cairo; const Cairo = imports.cairo;
@ -17,6 +16,14 @@ const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Calendar = imports.ui.calendar; const Calendar = imports.ui.calendar;
const UPowerGlib = imports.gi.UPowerGlib;
// in org.gnome.desktop.interface
const CLOCK_FORMAT_KEY = 'clock-format';
// in org.gnome.shell.clock
const CLOCK_SHOW_DATE_KEY = 'show-date';
const CLOCK_SHOW_SECONDS_KEY = 'show-seconds';
function _onVertSepRepaint (area) function _onVertSepRepaint (area)
{ {
@ -38,7 +45,9 @@ const DateMenuButton = new Lang.Class({
Name: 'DateMenuButton', Name: 'DateMenuButton',
Extends: PanelMenu.Button, Extends: PanelMenu.Button,
_init: function() { _init: function(params) {
params = Params.parse(params, { showEvents: true });
let item; let item;
let hbox; let hbox;
let vbox; let vbox;
@ -53,8 +62,8 @@ const DateMenuButton = new Lang.Class({
// role ATK_ROLE_MENU like other elements of the panel. // role ATK_ROLE_MENU like other elements of the panel.
this.actor.accessible_role = Atk.Role.LABEL; this.actor.accessible_role = Atk.Role.LABEL;
this._clockDisplay = new St.Label(); this._clock = new St.Label();
this.actor.add_actor(this._clockDisplay); this.actor.add_actor(this._clock);
hbox = new St.BoxLayout({name: 'calendarArea' }); hbox = new St.BoxLayout({name: 'calendarArea' });
this.menu.addActor(hbox); this.menu.addActor(hbox);
@ -66,12 +75,20 @@ const DateMenuButton = new Lang.Class({
// Date // Date
this._date = new St.Label(); this._date = new St.Label();
this.actor.label_actor = this._clockDisplay; this.actor.label_actor = this._date;
this._date.style_class = 'datemenu-date-label'; this._date.style_class = 'datemenu-date-label';
vbox.add(this._date); vbox.add(this._date);
this._eventList = new Calendar.EventsList(); if (params.showEvents) {
this._calendar = new Calendar.Calendar(); this._eventSource = new Calendar.DBusEventSource();
this._eventList = new Calendar.EventsList(this._eventSource);
} else {
this._eventSource = null;
this._eventList = null;
}
// Calendar
this._calendar = new Calendar.Calendar(this._eventSource);
this._calendar.connect('selected-date-changed', this._calendar.connect('selected-date-changed',
Lang.bind(this, function(calendar, date) { Lang.bind(this, function(calendar, date) {
@ -89,34 +106,31 @@ const DateMenuButton = new Lang.Class({
separator.setColumnWidths(1); separator.setColumnWidths(1);
vbox.add(separator.actor, {y_align: St.Align.END, expand: true, y_fill: false}); vbox.add(separator.actor, {y_align: St.Align.END, expand: true, y_fill: false});
item.actor.show_on_set_parent = false;
item.actor.can_focus = false; item.actor.can_focus = false;
item.actor.reparent(vbox); item.actor.reparent(vbox);
this._dateAndTimeSeparator = separator;
} }
this._separator = new St.DrawingArea({ style_class: 'calendar-vertical-separator', if (params.showEvents) {
pseudo_class: 'highlighted' }); // Add vertical separator
this._separator.connect('repaint', Lang.bind(this, _onVertSepRepaint));
hbox.add(this._separator);
// Fill up the second column item = new St.DrawingArea({ style_class: 'calendar-vertical-separator',
vbox = new St.BoxLayout({ name: 'calendarEventsArea', pseudo_class: 'highlighted' });
vertical: true }); item.connect('repaint', Lang.bind(this, _onVertSepRepaint));
hbox.add(vbox, { expand: true }); hbox.add(item);
// Event list // Fill up the second column
vbox.add(this._eventList.actor, { expand: true }); vbox = new St.BoxLayout({name: 'calendarEventsArea',
vertical: true});
hbox.add(vbox, { expand: true });
this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar")); // Event list
this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate)); vbox.add(this._eventList.actor, { expand: true });
this._openCalendarItem.actor.can_focus = false;
vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: true, y_fill: false});
this._calendarSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.office.calendar' }); item = new PopupMenu.PopupMenuItem(_("Open Calendar"));
this._calendarSettings.connect('changed::exec', item.connect('activate', Lang.bind(this, this._onOpenCalendarActivate));
Lang.bind(this, this._calendarSettingsChanged)); item.actor.can_focus = false;
this._calendarSettingsChanged(); vbox.add(item.actor, {y_align: St.Align.END, expand: true, y_fill: false});
}
// Whenever the menu is opened, select today // Whenever the menu is opened, select today
this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) { this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
@ -143,75 +157,79 @@ const DateMenuButton = new Lang.Class({
// Done with hbox for calendar and event list // Done with hbox for calendar and event list
this._clock = new GnomeDesktop.WallClock(); // Track changes to clock settings
this._clock.connect('notify::clock', Lang.bind(this, this._updateClockAndDate)); this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
this._clockSettings = new Gio.Settings({ schema: 'org.gnome.shell.clock' });
this._desktopSettings.connect('changed', Lang.bind(this, this._updateClockAndDate));
this._clockSettings.connect('changed', Lang.bind(this, this._updateClockAndDate));
// https://bugzilla.gnome.org/show_bug.cgi?id=655129
this._upClient = new UPowerGlib.Client();
this._upClient.connect('notify-resume', Lang.bind(this, this._updateClockAndDate));
// Start the clock
this._updateClockAndDate(); this._updateClockAndDate();
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
this._sessionUpdated();
},
_calendarSettingsChanged: function() {
let exec = this._calendarSettings.get_string('exec');
let fullExec = GLib.find_program_in_path(exec);
this._openCalendarItem.actor.visible = fullExec != null;
},
_setEventsVisibility: function(visible) {
this._openCalendarItem.actor.visible = visible;
this._separator.visible = visible;
if (visible) {
let alignment = 0.25;
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
alignment = 1.0 - alignment;
this.menu._arrowAlignment = alignment;
this._eventList.actor.get_parent().show();
} else {
this.menu._arrowAlignment = 0.5;
this._eventList.actor.get_parent().hide();
}
},
_setEventSource: function(eventSource) {
this._calendar.setEventSource(eventSource);
this._eventList.setEventSource(eventSource);
},
_sessionUpdated: function() {
let eventSource;
let showEvents = Main.sessionMode.showCalendarEvents;
if (showEvents) {
eventSource = new Calendar.DBusEventSource();
} else {
eventSource = null;
}
this._setEventSource(eventSource);
this._setEventsVisibility(showEvents);
// This needs to be handled manually, as the code to
// autohide separators doesn't work across the vbox
this._dateAndTimeSeparator.actor.visible = Main.sessionMode.allowSettings;
}, },
_updateClockAndDate: function() { _updateClockAndDate: function() {
this._clockDisplay.set_text(this._clock.clock); let format = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
let showDate = this._clockSettings.get_boolean(CLOCK_SHOW_DATE_KEY);
let showSeconds = this._clockSettings.get_boolean(CLOCK_SHOW_SECONDS_KEY);
let clockFormat;
let dateFormat;
switch (format) {
case '24h':
if (showDate)
/* Translators: This is the time format with date used
in 24-hour mode. */
clockFormat = showSeconds ? _("%a %b %e, %R:%S")
: _("%a %b %e, %R");
else
/* Translators: This is the time format without date used
in 24-hour mode. */
clockFormat = showSeconds ? _("%a %R:%S")
: _("%a %R");
break;
case '12h':
default:
if (showDate)
/* Translators: This is a time format with date used
for AM/PM. */
clockFormat = showSeconds ? _("%a %b %e, %l:%M:%S %p")
: _("%a %b %e, %l:%M %p");
else
/* Translators: This is a time format without date used
for AM/PM. */
clockFormat = showSeconds ? _("%a %l:%M:%S %p")
: _("%a %l:%M %p");
break;
}
let displayDate = new Date();
this._clock.set_text(displayDate.toLocaleFormat(clockFormat));
/* Translators: This is the date format to use when the calendar popup is /* Translators: This is the date format to use when the calendar popup is
* shown - it is shown just below the time in the shell (e.g. "Tue 9:29 AM"). * shown - it is shown just below the time in the shell (e.g. "Tue 9:29 AM").
*/ */
let dateFormat = _("%A %B %e, %Y"); dateFormat = _("%A %B %e, %Y");
let displayDate = new Date();
this._date.set_text(displayDate.toLocaleFormat(dateFormat)); this._date.set_text(displayDate.toLocaleFormat(dateFormat));
Mainloop.timeout_add_seconds(1, Lang.bind(this, this._updateClockAndDate));
return false;
}, },
_onOpenCalendarActivate: function() { _onOpenCalendarActivate: function() {
this.menu.close(); this.menu.close();
let tool = this._calendarSettings.get_string('exec'); let calendarSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.office.calendar' });
if (tool.length == 0 || tool.substr(0, 9) == 'evolution') { let tool = calendarSettings.get_string('exec');
if (tool.length == 0 || tool == 'evolution') {
// TODO: pass the selected day // TODO: pass the selected day
let app = Shell.AppSystem.get_default().lookup_app('evolution-calendar.desktop'); Util.spawn(['evolution', '-c', 'calendar']);
app.activate();
} else { } else {
let needTerm = this._calendarSettings.get_boolean('needs-term'); let needTerm = calendarSettings.get_boolean('needs-term');
if (needTerm) { if (needTerm) {
let terminalSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.terminal' }); let terminalSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.terminal' });
let term = terminalSettings.get_string('exec'); let term = terminalSettings.get_string('exec');

View File

@ -235,10 +235,6 @@ const _Draggable = new Lang.Class({
if (this.actor._delegate && this.actor._delegate.getDragActor) { if (this.actor._delegate && this.actor._delegate.getDragActor) {
this._dragActor = this.actor._delegate.getDragActor(this._dragStartX, this._dragStartY); this._dragActor = this.actor._delegate.getDragActor(this._dragStartX, this._dragStartY);
this._dragActor.reparent(Main.uiGroup);
this._dragActor.raise_top();
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 // Drag actor does not always have to be the same as actor. For example drag actor
// can be an image that's part of the actor. So to perform "snap back" correctly we need // can be an image that's part of the actor. So to perform "snap back" correctly we need
// to know what was the drag actor source. // to know what was the drag actor source.
@ -267,17 +263,12 @@ const _Draggable = new Lang.Class({
this._dragOffsetY = this._dragActor.y - this._dragStartY; this._dragOffsetY = this._dragActor.y - this._dragStartY;
} else { } else {
this._dragActor = this.actor; this._dragActor = this.actor;
this._dragActorSource = undefined; this._dragActorSource = undefined;
this._dragOrigParent = this.actor.get_parent(); this._dragOrigParent = this.actor.get_parent();
this._dragOrigX = this._dragActor.x; this._dragOrigX = this._dragActor.x;
this._dragOrigY = this._dragActor.y; this._dragOrigY = this._dragActor.y;
this._dragOrigScale = this._dragActor.scale_x; this._dragOrigScale = this._dragActor.scale_x;
this._dragActor.reparent(Main.uiGroup);
this._dragActor.raise_top();
Shell.util_set_hidden_from_pick(this._dragActor, true);
let [actorStageX, actorStageY] = this.actor.get_transformed_position(); let [actorStageX, actorStageY] = this.actor.get_transformed_position();
this._dragOffsetX = actorStageX - this._dragStartX; this._dragOffsetX = actorStageX - this._dragStartX;
this._dragOffsetY = actorStageY - this._dragStartY; this._dragOffsetY = actorStageY - this._dragStartY;
@ -289,6 +280,10 @@ const _Draggable = new Lang.Class({
scaledHeight / this.actor.height); scaledHeight / this.actor.height);
} }
this._dragActor.reparent(Main.uiGroup);
this._dragActor.raise_top();
Shell.util_set_hidden_from_pick(this._dragActor, true);
this._dragOrigOpacity = this._dragActor.opacity; this._dragOrigOpacity = this._dragActor.opacity;
if (this._dragActorOpacity != undefined) if (this._dragActorOpacity != undefined)
this._dragActor.opacity = this._dragActorOpacity; this._dragActor.opacity = this._dragActorOpacity;

View File

@ -31,10 +31,10 @@ const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const GnomeSession = imports.misc.gnomeSession; const GnomeSession = imports.misc.gnomeSession;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main; const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog; const ModalDialog = imports.ui.modalDialog;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const UserMenu = imports.ui.userMenu;
let _endSessionDialog = null; let _endSessionDialog = null;
@ -90,7 +90,7 @@ const shutdownDialogContent = {
label: C_("button", "Restart") }, label: C_("button", "Restart") },
{ signal: 'ConfirmedShutdown', { signal: 'ConfirmedShutdown',
label: C_("button", "Power Off") }], label: C_("button", "Power Off") }],
iconName: 'system-shutdown-symbolic', iconName: 'system-shutdown',
iconStyleClass: 'end-session-dialog-shutdown-icon' iconStyleClass: 'end-session-dialog-shutdown-icon'
}; };
@ -105,7 +105,7 @@ const restartDialogContent = {
endDescription: _("Restarting the system."), endDescription: _("Restarting the system."),
confirmButtons: [{ signal: 'ConfirmedReboot', confirmButtons: [{ signal: 'ConfirmedReboot',
label: C_("button", "Restart") }], label: C_("button", "Restart") }],
iconName: 'system-shutdown-symbolic', iconName: 'system-shutdown',
iconStyleClass: 'end-session-dialog-shutdown-icon' iconStyleClass: 'end-session-dialog-shutdown-icon'
}; };
@ -161,7 +161,6 @@ const ListItem = new Lang.Class({
this._descriptionLabel = new St.Label({ text: this._reason, this._descriptionLabel = new St.Label({ text: this._reason,
style_class: 'end-session-dialog-app-list-item-description' }); style_class: 'end-session-dialog-app-list-item-description' });
this.actor.label_actor = this._nameLabel;
textLayout.add(this._descriptionLabel, textLayout.add(this._descriptionLabel,
{ expand: true, { expand: true,
x_fill: true }); x_fill: true });
@ -281,17 +280,21 @@ const EndSessionDialog = new Lang.Class({
scrollView.hide(); scrollView.hide();
this._applicationList = new St.BoxLayout({ vertical: true }); this._applicationList = new St.BoxLayout({ vertical: true });
scrollView.add_actor(this._applicationList); scrollView.add_actor(this._applicationList,
{ x_fill: true,
y_fill: true,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
this._applicationList.connect('actor-added', this._applicationList.connect('actor-added',
Lang.bind(this, function() { Lang.bind(this, function() {
if (this._applicationList.get_n_children() == 1) if (this._applicationList.get_children().length == 1)
scrollView.show(); scrollView.show();
})); }));
this._applicationList.connect('actor-removed', this._applicationList.connect('actor-removed',
Lang.bind(this, function() { Lang.bind(this, function() {
if (this._applicationList.get_n_children() == 0) if (this._applicationList.get_children().length == 0)
scrollView.hide(); scrollView.hide();
})); }));
@ -304,7 +307,42 @@ const EndSessionDialog = new Lang.Class({
this._user.disconnect(this._userChangedId); this._user.disconnect(this._userChangedId);
}, },
_updateDescription: function() { _setIconFromFile: function(iconFile, styleClass) {
if (styleClass)
this._iconBin.set_style_class_name(styleClass);
this._iconBin.set_style(null);
this._iconBin.child = null;
if (iconFile) {
this._iconBin.show();
this._iconBin.set_style('background-image: url("' + iconFile + '");' +
'background-size: contain;');
} else {
this._iconBin.hide();
}
},
_setIconFromName: function(iconName, styleClass) {
if (styleClass)
this._iconBin.set_style_class_name(styleClass);
this._iconBin.set_style(null);
if (iconName != null) {
let textureCache = St.TextureCache.get_default();
let icon = textureCache.load_icon_name(this._iconBin.get_theme_node(),
iconName,
St.IconType.SYMBOLIC,
_DIALOG_ICON_SIZE);
this._iconBin.child = icon;
this._iconBin.show();
} else {
this._iconBin.child = null;
this._iconBin.hide();
}
},
_updateContent: function() {
if (this.state != ModalDialog.State.OPENING && if (this.state != ModalDialog.State.OPENING &&
this.state != ModalDialog.State.OPENED) this.state != ModalDialog.State.OPENED)
return; return;
@ -314,6 +352,17 @@ const EndSessionDialog = new Lang.Class({
let subject = dialogContent.subject; let subject = dialogContent.subject;
let description; let description;
if (this._user.is_loaded && !dialogContent.iconName) {
let iconFile = this._user.get_icon_file();
if (GLib.file_test(iconFile, GLib.FileTest.EXISTS))
this._setIconFromFile(iconFile, dialogContent.iconStyleClass);
else
this._setIconFromName('avatar-default', dialogContent.iconStyleClass);
} else if (dialogContent.iconName) {
this._setIconFromName(dialogContent.iconName,
dialogContent.iconStyleClass);
}
if (this._inhibitors.length > 0) { if (this._inhibitors.length > 0) {
this._stopTimer(); this._stopTimer();
description = dialogContent.inhibitedDescription; description = dialogContent.inhibitedDescription;
@ -346,27 +395,6 @@ const EndSessionDialog = new Lang.Class({
_setLabelText(this._descriptionLabel, description); _setLabelText(this._descriptionLabel, description);
}, },
_updateContent: function() {
if (this.state != ModalDialog.State.OPENING &&
this.state != ModalDialog.State.OPENED)
return;
let dialogContent = DialogContent[this._type];
if (dialogContent.iconName) {
this._iconBin.child = new St.Icon({ icon_name: dialogContent.iconName,
icon_size: _DIALOG_ICON_SIZE,
style_class: dialogContent.iconStyleClass });
} else {
let avatarWidget = new UserMenu.UserAvatarWidget(this._user,
{ iconSize: _DIALOG_ICON_SIZE,
styleClass: dialogContent.iconStyleClass });
this._iconBin.child = avatarWidget.actor;
avatarWidget.update();
}
this._updateDescription();
},
_updateButtons: function() { _updateButtons: function() {
let dialogContent = DialogContent[this._type]; let dialogContent = DialogContent[this._type];
let buttons = [{ action: Lang.bind(this, this.cancel), let buttons = [{ action: Lang.bind(this, this.cancel),
@ -413,7 +441,7 @@ const EndSessionDialog = new Lang.Class({
{ _secondsLeft: 0, { _secondsLeft: 0,
time: this._secondsLeft, time: this._secondsLeft,
transition: 'linear', transition: 'linear',
onUpdate: Lang.bind(this, this._updateDescription), onUpdate: Lang.bind(this, this._updateContent),
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, function() {
let dialogContent = DialogContent[this._type]; let dialogContent = DialogContent[this._type];
let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1]; let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1];

View File

@ -90,7 +90,7 @@ function init() {
} }
// OK, now things are initialized enough that we can import shell JS // OK, now things are initialized enough that we can import shell JS
const Format = imports.format; const Format = imports.misc.format;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
Tweener.init(); Tweener.init();

View File

@ -1,270 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Soup = imports.gi.Soup;
const St = imports.gi.St;
const Shell = imports.gi.Shell;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
const ExtensionSystem = imports.ui.extensionSystem;
const FileUtils = imports.misc.fileUtils;
const ModalDialog = imports.ui.modalDialog;
const _signals = ExtensionSystem._signals;
const REPOSITORY_URL_BASE = 'https://extensions.gnome.org';
const REPOSITORY_URL_DOWNLOAD = REPOSITORY_URL_BASE + '/download-extension/%s.shell-extension.zip';
const REPOSITORY_URL_INFO = REPOSITORY_URL_BASE + '/extension-info/';
const REPOSITORY_URL_UPDATE = REPOSITORY_URL_BASE + '/update-info/';
let _httpSession;
function installExtension(uuid, invocation) {
let params = { uuid: uuid,
shell_version: Config.PACKAGE_VERSION };
let message = Soup.form_request_new_from_hash('GET', REPOSITORY_URL_INFO, params);
_httpSession.queue_message(message, function(session, message) {
if (message.status_code != Soup.KnownStatusCode.OK) {
ExtensionSystem.logExtensionError(uuid, 'downloading info: ' + message.status_code);
invocation.return_dbus_error('org.gnome.Shell.DownloadInfoError', message.status_code.toString());
return;
}
let info;
try {
info = JSON.parse(message.response_body.data);
} catch (e) {
ExtensionSystem.logExtensionError(uuid, 'parsing info: ' + e);
invocation.return_dbus_error('org.gnome.Shell.ParseInfoError', e.toString());
return;
}
let dialog = new InstallExtensionDialog(uuid, info, invocation);
dialog.open(global.get_current_time());
});
}
function uninstallExtension(uuid) {
let extension = ExtensionUtils.extensions[uuid];
if (!extension)
return false;
// Don't try to uninstall system extensions
if (extension.type != ExtensionUtils.ExtensionType.PER_USER)
return false;
if (!ExtensionSystem.unloadExtension(extension))
return false;
FileUtils.recursivelyDeleteDir(extension.dir, true);
return true;
}
function gotExtensionZipFile(session, message, uuid, dir, callback, errback) {
if (message.status_code != Soup.KnownStatusCode.OK) {
errback('DownloadExtensionError', message.status_code);
return;
}
try {
if (!dir.query_exists(null))
dir.make_directory_with_parents(null);
} catch (e) {
errback('CreateExtensionDirectoryError', e);
return;
}
let [file, stream] = Gio.File.new_tmp('XXXXXX.shell-extension.zip');
let contents = message.response_body.flatten().get_as_bytes();
stream.output_stream.write_bytes(contents, null);
stream.close(null);
let [success, pid] = GLib.spawn_async(null,
['unzip', '-uod', dir.get_path(), '--', file.get_path()],
null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
null);
if (!success) {
errback('ExtractExtensionError');
return;
}
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, function(pid, status) {
GLib.spawn_close_pid(pid);
if (status != 0)
errback('ExtractExtensionError');
else
callback();
});
}
function updateExtension(uuid) {
// This gets a bit tricky. We want the update to be seamless -
// if we have any error during downloading or extracting, we
// want to not unload the current version.
let oldExtensionTmpDir = GLib.Dir.make_tmp('XXXXXX-shell-extension');
let newExtensionTmpDir = GLib.Dir.make_tmp('XXXXXX-shell-extension');
let params = { shell_version: Config.PACKAGE_VERSION };
let url = REPOSITORY_URL_DOWNLOAD.format(uuid);
let message = Soup.form_request_new_from_hash('GET', url, params);
_httpSession.queue_message(message, Lang.bind(this, function(session, message) {
gotExtensionZipFile(session, message, uuid, newExtensionTmpDir, function() {
let oldExtension = ExtensionUtils.extensions[uuid];
let extensionDir = oldExtension.dir;
if (!ExtensionSystem.unloadExtension(oldExtension))
return;
FileUtils.recursivelyMoveDir(extensionDir, oldExtensionTmpDir);
FileUtils.recursivelyMoveDir(newExtensionTmpDir, extensionDir);
let extension = ExtensionUtils.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER);
try {
ExtensionSystem.loadExtension(extension);
} catch(e) {
ExtensionSystem.unloadExtension(extension);
logError(e, 'Error loading extension %s'.format(uuid));
FileUtils.recursivelyDeleteDir(extensionDir, false);
FileUtils.recursivelyMoveDir(oldExtensionTmpDir, extensionDir);
// Restore what was there before. We can't do much if we
// fail here.
ExtensionSystem.loadExtension(oldExtension);
return;
}
FileUtils.recursivelyDeleteDir(oldExtensionTmpDir, true);
}, function(code, message) {
log('Error while updating extension %s: %s (%s)'.format(uuid, code, message ? message : ''));
});
}));
}
function checkForUpdates() {
let metadatas = {};
for (let uuid in ExtensionUtils.extensions) {
metadatas[uuid] = ExtensionUtils.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, function(session, message) {
if (message.status_code != Soup.KnownStatusCode.OK)
return;
let operations = JSON.parse(message.response_body.data);
for (let uuid in operations) {
let operation = operations[uuid];
if (operation == 'blacklist')
uninstallExtension(uuid);
else if (operation == 'upgrade' || operation == 'downgrade')
updateExtension(uuid);
}
});
}
const InstallExtensionDialog = new Lang.Class({
Name: 'InstallExtensionDialog',
Extends: ModalDialog.ModalDialog,
_init: function(uuid, info, invocation) {
this.parent({ styleClass: 'extension-dialog' });
this._uuid = uuid;
this._info = info;
this._invocation = invocation;
this.setButtons([{ label: _("Cancel"),
action: Lang.bind(this, this._onCancelButtonPressed),
key: Clutter.Escape
},
{ label: _("Install"),
action: Lang.bind(this, this._onInstallButtonPressed),
default: true
}]);
let message = _("Download and install '%s' from extensions.gnome.org?").format(info.name);
let box = new St.BoxLayout();
this.contentLayout.add(box);
let gicon = new Gio.FileIcon({ file: Gio.File.new_for_uri(REPOSITORY_URL_BASE + info.icon) })
let icon = new St.Icon({ gicon: gicon });
box.add(icon);
let label = new St.Label({ text: message });
box.add(label);
},
_onCancelButtonPressed: function(button, event) {
this.close(global.get_current_time());
this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled']));
},
_onInstallButtonPressed: function(button, event) {
let params = { shell_version: Config.PACKAGE_VERSION };
let url = REPOSITORY_URL_DOWNLOAD.format(this._uuid);
let message = Soup.form_request_new_from_hash('GET', url, params);
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) {
invocation.return_dbus_error('org.gnome.Shell.' + code, message ? message.toString() : '');
}
function callback() {
// Add extension to 'enabled-extensions' for the user, always...
let enabledExtensions = global.settings.get_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY);
if (enabledExtensions.indexOf(uuid) == -1) {
enabledExtensions.push(uuid);
global.settings.set_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY, enabledExtensions);
}
let extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
try {
ExtensionSystem.loadExtension(extension);
} catch(e) {
uninstallExtension(uuid);
errback('LoadExtensionError', e);
return;
}
invocation.return_value(GLib.Variant.new('(s)', 'successful'));
}
_httpSession.queue_message(message, Lang.bind(this, function(session, message) {
gotExtensionZipFile(session, message, uuid, dir, callback, errback);
}));
this.close(global.get_current_time());
}
});
function init() {
_httpSession = new Soup.SessionAsync({ ssl_use_system_ca_file: true });
// See: https://bugzilla.gnome.org/show_bug.cgi?id=655189 for context.
// _httpSession.add_feature(new Soup.ProxyResolverDefault());
Soup.Session.prototype.add_feature.call(_httpSession, new Soup.ProxyResolverDefault());
}

View File

@ -3,12 +3,19 @@
const Lang = imports.lang; const Lang = imports.lang;
const Signals = imports.signals; const Signals = imports.signals;
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const St = imports.gi.St; const St = imports.gi.St;
const Shell = imports.gi.Shell;
const Soup = imports.gi.Soup;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Main = imports.ui.main; const FileUtils = imports.misc.fileUtils;
const ModalDialog = imports.ui.modalDialog;
const API_VERSION = 1;
const ExtensionState = { const ExtensionState = {
ENABLED: 1, ENABLED: 1,
@ -23,6 +30,29 @@ const ExtensionState = {
UNINSTALLED: 99 UNINSTALLED: 99
}; };
const REPOSITORY_URL_BASE = 'https://extensions.gnome.org';
const REPOSITORY_URL_DOWNLOAD = REPOSITORY_URL_BASE + '/download-extension/%s.shell-extension.zip';
const REPOSITORY_URL_INFO = REPOSITORY_URL_BASE + '/extension-info/';
const _httpSession = new Soup.SessionAsync();
// The unfortunate state of gjs, gobject-introspection and libsoup
// means that I have to do a hack to add a feature.
// See: https://bugzilla.gnome.org/show_bug.cgi?id=655189 for context.
if (Soup.Session.prototype.add_feature != null)
Soup.Session.prototype.add_feature.call(_httpSession, new Soup.ProxyResolverDefault());
function _getCertFile() {
let localCert = GLib.build_filenamev([global.userdatadir, 'extensions.gnome.org.crt']);
if (GLib.file_test(localCert, GLib.FileTest.EXISTS))
return localCert;
else
return Config.SHELL_SYSTEM_CA_FILE;
}
_httpSession.ssl_ca_file = _getCertFile();
// Arrays of uuids // Arrays of uuids
var enabledExtensions; var enabledExtensions;
// Contains the order that extensions were enabled in. // Contains the order that extensions were enabled in.
@ -39,8 +69,89 @@ const disconnect = Lang.bind(_signals, _signals.disconnect);
const ENABLED_EXTENSIONS_KEY = 'enabled-extensions'; const ENABLED_EXTENSIONS_KEY = 'enabled-extensions';
var initted = false; function installExtensionFromUUID(uuid, version_tag) {
var enabled; let params = { uuid: uuid,
version_tag: version_tag,
shell_version: Config.PACKAGE_VERSION,
api_version: API_VERSION.toString() };
let message = Soup.form_request_new_from_hash('GET', REPOSITORY_URL_INFO, params);
_httpSession.queue_message(message,
function(session, message) {
let info = JSON.parse(message.response_body.data);
let dialog = new InstallExtensionDialog(uuid, version_tag, info.name);
dialog.open(global.get_current_time());
});
}
function uninstallExtensionFromUUID(uuid) {
let extension = ExtensionUtils.extensions[uuid];
if (!extension)
return false;
// 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(uuid);
// Don't try to uninstall system extensions
if (extension.type != ExtensionUtils.ExtensionType.PER_USER)
return false;
extension.state = ExtensionState.UNINSTALLED;
_signals.emit('extension-state-changed', extension);
delete ExtensionUtils.extensions[uuid];
FileUtils.recursivelyDeleteDir(Gio.file_new_for_path(extension.path));
return true;
}
function gotExtensionZipFile(session, message, uuid) {
if (message.status_code != Soup.KnownStatusCode.OK) {
logExtensionError(uuid, 'downloading extension: ' + message.status_code);
return;
}
// FIXME: use a GFile mkstemp-type method once one exists
let fd, tmpzip;
try {
[fd, tmpzip] = GLib.file_open_tmp('XXXXXX.shell-extension.zip');
} catch (e) {
logExtensionError(uuid, 'tempfile: ' + e.toString());
return;
}
let stream = new Gio.UnixOutputStream({ fd: fd });
let dir = ExtensionUtils.userExtensionsDir.get_child(uuid);
Shell.write_soup_message_to_stream(stream, message);
stream.close(null);
let [success, pid] = GLib.spawn_async(null,
['unzip', '-uod', dir.get_path(), '--', tmpzip],
null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
null);
if (!success) {
logExtensionError(uuid, 'extract: could not extract');
return;
}
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, function(pid, status) {
GLib.spawn_close_pid(pid);
// Add extension to 'enabled-extensions' for the user, always...
let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
if (enabledExtensions.indexOf(uuid) == -1) {
enabledExtensions.push(uuid);
global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
}
loadExtension(dir, ExtensionUtils.ExtensionType.PER_USER, true);
});
}
function disableExtension(uuid) { function disableExtension(uuid) {
let extension = ExtensionUtils.extensions[uuid]; let extension = ExtensionUtils.extensions[uuid];
@ -67,23 +178,23 @@ function disableExtension(uuid) {
try { try {
ExtensionUtils.extensions[uuid].stateObj.disable(); ExtensionUtils.extensions[uuid].stateObj.disable();
} catch(e) { } catch(e) {
logExtensionError(uuid, e); logExtensionError(uuid, e.toString());
} }
} }
if (extension.stylesheet) { try {
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); extension.stateObj.disable();
theme.unload_stylesheet(extension.stylesheet.get_path()); } catch(e) {
logExtensionError(uuid, e.toString());
return;
} }
extension.stateObj.disable();
for (let i = 0; i < order.length; i++) { for (let i = 0; i < order.length; i++) {
let uuid = order[i]; let uuid = order[i];
try { try {
ExtensionUtils.extensions[uuid].stateObj.enable(); ExtensionUtils.extensions[uuid].stateObj.enable();
} catch(e) { } catch(e) {
logExtensionError(uuid, e); logExtensionError(uuid, e.toString());
} }
} }
@ -106,86 +217,68 @@ function enableExtension(uuid) {
extensionOrder.push(uuid); extensionOrder.push(uuid);
let stylesheetFile = extension.dir.get_child('stylesheet.css'); try {
if (stylesheetFile.query_exists(null)) { extension.stateObj.enable();
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); } catch(e) {
theme.load_stylesheet(stylesheetFile.get_path()); logExtensionError(uuid, e.toString());
extension.stylesheet = stylesheetFile; return;
} }
extension.stateObj.enable();
extension.state = ExtensionState.ENABLED; extension.state = ExtensionState.ENABLED;
_signals.emit('extension-state-changed', extension); _signals.emit('extension-state-changed', extension);
} }
function logExtensionError(uuid, error) { function logExtensionError(uuid, message, state) {
let extension = ExtensionUtils.extensions[uuid]; let extension = ExtensionUtils.extensions[uuid];
if (!extension) if (!extension)
return; return;
let message = '' + error;
if (error.state)
extension.state = error.state;
else
extension.state = ExtensionState.ERROR;
if (!extension.errors) if (!extension.errors)
extension.errors = []; extension.errors = [];
log('Extension "%s" had error: %s'.format(uuid, message)); extension.errors.push(message);
global.logError('Extension "%s" had error: %s'.format(uuid, message));
state = state || ExtensionState.ERROR;
_signals.emit('extension-state-changed', { uuid: uuid, _signals.emit('extension-state-changed', { uuid: uuid,
error: message, error: message,
state: extension.state }); state: state });
} }
function loadExtension(extension) { function loadExtension(dir, type, enabled) {
let uuid = dir.get_basename();
let extension;
if (ExtensionUtils.extensions[uuid] != undefined) {
global.logError('Extension "%s" is already loaded'.format(uuid));
return;
}
try {
extension = ExtensionUtils.createExtensionObject(uuid, dir, type);
} catch(e) {
logExtensionError(uuid, e.message);
return;
}
// Default to error, we set success as the last step // Default to error, we set success as the last step
extension.state = ExtensionState.ERROR; extension.state = ExtensionState.ERROR;
if (ExtensionUtils.isOutOfDate(extension)) { if (ExtensionUtils.isOutOfDate(extension)) {
let error = new Error('extension is not compatible with current GNOME Shell and/or GJS version'); logExtensionError(uuid, 'extension is not compatible with current GNOME Shell and/or GJS version', ExtensionState.OUT_OF_DATE);
error.state = ExtensionState.OUT_OF_DATE; extension.state = ExtensionState.OUT_OF_DATE;
throw error; return;
} }
let enabled = enabledExtensions.indexOf(extension.uuid) != -1;
if (enabled) { if (enabled) {
initExtension(extension.uuid); initExtension(uuid);
if (extension.state == ExtensionState.DISABLED) if (extension.state == ExtensionState.DISABLED)
enableExtension(extension.uuid); enableExtension(uuid);
} else { } else {
extension.state = ExtensionState.INITIALIZED; extension.state = ExtensionState.INITIALIZED;
} }
_signals.emit('extension-state-changed', extension); _signals.emit('extension-state-changed', extension);
} global.log('Loaded extension ' + uuid);
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);
extension.state = ExtensionState.UNINSTALLED;
_signals.emit('extension-state-changed', extension);
delete ExtensionUtils.extensions[extension.uuid];
return true;
}
function reloadExtension(oldExtension) {
// Grab the things we'll need to pass to createExtensionObject
// to reload it.
let { uuid: uuid, dir: dir, type: type } = oldExtension;
// Then unload the old extension.
unloadExtension(oldExtension);
// Now, recreate the extension and load it.
let newExtension = ExtensionUtils.createExtensionObject(uuid, dir, type);
loadExtension(newExtension);
} }
function initExtension(uuid) { function initExtension(uuid) {
@ -196,43 +289,76 @@ function initExtension(uuid) {
throw new Error("Extension was not properly created. Call loadExtension first"); throw new Error("Extension was not properly created. Call loadExtension first");
let extensionJs = dir.get_child('extension.js'); let extensionJs = dir.get_child('extension.js');
if (!extensionJs.query_exists(null)) if (!extensionJs.query_exists(null)) {
throw new Error('Missing extension.js'); logExtensionError(uuid, 'Missing extension.js');
return;
}
let stylesheetPath = null;
let themeContext = St.ThemeContext.get_for_stage(global.stage);
let theme = themeContext.get_theme();
let stylesheetFile = dir.get_child('stylesheet.css');
if (stylesheetFile.query_exists(null)) {
try {
theme.load_stylesheet(stylesheetFile.get_path());
} catch (e) {
logExtensionError(uuid, 'Stylesheet parse error: ' + e);
return;
}
}
let extensionModule; let extensionModule;
let extensionState = null; let extensionState = null;
try {
ExtensionUtils.installImporter(extension);
extensionModule = extension.imports.extension;
} catch (e) {
if (stylesheetPath != null)
theme.unload_stylesheet(stylesheetPath);
logExtensionError(uuid, '' + e);
return;
}
ExtensionUtils.installImporter(extension); if (!extensionModule.init) {
extensionModule = extension.imports.extension; logExtensionError(uuid, 'missing \'init\' function');
return;
}
if (extensionModule.init) { try {
extensionState = extensionModule.init(extension); extensionState = extensionModule.init(extension);
} catch (e) {
if (stylesheetPath != null)
theme.unload_stylesheet(stylesheetPath);
logExtensionError(uuid, 'Failed to evaluate init function:' + e);
return;
} }
if (!extensionState) if (!extensionState)
extensionState = extensionModule; extensionState = extensionModule;
extension.stateObj = extensionState; extension.stateObj = extensionState;
if (!extensionState.enable) {
logExtensionError(uuid, 'missing \'enable\' function');
return;
}
if (!extensionState.disable) {
logExtensionError(uuid, 'missing \'disable\' function');
return;
}
extension.state = ExtensionState.DISABLED; extension.state = ExtensionState.DISABLED;
_signals.emit('extension-loaded', uuid); _signals.emit('extension-loaded', uuid);
} }
function onEnabledExtensionsChanged() { function onEnabledExtensionsChanged() {
let newEnabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); let newEnabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
if (!enabled)
return;
// Find and enable all the newly enabled extensions: UUIDs found in the // Find and enable all the newly enabled extensions: UUIDs found in the
// new setting, but not in the old one. // new setting, but not in the old one.
newEnabledExtensions.filter(function(uuid) { newEnabledExtensions.filter(function(uuid) {
return enabledExtensions.indexOf(uuid) == -1; return enabledExtensions.indexOf(uuid) == -1;
}).forEach(function(uuid) { }).forEach(function(uuid) {
try { enableExtension(uuid);
enableExtension(uuid);
} catch(e) {
logExtensionError(uuid, e);
}
}); });
// Find and disable all the newly disabled extensions: UUIDs found in the // Find and disable all the newly disabled extensions: UUIDs found in the
@ -240,67 +366,87 @@ function onEnabledExtensionsChanged() {
enabledExtensions.filter(function(item) { enabledExtensions.filter(function(item) {
return newEnabledExtensions.indexOf(item) == -1; return newEnabledExtensions.indexOf(item) == -1;
}).forEach(function(uuid) { }).forEach(function(uuid) {
try { disableExtension(uuid);
disableExtension(uuid);
} catch(e) {
logExtensionError(uuid, e);
}
}); });
enabledExtensions = newEnabledExtensions; enabledExtensions = newEnabledExtensions;
} }
function _loadExtensions() { function init() {
ExtensionUtils.init();
global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged); global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged);
enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
}
let finder = new ExtensionUtils.ExtensionFinder(); function loadExtensions() {
finder.connect('extension-found', function(signals, extension) { ExtensionUtils.scanExtensions(function(uuid, dir, type) {
try { let enabled = enabledExtensions.indexOf(uuid) != -1;
loadExtension(extension); loadExtension(dir, type, enabled);
} catch(e) {
logExtensionError(extension.uuid, e);
}
}); });
finder.scanExtensions();
} }
function enableAllExtensions() { const InstallExtensionDialog = new Lang.Class({
if (enabled) Name: 'InstallExtensionDialog',
return; Extends: ModalDialog.ModalDialog,
if (!initted) { _init: function(uuid, version_tag, name) {
_loadExtensions(); this.parent({ styleClass: 'extension-dialog' });
initted = true;
} else { this._uuid = uuid;
enabledExtensions.forEach(function(uuid) { this._version_tag = version_tag;
enableExtension(uuid); this._name = name;
});
this.setButtons([{ label: _("Cancel"),
action: Lang.bind(this, this._onCancelButtonPressed),
key: Clutter.Escape
},
{ label: _("Install"),
action: Lang.bind(this, this._onInstallButtonPressed)
}]);
let message = _("Download and install '%s' from extensions.gnome.org?").format(name);
this._descriptionLabel = new St.Label({ text: message });
this.contentLayout.add(this._descriptionLabel,
{ y_fill: true,
y_align: St.Align.START });
},
_onCancelButtonPressed: function(button, event) {
this.close(global.get_current_time());
// Even though the extension is already "uninstalled", send through
// a state-changed signal for any users who want to know if the install
// went through correctly -- using proper async DBus would block more
// traditional clients like the plugin
let meta = { uuid: this._uuid,
state: ExtensionState.UNINSTALLED,
error: '' };
_signals.emit('extension-state-changed', meta);
},
_onInstallButtonPressed: function(button, event) {
let state = { uuid: this._uuid,
state: ExtensionState.DOWNLOADING,
error: '' };
_signals.emit('extension-state-changed', state);
let params = { version_tag: this._version_tag,
shell_version: Config.PACKAGE_VERSION,
api_version: API_VERSION.toString() };
let url = REPOSITORY_URL_DOWNLOAD.format(this._uuid);
let message = Soup.form_request_new_from_hash('GET', url, params);
_httpSession.queue_message(message,
Lang.bind(this, function(session, message) {
gotExtensionZipFile(session, message, this._uuid);
}));
this.close(global.get_current_time());
} }
enabled = true; });
}
function disableAllExtensions() {
if (!enabled)
return;
if (initted) {
enabledExtensions.forEach(function(uuid) {
disableExtension(uuid);
});
}
enabled = false;
}
function _sessionUpdated() {
if (Main.sessionMode.allowExtensions)
enableAllExtensions();
else
disableAllExtensions();
}
function init() {
Main.sessionMode.connect('updated', _sessionUpdated);
_sessionUpdated();
}

View File

@ -1,358 +0,0 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Main = imports.ui.main;
const Params = imports.misc.params;
function _navigateActor(actor) {
if (!actor)
return;
let needsGrab = true;
if (actor instanceof St.Widget)
needsGrab = !actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
if (needsGrab)
actor.grab_key_focus();
}
// GrabHelper:
// @owner: the actor that owns the GrabHelper
//
// Creates a new GrabHelper object, for dealing with keyboard and pointer grabs
// associated with a set of actors.
//
// Note that the grab can be automatically dropped at any time by the user, and
// your code just needs to deal with it; you shouldn't adjust behavior directly
// after you call ungrab(), but instead pass an 'onUngrab' callback when you
// call grab().
const GrabHelper = new Lang.Class({
Name: 'GrabHelper',
_init: function(owner) {
this._owner = owner;
this._grabStack = [];
this._actors = [];
this._capturedEventId = 0;
this._eventId = 0;
this._keyFocusNotifyId = 0;
this._focusWindowChangedId = 0;
this._ignoreRelease = false;
this._modalCount = 0;
this._grabFocusCount = 0;
},
// addActor:
// @actor: an actor
//
// Adds @actor to the set of actors that are allowed to process events
// during a grab.
addActor: function(actor) {
actor.__grabHelperDestroyId = actor.connect('destroy', Lang.bind(this, function() { this.removeActor(actor); }));
this._actors.push(actor);
},
// removeActor:
// @actor: an actor
//
// Removes @actor from the set of actors that are allowed to
// process events during a grab.
removeActor: function(actor) {
let index = this._actors.indexOf(actor);
if (index != -1)
this._actors.splice(index, 1);
if (actor.__grabHelperDestroyId) {
actor.disconnect(actor.__grabHelperDestroyId);
delete actor.__grabHelperDestroyId;
}
},
_isWithinGrabbedActor: function(actor) {
while (actor) {
if (this._actors.indexOf(actor) != -1)
return true;
actor = actor.get_parent();
}
return false;
},
get currentGrab() {
return this._grabStack[this._grabStack.length - 1] || {};
},
_findStackIndex: function(actor) {
if (!actor)
return -1;
for (let i = 0; i < this._grabStack.length; i++) {
if (this._grabStack[i].actor === actor)
return i;
}
return -1;
},
isActorGrabbed: function(actor) {
return this._findStackIndex(actor) >= 0;
},
// grab:
// @params: A bunch of parameters, see below
//
// Grabs the mouse and keyboard, according to the GrabHelper's
// parameters. If @newFocus is not %null, then the keyboard focus
// is moved to the first #StWidget:can-focus widget inside it.
//
// The grab will automatically be dropped if:
// - The user clicks outside the grabbed actors
// - The user types Escape
// - The keyboard focus is moved outside the grabbed actors
// - A window is focused
//
// If @params.actor is not null, then it will be focused as the
// new actor. If you attempt to grab an already focused actor, the
// request to be focused will be ignored. The actor will not be
// added to the grab stack, so do not call a paired ungrab().
//
// If @params contains { modal: true }, then grab() will push a modal
// on the owner of the GrabHelper. As long as there is at least one
// { modal: true } actor on the grab stack, the grab will be kept.
// When the last { modal: true } actor is ungrabbed, then the modal
// will be dropped. A modal grab can fail if there is already a grab
// in effect from aother application; in this case the function returns
// false and nothing happens. Non-modal grabs can never fail.
//
// If @params contains { grabFocus: true }, then if you call grab()
// while the shell is outside the overview, it will set the stage
// input mode to %Shell.StageInputMode.FOCUSED, and ungrab() will
// revert it back, and re-focus the previously-focused window (if
// another window hasn't been explicitly focused before then).
grab: function(params) {
params = Params.parse(params, { actor: null,
modal: false,
grabFocus: false,
onUngrab: null });
let focus = global.stage.key_focus;
let hadFocus = focus && this._isWithinGrabbedActor(focus);
let newFocus = params.actor;
if (this.isActorGrabbed(params.actor))
return true;
params.savedFocus = focus;
if (params.modal && !this._takeModalGrab())
return false;
if (params.grabFocus && !this._takeFocusGrab(hadFocus))
return false;
if (hadFocus || params.grabFocus)
_navigateActor(newFocus);
this._grabStack.push(params);
return true;
},
_takeModalGrab: function() {
let firstGrab = (this._modalCount == 0);
if (firstGrab) {
if (!Main.pushModal(this._owner))
return false;
this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
this._eventId = global.stage.connect('event', Lang.bind(this, this._onEvent));
}
this._modalCount++;
return true;
},
_releaseModalGrab: function() {
this._modalCount--;
if (this._modalCount > 0)
return;
if (this._capturedEventId > 0) {
global.stage.disconnect(this._capturedEventId);
this._capturedEventId = 0;
}
if (this._eventId > 0) {
global.stage.disconnect(this._eventId);
this._eventId = 0;
}
Main.popModal(this._owner);
global.sync_pointer();
},
_takeFocusGrab: function(hadFocus) {
let firstGrab = (this._grabFocusCount == 0);
if (firstGrab) {
let metaDisplay = global.screen.get_display();
this._grabbedFromKeynav = hadFocus;
this._preGrabInputMode = global.stage_input_mode;
this._prevFocusedWindow = metaDisplay.focus_window;
if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE ||
this._preGrabInputMode == Shell.StageInputMode.NORMAL) {
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
}
this._keyFocusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged));
this._focusWindowChangedId = metaDisplay.connect('notify::focus-window', Lang.bind(this, this._focusWindowChanged));
}
this._grabFocusCount++;
return true;
},
_releaseFocusGrab: function() {
this._grabFocusCount--;
if (this._grabFocusCount > 0)
return;
if (this._keyFocusNotifyId > 0) {
global.stage.disconnect(this._keyFocusNotifyId);
this._keyFocusNotifyId = 0;
}
if (!this._focusWindowChanged > 0) {
let metaDisplay = global.screen.get_display();
metaDisplay.disconnect(this._focusWindowChangedId);
this._focusWindowChangedId = 0;
}
let prePopInputMode = global.stage_input_mode;
if (this._grabbedFromKeynav) {
if (this._preGrabInputMode == Shell.StageInputMode.FOCUSED &&
prePopInputMode != Shell.StageInputMode.FULLSCREEN)
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
}
if (this._prevFocusedWindow) {
let metaDisplay = global.screen.get_display();
if (!metaDisplay.focus_window) {
metaDisplay.set_input_focus_window(this._prevFocusedWindow,
false, global.get_current_time());
}
}
},
// ignoreRelease:
//
// Make sure that the next button release event evaluated by the
// capture event handler returns false. This is designed for things
// like the ComboBoxMenu that go away on press, but need to eat
// the next release event.
ignoreRelease: function() {
this._ignoreRelease = true;
},
// ungrab:
// @params: The parameters for the grab; see below.
//
// Pops an actor from the grab stack, potentially dropping the grab.
//
// If the actor that was popped from the grab stack was not the actor
// That was passed in, this call is ignored.
ungrab: function(params) {
params = Params.parse(params, { actor: this.currentGrab.actor });
let grabStackIndex = this._findStackIndex(params.actor);
if (grabStackIndex < 0)
return;
let focus = global.stage.key_focus;
let hadFocus = focus && this._isWithinGrabbedActor(focus);
let poppedGrabs = this._grabStack.slice(grabStackIndex);
// "Pop" all newly ungrabbed actors off the grab stack
// by truncating the array.
this._grabStack.length = grabStackIndex;
for (let i = poppedGrabs.length - 1; i >= 0; i--) {
let poppedGrab = poppedGrabs[i];
if (poppedGrab.onUngrab)
poppedGrab.onUngrab();
if (poppedGrab.modal)
this._releaseModalGrab();
if (poppedGrab.grabFocus)
this._releaseFocusGrab();
}
if (hadFocus) {
let poppedGrab = poppedGrabs[0];
_navigateActor(poppedGrab.savedFocus);
}
},
_onCapturedEvent: function(actor, event) {
let type = event.type();
let press = type == Clutter.EventType.BUTTON_PRESS;
let release = type == Clutter.EventType.BUTTON_RELEASE;
let button = press || release;
if (release && this._ignoreRelease) {
this._ignoreRelease = false;
return false;
}
if (!button && this._modalCount == 0)
return false;
if (this._isWithinGrabbedActor(event.get_source()))
return false;
if (Main.keyboard.shouldTakeEvent(event))
return false;
if (button) {
// If we have a press event, ignore the next event,
// which should be a release event.
if (press)
this._ignoreRelease = true;
this.ungrab({ actor: this._grabStack[0].actor });
}
return this._modalCount > 0;
},
// We catch 'event' rather than 'key-press-event' so that we get
// a chance to run before the overview's own Escape check
_onEvent: function(actor, event) {
if (event.type() == Clutter.EventType.KEY_PRESS &&
event.get_key_symbol() == Clutter.KEY_Escape) {
this.ungrab();
return true;
}
return false;
},
_onKeyFocusChanged: function() {
let focus = global.stage.key_focus;
if (!focus || !this._isWithinGrabbedActor(focus))
this.ungrab();
},
_focusWindowChanged: function() {
let metaDisplay = global.screen.get_display();
if (metaDisplay.focus_window != null)
this.ungrab();
}
});

View File

@ -1,228 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const IBus = imports.gi.IBus;
const Lang = imports.lang;
const St = imports.gi.St;
const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const MAX_CANDIDATES_PER_PAGE = 16;
const CandidateArea = new Lang.Class({
Name: 'CandidateArea',
Extends: PopupMenu.PopupBaseMenuItem,
_init: function() {
this.parent({ reactive: false });
// St.Table exhibits some sizing problems so let's go with a
// clutter layout manager for now.
this._table = new Clutter.Actor();
this.addActor(this._table);
this._tableLayout = new Clutter.TableLayout();
this._table.set_layout_manager(this._tableLayout);
this._indexLabels = [];
this._candidateLabels = [];
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
this._indexLabels.push(new St.Label({ style_class: 'candidate-index' }));
this._candidateLabels.push(new St.Label({ style_class: 'candidate-label' }));
}
this._orientation = -1;
this._cursorPosition = 0;
},
_setOrientation: function(orientation) {
if (this._orientation == orientation)
return;
this._orientation = orientation;
this._table.remove_all_children();
if (this._orientation == IBus.Orientation.HORIZONTAL)
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
this._tableLayout.pack(this._indexLabels[i], i*2, 0);
this._tableLayout.pack(this._candidateLabels[i], i*2 + 1, 0);
}
else // VERTICAL || SYSTEM
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
this._tableLayout.pack(this._indexLabels[i], 0, i);
this._tableLayout.pack(this._candidateLabels[i], 1, i);
}
},
setCandidates: function(indexes, candidates, orientation, cursorPosition, cursorVisible) {
this._setOrientation(orientation);
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
let visible = i < candidates.length;
this._indexLabels[i].visible = visible;
this._candidateLabels[i].visible = visible;
if (!visible)
continue;
this._indexLabels[i].text = ((indexes && indexes[i]) ? indexes[i] : '%x.'.format(i + 1));
this._candidateLabels[i].text = candidates[i];
}
this._candidateLabels[this._cursorPosition].remove_style_pseudo_class('selected');
this._cursorPosition = cursorPosition;
if (cursorVisible)
this._candidateLabels[cursorPosition].add_style_pseudo_class('selected');
},
});
const CandidatePopup = new Lang.Class({
Name: 'CandidatePopup',
Extends: PopupMenu.PopupMenu,
_init: function() {
this._cursor = new St.Bin({ opacity: 0 });
Main.uiGroup.add_actor(this._cursor);
this.parent(this._cursor, 0, St.Side.TOP);
this.actor.hide();
Main.uiGroup.add_actor(this.actor);
this._preeditTextItem = new PopupMenu.PopupMenuItem('', { reactive: false });
this._preeditTextItem.actor.hide();
this.addMenuItem(this._preeditTextItem);
this._auxTextItem = new PopupMenu.PopupMenuItem('', { reactive: false });
this._auxTextItem.actor.hide();
this.addMenuItem(this._auxTextItem);
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._lookupTableItem = new CandidateArea();
this._lookupTableItem.actor.hide();
this.addMenuItem(this._lookupTableItem);
this._panelService = null;
},
setPanelService: function(panelService) {
this._panelService = panelService;
if (!panelService)
return;
panelService.connect('set-cursor-location',
Lang.bind(this, function(ps, x, y, w, h) {
this._cursor.set_position(x, y);
this._cursor.set_size(w, h);
}));
panelService.connect('update-preedit-text',
Lang.bind(this, function(ps, text, cursorPosition, visible) {
if (visible)
this._preeditTextItem.actor.show();
else
this._preeditTextItem.actor.hide();
this._updateVisibility();
this._preeditTextItem.actor.label_actor.text = text.get_text();
let attrs = text.get_attributes();
if (attrs)
this._setTextAttributes(this._preeditTextItem.actor.label_actor.clutter_text,
attrs);
}));
panelService.connect('show-preedit-text',
Lang.bind(this, function(ps) {
this._preeditTextItem.actor.show();
this._updateVisibility();
}));
panelService.connect('hide-preedit-text',
Lang.bind(this, function(ps) {
this._preeditTextItem.actor.hide();
this._updateVisibility();
}));
panelService.connect('update-auxiliary-text',
Lang.bind(this, function(ps, text, visible) {
if (visible)
this._auxTextItem.actor.show();
else
this._auxTextItem.actor.hide();
this._updateVisibility();
this._auxTextItem.actor.label_actor.text = text.get_text();
}));
panelService.connect('show-auxiliary-text',
Lang.bind(this, function(ps) {
this._auxTextItem.actor.show();
this._updateVisibility();
}));
panelService.connect('hide-auxiliary-text',
Lang.bind(this, function(ps) {
this._auxTextItem.actor.hide();
this._updateVisibility();
}));
panelService.connect('update-lookup-table',
Lang.bind(this, function(ps, lookupTable, visible) {
if (visible)
this._lookupTableItem.actor.show();
else
this._lookupTableItem.actor.hide();
this._updateVisibility();
let cursorPos = lookupTable.get_cursor_pos();
let pageSize = lookupTable.get_page_size();
let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize));
let startIndex = page * pageSize;
let endIndex = Math.min((page + 1) * pageSize,
lookupTable.get_number_of_candidates());
let indexes = [];
let indexLabel;
for (let i = 0; indexLabel = lookupTable.get_label(i); ++i)
indexes.push(indexLabel.get_text());
let candidates = [];
for (let i = startIndex; i < endIndex; ++i)
candidates.push(lookupTable.get_candidate(i).get_text());
this._lookupTableItem.setCandidates(indexes,
candidates,
lookupTable.get_orientation(),
cursorPos % pageSize,
lookupTable.is_cursor_visible());
}));
panelService.connect('show-lookup-table',
Lang.bind(this, function(ps) {
this._lookupTableItem.actor.show();
this._updateVisibility();
}));
panelService.connect('hide-lookup-table',
Lang.bind(this, function(ps) {
this._lookupTableItem.actor.hide();
this._updateVisibility();
}));
panelService.connect('focus-out',
Lang.bind(this, function(ps) {
this.close(BoxPointer.PopupAnimation.NONE);
}));
},
_updateVisibility: function() {
let isVisible = (this._preeditTextItem.actor.visible ||
this._auxTextItem.actor.visible ||
this._lookupTableItem.actor.visible);
if (isVisible)
this.open(BoxPointer.PopupAnimation.NONE);
else
this.close(BoxPointer.PopupAnimation.NONE);
},
_setTextAttributes: function(clutterText, ibusAttrList) {
let attr;
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

@ -282,10 +282,6 @@ const IconGrid = new Lang.Class({
return this._computeLayout(rowWidth)[0]; return this._computeLayout(rowWidth)[0];
}, },
getRowLimit: function() {
return this._rowLimit;
},
_computeLayout: function (forWidth) { _computeLayout: function (forWidth) {
let nColumns = 0; let nColumns = 0;
let usedWidth = 0; let usedWidth = 0;
@ -309,22 +305,21 @@ const IconGrid = new Lang.Class({
this._grid.queue_relayout(); this._grid.queue_relayout();
}, },
removeAll: function() { removeAll: function () {
this._grid.destroy_all_children(); this._grid.get_children().forEach(Lang.bind(this, function (child) {
child.destroy();
}));
}, },
addItem: function(actor, index) { addItem: function(actor) {
if (index !== undefined) this._grid.add_actor(actor);
this._grid.insert_child_at_index(actor, index);
else
this._grid.add_actor(actor);
}, },
getItemAtIndex: function(index) { getItemAtIndex: function(index) {
return this._grid.get_child_at_index(index); return this._grid.get_children()[index];
}, },
visibleItemsCount: function() { visibleItemsCount: function() {
return this._grid.get_n_children() - this._grid.get_n_skip_paint(); return this._grid.get_children().length - this._grid.get_n_skip_paint();
} }
}); });

View File

@ -93,7 +93,7 @@ const Key = new Lang.Class({
this._getExtendedKeys(); this._getExtendedKeys();
this.actor._extended_keys = this._extended_keyboard; this.actor._extended_keys = this._extended_keyboard;
this._boxPointer.actor.hide(); this._boxPointer.actor.hide();
Main.layoutManager.addChrome(this._boxPointer.actor); Main.layoutManager.addChrome(this._boxPointer.actor, { visibleInFullscreen: true });
} }
}, },
@ -175,7 +175,7 @@ const Key = new Lang.Class({
this.actor.fake_release(); this.actor.fake_release();
this._boxPointer.actor.raise_top(); this._boxPointer.actor.raise_top();
this._boxPointer.setPosition(this.actor, 0.5); this._boxPointer.setPosition(this.actor, 0.5);
this._boxPointer.show(BoxPointer.PopupAnimation.FULL); this._boxPointer.show(true);
this.actor.set_hover(false); this.actor.set_hover(false);
if (!this._grabbed) { if (!this._grabbed) {
Main.pushModal(this.actor); Main.pushModal(this.actor);
@ -186,7 +186,7 @@ const Key = new Lang.Class({
} else { } else {
if (this._grabbed) if (this._grabbed)
this._ungrab(); this._ungrab();
this._boxPointer.hide(BoxPointer.PopupAnimation.FULL); this._boxPointer.hide(true);
} }
} }
}); });
@ -200,10 +200,8 @@ const Keyboard = new Lang.Class({
this._impl.export(Gio.DBus.session, '/org/gnome/Caribou/Keyboard'); this._impl.export(Gio.DBus.session, '/org/gnome/Caribou/Keyboard');
this.actor = null; this.actor = null;
this._focusInTray = false;
this._focusInExtendedKeys = false;
this._timestamp = global.display.get_current_time_roundtrip(); this._timestamp = global.get_current_time();
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._redraw)); Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._redraw));
this._keyboardSettings = new Gio.Settings({ schema: KEYBOARD_SCHEMA }); this._keyboardSettings = new Gio.Settings({ schema: KEYBOARD_SCHEMA });
@ -290,15 +288,7 @@ const Keyboard = new Lang.Class({
// Showing an extended key popup and clicking a key from the extended keys // Showing an extended key popup and clicking a key from the extended keys
// will grab focus, but ignore that // will grab focus, but ignore that
let extendedKeysWereFocused = this._focusInExtendedKeys; if (focus && (focus._extended_keys || (focus._key && focus._key.extended_key)))
this._focusInExtendedKeys = focus && (focus._extended_keys || focus.extended_key);
if (this._focusInExtendedKeys || extendedKeysWereFocused)
return;
// Ignore focus changes caused by message tray showing/hiding
let trayWasFocused = this._focusInTray;
this._focusInTray = (focus && Main.messageTray.actor.contains(focus));
if (this._focusInTray || trayWasFocused)
return; return;
let time = global.get_current_time(); let time = global.get_current_time();
@ -349,13 +339,6 @@ const Keyboard = new Lang.Class({
trayButton.reactive = true; trayButton.reactive = true;
trayButton.remove_style_pseudo_class('grayed'); trayButton.remove_style_pseudo_class('grayed');
})); }));
Main.sessionMode.connect('updated', Lang.bind(this, function() {
trayButton.reactive = !Main.sessionMode.isLocked;
if (Main.sessionMode.isLocked)
trayButton.add_style_pseudo_class('grayed');
else
trayButton.remove_style_pseudo_class('grayed');
}));
return trayButton; return trayButton;
}, },
@ -485,12 +468,6 @@ const Keyboard = new Lang.Class({
} }
}, },
shouldTakeEvent: function(event) {
let actor = event.get_source();
return Main.layoutManager.keyboardBox.contains(actor) ||
actor._extended_keys || actor.extended_key;
},
show: function () { show: function () {
this._redraw(); this._redraw();
@ -517,30 +494,15 @@ const Keyboard = new Lang.Class({
this._moveTemporarily(); this._moveTemporarily();
}, },
// _compareTimestamp:
//
// Compare two timestamps taking into account
// CURRENT_TIME (0)
_compareTimestamp: function(one, two) {
if (one == two)
return 0;
if (one == Clutter.CURRENT_TIME)
return 1;
if (two == Clutter.CURRENT_TIME)
return -1;
return one - two;
},
// D-Bus methods // D-Bus methods
Show: function(timestamp) { Show: function(timestamp) {
if (!this._enableKeyboard) if (!this._enableKeyboard)
return; return;
if (this._compareTimestamp(timestamp, this._timestamp) < 0) if (timestamp - this._timestamp < 0)
return; return;
if (timestamp != Clutter.CURRENT_TIME) this._timestamp = timestamp;
this._timestamp = timestamp;
this.show(); this.show();
}, },
@ -548,11 +510,10 @@ const Keyboard = new Lang.Class({
if (!this._enableKeyboard) if (!this._enableKeyboard)
return; return;
if (this._compareTimestamp(timestamp, this._timestamp) < 0) if (timestamp - this._timestamp < 0)
return; return;
if (timestamp != Clutter.CURRENT_TIME) this._timestamp = timestamp;
this._timestamp = timestamp;
this.hide(); this.hide();
}, },
@ -580,9 +541,16 @@ const KeyboardSource = new Lang.Class({
Extends: MessageTray.Source, Extends: MessageTray.Source,
_init: function(keyboard) { _init: function(keyboard) {
this.parent(_("Keyboard"));
this._keyboard = keyboard; this._keyboard = keyboard;
this.parent(_("Keyboard"), 'input-keyboard-symbolic');
this.keepTrayOnSummaryClick = true; this._setSummaryIcon(this.createNotificationIcon());
},
createNotificationIcon: function() {
return new St.Icon({ icon_name: 'input-keyboard',
icon_type: St.IconType.SYMBOLIC,
icon_size: this.ICON_SIZE });
}, },
handleSummaryClick: function() { handleSummaryClick: function() {

View File

@ -65,8 +65,7 @@ const KeyringDialog = new Lang.Class({
key: Clutter.Escape key: Clutter.Escape
}, },
{ label: '', { label: '',
action: Lang.bind(this, this._onContinueButton), action: Lang.bind(this, this._onContinueButton)
default: true
}] }]
this.setButtons(buttons); this.setButtons(buttons);
@ -84,10 +83,7 @@ const KeyringDialog = new Lang.Class({
if (this.prompt.password_visible) { if (this.prompt.password_visible) {
let label = new St.Label(({ style_class: 'prompt-dialog-password-label' })); let label = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
label.set_text(_("Password:")); label.set_text(_("Password:"));
table.add(label, { row: row, col: 0, table.add(label, { row: row, col: 0, x_expand: false, x_fill: true, x_align: St.Align.START });
x_expand: false, x_fill: true,
x_align: St.Align.START,
y_fill: false, y_align: St.Align.MIDDLE });
this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry', this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: '', text: '',
can_focus: true}); can_focus: true});
@ -103,10 +99,7 @@ const KeyringDialog = new Lang.Class({
if (this.prompt.confirm_visible) { if (this.prompt.confirm_visible) {
var label = new St.Label(({ style_class: 'prompt-dialog-password-label' })); var label = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
label.set_text(_("Type again:")); label.set_text(_("Type again:"));
table.add(label, { row: row, col: 0, table.add(label, { row: row, col: 0, x_expand: false, x_fill: true, x_align: St.Align.START });
x_expand: false, x_fill: true,
x_align: St.Align.START,
y_fill: false, y_align: St.Align.MIDDLE });
this._confirmEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry', this._confirmEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: '', text: '',
can_focus: true}); can_focus: true});
@ -192,36 +185,23 @@ const KeyringDialog = new Lang.Class({
}, },
_onContinueButton: function() { _onContinueButton: function() {
this.prompt.complete(); this.prompt.complete()
}, },
_onCancelButton: function() { _onCancelButton: function() {
this.prompt.cancel(); this.prompt.cancel()
}, },
}); });
const KeyringPrompter = new Lang.Class({ function init() {
Name: 'KeyringPrompter', prompter = new Gcr.SystemPrompter();
prompter.connect('new-prompt', function(prompter) {
let dialog = new KeyringDialog();
return dialog.prompt;
});
_init: function() { let connection = Gio.DBus.session;
this._prompter = new Gcr.SystemPrompter(); prompter.register(connection);
this._prompter.connect('new-prompt', function(prompter) { Gio.bus_own_name_on_connection (connection, 'org.gnome.keyring.SystemPrompter',
let dialog = new KeyringDialog(); Gio.BusNameOwnerFlags.REPLACE, null, null);
return dialog.prompt; }
});
this._dbusId = null;
},
enable: function() {
this._prompter.register(Gio.DBus.session);
this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter',
Gio.BusNameOwnerFlags.REPLACE, null, null);
},
disable: function() {
this._prompter.unregister(false);
Gio.DBus.session.unown_name(this._dbusId);
}
});
const Component = KeyringPrompter;

View File

@ -1,7 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GObject = imports.gi.GObject;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
@ -12,88 +11,12 @@ const St = imports.gi.St;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Main = imports.ui.main; const Main = imports.ui.main;
const Params = imports.misc.params; const Params = imports.misc.params;
const ScreenSaver = imports.misc.screenSaver;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5; const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
const STARTUP_ANIMATION_TIME = 0.2; const STARTUP_ANIMATION_TIME = 0.2;
const KEYBOARD_ANIMATION_TIME = 0.5; const KEYBOARD_ANIMATION_TIME = 0.5;
const PLYMOUTH_TRANSITION_TIME = 1;
const MonitorConstraint = new Lang.Class({
Name: 'MonitorConstraint',
Extends: Clutter.Constraint,
Properties: {'primary': GObject.ParamSpec.boolean('primary',
'Primary', 'Track primary monitor',
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
false),
'index': GObject.ParamSpec.int('index',
'Monitor index', 'Track specific monitor',
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
-1, 64, -1)},
_init: function(props) {
this._primary = false;
this._index = -1;
this.parent(props);
},
get primary() {
return this._primary;
},
set primary(v) {
if (v)
this._index = -1;
this._primary = v;
if (this.actor)
this.actor.queue_relayout();
this.notify('primary');
},
get index() {
return this._index;
},
set index(v) {
this._primary = false;
this._index = v;
if (this.actor)
this.actor.queue_relayout();
this.notify('index');
},
vfunc_set_actor: function(actor) {
if (actor) {
if (!this._monitorsChangedId) {
this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', Lang.bind(this, function() {
this.actor.queue_relayout();
}));
}
} else {
if (this._monitorsChangedId)
Main.layoutManager.disconnect(this._monitorsChangedId);
this._monitorsChangedId = 0;
}
this.parent(actor);
},
vfunc_update_allocation: function(actor, actorBox) {
if (!this._primary && this._index < 0)
return;
let monitor;
if (this._primary) {
monitor = Main.layoutManager.primaryMonitor;
} else {
let index = Math.min(this._index, Main.layoutManager.monitors.length - 1);
monitor = Main.layoutManager.monitors[index];
}
actorBox.init_rect(monitor.x, monitor.y, monitor.width, monitor.height);
}
});
const LayoutManager = new Lang.Class({ const LayoutManager = new Lang.Class({
Name: 'LayoutManager', Name: 'LayoutManager',
@ -104,46 +27,32 @@ const LayoutManager = new Lang.Class({
this.primaryMonitor = null; this.primaryMonitor = null;
this.primaryIndex = -1; this.primaryIndex = -1;
this._hotCorners = []; this._hotCorners = [];
this._rootPixmap = null;
this._leftPanelBarrier = 0; this._leftPanelBarrier = 0;
this._rightPanelBarrier = 0; this._rightPanelBarrier = 0;
this._trayBarrier = 0; this._trayBarrier = 0;
this._chrome = new Chrome(this); this._chrome = new Chrome(this);
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', this.panelBox = new St.BoxLayout({ name: 'panelBox',
vertical: true }); vertical: true });
this.addChrome(this.panelBox, { affectsStruts: true, this.addChrome(this.panelBox, { affectsStruts: true });
trackFullscreen: true });
this.panelBox.connect('allocation-changed', this.panelBox.connect('allocation-changed',
Lang.bind(this, this._updatePanelBarriers)); Lang.bind(this, this._updatePanelBarriers));
this.trayBox = new St.Widget({ name: 'trayBox', this.trayBox = new St.BoxLayout({ name: 'trayBox' });
layout_manager: new Clutter.BinLayout() }); this.addChrome(this.trayBox, { visibleInFullscreen: true });
this.addChrome(this.trayBox);
this.trayBox.connect('allocation-changed', this.trayBox.connect('allocation-changed',
Lang.bind(this, this._updateTrayBarrier)); Lang.bind(this, this._updateTrayBarrier));
this.keyboardBox = new St.BoxLayout({ name: 'keyboardBox', this.keyboardBox = new St.BoxLayout({ name: 'keyboardBox',
reactive: true, reactive: true,
track_hover: true }); track_hover: true });
this.addChrome(this.keyboardBox); this.addChrome(this.keyboardBox, { visibleInFullscreen: true });
this._keyboardHeightNotifyId = 0; this._keyboardHeightNotifyId = 0;
global.screen.connect('monitors-changed', global.screen.connect('monitors-changed',
Lang.bind(this, this._monitorsChanged)); Lang.bind(this, this._monitorsChanged));
this._monitorsChanged(); this._monitorsChanged();
this._chrome.connect('primary-fullscreen-changed', Lang.bind(this, function(chrome, state) {
this.emit('primary-fullscreen-changed', state);
}));
}, },
// This is called by Main after everything else is constructed; // This is called by Main after everything else is constructed;
@ -236,9 +145,6 @@ const LayoutManager = new Lang.Class({
}, },
_updateBoxes: function() { _updateBoxes: function() {
this.screenShieldGroup.set_position(0, 0);
this.screenShieldGroup.set_size(global.screen_width, global.screen_height);
this.panelBox.set_position(this.primaryMonitor.x, this.primaryMonitor.y); this.panelBox.set_position(this.primaryMonitor.x, this.primaryMonitor.y);
this.panelBox.set_size(this.primaryMonitor.width, -1); this.panelBox.set_size(this.primaryMonitor.width, -1);
@ -318,47 +224,33 @@ const LayoutManager = new Lang.Class({
return false; return false;
}, },
get currentMonitor() { get focusIndex() {
let index = global.screen.get_current_monitor(); let focusWindow = global.display.focus_window;
return this.monitors[index];
},
_startupAnimation: function() { if (focusWindow) {
this.panelBox.anchor_y = this.panelBox.height; let wrect = focusWindow.get_outer_rect();
for (let i = 0; i < this.monitors.length; i++) {
let monitor = this.monitors[i];
let plymouthTransitionRunning = false; if (monitor.x <= wrect.x && monitor.y <= wrect.y &&
monitor.x + monitor.width > wrect.x &&
// If we're the greeter, put up the xrootpmap actor monitor.y + monitor.height > wrect.y)
// and fade it out to have a nice transition from plymouth return i;
// to the greeter. Otherwise, we'll just animate the panel,
// as usual.
if (Main.sessionMode.isGreeter) {
this._rootPixmap = global.create_xrootpmap_texture();
if (this._rootPixmap != null) {
Main.uiGroup.add_actor(this._rootPixmap);
Tweener.addTween(this._rootPixmap,
{ opacity: 0,
time: PLYMOUTH_TRANSITION_TIME,
transition: 'linear',
onComplete: this._fadeRootpmapComplete,
onCompleteScope: this });
plymouthTransitionRunning = true;
} }
} }
if (!plymouthTransitionRunning) return this.primaryIndex;
this._fadeRootpmapComplete();
}, },
_fadeRootpmapComplete: function() { get focusMonitor() {
return this.monitors[this.focusIndex];
},
_startupAnimation: function() {
// Don't animate the strut // Don't animate the strut
this._chrome.freezeUpdateRegions(); this._chrome.freezeUpdateRegions();
if (this._rootPixmap != null) { this.panelBox.anchor_y = this.panelBox.height;
this._rootPixmap.destroy();
this._rootPixmap = null;
}
Tweener.addTween(this.panelBox, Tweener.addTween(this.panelBox,
{ anchor_y: 0, { anchor_y: 0,
time: STARTUP_ANIMATION_TIME, time: STARTUP_ANIMATION_TIME,
@ -373,6 +265,7 @@ const LayoutManager = new Lang.Class({
}, },
showKeyboard: function () { showKeyboard: function () {
Main.messageTray.hide();
this.keyboardBox.raise_top(); this.keyboardBox.raise_top();
Tweener.addTween(this.keyboardBox, Tweener.addTween(this.keyboardBox,
{ anchor_y: this.keyboardBox.height, { anchor_y: this.keyboardBox.height,
@ -386,8 +279,6 @@ const LayoutManager = new Lang.Class({
time: KEYBOARD_ANIMATION_TIME, time: KEYBOARD_ANIMATION_TIME,
transition: 'easeOutQuad' transition: 'easeOutQuad'
}); });
this.emit('keyboard-visible-changed', true);
}, },
_showKeyboardComplete: function() { _showKeyboardComplete: function() {
@ -402,6 +293,7 @@ const LayoutManager = new Lang.Class({
}, },
hideKeyboard: function (immediate) { hideKeyboard: function (immediate) {
Main.messageTray.hide();
if (this._keyboardHeightNotifyId) { if (this._keyboardHeightNotifyId) {
this.keyboardBox.disconnect(this._keyboardHeightNotifyId); this.keyboardBox.disconnect(this._keyboardHeightNotifyId);
this._keyboardHeightNotifyId = 0; this._keyboardHeightNotifyId = 0;
@ -418,8 +310,6 @@ const LayoutManager = new Lang.Class({
time: immediate ? 0 : KEYBOARD_ANIMATION_TIME, time: immediate ? 0 : KEYBOARD_ANIMATION_TIME,
transition: 'easeOutQuad' transition: 'easeOutQuad'
}); });
this.emit('keyboard-visible-changed', false);
}, },
_hideKeyboardComplete: function() { _hideKeyboardComplete: function() {
@ -441,10 +331,8 @@ const LayoutManager = new Lang.Class({
// the window manager struts. Changes to @actor's visibility will // the window manager struts. Changes to @actor's visibility will
// NOT affect whether or not the strut is present, however. // NOT affect whether or not the strut is present, however.
// //
// If %trackFullscreen in @params is %true, the actor's visibility // If %visibleInFullscreen in @params is %true, the actor will be
// will be bound to the presence of fullscreen windows on the same // visible even when a fullscreen window should be covering it.
// monitor (it will be hidden whenever a fullscreen window is visible,
// and shown otherwise)
addChrome: function(actor, params) { addChrome: function(actor, params) {
this._chrome.addActor(actor, params); this._chrome.addActor(actor, params);
}, },
@ -458,8 +346,10 @@ const LayoutManager = new Lang.Class({
// struts or input region to cover specific children. // struts or input region to cover specific children.
// //
// @params can have any of the same values as in addChrome(), // @params can have any of the same values as in addChrome(),
// though some possibilities don't make sense. By default, @actor has // though some possibilities don't make sense (eg, trying to have
// the same params as its chrome ancestor. // a %visibleInFullscreen child of a non-%visibleInFullscreen
// parent). By default, @actor has the same params as its chrome
// ancestor.
trackChrome: function(actor, params) { trackChrome: function(actor, params) {
this._chrome.trackActor(actor, params); this._chrome.trackActor(actor, params);
}, },
@ -665,7 +555,7 @@ const HotCorner = new Lang.Class({
// workspace content. // workspace content.
const defaultParams = { const defaultParams = {
trackFullscreen: false, visibleInFullscreen: false,
affectsStruts: false, affectsStruts: false,
affectsInputRegion: true affectsInputRegion: true
}; };
@ -692,13 +582,24 @@ const Chrome = new Lang.Class({
global.screen.connect('notify::n-workspaces', global.screen.connect('notify::n-workspaces',
Lang.bind(this, this._queueUpdateRegions)); Lang.bind(this, this._queueUpdateRegions));
this._screenSaverActive = false;
this._screenSaverProxy = new ScreenSaver.ScreenSaverProxy();
this._screenSaverProxy.connectSignal('ActiveChanged', Lang.bind(this, function(proxy, senderName, [isActive]) {
this._onScreenSaverActiveChanged(isActive);
}));
this._screenSaverProxy.GetActiveRemote(Lang.bind(this, function(result, err) {
if (!err)
this._onScreenSaverActiveChanged(result[0]);
}));
this._relayout(); this._relayout();
}, },
init: function() { init: function() {
Main.overview.connect('showing', Lang.bind(this, this._overviewShowing)); Main.overview.connect('showing',
Main.overview.connect('hidden', Lang.bind(this, this._overviewHidden)); Lang.bind(this, this._overviewShowing));
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); Main.overview.connect('hidden',
Lang.bind(this, this._overviewHidden));
}, },
addActor: function(actor, params) { addActor: function(actor, params) {
@ -784,30 +685,28 @@ const Chrome = new Lang.Class({
_actorReparented: function(actor, oldParent) { _actorReparented: function(actor, oldParent) {
let newParent = actor.get_parent(); let newParent = actor.get_parent();
if (!newParent) { if (!newParent)
this._untrackActor(actor); this._untrackActor(actor);
} else { else
let i = this._findActor(actor);
let actorData = this._trackedActors[i];
actorData.isToplevel = (newParent == Main.uiGroup); actorData.isToplevel = (newParent == Main.uiGroup);
}
}, },
_updateVisibility: function() { _updateVisibility: function() {
for (let i = 0; i < this._trackedActors.length; i++) { for (let i = 0; i < this._trackedActors.length; i++) {
let actorData = this._trackedActors[i], visible; let actorData = this._trackedActors[i], visible;
if (!actorData.trackFullscreen)
continue;
if (!actorData.isToplevel) if (!actorData.isToplevel)
continue; continue;
if (this._inOverview || !Main.sessionMode.hasWindows) if (this._screenSaverActive)
visible = false;
else if (this._inOverview)
visible = true; visible = true;
else if (this.findMonitorForActor(actorData.actor).inFullscreen) else if (!actorData.visibleInFullscreen &&
this.findMonitorForActor(actorData.actor).inFullscreen)
visible = false; visible = false;
else else
visible = true; visible = true;
actorData.actor.visible = visible; Main.uiGroup.set_skip_paint(actorData.actor, !visible);
} }
}, },
@ -823,11 +722,6 @@ const Chrome = new Lang.Class({
this._queueUpdateRegions(); this._queueUpdateRegions();
}, },
_sessionUpdated: function() {
this._updateVisibility();
this._queueUpdateRegions();
},
_relayout: function() { _relayout: function() {
this._monitors = this._layoutManager.monitors; this._monitors = this._layoutManager.monitors;
this._primaryMonitor = this._layoutManager.primaryMonitor; this._primaryMonitor = this._layoutManager.primaryMonitor;
@ -837,6 +731,12 @@ const Chrome = new Lang.Class({
this._queueUpdateRegions(); this._queueUpdateRegions();
}, },
_onScreenSaverActiveChanged: function(screenSaverActive) {
this._screenSaverActive = screenSaverActive;
this._updateVisibility();
this._queueUpdateRegions();
},
_findMonitorForRect: function(x, y, w, h) { _findMonitorForRect: function(x, y, w, h) {
// First look at what monitor the center of the rectangle is at // First look at what monitor the center of the rectangle is at
let cx = x + w/2; let cx = x + w/2;
@ -952,8 +852,6 @@ const Chrome = new Lang.Class({
for (let i = 0; i < this._monitors.length; i++) for (let i = 0; i < this._monitors.length; i++)
wasInFullscreen[i] = this._monitors[i].inFullscreen; wasInFullscreen[i] = this._monitors[i].inFullscreen;
let primaryWasInFullscreen = this._primaryMonitor.inFullscreen;
this._updateFullscreen(); this._updateFullscreen();
let changed = false; let changed = false;
@ -963,15 +861,10 @@ const Chrome = new Lang.Class({
break; break;
} }
} }
if (changed) { if (changed) {
this._updateVisibility(); this._updateVisibility();
this._queueUpdateRegions(); this._queueUpdateRegions();
} }
if (primaryWasInFullscreen != this._primaryMonitor.inFullscreen) {
this.emit('primary-fullscreen-changed', this._primaryMonitor.inFullscreen);
}
}, },
updateRegions: function() { updateRegions: function() {
@ -1086,5 +979,3 @@ const Chrome = new Lang.Class({
return false; return false;
} }
}); });
Signals.addSignalMethods(Chrome.prototype);

View File

@ -8,8 +8,6 @@ const St = imports.gi.St;
const Params = imports.misc.params; const Params = imports.misc.params;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const DEFAULT_FADE_FACTOR = 0.4;
/** /**
* Lightbox: * Lightbox:
* @container: parent Clutter.Container * @container: parent Clutter.Container
@ -17,8 +15,7 @@ const DEFAULT_FADE_FACTOR = 0.4;
* - inhibitEvents: whether to inhibit events for @container * - inhibitEvents: whether to inhibit events for @container
* - width: shade actor width * - width: shade actor width
* - height: shade actor height * - height: shade actor height
* - fadeInTime: seconds used to fade in * - fadeTime: seconds used to fade in/out
* - fadeOutTime: seconds used to fade out
* *
* Lightbox creates a dark translucent "shade" actor to hide the * Lightbox creates a dark translucent "shade" actor to hide the
* contents of @container, and allows you to specify particular actors * contents of @container, and allows you to specify particular actors
@ -41,16 +38,12 @@ const Lightbox = new Lang.Class({
params = Params.parse(params, { inhibitEvents: false, params = Params.parse(params, { inhibitEvents: false,
width: null, width: null,
height: null, height: null,
fadeInTime: null, fadeTime: null
fadeOutTime: null,
fadeFactor: DEFAULT_FADE_FACTOR
}); });
this._container = container; this._container = container;
this._children = container.get_children(); this._children = container.get_children();
this._fadeInTime = params.fadeInTime; this._fadeTime = params.fadeTime;
this._fadeOutTime = params.fadeOutTime;
this._fadeFactor = params.fadeFactor;
this.actor = new St.Bin({ x: 0, this.actor = new St.Bin({ x: 0,
y: 0, y: 0,
style_class: 'lightbox', style_class: 'lightbox',
@ -59,7 +52,6 @@ const Lightbox = new Lang.Class({
container.add_actor(this.actor); container.add_actor(this.actor);
this.actor.raise_top(); this.actor.raise_top();
this.actor.hide(); this.actor.hide();
this.shown = false;
this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
@ -101,30 +93,24 @@ const Lightbox = new Lang.Class({
}, },
show: function() { show: function() {
if (this._fadeInTime) { if (this._fadeTime) {
this.shown = false;
this.actor.opacity = 0; this.actor.opacity = 0;
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 255 * this._fadeFactor, { opacity: 255,
time: this._fadeInTime, time: this._fadeTime,
transition: 'easeOutQuad', transition: 'easeOutQuad'
onComplete: Lang.bind(this, function() {
this.shown = true;
})
}); });
} else { } else {
this.actor.opacity = 255 * this._fadeFactor; this.actor.opacity = 255;
this.shown = true;
} }
this.actor.show(); this.actor.show();
}, },
hide: function() { hide: function() {
this.shown = false; if (this._fadeTime) {
if (this._fadeOutTime) {
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 0, { opacity: 0,
time: this._fadeOutTime, time: this._fadeTime,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, function() {
this.actor.hide(); this.actor.hide();

21
js/ui/link.js Normal file
View File

@ -0,0 +1,21 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Signals = imports.signals;
const St = imports.gi.St;
const Link = new Lang.Class({
Name: 'Link',
_init : function(props) {
let realProps = { reactive: true,
track_hover: true,
style_class: 'shell-link' };
// The user can pass in reactive: false to override the above and get
// a non-reactive link (a link to the current page, perhaps)
Lang.copyProperties(props, realProps);
this.actor = new St.Button(realProps);
}
});
Signals.addSignalMethods(Link.prototype);

View File

@ -12,18 +12,16 @@ const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const System = imports.system;
const History = imports.misc.history; const History = imports.misc.history;
const ExtensionSystem = imports.ui.extensionSystem; const ExtensionSystem = imports.ui.extensionSystem;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Link = imports.ui.link;
const ShellEntry = imports.ui.shellEntry; const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const Main = imports.ui.main; const Main = imports.ui.main;
const JsParse = imports.misc.jsParse; const JsParse = imports.misc.jsParse;
const CHEVRON = '>>> ';
/* Imports...feel free to add here as needed */ /* Imports...feel free to add here as needed */
var commandHeader = 'const Clutter = imports.gi.Clutter; ' + var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
'const GLib = imports.gi.GLib; ' + 'const GLib = imports.gi.GLib; ' +
@ -38,8 +36,9 @@ var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
/* Utility functions...we should probably be able to use these /* Utility functions...we should probably be able to use these
* in the shell core code too. */ * in the shell core code too. */
'const stage = global.stage; ' + 'const stage = global.stage; ' +
'const color = function(pixel) { let c= new Clutter.Color(); c.from_pixel(pixel); return c; }; ' +
/* Special lookingGlass functions */ /* Special lookingGlass functions */
'const it = Main.lookingGlass.getIt(); ' + 'const it = Main.lookingGlass.getIt(); ' +
'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); '; 'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); ';
const HISTORY_KEY = 'looking-glass-history'; const HISTORY_KEY = 'looking-glass-history';
@ -262,8 +261,9 @@ function objectToString(o) {
const ObjLink = new Lang.Class({ const ObjLink = new Lang.Class({
Name: 'ObjLink', Name: 'ObjLink',
Extends: Link.Link,
_init: function(lookingGlass, o, title) { _init: function(o, title) {
let text; let text;
if (title) if (title)
text = title; text = title;
@ -272,30 +272,24 @@ const ObjLink = new Lang.Class({
text = GLib.markup_escape_text(text, -1); text = GLib.markup_escape_text(text, -1);
this._obj = o; this._obj = o;
this.actor = new St.Button({ reactive: true, this.parent({ label: text });
track_hover: true,
style_class: 'shell-link',
label: text });
this.actor.get_child().single_line_mode = true; this.actor.get_child().single_line_mode = true;
this.actor.connect('clicked', Lang.bind(this, this._onClicked)); this.actor.connect('clicked', Lang.bind(this, this._onClicked));
this._lookingGlass = lookingGlass;
}, },
_onClicked: function (link) { _onClicked: function (link) {
this._lookingGlass.inspectObject(this._obj, this.actor); Main.lookingGlass.inspectObject(this._obj, this.actor);
} }
}); });
const Result = new Lang.Class({ const Result = new Lang.Class({
Name: 'Result', Name: 'Result',
_init: function(lookingGlass, command, o, index) { _init : function(command, o, index) {
this.index = index; this.index = index;
this.o = o; this.o = o;
this.actor = new St.BoxLayout({ vertical: true }); this.actor = new St.BoxLayout({ vertical: true });
this._lookingGlass = lookingGlass;
let cmdTxt = new St.Label({ text: command }); let cmdTxt = new St.Label({ text: command });
cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END; cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
@ -305,7 +299,7 @@ const Result = new Lang.Class({
let resultTxt = new St.Label({ text: 'r(' + index + ') = ' }); let resultTxt = new St.Label({ text: 'r(' + index + ') = ' });
resultTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END; resultTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
box.add(resultTxt); box.add(resultTxt);
let objLink = new ObjLink(this._lookingGlass, o); let objLink = new ObjLink(o);
box.add(objLink.actor); box.add(objLink.actor);
let line = new Clutter.Rectangle({ name: 'Separator' }); let line = new Clutter.Rectangle({ name: 'Separator' });
let padBin = new St.Bin({ name: 'Separator', x_fill: true, y_fill: true }); let padBin = new St.Bin({ name: 'Separator', x_fill: true, y_fill: true });
@ -317,18 +311,16 @@ const Result = new Lang.Class({
const WindowList = new Lang.Class({ const WindowList = new Lang.Class({
Name: 'WindowList', Name: 'WindowList',
_init: function(lookingGlass) { _init : function () {
this.actor = new St.BoxLayout({ name: 'Windows', vertical: true, style: 'spacing: 8px' }); this.actor = new St.BoxLayout({ name: 'Windows', vertical: true, style: 'spacing: 8px' });
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
this._updateId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._updateWindowList)); this._updateId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._updateWindowList));
global.display.connect('window-created', Lang.bind(this, this._updateWindowList)); global.display.connect('window-created', Lang.bind(this, this._updateWindowList));
tracker.connect('tracked-windows-changed', Lang.bind(this, this._updateWindowList)); tracker.connect('tracked-windows-changed', Lang.bind(this, this._updateWindowList));
this._lookingGlass = lookingGlass;
}, },
_updateWindowList: function() { _updateWindowList: function() {
this.actor.destroy_all_children(); this.actor.get_children().forEach(function (actor) { actor.destroy(); });
let windows = global.get_window_actors(); let windows = global.get_window_actors();
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
for (let i = 0; i < windows.length; i++) { for (let i = 0; i < windows.length; i++) {
@ -340,7 +332,7 @@ const WindowList = new Lang.Class({
} }
let box = new St.BoxLayout({ vertical: true }); let box = new St.BoxLayout({ vertical: true });
this.actor.add(box); this.actor.add(box);
let windowLink = new ObjLink(this._lookingGlass, metaWindow, metaWindow.title); let windowLink = new ObjLink(metaWindow, metaWindow.title);
box.add(windowLink.actor, { x_align: St.Align.START, x_fill: false }); box.add(windowLink.actor, { x_align: St.Align.START, x_fill: false });
let propsBox = new St.BoxLayout({ vertical: true, style: 'padding-left: 6px;' }); let propsBox = new St.BoxLayout({ vertical: true, style: 'padding-left: 6px;' });
box.add(propsBox); box.add(propsBox);
@ -351,7 +343,7 @@ const WindowList = new Lang.Class({
let propBox = new St.BoxLayout({ style: 'spacing: 6px; ' }); let propBox = new St.BoxLayout({ style: 'spacing: 6px; ' });
propsBox.add(propBox); propsBox.add(propBox);
propBox.add(new St.Label({ text: 'app: ' }), { y_fill: false }); propBox.add(new St.Label({ text: 'app: ' }), { y_fill: false });
let appLink = new ObjLink(this._lookingGlass, app, app.get_id()); let appLink = new ObjLink(app, app.get_id());
propBox.add(appLink.actor, { y_fill: false }); propBox.add(appLink.actor, { y_fill: false });
propBox.add(icon, { y_fill: false }); propBox.add(icon, { y_fill: false });
} else { } else {
@ -365,21 +357,18 @@ Signals.addSignalMethods(WindowList.prototype);
const ObjInspector = new Lang.Class({ const ObjInspector = new Lang.Class({
Name: 'ObjInspector', Name: 'ObjInspector',
_init: function(lookingGlass) { _init : function () {
this._obj = null; this._obj = null;
this._previousObj = null; this._previousObj = null;
this._parentList = []; this._parentList = [];
this.actor = new St.ScrollView({ pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }), this.actor = new St.ScrollView({ x_fill: true, y_fill: true });
x_fill: true, y_fill: true });
this.actor.get_hscroll_bar().hide(); this.actor.get_hscroll_bar().hide();
this._container = new St.BoxLayout({ name: 'LookingGlassPropertyInspector', this._container = new St.BoxLayout({ name: 'LookingGlassPropertyInspector',
style_class: 'lg-dialog', style_class: 'lg-dialog',
vertical: true }); vertical: true });
this.actor.add_actor(this._container); this.actor.add_actor(this._container);
this._lookingGlass = lookingGlass;
}, },
selectObject: function(obj, skipPrevious) { selectObject: function(obj, skipPrevious) {
@ -389,7 +378,7 @@ const ObjInspector = new Lang.Class({
this._previousObj = null; this._previousObj = null;
this._obj = obj; this._obj = obj;
this._container.destroy_all_children(); this._container.get_children().forEach(function (child) { child.destroy(); });
let hbox = new St.BoxLayout({ style_class: 'lg-obj-inspector-title' }); let hbox = new St.BoxLayout({ style_class: 'lg-obj-inspector-title' });
this._container.add_actor(hbox); this._container.add_actor(hbox);
@ -423,7 +412,7 @@ const ObjInspector = new Lang.Class({
let link; let link;
try { try {
let prop = obj[propName]; let prop = obj[propName];
link = new ObjLink(this._lookingGlass, prop).actor; link = new ObjLink(prop).actor;
} catch (e) { } catch (e) {
link = new St.Label({ text: '<error>' }); link = new St.Label({ text: '<error>' });
} }
@ -444,6 +433,10 @@ const ObjInspector = new Lang.Class({
this.actor.show(); this.actor.show();
if (sourceActor) { if (sourceActor) {
this.actor.set_scale(0, 0); this.actor.set_scale(0, 0);
let [sourceX, sourceY] = sourceActor.get_transformed_position();
let [sourceWidth, sourceHeight] = sourceActor.get_transformed_size();
this.actor.move_anchor_point(Math.floor(sourceX + sourceWidth / 2),
Math.floor(sourceY + sourceHeight / 2));
Tweener.addTween(this.actor, { scale_x: 1, scale_y: 1, Tweener.addTween(this.actor, { scale_x: 1, scale_y: 1,
transition: 'easeOutQuad', transition: 'easeOutQuad',
time: 0.2 }); time: 0.2 });
@ -464,7 +457,7 @@ const ObjInspector = new Lang.Class({
_onInsert: function() { _onInsert: function() {
let obj = this._obj; let obj = this._obj;
this.close(); this.close();
this._lookingGlass.insertObject(obj); Main.lookingGlass.insertObject(obj);
}, },
_onBack: function() { _onBack: function() {
@ -472,36 +465,34 @@ const ObjInspector = new Lang.Class({
} }
}); });
const RedBorderEffect = new Lang.Class({ function addBorderPaintHook(actor) {
Name: 'RedBorderEffect', let signalId = actor.connect_after('paint',
Extends: Clutter.Effect, function () {
let color = new Cogl.Color();
color.init_from_4ub(0xff, 0, 0, 0xc4);
Cogl.set_source_color(color);
vfunc_paint: function() { let geom = actor.get_allocation_geometry();
let actor = this.get_actor(); let width = 2;
actor.continue_paint();
let color = new Cogl.Color(); // clockwise order
color.init_from_4ub(0xff, 0, 0, 0xc4); Cogl.rectangle(0, 0, geom.width, width);
Cogl.set_source_color(color); 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);
});
let geom = actor.get_allocation_geometry(); actor.queue_redraw();
let width = 2; return signalId;
}
// 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);
},
});
const Inspector = new Lang.Class({ const Inspector = new Lang.Class({
Name: 'Inspector', Name: 'Inspector',
_init: function(lookingGlass) { _init: function() {
let container = new Shell.GenericContainer({ width: 0, let container = new Shell.GenericContainer({ width: 0,
height: 0 }); height: 0 });
container.connect('allocate', Lang.bind(this, this._allocate)); container.connect('allocate', Lang.bind(this, this._allocate));
@ -515,6 +506,9 @@ const Inspector = new Lang.Class({
this._displayText = new St.Label(); this._displayText = new St.Label();
eventHandler.add(this._displayText, { expand: true }); eventHandler.add(this._displayText, { expand: true });
this._borderPaintTarget = null;
this._borderPaintId = null;
eventHandler.connect('destroy', Lang.bind(this, this._onDestroy));
eventHandler.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); eventHandler.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
eventHandler.connect('button-press-event', Lang.bind(this, this._onButtonPressEvent)); eventHandler.connect('button-press-event', Lang.bind(this, this._onButtonPressEvent));
eventHandler.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); eventHandler.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
@ -529,8 +523,6 @@ const Inspector = new Lang.Class({
// out, or move the pointer outside of _pointerTarget. // out, or move the pointer outside of _pointerTarget.
this._target = null; this._target = null;
this._pointerTarget = null; this._pointerTarget = null;
this._lookingGlass = lookingGlass;
}, },
_allocate: function(actor, box, flags) { _allocate: function(actor, box, flags) {
@ -551,13 +543,18 @@ const Inspector = new Lang.Class({
}, },
_close: function() { _close: function() {
Clutter.ungrab_pointer(); Clutter.ungrab_pointer(this._eventHandler);
Clutter.ungrab_keyboard(); Clutter.ungrab_keyboard(this._eventHandler);
this._eventHandler.destroy(); this._eventHandler.destroy();
this._eventHandler = null; this._eventHandler = null;
this.emit('closed'); this.emit('closed');
}, },
_onDestroy: function() {
if (this._borderPaintTarget != null)
this._borderPaintTarget.disconnect(this._borderPaintId);
},
_onKeyPressEvent: function (actor, event) { _onKeyPressEvent: function (actor, event) {
if (event.get_key_symbol() == Clutter.Escape) if (event.get_key_symbol() == Clutter.Escape)
this._close(); this._close();
@ -626,7 +623,12 @@ const Inspector = new Lang.Class({
this._displayText.text = ''; this._displayText.text = '';
this._displayText.text = position + ' ' + this._target; this._displayText.text = position + ' ' + this._target;
this._lookingGlass.setBorderPaintTarget(this._target); if (this._borderPaintTarget != this._target) {
if (this._borderPaintTarget != null)
this._borderPaintTarget.disconnect(this._borderPaintId);
this._borderPaintTarget = this._target;
this._borderPaintId = addBorderPaintHook(this._target);
}
} }
}); });
@ -660,7 +662,7 @@ const Memory = new Lang.Class({
this._gcbutton = new St.Button({ label: 'Full GC', this._gcbutton = new St.Button({ label: 'Full GC',
style_class: 'lg-obj-inspector-button' }); style_class: 'lg-obj-inspector-button' });
this._gcbutton.connect('clicked', Lang.bind(this, function () { System.gc(); this._renderText(); })); this._gcbutton.connect('clicked', Lang.bind(this, function () { global.gc(); this._renderText(); }));
this.actor.add(this._gcbutton, { x_align: St.Align.START, this.actor.add(this._gcbutton, { x_align: St.Align.START,
x_fill: false }); x_fill: false });
@ -721,13 +723,13 @@ const Extensions = new Lang.Class({
let extension = actor._extension; let extension = actor._extension;
let uri = extension.dir.get_uri(); let uri = extension.dir.get_uri();
Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context()); Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context());
this._lookingGlass.close(); Main.lookingGlass.close();
}, },
_onWebPage: function (actor) { _onWebPage: function (actor) {
let extension = actor._extension; let extension = actor._extension;
Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context()); Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context());
this._lookingGlass.close(); Main.lookingGlass.close();
}, },
_onViewErrors: function (actor) { _onViewErrors: function (actor) {
@ -791,33 +793,24 @@ const Extensions = new Lang.Class({
text: this._stateToString(extension.state) }); text: this._stateToString(extension.state) });
metaBox.add(state); metaBox.add(state);
let viewsource = new St.Button({ reactive: true, let viewsource = new Link.Link({ label: _("View Source") });
track_hover: true, viewsource.actor._extension = extension;
style_class: 'shell-link', viewsource.actor.connect('clicked', Lang.bind(this, this._onViewSource));
label: _("View Source") }); metaBox.add(viewsource.actor);
viewsource._extension = extension;
viewsource.connect('clicked', Lang.bind(this, this._onViewSource));
metaBox.add(viewsource);
if (extension.metadata.url) { if (extension.metadata.url) {
let webpage = new St.Button({ reactive: true, let webpage = new Link.Link({ label: _("Web Page") });
track_hover: true, webpage.actor._extension = extension;
style_class: 'shell-link', webpage.actor.connect('clicked', Lang.bind(this, this._onWebPage));
label: _("Web Page") }); metaBox.add(webpage.actor);
webpage._extension = extension;
webpage.connect('clicked', Lang.bind(this, this._onWebPage));
metaBox.add(webpage);
} }
let viewerrors = new St.Button({ reactive: true, let viewerrors = new Link.Link({ label: _("Show Errors") });
track_hover: true, viewerrors.actor._extension = extension;
style_class: 'shell-link', viewerrors.actor._parentBox = box;
label: _("Show Errors") }); viewerrors.actor._isShowing = false;
viewerrors._extension = extension; viewerrors.actor.connect('clicked', Lang.bind(this, this._onViewErrors));
viewerrors._parentBox = box; metaBox.add(viewerrors.actor);
viewerrors._isShowing = false;
viewerrors.connect('clicked', Lang.bind(this, this._onViewErrors));
metaBox.add(viewerrors);
return box; return box;
} }
@ -828,7 +821,8 @@ const LookingGlass = new Lang.Class({
_init : function() { _init : function() {
this._borderPaintTarget = null; this._borderPaintTarget = null;
this._redBorderEffect = new RedBorderEffect(); this._borderPaintId = 0;
this._borderDestroyId = 0;
this._open = false; this._open = false;
@ -858,18 +852,19 @@ const LookingGlass = new Lang.Class({
Main.layoutManager.keyboardBox.connect('allocation-changed', Main.layoutManager.keyboardBox.connect('allocation-changed',
Lang.bind(this, this._queueResize)); Lang.bind(this, this._queueResize));
this._objInspector = new ObjInspector(this); this._objInspector = new ObjInspector();
Main.uiGroup.add_actor(this._objInspector.actor); Main.uiGroup.add_actor(this._objInspector.actor);
this._objInspector.actor.hide(); this._objInspector.actor.hide();
let toolbar = new St.BoxLayout({ name: 'Toolbar' }); let toolbar = new St.BoxLayout({ name: 'Toolbar' });
this.actor.add_actor(toolbar); this.actor.add_actor(toolbar);
let inspectIcon = new St.Icon({ icon_name: 'gtk-color-picker', let inspectIcon = new St.Icon({ icon_name: 'gtk-color-picker',
icon_type: St.IconType.FULLCOLOR,
icon_size: 24 }); icon_size: 24 });
toolbar.add_actor(inspectIcon); toolbar.add_actor(inspectIcon);
inspectIcon.reactive = true; inspectIcon.reactive = true;
inspectIcon.connect('button-press-event', Lang.bind(this, function () { inspectIcon.connect('button-press-event', Lang.bind(this, function () {
let inspector = new Inspector(this); let inspector = new Inspector();
inspector.connect('target', Lang.bind(this, function(i, target, stageX, stageY) { inspector.connect('target', Lang.bind(this, function(i, target, stageX, stageY) {
this._pushResult('<inspect x:' + stageX + ' y:' + stageY + '>', this._pushResult('<inspect x:' + stageX + ' y:' + stageY + '>',
target); target);
@ -899,14 +894,18 @@ const LookingGlass = new Lang.Class({
this._entryArea = new St.BoxLayout({ name: 'EntryArea' }); this._entryArea = new St.BoxLayout({ name: 'EntryArea' });
this._evalBox.add_actor(this._entryArea); this._evalBox.add_actor(this._entryArea);
let label = new St.Label({ text: CHEVRON }); let label = new St.Label({ text: 'js>>> ' });
this._entryArea.add(label); this._entryArea.add(label);
this._entry = new St.Entry({ can_focus: true }); this._entry = new St.Entry({ can_focus: true });
ShellEntry.addContextMenu(this._entry); ShellEntry.addContextMenu(this._entry);
this._entryArea.add(this._entry, { expand: true }); this._entryArea.add(this._entry, { expand: true });
this._windowList = new WindowList(this); this._windowList = new WindowList();
this._windowList.connect('selected', Lang.bind(this, function(list, window) {
notebook.selectIndex(0);
this._pushResult('<window selection>', window);
}));
notebook.appendPage('Windows', this._windowList.actor); notebook.appendPage('Windows', this._windowList.actor);
this._memory = new Memory(); this._memory = new Memory();
@ -960,22 +959,23 @@ const LookingGlass = new Lang.Class({
+ 'font-family: "' + fontDesc.get_family() + '";'; + 'font-family: "' + fontDesc.get_family() + '";';
}, },
setBorderPaintTarget: function(obj) {
if (this._borderPaintTarget != null)
this._borderPaintTarget.remove_effect(this._redBorderEffect);
this._borderPaintTarget = obj;
if (this._borderPaintTarget != null)
this._borderPaintTarget.add_effect(this._redBorderEffect);
},
_pushResult: function(command, obj) { _pushResult: function(command, obj) {
let index = this._results.length + this._offset; let index = this._results.length + this._offset;
let result = new Result(this, CHEVRON + command, obj, index); let result = new Result('>>> ' + command, obj, index);
this._results.push(result); this._results.push(result);
this._resultsArea.add(result.actor); this._resultsArea.add(result.actor);
if (obj instanceof Clutter.Actor) if (this._borderPaintTarget != null) {
this.setBorderPaintTarget(obj); this._borderPaintTarget.disconnect(this._borderPaintId);
this._borderPaintTarget = null;
}
if (obj instanceof Clutter.Actor) {
this._borderPaintTarget = obj;
this._borderPaintId = addBorderPaintHook(obj);
this._borderDestroyId = obj.connect('destroy', Lang.bind(this, function () {
this._borderDestroyId = 0;
this._borderPaintTarget = null;
}));
}
let children = this._resultsArea.get_children(); let children = this._resultsArea.get_children();
if (children.length > this._maxItems) { if (children.length > this._maxItems) {
this._results.shift(); this._results.shift();
@ -1089,8 +1089,8 @@ const LookingGlass = new Lang.Class({
this.actor.width = myWidth; this.actor.width = myWidth;
this.actor.height = myHeight; this.actor.height = myHeight;
this._objInspector.actor.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8)); this._objInspector.actor.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8));
this._objInspector.actor.set_position(primary.x + this.actor.x + Math.floor(myWidth * 0.1), this._objInspector.actor.set_position(this.actor.x + Math.floor(myWidth * 0.1),
primary.y + this._targetY + Math.floor(myHeight * 0.1)); this._targetY + Math.floor(myHeight * 0.1));
}, },
insertObject: function(obj) { insertObject: function(obj) {
@ -1156,7 +1156,11 @@ const LookingGlass = new Lang.Class({
this._open = false; this._open = false;
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);
this.setBorderPaintTarget(null); if (this._borderPaintTarget != null) {
this._borderPaintTarget.disconnect(this._borderPaintId);
this._borderPaintTarget.disconnect(this._borderDestroyId);
this._borderPaintTarget = null;
}
Main.popModal(this._entry); Main.popModal(this._entry);

View File

@ -12,11 +12,10 @@ const Signals = imports.signals;
const Main = imports.ui.main; const Main = imports.ui.main;
const MagnifierDBus = imports.ui.magnifierDBus; const MagnifierDBus = imports.ui.magnifierDBus;
const Params = imports.misc.params; const Params = imports.misc.params;
const PointerWatcher = imports.ui.pointerWatcher;
const MOUSE_POLL_FREQUENCY = 50; const MOUSE_POLL_FREQUENCY = 50;
const CROSSHAIRS_CLIP_SIZE = [100, 100]; const CROSSHAIRS_CLIP_SIZE = [100, 100];
const NO_CHANGE = 0.0;
// Settings // Settings
const APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications'; const APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications';
@ -25,14 +24,6 @@ const SHOW_KEY = 'screen-magnifier-enabled';
const MAGNIFIER_SCHEMA = 'org.gnome.desktop.a11y.magnifier'; const MAGNIFIER_SCHEMA = 'org.gnome.desktop.a11y.magnifier';
const SCREEN_POSITION_KEY = 'screen-position'; const SCREEN_POSITION_KEY = 'screen-position';
const MAG_FACTOR_KEY = 'mag-factor'; const MAG_FACTOR_KEY = 'mag-factor';
const INVERT_LIGHTNESS_KEY = 'invert-lightness';
const COLOR_SATURATION_KEY = 'color-saturation';
const BRIGHT_RED_KEY = 'brightness-red';
const BRIGHT_GREEN_KEY = 'brightness-green';
const BRIGHT_BLUE_KEY = 'brightness-blue';
const CONTRAST_RED_KEY = 'contrast-red';
const CONTRAST_GREEN_KEY = 'contrast-green';
const CONTRAST_BLUE_KEY = 'contrast-blue';
const LENS_MODE_KEY = 'lens-mode'; const LENS_MODE_KEY = 'lens-mode';
const CLAMP_MODE_KEY = 'scroll-at-edges'; const CLAMP_MODE_KEY = 'scroll-at-edges';
const MOUSE_TRACKING_KEY = 'mouse-tracking'; const MOUSE_TRACKING_KEY = 'mouse-tracking';
@ -56,7 +47,7 @@ const Magnifier = new Lang.Class({
let xfixesCursor = Shell.XFixesCursor.get_for_stage(global.stage); let xfixesCursor = Shell.XFixesCursor.get_for_stage(global.stage);
this._mouseSprite = new Clutter.Texture(); this._mouseSprite = new Clutter.Texture();
xfixesCursor.update_texture_image(this._mouseSprite); xfixesCursor.update_texture_image(this._mouseSprite);
this._cursorRoot = new Clutter.Actor(); this._cursorRoot = new Clutter.Group();
this._cursorRoot.add_actor(this._mouseSprite); this._cursorRoot.add_actor(this._mouseSprite);
// Create the first ZoomRegion and initialize it according to the // Create the first ZoomRegion and initialize it according to the
@ -136,8 +127,11 @@ const Magnifier = new Lang.Class({
* Turn on mouse tracking, if not already doing so. * Turn on mouse tracking, if not already doing so.
*/ */
startTrackingMouse: function() { startTrackingMouse: function() {
if (!this._pointerWatch) if (!this._mouseTrackingId)
this._pointerWatch = PointerWatcher.getPointerWatcher().addWatch(MOUSE_POLL_FREQUENCY, Lang.bind(this, this.scrollToMousePos)); this._mouseTrackingId = Mainloop.timeout_add(
MOUSE_POLL_FREQUENCY,
Lang.bind(this, this.scrollToMousePos)
);
}, },
/** /**
@ -145,10 +139,10 @@ const Magnifier = new Lang.Class({
* Turn off mouse tracking, if not already doing so. * Turn off mouse tracking, if not already doing so.
*/ */
stopTrackingMouse: function() { stopTrackingMouse: function() {
if (this._pointerWatch) if (this._mouseTrackingId)
this._pointerWatch.remove(); Mainloop.source_remove(this._mouseTrackingId);
this._pointerWatch = null; this._mouseTrackingId = null;
}, },
/** /**
@ -300,7 +294,8 @@ const Magnifier = new Lang.Class({
*/ */
setCrosshairsColor: function(color) { setCrosshairsColor: function(color) {
if (this._crossHairs) { if (this._crossHairs) {
let [res, clutterColor] = Clutter.Color.from_string(color); let clutterColor = new Clutter.Color();
clutterColor.from_string(color);
this._crossHairs.setColor(clutterColor); this._crossHairs.setColor(clutterColor);
} }
}, },
@ -448,25 +443,6 @@ const Magnifier = new Lang.Class({
aPref = this._settings.get_enum(MOUSE_TRACKING_KEY); aPref = this._settings.get_enum(MOUSE_TRACKING_KEY);
if (aPref) if (aPref)
zoomRegion.setMouseTrackingMode(aPref); zoomRegion.setMouseTrackingMode(aPref);
aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY);
if (aPref)
zoomRegion.setInvertLightness(aPref);
aPref = this._settings.get_double(COLOR_SATURATION_KEY);
if (aPref)
zoomRegion.setColorSaturation(aPref);
let bc = {};
bc.r = this._settings.get_double(BRIGHT_RED_KEY);
bc.g = this._settings.get_double(BRIGHT_GREEN_KEY);
bc.b = this._settings.get_double(BRIGHT_BLUE_KEY);
zoomRegion.setBrightness(bc);
bc.r = this._settings.get_double(CONTRAST_RED_KEY);
bc.g = this._settings.get_double(CONTRAST_GREEN_KEY);
bc.b = this._settings.get_double(CONTRAST_BLUE_KEY);
zoomRegion.setContrast(bc);
} }
let showCrosshairs = this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY); let showCrosshairs = this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY);
@ -489,25 +465,6 @@ const Magnifier = new Lang.Class({
this._settings.connect('changed::' + MOUSE_TRACKING_KEY, this._settings.connect('changed::' + MOUSE_TRACKING_KEY,
Lang.bind(this, this._updateMouseTrackingMode)); Lang.bind(this, this._updateMouseTrackingMode));
this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY,
Lang.bind(this, this._updateInvertLightness));
this._settings.connect('changed::' + COLOR_SATURATION_KEY,
Lang.bind(this, this._updateColorSaturation));
this._settings.connect('changed::' + BRIGHT_RED_KEY,
Lang.bind(this, this._updateBrightness));
this._settings.connect('changed::' + BRIGHT_GREEN_KEY,
Lang.bind(this, this._updateBrightness));
this._settings.connect('changed::' + BRIGHT_BLUE_KEY,
Lang.bind(this, this._updateBrightness));
this._settings.connect('changed::' + CONTRAST_RED_KEY,
Lang.bind(this, this._updateContrast));
this._settings.connect('changed::' + CONTRAST_GREEN_KEY,
Lang.bind(this, this._updateContrast));
this._settings.connect('changed::' + CONTRAST_BLUE_KEY,
Lang.bind(this, this._updateContrast));
this._settings.connect('changed::' + SHOW_CROSS_HAIRS_KEY, this._settings.connect('changed::' + SHOW_CROSS_HAIRS_KEY,
Lang.bind(this, function() { Lang.bind(this, function() {
this.setCrosshairsVisible(this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY)); this.setCrosshairsVisible(this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY));
@ -583,47 +540,7 @@ const Magnifier = new Lang.Class({
this._settings.get_enum(MOUSE_TRACKING_KEY) this._settings.get_enum(MOUSE_TRACKING_KEY)
); );
} }
}, }
_updateInvertLightness: function() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
this._zoomRegions[0].setInvertLightness(
this._settings.get_boolean(INVERT_LIGHTNESS_KEY)
);
}
},
_updateColorSaturation: function() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
this._zoomRegions[0].setColorSaturation(
this._settings.get_double(COLOR_SATURATION_KEY)
);
}
},
_updateBrightness: function() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
let brightness = {};
brightness.r = this._settings.get_double(BRIGHT_RED_KEY);
brightness.g = this._settings.get_double(BRIGHT_GREEN_KEY);
brightness.b = this._settings.get_double(BRIGHT_BLUE_KEY);
this._zoomRegions[0].setBrightness(brightness);
}
},
_updateContrast: function() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
let contrast = {};
contrast.r = this._settings.get_double(CONTRAST_RED_KEY);
contrast.g = this._settings.get_double(CONTRAST_GREEN_KEY);
contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY);
this._zoomRegions[0].setContrast(contrast);
}
},
}); });
Signals.addSignalMethods(Magnifier.prototype); Signals.addSignalMethods(Magnifier.prototype);
@ -637,10 +554,6 @@ const ZoomRegion = new Lang.Class({
this._clampScrollingAtEdges = false; this._clampScrollingAtEdges = false;
this._lensMode = false; this._lensMode = false;
this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN; this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN;
this._invertLightness = false;
this._colorSaturation = 1.0;
this._brightness = { r: NO_CHANGE, g: NO_CHANGE, b: NO_CHANGE };
this._contrast = { r: NO_CHANGE, g: NO_CHANGE, b: NO_CHANGE };
this._magView = null; this._magView = null;
this._background = null; this._background = null;
@ -966,107 +879,6 @@ const ZoomRegion = new Lang.Class({
} }
}, },
/**
* setInvertLightness:
* Set whether to invert the lightness of the magnified view.
* @flag Boolean to either invert brightness (true), or not (false).
*/
setInvertLightness: function(flag) {
this._invertLightness = flag;
if (this._magShaderEffects)
this._magShaderEffects.setInvertLightness(this._invertLightness);
},
/**
* getInvertLightness:
* Retrieve whether the lightness is inverted.
* @return Boolean indicating inversion (true), or not (false).
*/
getInvertLightness: function() {
return this._invertLightness;
},
/**
* 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.
*/
setColorSaturation: function(saturation) {
this._colorSaturation = saturation;
if (this._magShaderEffects)
this._magShaderEffects.setColorSaturation(this._colorSaturation);
},
/**
* getColorSaturation:
* Retrieve the color saturation of the magnified view.
*/
getColorSaturation: function() {
return this._colorSaturation;
},
/**
* 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.
*/
setBrightness: function(brightness) {
this._brightness.r = brightness.r;
this._brightness.g = brightness.g;
this._brightness.b = brightness.b;
if (this._magShaderEffects)
this._magShaderEffects.setBrightness(this._brightness);
},
/**
* getBrightness:
* Retrive the current brightness of the Zoom Region.
* @return Object containing the brightness change for the red, green,
* and blue channels.
*/
getBrightness: function() {
let brightness = {};
brightness.r = this._brightness.r;
brightness.g = this._brightness.g;
brightness.b = this._brightness.b;
return brightness;
},
/**
* 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.
*/
setContrast: function(contrast) {
this._contrast.r = contrast.r;
this._contrast.g = contrast.g;
this._contrast.b = contrast.b;
if (this._magShaderEffects)
this._magShaderEffects.setContrast(this._contrast);
},
/**
* getContrast:
* Retreive the contrast of the magnified view.
* @return Object containing the contrast for the red, green,
* and blue channels.
*/
getContrast: function() {
let contrast = {};
contrast.r = this._contrast.r;
contrast.g = this._contrast.g;
contrast.b = this._contrast.b;
return contrast;
},
//// Private methods //// //// Private methods ////
_createActors: function() { _createActors: function() {
@ -1077,21 +889,21 @@ const ZoomRegion = new Lang.Class({
// hide the magnified region from CLUTTER_PICK_ALL // 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. // Append a Clutter.Group to clip the contents of the magnified view.
let mainGroup = new Clutter.Actor({ clip_to_allocation: true }); let mainGroup = new Clutter.Group({ clip_to_allocation: true });
this._magView.set_child(mainGroup); this._magView.set_child(mainGroup);
// Add a background for when the magnified uiGroup is scrolled // Add a background for when the magnified uiGroup is scrolled
// out of view (don't want to see desktop showing through). // out of view (don't want to see desktop showing through).
this._background = new Clutter.Actor({ background_color: Main.DEFAULT_BACKGROUND_COLOR, this._background = new Clutter.Rectangle({ color: Main.DEFAULT_BACKGROUND_COLOR });
width: global.screen_width,
height: global.screen_height });
mainGroup.add_actor(this._background); mainGroup.add_actor(this._background);
// Clone the group that contains all of UI on the screen. This is the // Clone the group that contains all of UI on the screen. This is the
// chrome, the windows, etc. // chrome, the windows, etc.
this._uiGroupClone = new Clutter.Clone({ source: Main.uiGroup }); this._uiGroupClone = new Clutter.Clone({ source: Main.uiGroup });
mainGroup.add_actor(this._uiGroupClone); mainGroup.add_actor(this._uiGroupClone);
Main.uiGroup.set_size(global.screen_width, global.screen_height);
this._background.set_size(global.screen_width, global.screen_height);
// Add either the given mouseSourceActor to the ZoomRegion, or a clone of // Add either the given mouseSourceActor to the ZoomRegion, or a clone of
// it. // it.
@ -1105,13 +917,6 @@ const ZoomRegion = new Lang.Class({
this._crossHairsActor = this._crossHairs.addToZoomRegion(this, this._mouseActor); this._crossHairsActor = this._crossHairs.addToZoomRegion(this, this._mouseActor);
else else
this._crossHairsActor = null; this._crossHairsActor = null;
// Contrast and brightness effects.
this._magShaderEffects = new MagShaderEffects(this._uiGroupClone);
this._magShaderEffects.setColorSaturation(this._colorSaturation);
this._magShaderEffects.setInvertLightness(this._invertLightness);
this._magShaderEffects.setBrightness(this._brightness);
this._magShaderEffects.setContrast(this._contrast);
}, },
_destroyActors: function() { _destroyActors: function() {
@ -1120,8 +925,6 @@ const ZoomRegion = new Lang.Class({
if (this._crossHairs) if (this._crossHairs)
this._crossHairs.removeFromParent(this._crossHairsActor); this._crossHairs.removeFromParent(this._crossHairsActor);
this._magShaderEffects.destroyEffects();
this._magShaderEffects = null;
this._magView.destroy(); this._magView.destroy();
this._magView = null; this._magView = null;
this._background = null; this._background = null;
@ -1325,7 +1128,7 @@ const ZoomRegion = new Lang.Class({
this._mouseActor.set_scale(this._xMagFactor, this._yMagFactor); this._mouseActor.set_scale(this._xMagFactor, this._yMagFactor);
let [x, y] = this._screenToViewPort(0, 0); let [x, y] = this._screenToViewPort(0, 0);
this._uiGroupClone.set_position(Math.round(x), Math.round(y)); this._uiGroupClone.set_position(x, y);
this._updateMousePosition(); this._updateMousePosition();
}, },
@ -1353,6 +1156,7 @@ const ZoomRegion = new Lang.Class({
if (!this.isActive()) if (!this.isActive())
return; return;
Main.uiGroup.set_size(global.screen_width, global.screen_height);
this._background.set_size(global.screen_width, global.screen_height); this._background.set_size(global.screen_width, global.screen_height);
if (this._screenPosition == GDesktopEnums.MagnifierScreenPosition.NONE) if (this._screenPosition == GDesktopEnums.MagnifierScreenPosition.NONE)
@ -1376,15 +1180,15 @@ const Crosshairs = new Lang.Class({
let groupWidth = global.screen_width * 3; let groupWidth = global.screen_width * 3;
let groupHeight = global.screen_height * 3; let groupHeight = global.screen_height * 3;
this._actor = new Clutter.Actor({ this._actor = new Clutter.Group({
clip_to_allocation: false, clip_to_allocation: false,
width: groupWidth, width: groupWidth,
height: groupHeight height: groupHeight
}); });
this._horizLeftHair = new Clutter.Actor(); this._horizLeftHair = new Clutter.Rectangle();
this._horizRightHair = new Clutter.Actor(); this._horizRightHair = new Clutter.Rectangle();
this._vertTopHair = new Clutter.Actor(); this._vertTopHair = new Clutter.Rectangle();
this._vertBottomHair = new Clutter.Actor(); this._vertBottomHair = new Clutter.Rectangle();
this._actor.add_actor(this._horizLeftHair); this._actor.add_actor(this._horizLeftHair);
this._actor.add_actor(this._horizRightHair); this._actor.add_actor(this._horizRightHair);
this._actor.add_actor(this._vertTopHair); this._actor.add_actor(this._vertTopHair);
@ -1455,10 +1259,10 @@ const Crosshairs = new Lang.Class({
* @clutterColor: The color as a Clutter.Color. * @clutterColor: The color as a Clutter.Color.
*/ */
setColor: function(clutterColor) { setColor: function(clutterColor) {
this._horizLeftHair.background_color = clutterColor; this._horizLeftHair.set_color(clutterColor);
this._horizRightHair.background_color = clutterColor; this._horizRightHair.set_color(clutterColor);
this._vertTopHair.background_color = clutterColor; this._vertTopHair.set_color(clutterColor);
this._vertBottomHair.background_color = clutterColor; this._vertBottomHair.set_color(clutterColor);
}, },
/** /**
@ -1467,7 +1271,9 @@ const Crosshairs = new Lang.Class({
* @color: The color as a Clutter.Color. * @color: The color as a Clutter.Color.
*/ */
getColor: function() { getColor: function() {
return this._horizLeftHair.get_color(); let clutterColor = new Clutter.Color();
this._horizLeftHair.get_color(clutterColor);
return clutterColor;
}, },
/** /**
@ -1627,144 +1433,3 @@ const Crosshairs = new Lang.Class({
this._vertBottomHair.set_position((groupWidth - thickness) / 2, bottom); this._vertBottomHair.set_position((groupWidth - thickness) / 2, bottom);
} }
}); });
const MagShaderEffects = new Lang.Class({
Name: 'MagShaderEffects',
_init: function(uiGroupClone) {
this._inverse = new Shell.InvertLightnessEffect();
this._brightnessContrast = new Clutter.BrightnessContrastEffect();
this._colorDesaturation = new Clutter.DesaturateEffect();
this._inverse.set_enabled(false);
this._brightnessContrast.set_enabled(false);
this._magView = uiGroupClone;
this._magView.add_effect(this._inverse);
this._magView.add_effect(this._brightnessContrast);
this._magView.add_effect(this._colorDesaturation);
},
/**
* destroyEffects:
* Remove contrast and brightness effects from the magnified view, and
* lose the reference to the actor they were applied to. Don't use this
* object after calling this.
*/
destroyEffects: function() {
this._magView.clear_effects();
this._colorDesaturation = null;
this._brightnessContrast = null;
this._inverse = null;
this._magView = null;
},
/**
* setInvertLightness:
* Enable/disable invert lightness effect.
* @invertFlag: Enabled flag.
*/
setInvertLightness: function(invertFlag) {
this._inverse.set_enabled(invertFlag);
},
/**
* getInvertLightness:
* Report whether the inversion effect is enabled.
* @return: Boolean.
*/
getInvertLightness: function() {
return this._inverse.get_enabled();
},
setColorSaturation: function(factor) {
this._colorDesaturation.set_factor(1.0 - factor);
},
getColorSaturation: function() {
return 1.0 - this._colorDesaturation.get_factor();
},
/**
* 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.
*/
setBrightness: function(brightness) {
let bRed = brightness.r;
let bGreen = brightness.g;
let bBlue = brightness.b;
this._brightnessContrast.set_brightness_full(bRed, bGreen, bBlue);
// Enable the effect if the brightness OR contrast change are such that
// 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)
);
},
/**
* getBrightness:
* Retrieve current brightness of the magnified view.
* @return: 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.
*/
getBrightness: function() {
let result = {};
let [bRed, bGreen, bBlue] = this._brightnessContrast.get_brightness();
result.r = bRed;
result.g = bGreen;
result.b = bBlue;
return result;
},
/**
* 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.
*/
setContrast: function(contrast) {
let cRed = contrast.r;
let cGreen = contrast.g;
let cBlue = contrast.b;
this._brightnessContrast.set_contrast_full(cRed, cGreen, cBlue);
// Enable the effect if the contrast OR brightness change are such that
// it modifies the brightness and/or contrast.
// should be able to use Clutter.color_equal(), but that complains of
// a null first argument.
let [bRed, bGreen, bBlue] = this._brightnessContrast.get_brightness();
this._brightnessContrast.set_enabled(
cRed != NO_CHANGE || cGreen != NO_CHANGE || cBlue != NO_CHANGE ||
bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != NO_CHANGE
);
},
/**
* Retrieve current contrast of the magnified view.
* @return: 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.
*/
getContrast: function() {
let resutl = {};
let [cRed, cGreen, cBlue] = this._brightnessContrast.get_contrast();
result.r = cRed;
result.g = cGreen;
result.b = cBlue;
return result;
}
});

View File

@ -10,68 +10,145 @@ const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const Components = imports.ui.components; const AutomountManager = imports.ui.automountManager;
const AutorunManager = imports.ui.autorunManager;
const CtrlAltTab = imports.ui.ctrlAltTab; const CtrlAltTab = imports.ui.ctrlAltTab;
const EndSessionDialog = imports.ui.endSessionDialog; const EndSessionDialog = imports.ui.endSessionDialog;
const PolkitAuthenticationAgent = imports.ui.polkitAuthenticationAgent;
const KeyringPrompt = imports.ui.keyringPrompt;
const Environment = imports.ui.environment; const Environment = imports.ui.environment;
const ExtensionSystem = imports.ui.extensionSystem; const ExtensionSystem = imports.ui.extensionSystem;
const ExtensionDownloader = imports.ui.extensionDownloader;
const Keyboard = imports.ui.keyboard; const Keyboard = imports.ui.keyboard;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const PlaceDisplay = imports.ui.placeDisplay;
const RunDialog = imports.ui.runDialog; const RunDialog = imports.ui.runDialog;
const Layout = imports.ui.layout; const Layout = imports.ui.layout;
const LookingGlass = imports.ui.lookingGlass; const LookingGlass = imports.ui.lookingGlass;
const NetworkAgent = imports.ui.networkAgent;
const NotificationDaemon = imports.ui.notificationDaemon; const NotificationDaemon = imports.ui.notificationDaemon;
const WindowAttentionHandler = imports.ui.windowAttentionHandler; const WindowAttentionHandler = imports.ui.windowAttentionHandler;
const ScreenShield = imports.ui.screenShield;
const Scripting = imports.ui.scripting; const Scripting = imports.ui.scripting;
const SessionMode = imports.ui.sessionMode;
const ShellDBus = imports.ui.shellDBus; const ShellDBus = imports.ui.shellDBus;
const ShellMountOperation = imports.ui.shellMountOperation; const TelepathyClient = imports.ui.telepathyClient;
const UnlockDialog = imports.ui.unlockDialog;
const WindowManager = imports.ui.windowManager; const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier; const Magnifier = imports.ui.magnifier;
const XdndHandler = imports.ui.xdndHandler; const XdndHandler = imports.ui.xdndHandler;
const StatusIconDispatcher = imports.ui.statusIconDispatcher;
const Util = imports.misc.util; const Util = imports.misc.util;
const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides'; const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides';
const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff); const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
let componentManager = null; let automountManager = null;
let autorunManager = null;
let panel = null; let panel = null;
let hotCorners = [];
let placesManager = null;
let overview = null; let overview = null;
let runDialog = null; let runDialog = null;
let lookingGlass = null; let lookingGlass = null;
let wm = null; let wm = null;
let messageTray = null; let messageTray = null;
let screenShield = null;
let notificationDaemon = null; let notificationDaemon = null;
let windowAttentionHandler = null; let windowAttentionHandler = null;
let telepathyClient = null;
let ctrlAltTabManager = null; let ctrlAltTabManager = null;
let sessionMode = null; let recorder = null;
let shellDBusService = null; let shellDBusService = null;
let shellMountOpDBusService = null;
let screenSaverDBus = null;
let modalCount = 0; let modalCount = 0;
let modalActorFocusStack = []; let modalActorFocusStack = [];
let uiGroup = null; let uiGroup = null;
let magnifier = null; let magnifier = null;
let xdndHandler = null; let xdndHandler = null;
let statusIconDispatcher = null;
let keyboard = null; let keyboard = null;
let layoutManager = null; let layoutManager = null;
let networkAgent = null;
let _startDate; let _startDate;
let _defaultCssStylesheet = null; let _defaultCssStylesheet = null;
let _cssStylesheet = null; let _cssStylesheet = null;
let _gdmCssStylesheet = null;
let _overridesSettings = null; let _overridesSettings = null;
let background = null; let background = null;
function _sessionUpdated() { function _createUserSession() {
Meta.keybindings_set_custom_handler('panel-run-dialog', sessionMode.hasRunDialog ? openRunDialog : null); // Load the calendar server. Note that we are careful about
if (sessionMode.isGreeter) // not loading any events until the user presses the clock
screenShield.showDialog(); global.launch_calendar_server();
placesManager = new PlaceDisplay.PlacesManager();
telepathyClient = new TelepathyClient.Client();
automountManager = new AutomountManager.AutomountManager();
autorunManager = new AutorunManager.AutorunManager();
networkAgent = new NetworkAgent.NetworkAgent();
}
function _createGDMSession() {
// We do this this here instead of at the top to prevent GDM
// related code from getting loaded in normal user sessions
const LoginDialog = imports.gdm.loginDialog;
let loginDialog = new LoginDialog.LoginDialog();
loginDialog.connect('loaded', function() {
loginDialog.open();
});
}
function _initRecorder() {
let recorderSettings = new Gio.Settings({ schema: 'org.gnome.shell.recorder' });
let bindingSettings = new Gio.Settings({ schema: 'org.gnome.shell.keybindings' });
global.display.add_keybinding('toggle-recording',
bindingSettings,
Meta.KeyBindingFlags.NONE, function() {
if (recorder == null) {
recorder = new Shell.Recorder({ stage: global.stage });
}
if (recorder.is_recording()) {
recorder.close();
Meta.enable_unredirect_for_screen(global.screen);
} else {
// read the parameters from GSettings always in case they have changed
recorder.set_framerate(recorderSettings.get_int('framerate'));
/* Translators: this is a filename used for screencast recording */
// xgettext:no-c-format
recorder.set_filename(_("Screencast from %d %t") + '.' + recorderSettings.get_string('file-extension'));
let pipeline = recorderSettings.get_string('pipeline');
if (!pipeline.match(/^\s*$/))
recorder.set_pipeline(pipeline);
else
recorder.set_pipeline(null);
Meta.disable_unredirect_for_screen(global.screen);
recorder.record();
}
});
}
function _initUserSession() {
_initRecorder();
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT, false, -1, 1);
ExtensionSystem.init();
ExtensionSystem.loadExtensions();
Meta.keybindings_set_custom_handler('panel-run-dialog', function() {
getRunDialog().open();
});
Meta.keybindings_set_custom_handler('panel-main-menu', function () {
overview.toggle();
});
global.display.connect('overlay-key', Lang.bind(overview, overview.toggle));
} }
function start() { function start() {
@ -84,9 +161,7 @@ function start() {
Gio.DesktopAppInfo.set_desktop_env('GNOME'); Gio.DesktopAppInfo.set_desktop_env('GNOME');
sessionMode = new SessionMode.SessionMode();
shellDBusService = new ShellDBus.GnomeShell(); shellDBusService = new ShellDBus.GnomeShell();
shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
// also initialize ShellAppSystem first. ShellAppSystem // also initialize ShellAppSystem first. ShellAppSystem
@ -108,6 +183,7 @@ function start() {
global.stage.no_clear_hint = true; global.stage.no_clear_hint = true;
_defaultCssStylesheet = global.datadir + '/theme/gnome-shell.css'; _defaultCssStylesheet = global.datadir + '/theme/gnome-shell.css';
_gdmCssStylesheet = global.datadir + '/theme/gdm.css';
loadTheme(); loadTheme();
// Set up stage hierarchy to group all UI actors under one container. // Set up stage hierarchy to group all UI actors under one container.
@ -135,36 +211,42 @@ function start() {
layoutManager = new Layout.LayoutManager(); layoutManager = new Layout.LayoutManager();
xdndHandler = new XdndHandler.XdndHandler(); xdndHandler = new XdndHandler.XdndHandler();
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
overview = new Overview.Overview(); // This overview object is just a stub for non-user sessions
overview = new Overview.Overview({ isDummy: global.session_type != Shell.SessionType.USER });
magnifier = new Magnifier.Magnifier(); magnifier = new Magnifier.Magnifier();
if (UnlockDialog.isSupported()) statusIconDispatcher = new StatusIconDispatcher.StatusIconDispatcher();
screenShield = new ScreenShield.ScreenShield();
else
screenShield = new ScreenShield.ScreenShieldFallback();
panel = new Panel.Panel(); panel = new Panel.Panel();
wm = new WindowManager.WindowManager(); wm = new WindowManager.WindowManager();
messageTray = new MessageTray.MessageTray(); messageTray = new MessageTray.MessageTray();
keyboard = new Keyboard.Keyboard(); keyboard = new Keyboard.Keyboard();
notificationDaemon = new NotificationDaemon.NotificationDaemon(); notificationDaemon = new NotificationDaemon.NotificationDaemon();
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler(); windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
componentManager = new Components.ComponentManager();
if (global.session_type == Shell.SessionType.USER)
_createUserSession();
else if (global.session_type == Shell.SessionType.GDM)
_createGDMSession();
panel.startStatusArea();
layoutManager.init(); layoutManager.init();
keyboard.init(); keyboard.init();
overview.init(); overview.init();
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT, if (global.session_type == Shell.SessionType.USER)
false, -1, 1); _initUserSession();
Meta.keybindings_set_custom_handler('panel-main-menu', Lang.bind(overview, overview.toggle)); statusIconDispatcher.start(messageTray.actor);
global.display.connect('overlay-key', Lang.bind(overview, overview.toggle));
sessionMode.connect('update', _sessionUpdated);
_sessionUpdated();
// Provide the bus object for gnome-session to // Provide the bus object for gnome-session to
// initiate logouts. // initiate logouts.
EndSessionDialog.init(); EndSessionDialog.init();
// Attempt to become a PolicyKit authentication agent
PolkitAuthenticationAgent.init()
// Become a prompter for gnome keyring
KeyringPrompt.init();
_startDate = new Date(); _startDate = new Date();
global.stage.connect('captured-event', _globalKeyPressHandler); global.stage.connect('captured-event', _globalKeyPressHandler);
@ -188,9 +270,6 @@ function start() {
global.screen.connect('restacked', _windowsRestacked); global.screen.connect('restacked', _windowsRestacked);
_nWorkspacesChanged(); _nWorkspacesChanged();
ExtensionDownloader.init();
ExtensionSystem.init();
} }
let _workspaces = []; let _workspaces = [];
@ -414,6 +493,9 @@ function loadTheme() {
let theme = new St.Theme ({ application_stylesheet: cssStylesheet }); let theme = new St.Theme ({ application_stylesheet: cssStylesheet });
if (global.session_type == Shell.SessionType.GDM)
theme.load_stylesheet(_gdmCssStylesheet);
if (previousTheme) { if (previousTheme) {
let customStylesheets = previousTheme.get_custom_stylesheets(); let customStylesheets = previousTheme.get_custom_stylesheets();
@ -476,11 +558,6 @@ function _globalKeyPressHandler(actor, event) {
if (event.type() != Clutter.EventType.KEY_PRESS) if (event.type() != Clutter.EventType.KEY_PRESS)
return false; return false;
if (!sessionMode.allowKeybindingsWhenModal) {
if (modalCount > (overview.visible ? 1 : 0))
return false;
}
let symbol = event.get_key_symbol(); let symbol = event.get_key_symbol();
let keyCode = event.get_key_code(); let keyCode = event.get_key_code();
let ignoredModifiers = global.display.get_ignored_modifier_mask(); let ignoredModifiers = global.display.get_ignored_modifier_mask();
@ -489,49 +566,48 @@ function _globalKeyPressHandler(actor, event) {
// This relies on the fact that Clutter.ModifierType is the same as Gdk.ModifierType // This relies on the fact that Clutter.ModifierType is the same as Gdk.ModifierType
let action = global.display.get_keybinding_action(keyCode, modifierState); let action = global.display.get_keybinding_action(keyCode, modifierState);
// Other bindings are only available to the user session when the overview is up and
// no modal dialog is present.
if (global.session_type == Shell.SessionType.USER && (!overview.visible || modalCount > 1))
return false;
// This isn't a Meta.KeyBindingAction yet
if (symbol == Clutter.Super_L || symbol == Clutter.Super_R) {
overview.hide();
return true;
}
if (action == Meta.KeyBindingAction.SWITCH_PANELS) { if (action == Meta.KeyBindingAction.SWITCH_PANELS) {
ctrlAltTabManager.popup(modifierState & Clutter.ModifierType.SHIFT_MASK, ctrlAltTabManager.popup(modifierState & Clutter.ModifierType.SHIFT_MASK,
modifierState); modifierState);
return true; return true;
} }
// None of the other bindings are relevant outside of the user's session
if (global.session_type != Shell.SessionType.USER)
return false;
switch (action) { switch (action) {
// left/right would effectively act as synonyms for up/down if we enabled them; // left/right would effectively act as synonyms for up/down if we enabled them;
// but that could be considered confusing; we also disable them in the main view. // but that could be considered confusing; we also disable them in the main view.
// //
// case Meta.KeyBindingAction.WORKSPACE_LEFT: // case Meta.KeyBindingAction.WORKSPACE_LEFT:
// if (!sessionMode.hasWorkspaces)
// return false;
//
// wm.actionMoveWorkspaceLeft(); // wm.actionMoveWorkspaceLeft();
// return true; // return true;
// case Meta.KeyBindingAction.WORKSPACE_RIGHT: // case Meta.KeyBindingAction.WORKSPACE_RIGHT:
// if (!sessionMode.hasWorkspaces)
// return false;
//
// wm.actionMoveWorkspaceRight(); // wm.actionMoveWorkspaceRight();
// return true; // return true;
case Meta.KeyBindingAction.WORKSPACE_UP: case Meta.KeyBindingAction.WORKSPACE_UP:
if (!sessionMode.hasWorkspaces) wm.actionMoveWorkspaceUp();
return false;
wm.actionMoveWorkspace(Meta.MotionDirection.UP);
return true; return true;
case Meta.KeyBindingAction.WORKSPACE_DOWN: case Meta.KeyBindingAction.WORKSPACE_DOWN:
if (!sessionMode.hasWorkspaces) wm.actionMoveWorkspaceDown();
return false;
wm.actionMoveWorkspace(Meta.MotionDirection.DOWN);
return true; return true;
case Meta.KeyBindingAction.PANEL_RUN_DIALOG: case Meta.KeyBindingAction.PANEL_RUN_DIALOG:
case Meta.KeyBindingAction.COMMAND_2: case Meta.KeyBindingAction.COMMAND_2:
if (!sessionMode.hasRunDialog) getRunDialog().open();
return false;
openRunDialog();
return true; return true;
case Meta.KeyBindingAction.PANEL_MAIN_MENU: case Meta.KeyBindingAction.PANEL_MAIN_MENU:
case Meta.KeyBindingAction.OVERLAY_KEY:
overview.hide(); overview.hide();
return true; return true;
} }
@ -547,10 +623,6 @@ function _findModal(actor) {
return -1; return -1;
} }
function isInModalStack(actor) {
return _findModal(actor) != -1;
}
/** /**
* pushModal: * pushModal:
* @actor: #ClutterActor which will be given keyboard focus * @actor: #ClutterActor which will be given keyboard focus
@ -583,7 +655,6 @@ function pushModal(actor, timestamp, options) {
log('pushModal: invocation of begin_modal failed'); log('pushModal: invocation of begin_modal failed');
return false; return false;
} }
Meta.disable_unredirect_for_screen(global.screen);
} }
global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN); global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN);
@ -592,7 +663,7 @@ function pushModal(actor, timestamp, options) {
let actorDestroyId = actor.connect('destroy', function() { let actorDestroyId = actor.connect('destroy', function() {
let index = _findModal(actor); let index = _findModal(actor);
if (index >= 0) if (index >= 0)
popModal(actor); modalActorFocusStack.splice(index, 1);
}); });
let curFocus = global.stage.get_key_focus(); let curFocus = global.stage.get_key_focus();
let curFocusDestroyId; let curFocusDestroyId;
@ -664,7 +735,6 @@ function popModal(actor, timestamp) {
global.end_modal(timestamp); global.end_modal(timestamp);
global.set_stage_input_mode(Shell.StageInputMode.NORMAL); global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
Meta.enable_unredirect_for_screen(global.screen);
} }
function createLookingGlass() { function createLookingGlass() {
@ -674,11 +744,11 @@ function createLookingGlass() {
return lookingGlass; return lookingGlass;
} }
function openRunDialog() { function getRunDialog() {
if (runDialog == null) { if (runDialog == null) {
runDialog = new RunDialog.RunDialog(); runDialog = new RunDialog.RunDialog();
} }
runDialog.open(); return runDialog;
} }
/** /**
@ -807,8 +877,7 @@ function initializeDeferredWork(actor, callback, props) {
function queueDeferredWork(workId) { function queueDeferredWork(workId) {
let data = _deferredWorkData[workId]; let data = _deferredWorkData[workId];
if (!data) { if (!data) {
let message = 'Invalid work id %d'.format(workId); global.logError('invalid work id ', workId);
logError(new Error(message), message);
return; return;
} }
if (_deferredWorkQueue.indexOf(workId) < 0) if (_deferredWorkQueue.indexOf(workId) < 0)

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,6 @@ const Atk = imports.gi.Atk;
const Params = imports.misc.params; const Params = imports.misc.params;
const Layout = imports.ui.layout;
const Lightbox = imports.ui.lightbox; const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
@ -36,20 +35,17 @@ const ModalDialog = new Lang.Class({
_init: function(params) { _init: function(params) {
params = Params.parse(params, { shellReactive: false, params = Params.parse(params, { shellReactive: false,
styleClass: null, styleClass: null });
parentActor: Main.uiGroup,
shouldFadeIn: true });
this.state = State.CLOSED; this.state = State.CLOSED;
this._hasModal = false; this._hasModal = false;
this._shellReactive = params.shellReactive; this._shellReactive = params.shellReactive;
this._shouldFadeIn = params.shouldFadeIn;
this._group = new St.Widget({ visible: false, this._group = new St.Widget({ visible: false,
x: 0, x: 0,
y: 0, y: 0,
accessible_role: Atk.Role.DIALOG }); accessible_role: Atk.Role.DIALOG });
params.parentActor.add_actor(this._group); Main.uiGroup.add_actor(this._group);
let constraint = new Clutter.BindConstraint({ source: global.stage, let constraint = new Clutter.BindConstraint({ source: global.stage,
coordinate: Clutter.BindCoordinate.ALL }); coordinate: Clutter.BindCoordinate.ALL });
@ -58,17 +54,15 @@ const ModalDialog = new Lang.Class({
this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy)); this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy));
this._actionKeys = {}; this._actionKeys = {};
this._group.connect('key-release-event', Lang.bind(this, this._onKeyReleaseEvent)); this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
this._backgroundBin = new St.Bin(); this._backgroundBin = new St.Bin();
this._monitorConstraint = new Layout.MonitorConstraint();
this._backgroundBin.add_constraint(this._monitorConstraint);
this._group.add_actor(this._backgroundBin); this._group.add_actor(this._backgroundBin);
this.dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog', this._dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog',
vertical: true }); vertical: true });
if (params.styleClass != null) { if (params.styleClass != null) {
this.dialogLayout.add_style_class_name(params.styleClass); this._dialogLayout.add_style_class_name(params.styleClass);
} }
if (!this._shellReactive) { if (!this._shellReactive) {
@ -81,29 +75,29 @@ const ModalDialog = new Lang.Class({
this._eventBlocker = new Clutter.Group({ reactive: true }); this._eventBlocker = new Clutter.Group({ reactive: true });
stack.add_actor(this._eventBlocker); stack.add_actor(this._eventBlocker);
stack.add_actor(this.dialogLayout); stack.add_actor(this._dialogLayout);
} else { } else {
this._backgroundBin.child = this.dialogLayout; this._backgroundBin.child = this._dialogLayout;
} }
this.contentLayout = new St.BoxLayout({ vertical: true }); this.contentLayout = new St.BoxLayout({ vertical: true });
this.dialogLayout.add(this.contentLayout, this._dialogLayout.add(this.contentLayout,
{ x_fill: true, { x_fill: true,
y_fill: true, y_fill: true,
x_align: St.Align.MIDDLE, x_align: St.Align.MIDDLE,
y_align: St.Align.START }); y_align: St.Align.START });
this._buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box', this._buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box',
visible: false, visible: false,
vertical: false }); vertical: false });
this.dialogLayout.add(this._buttonLayout, this._dialogLayout.add(this._buttonLayout,
{ expand: true, { expand: true,
x_align: St.Align.MIDDLE, x_align: St.Align.MIDDLE,
y_align: St.Align.END }); y_align: St.Align.END });
global.focus_manager.add_group(this.dialogLayout); global.focus_manager.add_group(this._dialogLayout);
this._initialKeyFocus = this.dialogLayout; this._initialKeyFocus = this._dialogLayout;
this._initialKeyFocusDestroyId = 0; this._initialKeyFocusDestroyId = 0;
this._savedKeyFocus = null; this._savedKeyFocus = null;
}, },
@ -112,10 +106,6 @@ const ModalDialog = new Lang.Class({
this._group.destroy(); this._group.destroy();
}, },
setActionKey: function(key, action) {
this._actionKeys[key] = action;
},
setButtons: function(buttons) { setButtons: function(buttons) {
let hadChildren = this._buttonLayout.get_children() > 0; let hadChildren = this._buttonLayout.get_children() > 0;
@ -129,20 +119,11 @@ const ModalDialog = new Lang.Class({
let label = buttonInfo['label']; let label = buttonInfo['label'];
let action = buttonInfo['action']; let action = buttonInfo['action'];
let key = buttonInfo['key']; let key = buttonInfo['key'];
let isDefault = buttonInfo['default'];
if (isDefault && !key) {
this._actionKeys[Clutter.KEY_KP_Enter] = action;
this._actionKeys[Clutter.KEY_ISO_Enter] = action;
key = Clutter.KEY_Return;
}
buttonInfo.button = new St.Button({ style_class: 'modal-dialog-button', buttonInfo.button = new St.Button({ style_class: 'modal-dialog-button',
reactive: true, reactive: true,
can_focus: true, can_focus: true,
label: label }); label: label });
if (isDefault)
buttonInfo.button.add_style_pseudo_class('default');
let x_alignment; let x_alignment;
if (buttons.length == 1) if (buttons.length == 1)
@ -186,38 +167,34 @@ const ModalDialog = new Lang.Class({
}, },
_onKeyReleaseEvent: function(object, event) { _onKeyPressEvent: function(object, keyPressEvent) {
let symbol = event.get_key_symbol(); let symbol = keyPressEvent.get_key_symbol();
let action = this._actionKeys[symbol]; let action = this._actionKeys[symbol];
if (action) { if (action)
action(); action();
return true;
}
return false;
}, },
_onGroupDestroy: function() { _onGroupDestroy: function() {
this.emit('destroy'); this.emit('destroy');
}, },
_fadeOpen: function(onPrimary) { _fadeOpen: function() {
if (onPrimary) let monitor = Main.layoutManager.focusMonitor;
this._monitorConstraint.primary = true;
else this._backgroundBin.set_position(monitor.x, monitor.y);
this._monitorConstraint.index = global.screen.get_current_monitor(); this._backgroundBin.set_size(monitor.width, monitor.height);
this.state = State.OPENING; this.state = State.OPENING;
this.dialogLayout.opacity = 255; this._dialogLayout.opacity = 255;
if (this._lightbox) if (this._lightbox)
this._lightbox.show(); this._lightbox.show();
this._group.opacity = 0; this._group.opacity = 0;
this._group.show(); this._group.show();
Tweener.addTween(this._group, Tweener.addTween(this._group,
{ opacity: 255, { opacity: 255,
time: this._shouldFadeIn ? OPEN_AND_CLOSE_TIME : 0, time: OPEN_AND_CLOSE_TIME,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: Lang.bind(this, onComplete: Lang.bind(this,
function() { function() {
@ -234,19 +211,19 @@ const ModalDialog = new Lang.Class({
this._initialKeyFocus = actor; this._initialKeyFocus = actor;
this._initialKeyFocusDestroyId = actor.connect('destroy', Lang.bind(this, function() { this._initialKeyFocusDestroyId = actor.connect('destroy', Lang.bind(this, function() {
this._initialKeyFocus = this.dialogLayout; this._initialKeyFocus = this._dialogLayout;
this._initialKeyFocusDestroyId = 0; this._initialKeyFocusDestroyId = 0;
})); }));
}, },
open: function(timestamp, onPrimary) { open: function(timestamp) {
if (this.state == State.OPENED || this.state == State.OPENING) if (this.state == State.OPENED || this.state == State.OPENING)
return true; return true;
if (!this.pushModal(timestamp)) if (!this.pushModal(timestamp))
return false; return false;
this._fadeOpen(onPrimary); this._fadeOpen();
return true; return true;
}, },
@ -327,7 +304,7 @@ const ModalDialog = new Lang.Class({
return; return;
this.popModal(timestamp); this.popModal(timestamp);
Tweener.addTween(this.dialogLayout, Tweener.addTween(this._dialogLayout,
{ opacity: 0, { opacity: 0,
time: FADE_OUT_DIALOG_TIME, time: FADE_OUT_DIALOG_TIME,
transition: 'easeOutQuad', transition: 'easeOutQuad',

View File

@ -1,4 +1,23 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/*
* Copyright 2011 Giovanni Campagna <scampa.giovanni@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
*/
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
@ -116,10 +135,7 @@ const NetworkSecretDialog = new Lang.Class({
} else } else
secret.valid = true; secret.valid = true;
secretTable.add(label, { row: pos, col: 0, secretTable.add(label, { row: pos, col: 0, x_expand: false, x_fill: true, x_align: St.Align.START, y_align: St.Align.START });
x_expand: false, x_fill: true,
x_align: St.Align.START,
y_fill: false, y_align: St.Align.MIDDLE });
secretTable.add(secret.entry, { row: pos, col: 1, x_expand: true, x_fill: true, y_align: St.Align.END }); secretTable.add(secret.entry, { row: pos, col: 1, x_expand: true, x_fill: true, y_align: St.Align.END });
pos++; pos++;
@ -131,7 +147,7 @@ const NetworkSecretDialog = new Lang.Class({
this._okButton = { label: _("Connect"), this._okButton = { label: _("Connect"),
action: Lang.bind(this, this._onOk), action: Lang.bind(this, this._onOk),
default: true key: Clutter.KEY_Return,
}; };
this.setButtons([{ label: _("Cancel"), this.setButtons([{ label: _("Cancel"),
@ -150,6 +166,10 @@ const NetworkSecretDialog = new Lang.Class({
this._okButton.button.reactive = valid; this._okButton.button.reactive = valid;
this._okButton.button.can_focus = valid; this._okButton.button.can_focus = valid;
if (valid)
this._okButton.button.remove_style_pseudo_class('disabled');
else
this._okButton.button.add_style_pseudo_class('disabled');
}, },
_onOk: function() { _onOk: function() {
@ -408,10 +428,7 @@ const VPNRequestHandler = new Lang.Class({
} }
}, },
cancel: function(respond) { cancel: function() {
if (respond)
this._agent.respond(this._requestId, Shell.NetworkAgentResponse.USER_CANCELED);
if (this._newStylePlugin && this._shellDialog) { if (this._newStylePlugin && this._shellDialog) {
this._shellDialog.close(global.get_current_time()); this._shellDialog.close(global.get_current_time());
this._shellDialog.destroy(); this._shellDialog.destroy();
@ -587,7 +604,7 @@ const NetworkAgent = new Lang.Class({
Name: 'NetworkAgent', Name: 'NetworkAgent',
_init: function() { _init: function() {
this._native = new Shell.NetworkAgent({ auto_register: false, this._native = new Shell.NetworkAgent({ auto_register: true,
identifier: 'org.gnome.Shell.NetworkAgent' }); identifier: 'org.gnome.Shell.NetworkAgent' });
this._dialogs = { }; this._dialogs = { };
@ -597,24 +614,6 @@ const NetworkAgent = new Lang.Class({
this._native.connect('cancel-request', Lang.bind(this, this._cancelRequest)); this._native.connect('cancel-request', Lang.bind(this, this._cancelRequest));
}, },
enable: function() {
this._native.register();
},
disable: function() {
let requestId;
for (requestId in this._dialogs)
this._dialogs[requestId].cancel();
this._dialogs = { };
for (requestId in this._vpnRequests)
this._vpnRequests[requestId].cancel(true);
this._vpnRequests = { };
this._native.unregister();
},
_newRequest: function(agent, requestId, connection, settingName, hints, flags) { _newRequest: function(agent, requestId, connection, settingName, hints, flags) {
if (settingName == 'vpn') { if (settingName == 'vpn') {
this._vpnRequest(requestId, connection, hints, flags); this._vpnRequest(requestId, connection, hints, flags);
@ -635,7 +634,7 @@ const NetworkAgent = new Lang.Class({
this._dialogs[requestId].destroy(); this._dialogs[requestId].destroy();
delete this._dialogs[requestId]; delete this._dialogs[requestId];
} else if (this._vpnRequests[requestId]) { } else if (this._vpnRequests[requestId]) {
this._vpnRequests[requestId].cancel(false); this._vpnRequests[requestId].cancel();
delete this._vpnRequests[requestId]; delete this._vpnRequests[requestId];
} }
}, },
@ -684,10 +683,7 @@ const NetworkAgent = new Lang.Class({
try { try {
externalUIMode = keyfile.get_boolean('GNOME', 'supports-external-ui-mode'); externalUIMode = keyfile.get_boolean('GNOME', 'supports-external-ui-mode');
} catch(e) { } // ignore errors if key does not exist } catch(e) { } // ignore errors if key does not exist
let path = binary; let path = GLib.build_filenamev([Config.LIBEXECDIR, binary]);
if (!GLib.path_is_absolute(path)) {
path = GLib.build_filenamev([Config.LIBEXECDIR, path]);
}
if (GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE)) if (GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE))
this._vpnBinaries[service] = { fileName: path, externalUIMode: externalUIMode }; this._vpnBinaries[service] = { fileName: path, externalUIMode: externalUIMode };
@ -704,4 +700,3 @@ const NetworkAgent = new Lang.Class({
} }
} }
}); });
const Component = NetworkAgent;

View File

@ -1,7 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Lang = imports.lang; const Lang = imports.lang;
@ -88,21 +87,6 @@ const rewriteRules = {
] ]
}; };
const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
'bluetooth-applet': 'bluetooth',
'gnome-volume-control-applet': 'volume', // renamed to gnome-sound-applet
// when moved to control center
'gnome-sound-applet': 'volume',
'nm-applet': 'network',
'gnome-power-manager': 'battery',
'keyboard': 'keyboard',
'a11y-keyboard': 'a11y',
'kbd-scrolllock': 'keyboard',
'kbd-numlock': 'keyboard',
'kbd-capslock': 'keyboard',
'ibus-ui-gtk': 'keyboard'
};
const NotificationDaemon = new Lang.Class({ const NotificationDaemon = new Lang.Class({
Name: 'NotificationDaemon', Name: 'NotificationDaemon',
@ -115,19 +99,18 @@ const NotificationDaemon = new Lang.Class({
this._notifications = {}; this._notifications = {};
this._busProxy = new Bus(); this._busProxy = new Bus();
this._trayManager = new Shell.TrayManager(); Main.statusIconDispatcher.connect('message-icon-added', Lang.bind(this, this._onTrayIconAdded));
this._trayIconAddedId = this._trayManager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded)); Main.statusIconDispatcher.connect('message-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
this._trayIconRemovedId = this._trayManager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
Shell.WindowTracker.get_default().connect('notify::focus-app', Shell.WindowTracker.get_default().connect('notify::focus-app',
Lang.bind(this, this._onFocusAppChanged)); Lang.bind(this, this._onFocusAppChanged));
Main.overview.connect('hidden', Main.overview.connect('hidden',
Lang.bind(this, this._onFocusAppChanged)); Lang.bind(this, this._onFocusAppChanged));
this._trayManager.manage_stage(global.stage, Main.messageTray.actor);
}, },
_iconForNotificationData: function(icon, hints) { _iconForNotificationData: function(icon, hints, size) {
let textureCache = St.TextureCache.get_default();
// If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon // If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon
// and don't show a large image. There are currently many applications that use // and don't show a large image. There are currently many applications that use
// notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets // notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets
@ -138,18 +121,20 @@ const NotificationDaemon = new Lang.Class({
// a large image. // a large image.
if (icon) { if (icon) {
if (icon.substr(0, 7) == 'file://') if (icon.substr(0, 7) == 'file://')
return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) }); return textureCache.load_uri_async(icon, size, size);
else if (icon[0] == '/') { else if (icon[0] == '/') {
return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) }); let uri = GLib.filename_to_uri(icon, null);
return textureCache.load_uri_async(uri, size, size);
} else } else
return new Gio.ThemedIcon({ name: icon }); return new St.Icon({ icon_name: icon,
icon_type: St.IconType.FULLCOLOR,
icon_size: size });
} else if (hints['image-data']) { } else if (hints['image-data']) {
let [width, height, rowStride, hasAlpha, let [width, height, rowStride, hasAlpha,
bitsPerSample, nChannels, data] = hints['image-data']; bitsPerSample, nChannels, data] = hints['image-data'];
return Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha, return textureCache.load_from_raw(data, hasAlpha, width, height, rowStride, size);
bitsPerSample, width, height, rowStride);
} else if (hints['image-path']) { } else if (hints['image-path']) {
return new Gio.FileIcon({ file: Gio.File.new_for_path(hints['image-path']) }); return textureCache.load_uri_async(GLib.filename_to_uri(hints['image-path'], null), size, size);
} else { } else {
let stockIcon; let stockIcon;
switch (hints.urgency) { switch (hints.urgency) {
@ -161,7 +146,9 @@ const NotificationDaemon = new Lang.Class({
stockIcon = 'gtk-dialog-error'; stockIcon = 'gtk-dialog-error';
break; break;
} }
return new Gio.ThemedIcon({ name: stockIcon }); return new St.Icon({ icon_name: stockIcon,
icon_type: St.IconType.FULLCOLOR,
icon_size: size });
} }
}, },
@ -234,19 +221,12 @@ const NotificationDaemon = new Lang.Class({
let [appName, replacesId, icon, summary, body, actions, hints, timeout] = params; let [appName, replacesId, icon, summary, body, actions, hints, timeout] = params;
let id; let id;
for (let hint in hints) {
// unpack the variants
hints[hint] = hints[hint].deep_unpack();
}
hints = Params.parse(hints, { urgency: Urgency.NORMAL }, true);
// Filter out chat, presence, calls and invitation notifications from // Filter out chat, presence, calls and invitation notifications from
// Empathy, since we handle that information from telepathyClient.js // Empathy, since we handle that information from telepathyClient.js
if (appName == 'Empathy' && (hints['category'] == 'im.received' || if (appName == 'Empathy' && (hints['category'] == 'im.received' ||
hints['category'] == 'x-empathy.im.room-invitation' || hints['category'] == 'x-empathy.im.room-invitation' ||
hints['category'] == 'x-empathy.call.incoming' || hints['category'] == 'x-empathy.call.incoming' ||
hints['category'] == 'x-empathy.transfer.incoming' || hints['category'] == 'x-empathy.call.incoming"' ||
hints['category'] == 'x-empathy.im.subscription-request' || hints['category'] == 'x-empathy.im.subscription-request' ||
hints['category'] == 'presence.online' || hints['category'] == 'presence.online' ||
hints['category'] == 'presence.offline')) { hints['category'] == 'presence.offline')) {
@ -256,7 +236,6 @@ const NotificationDaemon = new Lang.Class({
Mainloop.idle_add(Lang.bind(this, Mainloop.idle_add(Lang.bind(this,
function () { function () {
this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED); this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED);
return false;
})); }));
return invocation.return_value(GLib.Variant.new('(u)', [id])); return invocation.return_value(GLib.Variant.new('(u)', [id]));
} }
@ -270,6 +249,13 @@ const NotificationDaemon = new Lang.Class({
} }
} }
for (let hint in hints) {
// unpack the variants
hints[hint] = hints[hint].deep_unpack();
}
hints = Params.parse(hints, { urgency: Urgency.NORMAL }, true);
// Be compatible with the various hints for image data and image path // Be compatible with the various hints for image data and image path
// 'image-data' and 'image-path' are the latest name of these hints, introduced in 1.2 // 'image-data' and 'image-path' are the latest name of these hints, introduced in 1.2
@ -355,9 +341,7 @@ const NotificationDaemon = new Lang.Class({
[ndata.id, ndata.icon, ndata.summary, ndata.body, [ndata.id, ndata.icon, ndata.summary, ndata.body,
ndata.actions, ndata.hints, ndata.notification]; ndata.actions, ndata.hints, ndata.notification];
let gicon = this._iconForNotificationData(icon, hints); let iconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE);
let iconActor = new St.Icon({ gicon: gicon,
icon_size: MessageTray.NOTIFICATION_ICON_SIZE });
if (notification == null) { if (notification == null) {
notification = new MessageTray.Notification(source, summary, body, notification = new MessageTray.Notification(source, summary, body,
@ -437,8 +421,8 @@ const NotificationDaemon = new Lang.Class({
// of the 'transient' hint with hints['transient'] rather than hints.transient // of the 'transient' hint with hints['transient'] rather than hints.transient
notification.setTransient(hints['transient'] == true); notification.setTransient(hints['transient'] == true);
let sourceGIcon = source.useNotificationIcon ? this._iconForNotificationData(icon, hints) : null; let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null;
source.processNotification(notification, sourceGIcon); source.processNotification(notification, sourceIconActor);
}, },
CloseNotification: function(id) { CloseNotification: function(id) {
@ -499,11 +483,7 @@ const NotificationDaemon = new Lang.Class({
}, },
_onTrayIconAdded: function(o, icon) { _onTrayIconAdded: function(o, icon) {
let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : ''; let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null, null, icon);
if (STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass] !== undefined)
return;
let source = this._getSource(icon.title || icon.wm_class || C_("program", "Unknown"), icon.pid, null, null, icon);
}, },
_onTrayIconRemoved: function(o, icon) { _onTrayIconRemoved: function(o, icon) {
@ -554,11 +534,11 @@ const Source = new Lang.Class({
this.destroy(); this.destroy();
}, },
processNotification: function(notification, gicon) { processNotification: function(notification, icon) {
if (gicon) if (!this.app)
this._gicon = gicon; this._setApp();
if (!this.trayIcon) if (!this.app && icon)
this.iconUpdated(); this._setSummaryIcon(icon);
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
if (notification.resident && this.app && tracker.focus_app == this.app) if (notification.resident && this.app && tracker.focus_app == this.app)
@ -583,12 +563,18 @@ const Source = new Lang.Class({
this.notifications.length > 0) this.notifications.length > 0)
return false; return false;
let id = global.connect('notify::stage-input-mode', Lang.bind(this, function () { if (Main.overview.visible) {
global.disconnect(id); // We can't just connect to Main.overview's 'hidden' signal,
// because it's emitted *before* it calls popModal()...
let id = global.connect('notify::stage-input-mode', Lang.bind(this,
function () {
global.disconnect(id);
this.trayIcon.click(event);
}));
Main.overview.hide();
} else {
this.trayIcon.click(event); this.trayIcon.click(event);
})); }
Main.overview.hide();
return true; return true;
}, },
@ -620,20 +606,10 @@ const Source = new Lang.Class({
// notification-based icons (ie, not a trayicon) or if it was unset before // notification-based icons (ie, not a trayicon) or if it was unset before
if (!this.trayIcon) { if (!this.trayIcon) {
this.useNotificationIcon = false; this.useNotificationIcon = false;
this.iconUpdated(); this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE));
} }
}, },
setTitle: function(title) {
// Do nothing if .app is set, we don't want to override the
// app name with whatever is provided through libnotify (usually
// garbage)
if (this.app)
return;
this.parent(title);
},
open: function(notification) { open: function(notification) {
this.destroyNonResidentNotifications(); this.destroyNonResidentNotifications();
this.openApp(); this.openApp();
@ -662,20 +638,5 @@ const Source = new Lang.Class({
} }
this.parent(); this.parent();
},
createIcon: function(size) {
if (this.trayIcon) {
return new Clutter.Clone({ width: size,
height: size,
source: this.trayIcon });
} else if (this.app) {
return this.app.create_icon_texture(size);
} else if (this._gicon) {
return new St.Icon({ gicon: this._gicon,
icon_size: size });
} else {
return null;
}
} }
}); });

View File

@ -10,14 +10,21 @@ const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Gdk = imports.gi.Gdk; const Gdk = imports.gi.Gdk;
const AppDisplay = imports.ui.appDisplay;
const ContactDisplay = imports.ui.contactDisplay;
const Dash = imports.ui.dash; const Dash = imports.ui.dash;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main; const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const Params = imports.misc.params; const Params = imports.misc.params;
const PlaceDisplay = imports.ui.placeDisplay;
const RemoteSearch = imports.ui.remoteSearch;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const ViewSelector = imports.ui.viewSelector; const ViewSelector = imports.ui.viewSelector;
const Wanda = imports.ui.wanda;
const WorkspacesView = imports.ui.workspacesView;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail; const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
// Time for initial animation going into Overview mode // Time for initial animation going into Overview mode
@ -70,12 +77,13 @@ const ShellInfo = new Lang.Class({
let notification = null; let notification = null;
if (this._source.notifications.length == 0) { if (this._source.notifications.length == 0) {
notification = new MessageTray.Notification(this._source, text, null); notification = new MessageTray.Notification(this._source, text, null);
notification.setTransient(true);
} else { } else {
notification = this._source.notifications[0]; notification = this._source.notifications[0];
notification.update(text, null, { clear: true }); notification.update(text, null, { clear: true });
} }
notification.setTransient(true);
this._undoCallback = undoCallback; this._undoCallback = undoCallback;
if (undoCallback) { if (undoCallback) {
notification.addButton('system-undo', notification.addButton('system-undo',
@ -91,21 +99,18 @@ const ShellInfo = new Lang.Class({
const Overview = new Lang.Class({ const Overview = new Lang.Class({
Name: 'Overview', Name: 'Overview',
_init: function() { _init : function(params) {
this._overviewCreated = false; params = Params.parse(params, { isDummy: false });
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); this.isDummy = params.isDummy;
this._sessionUpdated();
},
_createOverview: function() { // We only have an overview in user sessions, so
if (this._overviewCreated) // create a dummy overview in other cases
if (this.isDummy) {
this.animationInProgress = false;
this.visible = false;
return; return;
}
if (this.isDummy)
return;
this._overviewCreated = true;
// The main BackgroundActor is inside global.window_group which is // The main BackgroundActor is inside global.window_group which is
// hidden when displaying the overview, so we create a new // hidden when displaying the overview, so we create a new
@ -142,6 +147,8 @@ const Overview = new Lang.Class({
this._capturedEventId = 0; this._capturedEventId = 0;
this._buttonPressId = 0; this._buttonPressId = 0;
this._workspacesDisplay = null;
this.visible = false; // animating to overview, in overview, animating out this.visible = false; // animating to overview, in overview, animating out
this._shown = false; // show() and not hide() this._shown = false; // show() and not hide()
this._shownTemporarily = false; // showTemporarily() and not hideTemporarily() this._shownTemporarily = false; // showTemporarily() and not hideTemporarily()
@ -178,11 +185,6 @@ const Overview = new Lang.Class({
this._needsFakePointerEvent = false; this._needsFakePointerEvent = false;
}, },
_sessionUpdated: function() {
this.isDummy = !Main.sessionMode.hasOverview;
this._createOverview();
},
// The members we construct that are implemented in JS might // The members we construct that are implemented in JS might
// want to access the overview as Main.overview to connect // want to access the overview as Main.overview to connect
// signal handlers and so forth. So we create them after // signal handlers and so forth. So we create them after
@ -193,23 +195,30 @@ const Overview = new Lang.Class({
this._shellInfo = new ShellInfo(); this._shellInfo = new ShellInfo();
this._searchEntry = new St.Entry({ name: 'searchEntry', this._viewSelector = new ViewSelector.ViewSelector();
/* Translators: this is the text displayed
in the search entry when no search is
active; it should not exceed ~30
characters. */
hint_text: _("Type to search..."),
track_hover: true,
can_focus: true });
this._group.add_actor(this._searchEntry);
this._dash = new Dash.Dash();
this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry,
this._dash.showAppsButton);
this._group.add_actor(this._viewSelector.actor); this._group.add_actor(this._viewSelector.actor);
this._group.add_actor(this._dash.actor);
this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay();
this._viewSelector.addViewTab('windows', _("Windows"), this._workspacesDisplay.actor, 'text-x-generic');
let appView = new AppDisplay.AllAppDisplay();
this._viewSelector.addViewTab('applications', _("Applications"), appView.actor, 'system-run');
// Default search providers
// Wanda comes obviously first
this.addSearchProvider(new Wanda.WandaSearchProvider());
this.addSearchProvider(new AppDisplay.AppSearchProvider());
this.addSearchProvider(new AppDisplay.SettingsSearchProvider());
this.addSearchProvider(new PlaceDisplay.PlaceSearchProvider());
this.addSearchProvider(new ContactDisplay.ContactSearchProvider());
// Load remote search providers provided by applications
RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, this.addSearchProvider));
// TODO - recalculate everything when desktop size changes // TODO - recalculate everything when desktop size changes
this._dash = new Dash.Dash();
this._group.add_actor(this._dash.actor);
this._dash.actor.add_constraint(this._viewSelector.constrainY);
this._dash.actor.add_constraint(this._viewSelector.constrainHeight); this._dash.actor.add_constraint(this._viewSelector.constrainHeight);
this.dashIconSize = this._dash.iconSize; this.dashIconSize = this._dash.iconSize;
this._dash.connect('icon-size-changed', this._dash.connect('icon-size-changed',
@ -219,7 +228,7 @@ const Overview = new Lang.Class({
// Translators: this is the name of the dock/favorites area on // Translators: this is the name of the dock/favorites area on
// the left of the overview // the left of the overview
Main.ctrlAltTabManager.addGroup(this._dash.actor, _("Dash"), 'user-bookmarks-symbolic'); Main.ctrlAltTabManager.addGroup(this._dash.actor, _("Dash"), 'user-bookmarks');
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout)); Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout));
this._relayout(); this._relayout();
@ -470,9 +479,7 @@ const Overview = new Lang.Class({
if (windows.length == 0) if (windows.length == 0)
return null; return null;
let window = windows[0]; let clone = new Clutter.Clone({ source: windows[0].get_texture() });
let clone = new Clutter.Clone({ source: window.get_texture(),
x: window.x, y: window.y });
clone.source.connect('destroy', Lang.bind(this, function() { clone.source.connect('destroy', Lang.bind(this, function() {
clone.destroy(); clone.destroy();
})); }));
@ -497,13 +504,13 @@ const Overview = new Lang.Class({
this._coverPane.set_position(0, contentY); this._coverPane.set_position(0, contentY);
this._coverPane.set_size(primary.width, contentHeight); this._coverPane.set_size(primary.width, contentHeight);
let searchWidth = this._searchEntry.get_width();
let searchHeight = this._searchEntry.get_height();
let searchX = (primary.width - searchWidth) / 2;
let searchY = contentY + this._spacing;
let dashWidth = Math.round(DASH_SPLIT_FRACTION * primary.width); let dashWidth = Math.round(DASH_SPLIT_FRACTION * primary.width);
let dashY = searchY + searchHeight + this._spacing; let viewWidth = primary.width - dashWidth - this._spacing;
let viewHeight = contentHeight - 2 * this._spacing;
let viewY = contentY + this._spacing;
let viewX = rtl ? 0 : dashWidth + this._spacing;
// Set the dash's x position - y is handled by a constraint
let dashX; let dashX;
if (rtl) { if (rtl) {
this._dash.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST); this._dash.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
@ -511,14 +518,8 @@ const Overview = new Lang.Class({
} else { } else {
dashX = 0; dashX = 0;
} }
this._dash.actor.set_x(dashX);
let viewX = rtl ? 0 : dashWidth + this._spacing;
let viewY = searchY + searchHeight + this._spacing;
let viewWidth = primary.width - dashWidth - this._spacing;
let viewHeight = contentHeight - this._spacing - viewY;
this._searchEntry.set_position(searchX, searchY);
this._dash.actor.set_position(dashX, dashY);
this._viewSelector.actor.set_position(viewX, viewY); this._viewSelector.actor.set_position(viewX, viewY);
this._viewSelector.actor.set_size(viewWidth, viewHeight); this._viewSelector.actor.set_size(viewWidth, viewHeight);
}, },
@ -568,28 +569,6 @@ const Overview = new Lang.Class({
Lang.bind(this, this._onButtonPress)); Lang.bind(this, this._onButtonPress));
}, },
fadeInDesktop: function() {
this._desktopFade.opacity = 0;
this._desktopFade.show();
Tweener.addTween(this._desktopFade,
{ opacity: 255,
time: ANIMATION_TIME,
transition: 'easeOutQuad' });
},
fadeOutDesktop: function() {
if (!this._desktopFade.child)
this._desktopFade.child = this._getDesktopClone();
this._desktopFade.opacity = 255;
this._desktopFade.show();
Tweener.addTween(this._desktopFade,
{ opacity: 0,
time: ANIMATION_TIME,
transition: 'easeOutQuad'
});
},
_animateVisible: function() { _animateVisible: function() {
if (this.visible || this.animationInProgress) if (this.visible || this.animationInProgress)
return; return;
@ -610,7 +589,21 @@ const Overview = new Lang.Class({
global.window_group.hide(); global.window_group.hide();
this._group.show(); this._group.show();
this._background.show(); this._background.show();
this._viewSelector.show();
this._workspacesDisplay.show();
if (!this._desktopFade.child)
this._desktopFade.child = this._getDesktopClone();
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) {
this._desktopFade.opacity = 255;
this._desktopFade.show();
Tweener.addTween(this._desktopFade,
{ opacity: 0,
time: ANIMATION_TIME,
transition: 'easeOutQuad'
});
}
this._group.opacity = 0; this._group.opacity = 0;
Tweener.addTween(this._group, Tweener.addTween(this._group,
@ -737,7 +730,16 @@ const Overview = new Lang.Class({
this.animationInProgress = true; this.animationInProgress = true;
this._hideInProgress = true; this._hideInProgress = true;
this._viewSelector.zoomFromOverview(); if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) {
this._desktopFade.opacity = 0;
this._desktopFade.show();
Tweener.addTween(this._desktopFade,
{ opacity: 255,
time: ANIMATION_TIME,
transition: 'easeOutQuad' });
}
this._workspacesDisplay.zoomFromOverview();
// Make other elements fade out. // Make other elements fade out.
Tweener.addTween(this._group, Tweener.addTween(this._group,
@ -779,7 +781,8 @@ const Overview = new Lang.Class({
global.window_group.show(); global.window_group.show();
this._viewSelector.hide(); this._workspacesDisplay.hide();
this._desktopFade.hide(); this._desktopFade.hide();
this._background.hide(); this._background.hide();
this._group.hide(); this._group.hide();

View File

@ -14,7 +14,6 @@ const St = imports.gi.St;
const Signals = imports.signals; const Signals = imports.signals;
const Atk = imports.gi.Atk; const Atk = imports.gi.Atk;
const Config = imports.misc.config; const Config = imports.misc.config;
const CtrlAltTab = imports.ui.ctrlAltTab; const CtrlAltTab = imports.ui.ctrlAltTab;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
@ -22,6 +21,7 @@ const Layout = imports.ui.layout;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const DateMenu = imports.ui.dateMenu;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
@ -32,6 +32,33 @@ const BUTTON_DND_ACTIVATION_TIMEOUT = 250;
const ANIMATED_ICON_UPDATE_TIMEOUT = 100; const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
const SPINNER_ANIMATION_TIME = 0.2; const SPINNER_ANIMATION_TIME = 0.2;
const STANDARD_STATUS_AREA_ORDER = ['a11y', 'keyboard', 'volume', 'bluetooth', 'network', 'battery', 'userMenu'];
const STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION = {
'a11y': imports.ui.status.accessibility.ATIndicator,
'volume': imports.ui.status.volume.Indicator,
'battery': imports.ui.status.power.Indicator,
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
'userMenu': imports.ui.userMenu.UserMenuButton
};
if (Config.HAVE_BLUETOOTH)
STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION['bluetooth'] = imports.ui.status.bluetooth.Indicator;
try {
STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION['network'] = imports.ui.status.network.NMApplet;
} catch(e) {
log('NMApplet is not supported. It is possible that your NetworkManager version is too old');
}
const GDM_STATUS_AREA_ORDER = ['a11y', 'display', 'keyboard', 'volume', 'battery', 'powerMenu'];
const GDM_STATUS_AREA_SHELL_IMPLEMENTATION = {
'a11y': imports.ui.status.accessibility.ATIndicator,
'volume': imports.ui.status.volume.Indicator,
'battery': imports.ui.status.power.Indicator,
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
'powerMenu': imports.gdm.powerMenu.PowerMenuButton
};
// To make sure the panel corners blend nicely with the panel, // To make sure the panel corners blend nicely with the panel,
// we draw background and borders the same way, e.g. drawing // we draw background and borders the same way, e.g. drawing
// them as filled shapes from the outside inwards instead of // them as filled shapes from the outside inwards instead of
@ -222,14 +249,14 @@ const AppMenuButton = new Lang.Class({
Name: 'AppMenuButton', Name: 'AppMenuButton',
Extends: PanelMenu.Button, Extends: PanelMenu.Button,
_init: function(panel) { _init: function(menuManager) {
this.parent(0.0, null, true); this.parent(0.0, null, true);
this.actor.accessible_role = Atk.Role.MENU; this.actor.accessible_role = Atk.Role.MENU;
this._startingApps = []; this._startingApps = [];
this._menuManager = panel.menuManager; this._menuManager = menuManager;
this._targetApp = null; this._targetApp = null;
this._appMenuNotifyId = 0; this._appMenuNotifyId = 0;
this._actionGroupNotifyId = 0; this._actionGroupNotifyId = 0;
@ -286,7 +313,7 @@ const AppMenuButton = new Lang.Class({
}, },
show: function() { show: function() {
if (this._visible || Main.screenShield.locked) if (this._visible)
return; return;
this._visible = true; this._visible = true;
@ -347,7 +374,6 @@ const AppMenuButton = new Lang.Class({
return; return;
this._stop = true; this._stop = true;
this.actor.reactive = true;
Tweener.addTween(this._spinner.actor, Tweener.addTween(this._spinner.actor,
{ opacity: 0, { opacity: 0,
time: SPINNER_ANIMATION_TIME, time: SPINNER_ANIMATION_TIME,
@ -362,7 +388,6 @@ const AppMenuButton = new Lang.Class({
startAnimation: function() { startAnimation: function() {
this._stop = false; this._stop = false;
this.actor.reactive = false;
this._spinner.actor.show(); this._spinner.actor.show();
}, },
@ -601,8 +626,8 @@ const ActivitiesButton = new Lang.Class({
this.actor.label_actor = this._label; this.actor.label_actor = this._label;
this.hotCorner = new Layout.HotCorner(); this._hotCorner = new Layout.HotCorner();
container.add_actor(this.hotCorner.actor); container.add_actor(this._hotCorner.actor);
// Hack up our menu... // Hack up our menu...
this.menu.open = Lang.bind(this, this._onMenuOpenRequest); this.menu.open = Lang.bind(this, this._onMenuOpenRequest);
@ -652,10 +677,10 @@ const ActivitiesButton = new Lang.Class({
} }
hotBox.x1 = Math.round(x); hotBox.x1 = Math.round(x);
hotBox.x2 = hotBox.x1 + this.hotCorner.actor.width; hotBox.x2 = hotBox.x1 + this._hotCorner.actor.width;
hotBox.y1 = Math.round(y); hotBox.y1 = Math.round(y);
hotBox.y2 = hotBox.y1 + this.hotCorner.actor.height; hotBox.y2 = hotBox.y1 + this._hotCorner.actor.height;
this.hotCorner.actor.allocate(hotBox, flags); this._hotCorner.actor.allocate(hotBox, flags);
}, },
handleDragOver: function(source, actor, x, y, time) { handleDragOver: function(source, actor, x, y, time) {
@ -677,7 +702,7 @@ const ActivitiesButton = new Lang.Class({
_onCapturedEvent: function(actor, event) { _onCapturedEvent: function(actor, event) {
if (event.type() == Clutter.EventType.BUTTON_PRESS) { if (event.type() == Clutter.EventType.BUTTON_PRESS) {
if (!this.hotCorner.shouldToggleOverviewOnClick()) if (!this._hotCorner.shouldToggleOverviewOnClick())
return true; return true;
} }
return false; return false;
@ -754,11 +779,10 @@ const PanelCorner = new Lang.Class({
return null; return null;
// Start at the back and work backward // Start at the back and work backward
let index; let index = children.length - 1;
for (index = children.length - 1; index >= 0; index--) { while (!children[index].visible && index >= 0)
if (children[index].visible) index--;
break;
}
if (index < 0) if (index < 0)
return null; return null;
@ -779,11 +803,10 @@ const PanelCorner = new Lang.Class({
return null; return null;
// Start at the front and work forward // Start at the front and work forward
let index; let index = 0;
for (index = 0; index < children.length; index++) { while (!children[index].visible && index < children.length)
if (children[index].visible) index++;
break;
}
if (index == children.length) if (index == children.length)
return null; return null;
@ -893,29 +916,6 @@ const PanelCorner = new Lang.Class({
} }
}); });
const PANEL_ITEM_IMPLEMENTATIONS = {
'activities': ActivitiesButton,
'appMenu': AppMenuButton,
'dateMenu': imports.ui.dateMenu.DateMenuButton,
'a11y': imports.ui.status.accessibility.ATIndicator,
'volume': imports.ui.status.volume.Indicator,
'battery': imports.ui.status.power.Indicator,
'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
'powerMenu': imports.gdm.powerMenu.PowerMenuButton,
'userMenu': imports.ui.userMenu.UserMenuButton
};
if (Config.HAVE_BLUETOOTH)
PANEL_ITEM_IMPLEMENTATIONS['bluetooth'] =
imports.ui.status.bluetooth.Indicator;
try {
PANEL_ITEM_IMPLEMENTATIONS['network'] =
imports.ui.status.network.NMApplet;
} catch(e) {
log('NMApplet is not supported. It is possible that your NetworkManager version is too old');
}
const Panel = new Lang.Class({ const Panel = new Lang.Class({
Name: 'Panel', Name: 'Panel',
@ -925,7 +925,7 @@ const Panel = new Lang.Class({
reactive: true }); reactive: true });
this.actor._delegate = this; this.actor._delegate = this;
this.statusArea = {}; this._statusArea = {};
Main.overview.connect('shown', Lang.bind(this, function () { Main.overview.connect('shown', Lang.bind(this, function () {
this.actor.add_style_class_name('in-overview'); this.actor.add_style_class_name('in-overview');
@ -934,7 +934,7 @@ const Panel = new Lang.Class({
this.actor.remove_style_class_name('in-overview'); this.actor.remove_style_class_name('in-overview');
})); }));
this.menuManager = new PopupMenu.PopupMenuManager(this); this._menus = new PopupMenu.PopupMenuManager(this);
this._leftBox = new St.BoxLayout({ name: 'panelLeft' }); this._leftBox = new St.BoxLayout({ name: 'panelLeft' });
this.actor.add_actor(this._leftBox); this.actor.add_actor(this._leftBox);
@ -961,12 +961,43 @@ const Panel = new Lang.Class({
this.actor.connect('allocate', Lang.bind(this, this._allocate)); this.actor.connect('allocate', Lang.bind(this, this._allocate));
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress)); this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
Main.layoutManager.panelBox.add(this.actor); /* Button on the left side of the panel. */
Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar"), 'start-here-symbolic', if (global.session_type == Shell.SessionType.USER) {
{ sortGroup: CtrlAltTab.SortGroup.TOP }); this._activitiesButton = new ActivitiesButton();
this._activities = this._activitiesButton.actor;
this._leftBox.add(this._activities);
Main.sessionMode.connect('updated', Lang.bind(this, this._updatePanel)); // The activities button has a pretend menu, so as to integrate
this._updatePanel(); // more cleanly with the rest of the panel
this._menus.addMenu(this._activitiesButton.menu);
this._appMenu = new AppMenuButton(this._menus);
this._leftBox.add(this._appMenu.actor);
}
/* center */
if (global.session_type == Shell.SessionType.USER)
this._dateMenu = new DateMenu.DateMenuButton({ showEvents: true });
else
this._dateMenu = new DateMenu.DateMenuButton({ showEvents: false });
this._centerBox.add(this._dateMenu.actor, { y_fill: true });
this._menus.addMenu(this._dateMenu.menu);
/* right */
if (global.session_type == Shell.SessionType.GDM) {
this._status_area_order = GDM_STATUS_AREA_ORDER;
this._status_area_shell_implementation = GDM_STATUS_AREA_SHELL_IMPLEMENTATION;
} else {
this._status_area_order = STANDARD_STATUS_AREA_ORDER;
this._status_area_shell_implementation = STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION;
}
Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded));
Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
Main.layoutManager.panelBox.add(this.actor);
Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar"), 'start-here',
{ sortGroup: CtrlAltTab.SortGroup.TOP });
}, },
_getPreferredWidth: function(actor, forHeight, alloc) { _getPreferredWidth: function(actor, forHeight, alloc) {
@ -1026,19 +1057,16 @@ const Panel = new Lang.Class({
} }
this._rightBox.allocate(childBox, flags); this._rightBox.allocate(childBox, flags);
let cornerMinWidth, cornerMinHeight; let [cornerMinWidth, cornerWidth] = this._leftCorner.actor.get_preferred_width(-1);
let cornerWidth, cornerHeight; let [cornerMinHeight, cornerHeight] = this._leftCorner.actor.get_preferred_width(-1);
[cornerMinWidth, cornerWidth] = this._leftCorner.actor.get_preferred_width(-1);
[cornerMinHeight, cornerHeight] = this._leftCorner.actor.get_preferred_height(-1);
childBox.x1 = 0; childBox.x1 = 0;
childBox.x2 = cornerWidth; childBox.x2 = cornerWidth;
childBox.y1 = allocHeight; childBox.y1 = allocHeight;
childBox.y2 = allocHeight + cornerHeight; childBox.y2 = allocHeight + cornerHeight;
this._leftCorner.actor.allocate(childBox, flags); this._leftCorner.actor.allocate(childBox, flags);
[cornerMinWidth, cornerWidth] = this._rightCorner.actor.get_preferred_width(-1); let [cornerMinWidth, cornerWidth] = this._rightCorner.actor.get_preferred_width(-1);
[cornerMinHeight, cornerHeight] = this._rightCorner.actor.get_preferred_height(-1); let [cornerMinHeight, cornerHeight] = this._rightCorner.actor.get_preferred_width(-1);
childBox.x1 = allocWidth - cornerWidth; childBox.x1 = allocWidth - cornerWidth;
childBox.x2 = allocWidth; childBox.x2 = allocWidth;
childBox.y1 = allocHeight; childBox.y1 = allocHeight;
@ -1086,126 +1114,84 @@ const Panel = new Lang.Class({
}, },
openAppMenu: function() { openAppMenu: function() {
let indicator = this.statusArea.appMenu; let menu = this._appMenu.menu;
if (!indicator) // appMenu not supported by current session mode if (Main.overview.visible || menu.isOpen)
return; return;
let menu = indicator.menu;
if (!indicator.actor.reactive || menu.isOpen)
return;
menu.open(); menu.open();
menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
}, },
set boxOpacity(value) { startStatusArea: function() {
let isReactive = value > 0; for (let i = 0; i < this._status_area_order.length; i++) {
let role = this._status_area_order[i];
this._leftBox.opacity = value; let constructor = this._status_area_shell_implementation[role];
this._leftBox.reactive = isReactive;
this._centerBox.opacity = value;
this._centerBox.reactive = isReactive;
this._rightBox.opacity = value;
this._rightBox.reactive = isReactive;
},
get boxOpacity() {
return this._leftBox.opacity;
},
_updatePanel: function() {
let panel = Main.sessionMode.panel;
this._hideIndicators();
this._updateBox(panel.left, this._leftBox);
this._updateBox(panel.center, this._centerBox);
this._updateBox(panel.right, this._rightBox);
},
_initBox: function(elements, box) {
for (let i = 0; i < elements.length; i++) {
let role = elements[i];
let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
if (!constructor) {
// panel icon is not supported (can happen for
// bluetooth or network)
continue;
}
}
},
_hideIndicators: function() {
for (let role in PANEL_ITEM_IMPLEMENTATIONS) {
let indicator = this.statusArea[role];
if (!indicator)
continue;
if (indicator.menu)
indicator.menu.close();
indicator.container.hide();
}
},
_ensureIndicator: function(role) {
let indicator = this.statusArea[role];
if (!indicator) {
let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
if (!constructor) { if (!constructor) {
// This icon is not implemented (this is a bug) // This icon is not implemented (this is a bug)
return null;
}
indicator = new constructor(this);
this.statusArea[role] = indicator;
}
return indicator;
},
_updateBox: function(elements, box) {
let nChildren = box.get_n_children();
for (let i = 0; i < elements.length; i++) {
let role = elements[i];
let indicator = this._ensureIndicator(role);
if (indicator == null)
continue; continue;
}
this._addToPanelBox(role, indicator, i + nChildren, box); let indicator = new constructor();
this.addToStatusArea(role, indicator, i);
} }
}, },
_addToPanelBox: function(role, indicator, position, box) { _insertStatusItem: function(actor, position) {
let container = indicator.container; let children = this._rightBox.get_children();
container.show(); let i;
for (i = children.length - 1; i >= 0; i--) {
let parent = container.get_parent(); let rolePosition = children[i]._rolePosition;
if (parent) if (position > rolePosition) {
parent.remove_actor(container); this._rightBox.insert_child_at_index(actor, i + 1);
break;
box.insert_child_at_index(container, position); }
if (indicator.menu) }
this.menuManager.addMenu(indicator.menu); if (i == -1) {
this.statusArea[role] = indicator; // If we didn't find a position, we must be first
let destroyId = indicator.connect('destroy', Lang.bind(this, function(emitter) { this._rightBox.insert_child_at_index(actor, 0);
delete this.statusArea[role]; }
emitter.disconnect(destroyId); actor._rolePosition = position;
container.destroy();
}));
}, },
addToStatusArea: function(role, indicator, position, box) { addToStatusArea: function(role, indicator, position) {
if (this.statusArea[role]) if (this._statusArea[role])
throw new Error('Extension point conflict: there is already a status indicator for role ' + role); throw new Error('Extension point conflict: there is already a status indicator for role ' + role);
if (!(indicator instanceof PanelMenu.Button)) if (!(indicator instanceof PanelMenu.Button))
throw new TypeError('Status indicator must be an instance of PanelMenu.Button'); throw new TypeError('Status indicator must be an instance of PanelMenu.Button');
position = position || 0; if (!position)
let boxes = { position = 0;
left: this._leftBox, this._insertStatusItem(indicator.actor, position);
center: this._centerBox, this._menus.addMenu(indicator.menu);
right: this._rightBox
}; this._statusArea[role] = indicator;
let boxContainer = boxes[box] || this._rightBox; let destroyId = indicator.connect('destroy', Lang.bind(this, function(emitter) {
this.statusArea[role] = indicator; this._statusArea[role] = null;
this._addToPanelBox(role, indicator, position, boxContainer); emitter.disconnect(destroyId);
}));
return indicator; return indicator;
} },
_onTrayIconAdded: function(o, icon, role) {
if (this._status_area_shell_implementation[role]) {
// This icon is legacy, and replaced by a Shell version
// Hide it
return;
}
icon.height = PANEL_ICON_SIZE;
let buttonBox = new PanelMenu.ButtonBox();
let box = buttonBox.actor;
box.add_actor(icon);
this._insertStatusItem(box, this._status_area_order.indexOf(role));
},
_onTrayIconRemoved: function(o, icon) {
let box = icon.get_parent();
if (box && box._delegate instanceof PanelMenu.ButtonBox)
box.destroy();
},
}); });

View File

@ -1,7 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
@ -21,10 +20,6 @@ const ButtonBox = new Lang.Class({
this.actor = new Shell.GenericContainer(params); this.actor = new Shell.GenericContainer(params);
this.actor._delegate = this; this.actor._delegate = this;
this.container = new St.Bin({ y_fill: true,
x_fill: true,
child: this.actor });
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this.actor.connect('allocate', Lang.bind(this, this._allocate)); this.actor.connect('allocate', Lang.bind(this, this._allocate));
@ -119,12 +114,6 @@ const Button = new Lang.Class({
this.setName(nameText); this.setName(nameText);
}, },
setSensitive: function(sensitive) {
this.actor.reactive = sensitive;
this.actor.can_focus = sensitive;
this.actor.track_hover = sensitive;
},
setName: function(text) { setName: function(text) {
if (text != null) { if (text != null) {
// This is the easiest way to provide a accessible name to // This is the easiest way to provide a accessible name to
@ -186,7 +175,8 @@ const Button = new Lang.Class({
_onMenuKeyPress: function(actor, event) { _onMenuKeyPress: function(actor, event) {
let symbol = event.get_key_symbol(); let symbol = event.get_key_symbol();
if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) { if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) {
let group = global.focus_manager.get_group(this.actor); let focusManager = St.FocusManager.get_for_stage(global.stage);
let group = focusManager.get_group(this.actor);
if (group) { if (group) {
let direction = (symbol == Clutter.KEY_Left) ? Gtk.DirectionType.LEFT : Gtk.DirectionType.RIGHT; let direction = (symbol == Clutter.KEY_Left) ? Gtk.DirectionType.LEFT : Gtk.DirectionType.RIGHT;
group.navigate_focus(this.actor, direction, false); group.navigate_focus(this.actor, direction, false);
@ -214,8 +204,7 @@ const Button = new Lang.Class({
destroy: function() { destroy: function() {
this.actor._delegate = null; this.actor._delegate = null;
if (this.menu) this.menu.destroy();
this.menu.destroy();
this.actor.destroy(); this.actor.destroy();
this.emit('destroy'); this.emit('destroy');
@ -235,36 +224,19 @@ const SystemStatusButton = new Lang.Class({
_init: function(iconName, nameText) { _init: function(iconName, nameText) {
this.parent(0.0, nameText); this.parent(0.0, nameText);
this._iconActor = new St.Icon({ icon_name: iconName,
icon_type: St.IconType.SYMBOLIC,
style_class: 'system-status-icon' });
this.actor.add_actor(this._iconActor);
this.actor.add_style_class_name('panel-status-button'); this.actor.add_style_class_name('panel-status-button');
this._box = new St.BoxLayout({ style_class: 'panel-status-button-box' });
this.actor.add_actor(this._box);
if (iconName)
this.setIcon(iconName);
},
addIcon: function(gicon) {
let icon = new St.Icon({ gicon: gicon,
style_class: 'system-status-icon' });
this._box.add_actor(icon);
return icon;
}, },
setIcon: function(iconName) { setIcon: function(iconName) {
// Need to first add a NULL GIcon and then set icon_name, to ensure this._iconActor.icon_name = iconName;
// compatibility with -symbolic fallbacks
if (!this.mainIcon)
this.mainIcon = this.addIcon(null);
this.mainIcon.icon_name = iconName;
}, },
setGIcon: function(gicon) { setGIcon: function(gicon) {
if (this.mainIcon) this._iconActor.gicon = gicon;
this.mainIcon.gicon = gicon;
else
this.mainIcon = this.addIcon(gicon);
} }
}); });

434
js/ui/placeDisplay.js Normal file
View File

@ -0,0 +1,434 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Shell = imports.gi.Shell;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const St = imports.gi.St;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Search = imports.ui.search;
const Util = imports.misc.util;
/**
* Represents a place object, which is most normally a bookmark entry,
* a mount/volume, or a special place like the Home Folder, Computer, and Network.
*
* @name: String title
* @iconFactory: A JavaScript callback which will create an icon texture given a size parameter
* @launch: A JavaScript callback to launch the entry
*/
const PlaceInfo = new Lang.Class({
Name: 'PlaceInfo',
_init: function(id, name, iconFactory, launch) {
this.id = id;
this.name = name;
this._lowerName = name.toLowerCase();
this.iconFactory = iconFactory;
this.launch = launch;
},
matchTerms: function(terms) {
let mtype = Search.MatchType.NONE;
for (let i = 0; i < terms.length; i++) {
let term = terms[i];
let idx = this._lowerName.indexOf(term);
if (idx == 0) {
mtype = Search.MatchType.PREFIX;
} else if (idx > 0) {
if (mtype == Search.MatchType.NONE)
mtype = Search.MatchType.SUBSTRING;
} else {
return Search.MatchType.NONE;
}
}
return mtype;
},
isRemovable: function() {
return false;
}
});
// Helper function to translate launch parameters into a GAppLaunchContext
function _makeLaunchContext(params)
{
params = Params.parse(params, { workspace: -1,
timestamp: 0 });
let launchContext = global.create_app_launch_context();
if (params.workspace != -1)
launchContext.set_desktop(params.workspace);
if (params.timestamp != 0)
launchContext.set_timestamp(params.timestamp);
return launchContext;
}
const PlaceDeviceInfo = new Lang.Class({
Name: 'PlaceDeviceInfo',
Extends: PlaceInfo,
_init: function(mount) {
this._mount = mount;
this.name = mount.get_name();
this._lowerName = this.name.toLowerCase();
this.id = 'mount:' + mount.get_root().get_uri();
},
iconFactory: function(size) {
let icon = this._mount.get_icon();
return St.TextureCache.get_default().load_gicon(null, icon, size);
},
launch: function(params) {
Gio.app_info_launch_default_for_uri(this._mount.get_root().get_uri(),
_makeLaunchContext(params));
},
isRemovable: function() {
return this._mount.can_unmount();
},
remove: function() {
if (!this.isRemovable())
return;
if (this._mount.can_eject())
this._mount.eject(0, null, Lang.bind(this, this._removeFinish));
else
this._mount.unmount(0, null, Lang.bind(this, this._removeFinish));
},
_removeFinish: function(o, res, data) {
try {
if (this._mount.can_eject())
this._mount.eject_finish(res);
else
this._mount.unmount_finish(res);
} catch (e) {
let message = _("Failed to unmount '%s'").format(o.get_name());
Main.overview.setMessage(message,
Lang.bind(this, this.remove),
_("Retry"));
}
}
});
const PlacesManager = new Lang.Class({
Name: 'PlacesManager',
_init: function() {
this._defaultPlaces = [];
this._mounts = [];
this._bookmarks = [];
let homeFile = Gio.file_new_for_path (GLib.get_home_dir());
let homeUri = homeFile.get_uri();
let homeLabel = Shell.util_get_label_for_uri (homeUri);
let homeIcon = Shell.util_get_icon_for_uri (homeUri);
this._home = new PlaceInfo('special:home', homeLabel,
function(size) {
return St.TextureCache.get_default().load_gicon(null, homeIcon, size);
},
function(params) {
Gio.app_info_launch_default_for_uri(homeUri, _makeLaunchContext(params));
});
let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
let desktopFile = Gio.file_new_for_path (desktopPath);
let desktopUri = desktopFile.get_uri();
let desktopLabel = Shell.util_get_label_for_uri (desktopUri);
let desktopIcon = Shell.util_get_icon_for_uri (desktopUri);
this._desktopMenu = new PlaceInfo('special:desktop', desktopLabel,
function(size) {
return St.TextureCache.get_default().load_gicon(null, desktopIcon, size);
},
function(params) {
Gio.app_info_launch_default_for_uri(desktopUri, _makeLaunchContext(params));
});
this._connect = new PlaceInfo('special:connect', _("Connect to..."),
function (size) {
// do NOT use St.Icon here, it crashes the shell
// see wanda.js for details
return St.TextureCache.get_default().load_icon_name(null,
'applications-internet',
St.IconType.FULLCOLOR,
size);
},
function (params) {
// BUG: nautilus-connect-server doesn't have a desktop file, so we can't
// launch it with the workspace from params. It's probably pretty rare
// and odd to drag this place onto a workspace in any case
Util.spawn(['nautilus-connect-server']);
});
this._defaultPlaces.push(this._home);
this._defaultPlaces.push(this._desktopMenu);
this._defaultPlaces.push(this._connect);
/*
* Show devices, code more or less ported from nautilus-places-sidebar.c
*/
this._volumeMonitor = Gio.VolumeMonitor.get();
this._volumeMonitor.connect('volume-added', Lang.bind(this, this._updateDevices));
this._volumeMonitor.connect('volume-removed',Lang.bind(this, this._updateDevices));
this._volumeMonitor.connect('volume-changed', Lang.bind(this, this._updateDevices));
this._volumeMonitor.connect('mount-added', Lang.bind(this, this._updateDevices));
this._volumeMonitor.connect('mount-removed', Lang.bind(this, this._updateDevices));
this._volumeMonitor.connect('mount-changed', Lang.bind(this, this._updateDevices));
this._volumeMonitor.connect('drive-connected', Lang.bind(this, this._updateDevices));
this._volumeMonitor.connect('drive-disconnected', Lang.bind(this, this._updateDevices));
this._volumeMonitor.connect('drive-changed', Lang.bind(this, this._updateDevices));
this._updateDevices();
this._bookmarksPath = GLib.build_filenamev([GLib.get_home_dir(), '.gtk-bookmarks']);
this._bookmarksFile = Gio.file_new_for_path(this._bookmarksPath);
this._monitor = this._bookmarksFile.monitor_file(Gio.FileMonitorFlags.NONE, null);
this._bookmarkTimeoutId = 0;
this._monitor.connect('changed', Lang.bind(this, function () {
if (this._bookmarkTimeoutId > 0)
return;
/* Defensive event compression */
this._bookmarkTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function () {
this._bookmarkTimeoutId = 0;
this._reloadBookmarks();
return false;
}));
}));
this._reloadBookmarks();
},
_updateDevices: function() {
this._mounts = [];
/* first go through all connected drives */
let drives = this._volumeMonitor.get_connected_drives();
for (let i = 0; i < drives.length; i++) {
let volumes = drives[i].get_volumes();
for(let j = 0; j < volumes.length; j++) {
let mount = volumes[j].get_mount();
if(mount != null) {
this._addMount(mount);
}
}
}
/* add all volumes that is not associated with a drive */
let volumes = this._volumeMonitor.get_volumes();
for(let i = 0; i < volumes.length; i++) {
if(volumes[i].get_drive() != null)
continue;
let mount = volumes[i].get_mount();
if(mount != null) {
this._addMount(mount);
}
}
/* add mounts that have no volume (/etc/mtab mounts, ftp, sftp,...) */
let mounts = this._volumeMonitor.get_mounts();
for(let i = 0; i < mounts.length; i++) {
if(mounts[i].is_shadowed())
continue;
if(mounts[i].get_volume())
continue;
this._addMount(mounts[i]);
}
/* We emit two signals, one for a generic 'all places' update
* and the other for one specific to mounts. We do this because
* clients like PlaceDisplay may only care about places in general
* being updated while clients like DashPlaceDisplay care which
* specific type of place got updated.
*/
this.emit('mounts-updated');
this.emit('places-updated');
},
_reloadBookmarks: function() {
this._bookmarks = [];
if (!GLib.file_test(this._bookmarksPath, GLib.FileTest.EXISTS))
return;
let bookmarksContent = Shell.get_file_contents_utf8_sync(this._bookmarksPath);
let bookmarks = bookmarksContent.split('\n');
let bookmarksToLabel = {};
let bookmarksOrder = [];
for (let i = 0; i < bookmarks.length; i++) {
let bookmarkLine = bookmarks[i];
let components = bookmarkLine.split(' ');
let bookmark = components[0];
if (bookmark in bookmarksToLabel)
continue;
let label = null;
if (components.length > 1)
label = components.slice(1).join(' ');
bookmarksToLabel[bookmark] = label;
bookmarksOrder.push(bookmark);
}
for (let i = 0; i < bookmarksOrder.length; i++) {
let bookmark = bookmarksOrder[i];
let label = bookmarksToLabel[bookmark];
let file = Gio.file_new_for_uri(bookmark);
if (!file.query_exists(null))
continue;
if (label == null)
label = Shell.util_get_label_for_uri(bookmark);
if (label == null)
continue;
let icon = Shell.util_get_icon_for_uri(bookmark);
let item = new PlaceInfo('bookmark:' + bookmark, label,
function(size) {
return St.TextureCache.get_default().load_gicon(null, icon, size);
},
function(params) {
Gio.app_info_launch_default_for_uri(bookmark, _makeLaunchContext(params));
});
this._bookmarks.push(item);
}
/* See comment in _updateDevices for explanation why there are two signals. */
this.emit('bookmarks-updated');
this.emit('places-updated');
},
_addMount: function(mount) {
let devItem = new PlaceDeviceInfo(mount);
this._mounts.push(devItem);
},
getAllPlaces: function () {
return this.getDefaultPlaces().concat(this.getBookmarks(), this.getMounts());
},
getDefaultPlaces: function () {
return this._defaultPlaces;
},
getBookmarks: function () {
return this._bookmarks;
},
getMounts: function () {
return this._mounts;
},
_lookupIndexById: function(sourceArray, id) {
for (let i = 0; i < sourceArray.length; i++) {
let place = sourceArray[i];
if (place.id == id)
return i;
}
return -1;
},
lookupPlaceById: function(id) {
let colonIdx = id.indexOf(':');
let type = id.substring(0, colonIdx);
let sourceArray = null;
if (type == 'special')
sourceArray = this._defaultPlaces;
else if (type == 'mount')
sourceArray = this._mounts;
else if (type == 'bookmark')
sourceArray = this._bookmarks;
return sourceArray[this._lookupIndexById(sourceArray, id)];
},
_removeById: function(sourceArray, id) {
sourceArray.splice(this._lookupIndexById(sourceArray, id), 1);
}
});
Signals.addSignalMethods(PlacesManager.prototype);
const PlaceSearchProvider = new Lang.Class({
Name: 'PlaceSearchProvider',
Extends: Search.SearchProvider,
_init: function() {
this.parent(_("PLACES & DEVICES"));
},
getResultMetas: function(resultIds) {
let metas = [];
for (let i = 0; i < resultIds.length; i++) {
let placeInfo = Main.placesManager.lookupPlaceById(resultIds[i]);
if (!placeInfo)
metas.push(null);
else
metas.push({ 'id': resultIds[i],
'name': placeInfo.name,
'createIcon': function(size) {
return placeInfo.iconFactory(size);
}
});
}
return metas;
},
activateResult: function(id, params) {
let placeInfo = Main.placesManager.lookupPlaceById(id);
placeInfo.launch(params);
},
_compareResultMeta: function (idA, idB) {
let infoA = Main.placesManager.lookupPlaceById(idA);
let infoB = Main.placesManager.lookupPlaceById(idB);
return infoA.name.localeCompare(infoB.name);
},
_searchPlaces: function(places, terms) {
let multiplePrefixResults = [];
let prefixResults = [];
let multipleSubstringResults = [];
let substringResults = [];
terms = terms.map(String.toLowerCase);
for (let i = 0; i < places.length; i++) {
let place = places[i];
let mtype = place.matchTerms(terms);
if (mtype == Search.MatchType.MULTIPLE_PREFIX)
multiplePrefixResults.push(place.id);
else if (mtype == Search.MatchType.PREFIX)
prefixResults.push(place.id);
else if (mtype == Search.MatchType.MULTIPLE_SUBSTRING)
multipleSubstringResults.push(place.id);
else if (mtype == Search.MatchType.SUBSTRING)
substringResults.push(place.id);
}
multiplePrefixResults.sort(this._compareResultMeta);
prefixResults.sort(this._compareResultMeta);
multipleSubstringResults.sort(this._compareResultMeta);
substringResults.sort(this._compareResultMeta);
return multiplePrefixResults.concat(prefixResults.concat(multipleSubstringResults.concat(substringResults)));
},
getInitialResultSet: function(terms) {
let places = Main.placesManager.getAllPlaces();
return this._searchPlaces(places, terms);
},
getSubsearchResultSet: function(previousResults, terms) {
let places = previousResults.map(function (id) { return Main.placesManager.lookupPlaceById(id); });
return this._searchPlaces(places, terms);
}
});

View File

@ -1,126 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
// We stop polling if the user is idle for more than this amount of time
const IDLE_TIME = 1000;
// This file implements a reasonably efficient system for tracking the position
// of the mouse pointer. We simply query the pointer from the X server in a loop,
// but we turn off the polling when the user is idle.
let _pointerWatcher = null;
function getPointerWatcher() {
if (_pointerWatcher == null)
_pointerWatcher = new PointerWatcher();
return _pointerWatcher;
}
const PointerWatch = new Lang.Class({
Name: 'PointerWatch',
_init: function(watcher, interval, callback) {
this.watcher = watcher;
this.interval = interval;
this.callback = callback;
},
// remove:
// remove this watch. This function may safely be called
// while the callback is executing.
remove: function() {
this.watcher._removeWatch(this);
}
});
const PointerWatcher = new Lang.Class({
Name: 'PointerWatcher',
_init: function() {
let idleMonitor = Shell.IdleMonitor.get();
idleMonitor.add_watch(IDLE_TIME,
Lang.bind(this, this._onIdleMonitorWatch));
this._idle = idleMonitor.get_idletime() > IDLE_TIME;
this._watches = [];
this.pointerX = null;
this.pointerY = null;
},
// addWatch:
// @interval: hint as to the time resolution needed. When the user is
// not idle, the position of the pointer will be queried at least
// once every this many milliseconds.
// @callback: function to call when the pointer position changes - takes
// two arguments, X and Y.
//
// Set up a watch on the position of the mouse pointer. Returns a
// PointerWatch object which has a remove() method to remove the watch.
addWatch: function(interval, callback) {
// Avoid unreliably calling the watch for the current position
this._updatePointer();
let watch = new PointerWatch(this, interval, callback);
this._watches.push(watch);
this._updateTimeout();
return watch;
},
_removeWatch: function(watch) {
for (let i = 0; i < this._watches.length; i++) {
if (this._watches[i] == watch) {
this._watches.splice(i, 1);
this._updateTimeout();
return;
}
}
},
_onIdleMonitorWatch: function(monitor, id, userBecameIdle) {
this._idle = userBecameIdle;
if (!userBecameIdle)
this._updatePointer();
this._updateTimeout();
},
_updateTimeout: function() {
if (this._timeoutId) {
Mainloop.source_remove(this._timeoutId);
this._timeoutId = 0;
}
if (this._idle || this._watches.length == 0)
return;
let minInterval = this._watches[0].interval;
for (let i = 1; i < this._watches.length; i++)
minInterval = Math.min(this._watches[i].interval, minInterval);
this._timeoutId = Mainloop.timeout_add(minInterval,
Lang.bind(this, this._onTimeout));
},
_onTimeout: function() {
this._updatePointer();
return true;
},
_updatePointer: function() {
let [x, y, mods] = global.get_pointer();
if (this.pointerX == x && this.pointerY == y)
return;
this.pointerX = x;
this.pointerY = y;
for (let i = 0; i < this._watches.length;) {
let watch = this._watches[i];
watch.callback(x, y);
if (watch == this._watches[i]) // guard against self-removal
i++;
}
}
});

View File

@ -1,4 +1,24 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/*
* Copyright 2010 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Author: David Zeuthen <davidz@redhat.com>
*/
const Lang = imports.lang; const Lang = imports.lang;
const Signals = imports.signals; const Signals = imports.signals;
@ -13,12 +33,8 @@ const Mainloop = imports.mainloop;
const Polkit = imports.gi.Polkit; const Polkit = imports.gi.Polkit;
const PolkitAgent = imports.gi.PolkitAgent; const PolkitAgent = imports.gi.PolkitAgent;
const Components = imports.ui.components;
const ModalDialog = imports.ui.modalDialog; const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry; const ShellEntry = imports.ui.shellEntry;
const UserMenu = imports.ui.userMenu;
const DIALOG_ICON_SIZE = 48;
const AuthenticationDialog = new Lang.Class({ const AuthenticationDialog = new Lang.Class({
Name: 'AuthenticationDialog', Name: 'AuthenticationDialog',
@ -49,7 +65,7 @@ const AuthenticationDialog = new Lang.Class({
let messageBox = new St.BoxLayout({ style_class: 'prompt-dialog-message-layout', let messageBox = new St.BoxLayout({ style_class: 'prompt-dialog-message-layout',
vertical: true }); vertical: true });
mainContentBox.add(messageBox, mainContentBox.add(messageBox,
{ expand: true, y_align: St.Align.START }); { y_align: St.Align.START });
this._subjectLabel = new St.Label({ style_class: 'prompt-dialog-headline', this._subjectLabel = new St.Label({ style_class: 'prompt-dialog-headline',
text: _("Authentication Required") }); text: _("Authentication Required") });
@ -101,11 +117,9 @@ const AuthenticationDialog = new Lang.Class({
let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout', let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout',
vertical: false }); vertical: false });
messageBox.add(userBox); messageBox.add(userBox);
this._userAvatar = new UserMenu.UserAvatarWidget(this._user, this._userIcon = new St.Icon();
{ iconSize: DIALOG_ICON_SIZE, this._userIcon.hide();
styleClass: 'polkit-dialog-user-icon' }); userBox.add(this._userIcon,
this._userAvatar.actor.hide();
userBox.add(this._userAvatar.actor,
{ x_fill: true, { x_fill: true,
y_fill: false, y_fill: false,
x_align: St.Align.END, x_align: St.Align.END,
@ -121,17 +135,17 @@ const AuthenticationDialog = new Lang.Class({
this._onUserChanged(); this._onUserChanged();
this._passwordBox = new St.BoxLayout({ vertical: false, style_class: 'prompt-dialog-password-box' }); this._passwordBox = new St.BoxLayout({ vertical: false });
messageBox.add(this._passwordBox); messageBox.add(this._passwordBox);
this._passwordLabel = new St.Label(({ style_class: 'prompt-dialog-password-label' })); 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._passwordBox.add(this._passwordLabel);
this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry', this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
text: "", text: "",
can_focus: true}); can_focus: true});
ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true }); ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivate)); this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivate));
this._passwordBox.add(this._passwordEntry, this._passwordBox.add(this._passwordEntry,
{ expand: true }); {expand: true });
this.setInitialKeyFocus(this._passwordEntry); this.setInitialKeyFocus(this._passwordEntry);
this._passwordBox.hide(); this._passwordBox.hide();
@ -153,7 +167,6 @@ const AuthenticationDialog = new Lang.Class({
*/ */
this._nullMessageLabel = new St.Label({ style_class: 'prompt-dialog-null-label', this._nullMessageLabel = new St.Label({ style_class: 'prompt-dialog-null-label',
text: 'abc'}); text: 'abc'});
this._nullMessageLabel.add_style_class_name('hidden');
this._nullMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; this._nullMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._nullMessageLabel.clutter_text.line_wrap = true; this._nullMessageLabel.clutter_text.line_wrap = true;
messageBox.add(this._nullMessageLabel); messageBox.add(this._nullMessageLabel);
@ -164,8 +177,7 @@ const AuthenticationDialog = new Lang.Class({
key: Clutter.Escape key: Clutter.Escape
}, },
{ label: _("Authenticate"), { label: _("Authenticate"),
action: Lang.bind(this, this._onAuthenticateButtonPressed), action: Lang.bind(this, this._onAuthenticateButtonPressed)
default: true
}]); }]);
this._doneEmitted = false; this._doneEmitted = false;
@ -256,7 +268,7 @@ const AuthenticationDialog = new Lang.Class({
_onSessionRequest: function(session, request, echo_on) { _onSessionRequest: function(session, request, echo_on) {
// Cheap localization trick // Cheap localization trick
if (request == 'Password:' || request == 'Password: ') if (request == 'Password:')
this._passwordLabel.set_text(_("Password:")); this._passwordLabel.set_text(_("Password:"));
else else
this._passwordLabel.set_text(request); this._passwordLabel.set_text(request);
@ -299,9 +311,19 @@ const AuthenticationDialog = new Lang.Class({
}, },
_onUserChanged: function() { _onUserChanged: function() {
if (this._user.is_loaded && this._userAvatar) { if (this._user.is_loaded) {
this._userAvatar.update(); if (this._userIcon) {
this._userAvatar.actor.show(); let iconFileName = this._user.get_icon_file();
let iconFile = Gio.file_new_for_path(iconFileName);
let icon;
if (iconFile.query_exists(null)) {
icon = new Gio.FileIcon({file: iconFile});
} else {
icon = new Gio.ThemedIcon({name: 'avatar-default'});
}
this._userIcon.set_gicon (icon);
this._userIcon.show();
}
} }
}, },
@ -317,20 +339,11 @@ const AuthenticationAgent = new Lang.Class({
Name: 'AuthenticationAgent', Name: 'AuthenticationAgent',
_init: function() { _init: function() {
this._currentDialog = null;
this._isCompleting = false;
this._handle = null;
this._native = new Shell.PolkitAuthenticationAgent(); this._native = new Shell.PolkitAuthenticationAgent();
this._native.connect('initiate', Lang.bind(this, this._onInitiate)); this._native.connect('initiate', Lang.bind(this, this._onInitiate));
this._native.connect('cancel', Lang.bind(this, this._onCancel)); this._native.connect('cancel', Lang.bind(this, this._onCancel));
}, this._currentDialog = null;
this._isCompleting = false;
enable: function() {
this._native.register();
},
disable: function() {
this._native.unregister();
}, },
_onInitiate: function(nativeAgent, actionId, message, iconName, cookie, userNames) { _onInitiate: function(nativeAgent, actionId, message, iconName, cookie, userNames) {
@ -388,4 +401,6 @@ const AuthenticationAgent = new Lang.Class({
} }
}); });
const Component = AuthenticationAgent; function init() {
let agent = new AuthenticationAgent();
}

View File

@ -37,13 +37,12 @@ const PopupBaseMenuItem = new Lang.Class({
activate: true, activate: true,
hover: true, hover: true,
sensitive: true, sensitive: true,
style_class: null, style_class: null
can_focus: true
}); });
this.actor = new Shell.GenericContainer({ style_class: 'popup-menu-item', this.actor = new Shell.GenericContainer({ style_class: 'popup-menu-item',
reactive: params.reactive, reactive: params.reactive,
track_hover: params.reactive, track_hover: params.reactive,
can_focus: params.can_focus, can_focus: params.reactive,
accessible_role: Atk.Role.MENU_ITEM}); accessible_role: Atk.Role.MENU_ITEM});
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
@ -61,9 +60,6 @@ const PopupBaseMenuItem = new Lang.Class({
this.setSensitive(this.sensitive); this.setSensitive(this.sensitive);
if (!this._activatable)
this.actor.add_style_class_name('popup-inactive-menu-item');
if (params.style_class) if (params.style_class)
this.actor.add_style_class_name(params.style_class); this.actor.add_style_class_name(params.style_class);
@ -73,9 +69,10 @@ const PopupBaseMenuItem = new Lang.Class({
} }
if (params.reactive && params.hover) if (params.reactive && params.hover)
this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged)); this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged));
if (params.reactive) {
this.actor.connect('key-focus-in', Lang.bind(this, this._onKeyFocusIn)); this.actor.connect('key-focus-in', Lang.bind(this, this._onKeyFocusIn));
this.actor.connect('key-focus-out', Lang.bind(this, this._onKeyFocusOut)); this.actor.connect('key-focus-out', Lang.bind(this, this._onKeyFocusOut));
}
}, },
_onStyleChanged: function (actor) { _onStyleChanged: function (actor) {
@ -139,6 +136,10 @@ const PopupBaseMenuItem = new Lang.Class({
this.actor.reactive = sensitive; this.actor.reactive = sensitive;
this.actor.can_focus = sensitive; this.actor.can_focus = sensitive;
if (sensitive)
this.actor.remove_style_pseudo_class('insensitive');
else
this.actor.add_style_pseudo_class('insensitive');
this.emit('sensitive-changed', sensitive); this.emit('sensitive-changed', sensitive);
}, },
@ -183,14 +184,12 @@ const PopupBaseMenuItem = new Lang.Class({
this._dot = new St.DrawingArea({ style_class: 'popup-menu-item-dot' }); this._dot = new St.DrawingArea({ style_class: 'popup-menu-item-dot' });
this._dot.connect('repaint', Lang.bind(this, this._onRepaintDot)); this._dot.connect('repaint', Lang.bind(this, this._onRepaintDot));
this.actor.add_actor(this._dot); this.actor.add_actor(this._dot);
this.actor.add_accessible_state (Atk.StateType.CHECKED);
} else { } else {
if (!this._dot) if (!this._dot)
return; return;
this._dot.destroy(); this._dot.destroy();
this._dot = null; this._dot = null;
this.actor.remove_accessible_state (Atk.StateType.CHECKED);
} }
}, },
@ -401,8 +400,7 @@ const PopupSeparatorMenuItem = new Lang.Class({
Extends: PopupBaseMenuItem, Extends: PopupBaseMenuItem,
_init: function () { _init: function () {
this.parent({ reactive: false, this.parent({ reactive: false });
can_focus: false});
this._drawingArea = new St.DrawingArea({ style_class: 'popup-separator-menu-item' }); this._drawingArea = new St.DrawingArea({ style_class: 'popup-separator-menu-item' });
this.addActor(this._drawingArea, { span: -1, expand: true }); this.addActor(this._drawingArea, { span: -1, expand: true });
@ -721,8 +719,7 @@ const Switch = new Lang.Class({
_init: function(state) { _init: function(state) {
this.actor = new St.Bin({ style_class: 'toggle-switch', this.actor = new St.Bin({ style_class: 'toggle-switch',
accessible_role: Atk.Role.CHECK_BOX, accessible_role: Atk.Role.CHECK_BOX});
can_focus: true });
// Translators: this MUST be either "toggle-switch-us" // Translators: this MUST be either "toggle-switch-us"
// (for toggle switches containing the English words // (for toggle switches containing the English words
// "ON" and "OFF") or "toggle-switch-intl" (for toggle // "ON" and "OFF") or "toggle-switch-intl" (for toggle
@ -766,7 +763,7 @@ const PopupSwitchMenuItem = new Lang.Class({
{ expand: true, span: -1, align: St.Align.END }); { expand: true, span: -1, align: St.Align.END });
this._statusLabel = new St.Label({ text: '', this._statusLabel = new St.Label({ text: '',
style_class: 'popup-status-menu-item' style_class: 'popup-inactive-menu-item'
}); });
this._statusBin.child = this._switch.actor; this._statusBin.child = this._switch.actor;
}, },
@ -776,10 +773,12 @@ const PopupSwitchMenuItem = new Lang.Class({
this._statusLabel.text = text; this._statusLabel.text = text;
this._statusBin.child = this._statusLabel; this._statusBin.child = this._statusLabel;
this.actor.reactive = false; this.actor.reactive = false;
this.actor.can_focus = false;
this.actor.accessible_role = Atk.Role.MENU_ITEM; this.actor.accessible_role = Atk.Role.MENU_ITEM;
} else { } else {
this._statusBin.child = this._switch.actor; this._statusBin.child = this._switch.actor;
this.actor.reactive = true; this.actor.reactive = true;
this.actor.can_focus = true;
this.actor.accessible_role = Atk.Role.CHECK_MENU_ITEM; this.actor.accessible_role = Atk.Role.CHECK_MENU_ITEM;
} }
this.checkAccessibleState(); this.checkAccessibleState();
@ -870,15 +869,12 @@ const PopupMenuBase = new Lang.Class({
// for the menu which causes its prelight state to freeze // for the menu which causes its prelight state to freeze
this.blockSourceEvents = false; this.blockSourceEvents = false;
// Can be set while a menu is up to let all events through without special
// menu handling useful for scrollbars in menus, and probably not otherwise.
this.passEvents = false;
this._activeMenuItem = null; this._activeMenuItem = null;
this._childMenus = []; this._childMenus = [];
this._settingsActions = { };
this._sessionUpdatedId = Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
},
_sessionUpdated: function() {
this._setSettingsVisibility(Main.sessionMode.allowSettings);
}, },
addAction: function(title, callback) { addAction: function(title, callback) {
@ -892,6 +888,10 @@ const PopupMenuBase = new Lang.Class({
}, },
addSettingsAction: function(title, desktopFile) { addSettingsAction: function(title, desktopFile) {
// Don't allow user settings to get edited unless we're in a user session
if (global.session_type != Shell.SessionType.USER)
return null;
let menuItem = this.addAction(title, function() { let menuItem = this.addAction(title, function() {
let app = Shell.AppSystem.get_default().lookup_setting(desktopFile); let app = Shell.AppSystem.get_default().lookup_setting(desktopFile);
@ -903,22 +903,11 @@ const PopupMenuBase = new Lang.Class({
Main.overview.hide(); Main.overview.hide();
app.activate(); app.activate();
}); });
menuItem.actor.visible = Main.sessionMode.allowSettings;
this._settingsActions[desktopFile] = menuItem;
return menuItem; return menuItem;
}, },
_setSettingsVisibility: function(visible) {
for (let id in this._settingsActions) {
let item = this._settingsActions[id];
item.actor.visible = visible;
}
},
isEmpty: function() { isEmpty: function() {
return this.box.get_n_children() == 0; return this.box.get_children().length == 0;
}, },
isChildMenu: function(menu) { isChildMenu: function(menu) {
@ -954,7 +943,7 @@ const PopupMenuBase = new Lang.Class({
_connectSubMenuSignals: function(object, menu) { _connectSubMenuSignals: function(object, menu) {
object._subMenuActivateId = menu.connect('activate', Lang.bind(this, function() { object._subMenuActivateId = menu.connect('activate', Lang.bind(this, function() {
this.emit('activate'); this.emit('activate');
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
})); }));
object._subMenuActiveChangeId = menu.connect('active-changed', Lang.bind(this, function(submenu, submenuItem) { object._subMenuActiveChangeId = menu.connect('active-changed', Lang.bind(this, function(submenu, submenuItem) {
if (this._activeMenuItem && this._activeMenuItem != submenuItem) if (this._activeMenuItem && this._activeMenuItem != submenuItem)
@ -989,7 +978,7 @@ const PopupMenuBase = new Lang.Class({
})); }));
menuItem._activateId = menuItem.connect('activate', Lang.bind(this, function (menuItem, event) { menuItem._activateId = menuItem.connect('activate', Lang.bind(this, function (menuItem, event) {
this.emit('activate', menuItem); this.emit('activate', menuItem);
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
})); }));
// the weird name is to avoid a conflict with some random property // the weird name is to avoid a conflict with some random property
// the menuItem may have, called destroyId // the menuItem may have, called destroyId
@ -1059,17 +1048,14 @@ const PopupMenuBase = new Lang.Class({
if (menuItem instanceof PopupMenuSection) { if (menuItem instanceof PopupMenuSection) {
this._connectSubMenuSignals(menuItem, menuItem); this._connectSubMenuSignals(menuItem, menuItem);
menuItem._parentOpenStateChangedId = this.connect('open-state-changed', menuItem._closingId = this.connect('open-state-changed',
function(self, open) { function(self, open) {
if (open) if (!open)
menuItem.open(); menuItem.close(false);
else
menuItem.close();
}); });
menuItem.connect('destroy', Lang.bind(this, function() { menuItem.connect('destroy', Lang.bind(this, function() {
menuItem.disconnect(menuItem._subMenuActivateId); menuItem.disconnect(menuItem._subMenuActivateId);
menuItem.disconnect(menuItem._subMenuActiveChangeId); menuItem.disconnect(menuItem._subMenuActiveChangeId);
this.disconnect(menuItem._parentOpenStateChangedId);
this.length--; this.length--;
})); }));
@ -1082,7 +1068,7 @@ const PopupMenuBase = new Lang.Class({
this._connectItemSignals(menuItem); this._connectItemSignals(menuItem);
menuItem._closingId = this.connect('open-state-changed', function(self, open) { menuItem._closingId = this.connect('open-state-changed', function(self, open) {
if (!open) if (!open)
menuItem.menu.close(BoxPointer.PopupAnimation.FADE); menuItem.menu.close(false);
}); });
} else if (menuItem instanceof PopupSeparatorMenuItem) { } else if (menuItem instanceof PopupSeparatorMenuItem) {
this._connectItemSignals(menuItem); this._connectItemSignals(menuItem);
@ -1104,8 +1090,7 @@ const PopupMenuBase = new Lang.Class({
let columnWidths = []; let columnWidths = [];
let items = this.box.get_children(); let items = this.box.get_children();
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
if (!items[i].visible && if (!items[i].visible)
!(items[i]._delegate instanceof PopupSubMenu && items[i-1].visible))
continue; continue;
if (items[i]._delegate instanceof PopupBaseMenuItem || items[i]._delegate instanceof PopupMenuBase) { if (items[i]._delegate instanceof PopupBaseMenuItem || items[i]._delegate instanceof PopupMenuBase) {
let itemColumnWidths = items[i]._delegate.getColumnWidths(); let itemColumnWidths = items[i]._delegate.getColumnWidths();
@ -1170,9 +1155,9 @@ const PopupMenuBase = new Lang.Class({
toggle: function() { toggle: function() {
if (this.isOpen) if (this.isOpen)
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
else else
this.open(BoxPointer.PopupAnimation.FULL); this.open(true);
}, },
destroy: function() { destroy: function() {
@ -1180,9 +1165,6 @@ const PopupMenuBase = new Lang.Class({
this.actor.destroy(); this.actor.destroy();
this.emit('destroy'); this.emit('destroy');
Main.sessionMode.disconnect(this._sessionUpdatedId);
this._sessionUpdatedId = 0;
} }
}); });
Signals.addSignalMethods(PopupMenuBase.prototype); Signals.addSignalMethods(PopupMenuBase.prototype);
@ -1236,7 +1218,7 @@ const PopupMenu = new Lang.Class({
_onKeyPressEvent: function(actor, event) { _onKeyPressEvent: function(actor, event) {
if (event.get_key_symbol() == Clutter.Escape) { if (event.get_key_symbol() == Clutter.Escape) {
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
return true; return true;
} }
@ -1269,14 +1251,13 @@ const PopupMenu = new Lang.Class({
}, },
close: function(animate) { close: function(animate) {
if (!this.isOpen)
return;
if (this._activeMenuItem) if (this._activeMenuItem)
this._activeMenuItem.setActive(false); this._activeMenuItem.setActive(false);
if (this._boxPointer.actor.visible) this._boxPointer.hide(animate);
this._boxPointer.hide(animate);
if (!this.isOpen)
return;
this.isOpen = false; this.isOpen = false;
this.emit('open-state-changed', false); this.emit('open-state-changed', false);
@ -1300,6 +1281,24 @@ const PopupSubMenu = new Lang.Class({
hscrollbar_policy: Gtk.PolicyType.NEVER, hscrollbar_policy: Gtk.PolicyType.NEVER,
vscrollbar_policy: Gtk.PolicyType.NEVER }); vscrollbar_policy: Gtk.PolicyType.NEVER });
// StScrollbar plays dirty tricks with events, calling
// clutter_set_motion_events_enabled (FALSE) during the scroll; this
// confuses our event tracking, so we just turn it off during the
// scroll.
let vscroll = this.actor.get_vscroll_bar();
vscroll.connect('scroll-start',
Lang.bind(this, function() {
let topMenu = this._getTopMenu();
if (topMenu)
topMenu.passEvents = true;
}));
vscroll.connect('scroll-stop',
Lang.bind(this, function() {
let topMenu = this._getTopMenu();
if (topMenu)
topMenu.passEvents = false;
}));
this.actor.add_actor(this.box); this.actor.add_actor(this.box);
this.actor._delegate = this; this.actor._delegate = this;
this.actor.clip_to_allocation = true; this.actor.clip_to_allocation = true;
@ -1349,11 +1348,6 @@ const PopupSubMenu = new Lang.Class({
this.actor.vscrollbar_policy = this.actor.vscrollbar_policy =
needsScrollbar ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; needsScrollbar ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER;
if (needsScrollbar)
this.actor.add_style_pseudo_class('scrolled');
else
this.actor.remove_style_pseudo_class('scrolled');
// It looks funny if we animate with a scrollbar (at what point is // It looks funny if we animate with a scrollbar (at what point is
// the scrollbar added?) so just skip that case // the scrollbar added?) so just skip that case
if (animate && needsScrollbar) if (animate && needsScrollbar)
@ -1426,7 +1420,7 @@ const PopupSubMenu = new Lang.Class({
// Move focus back to parent menu if the user types Left. // Move focus back to parent menu if the user types Left.
if (this.isOpen && event.get_key_symbol() == Clutter.KEY_Left) { if (this.isOpen && event.get_key_symbol() == Clutter.KEY_Left) {
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
this.sourceActor._delegate.setActive(true); this.sourceActor._delegate.setActive(true);
return true; return true;
} }
@ -1460,7 +1454,7 @@ const PopupMenuSection = new Lang.Class({
// deliberately ignore any attempt to open() or close(), but emit the // deliberately ignore any attempt to open() or close(), but emit the
// corresponding signal so children can still pick it up // corresponding signal so children can still pick it up
open: function() { this.emit('open-state-changed', true); }, open: function(animate) { this.emit('open-state-changed', true); },
close: function() { this.emit('open-state-changed', false); }, close: function() { this.emit('open-state-changed', false); },
destroy: function() { destroy: function() {
@ -1508,7 +1502,7 @@ const PopupSubMenuMenuItem = new Lang.Class({
let symbol = event.get_key_symbol(); let symbol = event.get_key_symbol();
if (symbol == Clutter.KEY_Right) { if (symbol == Clutter.KEY_Right) {
this.menu.open(BoxPointer.PopupAnimation.FULL); this.menu.open(true);
this.menu.actor.navigate_focus(null, Gtk.DirectionType.DOWN, false); this.menu.actor.navigate_focus(null, Gtk.DirectionType.DOWN, false);
return true; return true;
} else if (symbol == Clutter.KEY_Left && this.menu.isOpen) { } else if (symbol == Clutter.KEY_Left && this.menu.isOpen) {
@ -1520,7 +1514,7 @@ const PopupSubMenuMenuItem = new Lang.Class({
}, },
activate: function(event) { activate: function(event) {
this.menu.open(BoxPointer.PopupAnimation.FULL); this.menu.open(true);
}, },
_onButtonReleaseEvent: function(actor) { _onButtonReleaseEvent: function(actor) {
@ -1547,7 +1541,7 @@ const PopupComboMenu = new Lang.Class({
_onKeyPressEvent: function(actor, event) { _onKeyPressEvent: function(actor, event) {
if (event.get_key_symbol() == Clutter.Escape) { if (event.get_key_symbol() == Clutter.Escape) {
this.close(BoxPointer.PopupAnimation.FULL); this.close(true);
return true; return true;
} }
@ -1899,6 +1893,10 @@ const RemoteMenu = new Lang.Class({
} }
item.actor.reactive = item.actor.can_focus = action.enabled; item.actor.reactive = item.actor.can_focus = action.enabled;
if (action.enabled)
item.actor.remove_style_pseudo_class('insensitive');
else
item.actor.add_style_pseudo_class('insensitive');
destroyId = item.connect('destroy', Lang.bind(this, function() { destroyId = item.connect('destroy', Lang.bind(this, function() {
item.disconnect(destroyId); item.disconnect(destroyId);
@ -1923,7 +1921,7 @@ const RemoteMenu = new Lang.Class({
while (k0 < currentItems.length && currentItems[k0]._ignored) while (k0 < currentItems.length && currentItems[k0]._ignored)
k0++; k0++;
// find the right menu item matching the model item // find the right menu item matching the model item
for (j0 = 0; k0 < currentItems.length && j0 < position; j0++, k0++) { for (j0 = 0; j0 < position; j0++, k0++) {
if (currentItems[k0]._ignored) if (currentItems[k0]._ignored)
k0++; k0++;
} }
@ -1933,7 +1931,7 @@ const RemoteMenu = new Lang.Class({
for (k = k0; k < currentItems.length; k++) for (k = k0; k < currentItems.length; k++)
currentItems[k].destroy(); currentItems[k].destroy();
} else { } else {
for (j = j0, k = k0; k < currentItems.length && j < j0 + removed; j++, k++) { for (j = j0, k = k0; j < j0 + removed; j++, k++) {
currentItems[k].destroy(); currentItems[k].destroy();
if (currentItems[k]._ignored) if (currentItems[k]._ignored)
@ -1964,9 +1962,8 @@ const RemoteMenu = new Lang.Class({
k++; k++;
} }
} else if (changeSignal) { } else if (changeSignal) {
let signalId = this.actionGroup.connect(changeSignal, Lang.bind(this, function(actionGroup, actionName) { let signalId = this.actionGroup.connect(changeSignal, Lang.bind(this, function() {
actionGroup.disconnect(signalId); this.actionGroup.disconnect(signalId);
if (this._actions[actionName]) return;
// force a full update // force a full update
this._modelChanged(model, 0, -1, model.get_n_items(), target); this._modelChanged(model, 0, -1, model.get_n_items(), target);
@ -2031,6 +2028,11 @@ const RemoteMenu = new Lang.Class({
for (let i = 0; i < action.items.length; i++) { for (let i = 0; i < action.items.length; i++) {
let item = action.items[i]; let item = action.items[i];
item.actor.reactive = item.actor.can_focus = action.enabled; item.actor.reactive = item.actor.can_focus = action.enabled;
if (action.enabled)
item.actor.remove_style_pseudo_class('insensitive');
else
item.actor.add_style_pseudo_class('insensitive');
} }
} }
} }
@ -2058,9 +2060,6 @@ const PopupMenuManager = new Lang.Class({
}, },
addMenu: function(menu, position) { addMenu: function(menu, position) {
if (this._findMenu(menu) > -1)
return;
let menudata = { let menudata = {
menu: menu, menu: menu,
openStateChangeId: menu.connect('open-state-changed', Lang.bind(this, this._onMenuOpenState)), openStateChangeId: menu.connect('open-state-changed', Lang.bind(this, this._onMenuOpenState)),
@ -2195,11 +2194,11 @@ const PopupMenuManager = new Lang.Class({
let oldMenu = this._activeMenu; let oldMenu = this._activeMenu;
this._activeMenu = null; this._activeMenu = null;
for (let i = this._menuStack.length - 1; i >= 0; i--) for (let i = this._menuStack.length - 1; i >= 0; i--)
this._menuStack[i].close(BoxPointer.PopupAnimation.FADE); this._menuStack[i].close(false);
oldMenu.close(BoxPointer.PopupAnimation.FADE); oldMenu.close(false);
newMenu.open(BoxPointer.PopupAnimation.FADE); newMenu.open(false);
} else } else
newMenu.open(BoxPointer.PopupAnimation.FULL); newMenu.open(true);
}, },
_onMenuSourceEnter: function(menu) { _onMenuSourceEnter: function(menu) {
@ -2284,6 +2283,9 @@ const PopupMenuManager = new Lang.Class({
this._owner.menuEventFilter(event)) this._owner.menuEventFilter(event))
return true; return true;
if (this._activeMenu != null && this._activeMenu.passEvents)
return false;
if (this._didPop) { if (this._didPop) {
this._didPop = false; this._didPop = false;
return true; return true;
@ -2311,6 +2313,6 @@ const PopupMenuManager = new Lang.Class({
_closeMenu: function() { _closeMenu: function() {
if (this._activeMenu != null) if (this._activeMenu != null)
this._activeMenu.close(BoxPointer.PopupAnimation.FULL); this._activeMenu.close(true);
} }
}); });

View File

@ -34,17 +34,16 @@ var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface);
function loadRemoteSearchProviders(addProviderCallback) { function loadRemoteSearchProviders(addProviderCallback) {
let dataDirs = GLib.get_system_data_dirs(); let dataDirs = GLib.get_system_data_dirs();
let loadedProviders = {};
for (let i = 0; i < dataDirs.length; i++) { for (let i = 0; i < dataDirs.length; i++) {
let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'search-providers']); let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'search-providers']);
let dir = Gio.file_new_for_path(path); let dir = Gio.file_new_for_path(path);
if (!dir.query_exists(null)) if (!dir.query_exists(null))
continue; continue;
loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallback); loadRemoteSearchProvidersFromDir(dir, addProviderCallback);
} }
}; };
function loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallback) { function loadRemoteSearchProvidersFromDir(dir, addProviderCallback) {
let dirPath = dir.get_path(); let dirPath = dir.get_path();
FileUtils.listDirAsync(dir, Lang.bind(this, function(files) { FileUtils.listDirAsync(dir, Lang.bind(this, function(files) {
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
@ -63,34 +62,15 @@ function loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallb
let remoteProvider, title; let remoteProvider, title;
try { try {
let group = KEY_FILE_GROUP; let group = KEY_FILE_GROUP;
let icon = keyfile.get_string(group, 'Icon');
let busName = keyfile.get_string(group, 'BusName'); let busName = keyfile.get_string(group, 'BusName');
let objectPath = keyfile.get_string(group, 'ObjectPath'); let objectPath = keyfile.get_string(group, 'ObjectPath');
title = keyfile.get_locale_string(group, 'Title', null);
if (loadedProviders[objectPath])
continue;
let appInfo = null;
try {
let desktopId = keyfile.get_string(group, 'DesktopId');
appInfo = Gio.DesktopAppInfo.new(desktopId);
} catch (e) {
}
let icon;
if (appInfo) {
icon = appInfo.get_icon();
title = appInfo.get_name();
} else {
let iconName = keyfile.get_string(group, 'Icon');
icon = new Gio.ThemedIcon({ name: iconName });
title = keyfile.get_locale_string(group, 'Title', null);
}
remoteProvider = new RemoteSearchProvider(title, remoteProvider = new RemoteSearchProvider(title,
icon, icon,
busName, busName,
objectPath); objectPath);
loadedProviders[objectPath] = remoteProvider;
} catch(e) { } catch(e) {
log('Failed to add search provider "%s": %s'.format(title, e.toString())); log('Failed to add search provider "%s": %s'.format(title, e.toString()));
continue; continue;
@ -111,13 +91,15 @@ const RemoteSearchProvider = new Lang.Class({
dbusName, dbusPath); dbusName, dbusPath);
this.parent(title.toUpperCase()); this.parent(title.toUpperCase());
this.async = true;
this._cancellable = new Gio.Cancellable(); this._cancellable = new Gio.Cancellable();
}, },
createIcon: function(size, meta) { createIcon: function(size, meta) {
if (meta['gicon']) { if (meta['gicon']) {
return new St.Icon({ gicon: Gio.icon_new_for_string(meta['gicon']), return new St.Icon({ gicon: Gio.icon_new_for_string(meta['gicon']),
icon_size: size }); icon_size: size,
icon_type: St.IconType.FULLCOLOR });
} else if (meta['icon-data']) { } else if (meta['icon-data']) {
let [width, height, rowStride, hasAlpha, let [width, height, rowStride, hasAlpha,
bitsPerSample, nChannels, data] = meta['icon-data']; bitsPerSample, nChannels, data] = meta['icon-data'];
@ -128,7 +110,8 @@ const RemoteSearchProvider = new Lang.Class({
// Ugh, but we want to fall back to something ... // Ugh, but we want to fall back to something ...
return new St.Icon({ icon_name: 'text-x-generic', return new St.Icon({ icon_name: 'text-x-generic',
icon_size: size }); icon_size: size,
icon_type: St.IconType.FULLCOLOR });
}, },
_getResultsFinished: function(results, error) { _getResultsFinished: function(results, error) {
@ -137,7 +120,7 @@ const RemoteSearchProvider = new Lang.Class({
this.searchSystem.pushResults(this, results[0]); this.searchSystem.pushResults(this, results[0]);
}, },
getInitialResultSet: function(terms) { getInitialResultSetAsync: function(terms) {
this._cancellable.cancel(); this._cancellable.cancel();
this._cancellable.reset(); this._cancellable.reset();
try { try {
@ -150,7 +133,7 @@ const RemoteSearchProvider = new Lang.Class({
} }
}, },
getSubsearchResultSet: function(previousResults, newTerms) { getSubsearchResultSetAsync: function(previousResults, newTerms) {
this._cancellable.cancel(); this._cancellable.cancel();
this._cancellable.reset(); this._cancellable.reset();
try { try {
@ -181,7 +164,7 @@ const RemoteSearchProvider = new Lang.Class({
callback(resultMetas); callback(resultMetas);
}, },
getResultMetas: function(ids, callback) { getResultMetasAsync: function(ids, callback) {
this._cancellable.cancel(); this._cancellable.cancel();
this._cancellable.reset(); this._cancellable.reset();
try { try {

View File

@ -219,9 +219,7 @@ const RunDialog = new Lang.Class({
this.contentLayout.add(this._errorBox, { expand: true }); this.contentLayout.add(this._errorBox, { expand: true });
let errorIcon = new St.Icon({ icon_name: 'dialog-error-symbolic', let errorIcon = new St.Icon({ icon_name: 'dialog-error', icon_size: 24, style_class: 'run-dialog-error-icon' });
icon_size: 24,
style_class: 'run-dialog-error-icon' });
this._errorBox.add(errorIcon, { y_align: St.Align.MIDDLE }); this._errorBox.add(errorIcon, { y_align: St.Align.MIDDLE });

View File

@ -1,973 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GnomeDesktop = imports.gi.GnomeDesktop;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Signals = imports.signals;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const TweenerEquations = imports.tweener.equations;
const GnomeSession = imports.misc.gnomeSession;
const Layout = imports.ui.layout;
const LoginManager = imports.misc.loginManager;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const MessageTray = imports.ui.messageTray;
const ShellDBus = imports.ui.shellDBus;
const Tweener = imports.ui.tweener;
const Util = imports.misc.util;
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const LOCK_ENABLED_KEY = 'lock-enabled';
const LOCK_DELAY_KEY = 'lock-delay';
const CURTAIN_SLIDE_TIME = 0.3;
// fraction of screen height the arrow must reach before completing
// the slide up automatically
const ARROW_DRAG_THRESHOLD = 0.1;
// Parameters for the arrow animation
const N_ARROWS = 3;
const ARROW_ANIMATION_TIME = 0.6;
const ARROW_ANIMATION_PEAK_OPACITY = 0.4;
// The distance in px that the lock screen will move to when pressing
// a key that has no effect in the lock screen (bumping it)
const BUMP_SIZE = 25;
const BUMP_TIME = 0.3;
const SUMMARY_ICON_SIZE = 48;
// Lightbox fading times
// STANDARD_FADE_TIME is used when the session goes idle, while
// SHORT_FADE_TIME is used when requesting lock explicitly from the user menu
const STANDARD_FADE_TIME = 10;
const SHORT_FADE_TIME = 0.3;
const Clock = new Lang.Class({
Name: 'ScreenShieldClock',
CLOCK_FORMAT_KEY: 'clock-format',
CLOCK_SHOW_SECONDS_KEY: 'clock-show-seconds',
_init: function() {
this.actor = new St.BoxLayout({ style_class: 'screen-shield-clock',
vertical: true });
this._time = new St.Label({ style_class: 'screen-shield-clock-time' });
this._date = new St.Label({ style_class: 'screen-shield-clock-date' });
this.actor.add(this._time, { x_align: St.Align.MIDDLE });
this.actor.add(this._date, { x_align: St.Align.MIDDLE });
this._wallClock = new GnomeDesktop.WallClock({ time_only: true });
this._wallClock.connect('notify::clock', Lang.bind(this, this._updateClock));
this._updateClock();
},
_updateClock: function() {
this._time.text = this._wallClock.clock;
let date = new Date();
/* Translators: This is a time format for a date in
long format */
this._date.text = date.toLocaleFormat(_("%A, %B %d"));
},
destroy: function() {
this.actor.destroy();
this._wallClock.run_dispose();
}
});
const NotificationsBox = new Lang.Class({
Name: 'NotificationsBox',
_init: function() {
this.actor = new St.BoxLayout({ vertical: true,
name: 'screenShieldNotifications',
style_class: 'screen-shield-notifications-box' });
this._residentNotificationBox = new St.BoxLayout({ vertical: true,
style_class: 'screen-shield-notifications-box' });
let scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START });
this._persistentNotificationBox = new St.BoxLayout({ vertical: true,
style_class: 'screen-shield-notifications-box' });
scrollView.add_actor(this._persistentNotificationBox);
this.actor.add(this._residentNotificationBox, { x_fill: true });
this.actor.add(scrollView, { x_fill: true, x_align: St.Align.START });
this._items = [];
Main.messageTray.getSummaryItems().forEach(Lang.bind(this, function(item) {
this._summaryItemAdded(Main.messageTray, item, true);
}));
this._updateVisibility();
this._summaryAddedId = Main.messageTray.connect('summary-item-added', Lang.bind(this, this._summaryItemAdded));
},
destroy: function() {
if (this._summaryAddedId) {
Main.messageTray.disconnect(this._summaryAddedId);
this._summaryAddedId = 0;
}
for (let i = 0; i < this._items.length; i++)
this._removeItem(this._items[i]);
this._items = [];
this.actor.destroy();
},
_updateVisibility: function() {
this._residentNotificationBox.visible = this._residentNotificationBox.get_n_children() > 0;
this._persistentNotificationBox.visible = this._persistentNotificationBox.get_children().some(function(a) {
return a.visible;
});
this.actor.visible = this._residentNotificationBox.visible || this._persistentNotificationBox.visible;
},
_sourceIsResident: function(source) {
return source.hasResidentNotification() && !source.isChat;
},
_makeNotificationCountText: function(count, isChat) {
if (isChat)
return ngettext("%d new message", "%d new messages", count).format(count);
else
return ngettext("%d new notification", "%d new notifications", count).format(count);
},
_makeNotificationSource: function(source) {
let box = new St.BoxLayout({ style_class: 'screen-shield-notification-source' });
let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
box.add(sourceActor.actor, { y_fill: true });
let textBox = new St.BoxLayout({ vertical: true });
box.add(textBox, { y_fill: false, y_align: St.Align.START });
let label = new St.Label({ text: source.title,
style_class: 'screen-shield-notification-label' });
textBox.add(label);
let count = source.unseenCount;
let countLabel = new St.Label({ text: this._makeNotificationCountText(count, source.isChat),
style_class: 'screen-shield-notification-count-text' });
textBox.add(countLabel);
box.visible = count != 0;
return [box, countLabel];
},
_summaryItemAdded: function(tray, item, dontUpdateVisibility) {
// Ignore transient sources, or sources explicitly marked not to show
// in the lock screen
if (item.source.isTransient || !item.source.showInLockScreen)
return;
let obj = {
item: item,
source: item.source,
resident: this._sourceIsResident(item.source),
contentUpdatedId: 0,
sourceDestroyId: 0,
sourceBox: null,
countLabel: null,
};
if (obj.resident) {
this._residentNotificationBox.add(item.notificationStackWidget);
item.closeButton.hide();
item.prepareNotificationStackForShowing();
} else {
[obj.sourceBox, obj.countLabel] = this._makeNotificationSource(item.source);
this._persistentNotificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
}
obj.contentUpdatedId = item.connect('content-updated', Lang.bind(this, this._onItemContentUpdated));
obj.sourceCountChangedId = item.source.connect('count-updated', Lang.bind(this, this._onSourceChanged));
obj.sourceTitleChangedId = item.source.connect('title-changed', Lang.bind(this, this._onSourceChanged));
obj.sourceDestroyId = item.source.connect('destroy', Lang.bind(this, this._onSourceDestroy));
this._items.push(obj);
if (!dontUpdateVisibility)
this._updateVisibility();
},
_findSource: function(source) {
for (let i = 0; i < this._items.length; i++) {
if (this._items[i].source == source)
return i;
}
return -1;
},
_onItemContentUpdated: function(item) {
let obj = this._items[this._findSource(item.source)];
this._updateItem(obj);
},
_onSourceChanged: function(source) {
let obj = this._items[this._findSource(source)];
this._updateItem(obj);
},
_updateItem: function(obj) {
let itemShouldBeResident = this._sourceIsResident(obj.source);
if (itemShouldBeResident && obj.resident) {
// Nothing to do here, the actor is already updated
return;
}
if (obj.resident && !itemShouldBeResident) {
// make into a regular item
obj.item.doneShowingNotificationStack();
this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget);
[obj.sourceBox, obj.countLabel] = this._makeNotificationSource(obj.source);
this._persistentNotificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
} else if (itemShouldBeResident && !obj.resident) {
// make into a resident item
obj.sourceBox.destroy();
obj.sourceBox = obj.countLabel = null;
obj.resident = true;
this._residentNotificationBox.add(obj.item.notificationStackWidget);
obj.item.closeButton.hide();
obj.item.prepareNotificationStackForShowing();
} else {
// just update the counter
let count = obj.source.unseenCount;
obj.countLabel.text = this._makeNotificationCountText(count, obj.source.isChat);
obj.sourceBox.visible = count != 0;
}
this._updateVisibility();
},
_onSourceDestroy: function(source) {
let idx = this._findSource(source);
this._removeItem(this._items[idx]);
this._items.splice(idx, 1);
this._updateVisibility();
},
_removeItem: function(obj) {
if (obj.resident) {
obj.item.doneShowingNotificationStack();
this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget);
} else {
obj.sourceBox.destroy();
}
obj.item.disconnect(obj.contentUpdatedId);
obj.source.disconnect(obj.sourceDestroyId);
obj.source.disconnect(obj.sourceCountChangedId);
},
});
const Arrow = new Lang.Class({
Name: 'Arrow',
Extends: St.Bin,
_init: function(params) {
this.parent(params);
this.x_fill = this.y_fill = true;
this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
this._drawingArea = new St.DrawingArea();
this._drawingArea.connect('repaint', Lang.bind(this, this._drawArrow));
this.child = this._drawingArea;
this._shadowHelper = null;
this._shadowWidth = this._shadowHeight = 0;
},
_drawArrow: function(arrow) {
let cr = arrow.get_context();
let [w, h] = arrow.get_surface_size();
let node = this.get_theme_node();
let thickness = node.get_length('-arrow-thickness');
Clutter.cairo_set_source_color(cr, node.get_foreground_color());
cr.setLineCap(Cairo.LineCap.ROUND);
cr.setLineWidth(thickness);
cr.moveTo(thickness / 2, h - thickness / 2);
cr.lineTo(w/2, thickness);
cr.lineTo(w - thickness / 2, h - thickness / 2);
cr.stroke();
},
vfunc_style_changed: function() {
let node = this.get_theme_node();
this._shadow = node.get_shadow('-arrow-shadow');
if (this._shadow)
this._shadowHelper = St.ShadowHelper.new(this._shadow);
else
this._shadowHelper = null;
},
vfunc_paint: function() {
if (this._shadowHelper) {
this._shadowHelper.update(this._drawingArea);
let allocation = this._drawingArea.get_allocation_box();
let paintOpacity = this._drawingArea.get_paint_opacity();
this._shadowHelper.paint(allocation, paintOpacity);
}
this._drawingArea.paint();
}
});
/**
* To test screen shield, make sure to kill gnome-screensaver.
*
* If you are setting org.gnome.desktop.session.idle-delay directly in dconf,
* rather than through System Settings, you also need to set
* org.gnome.settings-daemon.plugins.power.sleep-display-ac and
* org.gnome.settings-daemon.plugins.power.sleep-display-battery to the same value.
* This will ensure that the screen blanks at the right time when it fades out.
* https://bugzilla.gnome.org/show_bug.cgi?id=668703 explains the dependance.
*/
const ScreenShield = new Lang.Class({
Name: 'ScreenShield',
_init: function() {
this.actor = Main.layoutManager.screenShieldGroup;
this._lockScreenState = MessageTray.State.HIDDEN;
this._lockScreenGroup = new St.Widget({ x_expand: true,
y_expand: true,
reactive: true,
can_focus: true,
name: 'lockScreenGroup',
});
this._lockScreenGroup.connect('key-release-event',
Lang.bind(this, this._onLockScreenKeyRelease));
this._lockScreenGroup.connect('scroll-event',
Lang.bind(this, this._onLockScreenScroll));
this._lockScreenContents = new St.Widget({ layout_manager: new Clutter.BinLayout(),
name: 'lockScreenContents' });
this._lockScreenContents.add_constraint(new Layout.MonitorConstraint({ primary: true }));
this._background = new St.Bin({ style_class: 'screen-shield-background',
child: Meta.BackgroundActor.new_for_screen(global.screen) });
this._lockScreenGroup.add_actor(this._background);
this._lockScreenGroup.add_actor(this._lockScreenContents);
this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows',
vertical: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.END,
// HACK: without these, ClutterBinLayout
// ignores alignment properties on the actor
x_expand: true,
y_expand: true });
for (let i = 0; i < N_ARROWS; i++) {
let arrow = new Arrow({ opacity: 0 });
this._arrowContainer.add_actor(arrow);
}
this._lockScreenContents.add_actor(this._arrowContainer);
let dragArea = new Clutter.Rect({ origin: new Clutter.Point({ x: 0, y: -global.screen_height, }),
size: new Clutter.Size({ width: global.screen_width,
height: global.screen_height }) });
let action = new Clutter.DragAction({ drag_axis: Clutter.DragAxis.Y_AXIS,
drag_area: dragArea });
action.connect('drag-begin', Lang.bind(this, this._onDragBegin));
action.connect('drag-end', Lang.bind(this, this._onDragEnd));
this._lockScreenGroup.add_action(action);
this._lockDialogGroup = new St.Widget({ x_expand: true,
y_expand: true,
pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }),
name: 'lockDialogGroup' });
this.actor.add_actor(this._lockDialogGroup);
this.actor.add_actor(this._lockScreenGroup);
this._presence = new GnomeSession.Presence(Lang.bind(this, function(proxy, error) {
if (error) {
logError(error, 'Error while reading gnome-session presence');
return;
}
this._onStatusChanged(proxy.status);
}));
this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) {
this._onStatusChanged(status);
}));
this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this);
this._loginManager = LoginManager.getLoginManager();
this._loginSession = this._loginManager.getCurrentSessionProxy();
this._loginSession.connectSignal('Lock', Lang.bind(this, function() { this.lock(false); }));
this._loginSession.connectSignal('Unlock', Lang.bind(this, function() { this.unlock(); }));
this._settings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
this._isModal = false;
this._hasLockScreen = false;
this._isGreeter = false;
this._isActive = false;
this._inUnlockAnimation = false;
this._activationTime = 0;
this._lightbox = new Lightbox.Lightbox(Main.uiGroup,
{ inhibitEvents: true,
fadeInTime: STANDARD_FADE_TIME,
fadeFactor: 1 });
this.idleMonitor = Shell.IdleMonitor.get();
},
_becomeModal: function() {
if (this._isModal)
return true;
this._isModal = Main.pushModal(this.actor);
if (this._isModal)
return true;
// We failed to get a pointer grab, it means that
// something else has it. Try with a keyboard grab only
this._isModal = Main.pushModal(this.actor, { options: Meta.ModalOptions.POINTER_ALREADY_GRABBED });
return this._isModal;
},
_onLockScreenKeyRelease: function(actor, event) {
let symbol = event.get_key_symbol();
// Do nothing if the lock screen is not fully shown.
// This avoids reusing the previous (and stale) unlock
// dialog if esc is pressed while the curtain is going
// down after cancel.
// Similarly, don't bump if the lock screen is not showing or is
// animating, as the bump overrides the animation and would
// remove any onComplete handler.
if (this._lockScreenState != MessageTray.State.SHOWN)
return false;
if (symbol == Clutter.KEY_Escape ||
symbol == Clutter.KEY_Return ||
symbol == Clutter.KEY_KP_Enter) {
this._ensureUnlockDialog(true, true);
this._hideLockScreen(true);
return true;
}
this._bumpLockScreen();
return true;
},
_onLockScreenScroll: function(actor, event) {
if (this._lockScreenState != MessageTray.State.SHOWN)
return false;
let delta = 0;
if (event.get_scroll_direction() == Clutter.ScrollDirection.UP)
delta = 5;
else if (event.get_scroll_direction() == Clutter.ScrollDirection.SMOOTH)
delta = Math.max(0, event.get_scroll_delta()[0]);
this._lockScreenScrollCounter += delta;
// 7 standard scrolls to lift up
if (this._lockScreenScrollCounter > 35) {
this._ensureUnlockDialog(false, true);
this._hideLockScreen(true);
}
return true;
},
_animateArrows: function() {
let arrows = this._arrowContainer.get_children();
let unitaryDelay = ARROW_ANIMATION_TIME / (arrows.length + 1);
let maxOpacity = 255 * ARROW_ANIMATION_PEAK_OPACITY;
for (let i = 0; i < arrows.length; i++) {
arrows.opacity = 0;
Tweener.addTween(arrows[i],
{ opacity: 0,
delay: unitaryDelay * (N_ARROWS - (i + 1)),
time: ARROW_ANIMATION_TIME,
transition: function(t, b, c, d) {
if (t < d/2)
return TweenerEquations.easeOutQuad(t, 0, maxOpacity, d/2);
else
return TweenerEquations.easeInQuad(t - d/2, maxOpacity, -maxOpacity, d/2);
}
});
}
return true;
},
_onDragBegin: function() {
Tweener.removeTweens(this._lockScreenGroup);
this._lockScreenState = MessageTray.State.HIDING;
this._ensureUnlockDialog(false, false);
},
_onDragEnd: function(action, actor, eventX, eventY, modifiers) {
if (this._lockScreenGroup.y < -(ARROW_DRAG_THRESHOLD * global.stage.height)) {
// Complete motion automatically
this._hideLockScreen(true);
this._ensureUnlockDialog(false, true);
} else {
// restore the lock screen to its original place
// try to use the same speed as the normal animation
let h = global.stage.height;
let time = CURTAIN_SLIDE_TIME * (-this._lockScreenGroup.y) / h;
Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup,
{ y: 0,
time: time,
transition: 'easeInQuad',
onComplete: function() {
this._lockScreenGroup.fixed_position_set = false;
this._lockScreenState = MessageTray.State.SHOWN;
},
onCompleteScope: this,
});
// If we have a unlock dialog, cancel it
if (this._dialog) {
this._dialog.cancel();
if (!this._isGreeter) {
this._dialog = null;
}
}
}
},
_onStatusChanged: function(status) {
if (status != GnomeSession.PresenceStatus.IDLE)
return;
if (this._dialog) {
this._dialog.cancel();
if (!this._isGreeter) {
this._dialog = null;
}
}
if (!this._becomeModal()) {
// We could not become modal, so we can't activate the
// screenshield. The user is probably very upset at this
// point, but any application using global grabs is broken
// Just tell him to stop using this app
//
// XXX: another option is to kick the user into the gdm login
// screen, where we're not affected by grabs
Main.notifyError("Unable to lock",
"Lock was blocked by an application");
return;
}
if (!this._isActive) {
this._lightbox.show();
if (this._activationTime == 0)
this._activationTime = GLib.get_monotonic_time();
// What we want is a negative transition to 0, so we install a watch for
// 1 second and trigger it if the idle_time becomes lower than that
// The correct fix will come in GNOME 3.8 with GnomeDesktop.IdleMonitor
this._becameActiveId = this.idleMonitor.add_watch(1000, Lang.bind(this, function() {
if (this.idleMonitor.get_idletime() >= 1000)
return;
this.idleMonitor.remove_watch(this._becameActiveId);
let lightboxWasShown = this._lightbox.shown;
this._lightbox.hide();
// GLib.get_monotonic_time() returns microseconds, convert to seconds
let elapsedTime = (GLib.get_monotonic_time() - this._activationTime) / 1000000;
let shouldLock = lightboxWasShown &&
this._settings.get_boolean(LOCK_ENABLED_KEY) &&
(elapsedTime >= this._settings.get_uint(LOCK_DELAY_KEY));
if (shouldLock || this._isLocked) {
this.lock(false);
} else if (this._isActive) {
this.unlock();
}
}));
this._isActive = true;
this.emit('lock-status-changed');
}
},
showDialog: function() {
// Ensure that the stage window is mapped, before taking a grab
// otherwise X errors out
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
if (!this._becomeModal()) {
// In the login screen, this is a hard error. Fail-whale
log('Could not acquire modal grab for the login screen. Aborting login process.');
Meta.quit(Meta.ExitCode.ERROR);
}
return false;
}));
this.actor.show();
this._isGreeter = Main.sessionMode.isGreeter;
this._ensureUnlockDialog(true, true);
this._hideLockScreen(false);
},
_bumpLockScreen: function() {
Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup,
{ y: -BUMP_SIZE,
time: BUMP_TIME / 2,
transition: 'easeOutQuad',
onComplete: function() {
Tweener.addTween(this,
{ y: 0,
time: BUMP_TIME / 2,
transition: 'easeInQuad' });
}
});
},
_hideLockScreen: function(animate) {
this._lockScreenState = MessageTray.State.HIDING;
if (animate) {
// Tween the lock screen out of screen
// try to use the same speed regardless of original position
let h = global.stage.height;
let time = CURTAIN_SLIDE_TIME * (h + this._lockScreenGroup.y) / h;
Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup,
{ y: -h,
time: time,
transition: 'easeInQuad',
onComplete: function() {
this._lockScreenState = MessageTray.State.HIDDEN;
this._lockScreenGroup.hide();
},
onCompleteScope: this,
});
} else {
this._lockScreenState = MessageTray.State.HIDDEN;
this._lockScreenGroup.hide();
}
if (Main.sessionMode.currentMode == 'lock-screen')
Main.sessionMode.popMode('lock-screen');
},
_ensureUnlockDialog: function(onPrimary, allowCancel) {
if (!this._dialog) {
let constructor = Main.sessionMode.unlockDialog;
if (!constructor) {
// This session mode has no locking capabilities
this.unlock();
return;
}
this._dialog = new constructor(this._lockDialogGroup);
let time = global.get_current_time();
this._dialog.connect('loaded', Lang.bind(this, function() {
if (!this._dialog.open(time, onPrimary)) {
log('Could not open login dialog: failed to acquire grab');
this.unlock();
}
}));
this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed));
this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded));
}
this._dialog.allowCancel = allowCancel;
},
_onUnlockFailed: function() {
this._resetLockScreen(true, false);
},
_onUnlockSucceded: function() {
this._tweenUnlocked();
},
_resetLockScreen: function(animateLockScreen, animateLockDialog) {
this._ensureLockScreen();
this._lockDialogGroup.scale_x = 1;
this._lockDialogGroup.scale_y = 1;
this._lockScreenGroup.show();
this._lockScreenState = MessageTray.State.SHOWING;
if (animateLockScreen) {
this._lockScreenGroup.y = -global.screen_height;
Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup,
{ y: 0,
time: SHORT_FADE_TIME,
transition: 'easeOutQuad',
onComplete: function() {
this._lockScreenShown();
},
onCompleteScope: this
});
} else {
this._lockScreenGroup.fixed_position_set = false;
this._lockScreenShown();
}
if (animateLockDialog) {
this._lockDialogGroup.opacity = 0;
Tweener.removeTweens(this._lockDialogGroup);
Tweener.addTween(this._lockDialogGroup,
{ opacity: 255,
time: SHORT_FADE_TIME,
transition: 'easeOutQuad' });
} else {
this._lockDialogGroup.opacity = 255;
}
this._lockScreenGroup.grab_key_focus();
if (Main.sessionMode.currentMode != 'lock-screen')
Main.sessionMode.pushMode('lock-screen');
},
_lockScreenShown: function() {
if (this._dialog && !this._isGreeter) {
this._dialog.destroy();
this._dialog = null;
}
if (this._arrowAnimationId)
Mainloop.source_remove(this._arrowAnimationId);
this._arrowAnimationId = Mainloop.timeout_add(6000, Lang.bind(this, this._animateArrows));
this._animateArrows();
this._lockScreenState = MessageTray.State.SHOWN;
this._lockScreenGroup.fixed_position_set = false;
this._lockScreenScrollCounter = 0;
this.emit('lock-screen-shown');
},
// Some of the actors in the lock screen are heavy in
// resources, so we only create them when needed
_ensureLockScreen: function() {
if (this._hasLockScreen)
return;
this._lockScreenContentsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
x_expand: true,
y_expand: true,
vertical: true,
style_class: 'screen-shield-contents-box' });
this._clock = new Clock();
this._lockScreenContentsBox.add(this._clock.actor, { x_fill: true,
y_fill: true });
this._lockScreenContents.add_actor(this._lockScreenContentsBox);
if (this._settings.get_boolean('show-notifications')) {
this._notificationsBox = new NotificationsBox();
this._lockScreenContentsBox.add(this._notificationsBox.actor, { x_fill: true,
y_fill: true,
expand: true });
}
this._hasLockScreen = true;
},
_clearLockScreen: function() {
this._clock.destroy();
this._clock = null;
if (this._notificationsBox) {
this._notificationsBox.destroy();
this._notificationsBox = null;
}
this._lockScreenContentsBox.destroy();
if (this._arrowAnimationId) {
Mainloop.source_remove(this._arrowAnimationId);
this._arrowAnimationId = 0;
}
this._hasLockScreen = false;
},
get locked() {
return this._isActive;
},
get activationTime() {
return this._activationTime;
},
_tweenUnlocked: function() {
this._inUnlockAnimation = true;
this.unlock();
Tweener.addTween(this._lockDialogGroup, {
scale_x: 0,
scale_y: 0,
time: Overview.ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: function() {
if (this._dialog) {
this._dialog.destroy();
this._dialog = null;
}
this.actor.hide();
this._inUnlockAnimation = false;
},
onCompleteScope: this
});
},
unlock: function() {
if (this._hasLockScreen)
this._clearLockScreen();
if (this._dialog && !this._isGreeter) {
this._dialog.destroy();
this._dialog = null;
}
this._lightbox.hide();
if (this._isModal) {
Main.popModal(this.actor);
this._isModal = false;
}
if (!this._inUnlockAnimation)
this.actor.hide();
if (Main.sessionMode.currentMode == 'lock-screen')
Main.sessionMode.popMode('lock-screen');
if (Main.sessionMode.currentMode == 'unlock-dialog')
Main.sessionMode.popMode('unlock-dialog');
this._activationTime = 0;
this._isActive = false;
this._isLocked = false;
this.emit('lock-status-changed');
},
lock: function(animate) {
// Warn the user if we can't become modal
if (!this._becomeModal()) {
Main.notifyError("Unable to lock",
"Lock was blocked by an application");
return;
}
if (this._activationTime == 0)
this._activationTime = GLib.get_monotonic_time();
this.actor.show();
if (Main.sessionMode.currentMode != 'unlock-dialog' &&
Main.sessionMode.currentMode != 'lock-screen') {
this._isGreeter = Main.sessionMode.isGreeter;
if (!this._isGreeter)
Main.sessionMode.pushMode('unlock-dialog');
}
this._resetLockScreen(animate, animate);
this._isActive = true;
this._isLocked = true;
this.emit('lock-status-changed');
},
});
Signals.addSignalMethods(ScreenShield.prototype);
/* Fallback code to handle session locking using gnome-screensaver,
in case the required GDM dependency is not there
*/
const ScreenShieldFallback = new Lang.Class({
Name: 'ScreenShieldFallback',
_init: function() {
Util.spawn(['gnome-screensaver']);
this._proxy = new Gio.DBusProxy({ g_connection: Gio.DBus.session,
g_name: 'org.gnome.ScreenSaver',
g_object_path: '/org/gnome/ScreenSaver',
g_interface_name: 'org.gnome.ScreenSaver',
g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START |
Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES),
});
this._proxy.init(null);
this._proxy.connect('g-signal', Lang.bind(this, this._onSignal));
this._proxy.connect('notify::g-name-owner', Lang.bind(this, this._onNameOwnerChanged));
},
_onNameOwnerChanged: function(object, pspec) {
if (this._proxy.g_name_owner)
[this._locked] = this._proxy.call_sync('GetActive', null,
Gio.DBusCallFlags.NONE, -1, null).deep_unpack();
else
this._locked = false;
this.emit('lock-status-changed', this._locked);
},
_onSignal: function(proxy, senderName, signalName, params) {
if (signalName == 'ActiveChanged') {
[this._locked] = params.deep_unpack();
this.emit('lock-status-changed', this._locked);
}
},
get locked() {
return this._locked;
},
lock: function() {
this._proxy.call('Lock', null, Gio.DBusCallFlags.NONE, -1, null,
Lang.bind(this, function(proxy, result) {
proxy.call_finish(result);
this.emit('lock-screen-shown');
}));
},
unlock: function() {
this._proxy.call('SetActive', GLib.Variant.new('(b)', false),
Gio.DBusCallFlags.NONE, -1, null, null);
},
});
Signals.addSignalMethods(ScreenShieldFallback.prototype);

View File

@ -10,13 +10,17 @@ const Util = imports.misc.util;
const FileUtils = imports.misc.fileUtils; const FileUtils = imports.misc.fileUtils;
const Main = imports.ui.main; const Main = imports.ui.main;
const DISABLED_OPEN_SEARCH_PROVIDERS_KEY = 'disabled-open-search-providers';
// Not currently referenced by the search API, but // Not currently referenced by the search API, but
// this enumeration can be useful for provider // this enumeration can be useful for provider
// implementations. // implementations.
const MatchType = { const MatchType = {
NONE: 0, NONE: 0,
SUBSTRING: 1, SUBSTRING: 1,
PREFIX: 2 MULTIPLE_SUBSTRING: 2,
PREFIX: 3,
MULTIPLE_PREFIX: 4
}; };
const SearchResultDisplay = new Lang.Class({ const SearchResultDisplay = new Lang.Class({
@ -49,7 +53,7 @@ const SearchResultDisplay = new Lang.Class({
* Remove all results from this display. * Remove all results from this display.
*/ */
clear: function() { clear: function() {
this.actor.destroy_all_children(); this.actor.get_children().forEach(function (actor) { actor.destroy(); });
}, },
/** /**
@ -68,8 +72,11 @@ const SearchResultDisplay = new Lang.Class({
* Subclass this object to add a new result type * Subclass this object to add a new result type
* to the search system, then call registerProvider() * to the search system, then call registerProvider()
* in SearchSystem with an instance. * in SearchSystem with an instance.
* Search is asynchronous and uses the * By default, search is synchronous and uses the
* getInitialResultSet()/getSubsearchResultSet() methods. * getInitialResultSet()/getSubsearchResultSet() methods.
* For asynchronous search, set the async property to true
* and implement getInitialResultSetAsync()/getSubsearchResultSetAsync()
* instead.
*/ */
const SearchProvider = new Lang.Class({ const SearchProvider = new Lang.Class({
Name: 'SearchProvider', Name: 'SearchProvider',
@ -77,6 +84,7 @@ const SearchProvider = new Lang.Class({
_init: function(title) { _init: function(title) {
this.title = title; this.title = title;
this.searchSystem = null; this.searchSystem = null;
this.async = false;
}, },
/** /**
@ -87,7 +95,7 @@ const SearchProvider = new Lang.Class({
* therefore a single term of length one or two), or when * therefore a single term of length one or two), or when
* a new term is added. * a new term is added.
* *
* Should "return" an array of result identifier strings representing * Should return an array of result identifier strings representing
* items which match the given search terms. This * items which match the given search terms. This
* is expected to be a substring match on the metadata for a given * is expected to be a substring match on the metadata for a given
* item. Ordering of returned results is up to the discretion of the provider, * item. Ordering of returned results is up to the discretion of the provider,
@ -97,9 +105,6 @@ const SearchProvider = new Lang.Class({
* description) before single matches * description) before single matches
* * Put items which match on a prefix before non-prefix substring matches * * Put items which match on a prefix before non-prefix substring matches
* *
* We say "return" above, but in order to make the query asynchronous, use
* this.searchSystem.pushResults();. The return value should be ignored.
*
* This function should be fast; do not perform unindexed full-text searches * This function should be fast; do not perform unindexed full-text searches
* or network queries. * or network queries.
*/ */
@ -107,6 +112,18 @@ const SearchProvider = new Lang.Class({
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
/**
* getInitialResultSetAsync:
* @terms: Array of search terms, treated as logical AND
*
* Like getInitialResultSet(), but the method should return immediately
* without a return value - use SearchSystem.pushResults() when the
* corresponding results are ready.
*/
getInitialResultSetAsync: function(terms) {
throw new Error('Not implemented');
},
/** /**
* getSubsearchResultSet: * getSubsearchResultSet:
* @previousResults: Array of item identifiers * @previousResults: Array of item identifiers
@ -119,26 +136,62 @@ const SearchProvider = new Lang.Class({
* *
* This allows search providers to only search through the previous * This allows search providers to only search through the previous
* result set, rather than possibly performing a full re-query. * result set, rather than possibly performing a full re-query.
*
* Similar to getInitialResultSet, the return value for this will
* be ignored; use this.searchSystem.pushResults();.
*/ */
getSubsearchResultSet: function(previousResults, newTerms) { getSubsearchResultSet: function(previousResults, newTerms) {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
/**
* getSubsearchResultSetAsync:
* @previousResults: Array of item identifiers
* @newTerms: Updated search terms
*
* Like getSubsearchResultSet(), but the method should return immediately
* without a return value - use SearchSystem.pushResults() when the
* corresponding results are ready.
*/
getSubsearchResultSetAsync: function(previousResults, newTerms) {
throw new Error('Not implemented');
},
/** /**
* getResultMetas: * getResultMetas:
* @ids: Result identifier strings * @ids: Result identifier strings
* *
* Call callback with array of objects with 'id', 'name', (both strings) and * Return an array of objects with 'id', 'name', (both strings) and
* 'createIcon' (function(size) returning a Clutter.Texture) properties * 'createIcon' (function(size) returning a Clutter.Texture) properties
* with the same number of members as @ids * with the same number of members as @ids
*/ */
getResultMetas: function(ids, callback) { getResultMetas: function(ids) {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
/**
* getResultMetasAsync:
* @ids: Result identifier strings
* @callback: callback to pass the results to when ready
*
* Like getResultMetas(), but the method should return immediately
* without a return value - pass the results to the provided @callback
* when ready.
*/
getResultMetasAsync: function(ids, callback) {
throw new Error('Not implemented');
},
/**
* createResultContainer:
*
* Search providers may optionally override this to render their
* results in a custom fashion. The default implementation
* will create a vertical list.
*
* Returns: An instance of SearchResultDisplay.
*/
createResultContainerActor: function() {
return null;
},
/** /**
* createResultActor: * createResultActor:
* @resultMeta: Object with result metadata * @resultMeta: Object with result metadata
@ -167,6 +220,99 @@ const SearchProvider = new Lang.Class({
}); });
Signals.addSignalMethods(SearchProvider.prototype); Signals.addSignalMethods(SearchProvider.prototype);
const OpenSearchSystem = new Lang.Class({
Name: 'OpenSearchSystem',
_init: function() {
this._providers = [];
global.settings.connect('changed::' + DISABLED_OPEN_SEARCH_PROVIDERS_KEY, Lang.bind(this, this._refresh));
this._refresh();
},
getProviders: function() {
let res = [];
for (let i = 0; i < this._providers.length; i++)
res.push({ id: i, name: this._providers[i].name });
return res;
},
setSearchTerms: function(terms) {
this._terms = terms;
},
_checkSupportedProviderLanguage: function(provider) {
if (provider.url.search(/{language}/) == -1)
return true;
let langs = GLib.get_language_names();
langs.push('en');
let lang = null;
for (let i = 0; i < langs.length; i++) {
for (let k = 0; k < provider.langs.length; k++) {
if (langs[i] == provider.langs[k])
lang = langs[i];
}
if (lang)
break;
}
provider.lang = lang;
return lang != null;
},
activateResult: function(id, params) {
let searchTerms = this._terms.join(' ');
let url = this._providers[id].url.replace('{searchTerms}', encodeURIComponent(searchTerms));
if (url.match('{language}'))
url = url.replace('{language}', this._providers[id].lang);
try {
Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context());
} catch (e) {
// TODO: remove this after glib will be removed from moduleset
// In the default jhbuild, gio is in our prefix but gvfs is not
Util.spawn(['gvfs-open', url])
}
Main.overview.hide();
},
_addProvider: function(fileName) {
let path = global.datadir + '/open-search-providers/' + fileName;
let source = Shell.get_file_contents_utf8_sync(path);
let [success, name, url, langs, icon_uri] = Shell.parse_search_provider(source);
let provider ={ name: name,
url: url,
id: this._providers.length,
icon_uri: icon_uri,
langs: langs };
if (this._checkSupportedProviderLanguage(provider)) {
this._providers.push(provider);
this.emit('changed');
}
},
_refresh: function() {
this._providers = [];
let names = global.settings.get_strv(DISABLED_OPEN_SEARCH_PROVIDERS_KEY);
let file = Gio.file_new_for_path(global.datadir + '/open-search-providers');
FileUtils.listDirAsync(file, Lang.bind(this, function(files) {
for (let i = 0; i < files.length; i++) {
let enabled = true;
let name = files[i].get_name();
for (let k = 0; k < names.length; k++)
if (names[k] == name)
enabled = false;
if (enabled)
this._addProvider(name);
}
}));
}
});
Signals.addSignalMethods(OpenSearchSystem.prototype);
const SearchSystem = new Lang.Class({ const SearchSystem = new Lang.Class({
Name: 'SearchSystem', Name: 'SearchSystem',
@ -233,33 +379,42 @@ const SearchSystem = new Lang.Class({
} }
} }
let previousResultsArr = this._previousResults;
let results = []; let results = [];
this._previousTerms = terms;
this._previousResults = results;
if (isSubSearch) { if (isSubSearch) {
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providers.length; i++) {
let [provider, previousResults] = previousResultsArr[i]; let [provider, previousResults] = this._previousResults[i];
try { try {
results.push([provider, []]); if (provider.async) {
provider.getSubsearchResultSet(previousResults, terms); provider.getSubsearchResultSetAsync(previousResults, terms);
results.push([provider, []]);
} else {
let providerResults = provider.getSubsearchResultSet(previousResults, terms);
results.push([provider, providerResults]);
}
} catch (error) { } catch (error) {
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message); global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
} }
} }
} else { } else {
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providers.length; i++) {
let provider = this._providers[i]; let provider = this._providers[i];
try { try {
results.push([provider, []]); if (provider.async) {
provider.getInitialResultSet(terms); provider.getInitialResultSetAsync(terms);
results.push([provider, []]);
} else {
let providerResults = provider.getInitialResultSet(terms);
results.push([provider, providerResults]);
}
} catch (error) { } catch (error) {
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message); global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
} }
} }
} }
this._previousTerms = terms;
this._previousResults = results;
this.emit('search-completed', results);
}, },
}); });
Signals.addSignalMethods(SearchSystem.prototype); Signals.addSignalMethods(SearchSystem.prototype);

View File

@ -5,7 +5,6 @@ const Lang = imports.lang;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
const St = imports.gi.St; const St = imports.gi.St;
const Atk = imports.gi.Atk;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const IconGrid = imports.ui.iconGrid; const IconGrid = imports.ui.iconGrid;
@ -34,13 +33,12 @@ const SearchResult = new Lang.Class({
content = new St.Bin({ style_class: 'search-result-content', content = new St.Bin({ style_class: 'search-result-content',
reactive: true, reactive: true,
can_focus: true, can_focus: true,
track_hover: true, track_hover: true });
accessible_role: Atk.Role.PUSH_BUTTON });
let icon = new IconGrid.BaseIcon(this.metaInfo['name'], let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
{ createIcon: this.metaInfo['createIcon'] }); { createIcon: this.metaInfo['createIcon'] });
content.set_child(icon.actor); content.set_child(icon.actor);
this._dragActorSource = icon.icon; this._dragActorSource = icon.icon;
content.label_actor = icon.label; this.actor.label_actor = icon.label;
} else { } else {
if (content._delegate && content._delegate.getDragActorSource) if (content._delegate && content._delegate.getDragActorSource)
this._dragActorSource = content._delegate.getDragActorSource(); this._dragActorSource = content._delegate.getDragActorSource();
@ -121,7 +119,13 @@ const GridSearchResults = new Lang.Class({
if (results.length == 0) if (results.length == 0)
return; return;
provider.getResultMetas(results, Lang.bind(this, this.renderResults)); if (provider.async) {
provider.getResultMetasAsync(results,
Lang.bind(this, this.renderResults));
} else {
let metas = provider.getResultMetas(results);
this.renderResults(metas);
}
})); }));
})); }));
this._notDisplayedResult = []; this._notDisplayedResult = [];
@ -131,7 +135,7 @@ const GridSearchResults = new Lang.Class({
getResultsForDisplay: function() { getResultsForDisplay: function() {
let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount(); let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
let canDisplay = this._grid.childrenInRow(this._width) * this._grid.getRowLimit() let canDisplay = this._grid.childrenInRow(this._width) * MAX_SEARCH_RESULTS_ROWS
- alreadyVisible; - alreadyVisible;
let numResults = Math.min(this._notDisplayedResult.length, canDisplay); let numResults = Math.min(this._notDisplayedResult.length, canDisplay);
@ -173,9 +177,11 @@ const GridSearchResults = new Lang.Class({
const SearchResults = new Lang.Class({ const SearchResults = new Lang.Class({
Name: 'SearchResults', Name: 'SearchResults',
_init: function(searchSystem) { _init: function(searchSystem, openSearchSystem) {
this._searchSystem = searchSystem; this._searchSystem = searchSystem;
this._searchSystem.connect('search-updated', Lang.bind(this, this._updateResults)); this._searchSystem.connect('search-updated', Lang.bind(this, this._updateCurrentResults));
this._searchSystem.connect('search-completed', Lang.bind(this, this._updateResults));
this._openSearchSystem = openSearchSystem;
this.actor = new St.BoxLayout({ name: 'searchResults', this.actor = new St.BoxLayout({ name: 'searchResults',
vertical: true }); vertical: true });
@ -190,7 +196,7 @@ const SearchResults = new Lang.Class({
scrollView.add_actor(this._content); scrollView.add_actor(this._content);
this.actor.add(scrollView, { x_fill: true, this.actor.add(scrollView, { x_fill: true,
y_fill: true, y_fill: false,
expand: true, expand: true,
x_align: St.Align.START, x_align: St.Align.START,
y_align: St.Align.START }); y_align: St.Align.START });
@ -205,37 +211,87 @@ const SearchResults = new Lang.Class({
})); }));
this._statusText = new St.Label({ style_class: 'search-statustext' }); this._statusText = new St.Label({ style_class: 'search-statustext' });
this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE, this._content.add(this._statusText);
y_align: St.Align.MIDDLE });
this._content.add(this._statusBin, { expand: true });
this._statusBin.add_actor(this._statusText);
this._providers = this._searchSystem.getProviders(); this._providers = this._searchSystem.getProviders();
this._providerMeta = []; this._providerMeta = [];
this._providerMetaResults = {};
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providers.length; i++) {
this.createProviderMeta(this._providers[i]); this.createProviderMeta(this._providers[i]);
this._providerMetaResults[this.providers[i].title] = [];
} }
this._searchProvidersBox = new St.BoxLayout({ style_class: 'search-providers-box' });
this.actor.add(this._searchProvidersBox);
this._openSearchProviders = [];
this._openSearchSystem.connect('changed', Lang.bind(this, this._updateOpenSearchProviderButtons));
this._updateOpenSearchProviderButtons();
this._highlightDefault = false; this._highlightDefault = false;
this._defaultResult = null; this._defaultResult = null;
}, },
_updateOpenSearchProviderButtons: function() {
for (let i = 0; i < this._openSearchProviders.length; i++)
this._openSearchProviders[i].actor.destroy();
this._openSearchProviders = this._openSearchSystem.getProviders();
for (let i = 0; i < this._openSearchProviders.length; i++)
this._createOpenSearchProviderButton(this._openSearchProviders[i]);
},
_createOpenSearchProviderButton: function(provider) {
let button = new St.Button({ style_class: 'dash-search-button',
reactive: true,
can_focus: true,
x_fill: true,
y_align: St.Align.MIDDLE });
let bin = new St.Bin({ x_fill: false,
x_align:St.Align.MIDDLE });
button.connect('clicked', Lang.bind(this, function() {
this._openSearchSystem.activateResult(provider.id);
}));
let title = new St.Label({ text: provider.name,
style_class: 'dash-search-button-label' });
button.label_actor = title;
bin.set_child(title);
button.set_child(bin);
provider.actor = button;
button.setSelected = function(selected) {
if (selected)
button.add_style_pseudo_class('selected');
else
button.remove_style_pseudo_class('selected');
};
button.activate = Lang.bind(this, function() {
this._openSearchSystem.activateResult(provider.id);
});
button.actor = button;
this._searchProvidersBox.add(button);
},
createProviderMeta: function(provider) { createProviderMeta: function(provider) {
let providerBox = new St.BoxLayout({ style_class: 'search-section', let providerBox = new St.BoxLayout({ style_class: 'search-section',
vertical: true }); vertical: true });
let title = new St.Label({ style_class: 'search-section-header', let title = new St.Label({ style_class: 'search-section-header',
text: provider.title }); text: provider.title });
providerBox.add(title, { x_fill: false, x_align: St.Align.START }); providerBox.add(title);
let resultDisplayBin = new St.Bin({ style_class: 'search-section-results', let resultDisplayBin = new St.Bin({ style_class: 'search-section-results',
x_fill: true, x_fill: true,
y_fill: true }); y_fill: true });
providerBox.add(resultDisplayBin, { expand: true }); providerBox.add(resultDisplayBin, { expand: true });
let resultDisplay = new GridSearchResults(provider); let resultDisplay = provider.createResultContainerActor();
if (resultDisplay == null) {
resultDisplay = new GridSearchResults(provider);
}
resultDisplayBin.set_child(resultDisplay.actor); resultDisplayBin.set_child(resultDisplay.actor);
this._providerMeta.push({ provider: provider, this._providerMeta.push({ provider: provider,
actor: providerBox, actor: providerBox,
resultDisplay: resultDisplay }); resultDisplay: resultDisplay,
hasPendingResults: false });
this._content.add(providerBox); this._content.add(providerBox);
}, },
@ -251,6 +307,7 @@ const SearchResults = new Lang.Class({
}, },
_clearDisplay: function() { _clearDisplay: function() {
this._visibleResultsCount = 0;
for (let i = 0; i < this._providerMeta.length; i++) { for (let i = 0; i < this._providerMeta.length; i++) {
let meta = this._providerMeta[i]; let meta = this._providerMeta[i];
meta.resultDisplay.clear(); meta.resultDisplay.clear();
@ -266,14 +323,14 @@ const SearchResults = new Lang.Class({
reset: function() { reset: function() {
this._searchSystem.reset(); this._searchSystem.reset();
this._statusBin.hide(); this._statusText.hide();
this._clearDisplay(); this._clearDisplay();
}, },
startingSearch: function() { startingSearch: function() {
this.reset(); this.reset();
this._statusText.set_text(_("Searching...")); this._statusText.set_text(_("Searching..."));
this._statusBin.show(); this._statusText.show();
}, },
doSearch: function (searchString) { doSearch: function (searchString) {
@ -289,6 +346,8 @@ const SearchResults = new Lang.Class({
for (let i = 0; i < this._providerMeta.length; i++) { for (let i = 0; i < this._providerMeta.length; i++) {
let meta = this._providerMeta[i]; let meta = this._providerMeta[i];
if (meta.hasPendingResults)
return;
if (!meta.actor.visible) if (!meta.actor.visible)
continue; continue;
@ -300,6 +359,9 @@ const SearchResults = new Lang.Class({
} }
} }
if (!newDefaultResult)
newDefaultResult = this._searchProvidersBox.get_first_child();
if (newDefaultResult != this._defaultResult) { if (newDefaultResult != this._defaultResult) {
if (this._defaultResult) if (this._defaultResult)
this._defaultResult.setSelected(false); this._defaultResult.setSelected(false);
@ -310,57 +372,78 @@ const SearchResults = new Lang.Class({
} }
}, },
_updateStatusText: function () { _updateCurrentResults: function(searchSystem, results) {
let haveResults = false;
for (let i = 0; i < this._providerMeta.length; ++i)
if (this._providerMeta[i].resultDisplay.getFirstResult()) {
haveResults = true;
break;
}
if (!haveResults) {
this._statusText.set_text(_("No results."));
this._statusBin.show();
} else {
this._statusBin.hide();
}
},
_updateResults: function(searchSystem, results) {
let terms = searchSystem.getTerms(); let terms = searchSystem.getTerms();
let [provider, providerResults] = results; let [provider, providerResults] = results;
let meta = this._metaForProvider(provider); let meta = this._metaForProvider(provider);
meta.hasPendingResults = false;
this._updateProviderResults(provider, providerResults, terms);
},
_updateProviderResults: function(provider, providerResults, terms) {
let meta = this._metaForProvider(provider);
if (providerResults.length == 0) { if (providerResults.length == 0) {
this._clearDisplayForProvider(provider); this._clearDisplayForProvider(provider);
meta.resultDisplay.setResults([], []); meta.resultDisplay.setResults([], []);
this._maybeSetInitialSelection();
this._updateStatusText();
} else { } else {
this._providerMetaResults[provider.title] = providerResults;
meta.resultDisplay.setResults(providerResults, terms); meta.resultDisplay.setResults(providerResults, terms);
let results = meta.resultDisplay.getResultsForDisplay(); let results = meta.resultDisplay.getResultsForDisplay();
provider.getResultMetas(results, Lang.bind(this, function(metas) { if (provider.async) {
provider.getResultMetasAsync(results, Lang.bind(this,
function(metas) {
this._clearDisplayForProvider(provider);
meta.actor.show();
// Hinding drops the key focus if we have it
let focus = global.stage.get_key_focus();
this._content.hide();
meta.resultDisplay.renderResults(metas);
this._maybeSetInitialSelection();
this._content.show();
if (this._content.contains(focus))
global.stage.set_key_focus(focus);
}));
} else {
let metas = provider.getResultMetas(results);
this._clearDisplayForProvider(provider); this._clearDisplayForProvider(provider);
meta.actor.show(); meta.actor.show();
// Hiding drops the key focus if we have it
let focus = global.stage.get_key_focus();
// To avoid CSS transitions causing flickering when
// the first search result stays the same, we hide the
// content while filling in the results.
this._content.hide();
meta.resultDisplay.renderResults(metas); meta.resultDisplay.renderResults(metas);
this._maybeSetInitialSelection(); }
this._updateStatusText();
this._content.show();
if (this._content.contains(focus))
global.stage.set_key_focus(focus);
}));
} }
this._maybeSetInitialSelection();
},
_updateResults: function(searchSystem, results) {
if (results.length == 0) {
this._statusText.set_text(_("No matching results."));
this._statusText.show();
} else {
this._statusText.hide();
}
let terms = searchSystem.getTerms();
this._openSearchSystem.setSearchTerms(terms);
// To avoid CSS transitions causing flickering when the first search
// result stays the same, we hide the content while filling in the
// results.
this._content.hide();
for (let i = 0; i < results.length; i++) {
let [provider, providerResults] = results[i];
let meta = this._metaForProvider(provider);
meta.hasPendingResults = provider.async;
if (!meta.hasPendingResults)
this._updateProviderResults(provider, providerResults, terms);
}
this._content.show();
return true;
}, },
activateDefault: function() { activateDefault: function() {

View File

@ -1,160 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Signals = imports.signals;
const Main = imports.ui.main;
const Params = imports.misc.params;
const DEFAULT_MODE = 'restrictive';
const _modes = {
'restrictive': {
hasOverview: false,
showCalendarEvents: false,
allowSettings: false,
allowExtensions: false,
allowKeybindingsWhenModal: false,
hasRunDialog: false,
hasWorkspaces: false,
hasWindows: false,
hasNotifications: false,
isLocked: false,
isGreeter: false,
isPrimary: false,
unlockDialog: null,
components: [],
panel: {
left: [],
center: [],
right: []
},
},
'gdm': {
allowKeybindingsWhenModal: true,
hasNotifications: true,
isGreeter: true,
isPrimary: true,
unlockDialog: imports.gdm.loginDialog.LoginDialog,
components: ['polkitAgent'],
panel: {
left: [],
center: ['dateMenu'],
right: ['a11y', 'display', 'keyboard',
'volume', 'battery', 'powerMenu']
}
},
'lock-screen': {
isLocked: true,
isGreeter: undefined,
unlockDialog: undefined,
components: ['polkitAgent', 'telepathyClient'],
panel: {
left: ['userMenu'],
center: [],
right: ['lockScreen']
},
},
'unlock-dialog': {
isLocked: true,
unlockDialog: undefined,
components: ['polkitAgent', 'telepathyClient'],
panel: {
left: ['userMenu'],
center: [],
right: ['a11y', 'keyboard', 'lockScreen']
},
},
'initial-setup': {
isPrimary: true,
components: ['keyring'],
panel: {
left: [],
center: ['dateMenu'],
right: ['a11y', 'keyboard', 'volume']
}
},
'user': {
hasOverview: true,
showCalendarEvents: true,
allowSettings: true,
allowExtensions: true,
hasRunDialog: true,
hasWorkspaces: true,
hasWindows: true,
hasNotifications: true,
isLocked: false,
isPrimary: true,
unlockDialog: imports.ui.unlockDialog.UnlockDialog,
components: ['networkAgent', 'polkitAgent', 'telepathyClient',
'keyring', 'recorder', 'autorunManager', 'automountManager'],
panel: {
left: ['activities', 'appMenu'],
center: ['dateMenu'],
right: ['a11y', 'keyboard', 'volume', 'bluetooth',
'network', 'battery', 'userMenu']
}
}
};
function listModes() {
let modes = Object.getOwnPropertyNames(_modes);
for (let i = 0; i < modes.length; i++)
if (_modes[modes[i]].isPrimary)
print(modes[i]);
}
const SessionMode = new Lang.Class({
Name: 'SessionMode',
_init: function() {
global.connect('notify::session-mode', Lang.bind(this, this._sync));
let mode = _modes[global.session_mode].isPrimary ? global.session_mode
: 'user';
this._modeStack = [mode];
this._sync();
},
pushMode: function(mode) {
this._modeStack.push(mode);
this._sync();
},
popMode: function(mode) {
if (this.currentMode != mode || this._modeStack.length === 1)
throw new Error("Invalid SessionMode.popMode");
this._modeStack.pop();
this._sync();
},
switchMode: function(to) {
if (this.currentMode == to)
return;
this._modeStack[this._modeStack.length - 1] = to;
this._sync();
},
get currentMode() {
return this._modeStack[this._modeStack.length - 1];
},
_sync: function() {
let params = _modes[this.currentMode];
params = Params.parse(params, _modes[DEFAULT_MODE]);
// A simplified version of Lang.copyProperties, handles
// undefined as a special case for "no change / inherit from previous mode"
for (let prop in params) {
if (params[prop] !== undefined)
this[prop] = params[prop];
}
this.emit('updated');
}
});
Signals.addSignalMethods(SessionMode.prototype);

View File

@ -7,7 +7,6 @@ const Shell = imports.gi.Shell;
const Config = imports.misc.config; const Config = imports.misc.config;
const ExtensionSystem = imports.ui.extensionSystem; const ExtensionSystem = imports.ui.extensionSystem;
const ExtensionDownloader = imports.ui.extensionDownloader;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Flashspot = imports.ui.flashspot; const Flashspot = imports.ui.flashspot;
const Main = imports.ui.main; const Main = imports.ui.main;
@ -18,6 +17,17 @@ const GnomeShellIface = <interface name="org.gnome.Shell">
<arg type="b" direction="out" name="success" /> <arg type="b" direction="out" name="success" />
<arg type="s" direction="out" name="result" /> <arg type="s" direction="out" name="result" />
</method> </method>
<method name="ListExtensions">
<arg type="a{sa{sv}}" direction="out" name="extensions" />
</method>
<method name="GetExtensionInfo">
<arg type="s" direction="in" name="extension" />
<arg type="a{sv}" direction="out" name="info" />
</method>
<method name="GetExtensionErrors">
<arg type="s" direction="in" name="extension" />
<arg type="as" direction="out" name="errors" />
</method>
<method name="ScreenshotArea"> <method name="ScreenshotArea">
<arg type="i" direction="in" name="x"/> <arg type="i" direction="in" name="x"/>
<arg type="i" direction="in" name="y"/> <arg type="i" direction="in" name="y"/>
@ -46,24 +56,30 @@ const GnomeShellIface = <interface name="org.gnome.Shell">
<arg type="i" direction="in" name="width"/> <arg type="i" direction="in" name="width"/>
<arg type="i" direction="in" name="height"/> <arg type="i" direction="in" name="height"/>
</method> </method>
<method name="EnableExtension">
<arg type="s" direction="in" name="uuid"/>
</method>
<method name="DisableExtension">
<arg type="s" direction="in" name="uuid"/>
</method>
<method name="InstallRemoteExtension">
<arg type="s" direction="in" name="uuid"/>
<arg type="s" direction="in" name="version"/>
</method>
<method name="UninstallExtension">
<arg type="s" direction="in" name="uuid"/>
<arg type="b" direction="out" name="success"/>
</method>
<method name="LaunchExtensionPrefs">
<arg type="s" direction="in" name="uuid"/>
</method>
<property name="OverviewActive" type="b" access="readwrite" /> <property name="OverviewActive" type="b" access="readwrite" />
<property name="ApiVersion" type="i" access="read" />
<property name="ShellVersion" type="s" access="read" /> <property name="ShellVersion" type="s" access="read" />
</interface>; <signal name="ExtensionStatusChanged">
<arg type="s" name="uuid"/>
const ScreenSaverIface = <interface name="org.gnome.ScreenSaver"> <arg type="i" name="state"/>
<method name="Lock"> <arg type="s" name="error"/>
</method>
<method name="GetActive">
<arg name="active" direction="out" type="b" />
</method>
<method name="SetActive">
<arg name="value" direction="in" type="b" />
</method>
<method name="GetActiveTime">
<arg name="value" direction="out" type="u" />
</method>
<signal name="ActiveChanged">
<arg name="new_value" type="b" />
</signal> </signal>
</interface>; </interface>;
@ -73,8 +89,8 @@ const GnomeShell = new Lang.Class({
_init: function() { _init: function() {
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellIface, this); this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell'); this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
ExtensionSystem.connect('extension-state-changed',
this._extensionsSerivce = new GnomeShellExtensions(); Lang.bind(this, this._extensionStateChanged));
}, },
/** /**
@ -186,67 +202,6 @@ const GnomeShell = new Lang.Class({
flashspot.fire(); flashspot.fire();
}, },
get OverviewActive() {
return Main.overview.visible;
},
set OverviewActive(visible) {
if (visible)
Main.overview.show();
else
Main.overview.hide();
},
ShellVersion: Config.PACKAGE_VERSION
});
const GnomeShellExtensionsIface = <interface name="org.gnome.Shell.Extensions">
<method name="ListExtensions">
<arg type="a{sa{sv}}" direction="out" name="extensions" />
</method>
<method name="GetExtensionInfo">
<arg type="s" direction="in" name="extension" />
<arg type="a{sv}" direction="out" name="info" />
</method>
<method name="GetExtensionErrors">
<arg type="s" direction="in" name="extension" />
<arg type="as" direction="out" name="errors" />
</method>
<signal name="ExtensionStatusChanged">
<arg type="s" name="uuid"/>
<arg type="i" name="state"/>
<arg type="s" name="error"/>
</signal>
<method name="InstallRemoteExtension">
<arg type="s" direction="in" name="uuid"/>
<arg type="s" direction="out" name="result"/>
</method>
<method name="UninstallExtension">
<arg type="s" direction="in" name="uuid"/>
<arg type="b" direction="out" name="success"/>
</method>
<method name="LaunchExtensionPrefs">
<arg type="s" direction="in" name="uuid"/>
</method>
<method name="ReloadExtension">
<arg type="s" direction="in" name="uuid"/>
</method>
<method name="CheckForUpdates">
</method>
<property name="ShellVersion" type="s" access="read" />
</interface>;
const GnomeShellExtensions = new Lang.Class({
Name: 'GnomeShellExtensionsDBus',
_init: function() {
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellExtensionsIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
ExtensionSystem.connect('extension-state-changed',
Lang.bind(this, this._extensionStateChanged));
},
ListExtensions: function() { ListExtensions: function() {
let out = {}; let out = {};
for (let uuid in ExtensionUtils.extensions) { for (let uuid in ExtensionUtils.extensions) {
@ -305,12 +260,26 @@ const GnomeShellExtensions = new Lang.Class({
return extension.errors; return extension.errors;
}, },
InstallRemoteExtensionAsync: function([uuid], invocation) { EnableExtension: function(uuid) {
return ExtensionDownloader.installExtension(uuid, invocation); let enabledExtensions = global.settings.get_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY);
if (enabledExtensions.indexOf(uuid) == -1)
enabledExtensions.push(uuid);
global.settings.set_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY, enabledExtensions);
},
DisableExtension: function(uuid) {
let enabledExtensions = global.settings.get_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY);
while (enabledExtensions.indexOf(uuid) != -1)
enabledExtensions.splice(enabledExtensions.indexOf(uuid), 1);
global.settings.set_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY, enabledExtensions);
},
InstallRemoteExtension: function(uuid, version_tag) {
ExtensionSystem.installExtensionFromUUID(uuid, version_tag);
}, },
UninstallExtension: function(uuid) { UninstallExtension: function(uuid) {
return ExtensionDownloader.uninstallExtension(uuid); return ExtensionSystem.uninstallExtensionFromUUID(uuid);
}, },
LaunchExtensionPrefs: function(uuid) { LaunchExtensionPrefs: function(uuid) {
@ -320,18 +289,19 @@ const GnomeShellExtensions = new Lang.Class({
['extension:///' + uuid], -1, null); ['extension:///' + uuid], -1, null);
}, },
ReloadExtension: function(uuid) { get OverviewActive() {
let extension = ExtensionUtils.extensions[uuid]; return Main.overview.visible;
if (!extension)
return;
ExtensionSystem.reloadExtension(extension);
}, },
CheckForUpdates: function() { set OverviewActive(visible) {
ExtensionDownloader.checkForUpdates(); if (visible)
Main.overview.show();
else
Main.overview.hide();
}, },
ApiVersion: ExtensionSystem.API_VERSION,
ShellVersion: Config.PACKAGE_VERSION, ShellVersion: Config.PACKAGE_VERSION,
_extensionStateChanged: function(_, newState) { _extensionStateChanged: function(_, newState) {
@ -339,50 +309,3 @@ const GnomeShellExtensions = new Lang.Class({
GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error])); GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error]));
} }
}); });
const ScreenSaverDBus = new Lang.Class({
Name: 'ScreenSaverDBus',
_init: function(screenShield) {
this.parent();
this._screenShield = screenShield;
screenShield.connect('lock-status-changed', Lang.bind(this, function(shield) {
this._dbusImpl.emit_signal('ActiveChanged', GLib.Variant.new('(b)', [shield.locked]));
}));
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenSaverIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/ScreenSaver');
Gio.DBus.session.own_name('org.gnome.ScreenSaver', Gio.BusNameOwnerFlags.REPLACE, null, null);
},
LockAsync: function(parameters, invocation) {
let tmpId = this._screenShield.connect('lock-screen-shown', Lang.bind(this, function() {
this._screenShield.disconnect(tmpId);
invocation.return_value(null);
}));
this._screenShield.lock(true);
},
SetActive: function(active) {
if (active)
this._screenShield.lock(true);
else
this._screenShield.unlock();
},
GetActive: function() {
return this._screenShield.locked;
},
GetActiveTime: function() {
let started = this._screenShield.activationTime;
if (started > 0)
return Math.floor((GLib.get_monotonic_time() - started) / 1000000);
else
return 0;
},
});

View File

@ -7,7 +7,7 @@ const Main = imports.ui.main;
const Params = imports.misc.params; const Params = imports.misc.params;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const EntryMenu = new Lang.Class({ const _EntryMenu = new Lang.Class({
Name: 'ShellEntryMenu', Name: 'ShellEntryMenu',
Extends: PopupMenu.PopupMenu, Extends: PopupMenu.PopupMenu,
@ -34,37 +34,18 @@ const EntryMenu = new Lang.Class({
this._pasteItem = item; this._pasteItem = item;
this._passwordItem = null; this._passwordItem = null;
if (params.isPassword) if (params.isPassword) {
this._makePasswordItem(); item = new PopupMenu.PopupMenuItem('');
item.connect('activate', Lang.bind(this,
this._onPasswordActivated));
this.addMenuItem(item);
this._passwordItem = item;
}
Main.uiGroup.add_actor(this.actor); Main.uiGroup.add_actor(this.actor);
this.actor.hide(); this.actor.hide();
}, },
_makePasswordItem: function() {
let item = new PopupMenu.PopupMenuItem('');
item.connect('activate', Lang.bind(this,
this._onPasswordActivated));
this.addMenuItem(item);
this._passwordItem = item;
},
get isPassword() {
return this._passwordItem != null;
},
set isPassword(v) {
if (v == this.isPassword)
return;
if (v)
this._makePasswordItem();
else {
this._passwordItem.destroy();
this._passwordItem = null;
}
},
open: function() { open: function() {
this._updatePasteItem(); this._updatePasteItem();
this._updateCopyItem(); this._updateCopyItem();
@ -76,12 +57,6 @@ const EntryMenu = new Lang.Class({
this.actor.grab_key_focus(); this.actor.grab_key_focus();
this.parent(); this.parent();
this._entry.add_style_pseudo_class('focus');
},
close: function() {
this._entry.grab_key_focus();
this.parent();
}, },
_updateCopyItem: function() { _updateCopyItem: function() {
@ -129,42 +104,65 @@ const EntryMenu = new Lang.Class({
function _setMenuAlignment(entry, stageX) { function _setMenuAlignment(entry, stageX) {
let [success, entryX, entryY] = entry.transform_stage_point(stageX, 0); let [success, entryX, entryY] = entry.transform_stage_point(stageX, 0);
if (success) if (success)
entry.menu.setSourceAlignment(entryX / entry.width); entry._menu.setSourceAlignment(entryX / entry.width);
}; };
function _onButtonPressEvent(actor, event, entry) { function _onClicked(action, actor) {
if (entry.menu.isOpen) { let entry = actor._menu ? actor : actor.get_parent();
entry.menu.close();
return true; if (entry._menu.isOpen) {
} else if (event.get_button() == 3) { entry._menu.close();
let [stageX, stageY] = event.get_coords(); } else if (action.get_button() == 3) {
let [stageX, stageY] = action.get_coords();
_setMenuAlignment(entry, stageX); _setMenuAlignment(entry, stageX);
entry.menu.open(); entry._menu.open();
return true; }
};
function _onLongPress(action, actor, state) {
let entry = actor._menu ? actor : actor.get_parent();
if (state == Clutter.LongPressState.QUERY)
return action.get_button() == 1 && !entry._menu.isOpen;
if (state == Clutter.LongPressState.ACTIVATE) {
let [stageX, stageY] = action.get_coords();
_setMenuAlignment(entry, stageX);
entry._menu.open();
} }
return false; return false;
}; };
function _onPopup(actor, entry) { function _onPopup(actor) {
let entry = actor._menu ? actor : actor.get_parent();
let [success, textX, textY, lineHeight] = entry.clutter_text.position_to_coords(-1); let [success, textX, textY, lineHeight] = entry.clutter_text.position_to_coords(-1);
if (success) if (success)
entry.menu.setSourceAlignment(textX / entry.width); entry._menu.setSourceAlignment(textX / entry.width);
entry.menu.open(); entry._menu.open();
}; };
function addContextMenu(entry, params) { function addContextMenu(entry, params) {
if (entry.menu) if (entry._menu)
return; return;
entry.menu = new EntryMenu(entry, params); entry._menu = new _EntryMenu(entry, params);
entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry }); entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry });
entry._menuManager.addMenu(entry.menu); entry._menuManager.addMenu(entry._menu);
// Add an event handler to both the entry and its clutter_text; the former let clickAction;
// Add a click action to both the entry and its clutter_text; the former
// so padding is included in the clickable area, the latter because the // so padding is included in the clickable area, the latter because the
// event processing of ClutterText prevents event-bubbling. // event processing of ClutterText prevents event-bubbling.
entry.clutter_text.connect('button-press-event', Lang.bind(null, _onButtonPressEvent, entry)); clickAction = new Clutter.ClickAction();
entry.connect('button-press-event', Lang.bind(null, _onButtonPressEvent, entry)); clickAction.connect('clicked', _onClicked);
clickAction.connect('long-press', _onLongPress);
entry.clutter_text.add_action(clickAction);
entry.connect('popup-menu', Lang.bind(null, _onPopup, entry)); clickAction = new Clutter.ClickAction();
clickAction.connect('clicked', _onClicked);
clickAction.connect('long-press', _onLongPress);
entry.add_action(clickAction);
entry.connect('popup-menu', _onPopup);
} }

View File

@ -1,21 +1,17 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const Lang = imports.lang; const Lang = imports.lang;
const Signals = imports.signals; const Signals = imports.signals;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Pango = imports.gi.Pango; const Pango = imports.gi.Pango;
const St = imports.gi.St; const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const CheckBox = imports.ui.checkBox;
const Main = imports.ui.main; const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const ModalDialog = imports.ui.modalDialog; const ModalDialog = imports.ui.modalDialog;
const Params = imports.misc.params; const Params = imports.misc.params;
const ShellEntry = imports.ui.shellEntry;
const LIST_ITEM_ICON_SIZE = 48; const LIST_ITEM_ICON_SIZE = 48;
@ -52,11 +48,6 @@ function _setLabelsForMessage(dialog, message) {
_setLabelText(dialog.descriptionLabel, labels[1]); _setLabelText(dialog.descriptionLabel, labels[1]);
} }
function _createIcon(gicon) {
return new St.Icon({ gicon: gicon,
style_class: 'shell-mount-operation-icon' })
}
/* -------------------------------------------------------- */ /* -------------------------------------------------------- */
const ListItem = new Lang.Class({ const ListItem = new Lang.Class({
@ -100,11 +91,11 @@ const ShellMountOperation = new Lang.Class({
Name: 'ShellMountOperation', Name: 'ShellMountOperation',
_init: function(source, params) { _init: function(source, params) {
params = Params.parse(params, { existingDialog: null }); params = Params.parse(params, { reaskPassword: false });
this._reaskPassword = params.reaskPassword;
this._dialog = null; this._dialog = null;
this._dialogId = 0;
this._existingDialog = params.existingDialog;
this._processesDialog = null; this._processesDialog = null;
this.mountOp = new Shell.MountOperation(); this.mountOp = new Shell.MountOperation();
@ -116,177 +107,99 @@ const ShellMountOperation = new Lang.Class({
this.mountOp.connect('show-processes-2', this.mountOp.connect('show-processes-2',
Lang.bind(this, this._onShowProcesses2)); Lang.bind(this, this._onShowProcesses2));
this.mountOp.connect('aborted', this.mountOp.connect('aborted',
Lang.bind(this, this.close)); Lang.bind(this, this._onAborted));
this.mountOp.connect('show-unmount-progress',
Lang.bind(this, this._onShowUnmountProgress));
this._gicon = source.get_icon(); this._icon = new St.Icon({ gicon: source.get_icon(),
}, style_class: 'shell-mount-operation-icon' });
_closeExistingDialog: function() {
if (!this._existingDialog)
return;
this._existingDialog.close();
this._existingDialog = null;
}, },
_onAskQuestion: function(op, message, choices) { _onAskQuestion: function(op, message, choices) {
this._closeExistingDialog(); this._dialog = new ShellMountQuestionDialog(this._icon);
this._dialog = new ShellMountQuestionDialog(this._gicon);
this._dialogId = this._dialog.connect('response', Lang.bind(this, this._dialog.connect('response',
function(object, choice) { Lang.bind(this, function(object, choice) {
this.mountOp.set_choice(choice); this.mountOp.set_choice(choice);
this.mountOp.reply(Gio.MountOperationResult.HANDLED); this.mountOp.reply(Gio.MountOperationResult.HANDLED);
this.close(); this._dialog.close(global.get_current_time());
})); this._dialog = null;
}));
this._dialog.update(message, choices); this._dialog.update(message, choices);
this._dialog.open(); this._dialog.open(global.get_current_time());
}, },
_onAskPassword: function(op, message, defaultUser, defaultDomain, flags) { _onAskPassword: function(op, message) {
if (this._existingDialog) { this._notificationShowing = true;
this._dialog = this._existingDialog; this._source = new ShellMountPasswordSource(message, this._icon, this._reaskPassword);
this._dialog.reaskPassword();
} else {
this._dialog = new ShellMountPasswordDialog(message, this._gicon, flags);
}
this._dialogId = this._dialog.connect('response', Lang.bind(this, this._source.connect('password-ready',
function(object, choice, password, remember) { Lang.bind(this, function(source, password) {
if (choice == -1) { this.mountOp.set_password(password);
this.mountOp.reply(Gio.MountOperationResult.ABORTED); this.mountOp.reply(Gio.MountOperationResult.HANDLED);
} else {
if (remember)
this.mountOp.set_password_save(Gio.PasswordSave.PERMANENTLY);
else
this.mountOp.set_password_save(Gio.PasswordSave.NEVER);
this.mountOp.set_password(password); this._notificationShowing = false;
this.mountOp.reply(Gio.MountOperationResult.HANDLED); this._source.destroy();
} }));
}));
this._dialog.open(); this._source.connect('destroy',
Lang.bind(this, function() {
if (!this._notificationShowing)
return;
this._notificationShowing = false;
this.mountOp.reply(Gio.MountOperationResult.ABORTED);
}));
}, },
close: function(op) { _onAborted: function(op) {
this._closeExistingDialog(); if (!this._dialog)
this._processesDialog = null; return;
if (this._dialog) { this._dialog.close(global.get_current_time());
this._dialog.close(); this._dialog = null;
this._dialog = null;
}
if (this._notifier) {
this._notifier.done();
this._notifier = null;
}
}, },
_onShowProcesses2: function(op) { _onShowProcesses2: function(op) {
this._closeExistingDialog();
let processes = op.get_show_processes_pids(); let processes = op.get_show_processes_pids();
let choices = op.get_show_processes_choices(); let choices = op.get_show_processes_choices();
let message = op.get_show_processes_message(); let message = op.get_show_processes_message();
if (!this._processesDialog) { if (!this._processesDialog) {
this._processesDialog = new ShellProcessesDialog(this._gicon); this._processesDialog = new ShellProcessesDialog(this._icon);
this._dialog = this._processesDialog; this._dialog = this._processesDialog;
this._dialogId = this._processesDialog.connect('response', Lang.bind(this, this._processesDialog.connect('response',
function(object, choice) { Lang.bind(this, function(object, choice) {
if (choice == -1) { if (choice == -1) {
this.mountOp.reply(Gio.MountOperationResult.ABORTED); this.mountOp.reply(Gio.MountOperationResult.ABORTED);
} else { } else {
this.mountOp.set_choice(choice); this.mountOp.set_choice(choice);
this.mountOp.reply(Gio.MountOperationResult.HANDLED); this.mountOp.reply(Gio.MountOperationResult.HANDLED);
} }
this.close(); this._processesDialog.close(global.get_current_time());
})); this._dialog = null;
this._processesDialog.open(); }));
this._processesDialog.open(global.get_current_time());
} }
this._processesDialog.update(message, processes, choices); this._processesDialog.update(message, processes, choices);
}, },
_onShowUnmountProgress: function(op, message, timeLeft, bytesLeft) {
if (!this._notifier)
this._notifier = new ShellUnmountNotifier();
if (bytesLeft == 0)
this._notifier.done(message);
else
this._notifier.show(message);
},
borrowDialog: function() {
if (this._dialogId != 0) {
this._dialog.disconnect(this._dialogId);
this._dialogId = 0;
}
return this._dialog;
}
});
const ShellUnmountNotifier = new Lang.Class({
Name: 'ShellUnmountNotifier',
Extends: MessageTray.Source,
_init: function() {
this.parent('', 'media-removable');
this._notification = null;
Main.messageTray.add(this);
},
show: function(message) {
let [header, text] = message.split('\n', 2);
if (!this._notification) {
this._notification = new MessageTray.Notification(this, header, text);
this._notification.setTransient(true);
this._notification.setUrgency(MessageTray.Urgency.CRITICAL);
} else {
this._notification.update(header, text);
}
this.notify(this._notification);
},
done: function(message) {
if (this._notification) {
this._notification.destroy();
this._notification = null;
}
if (message) {
let notification = new MessageTray.Notification(this, message, null);
notification.setTransient(true);
this.notify(notification);
}
}
}); });
const ShellMountQuestionDialog = new Lang.Class({ const ShellMountQuestionDialog = new Lang.Class({
Name: 'ShellMountQuestionDialog', Name: 'ShellMountQuestionDialog',
Extends: ModalDialog.ModalDialog, Extends: ModalDialog.ModalDialog,
_init: function(gicon) { _init: function(icon) {
this.parent({ styleClass: 'mount-question-dialog' }); this.parent({ styleClass: 'mount-question-dialog' });
let mainContentLayout = new St.BoxLayout(); let mainContentLayout = new St.BoxLayout();
this.contentLayout.add(mainContentLayout, { x_fill: true, this.contentLayout.add(mainContentLayout, { x_fill: true,
y_fill: false }); y_fill: false });
this._iconBin = new St.Bin({ child: _createIcon(gicon) }); this._iconBin = new St.Bin({ child: icon });
mainContentLayout.add(this._iconBin, mainContentLayout.add(this._iconBin,
{ x_fill: true, { x_fill: true,
y_fill: false, y_fill: false,
@ -321,108 +234,62 @@ const ShellMountQuestionDialog = new Lang.Class({
}); });
Signals.addSignalMethods(ShellMountQuestionDialog.prototype); Signals.addSignalMethods(ShellMountQuestionDialog.prototype);
const ShellMountPasswordDialog = new Lang.Class({ const ShellMountPasswordSource = new Lang.Class({
Name: 'ShellMountPasswordDialog', Name: 'ShellMountPasswordSource',
Extends: ModalDialog.ModalDialog, Extends: MessageTray.Source,
_init: function(message, gicon, flags) { _init: function(message, icon, reaskPassword) {
let strings = message.split('\n'); let strings = message.split('\n');
this.parent({ styleClass: 'prompt-dialog' }); this.parent(strings[0]);
let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout', this._notification = new ShellMountPasswordNotification(this, strings, icon, reaskPassword);
vertical: false });
this.contentLayout.add(mainContentBox);
let icon = _createIcon(gicon); // add ourselves as a source, and popup the notification
mainContentBox.add(icon, Main.messageTray.add(this);
{ x_fill: true, this.notify(this._notification);
y_fill: false, },
x_align: St.Align.END, });
y_align: St.Align.START }); Signals.addSignalMethods(ShellMountPasswordSource.prototype);
this._messageBox = new St.BoxLayout({ style_class: 'prompt-dialog-message-layout', const ShellMountPasswordNotification = new Lang.Class({
vertical: true }); Name: 'ShellMountPasswordNotification',
mainContentBox.add(this._messageBox, Extends: MessageTray.Notification,
{ y_align: St.Align.START, expand: true, x_fill: true, y_fill: true });
let subject = new St.Label({ style_class: 'prompt-dialog-headline' }); _init: function(source, strings, icon, reaskPassword) {
this._messageBox.add(subject, this.parent(source, strings[0], null, { customContent: true, icon: icon });
{ y_fill: false,
y_align: St.Align.START }); // set the notification to transient and urgent, so that it
if (strings[0]) // expands out
subject.set_text(strings[0]); this.setTransient(true);
this.setUrgency(MessageTray.Urgency.CRITICAL);
let description = new St.Label({ style_class: 'prompt-dialog-description' });
description.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
description.clutter_text.line_wrap = true;
this._messageBox.add(description,
{ y_fill: true,
y_align: St.Align.START });
if (strings[1]) if (strings[1])
description.set_text(strings[1]); this.addBody(strings[1]);
this._passwordBox = new St.BoxLayout({ vertical: false, style_class: 'prompt-dialog-password-box' }); if (reaskPassword) {
this._messageBox.add(this._passwordBox); let label = new St.Label({ style_class: 'mount-password-reask',
text: _("Wrong password, please try again") });
this._passwordLabel = new St.Label(({ style_class: 'prompt-dialog-password-label', this.addActor(label);
text: _("Password") }));
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._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivate));
this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
this._passwordBox.add(this._passwordEntry, {expand: true });
this.setInitialKeyFocus(this._passwordEntry);
this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label',
text: _("Sorry, that didn\'t work. Please try again.") });
this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._errorMessageLabel.clutter_text.line_wrap = true;
this._errorMessageLabel.hide();
this._messageBox.add(this._errorMessageLabel);
if (flags & Gio.AskPasswordFlags.SAVING_SUPPORTED) {
this._rememberChoice = new CheckBox.CheckBox();
this._rememberChoice.getLabelActor().text = _("Remember Password");
this._rememberChoice.actor.checked = true;
this._messageBox.add(this._rememberChoice.actor);
} else {
this._rememberChoice = null;
} }
let buttons = [{ label: _("Cancel"), this._responseEntry = new St.Entry({ style_class: 'mount-password-entry',
action: Lang.bind(this, this._onCancelButton), can_focus: true });
key: Clutter.Escape this.setActionArea(this._responseEntry);
},
{ label: _("Unlock"),
action: Lang.bind(this, this._onUnlockButton),
default: true
}];
this.setButtons(buttons); this._responseEntry.clutter_text.connect('activate',
Lang.bind(this, this._onEntryActivated));
this._responseEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
this._responseEntry.grab_key_focus();
}, },
reaskPassword: function() { _onEntryActivated: function() {
this._passwordEntry.set_text(''); let text = this._responseEntry.get_text();
this._errorMessageLabel.show(); if (text == '')
}, return;
_onCancelButton: function() { this.source.emit('password-ready', text);
this.emit('response', -1, '', false);
},
_onUnlockButton: function() {
this._onEntryActivate();
},
_onEntryActivate: function() {
this.emit('response', 1,
this._passwordEntry.get_text(),
this._rememberChoice &&
this._rememberChoice.actor.checked);
} }
}); });
@ -430,14 +297,14 @@ const ShellProcessesDialog = new Lang.Class({
Name: 'ShellProcessesDialog', Name: 'ShellProcessesDialog',
Extends: ModalDialog.ModalDialog, Extends: ModalDialog.ModalDialog,
_init: function(gicon) { _init: function(icon) {
this.parent({ styleClass: 'show-processes-dialog' }); this.parent({ styleClass: 'show-processes-dialog' });
let mainContentLayout = new St.BoxLayout(); let mainContentLayout = new St.BoxLayout();
this.contentLayout.add(mainContentLayout, { x_fill: true, this.contentLayout.add(mainContentLayout, { x_fill: true,
y_fill: false }); y_fill: false });
this._iconBin = new St.Bin({ child: _createIcon(gicon) }); this._iconBin = new St.Bin({ child: icon });
mainContentLayout.add(this._iconBin, mainContentLayout.add(this._iconBin,
{ x_fill: true, { x_fill: true,
y_fill: false, y_fill: false,
@ -471,17 +338,21 @@ const ShellProcessesDialog = new Lang.Class({
scrollView.hide(); scrollView.hide();
this._applicationList = new St.BoxLayout({ vertical: true }); this._applicationList = new St.BoxLayout({ vertical: true });
scrollView.add_actor(this._applicationList); scrollView.add_actor(this._applicationList,
{ x_fill: true,
y_fill: true,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
this._applicationList.connect('actor-added', this._applicationList.connect('actor-added',
Lang.bind(this, function() { Lang.bind(this, function() {
if (this._applicationList.get_n_children() == 1) if (this._applicationList.get_children().length == 1)
scrollView.show(); scrollView.show();
})); }));
this._applicationList.connect('actor-removed', this._applicationList.connect('actor-removed',
Lang.bind(this, function() { Lang.bind(this, function() {
if (this._applicationList.get_n_children() == 0) if (this._applicationList.get_children().length == 0)
scrollView.hide(); scrollView.hide();
})); }));
}, },
@ -515,253 +386,3 @@ const ShellProcessesDialog = new Lang.Class({
} }
}); });
Signals.addSignalMethods(ShellProcessesDialog.prototype); Signals.addSignalMethods(ShellProcessesDialog.prototype);
const GnomeShellMountOpIface = <interface name="org.Gtk.MountOperationHandler">
<method name="AskPassword">
<arg type="s" direction="in" name="object_id"/>
<arg type="s" direction="in" name="message"/>
<arg type="s" direction="in" name="icon_name"/>
<arg type="s" direction="in" name="default_user"/>
<arg type="s" direction="in" name="default_domain"/>
<arg type="u" direction="in" name="flags"/>
<arg type="u" direction="out" name="response"/>
<arg type="a{sv}" direction="out" name="response_details"/>
</method>
<method name="AskQuestion">
<arg type="s" direction="in" name="object_id"/>
<arg type="s" direction="in" name="message"/>
<arg type="s" direction="in" name="icon_name"/>
<arg type="as" direction="in" name="choices"/>
<arg type="u" direction="out" name="response"/>
<arg type="a{sv}" direction="out" name="response_details"/>
</method>
<method name="ShowProcesses">
<arg type="s" direction="in" name="object_id"/>
<arg type="s" direction="in" name="message"/>
<arg type="s" direction="in" name="icon_name"/>
<arg type="ai" direction="in" name="application_pids"/>
<arg type="as" direction="in" name="choices"/>
<arg type="u" direction="out" name="response"/>
<arg type="a{sv}" direction="out" name="response_details"/>
</method>
<method name="Close"/>
</interface>;
const ShellMountOperationType = {
NONE: 0,
ASK_PASSWORD: 1,
ASK_QUESTION: 2,
SHOW_PROCESSES: 3
};
const GnomeShellMountOpHandler = new Lang.Class({
Name: 'GnomeShellMountOpHandler',
_init: function() {
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellMountOpIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gtk/MountOperationHandler');
Gio.bus_own_name_on_connection(Gio.DBus.session, 'org.gtk.MountOperationHandler',
Gio.BusNameOwnerFlags.REPLACE, null, null);
this._dialog = null;
this._volumeMonitor = Gio.VolumeMonitor.get();
this._ensureEmptyRequest();
},
_ensureEmptyRequest: function() {
this._currentId = null;
this._currentInvocation = null;
this._currentType = ShellMountOperationType.NONE;
},
_clearCurrentRequest: function(response, details) {
if (this._currentInvocation) {
this._currentInvocation.return_value(
GLib.Variant.new('(ua{sv})', [response, details]));
}
this._ensureEmptyRequest();
},
_setCurrentRequest: function(invocation, id, type) {
let oldId = this._currentId;
let oldType = this._currentType;
let requestId = id + '@' + invocation.get_sender();
this._clearCurrentRequest(Gio.MountOperationResult.UNHANDLED, {});
this._currentInvocation = invocation;
this._currentId = requestId;
this._currentType = type;
if (this._dialog && (oldId == requestId) && (oldType == type))
return true;
return false;
},
_closeDialog: function() {
if (this._dialog) {
this._dialog.close();
this._dialog = null;
}
},
_createGIcon: function(iconName) {
let realIconName = iconName ? iconName : 'drive-harddisk';
return new Gio.ThemedIcon({ name: realIconName,
use_default_fallbacks: true });
},
/**
* AskPassword:
* @id: an opaque ID identifying the object for which the operation is requested
* The ID must be unique in the context of the calling process.
* @message: the message to display
* @icon_name: the name of an icon to display
* @default_user: the default username for display
* @default_domain: the default domain for display
* @flags: a set of GAskPasswordFlags
* @response: a GMountOperationResult
* @response_details: a dictionary containing the response details as
* entered by the user. The dictionary MAY contain the following properties:
* - "password" -> (s): a password to be used to complete the mount operation
* - "password_save" -> (u): a GPasswordSave
*
* The dialog will stay visible until clients call the Close() method, or
* another dialog becomes visible.
* Calling AskPassword again for the same id will have the effect to clear
* the existing dialog and update it with a message indicating the previous
* attempt went wrong.
*/
AskPasswordAsync: function(params, invocation) {
let [id, message, iconName, defaultUser, defaultDomain, flags] = params;
if (this._setCurrentRequest(invocation, id, ShellMountOperationType.ASK_PASSWORD)) {
this._dialog.reaskPassword();
return;
}
this._closeDialog();
this._dialog = new ShellMountPasswordDialog(message, this._createGIcon(iconName), flags);
this._dialog.connect('response', Lang.bind(this,
function(object, choice, password, remember) {
let details = {};
let response;
if (choice == -1) {
response = Gio.MountOperationResult.ABORTED;
} else {
response = Gio.MountOperationResult.HANDLED;
let passSave = remember ? Gio.PasswordSave.PERMANENTLY : Gio.PasswordSave.NEVER;
details['password_save'] = GLib.Variant.new('u', passSave);
details['password'] = GLib.Variant.new('s', password);
}
this._clearCurrentRequest(response, details);
}));
this._dialog.open();
},
/**
* AskQuestion:
* @id: an opaque ID identifying the object for which the operation is requested
* The ID must be unique in the context of the calling process.
* @message: the message to display
* @icon_name: the name of an icon to display
* @choices: an array of choice strings
* GetResponse:
* @response: a GMountOperationResult
* @response_details: a dictionary containing the response details as
* entered by the user. The dictionary MAY contain the following properties:
* - "choice" -> (i): the chosen answer among the array of strings passed in
*
* The dialog will stay visible until clients call the Close() method, or
* another dialog becomes visible.
* Calling AskQuestion again for the same id will have the effect to clear
* update the dialog with the new question.
*/
AskQuestionAsync: function(params, invocation) {
let [id, message, iconName, choices] = params;
if (this._setCurrentRequest(invocation, id, ShellMountOperationType.ASK_QUESTION)) {
this._dialog.update(message, choices);
return;
}
this._closeDialog();
this._dialog = new ShellMountQuestionDialog(this._createGIcon(iconName), message);
this._dialog.connect('response', Lang.bind(this,
function(object, choice) {
this._clearCurrentRequest(Gio.MountOperationResult.HANDLED,
{ choice: GLib.Variant.new('i', choice) });
}));
this._dialog.update(message, choices);
this._dialog.open();
},
/**
* ShowProcesses:
* @id: an opaque ID identifying the object for which the operation is requested
* The ID must be unique in the context of the calling process.
* @message: the message to display
* @icon_name: the name of an icon to display
* @application_pids: the PIDs of the applications to display
* @choices: an array of choice strings
* @response: a GMountOperationResult
* @response_details: a dictionary containing the response details as
* entered by the user. The dictionary MAY contain the following properties:
* - "choice" -> (i): the chosen answer among the array of strings passed in
*
* The dialog will stay visible until clients call the Close() method, or
* another dialog becomes visible.
* Calling ShowProcesses again for the same id will have the effect to clear
* the existing dialog and update it with the new message and the new list
* of processes.
*/
ShowProcessesAsync: function(params, invocation) {
let [id, message, iconName, applicationPids, choices] = params;
if (this._setCurrentRequest(invocation, id, ShellMountOperationType.SHOW_PROCESSES)) {
this._dialog.update(message, applicationPids, choices);
return;
}
this._closeDialog();
this._dialog = new ShellProcessesDialog(this._createGIcon(iconName));
this._dialog.connect('response', Lang.bind(this,
function(object, choice) {
let response;
let details = {};
if (choice == -1) {
response = Gio.MountOperationResult.ABORTED;
} else {
response = Gio.MountOperationResult.HANDLED;
details['choice'] = GLib.Variant.new('i', choice);
}
this._clearCurrentRequest(response, details);
}));
this._dialog.update(message, applicationPids, choices);
this._dialog.open();
},
/**
* Close:
*
* Closes a dialog previously opened by AskPassword, AskQuestion or ShowProcesses.
* If no dialog is open, does nothing.
*/
Close: function(params, invocation) {
this._clearCurrentRequest(Gio.MountOperationResult.UNHANDLED, {});
this._closeDialog();
}
});

View File

@ -1,10 +1,18 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const GDesktopEnums = imports.gi.GDesktopEnums;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const St = imports.gi.St;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard'; const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable'; const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable';
@ -36,7 +44,7 @@ const ATIndicator = new Lang.Class({
Extends: PanelMenu.SystemStatusButton, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
this.parent('preferences-desktop-accessibility-symbolic', _("Accessibility")); this.parent('preferences-desktop-accessibility', _("Accessibility"));
let highContrast = this._buildHCItem(); let highContrast = this._buildHCItem();
this.menu.addMenuItem(highContrast); this.menu.addMenuItem(highContrast);
@ -48,9 +56,9 @@ const ATIndicator = new Lang.Class({
let textZoom = this._buildFontItem(); let textZoom = this._buildFontItem();
this.menu.addMenuItem(textZoom); this.menu.addMenuItem(textZoom);
let screenReader = this._buildItem(_("Screen Reader"), APPLICATIONS_SCHEMA, // let screenReader = this._buildItem(_("Screen Reader"), APPLICATIONS_SCHEMA,
'screen-reader-enabled'); // 'screen-reader-enabled');
this.menu.addMenuItem(screenReader); // this.menu.addMenuItem(screenReader);
let screenKeyboard = this._buildItem(_("Screen Keyboard"), APPLICATIONS_SCHEMA, let screenKeyboard = this._buildItem(_("Screen Keyboard"), APPLICATIONS_SCHEMA,
'screen-keyboard-enabled'); 'screen-keyboard-enabled');

View File

@ -1,11 +1,15 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gdk = imports.gi.Gdk;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const GnomeBluetoothApplet = imports.gi.GnomeBluetoothApplet; const GnomeBluetoothApplet = imports.gi.GnomeBluetoothApplet;
const GnomeBluetooth = imports.gi.GnomeBluetooth; const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop;
const St = imports.gi.St; const St = imports.gi.St;
const Shell = imports.gi.Shell;
const Main = imports.ui.main; const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
@ -24,7 +28,7 @@ const Indicator = new Lang.Class({
Extends: PanelMenu.SystemStatusButton, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
this.parent('bluetooth-disabled-symbolic', _("Bluetooth")); this.parent('bluetooth-disabled', _("Bluetooth"));
this._applet = new GnomeBluetoothApplet.Applet(); this._applet = new GnomeBluetoothApplet.Applet();
@ -32,11 +36,11 @@ const Indicator = new Lang.Class({
this._applet.connect('notify::killswitch-state', Lang.bind(this, this._updateKillswitch)); this._applet.connect('notify::killswitch-state', Lang.bind(this, this._updateKillswitch));
this._killswitch.connect('toggled', Lang.bind(this, function() { this._killswitch.connect('toggled', Lang.bind(this, function() {
let current_state = this._applet.killswitch_state; let current_state = this._applet.killswitch_state;
if (current_state != GnomeBluetooth.KillswitchState.HARD_BLOCKED && if (current_state != GnomeBluetoothApplet.KillswitchState.HARD_BLOCKED &&
current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER) { current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER) {
this._applet.killswitch_state = this._killswitch.state ? this._applet.killswitch_state = this._killswitch.state ?
GnomeBluetooth.KillswitchState.UNBLOCKED: GnomeBluetoothApplet.KillswitchState.UNBLOCKED:
GnomeBluetooth.KillswitchState.SOFT_BLOCKED; GnomeBluetoothApplet.KillswitchState.SOFT_BLOCKED;
} else } else
this._killswitch.setToggleState(false); this._killswitch.setToggleState(false);
})); }));
@ -90,10 +94,10 @@ const Indicator = new Lang.Class({
_updateKillswitch: function() { _updateKillswitch: function() {
let current_state = this._applet.killswitch_state; let current_state = this._applet.killswitch_state;
let on = current_state == GnomeBluetooth.KillswitchState.UNBLOCKED; let on = current_state == GnomeBluetoothApplet.KillswitchState.UNBLOCKED;
let has_adapter = current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER; let has_adapter = current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER;
let can_toggle = current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER && let can_toggle = current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER &&
current_state != GnomeBluetooth.KillswitchState.HARD_BLOCKED; current_state != GnomeBluetoothApplet.KillswitchState.HARD_BLOCKED;
this._killswitch.setToggleState(on); this._killswitch.setToggleState(on);
if (can_toggle) if (can_toggle)
@ -106,10 +110,10 @@ const Indicator = new Lang.Class({
if (on) { if (on) {
this._discoverable.actor.show(); this._discoverable.actor.show();
this.setIcon('bluetooth-active-symbolic'); this.setIcon('bluetooth-active');
} else { } else {
this._discoverable.actor.hide(); this._discoverable.actor.hide();
this.setIcon('bluetooth-disabled-symbolic'); this.setIcon('bluetooth-disabled');
} }
}, },
@ -301,7 +305,7 @@ const Indicator = new Lang.Class({
_ensureSource: function() { _ensureSource: function() {
if (!this._source) { if (!this._source) {
this._source = new MessageTray.Source(_("Bluetooth"), 'bluetooth-active'); this._source = new Source();
Main.messageTray.add(this._source); Main.messageTray.add(this._source);
} }
}, },
@ -326,6 +330,35 @@ const Indicator = new Lang.Class({
} }
}); });
const Source = new Lang.Class({
Name: 'BluetoothSource',
Extends: MessageTray.Source,
_init: function() {
this.parent(_("Bluetooth"));
this._setSummaryIcon(this.createNotificationIcon());
},
notify: function(notification) {
this._private_destroyId = notification.connect('destroy', Lang.bind(this, function(notification) {
if (this.notification == notification) {
// the destroyed notification is the last for this source
this.notification.disconnect(this._private_destroyId);
this.destroy();
}
}));
this.parent(notification);
},
createNotificationIcon: function() {
return new St.Icon({ icon_name: 'bluetooth-active',
icon_type: St.IconType.SYMBOLIC,
icon_size: this.ICON_SIZE });
}
});
const AuthNotification = new Lang.Class({ const AuthNotification = new Lang.Class({
Name: 'AuthNotification', Name: 'AuthNotification',
Extends: MessageTray.Notification, Extends: MessageTray.Notification,
@ -376,7 +409,7 @@ const ConfirmNotification = new Lang.Class({
this._applet = applet; this._applet = applet;
this._devicePath = device_path; this._devicePath = device_path;
this.addBody(_("Device %s wants to pair with this computer").format(long_name)); this.addBody(_("Device %s wants to pair with this computer").format(long_name));
this.addBody(_("Please confirm whether the PIN '%06d' matches the one on the device.").format(pin)); this.addBody(_("Please confirm whether the PIN '%s' matches the one on the device.").format(pin));
this.addButton('matches', _("Matches")); this.addButton('matches', _("Matches"));
this.addButton('does-not-match', _("Does not match")); this.addButton('does-not-match', _("Does not match"));
@ -412,8 +445,7 @@ const PinNotification = new Lang.Class({
this._entry.connect('key-release-event', Lang.bind(this, function(entry, event) { this._entry.connect('key-release-event', Lang.bind(this, function(entry, event) {
let key = event.get_key_symbol(); let key = event.get_key_symbol();
if (key == Clutter.KEY_Return) { if (key == Clutter.KEY_Return) {
if (this._canActivateOkButton()) this.emit('action-invoked', 'ok');
this.emit('action-invoked', 'ok');
return true; return true;
} else if (key == Clutter.KEY_Escape) { } else if (key == Clutter.KEY_Escape) {
this.emit('action-invoked', 'cancel'); this.emit('action-invoked', 'cancel');
@ -426,12 +458,6 @@ const PinNotification = new Lang.Class({
this.addButton('ok', _("OK")); this.addButton('ok', _("OK"));
this.addButton('cancel', _("Cancel")); this.addButton('cancel', _("Cancel"));
this.setButtonSensitive('ok', this._canActivateOkButton());
this._entry.clutter_text.connect('text-changed', Lang.bind(this,
function() {
this.setButtonSensitive('ok', this._canActivateOkButton());
}));
this.connect('action-invoked', Lang.bind(this, function(self, action) { this.connect('action-invoked', Lang.bind(this, function(self, action) {
if (action == 'ok') { if (action == 'ok') {
if (this._numeric) { if (this._numeric) {
@ -454,14 +480,6 @@ const PinNotification = new Lang.Class({
})); }));
}, },
_canActivateOkButton: function() {
// PINs have a fixed length of 6
if (this._numeric)
return this._entry.clutter_text.text.length == 6;
else
return true;
},
grabFocus: function(lockTray) { grabFocus: function(lockTray) {
this.parent(lockTray); this.parent(lockTray);
global.stage.set_key_focus(this._entry); global.stage.set_key_focus(this._entry);

Some files were not shown because too many files have changed in this diff Show More