Compare commits

..

4 Commits

Author SHA1 Message Date
11bf65e2b4 status/keyboard: Disregard the backwards flag for the popup switcher
This seems to cause more harm than good. It puzzles users looking at
the shortcuts list in g-c-c and it prevents otherwise valid shortcuts
like Shift+Hangul from working.

https://bugzilla.gnome.org/show_bug.cgi?id=695823
2013-04-05 18:12:48 +02:00
a8b7e559aa status/keyboard: Allow switching input source in the message tray
We still can't show a popup switcher in the message tray but we can at
least degrade gracefully and advance to the next input source.

https://bugzilla.gnome.org/show_bug.cgi?id=697009
2013-04-05 18:12:48 +02:00
b733e30a4a status/keyboard: Switch input source on special modifiers accelerator
This simply mimics the X server's layout switching behavior by
advancing to the next input source and wrapping around.

https://bugzilla.gnome.org/show_bug.cgi?id=697008
2013-04-05 18:12:48 +02:00
b607958134 status/keyboard: Synchronize input source switching with key events
Currently we simply set the gsettings key when activating an input
source. This obviously introduces a time window, between the event that
activates the switch and when the switch is complete, under which key
events are being delivered to applications and interpreted according
to the previous input source.

The patches in bug 696996 introduce a DBus API in g-s-d that allows us
to know when an input source if effectively active. Using that and
freezing keyboard events in the X server until we hear back from g-s-d
we can ensure that events won't be misinterpreted after an input
source switch.

https://bugzilla.gnome.org/show_bug.cgi?id=697007
2013-04-05 18:12:47 +02:00
251 changed files with 56217 additions and 57657 deletions

5
.gitignore vendored
View File

@ -19,8 +19,6 @@ configure
data/50-gnome-shell-*.xml 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-wayland.desktop
data/gnome-shell-wayland.desktop.in
data/gnome-shell-extension-prefs.desktop data/gnome-shell-extension-prefs.desktop
data/gnome-shell-extension-prefs.desktop.in data/gnome-shell-extension-prefs.desktop.in
data/gschemas.compiled data/gschemas.compiled
@ -73,14 +71,13 @@ 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
src/gnome-shell-extension-prefs
src/gnome-shell-extension-tool src/gnome-shell-extension-tool
src/gnome-shell-extension-prefs
src/gnome-shell-hotplug-sniffer src/gnome-shell-hotplug-sniffer
src/gnome-shell-jhbuild src/gnome-shell-jhbuild
src/gnome-shell-perf-helper src/gnome-shell-perf-helper
src/gnome-shell-perf-tool src/gnome-shell-perf-tool
src/gnome-shell-real src/gnome-shell-real
src/gnome-shell-wayland
src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service
src/run-js-test src/run-js-test
src/test-recorder src/test-recorder

View File

@ -138,8 +138,8 @@ GObjects, although this feature isn't used very often in the Shell itself.
_init: function(icon, label) { _init: function(icon, label) {
this.parent({ reactive: false }); this.parent({ reactive: false });
this.actor.add_child(icon); this.addActor(icon);
this.actor.add_child(label); this.addActor(label);
}, },
open: function() { open: function() {

409
NEWS
View File

@ -1,412 +1,3 @@
3.10.1
======
* Make sure lock screen is drawn once before switching user [Giovanni; #708051]
* Fix signal strength indicators in network selector [Jasper; #708442]
* Scroll search results when focusing provider icons [Jasper; #708868]
* Add separate hover/active states to page indicators [Carlos; #708852]
* Tweak appearance of user name and avatar [Yash; #702309]
* Hide "Turn On" in network menu when disabled by hardware [Giovanni; #709635]
* Cancel open keyring prompts when the screen is locked [Florian; #708910]
* Differentiate "Not Connected" and "Off" in network menu [Giovanni; #709043]
* Make network settings items point to the right device [Giovanni; #709246]
* Remove animation of window preview titles [Sebastien; #709392]
* Add 'Notifications' switch to tray menu [Florian; #707073]
* Make dropdown arrows consistent [Carlos; #709564]
* power: Use icon from primary device for status [Jasper; #709925]
* Fix XDND drags to overview [Adel; #708887]
* Fix workspace switcher disappearing with too many workspaces [Jasper; #694881]
* Handle search results with 'special:' prefix specially [Giovanni; #707055]
* gdm: Support pre-authenticated logins from oVirt [Vinzenz; #702162]
* Use ARROW role for labels representing arrows [Alejandro; #710120]
* Make selected view in app picker persistent [Florian; #710042]
* Make network selector navigable by keyboard [Alejandro; #710144]
* Misc bug fixes [Florian, Adel, Jasper, Aleksander, Giovanni, Dan, Michael,
Tim; #709034, #709263, #698486, #709286, #709248, #709543, #696564, #703265,
#709638, #709866, #709998, #710019, #710104, #710115]
Contributors:
Giovanni Campagna, Michael Catanzaro, Vinzenz Feenstra, Adel Gadllah,
Yash Girdhar, Sebastien Lafargue, Tim Lunn, Aleksander Morgado,
Florian Müllner, Alejandro Piñeiro, Carlos Soriano, Jasper St. Pierre,
Dieter Verfaillie, Dan Winship
Translations:
Inaki Larranaga Murgoitio [eu], Christian Kirbach [de], Muhammet Kara [tr],
Aurimas Černius [lt], Ryan Lortie [eo], Rūdolfs Mazurs [lv],
Dušan Kazik [sk], Fran Diéguez [gl], Enrico Nicoletto [pt_BR],
Kjartan Maraas [nb], Victor Ibragimov [tg], Matej Urbančič [sl],
A S Alam [pa], Nilamdyuti Goswami [as], Daniel Mustieles [es],
Cheng-Chia Tseng [zh_HK, zh_TW], Mattias Põldaru [et], Kenneth Nielsen [da],
Milo Casagrande [it], Marek Černocký [cs], Ihar Hrachyshka [be],
Мирослав Николић [sr, sr@latin], Arash Mousavi [fa], Yuri Myasoedov [ru],
Gil Forcada [ca], Carles Ferrando [ca@valencia], Andika Triwidada [id],
Timo Jyrinki [fi], Piotr Drąg [pl], Rafael Ferreira [pt_BR],
Gabor Kelemen [hu], Yosef Or Boczko [he], Daniel Korostil [uk],
Wouter Bolsterlee [nl], António Lima [pt]
3.10.0.1
=========
* Fix login screen [Ray; #708691]
Contributors:
Ray Strode, Giovanni Campagna, Jasper St. Pierree
Translations:
Kjartan Maraas [nb], Marek Černocký [cs], A S Alam [pa], Daniel Mustieles [es],
Ihar Hrachyshka [be], Chao-Hsiung Liao [zh_HK], Nilamdyuti Goswami [as],
Yuri Myasoedov [ru], Baurzhan Muftakhidinov [kk]
3.10.0
======
* Fix fade effect in ScrollViews [Carlos; #708256]
* network: Resync when activating connection changes [Jasper; #708322]
* Close run dialog when the screen locks [Florian; #708218]
* Fix entry growing out of password dialogs [Florian; #708324, #703833]
* Vertically center labels in submenu items [Jasper; #708330]
* https://bugzilla.gnome.org/show_bug.cgi?id=708387 [Mike; #708387]
* Fix bluetooth icon not being added to status menu [Jasper; #708541]
* Fix GNOME 2 keyring dialogs appearing on lock screen [Florian; #708187]
* Fix passwords being cleared twice when authentication fails [Florian; #708186]
* Fix message tray appearing in a11y popup on login screen [Florian; #708380]
* Increase width of aggregate menu popup [Adel; #708472]
Contributors:
Adel Gadllah, Mike Gorse, Ryan Lortie, Florian Müllner, Frédéric Péters,
Carlos Soriano, Jasper St. Pierre, Rico Tzschichholz
Translations:
Daniel Șerbănescu [ro], Ryan Lortie [eo], Ihar Hrachyshka [be],
A S Alam [pa], Jiro Matsuzawa [ja], Chao-Hsiung Liao [zh_HK, zh_TW],
Piotr Drąg [pl], Kristjan SCHMIDT [eo], Daniel Korostil [uk],
Rūdolfs Mazurs [lv], Reinout van Schouwen [nl], Yosef Or Boczko [he],
Fran Diéguez [gl], António Lima [pt], Andika Triwidada [id],
Alexandre Franke [fr], Rafael Ferreira [pt_BR], Milo Casagrande [it],
Kenneth Nielsen [da], Matej Urbančič [sl]
3.9.92
======
* Don't show page indicators if there's only one page [Florian; #707363]
* Make :active style of app and non-app results consistent [Jakub; #704714]
* Fade app pages when scrolled [Florian; #707409]
* Don't block scrolling on page indicators [Carlos; #707609]
* Tweak visual appearance of folder views [Florian; #707662]
* Don't put minimized apps at the end of the app switcher [Florian; #707663]
* Merge the wayland branch [Giovanni, Neil; #707467]
* Make search entry behave better in RTL locales [Matthias, Florian; #705779]
* Allow to change app pages with pageUp/pageDown keys [Carlos; #707979]
* Set approriate a11y states on expandable menu items [Alejandro; #708038]
* Improve page indicator animation [Carlos; #707565]
* Misc bug fixes and cleanups [Florian, Olivier, Jasper, Giovanni, Magdalen,
Adel, Carlos, Rico, Joanmarie; #707308, #707430, #707508, #707557, #707600,
#707614, #707666, #707814, #707806, #707801, #707889, #707892, #707935,
#707842, #707940, #707996, #708007, #708009, #708020, #707580, #708080]
Contributors:
Magdalen Berns, Olivier Blin, Giovanni Campagna, Matthias Clasen,
Joanmarie Diggs, Adel Gadllah, Florian Müllner, Alejandro Piñeiro,
Neil Roberts, Carlos Soriano, Jasper St. Pierre, Jakub Steiner,
Rico Tzschichholz
Translations:
Rafael Ferreira [pt_BR], Fran Diéguez [gl], Daniel Mustieles [es],
Aurimas Černius [lt], Luca Ferretti [it], Piotr Drąg [pl],
Chao-Hsiung Liao [zh_HK, zh_TW], Timo Jyrinki [fi], Daniel Korostil [uk],
Dušan Kazik [sk], Adam Matoušek [cs], Marek Černocký [cs],
Jiro Matsuzawa [ja], Yuri Myasoedov [ru], Tobias Endrigkeit [de],
Kjartan Maraas [nb], Victor Ibragimov [tg], Мирослав Николић [sr, sr@latin],
A S Alam [pa], Khaled Hosny [ar], Andika Triwidada [id],
Nilamdyuti Goswami [as], Ihar Hrachyshka [be], Rūdolfs Mazurs [lv],
Mattias Põldaru [et], Gabor Kelemen [hu], Bruce Cowan [en_GB],
Matej Urbančič [sl], Enrico Nicoletto [pt_BR], Benjamin Steinwender [de],
Changwoo Ryu [ko], Kris Thomsen [da], Alexandre Franke [fr],
Evgeny Bobkin [ru], Baurzhan Muftakhidinov [kk], Peter Mráz [sk],
Inaki Larranaga Murgoitio [eu], Yosef Or Boczko [he]
3.9.91
======
* Improve submenu styling [Jakub; #706037]
* Fix changing slider values via keyboard [Alejandro; #706386]
* Fix accessibility of sliders [Alejandro; #706391]
* Tweak system actions style [Jakub; #706638]
* Add support for auth without username / fix Not Listed? [Ray; #706607]
* Dash: Don't show tooltips for apps with open popups [Giovanni; #705611]
* Implement new end-session/power-off dialog design [Jasper, Matthias; #706612]
* Implement building separate binaries for x11 and wayland [Giovanni; #705497]
* authPrompt: Fix controls moving when showing messages [Ray; #706670]
* Tweak padding between system status icons [Allan; #706796]
* Add a generic accessible usable by JS code [Alejandro; #648623]
* Improve keynav and accessibility of the calendar [Alejandro; #706903]
* Update to new NetworkManager APIs [Jasper; #706098]
* Hide system actions section in the lock screen [Jasper; #706852]
* Don't show other logged in users at log out [Giovanni; #707124]
* Remove "Session" subtitle heading in login dialog [Jasper; #707072]
* dash: Reload favorites when installed apps change [Giovanni; #706878]
* Don't open overview after closing last window on workspace [Florian; #662581]
* Add FocusApp DBus method [Giovanni; #654086]
* Add ShowApplications DBus method [Giovanni; #698743]
* Implement new app picker design [Carlos, Florian; #706081]
* Improve frequent apps being empty by default [Carlos, Florian; #694710]
* Extend clickable area of page indicators [Giovanni; #707314]
* Misc bug fixes [Ray, Giovanni, Jasper, Emmanuele; #706542, #706654, #706005,
#706681, #706841, #706843, #707064, #706262, #707197, #707269]
Contributors:
Emmanuele Bassi, Giovanni Campagna, Matthias Clasen, Allan Day, Adel Gadllah,
Florian Müllner, Alejandro Piñeiro, Carlos Soriano, Jasper St. Pierre,
Jakub Steiner, Ray Strode, Seán de Búrca
Translations:
Piotr Drąg [pl], Kjartan Maraas [nb], Victor Ibragimov [tg],
Enrico Nicoletto [pt_BR], Benjamin Steinwender [de],
Baurzhan Muftakhidinov [kk], Aurimas Černius [lt], Seán de Búrca [ga],
Fran Diéguez [gl], Daniel Mustieles [es], Dušan Kazik [sk],
Matej Urbančič [sl], Andika Triwidada [id], Jordi Mas [ca],
Ihar Hrachyshka [be]
3.9.90
======
* workspaceThumbnails: Exclude transient windows when shifting workspaces
[Bradley; #705174]
* Never show a horizontal scrollbar on lock screen [Jasper; #704327]
* authPrompt: Fix disable-user-list / Not Listed? [Ray; #705370]
* Animate the lock screen notification transitions [Giovanni; #687660]
* Wake up the screen when new notifications appear [Giovanni; #703084]
* Use StartupWMClass for application matching [Giovanni; #673657, #705801]
* dateMenu: Add style class for the clock label [Jonh; #705634]
* keyboard: Translate IBus IME name if possible [Daiki; #695673]
* power: Display single digit minutes correctly [Sebastian; #705803]
* Implement new aggregate status menu [Jasper; #705845]
* Improve triangle animation when expanding sub-menus [Tarun; #703109]
* Fix alignment of search provider icons [Tarun; #695760]
* Slide dash and workspace switcher on overview transitions [Tarun; #694262]
* Respect always-show-universal-access-status setting [Tanner; #705733]
* Handle .desktop files with capital letters [Giovanni; #706252]
* authPrompt: Add smartcard support [Ray; #683437]
* Fix call notifications in busy mode [Emilio; #666221]
* Improve triangle animation when expanding sub-menus [Tarun; #703109]
* Move message tray menu to a tray button [Jasper; #699272]
* Wi-fi dialog improvements [Jasper, Allan; #705916, #706136]
* Work towards running as wayland compositor [Giovanni]
- Switch to Mutter abstraction layer for cursor tracking [#705911]
- Add confirmation dialog for display changes [#706208]
* Use a different background in screen shield [Giovanni; #688210]
* Add fade animation before blanking the screen [Giovanni; #699112]
* Misc. bugfixes and cleanups [Jasper, Giovanni, Adel, Colin, Ray, Florian,
Magdalen; #704448, #702536, #686855, #695581, #700901, #701761, #701495,
#701848, #697833, #701731, #705664, #705840, #705898, #706089, #706153,
#704646, #706262, #706324, #703810, #703811, #704015, #706232, #705917,
#706536]
Contributors:
Magdalen Berns, Giovanni Campagna, Allan Day, Tanner Doshier, Adel Gadllah,
Sebastian Keller, Tarun Kumar Joshi, Florian Müllner, Bradley Pankow,
Emilio Pozuelo Monfort, Jasper St. Pierre, Ray Strode, Rico Tzschichholz,
Daiki Ueno, Colin Walters, Jonh Wendell
Translations:
Kjartan Maraas [nb], Aurimas Černius [lt], Yaron Shahrabani [he],
Fran Diéguez [gl], Gabor Kelemen [hu],
Juan Diego Martins da Costa Cruz [pt_BR], Inaki Larranaga Murgoitio [eu],
Yuri Myasoedov [ru], Daniel Mustieles [es], Seán de Búrca [ga],
Khaled Hosny [ar], Victor Ibragimov [tg], Friedel Wolff [af],
Marek Černocký [cs], Matej Urbančič [sl], A S Alam [pa],
Rafael Ferreira [pt_BR], Andika Triwidada [id], Dušan Kazik [sk]
3.9.5
=====
* Fix width changes of the calendar popup [Florian; #704200]
* Work towards aggregate status menu [Jasper; #702539, #704336, #704368,
#704670]
* Update design of lock screen notifications [Allan; #702305]
* Don't show empty backgroundMenu [Michael; #703868]
* Add option to limit app switcher to current workspace [Adel; #703538]
* Consolidate design of login screen and unlock dialog [Ray; #702308, #704795]
* Respect hasWorkspace property of session mode [Jasper; #698593]
* Fix fade of app menu icon in RTL locales [Jasper; #704583]
* Destroy notifications when the close button is clicked [Adel; #687016]
* Fix clicks on legacy tray icons in the message tray [Florian; #704095]
* authPrompt: Fade out message if users start to type [Ray; #704817]
* Export timestamps of global shortcuts on DBus [Bastien; #704859]
* Fix duplicate search provider results [Jasper; #700283]
* Misc bug fixes and cleanups [Lionel, Florian, Emilio, Ray, Jasper; #703859,
#703540, #704077, #703997, #704318, #704347, #704265, #704411, #704430,
#704347, #704453, #704471, #704542, #704707, #703905, #705037]
Contributors:
Allan Day, Adel Gadllah, Lionel Landwerlin, Florian Müllner, Bastien Nocera,
Emilio Pozuelo Monfort, Jasper St. Pierre, Ray Strode, Colin Walters,
Michael Wood
Translations:
eternalhui [zh_CN], Victor Ibragimov [tg], Dušan Kazik [sk],
Jiro Matsuzawa [ja], Kjartan Maraas [nb], Milo Casagrande [it],
Marek Černocký [cs], Daniel Mustieles [es], Benjamin Steinwender [de]
3.9.4
=====
* Fix chat entries not being focused when expanded [Jasper; #698778]
* Fix alignment of "Not Listed?" label [Mathieu; #702307]
* Fix alignment of time stamps in chat notifications [Carlos; #687809]
* Round the ends of slider trough [Jasper; #702825]
* Add support for "box-shadow: none" [Cosimo; #702782]
* Keep chrome below popup windows [Florian; #702338]
* Move the session list to a popup menu [Ray; #702818]
* Fix autorun notifications for "non-native" volumes [Matthias; #703418]
* dnd: Speed up by not picking on each motion event [Jasper; #703443]
* Fix management of asynchronous background loading [Lionel; #703001]
* Rework focus handling [Jasper; #700735]
* Optimize box-shadow rendering [Lionel; #689858]
* Remove support for fixed positioning in BoxLayouts [Florian; #703808]
* Misc bug fixes and cleanups [Adel, Jasper, Florian, Ray, Lionel, Emilio;
#702849, #610279, #703132, #703105, #703160, #703126, #703304, #703403,
#698593, #703442, #703565, #700901, #703874, #703807, #703893, #703909]
Contributors:
Mathieu Bridon, Giovanni Campagna, Cosimo Cecchi, Matthias Clasen,
Fran Diéguez, Adel Gadllah, Lionel Landwerlin, Florian Müllner,
Emilio Pozuelo Monfort, Carlos Soriano, Jasper St. Pierre, Ray Strode
Translations:
Baurzhan Muftakhidinov [kk], Marek Černocký [cs], Daniel Mustieles [es],
Fran Diéguez [gl], Kjartan Maraas [nb], Andika Triwidada [id],
Benjamin Steinwender [de], Nguyễn Thái Ngọc Duy [vi], Trần Ngọc Quân [vi]
3.9.3
=====
* Don't push window thumbs when workspace switcher is hidden [Jasper; #701167]
* Tweak timeout for activating windows during XDND [Adel; #700150]
* Fix ellipsization in control buttons in app picker [Carlos; #696307]
* Fix DND to empty dash [Florian; #684618]
* Fix OSD window appearing below system modal dialogs [Rui; #701269]
* Clear clipboard on screen lock to prevent information leak [Florian; #698922]
* Allow session mode specific overrides schema [Florian; #701717]
* window-switcher: Only show windows from current workspace by default
[Florian; #701214]
* logout dialog: Show the correct text right away [Matthias; #702056]
* bluetooth: Port to bluez 5 [Emilio; #700891]
* dateMenu: Allow events to span multiple lines [Giovanni; #701231]
* gdm: Clear message queue when no more messages are pending [Jonh; #702458]
* Misc bug fixes and cleanups [Jasper, Florian, Adel, Giovanni; #693836,
#700972, #701386, #700877, #701755, #698918, #701224, #702125, #701954,
#701849, #702121]
Contributors:
Giovanni Campagna, Matthias Clasen, Fran Diéguez, Adel Gadllah, Rui Matos,
Florian Müllner, Emilio Pozuelo Monfort, Carlos Soriano, Jasper St. Pierre,
Jonh Wendell
Translations:
Marek Černocký [cs], Victor Ibragimov [tg], Fran Diéguez [gl],
Benjamin Steinwender [de], Cheng-Chia Tseng [zh_HK, zh_TW],
eternalhui [zh_CN], Ivaylo Valkov [bg], Kjartan Maraas [nb],
Daniel Mustieles [es]
3.9.2
=====
* Use a symbolic icon for DESKTOP windows [Matthias; #697914]
* Move paint state cache into StWidget [Jasper; #697274]
* gdm: Fix regression where domain login hint not shown [Stef; #698200]
* Make calendar keyboard navigable [Tanner; #667434]
* Hide "Open Calendar" item when no calendar app is installed [Lionel; #697725]
* Update how branding appears on login screen [Florian; #694912, #699877]
* Allow OSD popups to grow if necessary [Marta; #696523]
* Fix offset of shadow offscreen rendering [Lionel; #698301]
* Fix insensitive button preventing empty keyring password [Stef; #696304]
* Allow cancelling keyring dialog between prompts [Stef; #682830]
* modalDialog: Show spinner while working [Stef; #684438]
* overview: Only show close buttons for windows that may close [Jasper; #699269]
* Add input purpose and hints to StEntry and StIMText [Daiki; #691392]
* Set input-purpose property for password entries [Rui; #700043]
* Provide a DBus API for screencasting [Florian; #696247]
* overview: Disable hotcorner during DND [Jasper; #698484]
* polkitAgent: Allow retrying after mistyped passwords [Stef; #684431]
* Add a way to get backtraces from criticals and warnings [Giovanni; #700262]
* Allow switch-to-workspace-n keybindings in overview [Florian; #649977]
* Update man page [Matthias; #700339]
* Add FocusSearch DBus method [Florian; #700536]
* Hide frequent view when app monitoring is disabled [Florian; #699714]
* Show switcher popup for switch-to-workspace-n keybindings [Elad; #659288]
* gdm: Update the session chooser style [Allan; #695742]
* Fix some app folders getting truncated at the top [Florian; #694371]
* Don't block the message tray while a notification is showing [Jasper; #700639]
* popupMenu: Allow for an optional border for slider handle [Florian; #697917]
* Re-lock screen when restarted after a crash [Colin; #691987]
* Synchronize input source switching with key events [Rui; #697007]
* Switch input source on modifiers-only accelerator [Rui; #697008]
* Allow input source switching in message tray [Rui; #697009]
* Misc bug fixes and cleanups [Alban, Jasper, Giovanni, Florian, Rui, Tomeu,
Stef, Gustavo; #698863, #699799, #699800, #676285, #699975, #700097, #698812,
#698486, #700194, #695314, #700257, #699678, #700356, #700322, #700394,
#700409, #700595, #700625, #691746, #700620, #700807, #659288, #700784,
#700842, #700847, #700488, #700735, #696159, #700900, #700853, #700923,
#700944, #697661, #700854, #700190, #699189, #701097]
Contributors:
Elad Alfassa, Alban Browaeys, Giovanni Campagna, Matthias Clasen, Allan Day,
Tanner Doshier, Lionel Landwerlin, Rui Matos, Simon McVittie,
Marta Milakovic, Florian Müllner, Gustavo Padovan, Jasper St. Pierre,
Daiki Ueno, Tomeu Vizoso, Stef Walter, Colin Walters
Translations:
Matej Urbančič [sl], Kjartan Maraas [nb], Victor Ibragimov [tg],
Dušan Kazik [sk], Gil Forcada [ca], Daniel Mustieles [es]
3.9.1
=====
* Add additional toggle-overview keybinding [Matthias; #698251]
* Disable <super> shortcut when sticky keys are enabled [Matthias; #685974]
* Disable tray context menu while a notification displays [Jasper; #695800]
* Watch GApplication busy state [Cosimo; #697207]
* Disable style transitions if animations are disabled [Jasper; #698391]
* Filter out hidden applications from "Frequent" view [Giovanni; #696949]
* Fix window previews swapping place randomly [Jasper; #694469, #698776]
* Add support for serialized GIcons in remote search providers [Cosimo; #698761]
* Fix hotcorner regression in RTL locales [Jasper; #698884]
* Allow some keybindings to work while a top bar menu is open [Florian; #698938]
* Make open-app-menu keybinding a toggle action [Florian; #686756]
* Only recognize common URL schemes in notification messages [Monica; #661225]
* Misc fixes and cleanups [Tim, Jasper, Florian, Giovanni, Owen; #698531,
#698622, #698427, #698483, #698513, #697203, #698959, #698918, #699029,
#699075, #696720, #649748]
Contributors:
Giovanni Campagna, Cosimo Cecchi, Monica Chelliah, Matthias Clasen, Tim Lunn,
Florian Müllner, Jasper St. Pierre, Michael Wood, Owen W. Taylor
Translations:
Fran Diéguez [gl], Muhammet Kara [tr], Daniel Mustieles [es],
Gil Forcada [ia], Anish A [ml], Dimitris Spingos [el], Marek Černocký [cs],
Žygimantas Beručka [lt]
3.8.1
=====
* Clip window group during startup animation [Jasper; #696323]
* Check for logind rather than systemd [Martin; #696252]
* Don't special-case last remote search provider position [Giovanni; #694974]
* Fix memory leaks [Ray, Jasper; ##697119, #697295, #697300, #697395]
* AppSwitcherPopup: Activate only the selected window if any [Rui; #697480]
* Enable screen recorder keybinding in all modes [Florian; #696200]
* Remove box-shadow from screen shield for performance reasons [Adel; #697274]
* Add support for -st-natural-width/height CSS properties [Giovanni; #664411]
* Remove excessive padding from notification buttons [Allan; #664411]
* Fix thumbnail dragging in overview [Jasper; #697504]
* theme-node: Add get_url()/lookup_url() methods [Florian; #693688]
* Misc bug fixes and cleanups [Jasper, Rui, Colin, David, Ray, Matthias:
#695859, #696259, #696585, #696436, #697432, #697435, #697560, #697722,
#697709]
Contributors:
Giovanni Campagna, Matthias Clasen, Allan Day, Adel Gadllah, David Gumberg,
Rui Matos, Florian Müllner, Martin Pitt, Jasper St. Pierre, Ray Strode,
Colin Walters
Translations:
Daniel Martinez [an], Bruce Cowan [en_GB], Khaled Hosny [ar],
Ihar Hrachyshka [be], Aron Xu [zh_CN], Wojciech Szczęsny [pl],
Inaki Larranaga Murgoitio [eu], Kjartan Maraas [nb],
Милош Поповић [sr, sr@latin], Trần Ngọc Quân [vi]
3.8.0.1 3.8.0.1
======= =======
* Background bug fixes [Ray; #696712] * Background bug fixes [Ray; #696712]

View File

@ -17,4 +17,5 @@ libgnome_shell_browser_plugin_la_SOURCES = \
libgnome_shell_browser_plugin_la_CFLAGS = \ libgnome_shell_browser_plugin_la_CFLAGS = \
$(BROWSER_PLUGIN_CFLAGS) \ $(BROWSER_PLUGIN_CFLAGS) \
-DG_DISABLE_DEPRECATED \
-DG_LOG_DOMAIN=\"GnomeShellBrowserPlugin\" -DG_LOG_DOMAIN=\"GnomeShellBrowserPlugin\"

View File

@ -1,5 +1,5 @@
AC_PREREQ(2.63) AC_PREREQ(2.63)
AC_INIT([gnome-shell],[3.10.1],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell]) AC_INIT([gnome-shell],[3.8.0.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])
@ -24,6 +24,9 @@ LT_INIT([disable-static])
# i18n # i18n
IT_PROG_INTLTOOL([0.40]) IT_PROG_INTLTOOL([0.40])
AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION([0.17])
GETTEXT_PACKAGE=gnome-shell GETTEXT_PACKAGE=gnome-shell
AC_SUBST(GETTEXT_PACKAGE) AC_SUBST(GETTEXT_PACKAGE)
AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE",
@ -50,7 +53,7 @@ if $PKG_CONFIG --exists gstreamer-1.0 '>=' $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 gtk+-3.0" recorder_modules="gstreamer-1.0 gstreamer-base-1.0 x11 gtk+-3.0"
PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0) PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0 xfixes)
else else
AC_MSG_RESULT(no) AC_MSG_RESULT(no)
fi fi
@ -59,51 +62,41 @@ AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
CLUTTER_MIN_VERSION=1.13.4 CLUTTER_MIN_VERSION=1.13.4
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1 GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
GJS_MIN_VERSION=1.38.1 GJS_MIN_VERSION=1.35.4
MUTTER_MIN_VERSION=3.10.1 MUTTER_MIN_VERSION=3.8.0
GTK_MIN_VERSION=3.7.9 GTK_MIN_VERSION=3.7.9
GIO_MIN_VERSION=2.37.0 GIO_MIN_VERSION=2.35.0
LIBECAL_MIN_VERSION=3.5.3 LIBECAL_MIN_VERSION=3.5.3
LIBEDATASERVER_MIN_VERSION=3.5.3 LIBEDATASERVER_MIN_VERSION=3.5.3
TELEPATHY_GLIB_MIN_VERSION=0.17.5 TELEPATHY_GLIB_MIN_VERSION=0.17.5
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.7.5 GCR_MIN_VERSION=3.3.90
GNOME_DESKTOP_REQUIRED_VERSION=3.7.90 GNOME_DESKTOP_REQUIRED_VERSION=3.7.90
GNOME_MENUS_REQUIRED_VERSION=3.5.3 GNOME_MENUS_REQUIRED_VERSION=3.5.3
NETWORKMANAGER_MIN_VERSION=0.9.8 NETWORKMANAGER_MIN_VERSION=0.9.6
PULSE_MIN_VERS=2.0 PULSE_MIN_VERS=2.0
# Collect more than 20 libraries for a prize! # Collect more than 20 libraries for a prize!
SHARED_PCS="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 atk-bridge-2.0
gjs-internals-1.0 >= $GJS_MIN_VERSION libmutter >= $MUTTER_MIN_VERSION
libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION gjs-internals-1.0 >= $GJS_MIN_VERSION
$recorder_modules libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION
gdk-x11-3.0 libsoup-2.4 $recorder_modules
xtst gdk-x11-3.0 libsoup-2.4
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
clutter-glx-1.0 >= $CLUTTER_MIN_VERSION clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION
gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
libcanberra libcanberra-gtk3 libcanberra libcanberra-gtk3
telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION
polkit-agent-1 >= $POLKIT_MIN_VERSION polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes
libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION
libnm-gtk >= $NETWORKMANAGER_MIN_VERSION libnm-gtk >= $NETWORKMANAGER_MIN_VERSION
libsecret-unstable gcr-base-3 >= $GCR_MIN_VERSION" libsecret-unstable gcr-3 >= $GCR_MIN_VERSION)
PKG_CHECK_MODULES(GNOME_SHELL, $SHARED_PCS)
PKG_CHECK_MODULES(MUTTER, libmutter >= $MUTTER_MIN_VERSION)
PKG_CHECK_MODULES(MUTTER_WAYLAND, [libmutter-wayland >= $MUTTER_MIN_VERSION],
[MUTTER_WAYLAND_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter-wayland`
AC_SUBST(MUTTER_WAYLAND_TYPELIB_DIR)
have_mutter_wayland=yes],
[have_mutter_wayland=no])
AM_CONDITIONAL(HAVE_MUTTER_WAYLAND, test $have_mutter_wayland != no)
PKG_CHECK_MODULES(GNOME_SHELL_JS, gio-2.0 gjs-internals-1.0 >= $GJS_MIN_VERSION) 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.8 x11) PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.8 x11)
@ -116,7 +109,7 @@ PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.7.4)
PKG_CHECK_MODULES(CARIBOU, caribou-1.0 >= 0.4.8) PKG_CHECK_MODULES(CARIBOU, caribou-1.0 >= 0.4.8)
AC_MSG_CHECKING([for bluetooth support]) AC_MSG_CHECKING([for bluetooth support])
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.9.0], PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0],
[BLUETOOTH_DIR=`$PKG_CONFIG --variable=applet_libdir gnome-bluetooth-1.0` [BLUETOOTH_DIR=`$PKG_CONFIG --variable=applet_libdir gnome-bluetooth-1.0`
BLUETOOTH_LIBS=`$PKG_CONFIG --variable=applet_libs gnome-bluetooth-1.0` BLUETOOTH_LIBS=`$PKG_CONFIG --variable=applet_libs gnome-bluetooth-1.0`
AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"]) AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"])
@ -139,9 +132,8 @@ AC_SUBST([GNOME_KEYBINDINGS_KEYSDIR])
GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION]) GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION])
MUTTER_GIR_DIR=`$PKG_CONFIG --variable=girdir libmutter` MUTTER_GIR_DIR=`$PKG_CONFIG --variable=girdir libmutter`
AC_SUBST(MUTTER_GIR_DIR)
MUTTER_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter` MUTTER_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter`
AC_SUBST(MUTTER_GIR_DIR)
AC_SUBST(MUTTER_TYPELIB_DIR) AC_SUBST(MUTTER_TYPELIB_DIR)
GJS_CONSOLE=`$PKG_CONFIG --variable=gjs_console gjs-1.0` GJS_CONSOLE=`$PKG_CONFIG --variable=gjs_console gjs-1.0`
@ -181,6 +173,10 @@ AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no)
GNOME_COMPILE_WARNINGS([error]) GNOME_COMPILE_WARNINGS([error])
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)
AM_CONDITIONAL(USE_JHBUILD_WRAPPER_SCRIPT, test "x$enable_jhbuild_wrapper_script" = xyes)
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])

View File

@ -0,0 +1,12 @@
<?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

@ -11,9 +11,6 @@
<KeyListEntry name="focus-active-notification" <KeyListEntry name="focus-active-notification"
_description="Focus the active notification"/> _description="Focus the active notification"/>
<KeyListEntry name="toggle-overview"
_description="Show the overview"/>
<KeyListEntry name="toggle-application-view" <KeyListEntry name="toggle-application-view"
_description="Show all applications"/> _description="Show all applications"/>

View File

@ -3,10 +3,6 @@ dist_wanda_DATA = wanda.png
desktopdir=$(datadir)/applications desktopdir=$(datadir)/applications
desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
if HAVE_MUTTER_WAYLAND
desktop_DATA += gnome-shell-wayland.desktop
endif HAVE_MUTTER_WAYLAND
# We substitute in bindir so it works as an autostart # We substitute in bindir so it works as an autostart
# file when built in a non-system prefix # file when built in a non-system prefix
@ -19,7 +15,6 @@ endif HAVE_MUTTER_WAYLAND
introspectiondir = $(datadir)/dbus-1/interfaces introspectiondir = $(datadir)/dbus-1/interfaces
introspection_DATA = \ introspection_DATA = \
org.gnome.Shell.Screencast.xml \
org.gnome.Shell.Screenshot.xml \ org.gnome.Shell.Screenshot.xml \
org.gnome.ShellSearchProvider.xml \ org.gnome.ShellSearchProvider.xml \
org.gnome.ShellSearchProvider2.xml org.gnome.ShellSearchProvider2.xml
@ -45,10 +40,6 @@ dist_theme_DATA = \
theme/message-tray-background.png \ theme/message-tray-background.png \
theme/more-results.svg \ theme/more-results.svg \
theme/noise-texture.png \ theme/noise-texture.png \
theme/page-indicator-active.svg \
theme/page-indicator-inactive.svg \
theme/page-indicator-checked.svg \
theme/page-indicator-hover.svg \
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 \
@ -64,7 +55,10 @@ dist_theme_DATA = \
theme/ws-switch-arrow-down.png theme/ws-switch-arrow-down.png
keysdir = @GNOME_KEYBINDINGS_KEYSDIR@ keysdir = @GNOME_KEYBINDINGS_KEYSDIR@
keys_in_files = 50-gnome-shell-system.xml.in keys_in_files = \
50-gnome-shell-screenshot.xml.in \
50-gnome-shell-system.xml.in \
$(NULL)
keys_DATA = $(keys_in_files:.xml.in=.xml) keys_DATA = $(keys_in_files:.xml.in=.xml)
gsettings_SCHEMAS = org.gnome.shell.gschema.xml gsettings_SCHEMAS = org.gnome.shell.gschema.xml
@ -89,7 +83,6 @@ convert_DATA = gnome-shell-overrides.convert
EXTRA_DIST = \ EXTRA_DIST = \
gnome-shell.desktop.in.in \ gnome-shell.desktop.in.in \
gnome-shell-wayland.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) \
@ -99,7 +92,6 @@ EXTRA_DIST = \
CLEANFILES = \ CLEANFILES = \
gnome-shell.desktop.in \ gnome-shell.desktop.in \
gnome-shell-wayland.desktop.in \
gnome-shell-extension-prefs.in \ gnome-shell-extension-prefs.in \
$(desktop_DATA) \ $(desktop_DATA) \
$(keys_DATA) \ $(keys_DATA) \

View File

@ -1,15 +0,0 @@
[Desktop Entry]
Type=Application
_Name=GNOME Shell (wayland compositor)
_Comment=Window management and application launching
Exec=@bindir@/mutter-launch -- gnome-shell-wayland --wayland
X-GNOME-Bugzilla-Bugzilla=GNOME
X-GNOME-Bugzilla-Product=gnome-shell
X-GNOME-Bugzilla-Component=general
X-GNOME-Bugzilla-Version=@VERSION@
Categories=GNOME;GTK;Core;
OnlyShowIn=GNOME;
NoDisplay=true
X-GNOME-Autostart-Phase=DisplayServer
X-GNOME-Autostart-Notify=true
X-GNOME-AutoRestart=false

View File

@ -1,96 +0,0 @@
<!DOCTYPE node PUBLIC
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
<node>
<!--
org.gnome.Shell.Screencast:
@short_description: Screencast interface
The interface used to record screen contents.
-->
<interface name="org.gnome.Shell.Screencast">
<!--
Screencast:
@file_template: the template for the filename to use
@options: a dictionary of optional parameters
@success: whether the screencast was started successfully
@filename_used: the file where the screencast is being saved
Records a screencast of the whole screen and saves it
(by default) as webm video under a filename derived from
@file_template. The template is either a relative or absolute
filename which may contain some escape sequences - %d and %t
will be replaced by the start date and time of the recording.
If a relative name is used, the screencast will be saved in the
$XDG_VIDEOS_DIR if it exists, or the home directory otherwise.
The actual filename of the saved video is returned in @filename_used.
The set of optional parameters in @options currently consists of:
'draw-cursor'(b): whether the cursor should be included in the
recording (true)
'framerate'(i): the number of frames per second that should be
recorded if possible (30)
'pipeline'(s): the GStreamer pipeline used to encode recordings
in gst-launch format; if not specified, the
recorder will produce vp8 (webm) video (unset)
-->
<method name="Screencast">
<arg type="s" direction="in" name="file_template"/>
<arg type="a{sv}" direction="in" name="options"/>
<arg type="b" direction="in" name="flash"/>
<arg type="b" direction="out" name="success"/>
<arg type="s" direction="out" name="filename_used"/>
</method>
<!--
ScreencastArea:
@x: the X coordinate of the area to capture
@y: the Y coordinate of the area to capture
@width: the width of the area to capture
@height: the height of the area to capture
@file_template: the template for the filename to use
@options: a dictionary of optional parameters
@success: whether the screencast was started successfully
@filename_used: the file where the screencast is being saved
Records a screencast of the passed in area and saves it
(by default) as webm video under a filename derived from
@file_template. The template is either a relative or absolute
filename which may contain some escape sequences - %d and %t
will be replaced by the start date and time of the recording.
If a relative name is used, the screencast will be saved in the
$XDG_VIDEOS_DIR if it exists, or the home directory otherwise.
The actual filename of the saved video is returned in @filename_used.
The set of optional parameters in @options currently consists of:
'draw-cursor'(b): whether the cursor should be included in the
recording (true)
'framerate'(i): the number of frames per second that should be
recorded if possible (30)
'pipeline'(s): the GStreamer pipeline used to encode recordings
in gst-launch format; if not specified, the
recorder will produce vp8 (webm) video (unset)
-->
<method name="ScreencastArea">
<arg type="i" direction="in" name="x"/>
<arg type="i" direction="in" name="y"/>
<arg type="i" direction="in" name="width"/>
<arg type="i" direction="in" name="height"/>
<arg type="s" direction="in" name="file_template"/>
<arg type="a{sv}" direction="in" name="options"/>
<arg type="b" direction="out" name="success"/>
<arg type="s" direction="out" name="filename_used"/>
</method>
<!--
StopScreencast:
@success: whether stopping the recording was successful
Stop the recording started by either Screencast or ScreencastArea.
-->
<method name="StopScreencast">
<arg type="b" direction="out" name="success"/>
</method>
</interface>
</node>

View File

@ -46,7 +46,7 @@
<!-- <!--
GetResultMetas: GetResultMetas:
@identifiers: An array of result identifiers as returned by GetInitialResultSet() or GetSubsearchResultSet() @identifiers: An array of result identifiers as returned by GetInitialResultSet() or GetSubsearchResultSet()
@metas: A dictionary describing the given search result, containing a human-readable 'name' (string), along with the result identifier this meta is for, 'id' (string). Optionally, 'icon' (a serialized GIcon as obtained by g_icon_serialize) can be specified if the result can be better served with a thumbnail of the content (such as with images). 'gicon' (a serialized GIcon as obtained by g_icon_to_string) or 'icon-data' (raw image data as (iiibiiay) - width, height, rowstride, has-alpha, bits per sample, channels, data) are deprecated values that can also be used for that purpose. A 'description' field (string) may also be specified if more context would help the user find the desired result. @metas: A dictionary describing the given search result, containing a human-readable 'name' (string), along with the result identifier this meta is for, 'id' (string). Optionally, either 'gicon' (a serialized GIcon) or 'icon-data' (raw image data as (iiibiiay) - width, height, rowstride, has-alpha, bits per sample, channels, data) can be specified if the result can be better served with a thumbnail of the content (such as with images). A 'description' field (string) may also be specified if more context would help the user find the desired result.
Return an array of meta data used to display each given result Return an array of meta data used to display each given result
--> -->

View File

@ -21,6 +21,16 @@
EnableExtension and DisableExtension DBus methods on org.gnome.Shell. EnableExtension and DisableExtension DBus methods on org.gnome.Shell.
</_description> </_description>
</key> </key>
<key name="enable-app-monitoring" type="b">
<default>true</default>
<_summary>Whether to collect stats about applications usage</_summary>
<_description>
The shell normally monitors active applications in order to present
the most used ones (e.g. in launchers). While this data will be
kept private, you may want to disable this for privacy reasons.
Please note that doing so won't remove already saved data.
</_description>
</key>
<key name="favorite-apps" type="as"> <key name="favorite-apps" type="as">
<default>[ 'epiphany.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'libreoffice-writer.desktop', 'nautilus.desktop', 'gnome-documents.desktop' ]</default> <default>[ 'epiphany.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'libreoffice-writer.desktop', 'nautilus.desktop', 'gnome-documents.desktop' ]</default>
<_summary>List of desktop file IDs for favorite applications</_summary> <_summary>List of desktop file IDs for favorite applications</_summary>
@ -37,13 +47,6 @@
application view, rather than being displayed inline in the main view. application view, rather than being displayed inline in the main view.
</_description> </_description>
</key> </key>
<key name="app-picker-view" type="u">
<default>0</default>
<summary>App Picker View</summary>
<description>
Index of the currently selected view in the application picker.
</description>
</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>
@ -52,6 +55,16 @@
<default>[]</default> <default>[]</default>
<_summary>History for the looking glass dialog</_summary> <_summary>History for the looking glass dialog</_summary>
</key> </key>
<key name="saved-im-presence" type="i">
<default>1</default>
<_summary>Internally used to store the last IM presence explicitly set by the user. The
value here is from the TpConnectionPresenceType enumeration.</_summary>
</key>
<key name="saved-session-presence" type="i">
<default>0</default>
<_summary>Internally used to store the last session presence status for the user. The
value here is from the GsmPresenceStatus enumeration.</_summary>
</key>
<key name="always-show-log-out" type="b"> <key name="always-show-log-out" type="b">
<default>false</default> <default>false</default>
<_summary>Always show the 'Log out' menuitem in the user menu.</_summary> <_summary>Always show the 'Log out' menuitem in the user menu.</_summary>
@ -71,6 +84,7 @@
</_description> </_description>
</key> </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="keybindings" schema="org.gnome.shell.keybindings"/> <child name="keybindings" schema="org.gnome.shell.keybindings"/>
<child name="keyboard" schema="org.gnome.shell.keyboard"/> <child name="keyboard" schema="org.gnome.shell.keyboard"/>
</schema> </schema>
@ -103,13 +117,6 @@
Overview. Overview.
</_description> </_description>
</key> </key>
<key name="toggle-overview" type="as">
<default>["&lt;Super&gt;s"]</default>
<_summary>Keybinding to open the overview</_summary>
<_description>
Keybinding to open the Activities Overview.
</_description>
</key>
<key name="toggle-message-tray" type="as"> <key name="toggle-message-tray" type="as">
<default>["&lt;Super&gt;m"]</default> <default>["&lt;Super&gt;m"]</default>
<_summary>Keybinding to toggle the visibility of the message tray</_summary> <_summary>Keybinding to toggle the visibility of the message tray</_summary>
@ -124,6 +131,13 @@
Keybinding to focus the active notification. Keybinding to focus the active notification.
</_description> </_description>
</key> </key>
<key name="toggle-recording" type="as">
<default><![CDATA[['<Control><Shift><Alt>r']]]></default>
<_summary>Keybinding to toggle the screen recorder</_summary>
<_description>
Keybinding to start/stop the builtin screen recorder.
</_description>
</key>
</schema> </schema>
<schema id="org.gnome.shell.keyboard" path="/org/gnome/shell/keyboard/" <schema id="org.gnome.shell.keyboard" path="/org/gnome/shell/keyboard/"
@ -137,16 +151,41 @@
</key> </key>
</schema> </schema>
<schema id="org.gnome.shell.app-switcher" <schema id="org.gnome.shell.recorder" path="/org/gnome/shell/recorder/"
path="/org/gnome/shell/app-switcher/"
gettext-domain="@GETTEXT_PACKAGE@"> gettext-domain="@GETTEXT_PACKAGE@">
<key type="b" name="current-workspace-only"> <key name="framerate" type="i">
<default>false</default> <default>30</default>
<summary>Limit switcher to current workspace.</summary> <_summary>Framerate used for recording screencasts.</_summary>
<description> <_description>
If true, only applications that have windows on the current workspace are shown in the switcher. The framerate of the resulting screencast recordered
Otherwise, all applications are included. by GNOME Shell's screencast recorder in frames-per-second.
</description> </_description>
</key>
<key name="pipeline" type="s">
<default>''</default>
<_summary>The gstreamer pipeline used to encode the screencast</_summary>
<_description>
Sets the GStreamer pipeline used to encode recordings.
It follows the syntax used for gst-launch. The pipeline should have
an unconnected sink pad where the recorded video is recorded. It will
normally have a unconnected source pad; output from that pad
will be written into the output file. However the pipeline can also
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 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'
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.
</_description>
</key>
<key name="file-extension" type="s">
<default>'webm'</default>
<_summary>File extension used for storing the screencast</_summary>
<_description>
The filename for recorded screencasts will be a unique filename
based on the current date, and use this extension. It should be
changed when recording to a different container format.
</_description>
</key> </key>
</schema> </schema>
@ -168,7 +207,7 @@
</_description> </_description>
</key> </key>
<key type="b" name="current-workspace-only"> <key type="b" name="current-workspace-only">
<default>true</default> <default>false</default>
<summary>Limit switcher to current workspace.</summary> <summary>Limit switcher to current workspace.</summary>
<description> <description>
If true, only windows from the current workspace are shown in the switcher. If true, only windows from the current workspace are shown in the switcher.
@ -223,10 +262,10 @@
<key name="focus-change-on-pointer-rest" type="b"> <key name="focus-change-on-pointer-rest" type="b">
<default>true</default> <default>true</default>
<_summary>Delay focus changes in mouse mode until the pointer stops moving</_summary> <summary>Delay focus changes in mouse mode until the pointer stops moving</summary>
<_description> <description>
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>
</schema> </schema>
</schemalist> </schemalist>

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
height="16" height="16"
id="svg12430" id="svg12430"
version="1.1" version="1.1"
inkscape:version="0.48.4 r9939" inkscape:version="0.48.3.1 r9886"
sodipodi:docname="more-results.svg"> sodipodi:docname="more-results.svg">
<defs <defs
id="defs12432" /> id="defs12432" />
@ -25,18 +25,18 @@
borderopacity="1.0" borderopacity="1.0"
inkscape:pageopacity="1" inkscape:pageopacity="1"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="90.509668" inkscape:zoom="1"
inkscape:cx="6.5009792" inkscape:cx="8.3155237"
inkscape:cy="8.3589595" inkscape:cy="0.89548874"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="g14642-3-0" inkscape:current-layer="g14642-3-0"
showgrid="false" showgrid="false"
borderlayer="true" borderlayer="true"
inkscape:showpageshadow="false" inkscape:showpageshadow="false"
inkscape:window-width="1440" inkscape:window-width="2560"
inkscape:window-height="840" inkscape:window-height="1376"
inkscape:window-x="0" inkscape:window-x="1200"
inkscape:window-y="27" inkscape:window-y="187"
inkscape:window-maximized="1"> inkscape:window-maximized="1">
<inkscape:grid <inkscape:grid
type="xygrid" type="xygrid"
@ -90,11 +90,6 @@
transform="translate(-2,0)" transform="translate(-2,0)"
width="16" width="16"
height="16" /> height="16" />
<path
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1"
d="M 7 5 L 7 7 L 5 7 L 5 9 L 7 9 L 7 11 L 9 11 L 9 9 L 11 9 L 11 7 L 9 7 L 9 5 L 7 5 z "
transform="translate(141.99984,397.99107)"
id="rect3757" />
<path <path
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
style="color:#bebebe;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible" style="color:#bebebe;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible"

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,71 +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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="18"
height="18"
id="svg4703"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="page-indicator-pushed.svg">
<defs
id="defs4705" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="31.392433"
inkscape:cx="1.0245308"
inkscape:cy="13.3715"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="2560"
inkscape:window-height="1374"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid6140" />
</sodipodi:namedview>
<metadata
id="metadata4708">
<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
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(0,2)">
<path
transform="matrix(0.54617904,0,0,0.62523128,-1131.9904,-392.39214)"
d="m 2099.9808,638.83099 a 10.985409,9.5964489 0 1 1 -21.9708,0 10.985409,9.5964489 0 1 1 21.9708,0 z"
sodipodi:ry="9.5964489"
sodipodi:rx="10.985409"
sodipodi:cy="638.83099"
sodipodi:cx="2088.9954"
id="path4711"
style="fill:#fdffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,67 +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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="18"
height="18"
id="svg4703"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="page-indicator-active.svg">
<defs
id="defs4705" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.197802"
inkscape:cx="2.1522887"
inkscape:cy="16.782904"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1920"
inkscape:window-height="1021"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata4708">
<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,2)">
<path
transform="matrix(0.72823872,0,0,0.8336417,-1512.2872,-525.55618)"
d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 -10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z"
sodipodi:ry="9.5964489"
sodipodi:rx="10.985409"
sodipodi:cy="638.83099"
sodipodi:cx="2088.9954"
id="path4711"
style="fill:#fdffff;fill-opacity:0.94117647;stroke:none"
sodipodi:type="arc" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,67 +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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="18"
height="18"
id="svg5266"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="page-indicator-inactive.svg">
<defs
id="defs5268" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="11.313709"
inkscape:cx="-2.307566"
inkscape:cy="17.859535"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="2560"
inkscape:window-height="1374"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata5271">
<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
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(0,2)">
<path
sodipodi:type="arc"
style="fill:none;fill-opacity:0;stroke:#ffffff;stroke-width:2.93356276000000005;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path5274"
sodipodi:cx="2088.9954"
sodipodi:cy="638.83099"
sodipodi:rx="10.985409"
sodipodi:ry="9.5964489"
d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 -10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z"
transform="matrix(0.63720887,0,0,0.72943648,-1322.1264,-458.98661)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,67 +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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="18"
height="18"
id="svg5266"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="page-indicator-inactive.svg">
<defs
id="defs5268" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="11.313709"
inkscape:cx="-2.307566"
inkscape:cy="17.859535"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="2560"
inkscape:window-height="1374"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata5271">
<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,2)">
<path
sodipodi:type="arc"
style="fill:none;fill-opacity:0;stroke:#ffffff;stroke-width:2.93356276000000005;stroke-miterlimit:4;stroke-opacity:0.39215686000000000;stroke-dasharray:none"
id="path5274"
sodipodi:cx="2088.9954"
sodipodi:cy="638.83099"
sodipodi:rx="10.985409"
sodipodi:ry="9.5964489"
d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 -10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z"
transform="matrix(0.63720887,0,0,0.72943648,-1322.1264,-458.98661)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -112,7 +112,7 @@ expand_content_files=
# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS) # e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) # e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
GTKDOC_CFLAGS=$(GNOME_SHELL_CFLAGS) GTKDOC_CFLAGS=$(GNOME_SHELL_CFLAGS)
GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(BLUETOOTH_LIBS) $(top_builddir)/src/libgnome-shell-menu.la $(top_builddir)/src/libgnome-shell-base.la $(top_builddir)/src/libgnome-shell.la GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(BLUETOOTH_LIBS) $(top_builddir)/src/libgnome-shell.la
# This includes the standard gtk-doc make rules, copied by gtkdocize. # This includes the standard gtk-doc make rules, copied by gtkdocize.
include $(top_srcdir)/gtk-doc.make include $(top_srcdir)/gtk-doc.make

View File

@ -46,8 +46,8 @@
<xi:include href="doc-gen-org.gnome.Shell.SearchProvider.xml"/> <xi:include href="doc-gen-org.gnome.Shell.SearchProvider.xml"/>
<xi:include href="doc-gen-org.gnome.Shell.SearchProvider2.xml"/> <xi:include href="doc-gen-org.gnome.Shell.SearchProvider2.xml"/>
<xi:include href="xml/shell-global.xml"/> <xi:include href="xml/shell-global.xml"/>
<xi:include href="xml/shell-keybinding-modes.xml"/>
<xi:include href="xml/shell-wm.xml"/> <xi:include href="xml/shell-wm.xml"/>
<xi:include href="xml/shell-xfixes-cursor.xml"/>
<xi:include href="xml/shell-util.xml"/> <xi:include href="xml/shell-util.xml"/>
<xi:include href="xml/shell-mount-operation.xml"/> <xi:include href="xml/shell-mount-operation.xml"/>
<xi:include href="xml/shell-network-agent.xml"/> <xi:include href="xml/shell-network-agent.xml"/>

View File

@ -17,11 +17,10 @@ misc/config.js: misc/config.js.in Makefile
jsdir = $(pkgdatadir)/js jsdir = $(pkgdatadir)/js
nobase_dist_js_DATA = \ nobase_dist_js_DATA = \
gdm/authPrompt.js \
gdm/batch.js \ gdm/batch.js \
gdm/fingerprint.js \ gdm/fingerprint.js \
gdm/loginDialog.js \ gdm/loginDialog.js \
gdm/oVirt.js \ gdm/powerMenu.js \
gdm/realmd.js \ gdm/realmd.js \
gdm/util.js \ gdm/util.js \
extensionPrefs/main.js \ extensionPrefs/main.js \
@ -34,13 +33,10 @@ nobase_dist_js_DATA = \
misc/jsParse.js \ misc/jsParse.js \
misc/loginManager.js \ misc/loginManager.js \
misc/modemManager.js \ misc/modemManager.js \
misc/objectManager.js \
misc/params.js \ misc/params.js \
misc/smartcardManager.js \
misc/util.js \ misc/util.js \
perf/core.js \ perf/core.js \
ui/altTab.js \ ui/altTab.js \
ui/animation.js \
ui/appDisplay.js \ ui/appDisplay.js \
ui/appFavorites.js \ ui/appFavorites.js \
ui/backgroundMenu.js \ ui/backgroundMenu.js \
@ -56,7 +52,6 @@ nobase_dist_js_DATA = \
ui/extensionSystem.js \ ui/extensionSystem.js \
ui/extensionDownloader.js \ ui/extensionDownloader.js \
ui/environment.js \ ui/environment.js \
ui/focusCaretTracker.js\
ui/ibusCandidatePopup.js\ ui/ibusCandidatePopup.js\
ui/grabHelper.js \ ui/grabHelper.js \
ui/iconGrid.js \ ui/iconGrid.js \
@ -73,7 +68,6 @@ nobase_dist_js_DATA = \
ui/sessionMode.js \ ui/sessionMode.js \
ui/shellEntry.js \ ui/shellEntry.js \
ui/shellMountOperation.js \ ui/shellMountOperation.js \
ui/slider.js \
ui/notificationDaemon.js \ ui/notificationDaemon.js \
ui/osdWindow.js \ ui/osdWindow.js \
ui/overview.js \ ui/overview.js \
@ -83,9 +77,7 @@ nobase_dist_js_DATA = \
ui/pointerWatcher.js \ ui/pointerWatcher.js \
ui/popupMenu.js \ ui/popupMenu.js \
ui/remoteSearch.js \ ui/remoteSearch.js \
ui/remoteMenu.js \
ui/runDialog.js \ ui/runDialog.js \
ui/screencast.js \
ui/screenshot.js \ ui/screenshot.js \
ui/screenShield.js \ ui/screenShield.js \
ui/scripting.js \ ui/scripting.js \
@ -93,18 +85,16 @@ nobase_dist_js_DATA = \
ui/searchDisplay.js \ ui/searchDisplay.js \
ui/shellDBus.js \ ui/shellDBus.js \
ui/status/accessibility.js \ ui/status/accessibility.js \
ui/status/brightness.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/rfkill.js \
ui/status/volume.js \ ui/status/volume.js \
ui/status/bluetooth.js \ ui/status/bluetooth.js \
ui/status/screencast.js \
ui/status/system.js \
ui/switcherPopup.js \ ui/switcherPopup.js \
ui/tweener.js \ ui/tweener.js \
ui/unlockDialog.js \ ui/unlockDialog.js \
ui/userMenu.js \
ui/userWidget.js \ ui/userWidget.js \
ui/viewSelector.js \ ui/viewSelector.js \
ui/wanda.js \ ui/wanda.js \
@ -120,6 +110,7 @@ nobase_dist_js_DATA = \
ui/components/automountManager.js \ ui/components/automountManager.js \
ui/components/networkAgent.js \ ui/components/networkAgent.js \
ui/components/polkitAgent.js \ ui/components/polkitAgent.js \
ui/components/recorder.js \
ui/components/telepathyClient.js \ ui/components/telepathyClient.js \
ui/components/keyring.js \ ui/components/keyring.js \
$(NULL) $(NULL)

View File

@ -1,507 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const Signals = imports.signals;
const St = imports.gi.St;
const Animation = imports.ui.animation;
const Batch = imports.gdm.batch;
const GdmUtil = imports.gdm.util;
const Params = imports.misc.params;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener;
const UserWidget = imports.ui.userWidget;
const DEFAULT_BUTTON_WELL_ICON_SIZE = 24;
const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0;
const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3;
const MESSAGE_FADE_OUT_ANIMATION_TIME = 0.5;
const AuthPromptMode = {
UNLOCK_ONLY: 0,
UNLOCK_OR_LOG_IN: 1
};
const AuthPromptStatus = {
NOT_VERIFYING: 0,
VERIFYING: 1,
VERIFICATION_FAILED: 2,
VERIFICATION_SUCCEEDED: 3
};
const BeginRequestType = {
PROVIDE_USERNAME: 0,
DONT_PROVIDE_USERNAME: 1
};
const AuthPrompt = new Lang.Class({
Name: 'AuthPrompt',
_init: function(gdmClient, mode) {
this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
this._gdmClient = gdmClient;
this._mode = mode;
let reauthenticationOnly;
if (this._mode == AuthPromptMode.UNLOCK_ONLY)
reauthenticationOnly = true;
else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN)
reauthenticationOnly = false;
this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly });
this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion));
this._userVerifier.connect('show-message', Lang.bind(this, this._onShowMessage));
this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed));
this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
this._userVerifier.connect('reset', Lang.bind(this, this._onReset));
this._userVerifier.connect('smartcard-status-changed', Lang.bind(this, this._onSmartcardStatusChanged));
this._userVerifier.connect('ovirt-user-authenticated', Lang.bind(this, this._onOVirtUserAuthenticated));
this.smartcardDetected = this._userVerifier.smartcardDetected;
this.connect('next', Lang.bind(this, function() {
this.updateSensitivity(false);
this.startSpinning();
if (this._queryingService) {
this._userVerifier.answerQuery(this._queryingService, this._entry.text);
} else {
this._preemptiveAnswer = this._entry.text;
}
}));
this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
vertical: true });
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this.actor.connect('key-press-event',
Lang.bind(this, function(actor, event) {
if (event.get_key_symbol() == Clutter.KEY_Escape) {
this.cancel();
}
}));
this._userWell = new St.Bin({ x_fill: true,
x_align: St.Align.START });
this.actor.add(this._userWell,
{ x_align: St.Align.START,
x_fill: true,
y_fill: true,
expand: true });
this._label = new St.Label({ style_class: 'login-dialog-prompt-label' });
this.actor.add(this._label,
{ expand: true,
x_fill: true,
y_fill: true,
x_align: St.Align.START });
this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
can_focus: true });
ShellEntry.addContextMenu(this._entry, { isPassword: true });
this.actor.add(this._entry,
{ expand: true,
x_fill: true,
y_fill: false,
x_align: St.Align.START });
this._entry.grab_key_focus();
this._message = new St.Label({ opacity: 0,
styleClass: 'login-dialog-message' });
this._message.clutter_text.line_wrap = true;
this.actor.add(this._message, { x_fill: true, y_align: St.Align.START });
this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box',
vertical: false });
this.actor.add(this._buttonBox,
{ expand: true,
x_align: St.Align.MIDDLE,
y_align: St.Align.END });
this._defaultButtonWell = new St.Widget({ layout_manager: new Clutter.BinLayout() });
this._defaultButtonWellActor = null;
this._initButtons();
let spinnerIcon = global.datadir + '/theme/process-working.svg';
this._spinner = new Animation.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE);
this._spinner.actor.opacity = 0;
this._spinner.actor.show();
this._defaultButtonWell.add_child(this._spinner.actor);
},
_onDestroy: function() {
this._userVerifier.clear();
this._userVerifier.disconnectAll();
this._userVerifier = null;
},
_initButtons: function() {
this.cancelButton = new St.Button({ style_class: 'modal-dialog-button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
label: _("Cancel") });
this.cancelButton.connect('clicked',
Lang.bind(this, function() {
this.cancel();
}));
this._buttonBox.add(this.cancelButton,
{ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.END });
this._buttonBox.add(this._defaultButtonWell,
{ expand: true,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
this.nextButton = new St.Button({ style_class: 'modal-dialog-button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
label: _("Next") });
this.nextButton.connect('clicked',
Lang.bind(this, function() {
this.emit('next');
}));
this.nextButton.add_style_pseudo_class('default');
this._buttonBox.add(this.nextButton,
{ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.END });
this._updateNextButtonSensitivity(this._entry.text.length > 0);
this._entry.clutter_text.connect('text-changed',
Lang.bind(this, function() {
if (!this._userVerifier.hasPendingMessages)
this._fadeOutMessage();
this._updateNextButtonSensitivity(this._entry.text.length > 0);
}));
this._entry.clutter_text.connect('activate', Lang.bind(this, function() {
this.emit('next');
}));
},
_onAskQuestion: function(verifier, serviceName, question, passwordChar) {
if (this._preemptiveAnswer) {
if (this._queryingService)
this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer);
this._preemptiveAnswer = null;
return;
}
if (this._queryingService)
this.clear();
this._queryingService = serviceName;
this.setPasswordChar(passwordChar);
this.setQuestion(question);
if (passwordChar) {
if (this._userVerifier.reauthenticating)
this.nextButton.label = _("Unlock");
else
this.nextButton.label = C_("button", "Sign In");
} else {
this.nextButton.label = _("Next");
}
this.updateSensitivity(true);
this.emit('prompted');
},
_onOVirtUserAuthenticated: function() {
if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED)
this.reset();
},
_onSmartcardStatusChanged: function() {
this.smartcardDetected = this._userVerifier.smartcardDetected;
// Most of the time we want to reset if the user inserts or removes
// a smartcard. Smartcard insertion "preempts" what the user was
// doing, and smartcard removal aborts the preemption.
// The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying
// with a smartcard
// 2) Don't reset if we've already succeeded at verification and
// the user is getting logged in.
if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) &&
this.verificationStatus == AuthPromptStatus.VERIFYING &&
this.smartcardDetected)
return;
if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED)
this.reset();
},
_onShowMessage: function(userVerifier, message, type) {
this.setMessage(message, type);
this.emit('prompted');
},
_onVerificationFailed: function() {
this._queryingService = null;
this.clear();
this.updateSensitivity(true);
this.setActorInDefaultButtonWell(null);
this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED;
},
_onVerificationComplete: function() {
this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED;
},
_onReset: function() {
if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) {
this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
this.reset();
}
},
addActorToDefaultButtonWell: function(actor) {
this._defaultButtonWell.add_child(actor);
},
setActorInDefaultButtonWell: function(actor, animate) {
if (!this._defaultButtonWellActor &&
!actor)
return;
let oldActor = this._defaultButtonWellActor;
if (oldActor)
Tweener.removeTweens(oldActor);
let isSpinner;
if (actor == this._spinner.actor)
isSpinner = true;
else
isSpinner = false;
if (this._defaultButtonWellActor != actor && oldActor) {
if (!animate) {
oldActor.opacity = 0;
} else {
Tweener.addTween(oldActor,
{ opacity: 0,
time: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
transition: 'linear',
onCompleteScope: this,
onComplete: function() {
if (isSpinner) {
if (this._spinner)
this._spinner.stop();
}
}
});
}
}
if (actor) {
if (isSpinner)
this._spinner.play();
if (!animate)
actor.opacity = 255;
else
Tweener.addTween(actor,
{ opacity: 255,
time: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
transition: 'linear' });
}
this._defaultButtonWellActor = actor;
},
startSpinning: function() {
this.setActorInDefaultButtonWell(this._spinner.actor, true);
},
stopSpinning: function() {
this.setActorInDefaultButtonWell(null, false);
},
clear: function() {
this._entry.text = '';
this.stopSpinning();
},
setPasswordChar: function(passwordChar) {
this._entry.clutter_text.set_password_char(passwordChar);
this._entry.menu.isPassword = passwordChar != '';
},
setQuestion: function(question) {
this._label.set_text(question);
this._label.show();
this._entry.show();
this._entry.grab_key_focus();
},
getAnswer: function() {
let text;
if (this._preemptiveAnswer) {
text = this._preemptiveAnswer;
this._preemptiveAnswer = null;
} else {
text = this._entry.get_text();
}
return text;
},
_fadeOutMessage: function() {
if (this._message.opacity == 0)
return;
Tweener.removeTweens(this._message);
Tweener.addTween(this._message,
{ opacity: 0,
time: MESSAGE_FADE_OUT_ANIMATION_TIME,
transition: 'easeOutQuad'
});
},
setMessage: function(message, type) {
if (type == GdmUtil.MessageType.ERROR)
this._message.add_style_class_name('login-dialog-message-warning');
else
this._message.remove_style_class_name('login-dialog-message-warning');
if (type == GdmUtil.MessageType.HINT)
this._message.add_style_class_name('login-dialog-message-hint');
else
this._message.remove_style_class_name('login-dialog-message-hint');
if (message) {
Tweener.removeTweens(this._message);
this._message.text = message;
this._message.opacity = 255;
} else {
this._message.opacity = 0;
}
},
_updateNextButtonSensitivity: function(sensitive) {
this.nextButton.reactive = sensitive;
this.nextButton.can_focus = sensitive;
},
updateSensitivity: function(sensitive) {
this._updateNextButtonSensitivity(sensitive);
this._entry.reactive = sensitive;
this._entry.clutter_text.editable = sensitive;
},
hide: function() {
this.setActorInDefaultButtonWell(null, true);
this.actor.hide();
this._message.opacity = 0;
this.setUser(null);
this.updateSensitivity(true);
this._entry.set_text('');
},
setUser: function(user) {
if (user) {
let userWidget = new UserWidget.UserWidget(user);
this._userWell.set_child(userWidget.actor);
} else {
this._userWell.set_child(null);
}
},
reset: function() {
let oldStatus = this.verificationStatus;
this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
if (oldStatus == AuthPromptStatus.VERIFYING)
this._userVerifier.cancel();
this._queryingService = null;
this.clear();
this._message.opacity = 0;
this.setUser(null);
this.stopSpinning();
if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED)
this.emit('failed');
let beginRequestType;
if (this._mode == AuthPromptMode.UNLOCK_ONLY) {
// The user is constant at the unlock screen, so it will immediately
// respond to the request with the username
beginRequestType = BeginRequestType.PROVIDE_USERNAME;
} else if (this._userVerifier.serviceIsForeground(GdmUtil.OVIRT_SERVICE_NAME) ||
(this.smartcardDetected &&
this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME))) {
// We don't need to know the username if the user preempted the login screen
// with a smartcard or with preauthenticated oVirt credentials
beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME;
} else {
// In all other cases, we should get the username up front.
beginRequestType = BeginRequestType.PROVIDE_USERNAME;
}
this.emit('reset', beginRequestType);
},
addCharacter: function(unichar) {
if (!this._entry.visible)
return;
this._entry.grab_key_focus();
this._entry.clutter_text.insert_unichar(unichar);
},
begin: function(params) {
params = Params.parse(params, { userName: null,
hold: null });
this.updateSensitivity(false);
let hold = params.hold;
if (!hold)
hold = new Batch.Hold();
this._userVerifier.begin(params.userName, hold);
this.verificationStatus = AuthPromptStatus.VERIFYING;
},
finish: function(onComplete) {
if (!this._userVerifier.hasPendingMessages) {
onComplete();
return;
}
let signalId = this._userVerifier.connect('no-more-messages',
Lang.bind(this, function() {
this._userVerifier.disconnect(signalId);
onComplete();
}));
},
cancel: function() {
this.reset();
this.emit('cancelled');
}
});
Signals.addSignalMethods(AuthPrompt.prototype);

File diff suppressed because it is too large Load Diff

View File

@ -1,62 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Signals = imports.signals;
const OVirtCredentialsIface = <interface name='org.ovirt.vdsm.Credentials'>
<signal name="UserAuthenticated">
<arg type="s" name="token"/>
</signal>
</interface>;
const OVirtCredentialsInfo = Gio.DBusInterfaceInfo.new_for_xml(OVirtCredentialsIface);
let _oVirtCredentialsManager = null;
function OVirtCredentials() {
var self = new Gio.DBusProxy({ g_connection: Gio.DBus.system,
g_interface_name: OVirtCredentialsInfo.name,
g_interface_info: OVirtCredentialsInfo,
g_name: 'org.ovirt.vdsm.Credentials',
g_object_path: '/org/ovirt/vdsm/Credentials',
g_flags: (Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
self.init(null);
return self;
}
const OVirtCredentialsManager = new Lang.Class({
Name: 'OVirtCredentialsManager',
_init: function() {
this._token = null;
this._credentials = new OVirtCredentials();
this._credentials.connectSignal('UserAuthenticated',
Lang.bind(this, this._onUserAuthenticated));
},
_onUserAuthenticated: function(proxy, sender, [token]) {
this._token = token;
this.emit('user-authenticated', token);
},
hasToken: function() {
return this._token != null;
},
getToken: function() {
return this._token;
},
resetToken: function() {
this._token = null;
}
});
Signals.addSignalMethods(OVirtCredentialsManager.prototype);
function getOVirtCredentialsManager() {
if (!_oVirtCredentialsManager)
_oVirtCredentialsManager = new OVirtCredentialsManager();
return _oVirtCredentialsManager;
}

129
js/gdm/powerMenu.js Normal file
View File

@ -0,0 +1,129 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/*
* Copyright 2011 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.
*/
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const LoginManager = imports.misc.loginManager;
const GdmUtil = imports.gdm.util;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const PowerMenuButton = new Lang.Class({
Name: 'PowerMenuButton',
Extends: PanelMenu.SystemStatusButton,
_init: function() {
/* Translators: accessible name of the power menu in the login screen */
this.parent('system-shutdown-symbolic', _("Power"));
this._loginManager = LoginManager.getLoginManager();
this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA });
this._settings.connect('changed::disable-restart-buttons',
Lang.bind(this, this._updateVisibility));
this._createSubMenu();
// ConsoleKit doesn't send notifications when shutdown/reboot
// are disabled, so we update the menu item each time the menu opens
this.menu.connect('open-state-changed', Lang.bind(this,
function(menu, open) {
if (open) {
this._updateHaveShutdown();
this._updateHaveRestart();
this._updateHaveSuspend();
}
}));
this._updateHaveShutdown();
this._updateHaveRestart();
this._updateHaveSuspend();
},
_updateVisibility: function() {
let shouldBeVisible = (this._haveSuspend || this._haveShutdown || this._haveRestart);
this.actor.visible = shouldBeVisible && !this._settings.get_boolean('disable-restart-buttons');
},
_updateHaveShutdown: function() {
this._loginManager.canPowerOff(Lang.bind(this, function(result) {
this._haveShutdown = result;
this._powerOffItem.actor.visible = this._haveShutdown;
this._updateVisibility();
}));
},
_updateHaveRestart: function() {
this._loginManager.canReboot(Lang.bind(this, function(result) {
this._haveRestart = result;
this._restartItem.actor.visible = this._haveRestart;
this._updateVisibility();
}));
},
_updateHaveSuspend: function() {
this._loginManager.canSuspend(Lang.bind(this, function(result) {
this._haveSuspend = result;
this._suspendItem.actor.visible = this._haveSuspend;
this._updateVisibility();
}));
},
_createSubMenu: function() {
let item;
item = new PopupMenu.PopupMenuItem(_("Suspend"));
item.connect('activate', Lang.bind(this, this._onActivateSuspend));
this.menu.addMenuItem(item);
this._suspendItem = item;
item = new PopupMenu.PopupMenuItem(_("Restart"));
item.connect('activate', Lang.bind(this, this._onActivateRestart));
this.menu.addMenuItem(item);
this._restartItem = item;
item = new PopupMenu.PopupMenuItem(_("Power Off"));
item.connect('activate', Lang.bind(this, this._onActivatePowerOff));
this.menu.addMenuItem(item);
this._powerOffItem = item;
},
_onActivateSuspend: function() {
if (!this._haveSuspend)
return;
this._loginManager.suspend();
},
_onActivateRestart: function() {
if (!this._haveRestart)
return;
this._loginManager.reboot();
},
_onActivatePowerOff: function() {
if (!this._haveShutdown)
return;
this._loginManager.powerOff();
}
});

View File

@ -63,7 +63,7 @@ const Manager = new Lang.Class({
Lang.bind(this, this._reloadRealms)) Lang.bind(this, this._reloadRealms))
this._realms = {}; this._realms = {};
this._signalId = this._aggregateProvider.connect('g-properties-changed', this._aggregateProvider.connect('g-properties-changed',
Lang.bind(this, function(proxy, properties) { Lang.bind(this, function(proxy, properties) {
if ('Realms' in properties.deep_unpack()) if ('Realms' in properties.deep_unpack())
this._reloadRealms(); this._reloadRealms();
@ -106,7 +106,7 @@ const Manager = new Lang.Class({
realm.connect('g-properties-changed', realm.connect('g-properties-changed',
Lang.bind(this, function(proxy, properties) { Lang.bind(this, function(proxy, properties) {
if ('Configured' in properties.deep_unpack()) if ('Configured' in properties.deep_unpack())
this._reloadRealm(realm); this._reloadRealm();
})); }));
}, },
@ -134,18 +134,6 @@ const Manager = new Lang.Class({
this._updateLoginFormat(); this._updateLoginFormat();
return this._loginFormat; return this._loginFormat;
},
release: function() {
Service(Gio.DBus.system,
'org.freedesktop.realmd',
'/org/freedesktop/realmd',
function(service) {
service.ReleaseRemote();
});
this._aggregateProvider.disconnect(this._signalId);
this._realms = { };
this._updateLoginFormat();
} }
}); });
Signals.addSignalMethods(Manager.prototype) Signals.addSignalMethods(Manager.prototype)

View File

@ -6,28 +6,21 @@ const GLib = imports.gi.GLib;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Signals = imports.signals; const Signals = imports.signals;
const St = imports.gi.St;
const Batch = imports.gdm.batch; const Batch = imports.gdm.batch;
const Fprint = imports.gdm.fingerprint; const Fprint = imports.gdm.fingerprint;
const OVirt = imports.gdm.oVirt; const Realmd = imports.gdm.realmd;
const Main = imports.ui.main; const Main = imports.ui.main;
const Params = imports.misc.params; const Params = imports.misc.params;
const ShellEntry = imports.ui.shellEntry;
const SmartcardManager = imports.misc.smartcardManager;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const PASSWORD_SERVICE_NAME = 'gdm-password'; const PASSWORD_SERVICE_NAME = 'gdm-password';
const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
const SMARTCARD_SERVICE_NAME = 'gdm-smartcard';
const OVIRT_SERVICE_NAME = 'gdm-ovirtcred';
const FADE_ANIMATION_TIME = 0.16; const FADE_ANIMATION_TIME = 0.16;
const CLONE_FADE_ANIMATION_TIME = 0.25; const CLONE_FADE_ANIMATION_TIME = 0.25;
const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
const PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication';
const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
const SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication';
const BANNER_MESSAGE_KEY = 'banner-message-enable'; const BANNER_MESSAGE_KEY = 'banner-message-enable';
const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text';
const ALLOWED_FAILURES_KEY = 'allowed-failures'; const ALLOWED_FAILURES_KEY = 'allowed-failures';
@ -38,13 +31,6 @@ const DISABLE_USER_LIST_KEY = 'disable-user-list';
// Give user 16ms to read each character of a PAM message // Give user 16ms to read each character of a PAM message
const USER_READ_TIME = 16 const USER_READ_TIME = 16
const MessageType = {
NONE: 0,
ERROR: 1,
INFO: 2,
HINT: 3
};
function fadeInActor(actor) { function fadeInActor(actor) {
if (actor.opacity == 255 && actor.visible) if (actor.opacity == 255 && actor.visible)
return null; return null;
@ -129,45 +115,20 @@ const ShellUserVerifier = new Lang.Class({
this._client = client; this._client = client;
this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA }); this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA });
this._settings.connect('changed',
Lang.bind(this, this._updateDefaultService));
this._updateDefaultService();
this._fprintManager = new Fprint.FprintManager(); this._fprintManager = new Fprint.FprintManager();
this._smartcardManager = SmartcardManager.getSmartcardManager(); this._realmManager = new Realmd.Manager();
// We check for smartcards right away, since an inserted smartcard
// at startup should result in immediately initiating authentication.
// This is different than fingeprint readers, where we only check them
// after a user has been picked.
this._checkForSmartcard();
this._smartcardManager.connect('smartcard-inserted',
Lang.bind(this, this._checkForSmartcard));
this._smartcardManager.connect('smartcard-removed',
Lang.bind(this, this._checkForSmartcard));
this._messageQueue = []; this._messageQueue = [];
this._messageQueueTimeoutId = 0; this._messageQueueTimeoutId = 0;
this.hasPendingMessages = false; this.hasPendingMessages = false;
this.reauthenticating = false;
this._failCounter = 0; this._failCounter = 0;
this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager();
if (this._oVirtCredentialsManager.hasToken())
this._oVirtUserAuthenticated(this._oVirtCredentialsManager.getToken());
this._oVirtCredentialsManager.connect('user-authenticated',
Lang.bind(this, this._oVirtUserAuthenticated));
}, },
begin: function(userName, hold) { begin: function(userName, hold) {
this._cancellable = new Gio.Cancellable(); this._cancellable = new Gio.Cancellable();
this._hold = hold; this._hold = hold;
this._userName = userName; this._userName = userName;
this.reauthenticating = false;
this._checkForFingerprintReader(); this._checkForFingerprintReader();
@ -185,10 +146,8 @@ const ShellUserVerifier = new Lang.Class({
if (this._cancellable) if (this._cancellable)
this._cancellable.cancel(); this._cancellable.cancel();
if (this._userVerifier) { if (this._userVerifier)
this._userVerifier.call_cancel_sync(null); this._userVerifier.call_cancel_sync(null);
this.clear();
}
}, },
clear: function() { clear: function() {
@ -206,14 +165,14 @@ const ShellUserVerifier = new Lang.Class({
}, },
answerQuery: function(serviceName, answer) { answerQuery: function(serviceName, answer) {
if (!this.hasPendingMessages) { if (!this._userVerifier.hasPendingMessages) {
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
} else { } else {
let signalId = this.connect('no-more-messages', let signalId = this._userVerifier.connect('no-more-messages',
Lang.bind(this, function() { Lang.bind(this, function() {
this.disconnect(signalId); this._userVerifier.disconnect(signalId);
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
})); }));
} }
}, },
@ -242,8 +201,7 @@ const ShellUserVerifier = new Lang.Class({
return; return;
let message = this._messageQueue.shift(); let message = this._messageQueue.shift();
this.emit('show-message', message.text, message.iconName);
this.emit('show-message', message.text, message.type);
this._messageQueueTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._messageQueueTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
message.interval, message.interval,
@ -253,11 +211,11 @@ const ShellUserVerifier = new Lang.Class({
})); }));
}, },
_queueMessage: function(message, messageType) { _queueMessage: function(message, iconName) {
let interval = this._getIntervalForMessage(message); let interval = this._getIntervalForMessage(message);
this.hasPendingMessages = true; this.hasPendingMessages = true;
this._messageQueue.push({ text: message, type: messageType, interval: interval }); this._messageQueue.push({ text: message, interval: interval, iconName: iconName });
this._queueMessageTimeout(); this._queueMessageTimeout();
}, },
@ -268,57 +226,27 @@ const ShellUserVerifier = new Lang.Class({
GLib.source_remove(this._messageQueueTimeoutId); GLib.source_remove(this._messageQueueTimeoutId);
this._messageQueueTimeoutId = 0; this._messageQueueTimeoutId = 0;
} }
this.emit('show-message', null, MessageType.NONE); this.emit('show-message', null, null);
}, },
_checkForFingerprintReader: function() { _checkForFingerprintReader: function() {
this._haveFingerprintReader = false; this._haveFingerprintReader = false;
if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) { if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY))
this._updateDefaultService();
return; return;
}
this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this, this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this,
function(device, error) { function(device, error) {
if (!error && device) if (!error && device)
this._haveFingerprintReader = true; this._haveFingerprintReader = true;
this._updateDefaultService();
})); }));
}, },
_oVirtUserAuthenticated: function(token) {
this._preemptingService = OVIRT_SERVICE_NAME;
this.emit('ovirt-user-authenticated');
},
_checkForSmartcard: function() {
let smartcardDetected;
if (!this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY))
smartcardDetected = false;
else if (this.reauthenticating)
smartcardDetected = this._smartcardManager.hasInsertedLoginToken();
else
smartcardDetected = this._smartcardManager.hasInsertedTokens();
if (smartcardDetected != this.smartcardDetected) {
this.smartcardDetected = smartcardDetected;
if (this.smartcardDetected)
this._preemptingService = SMARTCARD_SERVICE_NAME;
else if (this._preemptingService == SMARTCARD_SERVICE_NAME)
this._preemptingService = null;
this.emit('smartcard-status-changed');
}
},
_reportInitError: function(where, error) { _reportInitError: function(where, error) {
logError(error, where); logError(error, where);
this._hold.release(); this._hold.release();
this._queueMessage(_("Authentication error"), MessageType.ERROR); this._queueMessage(_("Authentication error"), 'login-dialog-message-warning');
this._verificationFailed(false); this._verificationFailed(false);
}, },
@ -339,7 +267,6 @@ const ShellUserVerifier = new Lang.Class({
return; return;
} }
this.reauthenticating = true;
this._connectSignals(); this._connectSignals();
this._beginVerification(); this._beginVerification();
this._hold.release(); this._hold.release();
@ -370,119 +297,124 @@ const ShellUserVerifier = new Lang.Class({
this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
}, },
_getForegroundService: function() { _beginVerification: function() {
if (this._preemptingService)
return this._preemptingService;
return this._defaultService;
},
serviceIsForeground: function(serviceName) {
return serviceName == this._getForegroundService();
},
serviceIsDefault: function(serviceName) {
return serviceName == this._defaultService;
},
_updateDefaultService: function() {
if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY))
this._defaultService = PASSWORD_SERVICE_NAME;
else if (this.smartcardDetected)
this._defaultService = SMARTCARD_SERVICE_NAME;
else if (this._haveFingerprintReader)
this._defaultService = FINGERPRINT_SERVICE_NAME;
},
_startService: function(serviceName) {
this._hold.acquire(); this._hold.acquire();
if (this._userName) { if (this._userName) {
this._userVerifier.call_begin_verification_for_user(serviceName, this._userVerifier.call_begin_verification_for_user(PASSWORD_SERVICE_NAME,
this._userName, this._userName,
this._cancellable, this._cancellable,
Lang.bind(this, function(obj, result) { Lang.bind(this, function(obj, result) {
try { try {
obj.call_begin_verification_for_user_finish(result); obj.call_begin_verification_for_user_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return; return;
} catch(e) { } catch(e) {
this._reportInitError('Failed to start verification for user', e); this._reportInitError('Failed to start verification for user', e);
return; return;
} }
this._hold.release(); 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 { } else {
this._userVerifier.call_begin_verification(serviceName, this._userVerifier.call_begin_verification(PASSWORD_SERVICE_NAME,
this._cancellable, this._cancellable,
Lang.bind(this, function(obj, result) { Lang.bind(this, function(obj, result) {
try { try {
obj.call_begin_verification_finish(result); obj.call_begin_verification_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return; return;
} catch(e) { } catch(e) {
this._reportInitError('Failed to start verification', e); this._reportInitError('Failed to start verification', e);
return; return;
} }
this._hold.release(); this._hold.release();
})); }));
} }
}, },
_beginVerification: function() {
this._startService(this._getForegroundService());
if (this._userName && this._haveFingerprintReader && !this.serviceIsForeground(FINGERPRINT_SERVICE_NAME))
this._startService(FINGERPRINT_SERVICE_NAME);
},
_onInfo: function(client, serviceName, info) { _onInfo: function(client, serviceName, info) {
if (this.serviceIsForeground(serviceName)) { // We don't display fingerprint messages, because they
this._queueMessage(info, MessageType.INFO); // have words like UPEK in them. Instead we use the messages
} else if (serviceName == FINGERPRINT_SERVICE_NAME && // as a cue to display our own message.
if (serviceName == FINGERPRINT_SERVICE_NAME &&
this._haveFingerprintReader) { this._haveFingerprintReader) {
// We don't show fingerprint messages directly since it's
// not the main auth service. Instead we use the messages
// as a cue to display our own message.
// Translators: this message is shown below the password entry field // Translators: this message is shown below the password entry field
// to indicate the user can swipe their finger instead // to indicate the user can swipe their finger instead
this._queueMessage(_("(or swipe finger)"), MessageType.HINT); this.emit('show-login-hint', _("(or swipe finger)"));
} else if (serviceName == PASSWORD_SERVICE_NAME) {
this._queueMessage(info, 'login-dialog-message-info');
} }
}, },
_onProblem: function(client, serviceName, problem) { _onProblem: function(client, serviceName, problem) {
if (!this.serviceIsForeground(serviceName)) // we don't want to show auth failed messages to
// users who haven't enrolled their fingerprint.
if (serviceName != PASSWORD_SERVICE_NAME)
return; return;
this._queueMessage(problem, 'login-dialog-message-warning');
},
this._queueMessage(problem, MessageType.ERROR); _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) { _onInfoQuery: function(client, serviceName, question) {
if (!this.serviceIsForeground(serviceName)) // We only expect questions to come from the main auth service
if (serviceName != PASSWORD_SERVICE_NAME)
return; return;
this._showRealmLoginHint();
this._realmLoginHintSignalId = this._realmManager.connect('login-format-changed',
Lang.bind(this, this._showRealmLoginHint));
this.emit('ask-question', serviceName, question, ''); this.emit('ask-question', serviceName, question, '');
}, },
_onSecretInfoQuery: function(client, serviceName, secretQuestion) { _onSecretInfoQuery: function(client, serviceName, secretQuestion) {
if (!this.serviceIsForeground(serviceName)) // We only expect secret requests to come from the main auth service
if (serviceName != PASSWORD_SERVICE_NAME)
return; return;
if (serviceName == OVIRT_SERVICE_NAME) {
// The only question asked by this service is "Token?"
this.answerQuery(serviceName, this._oVirtCredentialsManager.getToken());
return;
}
this.emit('ask-question', serviceName, secretQuestion, '\u25cf'); this.emit('ask-question', serviceName, secretQuestion, '\u25cf');
}, },
_onReset: function() { _onReset: function() {
// Clear previous attempts to authenticate // Clear previous attempts to authenticate
this._failCounter = 0; this._failCounter = 0;
this._updateDefaultService();
this.emit('reset'); this.emit('reset');
}, },
@ -511,24 +443,24 @@ const ShellUserVerifier = new Lang.Class({
this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY); this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY);
if (canRetry) { if (canRetry) {
if (!this.hasPendingMessages) { if (!this._userVerifier.hasPendingMessages) {
this._retry(); this._retry();
} else { } else {
let signalId = this.connect('no-more-messages', let signalId = this._userVerifier.connect('no-more-messages',
Lang.bind(this, function() { Lang.bind(this, function() {
this.disconnect(signalId); this._userVerifier.disconnect(signalId);
this._retry(); this._retry();
})); }));
} }
} else { } else {
if (!this.hasPendingMessages) { if (!this._userVerifier.hasPendingMessages) {
this._cancelAndReset(); this._cancelAndReset();
} else { } else {
let signalId = this.connect('no-more-messages', let signalId = this._userVerifier.connect('no-more-messages',
Lang.bind(this, function() { Lang.bind(this, function() {
this.disconnect(signalId); this._userVerifier.disconnect(signalId);
this._cancelAndReset(); this._cancelAndReset();
})); }));
} }
} }
@ -536,22 +468,19 @@ const ShellUserVerifier = new Lang.Class({
}, },
_onConversationStopped: function(client, serviceName) { _onConversationStopped: function(client, serviceName) {
// If the login failed with the preauthenticated oVirt credentials
// then discard the credentials and revert to default authentication
// mechanism.
if (this.serviceIsForeground(OVIRT_SERVICE_NAME)) {
this._oVirtCredentialsManager.resetToken();
this._preemptingService = null;
this._verificationFailed(false);
return;
}
// if the password service fails, then cancel everything. // if the password service fails, then cancel everything.
// But if, e.g., fingerprint fails, still give // But if, e.g., fingerprint fails, still give
// password authentication a chance to succeed // password authentication a chance to succeed
if (this.serviceIsForeground(serviceName)) { if (serviceName == PASSWORD_SERVICE_NAME) {
this._verificationFailed(true); this._verificationFailed(true);
} }
this.emit('hide-login-hint');
if (this._realmLoginHintSignalId) {
this._realmManager.disconnect(this._realmLoginHintSignalId);
this._realmLoginHintSignalId = 0;
}
}, },
}); });
Signals.addSignalMethods(ShellUserVerifier.prototype); Signals.addSignalMethods(ShellUserVerifier.prototype);

View File

@ -117,6 +117,7 @@ function recursivelyMoveDir(srcDir, destDir) {
let type = info.get_file_type(); let type = info.get_file_type();
let srcChild = srcDir.get_child(info.get_name()); let srcChild = srcDir.get_child(info.get_name());
let destChild = destDir.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) if (type == Gio.FileType.REGULAR)
srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null); srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null);
else if (type == Gio.FileType.DIRECTORY) else if (type == Gio.FileType.DIRECTORY)

View File

@ -58,7 +58,6 @@ const Map = new Lang.Class({
_init: function(iterable) { _init: function(iterable) {
this._pool = { }; this._pool = { };
this._size = 0;
if (iterable) { if (iterable) {
for (let i = 0; i < iterable.length; i++) { for (let i = 0; i < iterable.length; i++) {
@ -100,7 +99,6 @@ const Map = new Lang.Class({
node.value = value; node.value = value;
} else { } else {
this._pool[hash] = { key: key, value: value }; this._pool[hash] = { key: key, value: value };
this._size++;
} }
}, },
@ -110,7 +108,6 @@ const Map = new Lang.Class({
if (node && _sameValue(node.key, key)) { if (node && _sameValue(node.key, key)) {
delete this._pool[hash]; delete this._pool[hash];
this._size--;
return [node.key, node.value]; return [node.key, node.value];
} else { } else {
return [null, null]; return [null, null];
@ -139,6 +136,6 @@ const Map = new Lang.Class({
}, },
size: function() { size: function() {
return this._size; return Object.getOwnPropertyNames(this._pool).length;
}, },
}); });

View File

@ -8,9 +8,21 @@ const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager'> 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='Suspend'> <method name='Suspend'>
<arg type='b' direction='in'/> <arg type='b' direction='in'/>
</method> </method>
<method name='CanPowerOff'>
<arg type='s' direction='out'/>
</method>
<method name='CanReboot'>
<arg type='s' direction='out'/>
</method>
<method name='CanSuspend'> <method name='CanSuspend'>
<arg type='s' direction='out'/> <arg type='s' direction='out'/>
</method> </method>
@ -72,10 +84,8 @@ function versionCompare(required, reference) {
reference = reference.split('.'); reference = reference.split('.');
for (let i = 0; i < required.length; i++) { for (let i = 0; i < required.length; i++) {
let requiredInt = parseInt(required[i]); if (required[i] != reference[i])
let referenceInt = parseInt(reference[i]); return required[i] < reference[i];
if (requiredInt != referenceInt)
return requiredInt < referenceInt;
} }
return true; return true;
@ -149,6 +159,24 @@ const LoginManagerSystemd = new Lang.Class({
})); }));
}, },
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');
});
},
canSuspend: function(asyncCallback) { canSuspend: function(asyncCallback) {
this._proxy.CanSuspendRemote(function(result, error) { this._proxy.CanSuspendRemote(function(result, error) {
if (error) if (error)
@ -167,6 +195,14 @@ const LoginManagerSystemd = new Lang.Class({
}); });
}, },
powerOff: function() {
this._proxy.PowerOffRemote(true);
},
reboot: function() {
this._proxy.RebootRemote(true);
},
suspend: function() { suspend: function() {
this._proxy.SuspendRemote(true); this._proxy.SuspendRemote(true);
}, },
@ -228,6 +264,24 @@ const LoginManagerConsoleKit = new Lang.Class({
})); }));
}, },
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]);
});
},
canSuspend: function(asyncCallback) { canSuspend: function(asyncCallback) {
asyncCallback(false); asyncCallback(false);
}, },
@ -236,6 +290,14 @@ const LoginManagerConsoleKit = new Lang.Class({
asyncCallback([]); asyncCallback([]);
}, },
powerOff: function() {
this._proxy.StopRemote();
},
reboot: function() {
this._proxy.RestartRemote();
},
suspend: function() { suspend: function() {
this.emit('prepare-for-sleep', true); this.emit('prepare-for-sleep', true);
this.emit('prepare-for-sleep', false); this.emit('prepare-for-sleep', false);

View File

@ -1,257 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Params = imports.misc.params;
const Signals = imports.signals;
// Specified in the D-Bus specification here:
// http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
const ObjectManagerIface = <interface name="org.freedesktop.DBus.ObjectManager">
<method name="GetManagedObjects">
<arg name="objects" type="a{oa{sa{sv}}}" direction="out"/>
</method>
<signal name="InterfacesAdded">
<arg name="objectPath" type="o"/>
<arg name="interfaces" type="a{sa{sv}}" />
</signal>
<signal name="InterfacesRemoved">
<arg name="objectPath" type="o"/>
<arg name="interfaces" type="as" />
</signal>
</interface>;
const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface);
const ObjectManager = new Lang.Class({
Name: 'ObjectManager',
_init: function(params) {
params = Params.parse(params, { connection: null,
name: null,
objectPath: null,
knownInterfaces: null,
cancellable: null,
onLoaded: null });
this._connection = params.connection;
this._serviceName = params.name;
this._managerPath = params.objectPath;
this._cancellable = params.cancellable;
this._managerProxy = new Gio.DBusProxy({ g_connection: this._connection,
g_interface_name: ObjectManagerInfo.name,
g_interface_info: ObjectManagerInfo,
g_name: this._serviceName,
g_object_path: this._managerPath,
g_flags: Gio.DBusProxyFlags.NONE });
this._interfaceInfos = {};
this._objects = {};
this._interfaces = {};
this._onLoaded = params.onLoaded;
if (params.knownInterfaces)
this._registerInterfaces(params.knownInterfaces);
// Start out inhibiting load until at least the proxy
// manager is loaded and the remote objects are fetched
this._numLoadInhibitors = 1;
this._managerProxy.init_async(GLib.PRIORITY_DEFAULT,
this._cancellable,
Lang.bind(this, this._onManagerProxyLoaded));
},
_tryToCompleteLoad: function() {
this._numLoadInhibitors--;
if (this._numLoadInhibitors == 0) {
if (this._onLoaded)
this._onLoaded();
}
},
_addInterface: function(objectPath, interfaceName, onFinished) {
let info = this._interfaceInfos[interfaceName];
if (!info) {
if (onFinished)
onFinished();
return;
}
let proxy = new Gio.DBusProxy({ g_connection: this._connection,
g_name: this._serviceName,
g_object_path: objectPath,
g_interface_name: interfaceName,
g_interface_info: info,
g_flags: Gio.DBusProxyFlags.NONE });
proxy.init_async(GLib.PRIORITY_DEFAULT,
this._cancellable,
Lang.bind(this, function(initable, result) {
let error = null;
try {
initable.init_finish(result);
} catch(e) {
logError(e, 'could not initialize proxy for interface ' + interfaceName);
if (onFinished)
onFinished();
return;
}
let isNewObject;
if (!this._objects[objectPath]) {
this._objects[objectPath] = {};
isNewObject = true;
} else {
isNewObject = false;
}
this._objects[objectPath][interfaceName] = proxy;
if (!this._interfaces[interfaceName])
this._interfaces[interfaceName] = [];
this._interfaces[interfaceName].push(proxy);
if (isNewObject)
this.emit('object-added', objectPath);
this.emit('interface-added', interfaceName, proxy);
if (onFinished)
onFinished();
}));
},
_removeInterface: function(objectPath, interfaceName) {
if (!this._objects[objectPath])
return;
let proxy = this._objects[objectPath][interfaceName];
if (this._interfaces[interfaceName]) {
let index = this._interfaces[interfaceName].indexOf(proxy);
if (index >= 0)
this._interfaces[interfaceName].splice(index, 1);
if (this._interfaces[interfaceName].length == 0)
delete this._interfaces[interfaceName];
}
this.emit('interface-removed', interfaceName, proxy);
this._objects[objectPath][interfaceName] = null;
if (Object.keys(this._objects[objectPath]).length == 0) {
delete this._objects[objectPath];
this.emit('object-removed', objectPath);
}
},
_onManagerProxyLoaded: function(initable, result) {
let error = null;
try {
initable.init_finish(result);
} catch(e) {
logError(e, 'could not initialize object manager for object ' + params.name);
this._tryToCompleteLoad();
return;
}
this._managerProxy.connectSignal('InterfacesAdded',
Lang.bind(this, function(objectManager, sender, [objectPath, interfaces]) {
let interfaceNames = Object.keys(interfaces);
for (let i = 0; i < interfaceNames.length; i++)
this._addInterface(objectPath, interfaceNames[i]);
}));
this._managerProxy.connectSignal('InterfacesRemoved',
Lang.bind(this, function(objectManager, sender, [objectPath, interfaceNames]) {
for (let i = 0; i < interfaceNames.length; i++)
this._removeInterface(objectPath, interfaceNames[i]);
}));
if (Object.keys(this._interfaceInfos).length == 0) {
this._tryToCompleteLoad();
return;
}
this._managerProxy.GetManagedObjectsRemote(Lang.bind(this, function(result, error) {
if (!result) {
if (error) {
logError(error, 'could not get remote objects for service ' + this._serviceName + ' path ' + this._managerPath);
}
this._tryToCompleteLoad();
return;
}
let [objects] = result;
let objectPaths = Object.keys(objects);
for (let i = 0; i < objectPaths.length; i++) {
let objectPath = objectPaths[i];
let object = objects[objectPath];
let interfaceNames = Object.getOwnPropertyNames(object);
for (let j = 0; j < interfaceNames.length; j++) {
let interfaceName = interfaceNames[j];
// Prevent load from completing until the interface is loaded
this._numLoadInhibitors++;
this._addInterface(objectPath,
interfaceName,
Lang.bind(this, this._tryToCompleteLoad));
}
}
this._tryToCompleteLoad();
}));
},
_registerInterfaces: function(interfaces) {
for (let i = 0; i < interfaces.length; i++) {
let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]);
this._interfaceInfos[info.name] = info;
}
},
getProxy: function(objectPath, interfaceName) {
let object = this._objects[objectPath];
if (!object)
return null;
return object[interfaceName];
},
getProxiesForInterface: function(interfaceName) {
let proxyList = this._interfaces[interfaceName];
if (!proxyList)
return [];
return proxyList;
},
getAllProxies: function() {
let proxies = [];
let objectPaths = Object.keys(this._objects);
for (let i = 0; i < objectPaths.length; i++) {
let object = this._objects[objectPaths];
let interfaceNames = Object.keys(object);
for (let j = 0; i < interfaceNames.length; i++) {
let interfaceName = interfaceNames[i];
if (object[interfaceName])
proxies.push(object(interfaceName));
}
}
return proxies;
}
});
Signals.addSignalMethods(ObjectManager.prototype);

View File

@ -1,117 +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 ObjectManager = imports.misc.objectManager;
const SmartcardTokenIface = <interface name="org.gnome.SettingsDaemon.Smartcard.Token">
<property name="Name" type="s" access="read"/>
<property name="Driver" type="o" access="read"/>
<property name="IsInserted" type="b" access="read"/>
<property name="UsedToLogin" type="b" access="read"/>
</interface>;
let _smartcardManager = null;
function getSmartcardManager() {
if (_smartcardManager == null)
_smartcardManager = new SmartcardManager();
return _smartcardManager;
}
const SmartcardManager = new Lang.Class({
Name: 'SmartcardManager',
_init: function() {
this._objectManager = new ObjectManager.ObjectManager({ connection: Gio.DBus.session,
name: "org.gnome.SettingsDaemon.Smartcard",
objectPath: '/org/gnome/SettingsDaemon/Smartcard',
knownInterfaces: [ SmartcardTokenIface ],
onLoaded: Lang.bind(this, this._onLoaded) });
this._insertedTokens = {};
this._loginToken = null;
},
_onLoaded: function() {
let tokens = this._objectManager.getProxiesForInterface('org.gnome.SettingsDaemon.Smartcard.Token');
for (let i = 0; i < tokens.length; i++)
this._addToken(tokens[i]);
this._objectManager.connect('interface-added', Lang.bind(this, function(objectManager, interfaceName, proxy) {
if (interfaceName == 'org.gnome.SettingsDaemon.Smartcard.Token')
this._addToken(proxy);
}));
this._objectManager.connect('interface-removed', Lang.bind(this, function(objectManager, interfaceName, proxy) {
if (interfaceName == 'org.gnome.SettingsDaemon.Smartcard.Token')
this._removeToken(proxy);
}));
},
_updateToken: function(token) {
let objectPath = token.get_object_path();
delete this._insertedTokens[objectPath];
if (token.IsInserted)
this._insertedTokens[objectPath] = token;
if (token.UsedToLogin)
this._loginToken = token;
},
_addToken: function(token) {
this._updateToken(token);
token.connect('g-properties-changed',
Lang.bind(this, function(proxy, properties) {
if ('IsInserted' in properties.deep_unpack()) {
this._updateToken(token);
if (token.IsInserted) {
this.emit('smartcard-inserted', token);
} else {
this.emit('smartcard-removed', token);
}
}
}));
// Emit a smartcard-inserted at startup if it's already plugged in
if (token.IsInserted)
this.emit('smartcard-inserted', token);
},
_removeToken: function(token) {
let objectPath = token.get_object_path();
if (this._insertedTokens[objectPath] == token) {
delete this._insertedTokens[objectPath];
this.emit('smartcard-removed', token);
}
if (this._loginToken == token)
this._loginToken = null;
token.disconnectAll();
},
hasInsertedTokens: function() {
return Object.keys(this._insertedTokens).length > 0;
},
hasInsertedLoginToken: function() {
if (!this._loginToken)
return false;
if (!this._loginToken.IsInserted)
return false;
return true;
}
});
Signals.addSignalMethods(SmartcardManager.prototype);

View File

@ -1,9 +1,7 @@
// -*- 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 GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Lang = imports.lang;
const St = imports.gi.St; const St = imports.gi.St;
const Main = imports.ui.main; const Main = imports.ui.main;
@ -20,7 +18,7 @@ const _urlRegexp = new RegExp(
'(^|' + _leadingJunk + ')' + '(^|' + _leadingJunk + ')' +
'(' + '(' +
'(?:' + '(?:' +
'(?:http|https|ftp)://' + // scheme:// '[a-z][\\w-]+://' + // scheme://
'|' + '|' +
'www\\d{0,3}[.]' + // www. 'www\\d{0,3}[.]' + // www.
'|' + '|' +
@ -80,22 +78,6 @@ function spawnCommandLine(command_line) {
} }
} }
// spawnApp:
// @argv: an argv array
//
// Runs @argv as if it was an application, handling startup notification
function spawnApp(argv) {
try {
let app = Gio.AppInfo.create_from_commandline(argv.join(' '), null,
Gio.AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION);
let context = global.create_app_launch_context();
app.launch([], context);
} catch(err) {
_handleSpawnError(argv[0], err);
}
}
// trySpawn: // trySpawn:
// @argv: an argv array // @argv: an argv array
// //
@ -157,6 +139,31 @@ function _handleSpawnError(command, err) {
Main.notifyError(title, err.message); Main.notifyError(title, err.message);
} }
// killall:
// @processName: a process name
//
// Kills @processName. If no process with the given name is found,
// this will fail silently.
function killall(processName) {
try {
// pkill is more portable than killall, but on Linux at least
// it won't match if you pass more than 15 characters of the
// process name... However, if you use the '-f' flag to match
// the entire command line, it will work, but we have to be
// careful in that case that we can match
// '/usr/bin/processName' but not 'gedit processName.c' or
// whatever...
let argv = ['pkill', '-f', '^([^ ]*/)?' + processName + '($| )'];
GLib.spawn_sync(null, argv, null, GLib.SpawnFlags.SEARCH_PATH, null);
// It might be useful to return success/failure, but we'd need
// a wrapper around WIFEXITED and WEXITSTATUS. Since none of
// the current callers care, we don't bother.
} catch (e) {
logError(e, 'Failed to kill ' + processName);
}
}
// lowerBound: // lowerBound:
// @array: an array or array-like object, already sorted // @array: an array or array-like object, already sorted
// according to @cmp // according to @cmp
@ -207,57 +214,28 @@ function insertSorted(array, val, cmp) {
return pos; return pos;
} }
const CloseButton = new Lang.Class({ function makeCloseButton() {
Name: 'CloseButton', let closeButton = new St.Button({ style_class: 'notification-close'});
Extends: St.Button,
_init: function(boxpointer) { // This is a bit tricky. St.Bin has its own x-align/y-align properties
this.parent({ style_class: 'notification-close'}); // that compete with Clutter's properties. This should be fixed for
// Clutter 2.0. Since St.Bin doesn't define its own setters, the
// setters are a workaround to get Clutter's version.
closeButton.set_x_align(Clutter.ActorAlign.END);
closeButton.set_y_align(Clutter.ActorAlign.START);
// This is a bit tricky. St.Bin has its own x-align/y-align properties // XXX Clutter 2.0 workaround: ClutterBinLayout needs expand
// that compete with Clutter's properties. This should be fixed for // to respect the alignments.
// Clutter 2.0. Since St.Bin doesn't define its own setters, the closeButton.set_x_expand(true);
// setters are a workaround to get Clutter's version. closeButton.set_y_expand(true);
this.set_x_align(Clutter.ActorAlign.END);
this.set_y_align(Clutter.ActorAlign.START);
// XXX Clutter 2.0 workaround: ClutterBinLayout needs expand closeButton.connect('style-changed', function() {
// to respect the alignments. let themeNode = closeButton.get_theme_node();
this.set_x_expand(true); closeButton.translation_x = themeNode.get_length('-shell-close-overlap-x');
this.set_y_expand(true); closeButton.translation_y = themeNode.get_length('-shell-close-overlap-y');
});
this._boxPointer = boxpointer; return closeButton;
if (boxpointer)
this._boxPointer.connect('arrow-side-changed', Lang.bind(this, this._sync));
},
_computeBoxPointerOffset: function() {
if (!this._boxPointer || !this._boxPointer.actor.get_stage())
return 0;
let side = this._boxPointer.arrowSide;
if (side == St.Side.TOP)
return this._boxPointer.getArrowHeight();
else
return 0;
},
_sync: function() {
let themeNode = this.get_theme_node();
let offY = this._computeBoxPointerOffset();
this.translation_x = themeNode.get_length('-shell-close-overlap-x')
this.translation_y = themeNode.get_length('-shell-close-overlap-y') + offY;
},
vfunc_style_changed: function() {
this._sync();
this.parent();
},
});
function makeCloseButton(boxpointer) {
return new CloseButton(boxpointer);
} }
function ensureActorVisibleInScrollView(scrollView, actor) { function ensureActorVisibleInScrollView(scrollView, actor) {

View File

@ -233,10 +233,8 @@ const AppSwitcherPopup = new Lang.Class({
_finish : function(timestamp) { _finish : function(timestamp) {
let appIcon = this._items[this._selectedIndex]; let appIcon = this._items[this._selectedIndex];
if (this._currentWindow < 0) let window = this._currentWindow > 0 ? this._currentWindow : 0;
appIcon.app.activate_window(appIcon.cachedWindows[0], timestamp); appIcon.app.activate_window(appIcon.cachedWindows[window], timestamp);
else
Main.activateWindow(appIcon.cachedWindows[this._currentWindow], timestamp);
this.parent(); this.parent();
}, },
@ -355,13 +353,10 @@ const WindowSwitcherPopup = new Lang.Class({
Name: 'WindowSwitcherPopup', Name: 'WindowSwitcherPopup',
Extends: SwitcherPopup.SwitcherPopup, Extends: SwitcherPopup.SwitcherPopup,
_init: function(items) {
this.parent(items);
this._settings = new Gio.Settings({ schema: 'org.gnome.shell.window-switcher' });
},
_getWindowList: function() { _getWindowList: function() {
let workspace = this._settings.get_boolean('current-workspace-only') ? global.screen.get_active_workspace() : null; let settings = new Gio.Settings({ schema: 'org.gnome.shell.window-switcher' });
let workspace = settings.get_boolean('current-workspace-only') ? global.screen.get_active_workspace()
: null;
return global.display.get_tab_list(Meta.TabList.NORMAL, global.screen, workspace); return global.display.get_tab_list(Meta.TabList.NORMAL, global.screen, workspace);
}, },
@ -371,8 +366,7 @@ const WindowSwitcherPopup = new Lang.Class({
if (windows.length == 0) if (windows.length == 0)
return false; return false;
let mode = this._settings.get_enum('app-icon-mode'); this._switcherList = new WindowList(windows);
this._switcherList = new WindowList(windows, mode);
this._items = this._switcherList.icons; this._items = this._switcherList.icons;
return true; return true;
@ -440,11 +434,8 @@ const AppSwitcher = new Lang.Class({
this._arrows = []; this._arrows = [];
let windowTracker = Shell.WindowTracker.get_default(); let windowTracker = Shell.WindowTracker.get_default();
let settings = new Gio.Settings({ schema: 'org.gnome.shell.app-switcher' });
let workspace = settings.get_boolean('current-workspace-only') ? global.screen.get_active_workspace()
: null;
let allWindows = global.display.get_tab_list(Meta.TabList.NORMAL, let allWindows = global.display.get_tab_list(Meta.TabList.NORMAL,
global.screen, workspace); global.screen, null);
// Construct the AppIcons, add to the popup // Construct the AppIcons, add to the popup
for (let i = 0; i < apps.length; i++) { for (let i = 0; i < apps.length; i++) {
@ -454,9 +445,7 @@ const AppSwitcher = new Lang.Class({
appIcon.cachedWindows = allWindows.filter(function(w) { appIcon.cachedWindows = allWindows.filter(function(w) {
return windowTracker.get_window_app (w) == appIcon.app; return windowTracker.get_window_app (w) == appIcon.app;
}); });
if (workspace == null || appIcon.cachedWindows.length > 0) { this._addIcon(appIcon);
this._addIcon(appIcon);
}
} }
this._curApp = -1; this._curApp = -1;
@ -667,7 +656,7 @@ const ThumbnailList = new Lang.Class({
const WindowIcon = new Lang.Class({ const WindowIcon = new Lang.Class({
Name: 'WindowIcon', Name: 'WindowIcon',
_init: function(window, mode) { _init: function(window) {
this.window = window; this.window = window;
this.actor = new St.BoxLayout({ style_class: 'alt-tab-app', this.actor = new St.BoxLayout({ style_class: 'alt-tab-app',
@ -685,7 +674,8 @@ const WindowIcon = new Lang.Class({
this._icon.destroy_all_children(); this._icon.destroy_all_children();
switch (mode) { let settings = new Gio.Settings({ schema: 'org.gnome.shell.window-switcher' });
switch (settings.get_enum('app-icon-mode')) {
case AppIconMode.THUMBNAIL_ONLY: case AppIconMode.THUMBNAIL_ONLY:
size = WINDOW_PREVIEW_SIZE; size = WINDOW_PREVIEW_SIZE;
this._icon.add_actor(_createWindowClone(mutterWindow, WINDOW_PREVIEW_SIZE)); this._icon.add_actor(_createWindowClone(mutterWindow, WINDOW_PREVIEW_SIZE));
@ -723,7 +713,7 @@ const WindowList = new Lang.Class({
Name: 'WindowList', Name: 'WindowList',
Extends: SwitcherPopup.SwitcherList, Extends: SwitcherPopup.SwitcherList,
_init : function(windows, mode) { _init : function(windows) {
this.parent(true); this.parent(true);
this._label = new St.Label({ x_align: Clutter.ActorAlign.CENTER, this._label = new St.Label({ x_align: Clutter.ActorAlign.CENTER,
@ -735,7 +725,7 @@ const WindowList = new Lang.Class({
for (let i = 0; i < windows.length; i++) { for (let i = 0; i < windows.length; i++) {
let win = windows[i]; let win = windows[i];
let icon = new WindowIcon(win, mode); let icon = new WindowIcon(win);
this.addItem(icon.actor, icon.label); this.addItem(icon.actor, icon.label);
this.icons.push(icon); this.icons.push(icon);

View File

@ -1,84 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const St = imports.gi.St;
const Signals = imports.signals;
const Atk = imports.gi.Atk;
const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
const Animation = new Lang.Class({
Name: 'Animation',
_init: function(filename, width, height, speed) {
this.actor = new St.Bin();
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this._speed = speed;
this._isLoaded = false;
this._isPlaying = false;
this._timeoutId = 0;
this._frame = 0;
this._animations = St.TextureCache.get_default().load_sliced_image (filename, width, height,
Lang.bind(this, this._animationsLoaded));
this.actor.set_child(this._animations);
},
play: function() {
if (this._isLoaded && this._timeoutId == 0) {
if (this._frame == 0)
this._showFrame(0);
this._timeoutId = Mainloop.timeout_add(this._speed, Lang.bind(this, this._update));
}
this._isPlaying = true;
},
stop: function() {
if (this._timeoutId > 0) {
Mainloop.source_remove(this._timeoutId);
this._timeoutId = 0;
}
this._isPlaying = false;
},
_showFrame: function(frame) {
let oldFrameActor = this._animations.get_child_at_index(this._frame);
if (oldFrameActor)
oldFrameActor.hide();
this._frame = (frame % this._animations.get_n_children());
let newFrameActor = this._animations.get_child_at_index(this._frame);
if (newFrameActor)
newFrameActor.show();
},
_update: function() {
this._showFrame(this._frame + 1);
return true;
},
_animationsLoaded: function() {
this._isLoaded = true;
if (this._isPlaying)
this.play();
},
_onDestroy: function() {
this.stop();
}
});
const AnimatedIcon = new Lang.Class({
Name: 'AnimatedIcon',
Extends: Animation,
_init: function(filename, size) {
this.parent(filename, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
}
});

File diff suppressed because it is too large Load Diff

View File

@ -14,15 +14,15 @@ const AppFavorites = new Lang.Class({
_init: function() { _init: function() {
this._favorites = {}; this._favorites = {};
global.settings.connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged)); global.settings.connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged));
this.reload(); this._reload();
}, },
_onFavsChanged: function() { _onFavsChanged: function() {
this.reload(); this._reload();
this.emit('changed'); this.emit('changed');
}, },
reload: function() { _reload: function() {
let ids = global.settings.get_strv(this.FAVORITE_APPS_KEY); let ids = global.settings.get_strv(this.FAVORITE_APPS_KEY);
let appSys = Shell.AppSystem.get_default(); let appSys = Shell.AppSystem.get_default();
let apps = ids.map(function (id) { let apps = ids.map(function (id) {

View File

@ -126,11 +126,6 @@ const BackgroundCache = new Lang.Class({
}, },
removeImageContent: function(content) { removeImageContent: function(content) {
let filename = content.get_filename();
if (filename && this._fileMonitors[filename])
delete this._fileMonitors[filename];
this._removeContent(this._images, content); this._removeContent(this._images, content);
}, },
@ -295,15 +290,14 @@ const Background = new Lang.Class({
_init: function(params) { _init: function(params) {
params = Params.parse(params, { monitorIndex: 0, params = Params.parse(params, { monitorIndex: 0,
layoutManager: Main.layoutManager, layoutManager: Main.layoutManager,
effects: Meta.BackgroundEffects.NONE, effects: Meta.BackgroundEffects.NONE });
settings: null });
this.actor = new Meta.BackgroundGroup(); this.actor = new Meta.BackgroundGroup();
this.actor._delegate = this; this.actor._delegate = this;
this._destroySignalId = this.actor.connect('destroy', this._destroySignalId = this.actor.connect('destroy',
Lang.bind(this, this._destroy)); Lang.bind(this, this._destroy));
this._settings = params.settings; this._settings = new Gio.Settings({ schema: BACKGROUND_SCHEMA });
this._monitorIndex = params.monitorIndex; this._monitorIndex = params.monitorIndex;
this._layoutManager = params.layoutManager; this._layoutManager = params.layoutManager;
this._effects = params.effects; this._effects = params.effects;
@ -319,9 +313,9 @@ const Background = new Lang.Class({
this._cancellable = new Gio.Cancellable(); this._cancellable = new Gio.Cancellable();
this.isLoaded = false; this.isLoaded = false;
this._settingsChangedSignalId = this._settings.connect('changed', Lang.bind(this, function() { this._settings.connect('changed', Lang.bind(this, function() {
this.emit('changed'); this.emit('changed');
})); }));
this._load(); this._load();
}, },
@ -362,10 +356,6 @@ const Background = new Lang.Class({
this.actor.disconnect(this._destroySignalId); this.actor.disconnect(this._destroySignalId);
this._destroySignalId = 0; this._destroySignalId = 0;
if (this._settingsChangedSignalId != 0)
this._settings.disconnect(this._settingsChangedSignalId);
this._settingsChangedSignalId = 0;
}, },
_setLoaded: function() { _setLoaded: function() {
@ -436,7 +426,7 @@ const Background = new Lang.Class({
content.brightness = this._brightness; content.brightness = this._brightness;
content.vignette_sharpness = this._vignetteSharpness; content.vignette_sharpness = this._vignetteSharpness;
this._cache.removeImageContent(this._images[index].content); this._cache.removeImageContent(content);
this._images[index].content = content; this._images[index].content = content;
this._watchCacheFile(filename); this._watchCacheFile(filename);
}, },
@ -576,16 +566,7 @@ const Background = new Lang.Class({
} }
let uri = this._settings.get_string(PICTURE_URI_KEY); let uri = this._settings.get_string(PICTURE_URI_KEY);
let filename; let filename = Gio.File.new_for_uri(uri).get_path();
if (GLib.uri_parse_scheme(uri) != null)
filename = Gio.File.new_for_uri(uri).get_path();
else
filename = uri;
if (!filename) {
this._setLoaded();
return;
}
this._loadFile(filename); this._loadFile(filename);
}, },
@ -721,10 +702,8 @@ const BackgroundManager = new Lang.Class({
layoutManager: Main.layoutManager, layoutManager: Main.layoutManager,
monitorIndex: null, monitorIndex: null,
effects: Meta.BackgroundEffects.NONE, effects: Meta.BackgroundEffects.NONE,
controlPosition: true, controlPosition: true });
settingsSchema: BACKGROUND_SCHEMA });
this._settings = new Gio.Settings({ schema: params.settingsSchema });
this._container = params.container; this._container = params.container;
this._layoutManager = params.layoutManager; this._layoutManager = params.layoutManager;
this._effects = params.effects; this._effects = params.effects;
@ -763,15 +742,11 @@ const BackgroundManager = new Lang.Class({
time: FADE_ANIMATION_TIME, time: FADE_ANIMATION_TIME,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, function() {
if (this._newBackground == newBackground) { if (this.background == background) {
this.background = newBackground; this.background = newBackground;
this._newBackground = null; this._newBackground = null;
} else { background.actor.destroy();
newBackground.actor.destroy();
} }
background.actor.destroy();
this.emit('changed'); this.emit('changed');
}) })
}); });
@ -783,8 +758,7 @@ const BackgroundManager = new Lang.Class({
_createBackground: function() { _createBackground: function() {
let background = new Background({ monitorIndex: this._monitorIndex, let background = new Background({ monitorIndex: this._monitorIndex,
layoutManager: this._layoutManager, layoutManager: this._layoutManager,
effects: this._effects, effects: this._effects });
settings: this._settings });
this._container.add_child(background.actor); this._container.add_child(background.actor);
let monitor = this._layoutManager.monitors[this._monitorIndex]; let monitor = this._layoutManager.monitors[this._monitorIndex];

View File

@ -46,10 +46,8 @@ function addBackgroundMenu(actor) {
clickAction.connect('long-press', function(action, actor, state) { clickAction.connect('long-press', function(action, actor, state) {
if (state == Clutter.LongPressState.QUERY) if (state == Clutter.LongPressState.QUERY)
return action.get_button() == 1 && !actor._backgroundMenu.isOpen; return action.get_button() == 1 && !actor._backgroundMenu.isOpen;
if (state == Clutter.LongPressState.ACTIVATE) { if (state == Clutter.LongPressState.ACTIVATE)
openMenu(); openMenu();
actor._backgroundManager.ignoreRelease();
}
return true; return true;
}); });
clickAction.connect('clicked', function(action) { clickAction.connect('clicked', function(action) {
@ -62,7 +60,5 @@ function addBackgroundMenu(actor) {
actor._backgroundMenu.destroy(); actor._backgroundMenu.destroy();
actor._backgroundMenu = null; actor._backgroundMenu = null;
actor._backgroundManager = null; actor._backgroundManager = null;
cursor.destroy();
}); });
} }

View File

@ -3,9 +3,8 @@
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Lang = imports.lang; const Lang = imports.lang;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
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 Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
@ -62,10 +61,6 @@ const BoxPointer = new Lang.Class({
this._muteInput(); this._muteInput();
}, },
get arrowSide() {
return this._arrowSide;
},
_muteInput: function() { _muteInput: function() {
if (this._capturedEventId == 0) if (this._capturedEventId == 0)
this._capturedEventId = this.actor.connect('captured-event', this._capturedEventId = this.actor.connect('captured-event',
@ -185,9 +180,7 @@ const BoxPointer = new Lang.Class({
}, },
_getPreferredHeight: function(actor, forWidth, alloc) { _getPreferredHeight: function(actor, forWidth, alloc) {
let themeNode = this.actor.get_theme_node(); let [minSize, naturalSize] = this.bin.get_preferred_height(forWidth);
let borderWidth = themeNode.get_length('-arrow-border-width');
let [minSize, naturalSize] = this.bin.get_preferred_height(forWidth - 2 * borderWidth);
alloc.min_size = minSize; alloc.min_size = minSize;
alloc.natural_size = naturalSize; alloc.natural_size = naturalSize;
this._adjustAllocationForArrow(false, alloc); this._adjustAllocationForArrow(false, alloc);
@ -596,12 +589,12 @@ const BoxPointer = new Lang.Class({
return St.Side.TOP; return St.Side.TOP;
break; break;
case St.Side.LEFT: case St.Side.LEFT:
if (sourceAllocation.x2 + boxWidth > monitor.x + monitor.width && if (sourceAllocation.y2 + boxWidth > monitor.x + monitor.width &&
boxWidth < sourceAllocation.x1 - monitor.x) boxWidth < sourceAllocation.x1 - monitor.x)
return St.Side.RIGHT; return St.Side.RIGHT;
break; break;
case St.Side.RIGHT: case St.Side.RIGHT:
if (sourceAllocation.x1 - boxWidth < monitor.x && if (sourceAllocation.y1 - boxWidth < monitor.x &&
boxWidth < monitor.x + monitor.width - sourceAllocation.x2) boxWidth < monitor.x + monitor.width - sourceAllocation.x2)
return St.Side.LEFT; return St.Side.LEFT;
break; break;
@ -619,8 +612,6 @@ const BoxPointer = new Lang.Class({
this._container.queue_relayout(); this._container.queue_relayout();
return false; return false;
})); }));
this.emit('arrow-side-changed');
} }
}, },
@ -648,21 +639,5 @@ const BoxPointer = new Lang.Class({
get opacity() { get opacity() {
return this.actor.opacity; return this.actor.opacity;
},
updateArrowSide: function(side) {
this._arrowSide = side;
this._border.queue_repaint();
this.emit('arrow-side-changed');
},
getPadding: function(side) {
return this.bin.get_theme_node().get_padding(side);
},
getArrowHeight: function() {
return this.actor.get_theme_node().get_length('-arrow-rise');
} }
}); });
Signals.addSignalMethods(BoxPointer.prototype);

View File

@ -443,21 +443,16 @@ const Calendar = new Lang.Class({
this.actor.add(this._topBox, this.actor.add(this._topBox,
{ row: 0, col: 0, col_span: offsetCols + 7 }); { row: 0, col: 0, col_span: offsetCols + 7 });
this._backButton = new St.Button({ style_class: 'calendar-change-month-back', let back = new St.Button({ style_class: 'calendar-change-month-back' });
accessible_name: _("Previous month"), this._topBox.add(back);
can_focus: true }); back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
this._topBox.add(this._backButton);
this._backButton.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
this._monthLabel = new St.Label({style_class: 'calendar-month-label', this._monthLabel = new St.Label({style_class: 'calendar-month-label'});
can_focus: true });
this._topBox.add(this._monthLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE }); this._topBox.add(this._monthLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
this._forwardButton = new St.Button({ style_class: 'calendar-change-month-forward', let forward = new St.Button({ style_class: 'calendar-change-month-forward' });
accessible_name: _("Next month"), this._topBox.add(forward);
can_focus: true }); forward.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked));
this._topBox.add(this._forwardButton);
this._forwardButton.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked));
// Add weekday labels... // Add weekday labels...
// //
@ -516,12 +511,10 @@ const Calendar = new Lang.Class({
} }
} }
this._backButton.grab_key_focus();
this.setDate(newDate, false); this.setDate(newDate, false);
}, },
_onNextMonthButtonClicked: function() { _onNextMonthButtonClicked: function() {
let newDate = new Date(this._selectedDate); let newDate = new Date(this._selectedDate);
let oldMonth = newDate.getMonth(); let oldMonth = newDate.getMonth();
if (oldMonth == 11) { if (oldMonth == 11) {
@ -540,9 +533,7 @@ const Calendar = new Lang.Class({
} }
} }
this._forwardButton.grab_key_focus(); this.setDate(newDate, false);
this.setDate(newDate, false);
}, },
_onSettingsChange: function() { _onSettingsChange: function() {
@ -599,8 +590,7 @@ const Calendar = new Lang.Class({
// nRows here means 6 weeks + one header + one navbar // nRows here means 6 weeks + one header + one navbar
let nRows = 8; let nRows = 8;
while (row < 8) { while (row < 8) {
let button = new St.Button({ label: iter.getDate().toString(), let button = new St.Button({ label: iter.getDate().toString() });
can_focus: true });
let rtl = button.get_text_direction() == Clutter.TextDirection.RTL; let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;
if (this._eventSource.isDummy) if (this._eventSource.isDummy)
@ -608,12 +598,8 @@ const Calendar = new Lang.Class({
let iterStr = iter.toUTCString(); let iterStr = iter.toUTCString();
button.connect('clicked', Lang.bind(this, function() { button.connect('clicked', Lang.bind(this, function() {
this._shouldDateGrabFocus = true;
let newlySelectedDate = new Date(iterStr); let newlySelectedDate = new Date(iterStr);
this.setDate(newlySelectedDate, false); this.setDate(newlySelectedDate, false);
this._shouldDateGrabFocus = false;
})); }));
let hasEvents = this._eventSource.hasEvents(iter); let hasEvents = this._eventSource.hasEvents(iter);
@ -638,6 +624,9 @@ const Calendar = new Lang.Class({
else if (iter.getMonth() != this._selectedDate.getMonth()) else if (iter.getMonth() != this._selectedDate.getMonth())
styleClass += ' calendar-other-month-day'; styleClass += ' calendar-other-month-day';
if (_sameDay(this._selectedDate, iter))
button.add_style_pseudo_class('active');
if (hasEvents) if (hasEvents)
styleClass += ' calendar-day-with-events' styleClass += ' calendar-day-with-events'
@ -647,13 +636,6 @@ const Calendar = new Lang.Class({
this.actor.add(button, this.actor.add(button,
{ row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 }); { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 });
if (_sameDay(this._selectedDate, iter)) {
button.add_style_pseudo_class('active');
if (this._shouldDateGrabFocus)
button.grab_key_focus();
}
if (this._useWeekdate && iter.getDay() == 4) { if (this._useWeekdate && iter.getDay() == 4) {
let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(), let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(),
style_class: 'calendar-day-base calendar-week-number'}); style_class: 'calendar-day-base calendar-week-number'});
@ -678,7 +660,7 @@ const EventsList = new Lang.Class({
Name: 'EventsList', Name: 'EventsList',
_init: function() { _init: function() {
this.actor = new St.Table({ style_class: 'events-table' }); this.actor = new St.BoxLayout({ vertical: true, style_class: 'events-header-vbox'});
this._date = new Date(); this._date = new Date();
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));
@ -690,72 +672,55 @@ const EventsList = new Lang.Class({
this._eventSource.connect('changed', Lang.bind(this, this._update)); this._eventSource.connect('changed', Lang.bind(this, this._update));
}, },
_addEvent: function(event, index, includeDayName) { _addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) {
let dayString; if (includeDayName) {
if (includeDayName) dayNameBox.add(new St.Label( { style_class: 'events-day-dayname',
dayString = _getEventDayAbbreviation(event.date.getDay()); text: day } ),
else { x_fill: true } );
dayString = ''; }
timeBox.add(new St.Label( { style_class: 'events-day-time',
let dayLabel = new St.Label({ style_class: 'events-day-dayname', text: time} ),
text: dayString }); { x_fill: true } );
dayLabel.clutter_text.line_wrap = false; eventTitleBox.add(new St.Label( { style_class: 'events-day-task',
dayLabel.clutter_text.ellipsize = false; text: desc} ));
this.actor.add(dayLabel, { row: index, col: 0,
x_expand: false, x_align: St.Align.END,
y_fill: false, y_align: St.Align.START });
let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
let timeString = _formatEventTime(event, clockFormat);
let timeLabel = new St.Label({ style_class: 'events-day-time',
text: timeString });
timeLabel.clutter_text.line_wrap = false;
timeLabel.clutter_text.ellipsize = false;
this.actor.add(timeLabel, { row: index, col: 1,
x_expand: false, x_align: St.Align.MIDDLE,
y_fill: false, y_align: St.Align.START });
let titleLabel = new St.Label({ style_class: 'events-day-task',
text: event.summary });
titleLabel.clutter_text.line_wrap = true;
titleLabel.clutter_text.ellipsize = false;
this.actor.add(titleLabel, { row: index, col: 2,
x_expand: true, x_align: St.Align.START,
y_fill: false, y_align: St.Align.START });
}, },
_addPeriod: function(header, index, begin, end, includeDayName, showNothingScheduled) { _addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
let events = this._eventSource.getEvents(begin, end); let events = this._eventSource.getEvents(begin, end);
if (events.length == 0 && !showNothingScheduled) let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);;
return index;
this.actor.add(new St.Label({ style_class: 'events-day-header', text: header }), if (events.length == 0 && !showNothingScheduled)
{ row: index, col: 0, col_span: 3, return;
// In theory, x_expand should be true here, but x_expand
// is a property of the column for StTable, ie all day cells let vbox = new St.BoxLayout( {vertical: true} );
// get it too this.actor.add(vbox);
x_expand: false, x_align: St.Align.START,
y_fill: false, y_align: St.Align.START }); vbox.add(new St.Label({ style_class: 'events-day-header', text: header }));
index++; let box = new St.BoxLayout({style_class: 'events-header-hbox'});
let dayNameBox = new St.BoxLayout({ vertical: true, style_class: 'events-day-name-box' });
let timeBox = new St.BoxLayout({ vertical: true, style_class: 'events-time-box' });
let eventTitleBox = new St.BoxLayout({ vertical: true, style_class: 'events-event-box' });
box.add(dayNameBox, {x_fill: false});
box.add(timeBox, {x_fill: false});
box.add(eventTitleBox, {expand: true});
vbox.add(box);
for (let n = 0; n < events.length; n++) { for (let n = 0; n < events.length; n++) {
this._addEvent(events[n], index, includeDayName); let event = events[n];
index++; let dayString = _getEventDayAbbreviation(event.date.getDay());
let timeString = _formatEventTime(event, clockFormat);
let summaryString = event.summary;
this._addEvent(dayNameBox, timeBox, eventTitleBox, includeDayName, dayString, timeString, summaryString);
} }
if (events.length == 0 && showNothingScheduled) { if (events.length == 0 && showNothingScheduled) {
let now = new Date(); let now = new Date();
/* Translators: Text to show if there are no events */ /* Translators: Text to show if there are no events */
let nothingEvent = new CalendarEvent(now, now, _("Nothing Scheduled"), true); let nothingEvent = new CalendarEvent(now, now, _("Nothing Scheduled"), true);
this._addEvent(nothingEvent, index, false); let timeString = _formatEventTime(nothingEvent, clockFormat);
index++; this._addEvent(dayNameBox, timeBox, eventTitleBox, false, "", timeString, nothingEvent.summary);
} }
return index;
}, },
_showOtherDay: function(day) { _showOtherDay: function(day) {
@ -772,21 +737,20 @@ const EventsList = new Lang.Class({
else else
/* Translators: Shown on calendar heading when selected day occurs on different year */ /* Translators: Shown on calendar heading when selected day occurs on different year */
dayString = day.toLocaleFormat(C_("calendar heading", "%A, %B %d, %Y")); dayString = day.toLocaleFormat(C_("calendar heading", "%A, %B %d, %Y"));
this._addPeriod(dayString, 0, dayBegin, dayEnd, false, true); this._addPeriod(dayString, dayBegin, dayEnd, false, true);
}, },
_showToday: function() { _showToday: function() {
this.actor.destroy_all_children(); this.actor.destroy_all_children();
let index = 0;
let now = new Date(); let now = new Date();
let dayBegin = _getBeginningOfDay(now); let dayBegin = _getBeginningOfDay(now);
let dayEnd = _getEndOfDay(now); let dayEnd = _getEndOfDay(now);
index = this._addPeriod(_("Today"), index, dayBegin, dayEnd, false, true); this._addPeriod(_("Today"), dayBegin, dayEnd, false, true);
let tomorrowBegin = new Date(dayBegin.getTime() + 86400 * 1000); let tomorrowBegin = new Date(dayBegin.getTime() + 86400 * 1000);
let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000); let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000);
index = this._addPeriod(_("Tomorrow"), index, tomorrowBegin, tomorrowEnd, false, true); this._addPeriod(_("Tomorrow"), tomorrowBegin, tomorrowEnd, false, true);
let dayInWeek = (dayEnd.getDay() - this._weekStart + 7) % 7; let dayInWeek = (dayEnd.getDay() - this._weekStart + 7) % 7;
@ -797,7 +761,7 @@ const EventsList = new Lang.Class({
*/ */
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 - dayInWeek) * 86400 * 1000);
index = this._addPeriod(_("This week"), index, 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
* "Next week" and include events up until and including *next* * "Next week" and include events up until and including *next*
@ -805,7 +769,7 @@ const EventsList = new Lang.Class({
*/ */
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 - dayInWeek) * 86400 * 1000);
index = this._addPeriod(_("Next week"), index, nextWeekBegin, nextWeekEnd, true, false); this._addPeriod(_("Next week"), nextWeekBegin, nextWeekEnd, true, false);
} }
}, },

View File

@ -1,40 +1,115 @@
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Pango = imports.gi.Pango; const Pango = imports.gi.Pango;
const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const Lang = imports.lang; const Lang = imports.lang;
const CheckBoxContainer = new Lang.Class({
Name: 'CheckBoxContainer',
_init: function() {
this.actor = new Shell.GenericContainer();
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('allocate',
Lang.bind(this, this._allocate));
this.actor.connect('style-changed', Lang.bind(this,
function() {
let node = this.actor.get_theme_node();
this._spacing = node.get_length('spacing');
}));
this.actor.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
this._box = new St.Bin();
this.actor.add_actor(this._box);
this.label = new St.Label();
this.label.clutter_text.set_line_wrap(true);
this.label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
this.actor.add_actor(this.label);
this._spacing = 0;
},
_getPreferredWidth: function(actor, forHeight, alloc) {
let [minWidth, natWidth] = this._box.get_preferred_width(forHeight);
alloc.min_size = minWidth + this._spacing;
alloc.natural_size = natWidth + this._spacing;
},
_getPreferredHeight: function(actor, forWidth, alloc) {
/* FIXME: StBoxlayout currently does not handle
height-for-width children correctly, so hard-code
two lines for the label until that problem is fixed.
https://bugzilla.gnome.org/show_bug.cgi?id=672543 */
/*
let [minBoxHeight, natBoxHeight] =
this._box.get_preferred_height(forWidth);
let [minLabelHeight, natLabelHeight] =
this.label.get_preferred_height(forWidth);
alloc.min_size = Math.max(minBoxHeight, minLabelHeight);
alloc.natural_size = Math.max(natBoxHeight, natLabelHeight);
*/
let [minBoxHeight, natBoxHeight] =
this._box.get_preferred_height(-1);
let [minLabelHeight, natLabelHeight] =
this.label.get_preferred_height(-1);
alloc.min_size = Math.max(minBoxHeight, 2 * minLabelHeight);
alloc.natural_size = Math.max(natBoxHeight, 2 * natLabelHeight);
},
_allocate: function(actor, box, flags) {
let availWidth = box.x2 - box.x1;
let availHeight = box.y2 - box.y1;
let childBox = new Clutter.ActorBox();
let [minBoxWidth, natBoxWidth] =
this._box.get_preferred_width(-1);
let [minBoxHeight, natBoxHeight] =
this._box.get_preferred_height(-1);
childBox.x1 = box.x1;
childBox.x2 = box.x1 + natBoxWidth;
childBox.y1 = box.y1;
childBox.y2 = box.y1 + natBoxHeight;
this._box.allocate(childBox, flags);
childBox.x1 = box.x1 + natBoxWidth + this._spacing;
childBox.x2 = availWidth - childBox.x1;
childBox.y1 = box.y1;
childBox.y2 = box.y2;
this.label.allocate(childBox, flags);
}
});
const CheckBox = new Lang.Class({ const CheckBox = new Lang.Class({
Name: 'CheckBox', Name: 'CheckBox',
_init: function(label) { _init: function(label) {
let container = new St.BoxLayout();
this.actor = new St.Button({ style_class: 'check-box', this.actor = new St.Button({ style_class: 'check-box',
child: container,
button_mask: St.ButtonMask.ONE, button_mask: St.ButtonMask.ONE,
toggle_mode: true, toggle_mode: true,
can_focus: true, can_focus: true,
x_fill: true, x_fill: true,
y_fill: true }); y_fill: true });
this._container = new CheckBoxContainer();
this._box = new St.Bin(); this.actor.set_child(this._container.actor);
this._box.set_y_align(Clutter.ActorAlign.START);
container.add_actor(this._box);
this._label = new St.Label();
this._label.clutter_text.set_line_wrap(true);
this._label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
container.add_actor(this._label);
if (label) if (label)
this.setLabel(label); this.setLabel(label);
}, },
setLabel: function(label) { setLabel: function(label) {
this._label.set_text(label); this._container.label.set_text(label);
}, },
getLabelActor: function() { getLabelActor: function() {
return this._label; return this._container.label;
} }
}); });

View File

@ -31,7 +31,7 @@ function shouldAutorunMount(mount, forTransient) {
if (!volume || (!volume.allowAutorun && forTransient)) if (!volume || (!volume.allowAutorun && forTransient))
return false; return false;
if (root.is_native() && isMountRootHidden(root)) if (!root.is_native() || isMountRootHidden(root))
return false; return false;
return true; return true;

View File

@ -13,6 +13,8 @@ const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry; const ShellEntry = imports.ui.shellEntry;
const CheckBox = imports.ui.checkBox; const CheckBox = imports.ui.checkBox;
let prompter = null;
const KeyringDialog = new Lang.Class({ const KeyringDialog = new Lang.Class({
Name: 'KeyringDialog', Name: 'KeyringDialog',
Extends: ModalDialog.ModalDialog, Extends: ModalDialog.ModalDialog,
@ -23,7 +25,7 @@ const KeyringDialog = new Lang.Class({
this.prompt = new Shell.KeyringPrompt(); this.prompt = new Shell.KeyringPrompt();
this.prompt.connect('show-password', Lang.bind(this, this._onShowPassword)); this.prompt.connect('show-password', Lang.bind(this, this._onShowPassword));
this.prompt.connect('show-confirm', Lang.bind(this, this._onShowConfirm)); this.prompt.connect('show-confirm', Lang.bind(this, this._onShowConfirm));
this.prompt.connect('prompt-close', Lang.bind(this, this._onHidePrompt)); this.prompt.connect('hide-prompt', Lang.bind(this, this._onHidePrompt));
let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout', let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout',
vertical: false }); vertical: false });
@ -61,43 +63,34 @@ const KeyringDialog = new Lang.Class({
this._cancelButton = this.addButton({ label: '', this._cancelButton = this.addButton({ label: '',
action: Lang.bind(this, this._onCancelButton), action: Lang.bind(this, this._onCancelButton),
key: Clutter.Escape }, key: Clutter.Escape });
{ expand: true, x_fill: false, x_align: St.Align.START });
this.placeSpinner({ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
this._continueButton = this.addButton({ label: '', this._continueButton = this.addButton({ label: '',
action: Lang.bind(this, this._onContinueButton), action: Lang.bind(this, this._onContinueButton),
default: true }, default: true },
{ expand: false, x_fill: false, x_align: St.Align.END }); { expand: true, x_fill: false, x_align: St.Align.END });
this.prompt.bind_property('cancel-label', this._cancelButton, 'label', GObject.BindingFlags.SYNC_CREATE); this.prompt.bind_property('cancel-label', this._cancelButton, 'label', GObject.BindingFlags.SYNC_CREATE);
this.prompt.bind_property('continue-label', this._continueButton, 'label', GObject.BindingFlags.SYNC_CREATE); this.prompt.bind_property('continue-label', this._continueButton, 'label', GObject.BindingFlags.SYNC_CREATE);
}, },
_buildControlTable: function() { _buildControlTable: function() {
let layout = new Clutter.TableLayout(); let table = new St.Table({ style_class: 'keyring-dialog-control-table' });
let table = new St.Widget({ style_class: 'keyring-dialog-control-table',
layout_manager: layout });
layout.hookup_style(table);
let row = 0; let row = 0;
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:"));
label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; table.add(label, { row: row, col: 0,
layout.pack(label, 0, row); x_expand: false, x_fill: true,
layout.child_set(label, { x_expand: false, y_fill: false, x_align: St.Align.START,
x_align: Clutter.TableAlignment.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});
this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true }); ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onPasswordActivate)); this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onPasswordActivate));
layout.pack(this._passwordEntry, 1, row); table.add(this._passwordEntry, { row: row, col: 1, x_expand: true, x_fill: true, x_align: St.Align.START });
row++; row++;
} else { } else {
this._passwordEntry = null; this._passwordEntry = null;
@ -106,16 +99,17 @@ 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:"));
layout.pack(label, 0, row); table.add(label, { row: row, col: 0,
layout.child_set(label, { x_expand: false, y_fill: false, x_expand: false, x_fill: true,
x_align: Clutter.TableAlignment.START }); 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});
this._confirmEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE this._confirmEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
ShellEntry.addContextMenu(this._confirmEntry, { isPassword: true }); ShellEntry.addContextMenu(this._confirmEntry, { isPassword: true });
this._confirmEntry.clutter_text.connect('activate', Lang.bind(this, this._onConfirmActivate)); this._confirmEntry.clutter_text.connect('activate', Lang.bind(this, this._onConfirmActivate));
layout.pack(this._confirmEntry, 1, row); table.add(this._confirmEntry, { row: row, col: 1, x_expand: true, x_fill: true, x_align: St.Align.START });
row++; row++;
} else { } else {
this._confirmEntry = null; this._confirmEntry = null;
@ -128,14 +122,14 @@ const KeyringDialog = new Lang.Class({
let choice = new CheckBox.CheckBox(); let choice = new CheckBox.CheckBox();
this.prompt.bind_property('choice-label', choice.getLabelActor(), 'text', GObject.BindingFlags.SYNC_CREATE); this.prompt.bind_property('choice-label', choice.getLabelActor(), 'text', GObject.BindingFlags.SYNC_CREATE);
this.prompt.bind_property('choice-chosen', choice.actor, 'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL); this.prompt.bind_property('choice-chosen', choice.actor, 'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL);
layout.pack(choice.actor, 1, row); table.add(choice.actor, { row: row, col: 1, x_expand: false, x_fill: true, x_align: St.Align.START });
row++; row++;
} }
let warning = new St.Label({ style_class: 'prompt-dialog-error-label' }); let warning = new St.Label({ style_class: 'prompt-dialog-error-label' });
warning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; warning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
warning.clutter_text.line_wrap = true; warning.clutter_text.line_wrap = true;
layout.pack(warning, 1, row); table.add(warning, { row: row, col: 1, x_expand: false, x_fill: false, x_align: St.Align.START });
this.prompt.bind_property('warning-visible', warning, 'visible', GObject.BindingFlags.SYNC_CREATE); this.prompt.bind_property('warning-visible', warning, 'visible', GObject.BindingFlags.SYNC_CREATE);
this.prompt.bind_property('warning', warning, 'text', GObject.BindingFlags.SYNC_CREATE); this.prompt.bind_property('warning', warning, 'text', GObject.BindingFlags.SYNC_CREATE);
@ -149,19 +143,11 @@ const KeyringDialog = new Lang.Class({
}, },
_updateSensitivity: function(sensitive) { _updateSensitivity: function(sensitive) {
if (this._passwordEntry) { this._passwordEntry.reactive = sensitive;
this._passwordEntry.reactive = sensitive; this._passwordEntry.clutter_text.editable = sensitive;
this._passwordEntry.clutter_text.editable = sensitive;
}
if (this._confirmEntry) {
this._confirmEntry.reactive = sensitive;
this._confirmEntry.clutter_text.editable = sensitive;
}
this._continueButton.can_focus = sensitive; this._continueButton.can_focus = sensitive;
this._continueButton.reactive = sensitive; this._continueButton.reactive = sensitive;
this.setWorking(!sensitive);
}, },
_ensureOpen: function() { _ensureOpen: function() {
@ -221,56 +207,27 @@ const KeyringDialog = new Lang.Class({
}, },
}); });
const KeyringDummyDialog = new Lang.Class({
Name: 'KeyringDummyDialog',
_init: function() {
this.prompt = new Shell.KeyringPrompt();
this.prompt.connect('show-password',
Lang.bind(this, this._cancelPrompt));
this.prompt.connect('show-confirm', Lang.bind(this,
this._cancelPrompt));
},
_cancelPrompt: function() {
this.prompt.cancel();
}
});
const KeyringPrompter = new Lang.Class({ const KeyringPrompter = new Lang.Class({
Name: 'KeyringPrompter', Name: 'KeyringPrompter',
_init: function() { _init: function() {
this._prompter = new Gcr.SystemPrompter(); this._prompter = new Gcr.SystemPrompter();
this._prompter.connect('new-prompt', Lang.bind(this, this._prompter.connect('new-prompt', function(prompter) {
function() { let dialog = new KeyringDialog();
let dialog = this._enabled ? new KeyringDialog() return dialog.prompt;
: new KeyringDummyDialog(); });
this._currentPrompt = dialog.prompt;
return this._currentPrompt;
}));
this._dbusId = null; this._dbusId = null;
this._registered = false;
this._enabled = false;
this._currentPrompt = null;
}, },
enable: function() { enable: function() {
if (!this._registered) { this._prompter.register(Gio.DBus.session);
this._prompter.register(Gio.DBus.session); this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter',
this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter', Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
this._registered = true;
}
this._enabled = true;
}, },
disable: function() { disable: function() {
this._enabled = false; this._prompter.unregister(false);
Gio.DBus.session.unown_name(this._dbusId);
if (this._prompter.prompting)
this._currentPrompt.cancel();
this._currentPrompt = null;
} }
}); });

View File

@ -62,9 +62,14 @@ const NetworkSecretDialog = new Lang.Class({
if (this._content.message != null) { if (this._content.message != null) {
let descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description', let descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description',
text: this._content.message }); text: this._content.message,
// HACK: for reasons unknown to me, the label
// is not asked the correct height for width,
// and thus is underallocated
// place a fixed height to avoid overflowing
style: 'height: 3em'
});
descriptionLabel.clutter_text.line_wrap = true; descriptionLabel.clutter_text.line_wrap = true;
descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
messageBox.add(descriptionLabel, messageBox.add(descriptionLabel,
{ y_fill: true, { y_fill: true,
@ -72,18 +77,13 @@ const NetworkSecretDialog = new Lang.Class({
expand: true }); expand: true });
} }
let layout = new Clutter.TableLayout(); let secretTable = new St.Table({ style_class: 'network-dialog-secret-table' });
let secretTable = new St.Widget({ style_class: 'network-dialog-secret-table',
layout_manager: layout });
layout.hookup_style(secretTable);
let initialFocusSet = false; let initialFocusSet = false;
let pos = 0; let pos = 0;
for (let i = 0; i < this._content.secrets.length; i++) { for (let i = 0; i < this._content.secrets.length; i++) {
let secret = this._content.secrets[i]; let secret = this._content.secrets[i];
let label = new St.Label({ style_class: 'prompt-dialog-password-label', let label = new St.Label({ style_class: 'prompt-dialog-password-label',
text: secret.label }); text: secret.label });
label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
let reactive = secret.key != null; let reactive = secret.key != null;
@ -116,10 +116,11 @@ const NetworkSecretDialog = new Lang.Class({
} else } else
secret.valid = true; secret.valid = true;
layout.pack(label, 0, pos); secretTable.add(label, { row: pos, col: 0,
layout.child_set(label, { x_expand: false, y_fill: false, x_expand: false, x_fill: true,
x_align: Clutter.TableAlignment.START }); x_align: St.Align.START,
layout.pack(secret.entry, 1, pos); 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 });
pos++; pos++;
if (secret.password) if (secret.password)
@ -384,7 +385,11 @@ const VPNRequestHandler = new Lang.Class({
this._childPid = pid; this._childPid = pid;
this._stdin = new Gio.UnixOutputStream({ fd: stdin, close_fd: true }); this._stdin = new Gio.UnixOutputStream({ fd: stdin, close_fd: true });
this._stdout = new Gio.UnixInputStream({ fd: stdout, close_fd: true }); this._stdout = new Gio.UnixInputStream({ fd: stdout, close_fd: true });
GLib.close(stderr); // We need this one too, even if don't actually care of what the process
// has to say on stderr, because otherwise the fd opened by g_spawn_async_with_pipes
// is kept open indefinitely
let stderrStream = new Gio.UnixInputStream({ fd: stderr, close_fd: true });
stderrStream.close(null);
this._dataStdout = new Gio.DataInputStream({ base_stream: this._stdout }); this._dataStdout = new Gio.DataInputStream({ base_stream: this._stdout });
if (this._newStylePlugin) if (this._newStylePlugin)

View File

@ -16,7 +16,7 @@ const PolkitAgent = imports.gi.PolkitAgent;
const Components = imports.ui.components; 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 UserWidget = imports.ui.userWidget; const UserMenu = imports.ui.userMenu;
const DIALOG_ICON_SIZE = 48; const DIALOG_ICON_SIZE = 48;
@ -31,6 +31,7 @@ const AuthenticationDialog = new Lang.Class({
this.message = message; this.message = message;
this.userNames = userNames; this.userNames = userNames;
this._wasDismissed = false; this._wasDismissed = false;
this._completed = false;
let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout', let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout',
vertical: false }); vertical: false });
@ -100,9 +101,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 UserWidget.Avatar(this._user, this._userAvatar = new UserMenu.UserAvatarWidget(this._user,
{ iconSize: DIALOG_ICON_SIZE, { iconSize: DIALOG_ICON_SIZE,
styleClass: 'polkit-dialog-user-icon' }); styleClass: 'polkit-dialog-user-icon' });
this._userAvatar.actor.hide(); this._userAvatar.actor.hide();
userBox.add(this._userAvatar.actor, userBox.add(this._userAvatar.actor,
{ x_fill: true, { x_fill: true,
@ -160,32 +161,26 @@ const AuthenticationDialog = new Lang.Class({
this._cancelButton = this.addButton({ label: _("Cancel"), this._cancelButton = this.addButton({ label: _("Cancel"),
action: Lang.bind(this, this.cancel), action: Lang.bind(this, this.cancel),
key: Clutter.Escape }, key: Clutter.Escape });
{ expand: true, x_fill: false, x_align: St.Align.START });
this.placeSpinner({ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
this._okButton = this.addButton({ label: _("Authenticate"), this._okButton = this.addButton({ label: _("Authenticate"),
action: Lang.bind(this, this._onAuthenticateButtonPressed), action: Lang.bind(this, this._onAuthenticateButtonPressed),
default: true }, default: true },
{ expand: false, x_fill: false, x_align: St.Align.END }); { expand: true, x_fill: false, x_align: St.Align.END });
this._doneEmitted = false; this._doneEmitted = false;
this._identityToAuth = Polkit.UnixUser.new_for_name(userName); this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
this._cookie = cookie; this._cookie = cookie;
},
performAuthentication: function() {
this.destroySession();
this._session = new PolkitAgent.Session({ identity: this._identityToAuth, this._session = new PolkitAgent.Session({ identity: this._identityToAuth,
cookie: this._cookie }); cookie: this._cookie });
this._session.connect('completed', Lang.bind(this, this._onSessionCompleted)); this._session.connect('completed', Lang.bind(this, this._onSessionCompleted));
this._session.connect('request', Lang.bind(this, this._onSessionRequest)); this._session.connect('request', Lang.bind(this, this._onSessionRequest));
this._session.connect('show-error', Lang.bind(this, this._onSessionShowError)); this._session.connect('show-error', Lang.bind(this, this._onSessionShowError));
this._session.connect('show-info', Lang.bind(this, this._onSessionShowInfo)); this._session.connect('show-info', Lang.bind(this, this._onSessionShowInfo));
},
startAuthentication: function() {
this._session.initiate(); this._session.initiate();
}, },
@ -207,14 +202,14 @@ const AuthenticationDialog = new Lang.Class({
log('polkitAuthenticationAgent: Failed to show modal dialog.' + log('polkitAuthenticationAgent: Failed to show modal dialog.' +
' Dismissing authentication request for action-id ' + this.actionId + ' Dismissing authentication request for action-id ' + this.actionId +
' cookie ' + this._cookie); ' cookie ' + this._cookie);
this._emitDone(true); this._emitDone(false, true);
} }
}, },
_emitDone: function(dismissed) { _emitDone: function(keepVisible, dismissed) {
if (!this._doneEmitted) { if (!this._doneEmitted) {
this._doneEmitted = true; this._doneEmitted = true;
this.emit('done', dismissed); this.emit('done', keepVisible, dismissed);
} }
}, },
@ -224,7 +219,6 @@ const AuthenticationDialog = new Lang.Class({
this._okButton.can_focus = sensitive; this._okButton.can_focus = sensitive;
this._okButton.reactive = sensitive; this._okButton.reactive = sensitive;
this.setWorking(!sensitive);
}, },
_onEntryActivate: function() { _onEntryActivate: function() {
@ -243,16 +237,12 @@ const AuthenticationDialog = new Lang.Class({
}, },
_onSessionCompleted: function(session, gainedAuthorization) { _onSessionCompleted: function(session, gainedAuthorization) {
if (this._completed || this._doneEmitted) if (this._completed)
return; return;
this._completed = true; this._completed = true;
/* Yay, all done */ if (!gainedAuthorization) {
if (gainedAuthorization) {
this._emitDone(false);
} else {
/* Unless we are showing an existing error message from the PAM /* Unless we are showing an existing error message from the PAM
* module (the PAM module could be reporting the authentication * module (the PAM module could be reporting the authentication
* error providing authentication-method specific information), * error providing authentication-method specific information),
@ -268,10 +258,8 @@ const AuthenticationDialog = new Lang.Class({
this._infoMessageLabel.hide(); this._infoMessageLabel.hide();
this._nullMessageLabel.hide(); this._nullMessageLabel.hide();
} }
/* Try and authenticate again */
this.performAuthentication();
} }
this._emitDone(!gainedAuthorization, false);
}, },
_onSessionRequest: function(session, request, echo_on) { _onSessionRequest: function(session, request, echo_on) {
@ -315,7 +303,6 @@ const AuthenticationDialog = new Lang.Class({
if (this._session) { if (this._session) {
if (!this._completed) if (!this._completed)
this._session.cancel(); this._session.cancel();
this._completed = false;
this._session = null; this._session = null;
} }
}, },
@ -330,7 +317,7 @@ const AuthenticationDialog = new Lang.Class({
cancel: function() { cancel: function() {
this._wasDismissed = true; this._wasDismissed = true;
this.close(global.get_current_time()); this.close(global.get_current_time());
this._emitDone(true); this._emitDone(false, true);
}, },
}); });
Signals.addSignalMethods(AuthenticationDialog.prototype); Signals.addSignalMethods(AuthenticationDialog.prototype);
@ -340,6 +327,7 @@ const AuthenticationAgent = new Lang.Class({
_init: function() { _init: function() {
this._currentDialog = null; this._currentDialog = null;
this._isCompleting = false;
this._handle = null; 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));
@ -376,24 +364,45 @@ const AuthenticationAgent = new Lang.Class({
// discussion. // discussion.
this._currentDialog.connect('done', Lang.bind(this, this._onDialogDone)); this._currentDialog.connect('done', Lang.bind(this, this._onDialogDone));
this._currentDialog.performAuthentication(); this._currentDialog.startAuthentication();
}, },
_onCancel: function(nativeAgent) { _onCancel: function(nativeAgent) {
this._completeRequest(false); this._completeRequest(false, false);
}, },
_onDialogDone: function(dialog, dismissed) { _onDialogDone: function(dialog, keepVisible, dismissed) {
this._completeRequest(dismissed); this._completeRequest(keepVisible, dismissed);
}, },
_completeRequest: function(dismissed) { _reallyCompleteRequest: function(dismissed) {
this._currentDialog.close(); this._currentDialog.close();
this._currentDialog.destroySession(); this._currentDialog.destroySession();
this._currentDialog = null; this._currentDialog = null;
this._isCompleting = false;
this._native.complete(dismissed); this._native.complete(dismissed)
}, },
_completeRequest: function(keepVisible, wasDismissed) {
if (this._isCompleting)
return;
this._isCompleting = true;
if (keepVisible) {
// Give the user 2 seconds to read 'Authentication Failure' before
// dismissing the dialog
Mainloop.timeout_add(2000,
Lang.bind(this,
function() {
this._reallyCompleteRequest(wasDismissed);
return false;
}));
} else {
this._reallyCompleteRequest(wasDismissed);
}
}
}); });
const Component = AuthenticationAgent; const Component = AuthenticationAgent;

View File

@ -0,0 +1,62 @@
const Lang = imports.lang;
const Main = imports.ui.main;
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() {
Main.wm.addKeybinding('toggle-recording',
this._bindingSettings,
Meta.KeyBindingFlags.NONE,
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._toggleRecorder));
},
disable: function() {
Main.wm.removeKeybinding('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_file_template(_("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;

View File

@ -13,11 +13,12 @@ const Tp = imports.gi.TelepathyGLib;
const History = imports.misc.history; const History = imports.misc.history;
const Main = imports.ui.main; const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const NotificationDaemon = imports.ui.notificationDaemon;
const Params = imports.misc.params; const Params = imports.misc.params;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
// See Notification.appendMessage // See Notification.appendMessage
const SCROLLBACK_IMMEDIATE_TIME = 3 * 60; // 3 minutes const SCROLLBACK_IMMEDIATE_TIME = 60; // 1 minute
const SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes const SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes
const SCROLLBACK_RECENT_LENGTH = 20; const SCROLLBACK_RECENT_LENGTH = 20;
const SCROLLBACK_IDLE_LENGTH = 5; const SCROLLBACK_IDLE_LENGTH = 5;
@ -415,7 +416,7 @@ const TelepathyClient = new Lang.Class({
_ensureAppSource: function() { _ensureAppSource: function() {
if (this._appSource == null) { if (this._appSource == null) {
this._appSource = new MessageTray.Source(_("Chat"), 'empathy'); this._appSource = new MessageTray.Source(_("Chat"), 'empathy');
this._appSource.policy = new MessageTray.NotificationApplicationPolicy('empathy'); this._appSource.policy = new NotificationDaemon.NotificationApplicationPolicy('empathy');
Main.messageTray.add(this._appSource); Main.messageTray.add(this._appSource);
this._appSource.connect('destroy', Lang.bind(this, function () { this._appSource.connect('destroy', Lang.bind(this, function () {
@ -487,7 +488,7 @@ const ChatSource = new Lang.Class({
}, },
_createPolicy: function() { _createPolicy: function() {
return new MessageTray.NotificationApplicationPolicy('empathy'); return new NotificationDaemon.NotificationApplicationPolicy('empathy');
}, },
_updateAlias: function() { _updateAlias: function() {
@ -966,8 +967,7 @@ const ChatNotification = new Lang.Class({
let timeLabel = this._append({ body: this._formatTimestamp(lastMessageDate), let timeLabel = this._append({ body: this._formatTimestamp(lastMessageDate),
group: 'meta', group: 'meta',
styles: ['chat-meta-message'], styles: ['chat-meta-message'],
childProps: { expand: true, x_fill: false, childProps: { expand: true, x_fill: false },
x_align: St.Align.END },
noTimestamp: true, noTimestamp: true,
timestamp: lastMessageTime }); timestamp: lastMessageTime });
@ -1060,7 +1060,7 @@ const ApproverSource = new Lang.Class({
}, },
_createPolicy: function() { _createPolicy: function() {
return new MessageTray.NotificationApplicationPolicy('empathy'); return new NotificationDaemon.NotificationApplicationPolicy('empathy');
}, },
destroy: function() { destroy: function() {
@ -1134,8 +1134,6 @@ const AudioVideoNotification = new Lang.Class({
this.parent(source, title, null, { customContent: true }); this.parent(source, title, null, { customContent: true });
this.setResident(true); this.setResident(true);
this.setUrgency(MessageTray.Urgency.CRITICAL);
this.addButton('reject', _("Decline")); this.addButton('reject', _("Decline"));
/* translators: this is a button label (verb), not a noun */ /* translators: this is a button label (verb), not a noun */
this.addButton('answer', _("Answer")); this.addButton('answer', _("Answer"));

View File

@ -58,10 +58,15 @@ const CtrlAltTabManager = new Lang.Class({
}, },
focusGroup: function(item, timestamp) { focusGroup: function(item, timestamp) {
if (item.focusCallback) if (item.focusCallback) {
item.focusCallback(timestamp); item.focusCallback(timestamp);
else } else {
if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE ||
global.stage_input_mode == Shell.StageInputMode.NORMAL)
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
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,
@ -84,25 +89,19 @@ const CtrlAltTabManager = new Lang.Class({
let items = this._items.filter(function (item) { return item.proxy.mapped; }); let items = this._items.filter(function (item) { return item.proxy.mapped; });
// And add the windows metacity would show in its Ctrl-Alt-Tab list // And add the windows metacity would show in its Ctrl-Alt-Tab list
if (Main.sessionMode.hasWindows && !Main.overview.visible) { if (!Main.overview.visible) {
let screen = global.screen; let screen = global.screen;
let display = screen.get_display(); let display = screen.get_display();
let windows = display.get_tab_list(Meta.TabList.DOCKS, screen, screen.get_active_workspace ()); let windows = display.get_tab_list(Meta.TabList.DOCKS, screen, screen.get_active_workspace ());
let windowTracker = Shell.WindowTracker.get_default(); let windowTracker = Shell.WindowTracker.get_default();
let textureCache = St.TextureCache.get_default(); let textureCache = St.TextureCache.get_default();
for (let i = 0; i < windows.length; i++) { for (let i = 0; i < windows.length; i++) {
let icon = null; let icon;
let iconName = null; let app = windowTracker.get_window_app(windows[i]);
if (windows[i].get_window_type () == Meta.WindowType.DESKTOP) { if (app)
iconName = 'video-display-symbolic'; icon = app.create_icon_texture(POPUP_APPICON_SIZE);
} else { else
let app = windowTracker.get_window_app(windows[i]); icon = textureCache.bind_pixbuf_property(windows[i], 'icon');
if (app)
icon = app.create_icon_texture(POPUP_APPICON_SIZE);
else
icon = textureCache.bind_pixbuf_property(windows[i], 'icon');
}
items.push({ name: windows[i].title, items.push({ name: windows[i].title,
proxy: windows[i].get_compositor_private(), proxy: windows[i].get_compositor_private(),
focusCallback: Lang.bind(windows[i], focusCallback: Lang.bind(windows[i],
@ -110,7 +109,6 @@ const CtrlAltTabManager = new Lang.Class({
Main.activateWindow(this, timestamp); Main.activateWindow(this, timestamp);
}), }),
iconActor: icon, iconActor: icon,
iconName: iconName,
sortGroup: SortGroup.MIDDLE }); sortGroup: SortGroup.MIDDLE });
} }
} }
@ -132,6 +130,8 @@ const CtrlAltTabManager = new Lang.Class({
}, },
_focusWindows: function(timestamp) { _focusWindows: function(timestamp) {
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
global.stage.key_focus = null;
global.screen.focus_default_window(timestamp); global.screen.focus_default_window(timestamp);
} }
}); });

View File

@ -287,7 +287,13 @@ const ShowAppsIcon = new Lang.Class({
}, },
handleDragOver: function(source, actor, x, y, time) { handleDragOver: function(source, actor, x, y, time) {
if (!this._canRemoveApp(getAppFromSource(source))) 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.NO_DROP;
return DND.DragMotionResult.MOVE_DROP; return DND.DragMotionResult.MOVE_DROP;
@ -295,7 +301,7 @@ const ShowAppsIcon = new Lang.Class({
acceptDrop: function(source, actor, x, y, time) { acceptDrop: function(source, actor, x, y, time) {
let app = getAppFromSource(source); let app = getAppFromSource(source);
if (!this._canRemoveApp(app)) if (app == null)
return false; return false;
let id = app.get_id(); let id = app.get_id();
@ -320,16 +326,6 @@ const DragPlaceholderItem = new Lang.Class({
} }
}); });
const EmptyDropTargetItem = new Lang.Class({
Name: 'EmptyDropTargetItem',
Extends: DashItemContainer,
_init: function() {
this.parent();
this.setChild(new St.Bin({ style_class: 'empty-dash-drop-target' }));
}
});
const DashActor = new Lang.Class({ const DashActor = new Lang.Class({
Name: 'DashActor', Name: 'DashActor',
Extends: St.Widget, Extends: St.Widget,
@ -423,10 +419,7 @@ const Dash = new Lang.Class({
this._appSystem = Shell.AppSystem.get_default(); this._appSystem = Shell.AppSystem.get_default();
this._appSystem.connect('installed-changed', Lang.bind(this, function() { this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
AppFavorites.getAppFavorites().reload();
this._queueRedisplay();
}));
AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay)); AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay));
this._appSystem.connect('app-state-changed', Lang.bind(this, this._queueRedisplay)); this._appSystem.connect('app-state-changed', Lang.bind(this, this._queueRedisplay));
@ -448,12 +441,6 @@ const Dash = new Lang.Class({
dragMotion: Lang.bind(this, this._onDragMotion) dragMotion: Lang.bind(this, this._onDragMotion)
}; };
DND.addDragMonitor(this._dragMonitor); DND.addDragMonitor(this._dragMonitor);
if (this._box.get_n_children() == 0) {
this._emptyDropTarget = new EmptyDropTargetItem();
this._box.insert_child_at_index(this._emptyDropTarget, 0);
this._emptyDropTarget.show(true);
}
}, },
_onDragCancelled: function() { _onDragCancelled: function() {
@ -470,7 +457,6 @@ const Dash = new Lang.Class({
_endDrag: function() { _endDrag: function() {
this._clearDragPlaceholder(); this._clearDragPlaceholder();
this._clearEmptyDropTarget();
this._showAppsIcon.setDragApp(null); this._showAppsIcon.setDragApp(null);
DND.removeDragMonitor(this._dragMonitor); DND.removeDragMonitor(this._dragMonitor);
}, },
@ -505,21 +491,15 @@ const Dash = new Lang.Class({
Main.queueDeferredWork(this._workId); Main.queueDeferredWork(this._workId);
}, },
_hookUpLabel: function(item, appIcon) { _hookUpLabel: function(item) {
item.child.connect('notify::hover', Lang.bind(this, function() { item.child.connect('notify::hover', Lang.bind(this, function() {
this._syncLabel(item, appIcon); this._onHover(item);
})); }));
Main.overview.connect('hiding', Lang.bind(this, function() { Main.overview.connect('hiding', Lang.bind(this, function() {
this._labelShowing = false; this._labelShowing = false;
item.hideLabel(); item.hideLabel();
})); }));
if (appIcon) {
appIcon.connect('sync-tooltip', Lang.bind(this, function() {
this._syncLabel(item, appIcon);
}));
}
}, },
_createAppItem: function(app) { _createAppItem: function(app) {
@ -548,7 +528,7 @@ const Dash = new Lang.Class({
item.setLabelText(app.get_name()); item.setLabelText(app.get_name());
appIcon.icon.setIconSize(this.iconSize); appIcon.icon.setIconSize(this.iconSize);
this._hookUpLabel(item, appIcon); this._hookUpLabel(item);
return item; return item;
}, },
@ -566,10 +546,8 @@ const Dash = new Lang.Class({
} }
}, },
_syncLabel: function (item, appIcon) { _onHover: function (item) {
let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover(); if (item.child.get_hover()) {
if (shouldShow) {
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,
@ -819,21 +797,9 @@ const Dash = new Lang.Class({
_clearDragPlaceholder: function() { _clearDragPlaceholder: function() {
if (this._dragPlaceholder) { if (this._dragPlaceholder) {
this._animatingPlaceholdersCount++;
this._dragPlaceholder.animateOutAndDestroy(); this._dragPlaceholder.animateOutAndDestroy();
this._dragPlaceholder.connect('destroy',
Lang.bind(this, function() {
this._animatingPlaceholdersCount--;
}));
this._dragPlaceholder = null; this._dragPlaceholder = null;
} this._dragPlaceholderPos = -1;
this._dragPlaceholderPos = -1;
},
_clearEmptyDropTarget: function() {
if (this._emptyDropTarget) {
this._emptyDropTarget.animateOutAndDestroy();
this._emptyDropTarget = null;
} }
}, },
@ -861,18 +827,23 @@ const Dash = new Lang.Class({
numChildren--; numChildren--;
} }
let pos; let pos = Math.floor(y * numChildren / boxHeight);
if (!this._emptyDropTarget)
pos = Math.floor(y * numChildren / boxHeight);
else
pos = 0; // always insert at the top when dash is empty
if (pos != this._dragPlaceholderPos && pos <= numFavorites && this._animatingPlaceholdersCount == 0) { if (pos != this._dragPlaceholderPos && pos <= numFavorites && this._animatingPlaceholdersCount == 0) {
this._dragPlaceholderPos = pos; this._dragPlaceholderPos = pos;
// Don't allow positioning before or after self // Don't allow positioning before or after self
if (favPos != -1 && (pos == favPos || pos == favPos + 1)) { if (favPos != -1 && (pos == favPos || pos == favPos + 1)) {
this._clearDragPlaceholder(); if (this._dragPlaceholder) {
this._dragPlaceholder.animateOutAndDestroy();
this._animatingPlaceholdersCount++;
this._dragPlaceholder.connect('destroy',
Lang.bind(this, function() {
this._animatingPlaceholdersCount--;
}));
}
this._dragPlaceholder = null;
return DND.DragMotionResult.CONTINUE; return DND.DragMotionResult.CONTINUE;
} }
@ -897,9 +868,9 @@ const Dash = new Lang.Class({
// Remove the drag placeholder if we are not in the // Remove the drag placeholder if we are not in the
// "favorites zone" // "favorites zone"
if (pos > numFavorites) if (pos > numFavorites && this._dragPlaceholder) {
this._clearDragPlaceholder(); this._clearDragPlaceholder();
}
if (!this._dragPlaceholder) if (!this._dragPlaceholder)
return DND.DragMotionResult.NO_DROP; return DND.DragMotionResult.NO_DROP;

View File

@ -49,13 +49,16 @@ const DateMenuButton = new Lang.Class({
menuAlignment = 1.0 - menuAlignment; menuAlignment = 1.0 - menuAlignment;
this.parent(menuAlignment); this.parent(menuAlignment);
this._clockDisplay = new St.Label({ y_align: Clutter.ActorAlign.CENTER }); // At this moment calendar menu is not keyboard navigable at
this.actor.label_actor = this._clockDisplay; // all (so not accessible), so it doesn't make sense to set as
this.actor.add_actor(this._clockDisplay); // role ATK_ROLE_MENU like other elements of the panel.
this.actor.add_style_class_name ('clock-display'); this.actor.accessible_role = Atk.Role.LABEL;
hbox = new St.BoxLayout({ name: 'calendarArea' }); this._clockDisplay = new St.Label();
this.menu.box.add_child(hbox); this.actor.add_actor(this._clockDisplay);
hbox = new St.BoxLayout({name: 'calendarArea' });
this.menu.addActor(hbox);
// Fill up the first column // Fill up the first column
@ -63,8 +66,9 @@ const DateMenuButton = new Lang.Class({
hbox.add(vbox); hbox.add(vbox);
// Date // Date
this._date = new St.Label({ style_class: 'datemenu-date-label', this._date = new St.Label();
can_focus: true }); this.actor.label_actor = this._clockDisplay;
this._date.style_class = 'datemenu-date-label';
vbox.add(this._date); vbox.add(this._date);
this._eventList = new Calendar.EventsList(); this._eventList = new Calendar.EventsList();
@ -81,22 +85,27 @@ const DateMenuButton = new Lang.Class({
vbox.add(this._calendar.actor); vbox.add(this._calendar.actor);
let separator = new PopupMenu.PopupSeparatorMenuItem(); let separator = new PopupMenu.PopupSeparatorMenuItem();
vbox.add(separator.actor, { y_align: St.Align.END, expand: true, y_fill: false }); separator.setColumnWidths(1);
vbox.add(separator.actor, {y_align: St.Align.END, expand: true, y_fill: false});
this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar")); this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar"));
this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate)); this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate));
this._openCalendarItem.actor.can_focus = false;
vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: true, y_fill: false}); vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: true, y_fill: false});
this._openClocksItem = new PopupMenu.PopupMenuItem(_("Open Clocks")); this._openClocksItem = new PopupMenu.PopupMenuItem(_("Open Clocks"));
this._openClocksItem.connect('activate', Lang.bind(this, this._onOpenClocksActivate)); this._openClocksItem.connect('activate', Lang.bind(this, this._onOpenClocksActivate));
this._openClocksItem.actor.can_focus = false;
vbox.add(this._openClocksItem.actor, {y_align: St.Align.END, expand: true, y_fill: false}); vbox.add(this._openClocksItem.actor, {y_align: St.Align.END, expand: true, y_fill: false});
Shell.AppSystem.get_default().connect('installed-changed', Shell.AppSystem.get_default().connect('installed-changed',
Lang.bind(this, this._appInstalledChanged)); Lang.bind(this, this._appInstalledChanged));
this._appInstalledChanged();
item = this.menu.addSettingsAction(_("Date & Time Settings"), 'gnome-datetime-panel.desktop'); item = this.menu.addSettingsAction(_("Date & Time Settings"), 'gnome-datetime-panel.desktop');
if (item) { if (item) {
item.actor.show_on_set_parent = false; item.actor.show_on_set_parent = false;
item.actor.can_focus = false;
item.actor.reparent(vbox); item.actor.reparent(vbox);
this._dateAndTimeSeparator = separator; this._dateAndTimeSeparator = separator;
} }
@ -107,7 +116,12 @@ const DateMenuButton = new Lang.Class({
hbox.add(this._separator); hbox.add(this._separator);
// Fill up the second column // Fill up the second column
hbox.add(this._eventList.actor, { expand: true, y_fill: false, y_align: St.Align.START }); vbox = new St.BoxLayout({ name: 'calendarEventsArea',
vertical: true });
hbox.add(vbox, { expand: true });
// Event list
vbox.add(this._eventList.actor, { expand: true });
// 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,25 +157,24 @@ const DateMenuButton = new Lang.Class({
}, },
_appInstalledChanged: function() { _appInstalledChanged: function() {
this._calendarApp = undefined; let app = Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
this._updateEventsVisibility(); this._openClocksItem.actor.visible = app !== null;
}, },
_updateEventsVisibility: function() { _updateEventsVisibility: function() {
let visible = this._eventSource.hasCalendars; let visible = this._eventSource.hasCalendars;
this._openCalendarItem.actor.visible = visible && this._openCalendarItem.actor.visible = visible;
(this._getCalendarApp() != null); this._openClocksItem.actor.visible = visible;
this._openClocksItem.actor.visible = visible &&
(this._getClockApp() != null);
this._separator.visible = visible; this._separator.visible = visible;
this._eventList.actor.visible = visible;
if (visible) { if (visible) {
let alignment = 0.25; let alignment = 0.25;
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
alignment = 1.0 - alignment; alignment = 1.0 - alignment;
this.menu._arrowAlignment = alignment; this.menu._arrowAlignment = alignment;
this._eventList.actor.get_parent().show();
} else { } else {
this.menu._arrowAlignment = 0.5; this.menu._arrowAlignment = 0.5;
this._eventList.actor.get_parent().hide();
} }
}, },
@ -204,34 +217,18 @@ const DateMenuButton = new Lang.Class({
this._date.set_text(displayDate.toLocaleFormat(dateFormat)); this._date.set_text(displayDate.toLocaleFormat(dateFormat));
}, },
_getCalendarApp: function() {
if (this._calendarApp !== undefined)
return this._calendarApp;
let apps = Gio.AppInfo.get_recommended_for_type('text/calendar');
if (apps && (apps.length > 0))
this._calendarApp = apps[0];
else
this._calendarApp = null;
return this._calendarApp;
},
_getClockApp: function() {
return Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
},
_onOpenCalendarActivate: function() { _onOpenCalendarActivate: function() {
this.menu.close(); this.menu.close();
let app = this._getCalendarApp(); let app = Gio.AppInfo.get_default_for_type('text/calendar', false);
if (app.get_id() == 'evolution.desktop') if (app.get_id() == 'evolution')
app = Gio.DesktopAppInfo.new('evolution-calendar.desktop'); app = Gio.DesktopAppInfo.new('evolution-calendar');
app.launch([], global.create_app_launch_context()); app.launch([], global.create_app_launch_context());
}, },
_onOpenClocksActivate: function() { _onOpenClocksActivate: function() {
this.menu.close(); this.menu.close();
let app = this._getClockApp(); let app = Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
app.activate(); app.activate();
} }
}); });

View File

@ -1,11 +1,9 @@
// -*- 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 GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const St = imports.gi.St; const St = imports.gi.St;
const Lang = imports.lang; const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
@ -28,9 +26,9 @@ const DragMotionResult = {
}; };
const DRAG_CURSOR_MAP = { const DRAG_CURSOR_MAP = {
0: Meta.Cursor.DND_UNSUPPORTED_TARGET, 0: Shell.Cursor.DND_UNSUPPORTED_TARGET,
1: Meta.Cursor.DND_COPY, 1: Shell.Cursor.DND_COPY,
2: Meta.Cursor.DND_MOVE 2: Shell.Cursor.DND_MOVE
}; };
const DragDropResult = { const DragDropResult = {
@ -45,7 +43,9 @@ let dragMonitors = [];
function _getEventHandlerActor() { function _getEventHandlerActor() {
if (!eventHandlerActor) { if (!eventHandlerActor) {
eventHandlerActor = new Clutter.Actor({ width: 0, height: 0 }); eventHandlerActor = new Clutter.Rectangle();
eventHandlerActor.width = 0;
eventHandlerActor.height = 0;
Main.uiGroup.add_actor(eventHandlerActor); Main.uiGroup.add_actor(eventHandlerActor);
// We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen // We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen
// when you've grabbed the pointer. // when you've grabbed the pointer.
@ -86,6 +86,11 @@ const _Draggable = new Lang.Class({
this.actor.connect('destroy', Lang.bind(this, function() { this.actor.connect('destroy', Lang.bind(this, function() {
this._actorDestroyed = true; this._actorDestroyed = true;
// If the drag actor is destroyed and we were going to fix
// up its hover state, fix up the parent hover state instead
if (this.actor == this._firstLeaveActor)
this._firstLeaveActor = this._dragOrigParent;
if (this._dragInProgress && this._dragCancellable) if (this._dragInProgress && this._dragCancellable)
this._cancelDrag(global.get_current_time()); this._cancelDrag(global.get_current_time());
this.disconnectAll(); this.disconnectAll();
@ -101,6 +106,12 @@ const _Draggable = new Lang.Class({
this._animationInProgress = false; // The drag is over and the item is in the process of animating to its original position (snapping back or reverting). this._animationInProgress = false; // The drag is over and the item is in the process of animating to its original position (snapping back or reverting).
this._dragCancellable = true; this._dragCancellable = true;
// During the drag, we eat enter/leave events so that actors don't prelight.
// But we remember the actors that we first left/last entered so we can
// fix up the hover state after the drag ends.
this._firstLeaveActor = null;
this._lastEnterActor = null;
this._eventsGrabbed = false; this._eventsGrabbed = false;
}, },
@ -138,16 +149,16 @@ const _Draggable = new Lang.Class({
_grabEvents: function() { _grabEvents: function() {
if (!this._eventsGrabbed) { if (!this._eventsGrabbed) {
this._eventsGrabbed = Main.pushModal(_getEventHandlerActor()); Clutter.grab_pointer(_getEventHandlerActor());
if (this._eventsGrabbed) Clutter.grab_keyboard(_getEventHandlerActor());
Clutter.grab_pointer(_getEventHandlerActor()); this._eventsGrabbed = true;
} }
}, },
_ungrabEvents: function() { _ungrabEvents: function() {
if (this._eventsGrabbed) { if (this._eventsGrabbed) {
Clutter.ungrab_pointer(); Clutter.ungrab_pointer();
Main.popModal(_getEventHandlerActor()); Clutter.ungrab_keyboard();
this._eventsGrabbed = false; this._eventsGrabbed = false;
} }
}, },
@ -186,6 +197,11 @@ const _Draggable = new Lang.Class({
this._cancelDrag(event.get_time()); this._cancelDrag(event.get_time());
return true; return true;
} }
} else if (event.type() == Clutter.EventType.LEAVE) {
if (this._firstLeaveActor == null)
this._firstLeaveActor = event.get_source();
} else if (event.type() == Clutter.EventType.ENTER) {
this._lastEnterActor = event.get_source();
} }
return false; return false;
@ -229,7 +245,7 @@ const _Draggable = new Lang.Class({
if (this._onEventId) if (this._onEventId)
this._ungrabActor(); this._ungrabActor();
this._grabEvents(); this._grabEvents();
global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG); global.set_cursor(Shell.Cursor.DND_IN_DRAG);
this._dragX = this._dragStartX = stageX; this._dragX = this._dragStartX = stageX;
this._dragY = this._dragStartY = stageY; this._dragY = this._dragStartY = stageY;
@ -275,19 +291,19 @@ const _Draggable = new Lang.Class({
this._dragOrigY = this._dragActor.y; this._dragOrigY = this._dragActor.y;
this._dragOrigScale = this._dragActor.scale_x; this._dragOrigScale = this._dragActor.scale_x;
// Set the actor's scale such that it will keep the same this._dragActor.reparent(Main.uiGroup);
// transformed size when it's reparented to the uiGroup this._dragActor.raise_top();
let [scaledWidth, scaledHeight] = this.actor.get_transformed_size(); Shell.util_set_hidden_from_pick(this._dragActor, true);
this._dragActor.set_scale(scaledWidth / this.actor.width,
scaledHeight / this.actor.height);
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;
this._dragActor.reparent(Main.uiGroup); // Set the actor's scale such that it will keep the same
this._dragActor.raise_top(); // transformed size when it's reparented to the uiGroup
Shell.util_set_hidden_from_pick(this._dragActor, true); let [scaledWidth, scaledHeight] = this.actor.get_transformed_size();
this.actor.set_scale(scaledWidth / this.actor.width,
scaledHeight / this.actor.height);
} }
this._dragOrigOpacity = this._dragActor.opacity; this._dragOrigOpacity = this._dragActor.opacity;
@ -344,65 +360,60 @@ const _Draggable = new Lang.Class({
return true; return true;
}, },
_updateDragHover : function () {
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
this._dragX, this._dragY);
let dragEvent = {
x: this._dragX,
y: this._dragY,
dragActor: this._dragActor,
source: this.actor._delegate,
targetActor: target
};
for (let i = 0; i < dragMonitors.length; i++) {
let motionFunc = dragMonitors[i].dragMotion;
if (motionFunc) {
let result = motionFunc(dragEvent);
if (result != DragMotionResult.CONTINUE) {
global.screen.set_cursor(DRAG_CURSOR_MAP[result]);
return false;
}
}
}
while (target) {
if (target._delegate && target._delegate.handleDragOver) {
let [r, targX, targY] = target.transform_stage_point(this._dragX, this._dragY);
// We currently loop through all parents on drag-over even if one of the children has handled it.
// We can check the return value of the function and break the loop if it's true if we don't want
// to continue checking the parents.
let result = target._delegate.handleDragOver(this.actor._delegate,
this._dragActor,
targX,
targY,
0);
if (result != DragMotionResult.CONTINUE) {
global.screen.set_cursor(DRAG_CURSOR_MAP[result]);
return false;
}
}
target = target.get_parent();
}
global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG);
return false;
},
_queueUpdateDragHover: function() {
if (this._updateHoverId)
GLib.source_remove(this._updateHoverId);
this._updateHoverId = GLib.idle_add(GLib.PRIORITY_DEFAULT,
Lang.bind(this, this._updateDragHover));
},
_updateDragPosition : function (event) { _updateDragPosition : function (event) {
let [stageX, stageY] = event.get_coords(); let [stageX, stageY] = event.get_coords();
this._dragX = stageX; this._dragX = stageX;
this._dragY = stageY; this._dragY = stageY;
this._dragActor.set_position(stageX + this._dragOffsetX,
stageY + this._dragOffsetY);
this._queueUpdateDragHover(); // If we are dragging, update the position
if (this._dragActor) {
this._dragActor.set_position(stageX + this._dragOffsetX,
stageY + this._dragOffsetY);
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
stageX, stageY);
// We call observers only once per motion with the innermost
// target actor. If necessary, the observer can walk the
// parent itself.
let dragEvent = {
x: stageX,
y: stageY,
dragActor: this._dragActor,
source: this.actor._delegate,
targetActor: target
};
for (let i = 0; i < dragMonitors.length; i++) {
let motionFunc = dragMonitors[i].dragMotion;
if (motionFunc) {
let result = motionFunc(dragEvent);
if (result != DragMotionResult.CONTINUE) {
global.set_cursor(DRAG_CURSOR_MAP[result]);
return true;
}
}
}
while (target) {
if (target._delegate && target._delegate.handleDragOver) {
let [r, targX, targY] = target.transform_stage_point(stageX, stageY);
// We currently loop through all parents on drag-over even if one of the children has handled it.
// We can check the return value of the function and break the loop if it's true if we don't want
// to continue checking the parents.
let result = target._delegate.handleDragOver(this.actor._delegate,
this._dragActor,
targX,
targY,
event.get_time());
if (result != DragMotionResult.CONTINUE) {
global.set_cursor(DRAG_CURSOR_MAP[result]);
return true;
}
}
target = target.get_parent();
}
global.set_cursor(Shell.Cursor.DND_IN_DRAG);
}
return true; return true;
}, },
@ -455,7 +466,7 @@ const _Draggable = new Lang.Class({
} }
this._dragInProgress = false; this._dragInProgress = false;
global.screen.set_cursor(Meta.Cursor.DEFAULT); global.unset_cursor();
this.emit('drag-end', event.get_time(), true); this.emit('drag-end', event.get_time(), true);
this._dragComplete(); this._dragComplete();
return true; return true;
@ -507,7 +518,7 @@ const _Draggable = new Lang.Class({
let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation(); let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();
if (this._actorDestroyed) { if (this._actorDestroyed) {
global.screen.set_cursor(Meta.Cursor.DEFAULT); global.unset_cursor();
if (!this._buttonDown) if (!this._buttonDown)
this._dragComplete(); this._dragComplete();
this.emit('drag-end', eventTime, false); this.emit('drag-end', eventTime, false);
@ -561,7 +572,7 @@ const _Draggable = new Lang.Class({
} else { } else {
dragActor.destroy(); dragActor.destroy();
} }
global.screen.set_cursor(Meta.Cursor.DEFAULT); global.unset_cursor();
this.emit('drag-end', eventTime, false); this.emit('drag-end', eventTime, false);
this._animationInProgress = false; this._animationInProgress = false;
@ -569,16 +580,32 @@ const _Draggable = new Lang.Class({
this._dragComplete(); this._dragComplete();
}, },
// Actor is an actor we have entered or left during the drag; call
// st_widget_sync_hover on all StWidget ancestors
_syncHover: function(actor) {
while (actor) {
let parent = actor.get_parent();
if (actor instanceof St.Widget)
actor.sync_hover();
actor = parent;
}
},
_dragComplete: function() { _dragComplete: function() {
if (!this._actorDestroyed) if (!this._actorDestroyed)
Shell.util_set_hidden_from_pick(this._dragActor, false); Shell.util_set_hidden_from_pick(this._dragActor, false);
this._ungrabEvents(); this._ungrabEvents();
global.sync_pointer();
if (this._updateHoverId) { if (this._firstLeaveActor) {
GLib.source_remove(this._updateHoverId); this._syncHover(this._firstLeaveActor);
this._updateHoverId = 0; this._firstLeaveActor = null;
}
if (this._lastEnterActor) {
this._syncHover(this._lastEnterActor);
this._lastEnterActor = null;
} }
this._dragActor = undefined; this._dragActor = undefined;

View File

@ -20,6 +20,7 @@
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Signals = imports.signals;
const AccountsService = imports.gi.AccountsService; const AccountsService = imports.gi.AccountsService;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
@ -31,10 +32,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 LoginManager = imports.misc.loginManager; 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 UserWidget = imports.ui.userWidget; const UserMenu = imports.ui.userMenu;
let _endSessionDialog = null; let _endSessionDialog = null;
@ -61,96 +62,63 @@ const EndSessionDialogIface = <interface name="org.gnome.SessionManager.EndSessi
const logoutDialogContent = { const logoutDialogContent = {
subjectWithUser: C_("title", "Log Out %s"), subjectWithUser: C_("title", "Log Out %s"),
subject: C_("title", "Log Out"), subject: C_("title", "Log Out"),
descriptionWithUser: function(user, seconds) { inhibitedDescription: _("Click Log Out to quit these applications and log out of the system."),
uninhibitedDescriptionWithUser: function(user, seconds) {
return ngettext("%s will be logged out automatically in %d second.", return ngettext("%s will be logged out automatically in %d second.",
"%s will be logged out automatically in %d seconds.", "%s will be logged out automatically in %d seconds.",
seconds).format(user, seconds); seconds).format(user, seconds);
}, },
description: function(seconds) { uninhibitedDescription: function(seconds) {
return ngettext("You will be logged out automatically in %d second.", return ngettext("You will be logged out automatically in %d second.",
"You will be logged out automatically in %d seconds.", "You will be logged out automatically in %d seconds.",
seconds).format(seconds); seconds).format(seconds);
}, },
endDescription: _("Logging out of the system."),
confirmButtons: [{ signal: 'ConfirmedLogout', confirmButtons: [{ signal: 'ConfirmedLogout',
label: C_("button", "Log Out") }], label: C_("button", "Log Out") }],
iconStyleClass: 'end-session-dialog-logout-icon', iconStyleClass: 'end-session-dialog-logout-icon'
showOtherSessions: false,
}; };
const shutdownDialogContent = { const shutdownDialogContent = {
subject: C_("title", "Power Off"), subject: C_("title", "Power Off"),
description: function(seconds) { inhibitedDescription: _("Click Power Off to quit these applications and power off the system."),
uninhibitedDescription: function(seconds) {
return ngettext("The system will power off automatically in %d second.", return ngettext("The system will power off automatically in %d second.",
"The system will power off automatically in %d seconds.", "The system will power off automatically in %d seconds.",
seconds).format(seconds); seconds).format(seconds);
}, },
endDescription: _("Powering off the system."),
confirmButtons: [{ signal: 'ConfirmedReboot', confirmButtons: [{ signal: 'ConfirmedReboot',
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-symbolic',
iconStyleClass: 'end-session-dialog-shutdown-icon', iconStyleClass: 'end-session-dialog-shutdown-icon'
showOtherSessions: true,
}; };
const restartDialogContent = { const restartDialogContent = {
subject: C_("title", "Restart"), subject: C_("title", "Restart"),
description: function(seconds) { inhibitedDescription: _("Click Restart to quit these applications and restart the system."),
uninhibitedDescription: function(seconds) {
return ngettext("The system will restart automatically in %d second.", return ngettext("The system will restart automatically in %d second.",
"The system will restart automatically in %d seconds.", "The system will restart automatically in %d seconds.",
seconds).format(seconds); seconds).format(seconds);
}, },
endDescription: _("Restarting the system."),
confirmButtons: [{ signal: 'ConfirmedReboot', confirmButtons: [{ signal: 'ConfirmedReboot',
label: C_("button", "Restart") }], label: C_("button", "Restart") }],
iconName: 'view-refresh-symbolic', iconName: 'view-refresh-symbolic',
iconStyleClass: 'end-session-dialog-shutdown-icon', iconStyleClass: 'end-session-dialog-shutdown-icon'
showOtherSessions: true,
};
const restartInstallDialogContent = {
subject: C_("title", "Restart & Install Updates"),
description: function(seconds) {
return ngettext("The system will automatically restart and install updates in %d second.",
"The system will automatically restart and install updates in %d seconds.",
seconds).format(seconds);
},
confirmButtons: [{ signal: 'ConfirmedReboot',
label: C_("button", "Restart & Install") }],
iconName: 'view-refresh-symbolic',
iconStyleClass: 'end-session-dialog-shutdown-icon',
showOtherSessions: true,
}; };
const DialogContent = { const DialogContent = {
0 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT */: logoutDialogContent, 0 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT */: logoutDialogContent,
1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */: shutdownDialogContent, 1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */: shutdownDialogContent,
2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */: restartDialogContent, 2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */: restartDialogContent
3: restartInstallDialogContent
}; };
const MAX_USERS_IN_SESSION_DIALOG = 5;
const LogindSessionIface = <interface name='org.freedesktop.login1.Session'>
<property name="Id" type="s" access="read"/>
<property name="Remote" type="b" access="read"/>
<property name="Class" type="s" access="read"/>
<property name="Type" type="s" access="read"/>
<property name="State" type="s" access="read"/>
</interface>;
const LogindSession = Gio.DBusProxy.makeProxyWrapper(LogindSessionIface);
function findAppFromInhibitor(inhibitor) { function findAppFromInhibitor(inhibitor) {
let desktopFile; let [desktopFile] = inhibitor.GetAppIdSync();
try {
[desktopFile] = inhibitor.GetAppIdSync();
} catch(e) {
// XXX -- sometimes JIT inhibitors generated by gnome-session
// get removed too soon. Don't fail in this case.
log('gnome-session gave us a dead inhibitor: %s'.format(inhibitor.get_object_path()));
return null;
}
if (!GLib.str_has_suffix(desktopFile, '.desktop')) if (!GLib.str_has_suffix(desktopFile, '.desktop'))
desktopFile += '.desktop'; desktopFile += '.desktop';
@ -158,6 +126,58 @@ function findAppFromInhibitor(inhibitor) {
return Shell.AppSystem.get_default().lookup_heuristic_basename(desktopFile); return Shell.AppSystem.get_default().lookup_heuristic_basename(desktopFile);
} }
const ListItem = new Lang.Class({
Name: 'ListItem',
_init: function(app, reason) {
this._app = app;
this._reason = reason;
if (this._reason == null)
this._reason = '';
let layout = new St.BoxLayout({ vertical: false});
this.actor = new St.Button({ style_class: 'end-session-dialog-app-list-item',
can_focus: true,
child: layout,
reactive: true,
x_align: St.Align.START,
x_fill: true });
this._icon = this._app.create_icon_texture(_ITEM_ICON_SIZE);
let iconBin = new St.Bin({ style_class: 'end-session-dialog-app-list-item-icon',
child: this._icon });
layout.add(iconBin);
let textLayout = new St.BoxLayout({ style_class: 'end-session-dialog-app-list-item-text-box',
vertical: true });
layout.add(textLayout);
this._nameLabel = new St.Label({ text: this._app.get_name(),
style_class: 'end-session-dialog-app-list-item-name' });
textLayout.add(this._nameLabel,
{ expand: false,
x_fill: true });
this._descriptionLabel = new St.Label({ text: this._reason,
style_class: 'end-session-dialog-app-list-item-description' });
this.actor.label_actor = this._nameLabel;
textLayout.add(this._descriptionLabel,
{ expand: true,
x_fill: true });
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
},
_onClicked: function() {
this.emit('activate');
this._app.activate();
}
});
Signals.addSignalMethods(ListItem.prototype);
// The logout timer only shows updates every 10 seconds // The logout timer only shows updates every 10 seconds
// until the last 10 seconds, then it shows updates every // until the last 10 seconds, then it shows updates every
// second. This function takes a given time and returns // second. This function takes a given time and returns
@ -208,23 +228,22 @@ const EndSessionDialog = new Lang.Class({
this.parent({ styleClass: 'end-session-dialog', this.parent({ styleClass: 'end-session-dialog',
destroyOnClose: false }); destroyOnClose: false });
this._loginManager = LoginManager.getLoginManager(); this._user = AccountsService.UserManager.get_default().get_user(GLib.get_user_name());
this._userManager = AccountsService.UserManager.get_default();
this._user = this._userManager.get_user(GLib.get_user_name());
this._updatesFile = Gio.File.new_for_path('/system-update');
this._secondsLeft = 0; this._secondsLeft = 0;
this._totalSecondsToStayOpen = 0; this._totalSecondsToStayOpen = 0;
this._applications = []; this._inhibitors = [];
this._sessions = [];
this.connect('destroy', this.connect('destroy',
Lang.bind(this, this._onDestroy)); Lang.bind(this, this._onDestroy));
this.connect('opened', this.connect('opened',
Lang.bind(this, this._onOpened)); Lang.bind(this, this._onOpened));
this._userLoadedId = this._user.connect('notify::is_loaded', Lang.bind(this, this._sync)); this._userLoadedId = this._user.connect('notify::is_loaded',
this._userChangedId = this._user.connect('changed', Lang.bind(this, this._sync)); Lang.bind(this, this._updateContent));
this._userChangedId = this._user.connect('changed',
Lang.bind(this, this._updateContent));
let mainContentLayout = new St.BoxLayout({ vertical: false }); let mainContentLayout = new St.BoxLayout({ vertical: false });
this.contentLayout.add(mainContentLayout, this.contentLayout.add(mainContentLayout,
@ -256,28 +275,28 @@ const EndSessionDialog = new Lang.Class({
{ y_fill: true, { y_fill: true,
y_align: St.Align.START }); y_align: St.Align.START });
this._scrollView = new St.ScrollView({ style_class: 'end-session-dialog-list' }); let scrollView = new St.ScrollView({ style_class: 'end-session-dialog-app-list'});
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); scrollView.set_policy(Gtk.PolicyType.NEVER,
this.contentLayout.add(this._scrollView, Gtk.PolicyType.AUTOMATIC);
this.contentLayout.add(scrollView,
{ x_fill: true, { x_fill: true,
y_fill: true }); y_fill: true });
this._scrollView.hide(); scrollView.hide();
this._inhibitorSection = new St.BoxLayout({ vertical: true,
style_class: 'end-session-dialog-inhibitor-layout' });
this._scrollView.add_actor(this._inhibitorSection);
this._applicationHeader = new St.Label({ style_class: 'end-session-dialog-list-header',
text: _("Some applications are busy or have unsaved work.") });
this._applicationList = new St.BoxLayout({ vertical: true }); this._applicationList = new St.BoxLayout({ vertical: true });
this._inhibitorSection.add_actor(this._applicationHeader); scrollView.add_actor(this._applicationList);
this._inhibitorSection.add_actor(this._applicationList);
this._sessionHeader = new St.Label({ style_class: 'end-session-dialog-list-header', this._applicationList.connect('actor-added',
text: _("Other users are logged in.") }); Lang.bind(this, function() {
this._sessionList = new St.BoxLayout({ vertical: true }); if (this._applicationList.get_n_children() == 1)
this._inhibitorSection.add_actor(this._sessionHeader); scrollView.show();
this._inhibitorSection.add_actor(this._sessionList); }));
this._applicationList.connect('actor-removed',
Lang.bind(this, function() {
if (this._applicationList.get_n_children() == 0)
scrollView.hide();
}));
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(EndSessionDialogIface, this); this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(EndSessionDialogIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/SessionManager/EndSessionDialog'); this._dbusImpl.export(Gio.DBus.session, '/org/gnome/SessionManager/EndSessionDialog');
@ -288,42 +307,52 @@ const EndSessionDialog = new Lang.Class({
this._user.disconnect(this._userChangedId); this._user.disconnect(this._userChangedId);
}, },
_sync: function() { _updateDescription: function() {
let open = (this.state == ModalDialog.State.OPENING || this.state == ModalDialog.State.OPENED); if (this.state != ModalDialog.State.OPENING &&
if (!open) this.state != ModalDialog.State.OPENED)
return; return;
if (this._type == 2 && this._updatesFile.query_exists(null))
this._type = 3;
let dialogContent = DialogContent[this._type]; let dialogContent = DialogContent[this._type];
let subject = dialogContent.subject; let subject = dialogContent.subject;
let description; let description;
let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen,
this._secondsLeft,
10);
if (this._user.is_loaded) { if (this._inhibitors.length > 0) {
let realName = this._user.get_real_name(); this._stopTimer();
description = dialogContent.inhibitedDescription;
} else if (this._secondsLeft > 0 && this._inhibitors.length == 0) {
let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen,
this._secondsLeft,
10);
if (realName != null) { if (this._user.is_loaded) {
if (dialogContent.subjectWithUser) let realName = this._user.get_real_name();
subject = dialogContent.subjectWithUser.format(realName);
if (dialogContent.descriptionWithUser) if (realName != null) {
description = dialogContent.descriptionWithUser(realName, displayTime); if (dialogContent.subjectWithUser)
else subject = dialogContent.subjectWithUser.format(realName);
description = dialogContent.description(displayTime);
if (dialogContent.uninhibitedDescriptionWithUser)
description = dialogContent.uninhibitedDescriptionWithUser(realName, displayTime);
else
description = dialogContent.uninhibitedDescription(displayTime);
}
} }
if (!description)
description = dialogContent.uninhibitedDescription(displayTime);
} else {
description = dialogContent.endDescription;
} }
if (!description)
description = dialogContent.description(displayTime);
_setLabelText(this._descriptionLabel, description);
_setLabelText(this._subjectLabel, subject); _setLabelText(this._subjectLabel, subject);
_setLabelText(this._descriptionLabel, description);
},
_updateContent: function() {
if (this.state != ModalDialog.State.OPENING &&
this.state != ModalDialog.State.OPENED)
return;
let dialogContent = DialogContent[this._type]; let dialogContent = DialogContent[this._type];
if (dialogContent.iconName) { if (dialogContent.iconName) {
@ -331,18 +360,14 @@ const EndSessionDialog = new Lang.Class({
icon_size: _DIALOG_ICON_SIZE, icon_size: _DIALOG_ICON_SIZE,
style_class: dialogContent.iconStyleClass }); style_class: dialogContent.iconStyleClass });
} else { } else {
let avatarWidget = new UserWidget.Avatar(this._user, let avatarWidget = new UserMenu.UserAvatarWidget(this._user,
{ iconSize: _DIALOG_ICON_SIZE, { iconSize: _DIALOG_ICON_SIZE,
styleClass: dialogContent.iconStyleClass }); styleClass: dialogContent.iconStyleClass });
this._iconBin.child = avatarWidget.actor; this._iconBin.child = avatarWidget.actor;
avatarWidget.update(); avatarWidget.update();
} }
let hasApplications = this._applications.length > 0; this._updateDescription();
let hasSessions = this._sessions.length > 0;
this._scrollView.visible = hasApplications || hasSessions;
this._applicationHeader.visible = hasApplications;
this._sessionHeader.visible = hasSessions;
}, },
_updateButtons: function() { _updateButtons: function() {
@ -388,7 +413,8 @@ const EndSessionDialog = new Lang.Class({
}, },
_onOpened: function() { _onOpened: function() {
this._sync(); if (this._inhibitors.length == 0)
this._startTimer();
}, },
_startTimer: function() { _startTimer: function() {
@ -402,7 +428,7 @@ const EndSessionDialog = new Lang.Class({
this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed; this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed;
if (this._secondsLeft > 0) { if (this._secondsLeft > 0) {
this._sync(); this._updateDescription();
return true; return true;
} }
@ -415,7 +441,7 @@ const EndSessionDialog = new Lang.Class({
}, },
_stopTimer: function() { _stopTimer: function() {
if (this._timerId > 0) { if (this._timerId != 0) {
Mainloop.source_remove(this._timerId); Mainloop.source_remove(this._timerId);
this._timerId = 0; this._timerId = 0;
} }
@ -423,33 +449,8 @@ const EndSessionDialog = new Lang.Class({
this._secondsLeft = 0; this._secondsLeft = 0;
}, },
_constructListItemForApp: function(inhibitor, app) {
let actor = new St.BoxLayout({ style_class: 'end-session-dialog-app-list-item',
can_focus: true });
actor.add(app.create_icon_texture(_ITEM_ICON_SIZE));
let textLayout = new St.BoxLayout({ vertical: true,
y_expand: true,
y_align: Clutter.ActorAlign.CENTER });
actor.add(textLayout);
let nameLabel = new St.Label({ text: app.get_name(),
style_class: 'end-session-dialog-app-list-item-name' });
textLayout.add(nameLabel);
actor.label_actor = nameLabel;
let [reason] = inhibitor.GetReasonSync();
if (reason) {
let reasonLabel = new St.Label({ text: reason,
style_class: 'end-session-dialog-app-list-item-description' });
textLayout.add(reasonLabel);
}
return actor;
},
_onInhibitorLoaded: function(inhibitor) { _onInhibitorLoaded: function(inhibitor) {
if (this._applications.indexOf(inhibitor) < 0) { if (this._inhibitors.indexOf(inhibitor) < 0) {
// Stale inhibitor // Stale inhibitor
return; return;
} }
@ -457,91 +458,28 @@ const EndSessionDialog = new Lang.Class({
let app = findAppFromInhibitor(inhibitor); let app = findAppFromInhibitor(inhibitor);
if (app) { if (app) {
let actor = this._constructListItemForApp(inhibitor, app); let [reason] = inhibitor.GetReasonSync();
this._applicationList.add(actor); let item = new ListItem(app, reason);
item.connect('activate',
Lang.bind(this, function() {
this.close();
}));
this._applicationList.add(item.actor, { x_fill: true });
this._stopTimer();
} else { } else {
// inhibiting app is a service, not an application // inhibiting app is a service, not an application
this._applications.splice(this._applications.indexOf(inhibitor), 1); this._inhibitors.splice(this._inhibitors.indexOf(inhibitor), 1);
} }
this._sync(); this._updateContent();
},
_constructListItemForSession: function(session) {
let avatar = new UserWidget.Avatar(session.user, { iconSize: _ITEM_ICON_SIZE });
avatar.update();
let userName = session.user.get_real_name() ? session.user.get_real_name() : session.username;
let userLabelText;
if (session.remote)
/* Translators: Remote here refers to a remote session, like a ssh login */
userLabelText = _("%s (remote)").format(userName);
else if (session.type == "tty")
/* Translators: Console here refers to a tty like a VT console */
userLabelText = _("%s (console)").format(userName);
else
userLabelText = userName;
let actor = new St.BoxLayout({ style_class: 'end-session-dialog-session-list-item',
can_focus: true });
actor.add(avatar.actor);
let nameLabel = new St.Label({ text: userLabelText,
style_class: 'end-session-dialog-session-list-item-name',
y_expand: true,
y_align: Clutter.ActorAlign.CENTER });
actor.add(nameLabel);
actor.label_actor = nameLabel;
return actor;
},
_loadSessions: function() {
this._loginManager.listSessions(Lang.bind(this, function(result) {
let n = 0;
for (let i = 0; i < result.length; i++) {
let[id, uid, userName, seat, sessionPath] = result[i];
let proxy = new LogindSession(Gio.DBus.system, 'org.freedesktop.login1', sessionPath);
if (proxy.Class != 'user')
continue;
if (proxy.State == 'closing')
continue;
if (proxy.Id == GLib.getenv('XDG_SESSION_ID'))
continue;
let session = { user: this._userManager.get_user(userName),
username: userName,
type: proxy.Type,
remote: proxy.Remote };
this._sessions.push(session);
let actor = this._constructListItemForSession(session);
this._sessionList.add(actor);
// limit the number of entries
n++;
if (n == MAX_USERS_IN_SESSION_DIALOG)
break;
}
this._sync();
}));
}, },
OpenAsync: function(parameters, invocation) { OpenAsync: function(parameters, invocation) {
let [type, timestamp, totalSecondsToStayOpen, inhibitorObjectPaths] = parameters; let [type, timestamp, totalSecondsToStayOpen, inhibitorObjectPaths] = parameters;
this._totalSecondsToStayOpen = totalSecondsToStayOpen; this._totalSecondsToStayOpen = totalSecondsToStayOpen;
this._type = type; this._inhibitors = [];
this._applications = [];
this._applicationList.destroy_all_children(); this._applicationList.destroy_all_children();
this._type = type;
this._sessions = [];
this._sessionList.destroy_all_children();
if (!(this._type in DialogContent)) { if (!(this._type in DialogContent)) {
invocation.return_dbus_error('org.gnome.Shell.ModalDialog.TypeError', invocation.return_dbus_error('org.gnome.Shell.ModalDialog.TypeError',
@ -554,12 +492,9 @@ const EndSessionDialog = new Lang.Class({
this._onInhibitorLoaded(proxy); this._onInhibitorLoaded(proxy);
})); }));
this._applications.push(inhibitor); this._inhibitors.push(inhibitor);
} }
if (DialogContent[type].showOtherSessions)
this._loadSessions();
this._updateButtons(); this._updateButtons();
if (!this.open(timestamp)) { if (!this.open(timestamp)) {
@ -568,8 +503,7 @@ const EndSessionDialog = new Lang.Class({
return; return;
} }
this._startTimer(); this._updateContent();
this._sync();
let signalId = this.connect('opened', let signalId = this.connect('opened',
Lang.bind(this, function() { Lang.bind(this, function() {

View File

@ -10,7 +10,6 @@ const Clutter = imports.gi.Clutter;;
const Gettext = imports.gettext; const Gettext = imports.gettext;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
@ -40,22 +39,6 @@ function _patchContainerClass(containerClass) {
}; };
} }
function _patchLayoutClass(layoutClass, styleProps) {
if (styleProps)
layoutClass.prototype.hookup_style = function(container) {
container.connect('style-changed', Lang.bind(this, function() {
let node = container.get_theme_node();
for (let prop in styleProps)
this[prop] = node.get_length(styleProps[prop]);
}));
};
layoutClass.prototype.child_set = function(actor, props) {
let meta = this.get_child_meta(actor.get_parent(), actor);
for (let prop in props)
meta[prop] = props[prop];
};
}
function _makeLoggingFunc(func) { function _makeLoggingFunc(func) {
return function() { return function() {
return func([].join.call(arguments, ', ')); return func([].join.call(arguments, ', '));
@ -77,12 +60,6 @@ function init() {
_patchContainerClass(St.BoxLayout); _patchContainerClass(St.BoxLayout);
_patchContainerClass(St.Table); _patchContainerClass(St.Table);
_patchLayoutClass(Clutter.TableLayout, { row_spacing: 'spacing-rows',
column_spacing: 'spacing-columns' });
_patchLayoutClass(Clutter.GridLayout, { row_spacing: 'spacing-rows',
column_spacing: 'spacing-columns' });
_patchLayoutClass(Clutter.BoxLayout, { spacing: 'spacing' });
Clutter.Actor.prototype.toString = function() { Clutter.Actor.prototype.toString = function() {
return St.describe_actor(this); return St.describe_actor(this);
}; };

View File

@ -292,7 +292,7 @@ function disableAllExtensions() {
return; return;
if (initted) { if (initted) {
extensionOrder.slice().reverse().forEach(function(uuid) { enabledExtensions.forEach(function(uuid) {
disableExtension(uuid); disableExtension(uuid);
}); });
} }

View File

@ -1,65 +0,0 @@
/** -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 2012 Inclusive Design Research Centre, OCAD University.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Author:
* Joseph Scheuhammer <clown@alum.mit.edu>
* Contributor:
* Magdalen Berns <m.berns@sms.ed.ac.uk>
*/
const Atspi = imports.gi.Atspi;
const Lang = imports.lang;
const Signals = imports.signals;
const CARETMOVED = 'object:text-caret-moved';
const STATECHANGED = 'object:state-changed';
const FocusCaretTracker = new Lang.Class({
Name: 'FocusCaretTracker',
_init: function() {
Atspi.init();
Atspi.set_timeout(250, 250);
this._atspiListener = Atspi.EventListener.new(Lang.bind(this, this._onChanged));
},
_onChanged: function(event) {
if (event.type.indexOf(STATECHANGED) == 0)
this.emit('focus-changed', event);
else if (event.type == CARETMOVED)
this.emit('caret-moved', event);
},
registerFocusListener: function() {
return this._atspiListener.register(STATECHANGED + ':focused') &&
this._atspiListener.register(STATECHANGED + ':selected');
},
registerCaretListener: function() {
return this._atspiListener.register(CARETMOVED);
},
deregisterFocusListener: function() {
return this._atspiListener.deregister(STATECHANGED + ':focused') &&
this._atspiListener.deregister(STATECHANGED + ':selected');
},
deregisterCaretListener: function() {
return this._atspiListener.deregister(CARETMOVED);
}
});
Signals.addSignalMethods(FocusCaretTracker.prototype);

View File

@ -10,31 +10,6 @@ const St = imports.gi.St;
const Main = imports.ui.main; const Main = imports.ui.main;
const Params = imports.misc.params; const Params = imports.misc.params;
let _capturedEventId = 0;
let _grabHelperStack = [];
function _onCapturedEvent(actor, event) {
let grabHelper = _grabHelperStack[_grabHelperStack.length - 1];
return grabHelper.onCapturedEvent(event);
}
function _pushGrabHelper(grabHelper) {
_grabHelperStack.push(grabHelper);
if (_capturedEventId == 0)
_capturedEventId = global.stage.connect('captured-event', _onCapturedEvent);
}
function _popGrabHelper(grabHelper) {
let poppedHelper = _grabHelperStack.pop();
if (poppedHelper != grabHelper)
throw new Error("incorrect grab helper pop");
if (_grabHelperStack.length == 0) {
global.stage.disconnect(_capturedEventId);
_capturedEventId = 0;
}
}
// GrabHelper: // GrabHelper:
// @owner: the actor that owns the GrabHelper // @owner: the actor that owns the GrabHelper
// @params: optional parameters to pass to Main.pushModal() // @params: optional parameters to pass to Main.pushModal()
@ -56,9 +31,14 @@ const GrabHelper = new Lang.Class({
this._grabStack = []; this._grabStack = [];
this._actors = []; this._actors = [];
this._capturedEventId = 0;
this._keyFocusNotifyId = 0;
this._focusWindowChangedId = 0;
this._ignoreRelease = false; this._ignoreRelease = false;
this._isUngrabbingCount = 0;
this._modalCount = 0; this._modalCount = 0;
this._grabFocusCount = 0;
}, },
// addActor: // addActor:
@ -138,36 +118,38 @@ const GrabHelper = new Lang.Class({
// grab: // grab:
// @params: A bunch of parameters, see below // @params: A bunch of parameters, see below
// //
// The general effect of a "grab" is to ensure that the passed in actor // Grabs the mouse and keyboard, according to the GrabHelper's
// and all actors inside the grab get exclusive control of the mouse and // parameters. If @newFocus is not %null, then the keyboard focus
// keyboard, with the grab automatically being dropped if the user tries // is moved to the first #StWidget:can-focus widget inside it.
// to dismiss it. The actor is passed in through @params.actor.
// //
// grab() can be called multiple times, with the scope of the grab being // The grab will automatically be dropped if:
// changed to a different actor every time. A nested grab does not have // - The user clicks outside the grabbed actors
// to have its grabbed actor inside the parent grab actors. // - The user types Escape
// - The keyboard focus is moved outside the grabbed actors
// - A window is focused
// //
// Grabs can be automatically dropped if the user tries to dismiss it // If @params.actor is not null, then it will be focused as the
// in one of two ways: the user clicking outside the currently grabbed // new actor. If you attempt to grab an already focused actor, the
// actor, or the user typing the Escape key. // 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 the user clicks outside the grabbed actors, and the clicked on // If @params contains { modal: true }, then grab() will push a modal
// actor is part of a previous grab in the stack, grabs will be popped // on the owner of the GrabHelper. As long as there is at least one
// until that grab is active. However, the click event will not be // { modal: true } actor on the grab stack, the grab will be kept.
// replayed to the actor. // 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 the user types the Escape key, one grab from the grab stack will // If @params contains { grabFocus: true }, then if you call grab()
// be popped. // while the shell is outside the overview, it will set the stage
// // input mode to %Shell.StageInputMode.FOCUSED, and ungrab() will
// When a grab is popped by user interacting as described above, if you // revert it back, and re-focus the previously-focused window (if
// pass a callback as @params.onUngrab, it will be called with %true. // another window hasn't been explicitly focused before then).
//
// If @params.focus is not null, we'll set the key focus directly
// to that actor instead of navigating in @params.actor. This is for
// use cases like menus, where we want to grab the menu actor, but keep
// focus on the clicked on menu item.
grab: function(params) { grab: function(params) {
params = Params.parse(params, { actor: null, params = Params.parse(params, { actor: null,
modal: false,
grabFocus: false,
focus: null, focus: null,
onUngrab: null }); onUngrab: null });
@ -180,18 +162,24 @@ const GrabHelper = new Lang.Class({
params.savedFocus = focus; params.savedFocus = focus;
if (!this._takeModalGrab()) if (params.modal && !this._takeModalGrab())
return false;
if (params.grabFocus && !this._takeFocusGrab(hadFocus))
return false; return false;
this._grabStack.push(params); this._grabStack.push(params);
if (params.focus) { if (params.focus) {
params.focus.grab_key_focus(); params.focus.grab_key_focus();
} else if (newFocus && hadFocus) { } else if (newFocus && (hadFocus || params.grabFocus)) {
if (!newFocus.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false)) if (!newFocus.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false))
newFocus.grab_key_focus(); newFocus.grab_key_focus();
} }
if ((params.grabFocus || params.modal) && !this._capturedEventId)
this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
return true; return true;
}, },
@ -200,8 +188,6 @@ const GrabHelper = new Lang.Class({
if (firstGrab) { if (firstGrab) {
if (!Main.pushModal(this._owner, this._modalParams)) if (!Main.pushModal(this._owner, this._modalParams))
return false; return false;
_pushGrabHelper(this);
} }
this._modalCount++; this._modalCount++;
@ -213,14 +199,58 @@ const GrabHelper = new Lang.Class({
if (this._modalCount > 0) if (this._modalCount > 0)
return; return;
_popGrabHelper(this);
this._ignoreRelease = false;
Main.popModal(this._owner); Main.popModal(this._owner);
global.sync_pointer(); 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;
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._focusWindowChangedId > 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);
}
global.screen.focus_default_window(global.display.get_current_time_roundtrip());
},
// ignoreRelease: // ignoreRelease:
// //
// Make sure that the next button release event evaluated by the // Make sure that the next button release event evaluated by the
@ -234,14 +264,10 @@ const GrabHelper = new Lang.Class({
// ungrab: // ungrab:
// @params: The parameters for the grab; see below. // @params: The parameters for the grab; see below.
// //
// Pops @params.actor from the grab stack, potentially dropping // Pops an actor from the grab stack, potentially dropping the grab.
// the grab. If the actor is not on the grab stack, this call is
// ignored with no ill effects.
// //
// If the actor is not at the top of the grab stack, grabs are // If the actor that was popped from the grab stack was not the actor
// popped until the grabbed actor is at the top of the grab stack. // That was passed in, this call is ignored.
// The onUngrab callback for every grab is called for every popped
// grab with the parameter %false.
ungrab: function(params) { ungrab: function(params) {
params = Params.parse(params, { actor: this.currentGrab.actor, params = Params.parse(params, { actor: this.currentGrab.actor,
isUser: false }); isUser: false });
@ -250,6 +276,14 @@ const GrabHelper = new Lang.Class({
if (grabStackIndex < 0) if (grabStackIndex < 0)
return; return;
// We may get key focus changes when calling onUngrab, which
// would cause an extra ungrab() on the next actor in the
// stack, which is wrong. Ignore key focus changes during the
// ungrab, and restore the saved key focus ourselves afterwards.
// We use a count as ungrab() may be re-entrant, as onUngrab()
// may ungrab additional actors.
this._isUngrabbingCount++;
let focus = global.stage.key_focus; let focus = global.stage.key_focus;
let hadFocus = focus && this._isWithinGrabbedActor(focus); let hadFocus = focus && this._isWithinGrabbedActor(focus);
@ -264,7 +298,18 @@ const GrabHelper = new Lang.Class({
if (poppedGrab.onUngrab) if (poppedGrab.onUngrab)
poppedGrab.onUngrab(params.isUser); poppedGrab.onUngrab(params.isUser);
this._releaseModalGrab(); if (poppedGrab.modal)
this._releaseModalGrab();
if (poppedGrab.grabFocus)
this._releaseFocusGrab();
}
if (!this.grabbed && this._capturedEventId > 0) {
global.stage.disconnect(this._capturedEventId);
this._capturedEventId = 0;
this._ignoreRelease = false;
} }
if (hadFocus) { if (hadFocus) {
@ -272,9 +317,11 @@ const GrabHelper = new Lang.Class({
if (poppedGrab.savedFocus) if (poppedGrab.savedFocus)
poppedGrab.savedFocus.grab_key_focus(); poppedGrab.savedFocus.grab_key_focus();
} }
this._isUngrabbingCount--;
}, },
onCapturedEvent: function(event) { _onCapturedEvent: function(actor, event) {
let type = event.type(); let type = event.type();
if (type == Clutter.EventType.KEY_PRESS && if (type == Clutter.EventType.KEY_PRESS &&
@ -292,6 +339,9 @@ const GrabHelper = new Lang.Class({
return true; return true;
} }
if (!button && this._modalCount == 0)
return false;
if (this._isWithinGrabbedActor(event.get_source())) if (this._isWithinGrabbedActor(event.get_source()))
return false; return false;
@ -308,6 +358,21 @@ const GrabHelper = new Lang.Class({
return true; return true;
} }
return true; return this._modalCount > 0;
}, },
_onKeyFocusChanged: function() {
if (this._isUngrabbingCount > 0)
return;
let focus = global.stage.key_focus;
if (!focus || !this._isWithinGrabbedActor(focus))
this.ungrab({ isUser: true });
},
_focusWindowChanged: function() {
let metaDisplay = global.screen.get_display();
if (metaDisplay.focus_window != null)
this.ungrab({ isUser: true });
}
}); });

View File

@ -1,20 +1,14 @@
// -*- 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 Gtk = imports.gi.Gtk;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Signals = imports.signals;
const St = imports.gi.St; const St = imports.gi.St;
const Lang = imports.lang; const Lang = imports.lang;
const Params = imports.misc.params; const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
const ICON_SIZE = 96; const ICON_SIZE = 48;
const MIN_ICON_SIZE = 16;
const EXTRA_SPACE_ANIMATION_TIME = 0.25;
const BaseIcon = new Lang.Class({ const BaseIcon = new Lang.Class({
Name: 'BaseIcon', Name: 'BaseIcon',
@ -23,12 +17,7 @@ const BaseIcon = new Lang.Class({
params = Params.parse(params, { createIcon: null, params = Params.parse(params, { createIcon: null,
setSizeManually: false, setSizeManually: false,
showLabel: true }); showLabel: true });
this.actor = new St.Bin({ style_class: 'overview-icon',
let styleClass = 'overview-icon';
if (params.showLabel)
styleClass += ' overview-icon-with-label';
this.actor = new St.Bin({ style_class: styleClass,
x_fill: true, x_fill: true,
y_fill: true }); y_fill: true });
this.actor._delegate = this; this.actor._delegate = this;
@ -187,31 +176,19 @@ const IconGrid = new Lang.Class({
_init: function(params) { _init: function(params) {
params = Params.parse(params, { rowLimit: null, params = Params.parse(params, { rowLimit: null,
columnLimit: null, columnLimit: null,
minRows: 1,
minColumns: 1,
fillParent: false, fillParent: false,
xAlign: St.Align.MIDDLE, xAlign: St.Align.MIDDLE });
padWithSpacing: false });
this._rowLimit = params.rowLimit; this._rowLimit = params.rowLimit;
this._colLimit = params.columnLimit; this._colLimit = params.columnLimit;
this._minRows = params.minRows;
this._minColumns = params.minColumns;
this._xAlign = params.xAlign; this._xAlign = params.xAlign;
this._fillParent = params.fillParent; this._fillParent = params.fillParent;
this._padWithSpacing = params.padWithSpacing;
this.topPadding = 0;
this.bottomPadding = 0;
this.rightPadding = 0;
this.leftPadding = 0;
this.actor = new St.BoxLayout({ style_class: 'icon-grid', this.actor = new St.BoxLayout({ style_class: 'icon-grid',
vertical: true }); vertical: true });
this._items = [];
// Pulled from CSS, but hardcode some defaults here // Pulled from CSS, but hardcode some defaults here
this._spacing = 0; this._spacing = 0;
this._hItemSize = this._vItemSize = ICON_SIZE; this._hItemSize = this._vItemSize = ICON_SIZE;
this._fixedHItemSize = this._fixedVItemSize = undefined;
this._grid = new Shell.GenericContainer(); this._grid = new Shell.GenericContainer();
this.actor.add(this._grid, { expand: true, y_align: St.Align.START }); this.actor.add(this._grid, { expand: true, y_align: St.Align.START });
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged)); this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
@ -227,16 +204,16 @@ const IconGrid = new Lang.Class({
// later we'll allocate as many children as fit the parent // later we'll allocate as many children as fit the parent
return; return;
let nChildren = this._grid.get_n_children(); let children = this._grid.get_children();
let nColumns = this._colLimit ? Math.min(this._colLimit, let nColumns = this._colLimit ? Math.min(this._colLimit,
nChildren) children.length)
: nChildren; : children.length;
let totalSpacing = Math.max(0, nColumns - 1) * this._getSpacing(); let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
// Kind of a lie, but not really an issue right now. If // Kind of a lie, but not really an issue right now. If
// we wanted to support some sort of hidden/overflow that would // we wanted to support some sort of hidden/overflow that would
// need higher level design // need higher level design
alloc.min_size = this._getHItemSize() + this.leftPadding + this.rightPadding; alloc.min_size = this._hItemSize;
alloc.natural_size = nColumns * this._getHItemSize() + totalSpacing + this.leftPadding + this.rightPadding; alloc.natural_size = nColumns * this._hItemSize + totalSpacing;
}, },
_getVisibleChildren: function() { _getVisibleChildren: function() {
@ -254,11 +231,13 @@ const IconGrid = new Lang.Class({
return; return;
let children = this._getVisibleChildren(); let children = this._getVisibleChildren();
let nColumns; let nColumns, spacing;
if (forWidth < 0) if (forWidth < 0) {
nColumns = children.length; nColumns = children.length;
else spacing = this._spacing;
[nColumns, ] = this._computeLayout(forWidth); } else {
[nColumns, , spacing] = this._computeLayout(forWidth);
}
let nRows; let nRows;
if (nColumns > 0) if (nColumns > 0)
@ -267,8 +246,8 @@ const IconGrid = new Lang.Class({
nRows = 0; nRows = 0;
if (this._rowLimit) if (this._rowLimit)
nRows = Math.min(nRows, this._rowLimit); nRows = Math.min(nRows, this._rowLimit);
let totalSpacing = Math.max(0, nRows - 1) * this._getSpacing(); let totalSpacing = Math.max(0, nRows - 1) * spacing;
let height = nRows * this._getVItemSize() + totalSpacing + this.topPadding + this.bottomPadding; let height = nRows * this._vItemSize + totalSpacing;
alloc.min_size = height; alloc.min_size = height;
alloc.natural_size = height; alloc.natural_size = height;
}, },
@ -284,30 +263,48 @@ const IconGrid = new Lang.Class({
let children = this._getVisibleChildren(); let children = this._getVisibleChildren();
let availWidth = box.x2 - box.x1; let availWidth = box.x2 - box.x1;
let availHeight = box.y2 - box.y1; let availHeight = box.y2 - box.y1;
let spacing = this._getSpacing();
let [nColumns, usedWidth] = this._computeLayout(availWidth);
let leftEmptySpace; let [nColumns, usedWidth, spacing] = this._computeLayout(availWidth);
let leftPadding;
switch(this._xAlign) { switch(this._xAlign) {
case St.Align.START: case St.Align.START:
leftEmptySpace = 0; leftPadding = 0;
break; break;
case St.Align.MIDDLE: case St.Align.MIDDLE:
leftEmptySpace = Math.floor((availWidth - usedWidth) / 2); leftPadding = Math.floor((availWidth - usedWidth) / 2);
break; break;
case St.Align.END: case St.Align.END:
leftEmptySpace = availWidth - usedWidth; leftPadding = availWidth - usedWidth;
} }
let x = box.x1 + leftEmptySpace + this.leftPadding; let x = box.x1 + leftPadding;
let y = box.y1 + this.topPadding; let y = box.y1;
let columnIndex = 0; let columnIndex = 0;
let rowIndex = 0; let rowIndex = 0;
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
let childBox = this._calculateChildBox(children[i], x, y, box); let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
= children[i].get_preferred_size();
/* Center the item in its allocation horizontally */
let width = Math.min(this._hItemSize, childNaturalWidth);
let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
let height = Math.min(this._vItemSize, childNaturalHeight);
let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
let childBox = new Clutter.ActorBox();
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
let _x = box.x2 - (x + width);
childBox.x1 = Math.floor(_x - childXSpacing);
} else {
childBox.x1 = Math.floor(x + childXSpacing);
}
childBox.y1 = Math.floor(y + childYSpacing);
childBox.x2 = childBox.x1 + width;
childBox.y2 = childBox.y1 + height;
if (this._rowLimit && rowIndex >= this._rowLimit || if (this._rowLimit && rowIndex >= this._rowLimit ||
this._fillParent && childBox.y2 > availHeight - this.bottomPadding) { this._fillParent && childBox.y2 > availHeight) {
this._grid.set_skip_paint(children[i], true); this._grid.set_skip_paint(children[i], true);
} else { } else {
children[i].allocate(childBox, flags); children[i].allocate(childBox, flags);
@ -321,38 +318,15 @@ const IconGrid = new Lang.Class({
} }
if (columnIndex == 0) { if (columnIndex == 0) {
y += this._getVItemSize() + spacing; y += this._vItemSize + spacing;
x = box.x1 + leftEmptySpace + this.leftPadding; x = box.x1 + leftPadding;
} else { } else {
x += this._getHItemSize() + spacing; x += this._hItemSize + spacing;
} }
} }
}, },
_calculateChildBox: function(child, x, y, box) { childrenInRow: function(rowWidth) {
let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight] =
child.get_preferred_size();
/* Center the item in its allocation horizontally */
let width = Math.min(this._getHItemSize(), childNaturalWidth);
let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
let height = Math.min(this._getVItemSize(), childNaturalHeight);
let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
let childBox = new Clutter.ActorBox();
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
let _x = box.x2 - (x + width);
childBox.x1 = Math.floor(_x - childXSpacing);
} else {
childBox.x1 = Math.floor(x + childXSpacing);
}
childBox.y1 = Math.floor(y + childYSpacing);
childBox.x2 = childBox.x1 + width;
childBox.y2 = childBox.y1 + height;
return childBox;
},
columnsForWidth: function(rowWidth) {
return this._computeLayout(rowWidth)[0]; return this._computeLayout(rowWidth)[0];
}, },
@ -362,19 +336,26 @@ const IconGrid = new Lang.Class({
_computeLayout: function (forWidth) { _computeLayout: function (forWidth) {
let nColumns = 0; let nColumns = 0;
let usedWidth = this.leftPadding + this.rightPadding; let usedWidth = 0;
let spacing = this._getSpacing(); let spacing = this._spacing;
if (this._colLimit) {
let itemWidth = this._hItemSize * this._colLimit;
let emptyArea = forWidth - itemWidth;
spacing = Math.max(this._spacing, emptyArea / (2 * this._colLimit));
spacing = Math.round(spacing);
}
while ((this._colLimit == null || nColumns < this._colLimit) && while ((this._colLimit == null || nColumns < this._colLimit) &&
(usedWidth + this._getHItemSize() <= forWidth)) { (usedWidth + this._hItemSize <= forWidth)) {
usedWidth += this._getHItemSize() + spacing; usedWidth += this._hItemSize + spacing;
nColumns += 1; nColumns += 1;
} }
if (nColumns > 0) if (nColumns > 0)
usedWidth -= spacing; usedWidth -= spacing;
return [nColumns, usedWidth]; return [nColumns, usedWidth, spacing];
}, },
_onStyleChanged: function() { _onStyleChanged: function() {
@ -385,49 +366,15 @@ const IconGrid = new Lang.Class({
this._grid.queue_relayout(); this._grid.queue_relayout();
}, },
nRows: function(forWidth) {
let children = this._getVisibleChildren();
let nColumns = (forWidth < 0) ? children.length : this._computeLayout(forWidth)[0];
let nRows = (nColumns > 0) ? Math.ceil(children.length / nColumns) : 0;
if (this._rowLimit)
nRows = Math.min(nRows, this._rowLimit);
return nRows;
},
rowsForHeight: function(forHeight) {
return Math.floor((forHeight - (this.topPadding + this.bottomPadding) + this._getSpacing()) / (this._getVItemSize() + this._getSpacing()));
},
usedHeightForNRows: function(nRows) {
return (this._getVItemSize() + this._getSpacing()) * nRows - this._getSpacing() + this.topPadding + this.bottomPadding;
},
usedWidth: function(forWidth) {
return this.usedWidthForNColumns(this.columnsForWidth(forWidth));
},
usedWidthForNColumns: function(columns) {
let usedWidth = columns * (this._getHItemSize() + this._getSpacing());
usedWidth -= this._getSpacing();
return usedWidth + this.leftPadding + this.rightPadding;
},
removeAll: function() { removeAll: function() {
this._items = [];
this._grid.destroy_all_children(); this._grid.destroy_all_children();
}, },
addItem: function(item, index) { addItem: function(actor, index) {
if (!item.icon || !item.icon instanceof BaseIcon) {
log('Only items with a BaseIcon icon property can be added to IconGrid');
return;
}
this._items.push(item);
if (index !== undefined) if (index !== undefined)
this._grid.insert_child_at_index(item.actor, index); this._grid.insert_child_at_index(actor, index);
else else
this._grid.add_actor(item.actor); this._grid.add_actor(actor);
}, },
getItemAtIndex: function(index) { getItemAtIndex: function(index) {
@ -436,311 +383,5 @@ const IconGrid = new Lang.Class({
visibleItemsCount: function() { visibleItemsCount: function() {
return this._grid.get_n_children() - this._grid.get_n_skip_paint(); return this._grid.get_n_children() - this._grid.get_n_skip_paint();
},
setSpacing: function(spacing) {
this._fixedSpacing = spacing;
},
_getSpacing: function() {
return this._fixedSpacing ? this._fixedSpacing : this._spacing;
},
_getHItemSize: function() {
return this._fixedHItemSize ? this._fixedHItemSize : this._hItemSize;
},
_getVItemSize: function() {
return this._fixedVItemSize ? this._fixedVItemSize : this._vItemSize;
},
_updateSpacingForSize: function(availWidth, availHeight) {
let maxEmptyVArea = availHeight - this._minRows * this._getVItemSize();
let maxEmptyHArea = availWidth - this._minColumns * this._getHItemSize();
let maxHSpacing, maxVSpacing;
if (this._padWithSpacing) {
// minRows + 1 because we want to put spacing before the first row, so it is like we have one more row
// to divide the empty space
maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows +1));
maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns +1));
} else {
if (this._minRows <= 1)
maxVSpacing = maxEmptyVArea;
else
maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows - 1));
if (this._minColumns <= 1)
maxHSpacing = maxEmptyHArea;
else
maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns - 1));
}
let maxSpacing = Math.min(maxHSpacing, maxVSpacing);
// Limit spacing to the item size
maxSpacing = Math.min(maxSpacing, Math.min(this._getVItemSize(), this._getHItemSize()));
// The minimum spacing, regardless of whether it satisfies the row/columng minima,
// is the spacing we get from CSS.
let spacing = Math.max(this._spacing, maxSpacing);
this.setSpacing(spacing);
if (this._padWithSpacing)
this.topPadding = this.rightPadding = this.bottomPadding = this.leftPadding = spacing;
},
/**
* This function must to be called before iconGrid allocation,
* to know how much spacing can the grid has
*/
adaptToSize: function(availWidth, availHeight) {
this._fixedHItemSize = this._hItemSize;
this._fixedVItemSize = this._vItemSize;
this._updateSpacingForSize(availWidth, availHeight);
let spacing = this._getSpacing();
if (this.columnsForWidth(availWidth) < this._minColumns || this.rowsForHeight(availHeight) < this._minRows) {
let neededWidth = this.usedWidthForNColumns(this._minColumns) - availWidth ;
let neededHeight = this.usedHeightForNRows(this._minRows) - availHeight ;
let neededSpacePerItem = (neededWidth > neededHeight) ? Math.ceil(neededWidth / this._minColumns)
: Math.ceil(neededHeight / this._minRows);
this._fixedHItemSize = Math.max(this._hItemSize - neededSpacePerItem, MIN_ICON_SIZE);
this._fixedVItemSize = Math.max(this._vItemSize - neededSpacePerItem, MIN_ICON_SIZE);
if (this._fixedHItemSize < MIN_ICON_SIZE)
this._fixedHItemSize = MIN_ICON_SIZE;
if (this._fixedVItemSize < MIN_ICON_SIZE)
this._fixedVItemSize = MIN_ICON_SIZE;
this._updateSpacingForSize(availWidth, availHeight);
}
let scale = Math.min(this._fixedHItemSize, this._fixedVItemSize) / Math.max(this._hItemSize, this._vItemSize);
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { this._updateChildrenScale(scale); }));
},
// Note that this is ICON_SIZE as used by BaseIcon, not elsewhere in IconGrid; it's a bit messed up
_updateChildrenScale: function(scale) {
for (let i in this._items) {
let newIconSize = Math.floor(ICON_SIZE * scale);
this._items[i].icon.setIconSize(newIconSize);
}
} }
}); });
const PaginatedIconGrid = new Lang.Class({
Name: 'PaginatedIconGrid',
Extends: IconGrid,
_init: function(params) {
this.parent(params);
this._nPages = 0;
this._rowsPerPage = 0;
this._spaceBetweenPages = 0;
this._childrenPerPage = 0;
},
_getPreferredHeight: function (grid, forWidth, alloc) {
alloc.min_size = (this._availableHeightPerPageForItems() + this.bottomPadding + this.topPadding) * this._nPages + this._spaceBetweenPages * this._nPages;
alloc.natural_size = (this._availableHeightPerPageForItems() + this.bottomPadding + this.topPadding) * this._nPages + this._spaceBetweenPages * this._nPages;
},
_allocate: function (grid, box, flags) {
if (this._childrenPerPage == 0)
log('computePages() must be called before allocate(); pagination will not work.');
if (this._fillParent) {
// Reset the passed in box to fill the parent
let parentBox = this.actor.get_parent().allocation;
let gridBox = this.actor.get_theme_node().get_content_box(parentBox);
box = this._grid.get_theme_node().get_content_box(gridBox);
}
let children = this._getVisibleChildren();
let availWidth = box.x2 - box.x1;
let availHeight = box.y2 - box.y1;
let spacing = this._getSpacing();
let [nColumns, usedWidth] = this._computeLayout(availWidth);
let leftEmptySpace;
switch(this._xAlign) {
case St.Align.START:
leftEmptySpace = 0;
break;
case St.Align.MIDDLE:
leftEmptySpace = Math.floor((availWidth - usedWidth) / 2);
break;
case St.Align.END:
leftEmptySpace = availWidth - usedWidth;
}
let x = box.x1 + leftEmptySpace + this.leftPadding;
let y = box.y1 + this.topPadding;
let columnIndex = 0;
let rowIndex = 0;
for (let i = 0; i < children.length; i++) {
let childBox = this._calculateChildBox(children[i], x, y, box);
children[i].allocate(childBox, flags);
this._grid.set_skip_paint(children[i], false);
columnIndex++;
if (columnIndex == nColumns) {
columnIndex = 0;
rowIndex++;
}
if (columnIndex == 0) {
y += this._getVItemSize() + spacing;
if ((i + 1) % this._childrenPerPage == 0)
y += this._spaceBetweenPages - spacing + this.bottomPadding + this.topPadding;
x = box.x1 + leftEmptySpace + this.leftPadding;
} else
x += this._getHItemSize() + spacing;
}
},
_computePages: function (availWidthPerPage, availHeightPerPage) {
let [nColumns, usedWidth] = this._computeLayout(availWidthPerPage);
let nRows;
let children = this._getVisibleChildren();
if (nColumns > 0)
nRows = Math.ceil(children.length / nColumns);
else
nRows = 0;
if (this._rowLimit)
nRows = Math.min(nRows, this._rowLimit);
let spacing = this._getSpacing();
// We want to contain the grid inside the parent box with padding
this._rowsPerPage = this.rowsForHeight(availHeightPerPage);
this._nPages = Math.ceil(nRows / this._rowsPerPage);
this._spaceBetweenPages = availHeightPerPage - (this.topPadding + this.bottomPadding) - this._availableHeightPerPageForItems();
this._childrenPerPage = nColumns * this._rowsPerPage;
},
adaptToSize: function(availWidth, availHeight) {
this.parent(availWidth, availHeight);
this._computePages(availWidth, availHeight);
},
_availableHeightPerPageForItems: function() {
return this.usedHeightForNRows(this._rowsPerPage) - (this.topPadding + this.bottomPadding);
},
nPages: function() {
return this._nPages;
},
getPageY: function(pageNumber) {
if (!this._nPages)
return 0;
let firstPageItem = pageNumber * this._childrenPerPage
let childBox = this._getVisibleChildren()[firstPageItem].get_allocation_box();
return childBox.y1 - this.topPadding;
},
getItemPage: function(item) {
let children = this._getVisibleChildren();
let index = children.indexOf(item);
if (index == -1) {
throw new Error('Item not found.');
return 0;
}
return Math.floor(index / this._childrenPerPage);
},
/**
* openExtraSpace:
* @sourceItem: the item for which to create extra space
* @side: where @sourceItem should be located relative to the created space
* @nRows: the amount of space to create
*
* Pan view to create extra space for @nRows above or below @sourceItem.
*/
openExtraSpace: function(sourceItem, side, nRows) {
let children = this._getVisibleChildren();
let index = children.indexOf(sourceItem.actor);
if (index == -1) {
throw new Error('Item not found.');
return;
}
let pageIndex = Math.floor(index / this._childrenPerPage);
let pageOffset = pageIndex * this._childrenPerPage;
let childrenPerRow = this._childrenPerPage / this._rowsPerPage;
let sourceRow = Math.floor((index - pageOffset) / childrenPerRow);
let nRowsAbove = (side == St.Side.TOP) ? sourceRow + 1
: sourceRow;
let nRowsBelow = this._rowsPerPage - nRowsAbove;
let nRowsUp, nRowsDown;
if (side == St.Side.TOP) {
nRowsDown = Math.min(nRowsBelow, nRows);
nRowsUp = nRows - nRowsDown;
} else {
nRowsUp = Math.min(nRowsAbove, nRows);
nRowsDown = nRows - nRowsUp;
}
let childrenDown = children.splice(pageOffset +
nRowsAbove * childrenPerRow,
nRowsBelow * childrenPerRow);
let childrenUp = children.splice(pageOffset,
nRowsAbove * childrenPerRow);
// Special case: On the last row with no rows below the icon,
// there's no need to move any rows either up or down
if (childrenDown.length == 0 && nRowsUp == 0) {
this._translatedChildren = [];
this.emit('space-opened');
} else {
this._translateChildren(childrenUp, Gtk.DirectionType.UP, nRowsUp);
this._translateChildren(childrenDown, Gtk.DirectionType.DOWN, nRowsDown);
this._translatedChildren = childrenUp.concat(childrenDown);
}
},
_translateChildren: function(children, direction, nRows) {
let translationY = nRows * (this._getVItemSize() + this._getSpacing());
if (translationY == 0)
return;
if (direction == Gtk.DirectionType.UP)
translationY *= -1;
for (let i = 0; i < children.length; i++) {
children[i].translation_y = 0;
let params = { translation_y: translationY,
time: EXTRA_SPACE_ANIMATION_TIME,
transition: 'easeInOutQuad'
};
if (i == (children.length - 1))
params.onComplete = Lang.bind(this,
function() {
this.emit('space-opened');
});
Tweener.addTween(children[i], params);
}
},
closeExtraSpace: function() {
if (!this._translatedChildren || !this._translatedChildren.length) {
this.emit('space-closed');
return;
}
for (let i = 0; i < this._translatedChildren.length; i++) {
if (!this._translatedChildren[i].translation_y)
continue;
Tweener.addTween(this._translatedChildren[i],
{ translation_y: 0,
time: EXTRA_SPACE_ANIMATION_TIME,
transition: 'easeInOutQuad',
onComplete: Lang.bind(this,
function() {
this.emit('space-closed');
})
});
}
}
});
Signals.addSignalMethods(PaginatedIconGrid.prototype);

View File

@ -118,25 +118,10 @@ const MonitorConstraint = new Lang.Class({
} }
}); });
const Monitor = new Lang.Class({
Name: 'Monitor',
_init: function(index, geometry) {
this.index = index;
this.x = geometry.x;
this.y = geometry.y;
this.width = geometry.width;
this.height = geometry.height;
},
get inFullscreen() {
return global.screen.get_monitor_in_fullscreen(this.index);
}
})
const defaultParams = { const defaultParams = {
trackFullscreen: false, trackFullscreen: false,
affectsStruts: false, affectsStruts: false,
affectsInputRegion: true
}; };
const LayoutManager = new Lang.Class({ const LayoutManager = new Lang.Class({
@ -188,12 +173,10 @@ const LayoutManager = new Lang.Class({
global.stage.remove_actor(global.window_group); global.stage.remove_actor(global.window_group);
this.uiGroup.add_actor(global.window_group); this.uiGroup.add_actor(global.window_group);
global.stage.remove_actor(global.overlay_group);
this.uiGroup.add_actor(global.overlay_group);
global.stage.add_child(this.uiGroup); global.stage.add_child(this.uiGroup);
this.overviewGroup = new St.Widget({ name: 'overviewGroup',
visible: false });
this.addChrome(this.overviewGroup);
this.screenShieldGroup = new St.Widget({ name: 'screenShieldGroup', this.screenShieldGroup = new St.Widget({ name: 'screenShieldGroup',
visible: false, visible: false,
clip_to_allocation: true, clip_to_allocation: true,
@ -227,6 +210,11 @@ const LayoutManager = new Lang.Class({
this._backgroundGroup.lower_bottom(); this._backgroundGroup.lower_bottom();
this._bgManagers = []; this._bgManagers = [];
// This blocks the XDND picks from finding the activities button
// and we never attempt to pick anything from it anyway so make
// it invisible from picks
Shell.util_set_hidden_from_pick(global.top_window_group, true);
// Need to update struts on new workspaces when they are added // Need to update struts on new workspaces when they are added
global.screen.connect('notify::n-workspaces', global.screen.connect('notify::n-workspaces',
Lang.bind(this, this._queueUpdateRegions)); Lang.bind(this, this._queueUpdateRegions));
@ -239,24 +227,24 @@ const LayoutManager = new Lang.Class({
this._monitorsChanged(); this._monitorsChanged();
}, },
// This is called by Main after everything else is constructed // This is called by Main after everything else is constructed;
// it needs access to Main.overview, which didn't exist
// yet when the LayoutManager was constructed.
init: function() { init: function() {
Main.overview.connect('showing', Lang.bind(this, this._overviewShowing));
Main.overview.connect('hidden', Lang.bind(this, this._overviewHidden));
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
this._loadBackground(); this._prepareStartupAnimation();
}, },
showOverview: function() { _overviewShowing: function() {
this.overviewGroup.show();
this._inOverview = true; this._inOverview = true;
this._updateVisibility(); this._updateVisibility();
this._updateRegions(); this._queueUpdateRegions();
}, },
hideOverview: function() { _overviewHidden: function() {
this.overviewGroup.hide();
this._inOverview = false; this._inOverview = false;
this._updateVisibility(); this._updateVisibility();
this._queueUpdateRegions(); this._queueUpdateRegions();
@ -273,7 +261,7 @@ const LayoutManager = new Lang.Class({
this.monitors = []; this.monitors = [];
let nMonitors = screen.get_n_monitors(); let nMonitors = screen.get_n_monitors();
for (let i = 0; i < nMonitors; i++) for (let i = 0; i < nMonitors; i++)
this.monitors.push(new Monitor(i, screen.get_monitor_geometry(i))); this.monitors.push(screen.get_monitor_geometry(i));
if (nMonitors == 1) { if (nMonitors == 1) {
this.primaryIndex = this.bottomIndex = 0; this.primaryIndex = this.bottomIndex = 0;
@ -295,10 +283,8 @@ const LayoutManager = new Lang.Class({
_updateHotCorners: function() { _updateHotCorners: function() {
// destroy old hot corners // destroy old hot corners
this.hotCorners.forEach(function(corner) { for (let i = 0; i < this.hotCorners.length; i++)
if (corner) this.hotCorners[i].destroy();
corner.destroy();
});
this.hotCorners = []; this.hotCorners = [];
let size = this.panelBox.height; let size = this.panelBox.height;
@ -309,9 +295,9 @@ const LayoutManager = new Lang.Class({
let cornerX = this._rtl ? monitor.x + monitor.width : monitor.x; let cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
let cornerY = monitor.y; let cornerY = monitor.y;
let haveTopLeftCorner = true;
if (i != this.primaryIndex) { if (i != this.primaryIndex) {
let haveTopLeftCorner = true;
// Check if we have a top left (right for RTL) corner. // Check if we have a top left (right for RTL) corner.
// I.e. if there is no monitor directly above or to the left(right) // I.e. if there is no monitor directly above or to the left(right)
let besideX = this._rtl ? monitor.x + 1 : cornerX - 1; let besideX = this._rtl ? monitor.x + 1 : cornerX - 1;
@ -338,15 +324,14 @@ const LayoutManager = new Lang.Class({
break; break;
} }
} }
if (!haveTopLeftCorner)
continue;
} }
if (haveTopLeftCorner) { let corner = new HotCorner(this, monitor, cornerX, cornerY);
let corner = new HotCorner(this, monitor, cornerX, cornerY); corner.setBarrierSize(size);
corner.setBarrierSize(size); this.hotCorners.push(corner);
this.hotCorners.push(corner);
} else {
this.hotCorners.push(null);
}
} }
this.emit('hot-corners-changed'); this.emit('hot-corners-changed');
@ -362,7 +347,7 @@ const LayoutManager = new Lang.Class({
BackgroundMenu.addBackgroundMenu(bgManager.background.actor); BackgroundMenu.addBackgroundMenu(bgManager.background.actor);
})); }));
this._bgManagers[monitorIndex] = bgManager; this._bgManagers.push(bgManager);
return bgManager.background; return bgManager.background;
}, },
@ -423,8 +408,7 @@ const LayoutManager = new Lang.Class({
let size = this.panelBox.height; let size = this.panelBox.height;
this.hotCorners.forEach(function(corner) { this.hotCorners.forEach(function(corner) {
if (corner) corner.setBarrierSize(size);
corner.setBarrierSize(size);
}); });
}, },
@ -523,10 +507,17 @@ const LayoutManager = new Lang.Class({
get focusIndex() { get focusIndex() {
let i = Main.layoutManager.primaryIndex; let i = Main.layoutManager.primaryIndex;
if (global.stage.key_focus != null) if (global.stage_input_mode == Shell.StageInputMode.FOCUSED ||
i = this.findIndexForActor(global.stage.key_focus); global.stage_input_mode == Shell.StageInputMode.FULLSCREEN) {
else if (global.display.focus_window != null) let focusActor = global.stage.key_focus;
i = global.display.focus_window.get_monitor(); if (focusActor)
i = this.findIndexForActor(focusActor);
} else {
let focusWindow = global.display.focus_window;
if (focusWindow)
i = focusWindow.get_monitor();
}
return i; return i;
}, },
@ -545,25 +536,6 @@ const LayoutManager = new Lang.Class({
return this._keyboardIndex; return this._keyboardIndex;
}, },
_loadBackground: function() {
this._systemBackground = new Background.SystemBackground();
this._systemBackground.actor.hide();
global.stage.insert_child_below(this._systemBackground.actor, null);
let constraint = new Clutter.BindConstraint({ source: global.stage,
coordinate: Clutter.BindCoordinate.ALL });
this._systemBackground.actor.add_constraint(constraint);
let signalId = this._systemBackground.connect('loaded', Lang.bind(this, function() {
this._systemBackground.disconnect(signalId);
this._systemBackground.actor.show();
global.stage.show();
this._prepareStartupAnimation();
}));
},
// Startup Animations // Startup Animations
// //
// We have two different animations, depending on whether we're a greeter // We have two different animations, depending on whether we're a greeter
@ -584,19 +556,13 @@ const LayoutManager = new Lang.Class({
// screen. So, we set no_clear_hint at the end of the animation. // screen. So, we set no_clear_hint at the end of the animation.
_prepareStartupAnimation: function() { _prepareStartupAnimation: function() {
// During the initial transition, add a simple actor to block all events, // Set ourselves to FULLSCREEN input mode while the animation is running
// so they don't get delivered to X11 windows that have been transformed. // so events don't get delivered to X11 windows (which are distorted by the animation)
this._coverPane = new Clutter.Actor({ opacity: 0, global.stage_input_mode = Shell.StageInputMode.FULLSCREEN;
width: global.screen_width,
height: global.screen_height,
reactive: true });
this.addChrome(this._coverPane);
if (Main.sessionMode.isGreeter) { if (Main.sessionMode.isGreeter) {
this.panelBox.translation_y = -this.panelBox.height; this.panelBox.translation_y = -this.panelBox.height;
} else { } else {
this._createPrimaryBackground();
// We need to force an update of the regions now before we scale // We need to force an update of the regions now before we scale
// the UI group to get the coorect allocation for the struts. // the UI group to get the coorect allocation for the struts.
this._updateRegions(); this._updateRegions();
@ -615,18 +581,35 @@ const LayoutManager = new Lang.Class({
global.window_group.set_clip(monitor.x, monitor.y, monitor.width, monitor.height); global.window_group.set_clip(monitor.x, monitor.y, monitor.width, monitor.height);
} }
this.emit('startup-prepared'); this._systemBackground = new Background.SystemBackground();
this._systemBackground.actor.hide();
// We're mostly prepared for the startup animation global.stage.insert_child_below(this._systemBackground.actor, null);
// now, but since a lot is going on asynchronously
// during startup, let's defer the startup animation let constraint = new Clutter.BindConstraint({ source: global.stage,
// until the event loop is uncontended and idle. coordinate: Clutter.BindCoordinate.ALL });
// This helps to prevent us from running the animation this._systemBackground.actor.add_constraint(constraint);
// when the system is bogged down
GLib.idle_add(GLib.PRIORITY_LOW, Lang.bind(this, function() { let signalId = this._systemBackground.connect('loaded',
this._startupAnimation(); Lang.bind(this, function() {
return false; this._systemBackground.disconnect(signalId);
})); this._systemBackground.actor.show();
global.stage.show();
this.emit('startup-prepared');
// We're mostly prepared for the startup animation
// now, but since a lot is going on asynchronously
// during startup, let's defer the startup animation
// until the event loop is uncontended and idle.
// This helps to prevent us from running the animation
// when the system is bogged down
GLib.idle_add(GLib.PRIORITY_LOW,
Lang.bind(this, function() {
this._startupAnimation();
return false;
}));
}));
}, },
_startupAnimation: function() { _startupAnimation: function() {
@ -646,6 +629,7 @@ const LayoutManager = new Lang.Class({
}, },
_startupAnimationSession: function() { _startupAnimationSession: function() {
this._createPrimaryBackground();
Tweener.addTween(this.uiGroup, Tweener.addTween(this.uiGroup,
{ scale_x: 1, { scale_x: 1,
scale_y: 1, scale_y: 1,
@ -661,8 +645,7 @@ const LayoutManager = new Lang.Class({
// we no longer need to clear the stage // we no longer need to clear the stage
global.stage.no_clear_hint = true; global.stage.no_clear_hint = true;
this._coverPane.destroy(); global.stage_input_mode = Shell.StageInputMode.NORMAL;
this._coverPane = null;
this._systemBackground.actor.destroy(); this._systemBackground.actor.destroy();
this._systemBackground = null; this._systemBackground = null;
@ -683,6 +666,7 @@ const LayoutManager = new Lang.Class({
}, },
showKeyboard: function () { showKeyboard: function () {
this.keyboardBox.raise_top();
Tweener.addTween(this.keyboardBox, Tweener.addTween(this.keyboardBox,
{ anchor_y: this.keyboardBox.height, { anchor_y: this.keyboardBox.height,
time: KEYBOARD_ANIMATION_TIME, time: KEYBOARD_ANIMATION_TIME,
@ -727,10 +711,11 @@ const LayoutManager = new Lang.Class({
// @actor: an actor to add to the chrome // @actor: an actor to add to the chrome
// @params: (optional) additional params // @params: (optional) additional params
// //
// Adds @actor to the chrome, and extends the input region // Adds @actor to the chrome, and (unless %affectsInputRegion in
// to include it. Changes in @actor's size, position, and // @params is %false) extends the input region to include it.
// visibility will automatically result in appropriate changes // Changes in @actor's size, position, and visibility will
// to the input region. // automatically result in appropriate changes to the input
// region.
// //
// If %affectsStruts in @params is %true (and @actor is along a // If %affectsStruts in @params is %true (and @actor is along a
// screen edge), then @actor's size and position will also affect // screen edge), then @actor's size and position will also affect
@ -743,8 +728,6 @@ const LayoutManager = new Lang.Class({
// and shown otherwise) // and shown otherwise)
addChrome: function(actor, params) { addChrome: function(actor, params) {
this.uiGroup.add_actor(actor); this.uiGroup.add_actor(actor);
if (this.uiGroup.contains(global.top_window_group))
this.uiGroup.set_child_below_sibling(actor, global.top_window_group);
this._trackActor(actor, params); this._trackActor(actor, params);
}, },
@ -917,8 +900,13 @@ const LayoutManager = new Lang.Class({
}, },
_updateFullscreen: function() { _updateFullscreen: function() {
for (let i = 0; i < this.monitors.length; i++)
this.monitors[i].inFullscreen = global.screen.get_monitor_in_fullscreen (i);
this._updateVisibility(); this._updateVisibility();
this._queueUpdateRegions(); this._queueUpdateRegions();
this.emit('fullscreen-changed');
}, },
_windowsRestacked: function() { _windowsRestacked: function() {
@ -946,7 +934,7 @@ const LayoutManager = new Lang.Class({
for (i = 0; i < this._trackedActors.length; i++) { for (i = 0; i < this._trackedActors.length; i++) {
let actorData = this._trackedActors[i]; let actorData = this._trackedActors[i];
if (!wantsInputRegion && !actorData.affectsStruts) if (!(actorData.affectsInputRegion && wantsInputRegion) && !actorData.affectsStruts)
continue; continue;
let [x, y] = actorData.actor.get_transformed_position(); let [x, y] = actorData.actor.get_transformed_position();
@ -956,8 +944,13 @@ const LayoutManager = new Lang.Class({
w = Math.round(w); w = Math.round(w);
h = Math.round(h); h = Math.round(h);
if (wantsInputRegion && actorData.actor.get_paint_visibility()) if (actorData.affectsInputRegion && wantsInputRegion) {
rects.push(new Meta.Rectangle({ x: x, y: y, width: w, height: h })); let rect = new Meta.Rectangle({ x: x, y: y, width: w, height: h});
if (actorData.actor.get_paint_visibility() &&
!this.uiGroup.get_skip_paint(actorData.actor))
rects.push(rect);
}
if (actorData.affectsStruts) { if (actorData.affectsStruts) {
// Limit struts to the size of the screen // Limit struts to the size of the screen
@ -1099,21 +1092,12 @@ const HotCorner = new Lang.Class({
} }
if (size > 0) { if (size > 0) {
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) { this._verticalBarrier = new Meta.Barrier({ display: global.display,
this._verticalBarrier = new Meta.Barrier({ display: global.display, x1: this._x, x2: this._x, y1: this._y, y2: this._y + size,
x1: this._x, x2: this._x, y1: this._y, y2: this._y + size, directions: Meta.BarrierDirection.POSITIVE_X });
directions: Meta.BarrierDirection.NEGATIVE_X }); this._horizontalBarrier = new Meta.Barrier({ display: global.display,
this._horizontalBarrier = new Meta.Barrier({ display: global.display, x1: this._x, x2: this._x + size, y1: this._y, y2: this._y,
x1: this._x - size, x2: this._x, y1: this._y, y2: this._y, directions: Meta.BarrierDirection.POSITIVE_Y });
directions: Meta.BarrierDirection.POSITIVE_Y });
} else {
this._verticalBarrier = new Meta.Barrier({ display: global.display,
x1: this._x, x2: this._x, y1: this._y, y2: this._y + size,
directions: Meta.BarrierDirection.POSITIVE_X });
this._horizontalBarrier = new Meta.Barrier({ display: global.display,
x1: this._x, x2: this._x + size, y1: this._y, y2: this._y,
directions: Meta.BarrierDirection.POSITIVE_Y });
}
this._pressureBarrier.addBarrier(this._verticalBarrier); this._pressureBarrier.addBarrier(this._verticalBarrier);
this._pressureBarrier.addBarrier(this._horizontalBarrier); this._pressureBarrier.addBarrier(this._horizontalBarrier);
@ -1128,11 +1112,11 @@ const HotCorner = new Lang.Class({
height: 3, height: 3,
reactive: true }); reactive: true });
this._corner = new Clutter.Actor({ name: 'hot-corner', this._corner = new Clutter.Rectangle({ name: 'hot-corner',
width: 1, width: 1,
height: 1, height: 1,
opacity: 0, opacity: 0,
reactive: true }); reactive: true });
this._corner._delegate = this; this._corner._delegate = this;
this.actor.add_child(this._corner); this.actor.add_child(this._corner);

View File

@ -42,11 +42,15 @@ 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,
fadeFactor: DEFAULT_FADE_FACTOR, fadeInTime: 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._fadeOutTime = params.fadeOutTime;
this._fadeFactor = params.fadeFactor; this._fadeFactor = params.fadeFactor;
this.actor = new St.Bin({ x: 0, this.actor = new St.Bin({ x: 0,
y: 0, y: 0,
@ -97,16 +101,14 @@ const Lightbox = new Lang.Class({
} }
}, },
show: function(fadeInTime) { show: function() {
fadeInTime = fadeInTime || 0;
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);
if (fadeInTime != 0) { if (this._fadeInTime) {
this.shown = false; 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 * this._fadeFactor,
time: fadeInTime, time: this._fadeInTime,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, function() {
this.shown = true; this.shown = true;
@ -121,15 +123,13 @@ const Lightbox = new Lang.Class({
this.actor.show(); this.actor.show();
}, },
hide: function(fadeOutTime) { hide: function() {
fadeOutTime = fadeOutTime || 0;
this.shown = false; this.shown = false;
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);
if (fadeOutTime != 0) { if (this._fadeOutTime) {
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 0, { opacity: 0,
time: fadeOutTime, time: this._fadeOutTime,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, function() {
this.actor.hide(); this.actor.hide();

View File

@ -308,6 +308,10 @@ const Result = new Lang.Class({
box.add(resultTxt); box.add(resultTxt);
let objLink = new ObjLink(this._lookingGlass, o); let objLink = new ObjLink(this._lookingGlass, o);
box.add(objLink.actor); box.add(objLink.actor);
let line = new Clutter.Rectangle({ name: 'Separator' });
let padBin = new St.Bin({ name: 'Separator', x_fill: true, y_fill: true });
padBin.add_actor(line);
this.actor.add(padBin);
} }
}); });
@ -849,9 +853,8 @@ const LookingGlass = new Lang.Class({
this._updateFont(); this._updateFont();
// We want it to appear to slide out from underneath the panel // We want it to appear to slide out from underneath the panel
Main.uiGroup.add_actor(this.actor); Main.layoutManager.panelBox.add_actor(this.actor);
Main.uiGroup.set_child_below_sibling(this.actor, this.actor.lower_bottom();
Main.layoutManager.panelBox);
Main.layoutManager.panelBox.connect('allocation-changed', Main.layoutManager.panelBox.connect('allocation-changed',
Lang.bind(this, this._queueResize)); Lang.bind(this, this._queueResize));
Main.layoutManager.keyboardBox.connect('allocation-changed', Main.layoutManager.keyboardBox.connect('allocation-changed',
@ -920,7 +923,7 @@ const LookingGlass = new Lang.Class({
let text = o.get_text(); let text = o.get_text();
// Ensure we don't get newlines in the command; the history file is // Ensure we don't get newlines in the command; the history file is
// newline-separated. // newline-separated.
text = text.replace('\n', ' '); text.replace('\n', ' ');
// Strip leading and trailing whitespace // Strip leading and trailing whitespace
text = text.replace(/^\s+/g, '').replace(/\s+$/g, ''); text = text.replace(/^\s+/g, '').replace(/\s+$/g, '');
if (text == '') if (text == '')
@ -986,18 +989,28 @@ const LookingGlass = new Lang.Class({
_showCompletions: function(completions) { _showCompletions: function(completions) {
if (!this._completionActor) { if (!this._completionActor) {
this._completionActor = new St.Label({ name: 'LookingGlassAutoCompletionText', style_class: 'lg-completions-text' }); let actor = new St.BoxLayout({ vertical: true });
this._completionActor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._completionActor.clutter_text.line_wrap = true; this._completionText = new St.Label({ name: 'LookingGlassAutoCompletionText', style_class: 'lg-completions-text' });
this._completionText.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._completionText.clutter_text.line_wrap = true;
actor.add(this._completionText);
let line = new Clutter.Rectangle();
let padBin = new St.Bin({ x_fill: true, y_fill: true });
padBin.add_actor(line);
actor.add(padBin);
this._completionActor = actor;
this._evalBox.insert_child_below(this._completionActor, this._entryArea); this._evalBox.insert_child_below(this._completionActor, this._entryArea);
} }
this._completionActor.set_text(completions.join(', ')); this._completionText.set_text(completions.join(', '));
// Setting the height to -1 allows us to get its actual preferred height rather than // Setting the height to -1 allows us to get its actual preferred height rather than
// whatever was last given in set_height by Tweener. // whatever was last given in set_height by Tweener.
this._completionActor.set_height(-1); this._completionActor.set_height(-1);
let [minHeight, naturalHeight] = this._completionActor.get_preferred_height(this._resultsArea.get_width()); let [minHeight, naturalHeight] = this._completionText.get_preferred_height(this._resultsArea.get_width());
// Don't reanimate if we are already visible // Don't reanimate if we are already visible
if (this._completionActor.visible) { if (this._completionActor.visible) {
@ -1072,15 +1085,15 @@ const LookingGlass = new Lang.Class({
let myWidth = primary.width * 0.7; let myWidth = primary.width * 0.7;
let availableHeight = primary.height - Main.layoutManager.keyboardBox.height; let availableHeight = primary.height - Main.layoutManager.keyboardBox.height;
let myHeight = Math.min(primary.height * 0.7, availableHeight * 0.9); let myHeight = Math.min(primary.height * 0.7, availableHeight * 0.9);
this.actor.x = primary.x + (primary.width - myWidth) / 2; this.actor.x = (primary.width - myWidth) / 2;
this._hiddenY = primary.y + Main.layoutManager.panelBox.height - myHeight - 4; // -4 to hide the top corners this._hiddenY = this.actor.get_parent().height - myHeight - 4; // -4 to hide the top corners
this._targetY = this._hiddenY + myHeight; this._targetY = this._hiddenY + myHeight;
this.actor.y = this._hiddenY; this.actor.y = this._hiddenY;
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(this.actor.x + Math.floor(myWidth * 0.1), this._objInspector.actor.set_position(primary.x + this.actor.x + Math.floor(myWidth * 0.1),
this._targetY + Math.floor(myHeight * 0.1)); primary.y + this._targetY + Math.floor(myHeight * 0.1));
}, },
insertObject: function(obj) { insertObject: function(obj) {
@ -1150,7 +1163,7 @@ const LookingGlass = new Lang.Class({
Main.popModal(this._entry); Main.popModal(this._entry);
Tweener.addTween(this.actor, { time: Math.min(0.5 / St.get_slow_down_factor(), 0.5), Tweener.addTween(this.actor, { time: 0.5 / St.get_slow_down_factor(),
transition: 'easeOutQuad', transition: 'easeOutQuad',
y: this._hiddenY, y: this._hiddenY,
onComplete: Lang.bind(this, function () { onComplete: Lang.bind(this, function () {

View File

@ -1,6 +1,5 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Atspi = imports.gi.Atspi;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GDesktopEnums = imports.gi.GDesktopEnums; const GDesktopEnums = imports.gi.GDesktopEnums;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
@ -8,10 +7,8 @@ const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Signals = imports.signals; const Signals = imports.signals;
const FocusCaretTracker = imports.ui.focusCaretTracker;
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;
@ -39,8 +36,6 @@ 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';
const FOCUS_TRACKING_KEY = 'focus-tracking';
const CARET_TRACKING_KEY = 'caret-tracking';
const SHOW_CROSS_HAIRS_KEY = 'show-cross-hairs'; const SHOW_CROSS_HAIRS_KEY = 'show-cross-hairs';
const CROSS_HAIRS_THICKNESS_KEY = 'cross-hairs-thickness'; const CROSS_HAIRS_THICKNESS_KEY = 'cross-hairs-thickness';
const CROSS_HAIRS_COLOR_KEY = 'cross-hairs-color'; const CROSS_HAIRS_COLOR_KEY = 'cross-hairs-color';
@ -57,24 +52,10 @@ const Magnifier = new Lang.Class({
// Magnifier is a manager of ZoomRegions. // Magnifier is a manager of ZoomRegions.
this._zoomRegions = []; this._zoomRegions = [];
// Export to dbus.
magDBusService = new MagnifierDBus.ShellMagnifier();
let showAtLaunch = this._settingsInit();
this.setActive(showAtLaunch);
},
_initialize: function() {
if (this._initialized)
return;
this._initialized = true;
this._settingsInitLate();
// Create small clutter tree for the magnified mouse. // Create small clutter tree for the magnified mouse.
let cursorTracker = Meta.CursorTracker.get_for_screen(global.screen); let xfixesCursor = Shell.XFixesCursor.get_for_stage(global.stage);
this._mouseSprite = new Clutter.Texture(); this._mouseSprite = new Clutter.Texture();
Shell.util_cursor_tracker_to_clutter(cursorTracker, this._mouseSprite); xfixesCursor.update_texture_image(this._mouseSprite);
this._cursorRoot = new Clutter.Actor(); this._cursorRoot = new Clutter.Actor();
this._cursorRoot.add_actor(this._mouseSprite); this._cursorRoot.add_actor(this._mouseSprite);
@ -86,11 +67,15 @@ const Magnifier = new Lang.Class({
let aZoomRegion = new ZoomRegion(this, this._cursorRoot); let aZoomRegion = new ZoomRegion(this, this._cursorRoot);
this._zoomRegions.push(aZoomRegion); this._zoomRegions.push(aZoomRegion);
this._settingsInitRegion(aZoomRegion); let showAtLaunch = this._settingsInit(aZoomRegion);
aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse); aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse);
cursorTracker.connect('cursor-changed', Lang.bind(this, this._updateMouseSprite)); xfixesCursor.connect('cursor-change', Lang.bind(this, this._updateMouseSprite));
this._cursorTracker = cursorTracker; this._xfixesCursor = xfixesCursor;
// Export to dbus.
magDBusService = new MagnifierDBus.ShellMagnifier();
this.setActive(showAtLaunch);
}, },
/** /**
@ -98,7 +83,7 @@ const Magnifier = new Lang.Class({
* Show the system mouse pointer. * Show the system mouse pointer.
*/ */
showSystemCursor: function() { showSystemCursor: function() {
this._cursorTracker.set_pointer_visible(true); this._xfixesCursor.show();
}, },
/** /**
@ -106,7 +91,7 @@ const Magnifier = new Lang.Class({
* Hide the system mouse pointer. * Hide the system mouse pointer.
*/ */
hideSystemCursor: function() { hideSystemCursor: function() {
this._cursorTracker.set_pointer_visible(false); this._xfixesCursor.hide();
}, },
/** /**
@ -115,12 +100,6 @@ const Magnifier = new Lang.Class({
* @activate: Boolean to activate or de-activate the magnifier. * @activate: Boolean to activate or de-activate the magnifier.
*/ */
setActive: function(activate) { setActive: function(activate) {
if (activate == this.isActive())
return;
if (activate)
this._initialize();
this._zoomRegions.forEach (function(zoomRegion, index, array) { this._zoomRegions.forEach (function(zoomRegion, index, array) {
zoomRegion.setActive(activate); zoomRegion.setActive(activate);
}); });
@ -133,7 +112,7 @@ const Magnifier = new Lang.Class({
// Make sure system mouse pointer is shown when all zoom regions are // Make sure system mouse pointer is shown when all zoom regions are
// invisible. // invisible.
if (!activate) if (!activate)
this._cursorTracker.set_pointer_visible(true); this._xfixesCursor.show();
// Notify interested parties of this change // Notify interested parties of this change
this.emit('active-changed', activate); this.emit('active-changed', activate);
@ -443,73 +422,62 @@ const Magnifier = new Lang.Class({
//// Private methods //// //// Private methods ////
_updateMouseSprite: function() { _updateMouseSprite: function() {
Shell.util_cursor_tracker_to_clutter(this._cursorTracker, this._mouseSprite); this._xfixesCursor.update_texture_image(this._mouseSprite);
let [xHot, yHot] = this._cursorTracker.get_hot(); let xHot = this._xfixesCursor.get_hot_x();
let yHot = this._xfixesCursor.get_hot_y();
this._mouseSprite.set_anchor_point(xHot, yHot); this._mouseSprite.set_anchor_point(xHot, yHot);
}, },
_settingsInitRegion: function(zoomRegion) { _settingsInit: function(zoomRegion) {
// Mag factor is accurate to two decimal places.
let aPref = parseFloat(this._settings.get_double(MAG_FACTOR_KEY).toFixed(2));
if (aPref != 0.0)
zoomRegion.setMagFactor(aPref, aPref);
aPref = this._settings.get_enum(SCREEN_POSITION_KEY);
if (aPref)
zoomRegion.setScreenPosition(aPref);
zoomRegion.setLensMode(this._settings.get_boolean(LENS_MODE_KEY));
zoomRegion.setClampScrollingAtEdges(!this._settings.get_boolean(CLAMP_MODE_KEY));
aPref = this._settings.get_enum(MOUSE_TRACKING_KEY);
if (aPref)
zoomRegion.setMouseTrackingMode(aPref);
aPref = this._settings.get_enum(FOCUS_TRACKING_KEY);
if (aPref)
zoomRegion.setFocusTrackingMode(aPref);
aPref = this._settings.get_enum(CARET_TRACKING_KEY);
if (aPref)
zoomRegion.setCaretTrackingMode(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);
},
_settingsInit: function() {
this._appSettings = new Gio.Settings({ schema: APPLICATIONS_SCHEMA }); this._appSettings = new Gio.Settings({ schema: APPLICATIONS_SCHEMA });
this._settings = new Gio.Settings({ schema: MAGNIFIER_SCHEMA }); this._settings = new Gio.Settings({ schema: MAGNIFIER_SCHEMA });
this._appSettings.connect('changed::' + SHOW_KEY, Lang.bind(this, function() { if (zoomRegion) {
let active = this._appSettings.get_boolean(SHOW_KEY); // Mag factor is accurate to two decimal places.
this.setActive(active); let aPref = parseFloat(this._settings.get_double(MAG_FACTOR_KEY).toFixed(2));
})); if (aPref != 0.0)
zoomRegion.setMagFactor(aPref, aPref);
return this._appSettings.get_boolean(SHOW_KEY); aPref = this._settings.get_enum(SCREEN_POSITION_KEY);
}, if (aPref)
zoomRegion.setScreenPosition(aPref);
zoomRegion.setLensMode(this._settings.get_boolean(LENS_MODE_KEY));
zoomRegion.setClampScrollingAtEdges(!this._settings.get_boolean(CLAMP_MODE_KEY));
aPref = this._settings.get_enum(MOUSE_TRACKING_KEY);
if (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);
}
_settingsInitLate: function() {
let showCrosshairs = this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY); let showCrosshairs = this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY);
this.addCrosshairs(); this.addCrosshairs();
this.setCrosshairsVisible(showCrosshairs); this.setCrosshairsVisible(showCrosshairs);
this._appSettings.connect('changed::' + SHOW_KEY,
Lang.bind(this, function() {
this.setActive(this._appSettings.get_boolean(SHOW_KEY));
}));
this._settings.connect('changed::' + SCREEN_POSITION_KEY, this._settings.connect('changed::' + SCREEN_POSITION_KEY,
Lang.bind(this, this._updateScreenPosition)); Lang.bind(this, this._updateScreenPosition));
this._settings.connect('changed::' + MAG_FACTOR_KEY, this._settings.connect('changed::' + MAG_FACTOR_KEY,
@ -520,10 +488,6 @@ const Magnifier = new Lang.Class({
Lang.bind(this, this._updateClampMode)); Lang.bind(this, this._updateClampMode));
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::' + FOCUS_TRACKING_KEY,
Lang.bind(this, this._updateFocusTrackingMode));
this._settings.connect('changed::' + CARET_TRACKING_KEY,
Lang.bind(this, this._updateCaretTrackingMode));
this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY, this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY,
Lang.bind(this, this._updateInvertLightness)); Lang.bind(this, this._updateInvertLightness));
@ -573,6 +537,8 @@ const Magnifier = new Lang.Class({
Lang.bind(this, function() { Lang.bind(this, function() {
this.setCrosshairsClip(this._settings.get_boolean(CROSS_HAIRS_CLIP_KEY)); this.setCrosshairsClip(this._settings.get_boolean(CROSS_HAIRS_CLIP_KEY));
})); }));
return this._appSettings.get_boolean(SHOW_KEY);
}, },
_updateScreenPosition: function() { _updateScreenPosition: function() {
@ -619,24 +585,6 @@ const Magnifier = new Lang.Class({
} }
}, },
_updateFocusTrackingMode: function() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
this._zoomRegions[0].setFocusTrackingMode(
this._settings.get_enum(FOCUS_TRACKING_KEY)
);
}
},
_updateCaretTrackingMode: function() {
// Applies only to the first zoom region.
if (this._zoomRegions.length) {
this._zoomRegions[0].setCaretTrackingMode(
this._settings.get_enum(CARET_TRACKING_KEY)
);
}
},
_updateInvertLightness: function() { _updateInvertLightness: function() {
// Applies only to the first zoom region. // Applies only to the first zoom region.
if (this._zoomRegions.length) { if (this._zoomRegions.length) {
@ -675,7 +623,7 @@ const Magnifier = new Lang.Class({
contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY); contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY);
this._zoomRegions[0].setContrast(contrast); this._zoomRegions[0].setContrast(contrast);
} }
} },
}); });
Signals.addSignalMethods(Magnifier.prototype); Signals.addSignalMethods(Magnifier.prototype);
@ -684,11 +632,8 @@ const ZoomRegion = new Lang.Class({
_init: function(magnifier, mouseSourceActor) { _init: function(magnifier, mouseSourceActor) {
this._magnifier = magnifier; this._magnifier = magnifier;
this._focusCaretTracker = new FocusCaretTracker.FocusCaretTracker();
this._mouseTrackingMode = GDesktopEnums.MagnifierMouseTrackingMode.NONE; this._mouseTrackingMode = GDesktopEnums.MagnifierMouseTrackingMode.NONE;
this._focusTrackingMode = GDesktopEnums.MagnifierFocusTrackingMode.NONE;
this._caretTrackingMode = GDesktopEnums.MagnifierCaretTrackingMode.NONE;
this._clampScrollingAtEdges = false; this._clampScrollingAtEdges = false;
this._lensMode = false; this._lensMode = false;
this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN; this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN;
@ -714,35 +659,9 @@ const ZoomRegion = new Lang.Class({
this._xMagFactor = 1; this._xMagFactor = 1;
this._yMagFactor = 1; this._yMagFactor = 1;
this._followingCursor = false; this._followingCursor = false;
this._xFocus = 0;
this._yFocus = 0;
this._xCaret = 0;
this._yCaret = 0;
Main.layoutManager.connect('monitors-changed', Main.layoutManager.connect('monitors-changed',
Lang.bind(this, this._monitorsChanged)); Lang.bind(this, this._monitorsChanged));
this._focusCaretTracker.connect('caret-moved',
Lang.bind(this, this._updateCaret));
this._focusCaretTracker.connect('focus-changed',
Lang.bind(this, this._updateFocus));
},
_updateFocus: function(caller, event) {
let component = event.source.get_component_iface();
if (!component || event.detail1 != 1)
return;
let extents = component.get_extents(Atspi.CoordType.SCREEN);
[this._xFocus, this._yFocus] = [extents.x, extents.y]
this._centerFromFocusPosition();
},
_updateCaret: function(caller, event) {
let text = event.source.get_text_iface();
if (!text)
return;
let extents = text.get_character_extents(text.get_caret_offset(), 0);
[this._xCaret, this._yCaret] = [extents.x, extents.y];
this._centerFromCaretPosition();
}, },
/** /**
@ -750,17 +669,14 @@ const ZoomRegion = new Lang.Class({
* @activate: Boolean to show/hide the ZoomRegion. * @activate: Boolean to show/hide the ZoomRegion.
*/ */
setActive: function(activate) { setActive: function(activate) {
if (activate == this.isActive()) if (activate && !this.isActive()) {
return;
if (activate) {
this._createActors(); this._createActors();
if (this._isMouseOverRegion()) if (this._isMouseOverRegion())
this._magnifier.hideSystemCursor(); this._magnifier.hideSystemCursor();
this._updateMagViewGeometry(); this._updateMagViewGeometry();
this._updateCloneGeometry(); this._updateCloneGeometry();
this._updateMousePosition(); this._updateMousePosition();
} else { } else if (!activate && this.isActive()) {
this._destroyActors(); this._destroyActors();
} }
}, },
@ -816,30 +732,6 @@ const ZoomRegion = new Lang.Class({
return this._mouseTrackingMode; return this._mouseTrackingMode;
}, },
/**
* setFocusTrackingMode
* @mode: One of the enum FocusTrackingMode values.
*/
setFocusTrackingMode: function(mode) {
this._focusTrackingMode = mode;
if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.NONE)
this._focusCaretTracker.deregisterFocusListener();
else
this._focusCaretTracker.registerFocusListener();
},
/**
* setCaretTrackingMode
* @mode: One of the enum CaretTrackingMode values.
*/
setCaretTrackingMode: function(mode) {
this._caretTrackingMode = mode;
if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.NONE)
this._focusCaretTracker.deregisterCaretListener();
else
this._focusCaretTracker.registerCaretListener();
},
/** /**
* setViewPort * setViewPort
* Sets the position and size of the ZoomRegion on screen. * Sets the position and size of the ZoomRegion on screen.
@ -1131,6 +1023,20 @@ const ZoomRegion = new Lang.Class({
this._magShaderEffects.setBrightness(this._brightness); 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: * setContrast:
* Alter the contrast of the magnified view. * Alter the contrast of the magnified view.
@ -1337,47 +1243,19 @@ const ZoomRegion = new Lang.Class({
let yMouse = this._magnifier.yMouse; let yMouse = this._magnifier.yMouse;
if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) { if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) {
return this._centerFromPointProportional(xMouse, yMouse); return this._centerFromMouseProportional(xMouse, yMouse);
} }
else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) { else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) {
return this._centerFromPointPush(xMouse, yMouse); return this._centerFromMousePush(xMouse, yMouse);
} }
else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) { else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) {
return this._centerFromPointCentered(xMouse, yMouse); return this._centerFromMouseCentered(xMouse, yMouse);
} }
return null; // Should never be hit return null; // Should never be hit
}, },
_centerFromCaretPosition: function() { _centerFromMousePush: function(xMouse, yMouse) {
let xCaret = this._xCaret;
let yCaret = this._yCaret;
if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PROPORTIONAL)
[xCaret, yCaret] = this._centerFromPointProportional(xCaret, yCaret);
else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PUSH)
[xCaret, yCaret] = this._centerFromPointPush(xCaret, yCaret);
else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.CENTERED)
[xCaret, yCaret] = this._centerFromPointCentered(xCaret, yCaret);
this.scrollContentsTo(xCaret, yCaret);
},
_centerFromFocusPosition: function() {
let xFocus = this._xFocus;
let yFocus = this._yFocus;
if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PROPORTIONAL)
[xFocus, yFocus] = this._centerFromPointProportional(xFocus, yFocus);
else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PUSH)
[xFocus, yFocus] = this._centerFromPointPush(xFocus, yFocus);
else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.CENTERED)
[xFocus, yFocus] = this._centerFromPointCentered(xFocus, yFocus);
this.scrollContentsTo(xFocus, yFocus);
},
_centerFromPointPush: function(xPoint, yPoint) {
let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI(); let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
let [cursorWidth, cursorHeight] = this._mouseSourceActor.get_size(); let [cursorWidth, cursorHeight] = this._mouseSourceActor.get_size();
let xPos = xRoi + widthRoi / 2; let xPos = xRoi + widthRoi / 2;
@ -1385,20 +1263,20 @@ const ZoomRegion = new Lang.Class({
let xRoiRight = xRoi + widthRoi - cursorWidth; let xRoiRight = xRoi + widthRoi - cursorWidth;
let yRoiBottom = yRoi + heightRoi - cursorHeight; let yRoiBottom = yRoi + heightRoi - cursorHeight;
if (xPoint < xRoi) if (xMouse < xRoi)
xPos -= (xRoi - xPoint); xPos -= (xRoi - xMouse);
else if (xPoint > xRoiRight) else if (xMouse > xRoiRight)
xPos += (xPoint - xRoiRight); xPos += (xMouse - xRoiRight);
if (yPoint < yRoi) if (yMouse < yRoi)
yPos -= (yRoi - yPoint); yPos -= (yRoi - yMouse);
else if (yPoint > yRoiBottom) else if (yMouse > yRoiBottom)
yPos += (yPoint - yRoiBottom); yPos += (yMouse - yRoiBottom);
return [xPos, yPos]; return [xPos, yPos];
}, },
_centerFromPointProportional: function(xPoint, yPoint) { _centerFromMouseProportional: function(xMouse, yMouse) {
let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI(); let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
let halfScreenWidth = global.screen_width / 2; let halfScreenWidth = global.screen_width / 2;
let halfScreenHeight = global.screen_height / 2; let halfScreenHeight = global.screen_height / 2;
@ -1407,16 +1285,16 @@ const ZoomRegion = new Lang.Class({
let unscaledPadding = Math.min(this._viewPortWidth, this._viewPortHeight) / 5; let unscaledPadding = Math.min(this._viewPortWidth, this._viewPortHeight) / 5;
let xPadding = unscaledPadding / this._xMagFactor; let xPadding = unscaledPadding / this._xMagFactor;
let yPadding = unscaledPadding / this._yMagFactor; let yPadding = unscaledPadding / this._yMagFactor;
let xProportion = (xPoint - halfScreenWidth) / halfScreenWidth; // -1 ... 1 let xProportion = (xMouse - halfScreenWidth) / halfScreenWidth; // -1 ... 1
let yProportion = (yPoint - halfScreenHeight) / halfScreenHeight; // -1 ... 1 let yProportion = (yMouse - halfScreenHeight) / halfScreenHeight; // -1 ... 1
let xPos = xPoint - xProportion * (widthRoi / 2 - xPadding); let xPos = xMouse - xProportion * (widthRoi / 2 - xPadding);
let yPos = yPoint - yProportion * (heightRoi /2 - yPadding); let yPos = yMouse - yProportion * (heightRoi /2 - yPadding);
return [xPos, yPos]; return [xPos, yPos];
}, },
_centerFromPointCentered: function(xPoint, yPoint) { _centerFromMouseCentered: function(xMouse, yMouse) {
return [xPoint, yPoint]; return [xMouse, yMouse];
}, },
_screenToViewPort: function(screenX, screenY) { _screenToViewPort: function(screenX, screenY) {
@ -1633,6 +1511,15 @@ const Crosshairs = new Lang.Class({
this._vertBottomHair.set_opacity(opacity); this._vertBottomHair.set_opacity(opacity);
}, },
/**
* getOpacity:
* Retriev how opaque the crosshairs are.
* @return: A value between 0 (transparent) and 255 (opaque).
*/
getOpacity: function() {
return this._horizLeftHair.get_opacity();
},
/** /**
* setLength: * setLength:
* Set the length of the vertical and horizontal lines in the crosshairs. * Set the length of the vertical and horizontal lines in the crosshairs.
@ -1676,6 +1563,15 @@ const Crosshairs = new Lang.Class({
} }
}, },
/**
* getClip:
* Get the dimensions of the clip rectangle.
* @return: An array of the form [width, height].
*/
getClip: function() {
return this._clipSize;
},
/** /**
* show: * show:
* Show the crosshairs. * Show the crosshairs.
@ -1771,10 +1667,23 @@ const MagShaderEffects = new Lang.Class({
this._inverse.set_enabled(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) { setColorSaturation: function(factor) {
this._colorDesaturation.set_factor(1.0 - factor); this._colorDesaturation.set_factor(1.0 - factor);
}, },
getColorSaturation: function() {
return 1.0 - this._colorDesaturation.get_factor();
},
/** /**
* setBrightness: * setBrightness:
* Set the brightness of the magnified view. * Set the brightness of the magnified view.
@ -1799,6 +1708,24 @@ const MagShaderEffects = new Lang.Class({
); );
}, },
/**
* 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. * Set the contrast of the magnified view.
* @contrast: Object containing the contrast for the red, green, * @contrast: Object containing the contrast for the red, green,
@ -1823,4 +1750,21 @@ const MagShaderEffects = new Lang.Class({
bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != 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

@ -4,12 +4,14 @@ const Gio = imports.gi.Gio;
const Lang = imports.lang; const Lang = imports.lang;
const Main = imports.ui.main; const Main = imports.ui.main;
const MAG_SERVICE_NAME = 'org.gnome.Magnifier';
const MAG_SERVICE_PATH = '/org/gnome/Magnifier'; const MAG_SERVICE_PATH = '/org/gnome/Magnifier';
const ZOOM_SERVICE_NAME = 'org.gnome.Magnifier.ZoomRegion';
const ZOOM_SERVICE_PATH = '/org/gnome/Magnifier/ZoomRegion'; const ZOOM_SERVICE_PATH = '/org/gnome/Magnifier/ZoomRegion';
// Subset of gnome-mag's Magnifier dbus interface -- to be expanded. See: // Subset of gnome-mag's Magnifier dbus interface -- to be expanded. See:
// http://git.gnome.org/browse/gnome-mag/tree/xml/...Magnifier.xml // http://git.gnome.org/browse/gnome-mag/tree/xml/...Magnifier.xml
const MagnifierIface = <interface name="org.gnome.Magnifier"> const MagnifierIface = <interface name={MAG_SERVICE_NAME}>
<method name="setActive"> <method name="setActive">
<arg type="b" direction="in" /> <arg type="b" direction="in" />
</method> </method>
@ -64,7 +66,7 @@ const MagnifierIface = <interface name="org.gnome.Magnifier">
// Subset of gnome-mag's ZoomRegion dbus interface -- to be expanded. See: // Subset of gnome-mag's ZoomRegion dbus interface -- to be expanded. See:
// http://git.gnome.org/browse/gnome-mag/tree/xml/...ZoomRegion.xml // http://git.gnome.org/browse/gnome-mag/tree/xml/...ZoomRegion.xml
const ZoomRegionIface = <interface name="org.gnome.Magnifier.ZoomRegion"> const ZoomRegionIface = <interface name={ZOOM_SERVICE_NAME}>
<method name="setMagFactor"> <method name="setMagFactor">
<arg type="d" direction="in" /> <arg type="d" direction="in" />
<arg type="d" direction="in" /> <arg type="d" direction="in" />

View File

@ -28,7 +28,6 @@ const LoginManager = imports.misc.loginManager;
const LookingGlass = imports.ui.lookingGlass; const LookingGlass = imports.ui.lookingGlass;
const NotificationDaemon = imports.ui.notificationDaemon; const NotificationDaemon = imports.ui.notificationDaemon;
const WindowAttentionHandler = imports.ui.windowAttentionHandler; const WindowAttentionHandler = imports.ui.windowAttentionHandler;
const Screencast = imports.ui.screencast;
const ScreenShield = imports.ui.screenShield; const ScreenShield = imports.ui.screenShield;
const Scripting = imports.ui.scripting; const Scripting = imports.ui.scripting;
const SessionMode = imports.ui.sessionMode; const SessionMode = imports.ui.sessionMode;
@ -39,11 +38,9 @@ const Magnifier = imports.ui.magnifier;
const XdndHandler = imports.ui.xdndHandler; const XdndHandler = imports.ui.xdndHandler;
const Util = imports.misc.util; const Util = imports.misc.util;
const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides';
const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff); const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
const STICKY_KEYS_ENABLE = 'stickykeys-enable';
let componentManager = null; let componentManager = null;
let panel = null; let panel = null;
let overview = null; let overview = null;
@ -60,7 +57,6 @@ let sessionMode = null;
let shellDBusService = null; let shellDBusService = null;
let shellMountOpDBusService = null; let shellMountOpDBusService = null;
let screenSaverDBus = null; let screenSaverDBus = null;
let screencastService = null;
let modalCount = 0; let modalCount = 0;
let keybindingMode = Shell.KeyBindingMode.NONE; let keybindingMode = Shell.KeyBindingMode.NONE;
let modalActorFocusStack = []; let modalActorFocusStack = [];
@ -72,8 +68,7 @@ let layoutManager = null;
let _startDate; let _startDate;
let _defaultCssStylesheet = null; let _defaultCssStylesheet = null;
let _cssStylesheet = null; let _cssStylesheet = null;
let _a11ySettings = null; let _overridesSettings = null;
let dynamicWorkspacesSchema = null;
function _sessionUpdated() { function _sessionUpdated() {
_loadDefaultStylesheet(); _loadDefaultStylesheet();
@ -90,12 +85,8 @@ function _sessionUpdated() {
Shell.KeyBindingMode.OVERVIEW, Shell.KeyBindingMode.OVERVIEW,
sessionMode.hasRunDialog ? openRunDialog : null); sessionMode.hasRunDialog ? openRunDialog : null);
if (!sessionMode.hasRunDialog) { if (!sessionMode.hasRunDialog && lookingGlass)
if (runDialog) lookingGlass.close();
runDialog.close();
if (lookingGlass)
lookingGlass.close();
}
} }
function start() { function start() {
@ -103,9 +94,6 @@ function start() {
global.logError = window.log; global.logError = window.log;
global.log = window.log; global.log = window.log;
if (!Meta.is_wayland_compositor)
Meta.is_wayland_compositor = function () { return false; };
// Chain up async errors reported from C // Chain up async errors reported from C
global.connect('notify-error', function (global, msg, detail) { notifyError(msg, detail); }); global.connect('notify-error', function (global, msg, detail) { notifyError(msg, detail); });
@ -118,7 +106,6 @@ function start() {
function _sessionsLoaded() { function _sessionsLoaded() {
sessionMode.connect('updated', _sessionUpdated); sessionMode.connect('updated', _sessionUpdated);
_initializePrefs();
_initializeUI(); _initializeUI();
shellDBusService = new ShellDBus.GnomeShell(); shellDBusService = new ShellDBus.GnomeShell();
@ -127,17 +114,6 @@ function _sessionsLoaded() {
_sessionUpdated(); _sessionUpdated();
} }
function _initializePrefs() {
let keys = new Gio.Settings({ schema: sessionMode.overridesSchema }).list_keys();
for (let i = 0; i < keys.length; i++)
Meta.prefs_override_preference_schema(keys[i], sessionMode.overridesSchema);
if (keys.indexOf('dynamic-workspaces') > -1)
dynamicWorkspacesSchema = sessionMode.overridesSchema;
else
dynamicWorkspacesSchema = 'org.gnome.mutter';
}
function _initializeUI() { function _initializeUI() {
// 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
@ -147,9 +123,11 @@ function _initializeUI() {
// and recalculate application associations, so to avoid // and recalculate application associations, so to avoid
// races for now we initialize it here. It's better to // races for now we initialize it here. It's better to
// be predictable anyways. // be predictable anyways.
Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
Shell.AppUsage.get_default(); Shell.AppUsage.get_default();
tracker.connect('startup-sequence-changed', _queueCheckWorkspaces);
_loadDefaultStylesheet(); _loadDefaultStylesheet();
// Setup the stage hierarchy early // Setup the stage hierarchy early
@ -160,7 +138,6 @@ function _initializeUI() {
// working until it's updated. // working until it's updated.
uiGroup = layoutManager.uiGroup; uiGroup = layoutManager.uiGroup;
screencastService = new Screencast.ScreencastService();
xdndHandler = new XdndHandler.XdndHandler(); xdndHandler = new XdndHandler.XdndHandler();
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
osdWindow = new OsdWindow.OsdWindow(); osdWindow = new OsdWindow.OsdWindow();
@ -180,12 +157,9 @@ function _initializeUI() {
layoutManager.init(); layoutManager.init();
overview.init(); overview.init();
_a11ySettings = new Gio.Settings({ schema: A11Y_SCHEMA }); global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT,
false, -1, 1);
global.display.connect('overlay-key', Lang.bind(overview, function () { global.display.connect('overlay-key', Lang.bind(overview, overview.toggle));
if (!_a11ySettings.get_boolean (STICKY_KEYS_ENABLE))
overview.toggle();
}));
// Provide the bus object for gnome-session to // Provide the bus object for gnome-session to
// initiate logouts. // initiate logouts.
@ -205,6 +179,17 @@ function _initializeUI() {
Scripting.runPerfScript(module, perfOutput); Scripting.runPerfScript(module, perfOutput);
} }
_overridesSettings = new Gio.Settings({ schema: OVERRIDES_SCHEMA });
_overridesSettings.connect('changed::dynamic-workspaces', _queueCheckWorkspaces);
global.screen.connect('notify::n-workspaces', _nWorkspacesChanged);
global.screen.connect('window-entered-monitor', _windowEnteredMonitor);
global.screen.connect('window-left-monitor', _windowLeftMonitor);
global.screen.connect('restacked', _windowsRestacked);
_nWorkspacesChanged();
ExtensionDownloader.init(); ExtensionDownloader.init();
ExtensionSystem.init(); ExtensionSystem.init();
@ -218,12 +203,190 @@ function _initializeUI() {
if (keybindingMode == Shell.KeyBindingMode.NONE) { if (keybindingMode == Shell.KeyBindingMode.NONE) {
keybindingMode = Shell.KeyBindingMode.NORMAL; keybindingMode = Shell.KeyBindingMode.NORMAL;
} }
if (screenShield) {
screenShield.lockIfWasLocked();
}
}); });
} }
let _workspaces = [];
let _checkWorkspacesId = 0;
/*
* When the last window closed on a workspace is a dialog or splash
* screen, we assume that it might be an initial window shown before
* the main window of an application, and give the app a grace period
* where it can map another window before we remove the workspace.
*/
const LAST_WINDOW_GRACE_TIME = 1000;
function _checkWorkspaces() {
let i;
let emptyWorkspaces = [];
if (!Meta.prefs_get_dynamic_workspaces()) {
_checkWorkspacesId = 0;
return false;
}
for (i = 0; i < _workspaces.length; i++) {
let lastRemoved = _workspaces[i]._lastRemovedWindow;
if ((lastRemoved &&
(lastRemoved.get_window_type() == Meta.WindowType.SPLASHSCREEN ||
lastRemoved.get_window_type() == Meta.WindowType.DIALOG ||
lastRemoved.get_window_type() == Meta.WindowType.MODAL_DIALOG)) ||
_workspaces[i]._keepAliveId)
emptyWorkspaces[i] = false;
else
emptyWorkspaces[i] = true;
}
let sequences = Shell.WindowTracker.get_default().get_startup_sequences();
for (i = 0; i < sequences.length; i++) {
let index = sequences[i].get_workspace();
if (index >= 0 && index <= global.screen.n_workspaces)
emptyWorkspaces[index] = false;
}
let windows = global.get_window_actors();
for (i = 0; i < windows.length; i++) {
let win = windows[i];
if (win.get_meta_window().is_on_all_workspaces())
continue;
let workspaceIndex = win.get_workspace();
emptyWorkspaces[workspaceIndex] = false;
}
// If we don't have an empty workspace at the end, add one
if (!emptyWorkspaces[emptyWorkspaces.length -1]) {
global.screen.append_new_workspace(false, global.get_current_time());
emptyWorkspaces.push(false);
}
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let removingCurrentWorkspace = (emptyWorkspaces[activeWorkspaceIndex] &&
activeWorkspaceIndex < emptyWorkspaces.length - 1);
// Don't enter the overview when removing multiple empty workspaces at startup
let showOverview = (removingCurrentWorkspace &&
!emptyWorkspaces.every(function(x) { return x; }));
if (removingCurrentWorkspace) {
// "Merge" the empty workspace we are removing with the one at the end
wm.blockAnimations();
}
// Delete other empty workspaces; do it from the end to avoid index changes
for (i = emptyWorkspaces.length - 2; i >= 0; i--) {
if (emptyWorkspaces[i])
global.screen.remove_workspace(_workspaces[i], global.get_current_time());
}
if (removingCurrentWorkspace) {
global.screen.get_workspace_by_index(global.screen.n_workspaces - 1).activate(global.get_current_time());
wm.unblockAnimations();
if (!overview.visible && showOverview)
overview.show();
}
_checkWorkspacesId = 0;
return false;
}
function keepWorkspaceAlive(workspace, duration) {
if (workspace._keepAliveId)
Mainloop.source_remove(workspace._keepAliveId);
workspace._keepAliveId = Mainloop.timeout_add(duration, function() {
workspace._keepAliveId = 0;
_queueCheckWorkspaces();
return false;
});
}
function _windowRemoved(workspace, window) {
workspace._lastRemovedWindow = window;
_queueCheckWorkspaces();
Mainloop.timeout_add(LAST_WINDOW_GRACE_TIME, function() {
if (workspace._lastRemovedWindow == window) {
workspace._lastRemovedWindow = null;
_queueCheckWorkspaces();
}
return false;
});
}
function _windowLeftMonitor(metaScreen, monitorIndex, metaWin) {
// If the window left the primary monitor, that
// might make that workspace empty
if (monitorIndex == layoutManager.primaryIndex)
_queueCheckWorkspaces();
}
function _windowEnteredMonitor(metaScreen, monitorIndex, metaWin) {
// If the window entered the primary monitor, that
// might make that workspace non-empty
if (monitorIndex == layoutManager.primaryIndex)
_queueCheckWorkspaces();
}
function _windowsRestacked() {
// Figure out where the pointer is in case we lost track of
// it during a grab. (In particular, if a trayicon popup menu
// is dismissed, see if we need to close the message tray.)
global.sync_pointer();
}
function _queueCheckWorkspaces() {
if (_checkWorkspacesId == 0)
_checkWorkspacesId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, _checkWorkspaces);
}
function _nWorkspacesChanged() {
let oldNumWorkspaces = _workspaces.length;
let newNumWorkspaces = global.screen.n_workspaces;
if (oldNumWorkspaces == newNumWorkspaces)
return false;
let lostWorkspaces = [];
if (newNumWorkspaces > oldNumWorkspaces) {
let w;
// Assume workspaces are only added at the end
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++)
_workspaces[w] = global.screen.get_workspace_by_index(w);
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
let workspace = _workspaces[w];
workspace._windowAddedId = workspace.connect('window-added', _queueCheckWorkspaces);
workspace._windowRemovedId = workspace.connect('window-removed', _windowRemoved);
}
} else {
// Assume workspaces are only removed sequentially
// (e.g. 2,3,4 - not 2,4,7)
let removedIndex;
let removedNum = oldNumWorkspaces - newNumWorkspaces;
for (let w = 0; w < oldNumWorkspaces; w++) {
let workspace = global.screen.get_workspace_by_index(w);
if (_workspaces[w] != workspace) {
removedIndex = w;
break;
}
}
let lostWorkspaces = _workspaces.splice(removedIndex, removedNum);
lostWorkspaces.forEach(function(workspace) {
workspace.disconnect(workspace._windowAddedId);
workspace.disconnect(workspace._windowRemovedId);
});
}
_queueCheckWorkspaces();
return false;
}
function _loadDefaultStylesheet() { function _loadDefaultStylesheet() {
if (!sessionMode.isPrimary) if (!sessionMode.isPrimary)
return; return;
@ -270,8 +433,11 @@ function loadTheme() {
let themeContext = St.ThemeContext.get_for_stage (global.stage); let themeContext = St.ThemeContext.get_for_stage (global.stage);
let previousTheme = themeContext.get_theme(); let previousTheme = themeContext.get_theme();
let theme = new St.Theme ({ application_stylesheet: _cssStylesheet, let cssStylesheet = _defaultCssStylesheet;
default_stylesheet: _defaultCssStylesheet }); if (_cssStylesheet != null)
cssStylesheet = _cssStylesheet;
let theme = new St.Theme ({ application_stylesheet: cssStylesheet });
if (previousTheme) { if (previousTheme) {
let customStylesheets = previousTheme.get_custom_stylesheets(); let customStylesheets = previousTheme.get_custom_stylesheets();
@ -362,6 +528,8 @@ function pushModal(actor, params) {
Meta.disable_unredirect_for_screen(global.screen); Meta.disable_unredirect_for_screen(global.screen);
} }
global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN);
modalCount += 1; modalCount += 1;
let actorDestroyId = actor.connect('destroy', function() { let actorDestroyId = actor.connect('destroy', function() {
let index = _findModal(actor); let index = _findModal(actor);
@ -410,6 +578,7 @@ function popModal(actor, timestamp) {
if (focusIndex < 0) { if (focusIndex < 0) {
global.stage.set_key_focus(null); global.stage.set_key_focus(null);
global.end_modal(timestamp); global.end_modal(timestamp);
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
keybindingMode = Shell.KeyBindingMode.NORMAL; keybindingMode = Shell.KeyBindingMode.NORMAL;
throw new Error('incorrect pop'); throw new Error('incorrect pop');
@ -457,6 +626,7 @@ function popModal(actor, timestamp) {
return; return;
global.end_modal(timestamp); global.end_modal(timestamp);
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
Meta.enable_unredirect_for_screen(global.screen); Meta.enable_unredirect_for_screen(global.screen);
keybindingMode = Shell.KeyBindingMode.NORMAL; keybindingMode = Shell.KeyBindingMode.NORMAL;
} }

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 Animation = imports.ui.animation;
const Layout = imports.ui.layout; 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;
@ -23,10 +22,6 @@ const Tweener = imports.ui.tweener;
const OPEN_AND_CLOSE_TIME = 0.1; const OPEN_AND_CLOSE_TIME = 0.1;
const FADE_OUT_DIALOG_TIME = 1.0; const FADE_OUT_DIALOG_TIME = 1.0;
const WORK_SPINNER_ICON_SIZE = 24;
const WORK_SPINNER_ANIMATION_DELAY = 1.0;
const WORK_SPINNER_ANIMATION_TIME = 0.3;
const State = { const State = {
OPENED: 0, OPENED: 0,
CLOSED: 1, CLOSED: 1,
@ -70,38 +65,36 @@ const ModalDialog = new Lang.Class({
this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
this._group.connect('key-release-event', Lang.bind(this, this._onKeyReleaseEvent)); this._group.connect('key-release-event', Lang.bind(this, this._onKeyReleaseEvent));
this.backgroundStack = new St.Widget({ layout_manager: new Clutter.BinLayout() }); this._backgroundBin = new St.Bin();
this._backgroundBin = new St.Bin({ child: this.backgroundStack,
x_fill: true, y_fill: true });
this._monitorConstraint = new Layout.MonitorConstraint(); this._monitorConstraint = new Layout.MonitorConstraint();
this._backgroundBin.add_constraint(this._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 });
// modal dialogs are fixed width and grow vertically; set the request if (params.styleClass != null) {
// mode accordingly so wrapped labels are handled correctly during
// size requests.
this.dialogLayout.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
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) {
this._lightbox = new Lightbox.Lightbox(this._group, this._lightbox = new Lightbox.Lightbox(this._group,
{ inhibitEvents: true }); { inhibitEvents: true });
this._lightbox.highlight(this._backgroundBin); this._lightbox.highlight(this._backgroundBin);
let stack = new Shell.Stack();
this._backgroundBin.child = stack;
this._eventBlocker = new Clutter.Actor({ reactive: true }); this._eventBlocker = new Clutter.Actor({ reactive: true });
this.backgroundStack.add_actor(this._eventBlocker); stack.add_actor(this._eventBlocker);
stack.add_actor(this.dialogLayout);
} else {
this._backgroundBin.child = this.dialogLayout;
} }
this.backgroundStack.add_actor(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,
{ expand: true, { 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 });
@ -109,15 +102,14 @@ const ModalDialog = new Lang.Class({
this.buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box', this.buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box',
vertical: false }); vertical: false });
this.dialogLayout.add(this.buttonLayout, this.dialogLayout.add(this.buttonLayout,
{ x_align: St.Align.MIDDLE, { expand: true,
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;
this._workSpinner = null;
}, },
destroy: function() { destroy: function() {
@ -191,42 +183,6 @@ const ModalDialog = new Lang.Class({
return button; return button;
}, },
placeSpinner: function(layoutInfo) {
let spinnerIcon = global.datadir + '/theme/process-working.svg';
this._workSpinner = new Animation.AnimatedIcon(spinnerIcon, WORK_SPINNER_ICON_SIZE);
this._workSpinner.actor.opacity = 0;
this._workSpinner.actor.show();
this.buttonLayout.add(this._workSpinner.actor, layoutInfo);
},
setWorking: function(working) {
if (!this._workSpinner)
return;
Tweener.removeTweens(this._workSpinner.actor);
if (working) {
this._workSpinner.play();
Tweener.addTween(this._workSpinner.actor,
{ opacity: 255,
delay: WORK_SPINNER_ANIMATION_DELAY,
time: WORK_SPINNER_ANIMATION_TIME,
transition: 'linear'
});
} else {
Tweener.addTween(this._workSpinner.actor,
{ opacity: 0,
time: WORK_SPINNER_ANIMATION_TIME,
transition: 'linear',
onCompleteScope: this,
onComplete: function() {
if (this._workSpinner)
this._workSpinner.stop();
}
});
}
},
_onKeyPressEvent: function(object, event) { _onKeyPressEvent: function(object, event) {
this._pressedKey = event.get_key_symbol(); this._pressedKey = event.get_key_symbol();
}, },
@ -361,9 +317,8 @@ const ModalDialog = new Lang.Class({
if (this._savedKeyFocus) { if (this._savedKeyFocus) {
this._savedKeyFocus.grab_key_focus(); this._savedKeyFocus.grab_key_focus();
this._savedKeyFocus = null; this._savedKeyFocus = null;
} else { } else
this._initialKeyFocus.grab_key_focus(); this._initialKeyFocus.grab_key_focus();
}
if (!this._shellReactive) if (!this._shellReactive)
this._eventBlocker.lower_bottom(); this._eventBlocker.lower_bottom();

View File

@ -103,6 +103,126 @@ const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
'ibus-ui-gtk': 'keyboard' 'ibus-ui-gtk': 'keyboard'
}; };
const NotificationGenericPolicy = new Lang.Class({
Name: 'NotificationGenericPolicy',
Extends: MessageTray.NotificationPolicy,
_init: function() {
// Don't chain to parent, it would try setting
// our properties to the defaults
this.id = 'generic';
this._masterSettings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications' });
this._masterSettings.connect('changed', Lang.bind(this, this._changed));
},
store: function() { },
destroy: function() {
this._masterSettings.run_dispose();
},
_changed: function(settings, key) {
this.emit('policy-changed', key);
},
get enable() {
return true;
},
get enableSound() {
return true;
},
get showBanners() {
return this._masterSettings.get_boolean('show-banners');
},
get forceExpanded() {
return false;
},
get showInLockScreen() {
return this._masterSettings.get_boolean('show-in-lock-screen');
},
get detailsInLockScreen() {
return false;
}
});
const NotificationApplicationPolicy = new Lang.Class({
Name: 'NotificationApplicationPolicy',
Extends: MessageTray.NotificationPolicy,
_init: function(id) {
// Don't chain to parent, it would try setting
// our properties to the defaults
this.id = id;
this._canonicalId = this._canonicalizeId(id)
this._masterSettings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications' });
this._settings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications.application',
path: '/org/gnome/desktop/notifications/application/' + this._canonicalId + '/' });
this._masterSettings.connect('changed', Lang.bind(this, this._changed));
this._settings.connect('changed', Lang.bind(this, this._changed));
},
store: function() {
this._settings.set_string('application-id', this.id + '.desktop');
let apps = this._masterSettings.get_strv('application-children');
if (apps.indexOf(this._canonicalId) < 0) {
apps.push(this._canonicalId);
this._masterSettings.set_strv('application-children', apps);
}
},
destroy: function() {
this._masterSettings.run_dispose();
this._settings.run_dispose();
},
_changed: function(settings, key) {
this.emit('policy-changed', key);
},
_canonicalizeId: function(id) {
// Keys are restricted to lowercase alphanumeric characters and dash,
// and two dashes cannot be in succession
return id.toLowerCase().replace(/[^a-z0-9\-]/g, '-').replace(/--+/g, '-');
},
get enable() {
return this._settings.get_boolean('enable');
},
get enableSound() {
return this._settings.get_boolean('enable-sound-alerts');
},
get showBanners() {
return this._masterSettings.get_boolean('show-banners') &&
this._settings.get_boolean('show-banners');
},
get forceExpanded() {
return this._settings.get_boolean('force-expanded');
},
get showInLockScreen() {
return this._masterSettings.get_boolean('show-in-lock-screen') &&
this._settings.get_boolean('show-in-lock-screen');
},
get detailsInLockScreen() {
return this._settings.get_boolean('details-in-lock-screen');
}
});
const NotificationDaemon = new Lang.Class({ const NotificationDaemon = new Lang.Class({
Name: 'NotificationDaemon', Name: 'NotificationDaemon',
@ -276,13 +396,12 @@ const NotificationDaemon = new Lang.Class({
if (!hints['image-path'] && hints['image_path']) if (!hints['image-path'] && hints['image_path'])
hints['image-path'] = hints['image_path']; // version 1.1 of the spec hints['image-path'] = hints['image_path']; // version 1.1 of the spec
if (!hints['image-data']) { if (!hints['image-data'])
if (hints['image_data']) if (hints['image_data'])
hints['image-data'] = hints['image_data']; // version 1.1 of the spec hints['image-data'] = hints['image_data']; // version 1.1 of the spec
else if (hints['icon_data'] && !hints['image-path']) else if (hints['icon_data'] && !hints['image-path'])
// early versions of the spec; 'icon_data' should only be used if 'image-path' is not available // early versions of the spec; 'icon_data' should only be used if 'image-path' is not available
hints['image-data'] = hints['icon_data']; hints['image-data'] = hints['icon_data'];
}
let ndata = { appName: appName, let ndata = { appName: appName,
icon: icon, icon: icon,
@ -555,9 +674,9 @@ const Source = new Lang.Class({
_createPolicy: function() { _createPolicy: function() {
if (this.app) { if (this.app) {
let id = this.app.get_id().replace(/\.desktop$/,''); let id = this.app.get_id().replace(/\.desktop$/,'');
return new MessageTray.NotificationApplicationPolicy(id); return new NotificationApplicationPolicy(id);
} else { } else {
return new MessageTray.NotificationGenericPolicy(); return new NotificationGenericPolicy();
} }
}, },
@ -598,8 +717,8 @@ const Source = new Lang.Class({
this.notifications.length > 0) this.notifications.length > 0)
return false; return false;
let id = global.stage.connect('deactivate', Lang.bind(this, function () { let id = global.connect('notify::stage-input-mode', Lang.bind(this, function () {
global.stage.disconnect(id); global.disconnect(id);
this.trayIcon.click(event); this.trayIcon.click(event);
})); }));
@ -615,11 +734,7 @@ const Source = new Lang.Class({
return app; return app;
if (this.trayIcon) { if (this.trayIcon) {
app = Shell.AppSystem.get_default().lookup_startup_wmclass(this.trayIcon.wm_class); app = Shell.AppSystem.get_default().lookup_wmclass(this.trayIcon.wm_class);
if (app != null)
return app;
app = Shell.AppSystem.get_default().lookup_desktop_wmclass(this.trayIcon.wm_class);
if (app != null) if (app != null)
return app; return app;
} }
@ -633,6 +748,22 @@ const Source = new Lang.Class({
return null; return null;
}, },
_setApp: function(appId) {
if (this.app)
return;
this.app = this._getApp(appId);
if (!this.app)
return;
// Only override the icon if we were previously using
// notification-based icons (ie, not a trayicon) or if it was unset before
if (!this.trayIcon) {
this.useNotificationIcon = false;
this.iconUpdated();
}
},
setTitle: function(title) { setTitle: function(title) {
// Do nothing if .app is set, we don't want to override the // Do nothing if .app is set, we don't want to override the
// app name with whatever is provided through libnotify (usually // app name with whatever is provided through libnotify (usually
@ -644,8 +775,8 @@ const Source = new Lang.Class({
}, },
open: function(notification) { open: function(notification) {
this.openApp();
this.destroyNonResidentNotifications(); this.destroyNonResidentNotifications();
this.openApp();
}, },
_lastNotificationRemoved: function() { _lastNotificationRemoved: function() {
@ -657,8 +788,11 @@ const Source = new Lang.Class({
if (this.app == null) if (this.app == null)
return; return;
this.app.activate(); let windows = this.app.get_windows();
Main.overview.hide(); if (windows.length > 0) {
let mostRecentWindow = windows[0];
Main.activateWindow(mostRecentWindow);
}
}, },
destroy: function() { destroy: function() {

View File

@ -8,7 +8,6 @@ const Layout = imports.ui.layout;
const Main = imports.ui.main; const Main = imports.ui.main;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const Meta = imports.gi.Meta;
const HIDE_TIMEOUT = 1500; const HIDE_TIMEOUT = 1500;
const FADE_TIME = 0.1; const FADE_TIME = 0.1;
@ -72,7 +71,6 @@ const OsdWindow = new Lang.Class({
Name: 'OsdWindow', Name: 'OsdWindow',
_init: function() { _init: function() {
this._popupSize = 0;
this.actor = new St.Widget({ x_expand: true, this.actor = new St.Widget({ x_expand: true,
y_expand: true, y_expand: true,
x_align: Clutter.ActorAlign.CENTER, x_align: Clutter.ActorAlign.CENTER,
@ -82,15 +80,6 @@ const OsdWindow = new Lang.Class({
vertical: true }); vertical: true });
this.actor.add_actor(this._box); this.actor.add_actor(this._box);
this._box.connect('style-changed', Lang.bind(this, this._onStyleChanged));
this._box.connect('notify::height', Lang.bind(this,
function() {
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
function() {
this._box.width = this._box.height;
}));
}));
this._icon = new St.Icon(); this._icon = new St.Icon();
this._box.add(this._icon, { expand: true }); this._box.add(this._icon, { expand: true });
@ -107,7 +96,7 @@ const OsdWindow = new Lang.Class({
Lang.bind(this, this._monitorsChanged)); Lang.bind(this, this._monitorsChanged));
this._monitorsChanged(); this._monitorsChanged();
Main.uiGroup.add_child(this.actor); Main.layoutManager.addChrome(this.actor, { affectsInputRegion: false });
}, },
setIcon: function(icon) { setIcon: function(icon) {
@ -136,10 +125,8 @@ const OsdWindow = new Lang.Class({
return; return;
if (!this.actor.visible) { if (!this.actor.visible) {
Meta.disable_unredirect_for_screen(global.screen);
this.actor.show(); this.actor.show();
this.actor.opacity = 0; this.actor.opacity = 0;
this.actor.get_parent().set_child_above_sibling(this.actor, null);
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 255, { opacity: 255,
@ -158,20 +145,16 @@ const OsdWindow = new Lang.Class({
return; return;
Mainloop.source_remove(this._hideTimeoutId); Mainloop.source_remove(this._hideTimeoutId);
this._hideTimeoutId = 0;
this._hide(); this._hide();
}, },
_hide: function() { _hide: function() {
this._hideTimeoutId = 0;
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 0, { opacity: 0,
time: FADE_TIME, time: FADE_TIME,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, this._reset) });
this._reset();
Meta.enable_unredirect_for_screen(global.screen);
})
});
}, },
_reset: function() { _reset: function() {
@ -186,25 +169,11 @@ const OsdWindow = new Lang.Class({
let scalew = monitor.width / 640.0; let scalew = monitor.width / 640.0;
let scaleh = monitor.height / 480.0; let scaleh = monitor.height / 480.0;
let scale = Math.min(scalew, scaleh); let scale = Math.min(scalew, scaleh);
this._popupSize = 110 * Math.max(1, scale); let size = 110 * Math.max(1, scale);
this._box.set_size(size, size);
this._box.translation_y = monitor.height / 4; this._box.translation_y = monitor.height / 4;
this._icon.icon_size = this._popupSize / 2;
this._box.style_changed();
},
_onStyleChanged: function() { this._icon.icon_size = size / 2;
let themeNode = this._box.get_theme_node();
let horizontalPadding = themeNode.get_horizontal_padding();
let verticalPadding = themeNode.get_vertical_padding();
let topBorder = themeNode.get_border_width(St.Side.TOP);
let bottomBorder = themeNode.get_border_width(St.Side.BOTTOM);
let leftBorder = themeNode.get_border_width(St.Side.LEFT);
let rightBorder = themeNode.get_border_width(St.Side.RIGHT);
let minWidth = this._popupSize - verticalPadding - leftBorder - rightBorder;
let minHeight = this._popupSize - horizontalPadding - topBorder - bottomBorder;
this._box.style = 'min-height: %dpx;'.format(Math.max(minWidth, minHeight));
} }
}); });

View File

@ -11,6 +11,7 @@ const Shell = imports.gi.Shell;
const Gdk = imports.gi.Gdk; const Gdk = imports.gi.Gdk;
const Background = imports.ui.background; const Background = imports.ui.background;
const Dash = imports.ui.dash;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const LayoutManager = imports.ui.layout; const LayoutManager = imports.ui.layout;
const Main = imports.ui.main; const Main = imports.ui.main;
@ -19,6 +20,7 @@ const OverviewControls = imports.ui.overviewControls;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const Params = imports.misc.params; const Params = imports.misc.params;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const ViewSelector = imports.ui.viewSelector;
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
@ -29,7 +31,7 @@ const ANIMATION_TIME = 0.25;
// and don't want the shading animation to get cut off // and don't want the shading animation to get cut off
const SHADE_ANIMATION_TIME = .20; const SHADE_ANIMATION_TIME = .20;
const DND_WINDOW_SWITCH_TIMEOUT = 750; const DND_WINDOW_SWITCH_TIMEOUT = 1250;
const OVERVIEW_ACTIVATION_TIMEOUT = 0.5; const OVERVIEW_ACTIVATION_TIMEOUT = 0.5;
@ -115,7 +117,7 @@ const Overview = new Lang.Class({
let monitor = Main.layoutManager.primaryMonitor; let monitor = Main.layoutManager.primaryMonitor;
this._desktopFade = new St.Bin(); this._desktopFade = new St.Bin();
Main.layoutManager.overviewGroup.add_child(this._desktopFade); global.overlay_group.add_actor(this._desktopFade);
let layout = new Clutter.BinLayout(); let layout = new Clutter.BinLayout();
this._stack = new Clutter.Actor({ layout_manager: layout }); this._stack = new Clutter.Actor({ layout_manager: layout });
@ -131,8 +133,17 @@ const Overview = new Lang.Class({
y_expand: true }); y_expand: true });
this._overview._delegate = this; this._overview._delegate = this;
this._groupStack = new St.Widget({ layout_manager: new Clutter.BinLayout(),
x_expand: true, y_expand: true,
clip_to_allocation: true });
this._group = new St.BoxLayout({ name: 'overview-group',
reactive: true,
x_expand: true, y_expand: true });
this._groupStack.add_actor(this._group);
this._backgroundGroup = new Meta.BackgroundGroup(); this._backgroundGroup = new Meta.BackgroundGroup();
Main.layoutManager.overviewGroup.add_child(this._backgroundGroup); global.overlay_group.add_child(this._backgroundGroup);
this._backgroundGroup.hide();
this._bgManagers = []; this._bgManagers = [];
this._activationTime = 0; this._activationTime = 0;
@ -146,13 +157,14 @@ const Overview = new Lang.Class({
// During transitions, we raise this to the top to avoid having the overview // During transitions, we raise this to the top to avoid having the overview
// area be reactive; it causes too many issues such as double clicks on // area be reactive; it causes too many issues such as double clicks on
// Dash elements, or mouseover handlers in the workspaces. // Dash elements, or mouseover handlers in the workspaces.
this._coverPane = new Clutter.Actor({ opacity: 0, this._coverPane = new Clutter.Rectangle({ opacity: 0,
reactive: true }); reactive: true });
Main.layoutManager.overviewGroup.add_child(this._coverPane); this._overview.add_actor(this._coverPane);
this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; })); this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; }));
this._stack.hide();
this._stack.add_actor(this._overview); this._stack.add_actor(this._overview);
Main.layoutManager.overviewGroup.add_child(this._stack); global.overlay_group.add_actor(this._stack);
this._coverPane.hide(); this._coverPane.hide();
@ -165,6 +177,7 @@ const Overview = new Lang.Class({
Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd)); Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd));
global.screen.connect('restacked', Lang.bind(this, this._onRestacked)); global.screen.connect('restacked', Lang.bind(this, this._onRestacked));
this._group.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
this._windowSwitchTimeoutId = 0; this._windowSwitchTimeoutId = 0;
this._windowSwitchTimestamp = 0; this._windowSwitchTimestamp = 0;
@ -263,13 +276,28 @@ const Overview = new Lang.Class({
this._overview.add_actor(this._searchEntryBin); this._overview.add_actor(this._searchEntryBin);
// Create controls // Create controls
this._controls = new OverviewControls.ControlsManager(this._searchEntry); this._dash = new Dash.Dash();
this._dash = this._controls.dash; this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry,
this.viewSelector = this._controls.viewSelector; this._dash.showAppsButton);
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox();
this._controls = new OverviewControls.ControlsManager(this._dash,
this._thumbnailsBox,
this._viewSelector);
this._controls.dashActor.x_align = Clutter.ActorAlign.START;
this._controls.dashActor.y_expand = true;
// Put the dash in a separate layer to allow content to be centered
this._groupStack.add_actor(this._controls.dashActor);
// Pack all the actors into the group
this._group.add_actor(this._controls.dashSpacer);
this._group.add(this._viewSelector.actor, { x_fill: true,
expand: true });
this._group.add_actor(this._controls.thumbnailsActor);
// Add our same-line elements after the search entry // Add our same-line elements after the search entry
this._overview.add(this._controls.actor, { y_fill: true, expand: true }); this._overview.add(this._groupStack, { y_fill: true, expand: true });
this._controls.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
this._stack.add_actor(this._controls.indicatorActor); this._stack.add_actor(this._controls.indicatorActor);
@ -285,11 +313,11 @@ const Overview = new Lang.Class({
}, },
addSearchProvider: function(provider) { addSearchProvider: function(provider) {
this.viewSelector.addSearchProvider(provider); this._viewSelector.addSearchProvider(provider);
}, },
removeSearchProvider: function(provider) { removeSearchProvider: function(provider) {
this.viewSelector.removeSearchProvider(provider); this._viewSelector.removeSearchProvider(provider);
}, },
// //
@ -433,7 +461,6 @@ const Overview = new Lang.Class({
beginItemDrag: function(source) { beginItemDrag: function(source) {
this.emit('item-drag-begin'); this.emit('item-drag-begin');
this._inDrag = true;
}, },
cancelledItemDrag: function(source) { cancelledItemDrag: function(source) {
@ -442,12 +469,10 @@ const Overview = new Lang.Class({
endItemDrag: function(source) { endItemDrag: function(source) {
this.emit('item-drag-end'); this.emit('item-drag-end');
this._inDrag = false;
}, },
beginWindowDrag: function(source) { beginWindowDrag: function(source) {
this.emit('window-drag-begin'); this.emit('window-drag-begin');
this._inDrag = true;
}, },
cancelledWindowDrag: function(source) { cancelledWindowDrag: function(source) {
@ -456,31 +481,24 @@ const Overview = new Lang.Class({
endWindowDrag: function(source) { endWindowDrag: function(source) {
this.emit('window-drag-end'); this.emit('window-drag-end');
this._inDrag = false;
}, },
// show: // show:
// //
// Animates the overview visible and grabs mouse and keyboard input // Animates the overview visible and grabs mouse and keyboard input
show: function() { show : function() {
if (this.isDummy) if (this.isDummy)
return; return;
if (this._shown) if (this._shown)
return; return;
this._shown = true; this._shown = true;
if (!this._syncGrab()) if (!this._syncInputMode())
return; return;
Main.layoutManager.showOverview();
this._animateVisible(); this._animateVisible();
}, },
focusSearch: function() {
this.show();
this._searchEntry.grab_key_focus();
},
fadeInDesktop: function() { fadeInDesktop: function() {
this._desktopFade.opacity = 0; this._desktopFade.opacity = 0;
this._desktopFade.show(); this._desktopFade.show();
@ -512,8 +530,19 @@ const Overview = new Lang.Class({
this.visibleTarget = true; this.visibleTarget = true;
this._activationTime = Date.now() / 1000; this._activationTime = Date.now() / 1000;
// All the the actors in the window group are completely obscured,
// hiding the group holding them while the Overview is displayed greatly
// increases performance of the Overview especially when there are many
// windows visible.
//
// If we switched to displaying the actors in the Overview rather than
// clones of them, this would obviously no longer be necessary.
//
// Disable unredirection while in the overview
Meta.disable_unredirect_for_screen(global.screen); Meta.disable_unredirect_for_screen(global.screen);
this.viewSelector.show(); this._stack.show();
this._backgroundGroup.show();
this._viewSelector.show();
this._stack.opacity = 0; this._stack.opacity = 0;
Tweener.addTween(this._stack, Tweener.addTween(this._stack,
@ -553,7 +582,7 @@ const Overview = new Lang.Class({
this._animateNotVisible(); this._animateNotVisible();
this._shown = false; this._shown = false;
this._syncGrab(); this._syncInputMode();
}, },
toggle: function() { toggle: function() {
@ -575,8 +604,6 @@ const Overview = new Lang.Class({
shouldToggleByCornerOrButton: function() { shouldToggleByCornerOrButton: function() {
if (this.animationInProgress) if (this.animationInProgress)
return false; return false;
if (this._inDrag)
return false;
if (this._activationTime == 0 || Date.now() / 1000 - this._activationTime > OVERVIEW_ACTIVATION_TIMEOUT) if (this._activationTime == 0 || Date.now() / 1000 - this._activationTime > OVERVIEW_ACTIVATION_TIMEOUT)
return true; return true;
return false; return false;
@ -584,8 +611,8 @@ const Overview = new Lang.Class({
//// Private methods //// //// Private methods ////
_syncGrab: function() { _syncInputMode: function() {
// We delay grab changes during animation so that when removing the // We delay input mode changes during animation so that when removing the
// overview we don't have a problem with the release of a press/release // overview we don't have a problem with the release of a press/release
// going to an application. // going to an application.
if (this.animationInProgress) if (this.animationInProgress)
@ -603,12 +630,16 @@ const Overview = new Lang.Class({
return false; return false;
} }
} }
} else {
global.stage_input_mode = Shell.StageInputMode.FULLSCREEN;
} }
} else { } else {
if (this._modal) { if (this._modal) {
Main.popModal(this._overview); Main.popModal(this._overview);
this._modal = false; this._modal = false;
} }
else if (global.stage_input_mode == Shell.StageInputMode.FULLSCREEN)
global.stage_input_mode = Shell.StageInputMode.NORMAL;
} }
return true; return true;
}, },
@ -620,7 +651,7 @@ const Overview = new Lang.Class({
this.animationInProgress = true; this.animationInProgress = true;
this.visibleTarget = false; this.visibleTarget = false;
this.viewSelector.zoomFromOverview(); this._viewSelector.zoomFromOverview();
// Make other elements fade out. // Make other elements fade out.
Tweener.addTween(this._stack, Tweener.addTween(this._stack,
@ -647,7 +678,7 @@ const Overview = new Lang.Class({
if (!this._shown) if (!this._shown)
this._animateNotVisible(); this._animateNotVisible();
this._syncGrab(); this._syncInputMode();
global.sync_pointer(); global.sync_pointer();
}, },
@ -655,21 +686,22 @@ const Overview = new Lang.Class({
// Re-enable unredirection // Re-enable unredirection
Meta.enable_unredirect_for_screen(global.screen); Meta.enable_unredirect_for_screen(global.screen);
this.viewSelector.hide(); this._viewSelector.hide();
this._desktopFade.hide(); this._desktopFade.hide();
this._coverPane.hide(); this._backgroundGroup.hide();
this._stack.hide();
this.visible = false; this.visible = false;
this.animationInProgress = false; this.animationInProgress = false;
this._coverPane.hide();
this.emit('hidden'); this.emit('hidden');
// Handle any calls to show* while we were hiding // Handle any calls to show* while we were hiding
if (this._shown) if (this._shown)
this._animateVisible(); this._animateVisible();
else
Main.layoutManager.hideOverview();
this._syncGrab(); this._syncInputMode();
// Fake a pointer event if requested // Fake a pointer event if requested
if (this._needsFakePointerEvent) { if (this._needsFakePointerEvent) {

View File

@ -1,18 +1,15 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const GObject = imports.gi.GObject;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Lang = imports.lang; const Lang = imports.lang;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
const St = imports.gi.St; const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Dash = imports.ui.dash;
const Main = imports.ui.main; const Main = imports.ui.main;
const Params = imports.misc.params; const Params = imports.misc.params;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const ViewSelector = imports.ui.viewSelector; const ViewSelector = imports.ui.viewSelector;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
const SIDE_CONTROLS_ANIMATION_TIME = 0.16; const SIDE_CONTROLS_ANIMATION_TIME = 0.16;
@ -55,21 +52,18 @@ const SlideLayout = new Lang.Class({
vfunc_allocate: function(container, box, flags) { vfunc_allocate: function(container, box, flags) {
let child = container.get_first_child(); let child = container.get_first_child();
let [, , natWidth, natHeight] = child.get_preferred_size();
let availWidth = Math.round(box.x2 - box.x1); let availWidth = Math.round(box.x2 - box.x1);
let availHeight = Math.round(box.y2 - box.y1); let availHeight = Math.round(box.y2 - box.y1);
let [, natWidth] = child.get_preferred_width(availHeight);
// Align the actor inside the clipped box, as the actor's alignment
// flags only determine what to do if the allocated box is bigger
// than the actor's box.
let realDirection = getRtlSlideDirection(this._direction, child); let realDirection = getRtlSlideDirection(this._direction, child);
let alignX = (realDirection == SlideDirection.LEFT) ? (availWidth - natWidth) : 0; let translationX = (realDirection == SlideDirection.LEFT) ?
(availWidth - natWidth) : (natWidth - availWidth);
let actorBox = new Clutter.ActorBox(); let actorBox = new Clutter.ActorBox({ x1: translationX,
actorBox.x1 = alignX; y1: 0,
actorBox.x2 = actorBox.x1 + child.x_expand ? availWidth : natWidth; x2: child.x_expand ? availWidth : natWidth,
actorBox.y1 = 0; y2: child.y_expand ? availHeight : natHeight });
actorBox.y2 = actorBox.y1 + availHeight;
child.allocate(actorBox, flags); child.allocate(actorBox, flags);
}, },
@ -210,7 +204,6 @@ const SlidingControl = new Lang.Class({
slideIn: function() { slideIn: function() {
this.visible = true; this.visible = true;
this._updateTranslation();
// we will update slideX and the translation from pageEmpty // we will update slideX and the translation from pageEmpty
}, },
@ -239,15 +232,18 @@ const ThumbnailsSlider = new Lang.Class({
this._thumbnailsBox = thumbnailsBox; this._thumbnailsBox = thumbnailsBox;
// SlideLayout reads the actor's expand flags to decide
// whether to allocate the natural size to its child, or the whole
// available allocation
this._thumbnailsBox.actor.y_expand = true;
this.actor.request_mode = Clutter.RequestMode.WIDTH_FOR_HEIGHT; this.actor.request_mode = Clutter.RequestMode.WIDTH_FOR_HEIGHT;
this.actor.reactive = true; this.actor.reactive = true;
this.actor.track_hover = true; this.actor.track_hover = true;
this.actor.add_actor(this._thumbnailsBox.actor); this.actor.add_actor(this._thumbnailsBox.actor);
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this.updateSlide)); Main.layoutManager.connect('monitors-changed', Lang.bind(this, this.updateSlide));
Main.overview.connect('hiding', Lang.bind(this, this.slideOut));
this.actor.connect('notify::hover', Lang.bind(this, this.updateSlide)); this.actor.connect('notify::hover', Lang.bind(this, this.updateSlide));
this._thumbnailsBox.actor.bind_property('visible', this.actor, 'visible', GObject.BindingFlags.SYNC_CREATE);
}, },
_getAlwaysZoomOut: function() { _getAlwaysZoomOut: function() {
@ -273,18 +269,6 @@ const ThumbnailsSlider = new Lang.Class({
return alwaysZoomOut; return alwaysZoomOut;
}, },
_onOverviewShowing: function() {
this.visible = true;
this.layout.slideX = this.getSlide();
this.actor.translation_x = this._getTranslation();
this.slideIn();
},
getNonExpandedWidth: function() {
let child = this.actor.get_first_child();
return child.get_theme_node().get_length('visible-width');
},
getSlide: function() { getSlide: function() {
if (!this.visible) if (!this.visible)
return 0; return 0;
@ -296,16 +280,18 @@ const ThumbnailsSlider = new Lang.Class({
let child = this.actor.get_first_child(); let child = this.actor.get_first_child();
let preferredHeight = child.get_preferred_height(-1)[1]; let preferredHeight = child.get_preferred_height(-1)[1];
let expandedWidth = child.get_preferred_width(preferredHeight)[1]; let expandedWidth = child.get_preferred_width(preferredHeight)[1];
let visibleWidth = child.get_theme_node().get_length('visible-width');
return this.getNonExpandedWidth() / expandedWidth; return visibleWidth / expandedWidth;
}, },
getVisibleWidth: function() { getVisibleWidth: function() {
let alwaysZoomOut = this._getAlwaysZoomOut(); let alwaysZoomOut = this._getAlwaysZoomOut();
if (alwaysZoomOut) if (alwaysZoomOut)
return this.parent(); return this.parent();
else
return this.getNonExpandedWidth(); let child = this.actor.get_first_child();
return child.get_theme_node().get_length('visible-width');
} }
}); });
@ -322,14 +308,10 @@ const DashSlider = new Lang.Class({
// whether to allocate the natural size to its child, or the whole // whether to allocate the natural size to its child, or the whole
// available allocation // available allocation
this._dash.actor.x_expand = true; this._dash.actor.x_expand = true;
this._dash.actor.y_expand = true;
this.actor.x_align = Clutter.ActorAlign.START;
this.actor.y_expand = true;
this.actor.add_actor(this._dash.actor); this.actor.add_actor(this._dash.actor);
this._dash.connect('icon-size-changed', Lang.bind(this, this.updateSlide)); this._dash.connect('icon-size-changed', Lang.bind(this, this.updateSlide));
Main.overview.connect('hiding', Lang.bind(this, this.slideOut));
}, },
getSlide: function() { getSlide: function() {
@ -339,13 +321,6 @@ const DashSlider = new Lang.Class({
return 0; return 0;
}, },
_onOverviewShowing: function() {
this.visible = true;
this.layout.slideX = this.getSlide();
this.actor.translation_x = this._getTranslation();
this.slideIn();
},
_onWindowDragBegin: function() { _onWindowDragBegin: function() {
this.fadeHalf(); this.fadeHalf();
}, },
@ -501,92 +476,42 @@ const MessagesIndicator = new Lang.Class({
} }
}); });
const ControlsLayout = new Lang.Class({
Name: 'ControlsLayout',
Extends: Clutter.BinLayout,
Signals: { 'allocation-changed': { flags: GObject.SignalFlags.RUN_LAST } },
vfunc_allocate: function(container, box, flags) {
this.parent(container, box, flags);
this.emit('allocation-changed');
}
});
const ControlsManager = new Lang.Class({ const ControlsManager = new Lang.Class({
Name: 'ControlsManager', Name: 'ControlsManager',
_init: function(searchEntry) { _init: function(dash, thumbnails, viewSelector) {
this.dash = new Dash.Dash(); this._dashSlider = new DashSlider(dash);
this._dashSlider = new DashSlider(this.dash); this.dashActor = this._dashSlider.actor;
this._dashSpacer = new DashSpacer(); this.dashSpacer = new DashSpacer();
this._dashSpacer.setDashActor(this._dashSlider.actor); this.dashSpacer.setDashActor(this.dashActor);
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox(); this._thumbnailsSlider = new ThumbnailsSlider(thumbnails);
this._thumbnailsSlider = new ThumbnailsSlider(this._thumbnailsBox); this.thumbnailsActor = this._thumbnailsSlider.actor;
this.viewSelector = new ViewSelector.ViewSelector(searchEntry, this._indicator = new MessagesIndicator(viewSelector);
this.dash.showAppsButton);
this.viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility));
this.viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty));
this._indicator = new MessagesIndicator(this.viewSelector);
this.indicatorActor = this._indicator.actor; this.indicatorActor = this._indicator.actor;
let layout = new ControlsLayout(); this._viewSelector = viewSelector;
this.actor = new St.Widget({ layout_manager: layout, this._viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility));
reactive: true, this._viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty));
x_expand: true, y_expand: true,
clip_to_allocation: true });
this._group = new St.BoxLayout({ name: 'overview-group',
x_expand: true, y_expand: true });
this.actor.add_actor(this._group);
this.actor.add_actor(this._dashSlider.actor);
this._group.add_actor(this._dashSpacer);
this._group.add(this.viewSelector.actor, { x_fill: true,
expand: true });
this._group.add_actor(this._thumbnailsSlider.actor);
layout.connect('allocation-changed', Lang.bind(this, this._updateWorkspacesGeometry));
Main.overview.connect('showing', Lang.bind(this, this._updateSpacerVisibility)); Main.overview.connect('showing', Lang.bind(this, this._updateSpacerVisibility));
Main.overview.connect('item-drag-begin', Lang.bind(this, Main.overview.connect('item-drag-begin', Lang.bind(this,
function() { function() {
let activePage = this.viewSelector.getActivePage(); let activePage = this._viewSelector.getActivePage();
if (activePage != ViewSelector.ViewPage.WINDOWS) if (activePage != ViewSelector.ViewPage.WINDOWS)
this.viewSelector.fadeHalf(); this._viewSelector.fadeHalf();
})); }));
Main.overview.connect('item-drag-end', Lang.bind(this, Main.overview.connect('item-drag-end', Lang.bind(this,
function() { function() {
this.viewSelector.fadeIn(); this._viewSelector.fadeIn();
})); }));
Main.overview.connect('item-drag-cancelled', Lang.bind(this, Main.overview.connect('item-drag-cancelled', Lang.bind(this,
function() { function() {
this.viewSelector.fadeIn(); this._viewSelector.fadeIn();
})); }));
}, },
_updateWorkspacesGeometry: function() {
let [x, y] = this.actor.get_transformed_position();
let [width, height] = this.actor.get_transformed_size();
let geometry = { x: x, y: y, width: width, height: height };
let spacing = this.actor.get_theme_node().get_length('spacing');
let dashWidth = this._dashSlider.getVisibleWidth() + spacing;
let thumbnailsWidth = this._thumbnailsSlider.getNonExpandedWidth() + spacing;
geometry.width -= dashWidth;
geometry.width -= thumbnailsWidth;
if (this.actor.get_text_direction() == Clutter.TextDirection.LTR)
geometry.x += dashWidth;
else
geometry.x += thumbnailsWidth;
this.viewSelector.setWorkspacesFullGeometry(geometry);
},
_setVisibility: function() { _setVisibility: function() {
// Ignore the case when we're leaving the overview, since // Ignore the case when we're leaving the overview, since
// actors will be made visible again when entering the overview // actors will be made visible again when entering the overview
@ -596,7 +521,7 @@ const ControlsManager = new Lang.Class({
(Main.overview.animationInProgress && !Main.overview.visibleTarget)) (Main.overview.animationInProgress && !Main.overview.visibleTarget))
return; return;
let activePage = this.viewSelector.getActivePage(); let activePage = this._viewSelector.getActivePage();
let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS || let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS ||
activePage == ViewSelector.ViewPage.APPS); activePage == ViewSelector.ViewPage.APPS);
let thumbnailsVisible = (activePage == ViewSelector.ViewPage.WINDOWS); let thumbnailsVisible = (activePage == ViewSelector.ViewPage.WINDOWS);
@ -616,8 +541,8 @@ const ControlsManager = new Lang.Class({
if (Main.overview.animationInProgress && !Main.overview.visibleTarget) if (Main.overview.animationInProgress && !Main.overview.visibleTarget)
return; return;
let activePage = this.viewSelector.getActivePage(); let activePage = this._viewSelector.getActivePage();
this._dashSpacer.visible = (activePage == ViewSelector.ViewPage.WINDOWS); this.dashSpacer.visible = (activePage == ViewSelector.ViewPage.WINDOWS);
}, },
_onPageEmpty: function() { _onPageEmpty: function() {

View File

@ -15,14 +15,12 @@ const Signals = imports.signals;
const Atk = imports.gi.Atk; const Atk = imports.gi.Atk;
const Animation = imports.ui.animation;
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;
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 RemoteMenu = imports.ui.remoteMenu;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
@ -30,6 +28,7 @@ const PANEL_ICON_SIZE = 24;
const BUTTON_DND_ACTIVATION_TIMEOUT = 250; const BUTTON_DND_ACTIVATION_TIMEOUT = 250;
const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
const SPINNER_ANIMATION_TIME = 0.2; const SPINNER_ANIMATION_TIME = 0.2;
// To make sure the panel corners blend nicely with the panel, // To make sure the panel corners blend nicely with the panel,
@ -75,6 +74,81 @@ function _unpremultiply(color) {
blue: blue, alpha: color.alpha }); blue: blue, alpha: color.alpha });
}; };
const Animation = new Lang.Class({
Name: 'Animation',
_init: function(filename, width, height, speed) {
this.actor = new St.Bin();
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this._speed = speed;
this._isLoaded = false;
this._isPlaying = false;
this._timeoutId = 0;
this._frame = 0;
this._animations = St.TextureCache.get_default().load_sliced_image (filename, width, height,
Lang.bind(this, this._animationsLoaded));
this.actor.set_child(this._animations);
},
play: function() {
if (this._isLoaded && this._timeoutId == 0) {
if (this._frame == 0)
this._showFrame(0);
this._timeoutId = Mainloop.timeout_add(this._speed, Lang.bind(this, this._update));
}
this._isPlaying = true;
},
stop: function() {
if (this._timeoutId > 0) {
Mainloop.source_remove(this._timeoutId);
this._timeoutId = 0;
}
this._isPlaying = false;
},
_showFrame: function(frame) {
let oldFrameActor = this._animations.get_child_at_index(this._frame);
if (oldFrameActor)
oldFrameActor.hide();
this._frame = (frame % this._animations.get_n_children());
let newFrameActor = this._animations.get_child_at_index(this._frame);
if (newFrameActor)
newFrameActor.show();
},
_update: function() {
this._showFrame(this._frame + 1);
return true;
},
_animationsLoaded: function() {
this._isLoaded = true;
if (this._isPlaying)
this.play();
},
_onDestroy: function() {
this.stop();
}
});
const AnimatedIcon = new Lang.Class({
Name: 'AnimatedIcon',
Extends: Animation,
_init: function(name, size) {
this.parent(global.datadir + '/theme/' + name, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
}
});
const TextShadower = new Lang.Class({ const TextShadower = new Lang.Class({
Name: 'TextShadower', Name: 'TextShadower',
@ -184,11 +258,11 @@ const AppMenuButton = new Lang.Class({
this._actionGroupNotifyId = 0; this._actionGroupNotifyId = 0;
let bin = new St.Bin({ name: 'appMenu' }); let bin = new St.Bin({ name: 'appMenu' });
bin.connect('style-changed', Lang.bind(this, this._onStyleChanged));
this.actor.add_actor(bin); this.actor.add_actor(bin);
this.actor.bind_property("reactive", this.actor, "can-focus", 0); this.actor.bind_property("reactive", this.actor, "can-focus", 0);
this.actor.reactive = false; this.actor.reactive = false;
this._targetIsCurrent = false;
this._container = new Shell.GenericContainer(); this._container = new Shell.GenericContainer();
bin.set_child(this._container); bin.set_child(this._container);
@ -206,36 +280,35 @@ const AppMenuButton = new Lang.Class({
this._iconBox.connect('notify::allocation', this._iconBox.connect('notify::allocation',
Lang.bind(this, this._updateIconBoxClip)); Lang.bind(this, this._updateIconBoxClip));
this._container.add_actor(this._iconBox); this._container.add_actor(this._iconBox);
this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
this._container.add_actor(this._hbox);
this._label = new TextShadower(); this._label = new TextShadower();
this._label.actor.y_align = Clutter.ActorAlign.CENTER; this._container.add_actor(this._label.actor);
this._hbox.add_actor(this._label.actor);
this._arrow = PopupMenu.unicodeArrow(St.Side.BOTTOM);
this._hbox.add_actor(this._arrow);
this._iconBottomClip = 0; this._iconBottomClip = 0;
this._visible = !Main.overview.visible; this._visible = !Main.overview.visible;
if (!this._visible) if (!this._visible)
this.actor.hide(); this.actor.hide();
this._overviewHidingId = Main.overview.connect('hiding', Lang.bind(this, this._sync)); Main.overview.connect('hiding', Lang.bind(this, function () {
this._overviewShowingId = Main.overview.connect('showing', Lang.bind(this, this._sync)); this.show();
}));
Main.overview.connect('showing', Lang.bind(this, function () {
this.hide();
}));
this._stop = true; this._stop = true;
this._spinner = null; this._spinner = new AnimatedIcon('process-working.svg',
PANEL_ICON_SIZE);
this._container.add_actor(this._spinner.actor);
this._spinner.actor.hide();
this._spinner.actor.lower_bottom();
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
let appSys = Shell.AppSystem.get_default(); let appSys = Shell.AppSystem.get_default();
this._focusAppNotifyId = tracker.connect('notify::focus-app', Lang.bind(this, this._focusAppChanged));
tracker.connect('notify::focus-app', Lang.bind(this, this._focusAppChanged)); appSys.connect('app-state-changed', Lang.bind(this, this._onAppStateChanged));
this._appStateChangedSignalId =
appSys.connect('app-state-changed', Lang.bind(this, this._onAppStateChanged)); global.window_manager.connect('switch-workspace', Lang.bind(this, this._sync));
this._switchWorkspaceNotifyId =
global.window_manager.connect('switch-workspace', Lang.bind(this, this._sync));
this._sync(); this._sync();
}, },
@ -245,8 +318,13 @@ const AppMenuButton = new Lang.Class({
return; return;
this._visible = true; this._visible = true;
this.actor.reactive = true;
this.actor.show(); this.actor.show();
if (!this._targetIsCurrent)
return;
this.actor.reactive = true;
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 255, { opacity: 255,
@ -260,6 +338,11 @@ const AppMenuButton = new Lang.Class({
this._visible = false; this._visible = false;
this.actor.reactive = false; this.actor.reactive = false;
if (!this._targetIsCurrent) {
this.actor.hide();
return;
}
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 0, { opacity: 0,
@ -271,36 +354,19 @@ const AppMenuButton = new Lang.Class({
onCompleteScope: this }); onCompleteScope: this });
}, },
_onStyleChanged: function(actor) {
let node = actor.get_theme_node();
let [success, icon] = node.lookup_url('spinner-image', false);
if (!success || this._spinnerIcon == icon)
return;
this._spinnerIcon = icon;
this._spinner = new Animation.AnimatedIcon(this._spinnerIcon, PANEL_ICON_SIZE);
this._hbox.add_actor(this._spinner.actor);
this._spinner.actor.hide();
},
_onIconBoxStyleChanged: function() { _onIconBoxStyleChanged: function() {
let node = this._iconBox.get_theme_node(); let node = this._iconBox.get_theme_node();
this._iconBottomClip = node.get_length('app-icon-bottom-clip'); this._iconBottomClip = node.get_length('app-icon-bottom-clip');
this._updateIconBoxClip(); this._updateIconBoxClip();
}, },
_syncIcon: function() {
if (!this._targetApp)
return;
let icon = this._targetApp.get_faded_icon(2 * PANEL_ICON_SIZE, this._iconBox.text_direction);
this._iconBox.set_child(icon);
},
_onIconThemeChanged: function() { _onIconThemeChanged: function() {
if (this._iconBox.child == null) if (this._iconBox.child == null)
return; return;
this._syncIcon(); this._iconBox.child.destroy();
let icon = this._targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
this._iconBox.set_child(icon);
}, },
_updateIconBoxClip: function() { _updateIconBoxClip: function() {
@ -318,10 +384,7 @@ const AppMenuButton = new Lang.Class({
return; return;
this._stop = true; this._stop = true;
this.actor.reactive = true;
if (this._spinner == null)
return;
Tweener.addTween(this._spinner.actor, Tweener.addTween(this._spinner.actor,
{ opacity: 0, { opacity: 0,
time: SPINNER_ANIMATION_TIME, time: SPINNER_ANIMATION_TIME,
@ -337,10 +400,7 @@ const AppMenuButton = new Lang.Class({
startAnimation: function() { startAnimation: function() {
this._stop = false; this._stop = false;
this.actor.reactive = false;
if (this._spinner == null)
return;
this._spinner.play(); this._spinner.play();
this._spinner.actor.show(); this._spinner.actor.show();
}, },
@ -349,7 +409,7 @@ const AppMenuButton = new Lang.Class({
let [minSize, naturalSize] = this._iconBox.get_preferred_width(forHeight); let [minSize, naturalSize] = this._iconBox.get_preferred_width(forHeight);
alloc.min_size = minSize; alloc.min_size = minSize;
alloc.natural_size = naturalSize; alloc.natural_size = naturalSize;
[minSize, naturalSize] = this._hbox.get_preferred_width(forHeight); [minSize, naturalSize] = this._label.actor.get_preferred_width(forHeight);
alloc.min_size = alloc.min_size + Math.max(0, minSize - Math.floor(alloc.min_size / 2)); alloc.min_size = alloc.min_size + Math.max(0, minSize - Math.floor(alloc.min_size / 2));
alloc.natural_size = alloc.natural_size + Math.max(0, naturalSize - Math.floor(alloc.natural_size / 2)); alloc.natural_size = alloc.natural_size + Math.max(0, naturalSize - Math.floor(alloc.natural_size / 2));
}, },
@ -358,7 +418,7 @@ const AppMenuButton = new Lang.Class({
let [minSize, naturalSize] = this._iconBox.get_preferred_height(forWidth); let [minSize, naturalSize] = this._iconBox.get_preferred_height(forWidth);
alloc.min_size = minSize; alloc.min_size = minSize;
alloc.natural_size = naturalSize; alloc.natural_size = naturalSize;
[minSize, naturalSize] = this._hbox.get_preferred_height(forWidth); [minSize, naturalSize] = this._label.actor.get_preferred_height(forWidth);
if (minSize > alloc.min_size) if (minSize > alloc.min_size)
alloc.min_size = minSize; alloc.min_size = minSize;
if (naturalSize > alloc.natural_size) if (naturalSize > alloc.natural_size)
@ -388,10 +448,11 @@ const AppMenuButton = new Lang.Class({
let iconWidth = childBox.x2 - childBox.x1; let iconWidth = childBox.x2 - childBox.x1;
[minWidth, naturalWidth] = this._hbox.get_preferred_width(-1); [minWidth, minHeight, naturalWidth, naturalHeight] = this._label.actor.get_preferred_size();
childBox.y1 = 0; yPadding = Math.floor(Math.max(0, allocHeight - naturalHeight) / 2);
childBox.y2 = allocHeight; childBox.y1 = yPadding;
childBox.y2 = childBox.y1 + Math.min(naturalHeight, allocHeight);
if (direction == Clutter.TextDirection.LTR) { if (direction == Clutter.TextDirection.LTR) {
childBox.x1 = Math.floor(iconWidth / 2); childBox.x1 = Math.floor(iconWidth / 2);
@ -400,7 +461,21 @@ const AppMenuButton = new Lang.Class({
childBox.x2 = allocWidth - Math.floor(iconWidth / 2); childBox.x2 = allocWidth - Math.floor(iconWidth / 2);
childBox.x1 = Math.max(0, childBox.x2 - naturalWidth); childBox.x1 = Math.max(0, childBox.x2 - naturalWidth);
} }
this._hbox.allocate(childBox, flags); this._label.actor.allocate(childBox, flags);
if (direction == Clutter.TextDirection.LTR) {
childBox.x1 = Math.floor(iconWidth / 2) + this._label.actor.width;
childBox.x2 = childBox.x1 + this._spinner.actor.width;
childBox.y1 = box.y1;
childBox.y2 = box.y2 - 1;
this._spinner.actor.allocate(childBox, flags);
} else {
childBox.x1 = -this._spinner.actor.width;
childBox.x2 = childBox.x1 + this._spinner.actor.width;
childBox.y1 = box.y1;
childBox.y2 = box.y2 - 1;
this._spinner.actor.allocate(childBox, flags);
}
}, },
_onAppStateChanged: function(appSys, app) { _onAppStateChanged: function(appSys, app) {
@ -426,88 +501,109 @@ const AppMenuButton = new Lang.Class({
// If the app has just lost focus to the panel, pretend // If the app has just lost focus to the panel, pretend
// nothing happened; otherwise you can't keynav to the // nothing happened; otherwise you can't keynav to the
// app menu. // app menu.
if (global.stage.key_focus != null) if (global.stage_input_mode == Shell.StageInputMode.FOCUSED)
return; return;
} }
this._sync(); this._sync();
}, },
_findTargetApp: function() { _sync: function() {
let workspace = global.screen.get_active_workspace();
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
let focusedApp = tracker.focus_app; let focusedApp = tracker.focus_app;
if (focusedApp && focusedApp.is_on_workspace(workspace)) let lastStartedApp = null;
return focusedApp; let workspace = global.screen.get_active_workspace();
for (let i = 0; i < this._startingApps.length; i++) for (let i = 0; i < this._startingApps.length; i++)
if (this._startingApps[i].is_on_workspace(workspace)) if (this._startingApps[i].is_on_workspace(workspace))
return this._startingApps[i]; lastStartedApp = this._startingApps[i];
return null; let targetApp = focusedApp != null ? focusedApp : lastStartedApp;
},
_sync: function() { if (targetApp == null) {
let targetApp = this._findTargetApp(); if (!this._targetIsCurrent)
return;
if (this._targetApp != targetApp) { this.actor.reactive = false;
if (this._appMenuNotifyId) { this._targetIsCurrent = false;
this._targetApp.disconnect(this._appMenuNotifyId);
this._appMenuNotifyId = 0;
}
if (this._actionGroupNotifyId) {
this._targetApp.disconnect(this._actionGroupNotifyId);
this._actionGroupNotifyId = 0;
}
this._targetApp = targetApp; Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor, { opacity: 0,
if (this._targetApp) { time: Overview.ANIMATION_TIME,
this._appMenuNotifyId = this._targetApp.connect('notify::menu', Lang.bind(this, this._sync)); transition: 'easeOutQuad' });
this._actionGroupNotifyId = this._targetApp.connect('notify::action-group', Lang.bind(this, this._sync)); return;
this._label.setText(this._targetApp.get_name());
this.actor.set_accessible_name(this._targetApp.get_name());
}
} }
let visible = (this._targetApp != null && !Main.overview.visibleTarget); if (!targetApp.is_on_workspace(workspace))
if (visible) return;
this.show();
else
this.hide();
let isBusy = (this._targetApp != null && if (!this._targetIsCurrent) {
(this._targetApp.get_state() == Shell.AppState.STARTING || this.actor.reactive = true;
this._targetApp.get_state() == Shell.AppState.BUSY)); this._targetIsCurrent = true;
if (isBusy)
Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor, { opacity: 255,
time: Overview.ANIMATION_TIME,
transition: 'easeOutQuad' });
}
if (targetApp == this._targetApp) {
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING) {
this.stopAnimation();
this._maybeSetMenu();
}
return;
}
this._spinner.actor.hide();
if (this._iconBox.child != null)
this._iconBox.child.destroy();
this._iconBox.hide();
this._label.setText('');
if (this._appMenuNotifyId)
this._targetApp.disconnect(this._appMenuNotifyId);
if (this._actionGroupNotifyId)
this._targetApp.disconnect(this._actionGroupNotifyId);
if (targetApp) {
this._appMenuNotifyId = targetApp.connect('notify::menu', Lang.bind(this, this._sync));
this._actionGroupNotifyId = targetApp.connect('notify::action-group', Lang.bind(this, this._sync));
} else {
this._appMenuNotifyId = 0;
this._actionGroupNotifyId = 0;
}
this._targetApp = targetApp;
let icon = targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
this._label.setText(targetApp.get_name());
this.setName(targetApp.get_name());
this._iconBox.set_child(icon);
this._iconBox.show();
if (targetApp.get_state() == Shell.AppState.STARTING)
this.startAnimation(); this.startAnimation();
else else
this.stopAnimation(); this._maybeSetMenu();
this.actor.reactive = (visible && !isBusy);
this._syncIcon();
this._maybeSetMenu();
this.emit('changed'); this.emit('changed');
}, },
_maybeSetMenu: function() { _maybeSetMenu: function() {
let menu; let menu;
if (this._targetApp == null) { if (this._targetApp.action_group && this._targetApp.menu) {
menu = null; if (this.menu instanceof PopupMenu.RemoteMenu &&
} else if (this._targetApp.action_group && this._targetApp.menu) {
if (this.menu instanceof RemoteMenu.RemoteMenu &&
this.menu.actionGroup == this._targetApp.action_group) this.menu.actionGroup == this._targetApp.action_group)
return; return;
menu = new RemoteMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group); menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
menu.connect('activate', Lang.bind(this, function() { menu.connect('activate', Lang.bind(this, function() {
let win = this._targetApp.get_windows()[0]; let win = this._targetApp.get_windows()[0];
win.check_alive(global.get_current_time()); win.check_alive(global.get_current_time());
})); }));
} else { } else {
if (this.menu && this.menu.isDummyQuitMenu) if (this.menu.isDummyQuitMenu)
return; return;
// fallback to older menu // fallback to older menu
@ -519,35 +615,7 @@ const AppMenuButton = new Lang.Class({
} }
this.setMenu(menu); this.setMenu(menu);
if (menu) this._menuManager.addMenu(menu);
this._menuManager.addMenu(menu);
},
destroy: function() {
if (this._appStateChangedSignalId > 0) {
let appSys = Shell.AppSystem.get_default();
appSys.disconnect(this._appStateChangedSignalId);
this._appStateChangedSignalId = 0;
}
if (this._focusAppNotifyId > 0) {
let tracker = Shell.WindowTracker.get_default();
tracker.disconnect(this._focusAppNotifyId);
this._focusAppNotifyId = 0;
}
if (this._overviewHidingId > 0) {
Main.overview.disconnect(this._overviewHidingId);
this._overviewHidingId = 0;
}
if (this._overviewShowingId > 0) {
Main.overview.disconnect(this._overviewShowingId);
this._overviewShowingId = 0;
}
if (this._switchWorkspaceNotifyId > 0) {
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
this._switchWorkspaceNotifyId = 0;
}
this.parent();
} }
}); });
@ -565,8 +633,7 @@ const ActivitiesButton = new Lang.Class({
/* Translators: If there is no suitable word for "Activities" /* Translators: If there is no suitable word for "Activities"
in your language, you can use the word for "Overview". */ in your language, you can use the word for "Overview". */
this._label = new St.Label({ text: _("Activities"), this._label = new St.Label({ text: _("Activities") });
y_align: Clutter.ActorAlign.CENTER });
this.actor.add_actor(this._label); this.actor.add_actor(this._label);
this.actor.label_actor = this._label; this.actor.label_actor = this._label;
@ -609,7 +676,6 @@ const ActivitiesButton = new Lang.Class({
_onButtonRelease: function() { _onButtonRelease: function() {
Main.overview.toggle(); Main.overview.toggle();
this.menu.close();
}, },
_onKeyRelease: function(actor, event) { _onKeyRelease: function(actor, event) {
@ -798,65 +864,32 @@ const PanelCorner = new Lang.Class({
} }
}); });
const AggregateMenu = new Lang.Class({
Name: 'AggregateMenu',
Extends: PanelMenu.Button,
_init: function() {
this.parent(0.0, _("Settings"), false);
this.menu.actor.add_style_class_name('aggregate-menu');
this._indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box' });
this.actor.add_child(this._indicators);
this._network = new imports.ui.status.network.NMApplet();
if (Config.HAVE_BLUETOOTH) {
this._bluetooth = new imports.ui.status.bluetooth.Indicator();
} else {
this._bluetooth = null;
}
this._power = new imports.ui.status.power.Indicator();
this._rfkill = new imports.ui.status.rfkill.Indicator();
this._volume = new imports.ui.status.volume.Indicator();
this._brightness = new imports.ui.status.brightness.Indicator();
this._system = new imports.ui.status.system.Indicator();
this._screencast = new imports.ui.status.screencast.Indicator();
this._indicators.add_child(this._screencast.indicators);
this._indicators.add_child(this._network.indicators);
if (this._bluetooth) {
this._indicators.add_child(this._bluetooth.indicators);
}
this._indicators.add_child(this._rfkill.indicators);
this._indicators.add_child(this._volume.indicators);
this._indicators.add_child(this._power.indicators);
this._indicators.add_child(PopupMenu.unicodeArrow(St.Side.BOTTOM));
this.menu.addMenuItem(this._volume.menu);
this.menu.addMenuItem(this._brightness.menu);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addMenuItem(this._network.menu);
if (this._bluetooth) {
this.menu.addMenuItem(this._bluetooth.menu);
}
this.menu.addMenuItem(this._rfkill.menu);
this.menu.addMenuItem(this._power.menu);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addMenuItem(this._system.menu);
},
});
const PANEL_ITEM_IMPLEMENTATIONS = { const PANEL_ITEM_IMPLEMENTATIONS = {
'activities': ActivitiesButton, 'activities': ActivitiesButton,
'aggregateMenu': AggregateMenu,
'appMenu': AppMenuButton, 'appMenu': AppMenuButton,
'dateMenu': imports.ui.dateMenu.DateMenuButton, 'dateMenu': imports.ui.dateMenu.DateMenuButton,
'a11y': imports.ui.status.accessibility.ATIndicator, 'a11y': imports.ui.status.accessibility.ATIndicator,
'a11yGreeter': imports.ui.status.accessibility.ATGreeterIndicator, 'a11yGreeter': imports.ui.status.accessibility.ATGreeterIndicator,
'volume': imports.ui.status.volume.Indicator,
'battery': imports.ui.status.power.Indicator,
'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
'logo': imports.gdm.loginDialog.LogoMenuButton,
'keyboard': imports.ui.status.keyboard.InputSourceIndicator, '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',
@ -869,7 +902,7 @@ const Panel = new Lang.Class({
this.statusArea = {}; this.statusArea = {};
this.menuManager = new PopupMenu.PopupMenuManager(this, { keybindingMode: Shell.KeyBindingMode.TOPBAR_POPUP }); this.menuManager = 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);
@ -1023,18 +1056,17 @@ const Panel = new Lang.Class({
return true; return true;
}, },
toggleAppMenu: function() { openAppMenu: function() {
let indicator = this.statusArea.appMenu; let indicator = this.statusArea.appMenu;
if (!indicator) // appMenu not supported by current session mode if (!indicator) // appMenu not supported by current session mode
return; return;
let menu = indicator.menu; let menu = indicator.menu;
if (!indicator.actor.reactive) if (!indicator.actor.reactive || menu.isOpen)
return; return;
menu.toggle(); menu.open();
if (menu.isOpen) menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
}, },
set boxOpacity(value) { set boxOpacity(value) {

View File

@ -86,8 +86,13 @@ const ButtonBox = new Lang.Class({
childBox.x2 = availWidth - this._minHPadding; childBox.x2 = availWidth - this._minHPadding;
} }
childBox.y1 = 0; if (natHeight <= availHeight) {
childBox.y2 = availHeight; childBox.y1 = Math.floor((availHeight - natHeight) / 2);
childBox.y2 = childBox.y1 + natHeight;
} else {
childBox.y1 = 0;
childBox.y2 = availHeight;
}
child.allocate(childBox, flags); child.allocate(childBox, flags);
}, },
@ -101,17 +106,17 @@ const Button = new Lang.Class({
this.parent({ reactive: true, this.parent({ reactive: true,
can_focus: true, can_focus: true,
track_hover: true, track_hover: true,
accessible_name: nameText ? nameText : "",
accessible_role: Atk.Role.MENU }); accessible_role: Atk.Role.MENU });
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress)); this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress)); this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress));
this.actor.connect('notify::visible', Lang.bind(this, this._onVisibilityChanged));
if (dontCreateMenu) if (dontCreateMenu)
this.menu = new PopupMenu.PopupDummyMenu(this.actor); this.menu = new PopupMenu.PopupDummyMenu(this.actor);
else else
this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0)); this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0));
this.setName(nameText);
}, },
setSensitive: function(sensitive) { setSensitive: function(sensitive) {
@ -120,6 +125,22 @@ const Button = new Lang.Class({
this.actor.track_hover = sensitive; this.actor.track_hover = sensitive;
}, },
setName: function(text) {
if (text != null) {
// This is the easiest way to provide a accessible name to
// this widget. The label could be also used for other
// purposes in the future.
if (!this.label) {
this.label = new St.Label({ text: text });
this.actor.label_actor = this.label;
} else
this.label.text = text;
} else {
this.label = null;
this.actor.label_actor = null;
}
},
setMenu: function(menu) { setMenu: function(menu) {
if (this.menu) if (this.menu)
this.menu.destroy(); this.menu.destroy();
@ -162,18 +183,7 @@ const Button = new Lang.Class({
return false; return false;
}, },
_onVisibilityChanged: function() {
if (!this.menu)
return;
if (!this.actor.visible)
this.menu.close();
},
_onMenuKeyPress: function(actor, event) { _onMenuKeyPress: function(actor, event) {
if (global.focus_manager.navigate_from_event(event))
return true;
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 group = global.focus_manager.get_group(this.actor);
@ -211,35 +221,51 @@ const Button = new Lang.Class({
}); });
Signals.addSignalMethods(Button.prototype); Signals.addSignalMethods(Button.prototype);
/* SystemIndicator: /* SystemStatusButton:
* *
* This class manages one system indicator, which are the icons * This class manages one System Status indicator (network, keyboard,
* that you see at the top right. A system indicator is composed * volume, bluetooth...), which is just a PanelMenuButton with an
* of an icon and a menu section, which will be composed into the * icon.
* aggregate menu.
*/ */
const SystemIndicator = new Lang.Class({ const SystemStatusButton = new Lang.Class({
Name: 'SystemIndicator', Name: 'SystemStatusButton',
Extends: Button,
_init: function() { _init: function(iconName, nameText) {
this.indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box', this.parent(0.0, nameText);
reactive: true }); this.actor.add_style_class_name('panel-status-button');
this.indicators.hide();
this.menu = new PopupMenu.PopupMenuSection(); this._box = new St.BoxLayout({ style_class: 'panel-status-button-box' });
this.actor.add_actor(this._box);
if (iconName)
this.setIcon(iconName);
}, },
_syncIndicatorsVisible: function() { get icons() {
this.indicators.visible = this.indicators.get_children().some(function(actor) { return this._box.get_children();
return actor.visible;
});
}, },
_addIndicator: function() { addIcon: function(gicon) {
let icon = new St.Icon({ style_class: 'system-status-icon' }); let icon = new St.Icon({ gicon: gicon,
this.indicators.add_actor(icon); style_class: 'system-status-icon' });
icon.connect('notify::visible', Lang.bind(this, this._syncIndicatorsVisible)); this._box.add_actor(icon);
this._syncIndicatorsVisible();
this.emit('icons-changed');
return icon; return icon;
},
setIcon: function(iconName) {
if (!this.mainIcon)
this.mainIcon = this.addIcon(null);
this.mainIcon.icon_name = iconName;
},
setGIcon: function(gicon) {
if (this.mainIcon)
this.mainIcon.gicon = gicon;
else
this.mainIcon = this.addIcon(gicon);
} }
}); });
Signals.addSignalMethods(SystemIndicator.prototype);

View File

@ -2,7 +2,6 @@
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const GnomeDesktop = imports.gi.GnomeDesktop; const GnomeDesktop = imports.gi.GnomeDesktop;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
@ -42,7 +41,7 @@ const PointerWatcher = new Lang.Class({
Name: 'PointerWatcher', Name: 'PointerWatcher',
_init: function() { _init: function() {
this._idleMonitor = Meta.IdleMonitor.get_core(); this._idleMonitor = new GnomeDesktop.IdleMonitor();
this._idleMonitor.add_idle_watch(IDLE_TIME, Lang.bind(this, this._onIdleMonitorBecameIdle)); this._idleMonitor.add_idle_watch(IDLE_TIME, Lang.bind(this, this._onIdleMonitorBecameIdle));
this._idle = this._idleMonitor.get_idletime() > IDLE_TIME; this._idle = this._idleMonitor.get_idletime() > IDLE_TIME;
this._watches = []; this._watches = [];

File diff suppressed because it is too large Load Diff

View File

@ -1,199 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Atk = imports.gi.Atk;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const ShellMenu = imports.gi.ShellMenu;
const St = imports.gi.St;
const PopupMenu = imports.ui.popupMenu;
function stripMnemonics(label) {
if (!label)
return '';
// remove all underscores that are not followed by another underscore
return label.replace(/_([^_])/, '$1');
}
function _insertItem(menu, trackerItem, position) {
let mapper;
if (trackerItem.get_is_separator())
mapper = new RemoteMenuSeparatorItemMapper(trackerItem);
else if (trackerItem.get_has_submenu())
mapper = new RemoteMenuSubmenuItemMapper(trackerItem);
else
mapper = new RemoteMenuItemMapper(trackerItem);
let item = mapper.menuItem;
menu.addMenuItem(item, position);
}
function _removeItem(menu, position) {
let items = menu._getMenuItems();
items[position].destroy();
}
const RemoteMenuSeparatorItemMapper = new Lang.Class({
Name: 'RemoteMenuSeparatorItemMapper',
_init: function(trackerItem) {
this._trackerItem = trackerItem;
this.menuItem = new PopupMenu.PopupSeparatorMenuItem();
this._trackerItem.connect('notify::label', Lang.bind(this, this._updateLabel));
this._updateLabel();
this.menuItem.connect('destroy', function() {
trackerItem.run_dispose();
});
},
_updateLabel: function() {
this.menuItem.label.text = stripMnemonics(this._trackerItem.label);
},
});
const RequestSubMenu = new Lang.Class({
Name: 'RequestSubMenu',
Extends: PopupMenu.PopupSubMenuMenuItem,
_init: function() {
this.parent('');
this._requestOpen = false;
},
_setOpenState: function(open) {
this.emit('request-open', open);
this._requestOpen = open;
},
_getOpenState: function() {
return this._requestOpen;
},
});
const RemoteMenuSubmenuItemMapper = new Lang.Class({
Name: 'RemoteMenuSubmenuItemMapper',
_init: function(trackerItem) {
this._trackerItem = trackerItem;
this.menuItem = new RequestSubMenu();
this._trackerItem.connect('notify::label', Lang.bind(this, this._updateLabel));
this._updateLabel();
this._tracker = Shell.MenuTracker.new_for_item_submenu(this._trackerItem,
_insertItem.bind(null, this.menuItem.menu),
_removeItem.bind(null, this.menuItem.menu));
this.menuItem.connect('request-open', Lang.bind(this, function(menu, open) {
this._trackerItem.request_submenu_shown(open);
}));
this._trackerItem.connect('notify::submenu-shown', Lang.bind(this, function() {
this.menuItem.setSubmenuShown(this._trackerItem.get_submenu_shown());
}));
this.menuItem.connect('destroy', function() {
trackerItem.run_dispose();
});
},
destroy: function() {
this._tracker.destroy();
this.parent();
},
_updateLabel: function() {
this.menuItem.label.text = stripMnemonics(this._trackerItem.label);
},
});
const RemoteMenuItemMapper = new Lang.Class({
Name: 'RemoteMenuItemMapper',
_init: function(trackerItem) {
this._trackerItem = trackerItem;
this.menuItem = new PopupMenu.PopupBaseMenuItem();
this._label = new St.Label();
this.menuItem.actor.add_child(this._label);
this.menuItem.actor.label_actor = this._label;
this.menuItem.connect('activate', Lang.bind(this, function() {
this._trackerItem.activated();
}));
this._trackerItem.bind_property('visible', this.menuItem.actor, 'visible', GObject.BindingFlags.SYNC_CREATE);
this._trackerItem.connect('notify::label', Lang.bind(this, this._updateLabel));
this._trackerItem.connect('notify::sensitive', Lang.bind(this, this._updateSensitivity));
this._trackerItem.connect('notify::role', Lang.bind(this, this._updateRole));
this._trackerItem.connect('notify::toggled', Lang.bind(this, this._updateDecoration));
this._updateLabel();
this._updateSensitivity();
this._updateRole();
this.menuItem.connect('destroy', function() {
trackerItem.run_dispose();
});
},
_updateLabel: function() {
this._label.text = stripMnemonics(this._trackerItem.label);
},
_updateSensitivity: function() {
this.menuItem.setSensitive(this._trackerItem.sensitive);
},
_updateDecoration: function() {
let ornamentForRole = {};
ornamentForRole[ShellMenu.MenuTrackerItemRole.RADIO] = PopupMenu.Ornament.DOT;
ornamentForRole[ShellMenu.MenuTrackerItemRole.CHECK] = PopupMenu.Ornament.CHECK;
let ornament = PopupMenu.Ornament.NONE;
if (this._trackerItem.toggled)
ornament = ornamentForRole[this._trackerItem.role];
this.menuItem.setOrnament(ornament);
},
_updateRole: function() {
let a11yRoles = {};
a11yRoles[ShellMenu.MenuTrackerItemRole.NORMAL] = Atk.Role.MENU_ITEM;
a11yRoles[ShellMenu.MenuTrackerItemRole.RADIO] = Atk.Role.RADIO_MENU_ITEM;
a11yRoles[ShellMenu.MenuTrackerItemRole.CHECK] = Atk.Role.CHECK_MENU_ITEM;
let a11yRole = a11yRoles[this._trackerItem.role];
this.menuItem.actor.accessible_role = a11yRole;
this._updateDecoration();
},
});
const RemoteMenu = new Lang.Class({
Name: 'RemoteMenu',
Extends: PopupMenu.PopupMenu,
_init: function(sourceActor, model, actionGroup) {
this.parent(sourceActor, 0.0, St.Side.TOP);
this._model = model;
this._actionGroup = actionGroup;
this._tracker = Shell.MenuTracker.new(this._actionGroup,
this._model,
null, /* action namespace */
_insertItem.bind(null, this),
_removeItem.bind(null, this));
},
destroy: function() {
this._tracker.destroy();
this.parent();
},
});

View File

@ -7,6 +7,7 @@ const Lang = imports.lang;
const St = imports.gi.St; const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const FileUtils = imports.misc.fileUtils;
const Search = imports.ui.search; const Search = imports.ui.search;
const KEY_FILE_GROUP = 'Shell Search Provider'; const KEY_FILE_GROUP = 'Shell Search Provider';
@ -59,114 +60,108 @@ var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface);
var SearchProvider2Proxy = Gio.DBusProxy.makeProxyWrapper(SearchProvider2Iface); var SearchProvider2Proxy = Gio.DBusProxy.makeProxyWrapper(SearchProvider2Iface);
function loadRemoteSearchProviders(addProviderCallback) { function loadRemoteSearchProviders(addProviderCallback) {
let objectPaths = {}; let data = { loadedProviders: [],
let loadedProviders = []; objectPaths: {},
addProviderCallback: addProviderCallback };
FileUtils.collectFromDatadirsAsync('search-providers',
{ loadedCallback: remoteProvidersLoaded,
processFile: loadRemoteSearchProvider,
data: data
});
}
function loadRemoteSearchProvider(file) { function loadRemoteSearchProvider(file, info, data) {
let keyfile = new GLib.KeyFile(); let keyfile = new GLib.KeyFile();
let path = file.get_path(); let path = file.get_path();
try { try {
keyfile.load_from_file(path, 0); keyfile.load_from_file(path, 0);
} catch(e) { } catch(e) {
return; return;
}
if (!keyfile.has_group(KEY_FILE_GROUP))
return;
let remoteProvider;
try {
let group = KEY_FILE_GROUP;
let busName = keyfile.get_string(group, 'BusName');
let objectPath = keyfile.get_string(group, 'ObjectPath');
if (objectPaths[objectPath])
return;
let appInfo = null;
try {
let desktopId = keyfile.get_string(group, 'DesktopId');
appInfo = Gio.DesktopAppInfo.new(desktopId);
} catch (e) {
log('Ignoring search provider ' + path + ': missing DesktopId');
return;
}
let version = '1';
try {
version = keyfile.get_string(group, 'Version');
} catch (e) {
// ignore error
}
if (version >= 2)
remoteProvider = new RemoteSearchProvider2(appInfo, busName, objectPath);
else
remoteProvider = new RemoteSearchProvider(appInfo, busName, objectPath);
objectPaths[objectPath] = remoteProvider;
loadedProviders.push(remoteProvider);
} catch(e) {
log('Failed to add search provider %s: %s'.format(path, e.toString()));
}
} }
let dataDirs = GLib.get_system_data_dirs(); if (!keyfile.has_group(KEY_FILE_GROUP))
dataDirs.forEach(function(dataDir) { return;
let path = GLib.build_filenamev([dataDir, 'gnome-shell', 'search-providers']);
let dir = Gio.File.new_for_path(path);
let fileEnum;
try {
fileEnum = dir.enumerate_children('standard::name,standard::type',
Gio.FileQueryInfoFlags.NONE, null);
} catch (e) {
fileEnum = null;
}
if (fileEnum != null) {
let info;
while ((info = fileEnum.next_file(null)))
loadRemoteSearchProvider(fileEnum.get_child(info));
}
});
let remoteProvider;
try {
let group = KEY_FILE_GROUP;
let busName = keyfile.get_string(group, 'BusName');
let objectPath = keyfile.get_string(group, 'ObjectPath');
if (data.objectPaths[objectPath])
return;
let appInfo = null;
try {
let desktopId = keyfile.get_string(group, 'DesktopId');
appInfo = Gio.DesktopAppInfo.new(desktopId);
} catch (e) {
log('Ignoring search provider ' + path + ': missing DesktopId');
return;
}
let version = '1';
try {
version = keyfile.get_string(group, 'Version');
} catch (e) {
// ignore error
}
if (version >= 2)
remoteProvider = new RemoteSearchProvider2(appInfo, busName, objectPath);
else
remoteProvider = new RemoteSearchProvider(appInfo, busName, objectPath);
data.objectPaths[objectPath] = remoteProvider;
data.loadedProviders.push(remoteProvider);
} catch(e) {
log('Failed to add search provider %s: %s'.format(path, e.toString()));
}
}
function remoteProvidersLoaded(loadState) {
let searchSettings = new Gio.Settings({ schema: Search.SEARCH_PROVIDERS_SCHEMA }); let searchSettings = new Gio.Settings({ schema: Search.SEARCH_PROVIDERS_SCHEMA });
let sortOrder = searchSettings.get_strv('sort-order'); let sortOrder = searchSettings.get_strv('sort-order');
// Special case gnome-control-center to be always active and always first // Special case gnome-control-center to be always active and always first
sortOrder.unshift('gnome-control-center.desktop'); sortOrder.unshift('gnome-control-center.desktop');
loadedProviders.sort(function(providerA, providerB) { loadState.loadedProviders.sort(
let idxA, idxB; function(providerA, providerB) {
let appIdA, appIdB; let idxA, idxB;
let appIdA, appIdB;
appIdA = providerA.appInfo.get_id(); appIdA = providerA.appInfo.get_id();
appIdB = providerB.appInfo.get_id(); appIdB = providerB.appInfo.get_id();
idxA = sortOrder.indexOf(appIdA); idxA = sortOrder.indexOf(appIdA);
idxB = sortOrder.indexOf(appIdB); idxB = sortOrder.indexOf(appIdB);
// if no provider is found in the order, use alphabetical order // if no provider is found in the order, use alphabetical order
if ((idxA == -1) && (idxB == -1)) { if ((idxA == -1) && (idxB == -1)) {
let nameA = providerA.appInfo.get_name(); let nameA = providerA.appInfo.get_name();
let nameB = providerB.appInfo.get_name(); let nameB = providerB.appInfo.get_name();
return GLib.utf8_collate(nameA, nameB); return GLib.utf8_collate(nameA, nameB);
} }
// if providerA isn't found, it's sorted after providerB // if providerA isn't found, it's sorted after providerB
if (idxA == -1) if (idxA == -1)
return 1; return 1;
// if providerB isn't found, it's sorted after providerA // if providerB isn't found, it's sorted after providerA
if (idxB == -1) if (idxB == -1)
return -1; return -1;
// finally, if both providers are found, return their order in the list // finally, if both providers are found, return their order in the list
return (idxA - idxB); return (idxA - idxB);
}); });
loadedProviders.forEach(addProviderCallback); loadState.loadedProviders.forEach(
function(provider) {
loadState.addProviderCallback(provider);
});
} }
const RemoteSearchProvider = new Lang.Class({ const RemoteSearchProvider = new Lang.Class({
@ -192,9 +187,7 @@ const RemoteSearchProvider = new Lang.Class({
createIcon: function(size, meta) { createIcon: function(size, meta) {
let gicon; let gicon;
if (meta['icon']) { if (meta['gicon']) {
gicon = Gio.icon_deserialize(meta['icon']);
} else if (meta['gicon']) {
gicon = Gio.icon_new_for_string(meta['gicon']); gicon = Gio.icon_new_for_string(meta['gicon']);
} else if (meta['icon-data']) { } else if (meta['icon-data']) {
let [width, height, rowStride, hasAlpha, let [width, height, rowStride, hasAlpha,
@ -207,20 +200,10 @@ const RemoteSearchProvider = new Lang.Class({
icon_size: size }); icon_size: size });
}, },
filterResults: function(results, maxNumber) {
if (results.length <= maxNumber)
return results;
let regularResults = results.filter(function(r) { return !r.startsWith('special:'); });
let specialResults = results.filter(function(r) { return r.startsWith('special:'); });
return regularResults.slice(0, maxNumber).concat(specialResults.slice(0, maxNumber));
},
_getResultsFinished: function(results, error) { _getResultsFinished: function(results, error) {
if (error) if (error)
return; return;
this.searchSystem.setResults(this, results[0]); this.searchSystem.pushResults(this, results[0]);
}, },
getInitialResultSet: function(terms) { getInitialResultSet: function(terms) {
@ -232,7 +215,7 @@ const RemoteSearchProvider = new Lang.Class({
this._cancellable); this._cancellable);
} catch(e) { } catch(e) {
log('Error calling GetInitialResultSet for provider %s: %s'.format(this.id, e.toString())); log('Error calling GetInitialResultSet for provider %s: %s'.format(this.id, e.toString()));
this.searchSystem.setResults(this, []); this.searchSystem.pushResults(this, []);
} }
}, },
@ -245,7 +228,7 @@ const RemoteSearchProvider = new Lang.Class({
this._cancellable); this._cancellable);
} catch(e) { } catch(e) {
log('Error calling GetSubsearchResultSet for provider %s: %s'.format(this.id, e.toString())); log('Error calling GetSubsearchResultSet for provider %s: %s'.format(this.id, e.toString()));
this.searchSystem.setResults(this, []); this.searchSystem.pushResults(this, []);
} }
}, },
@ -257,12 +240,8 @@ const RemoteSearchProvider = new Lang.Class({
let metas = results[0]; let metas = results[0];
let resultMetas = []; let resultMetas = [];
for (let i = 0; i < metas.length; i++) { for (let i = 0; i < metas.length; i++) {
for (let prop in metas[i]) { for (let prop in metas[i])
// we can use the serialized icon variant directly metas[i][prop] = metas[i][prop].deep_unpack();
if (prop != 'icon')
metas[i][prop] = metas[i][prop].deep_unpack();
}
resultMetas.push({ id: metas[i]['id'], resultMetas.push({ id: metas[i]['id'],
name: metas[i]['name'], name: metas[i]['name'],
description: metas[i]['description'], description: metas[i]['description'],

View File

@ -35,8 +35,7 @@ const RunDialog = new Lang.Class({
Extends: ModalDialog.ModalDialog, Extends: ModalDialog.ModalDialog,
_init : function() { _init : function() {
this.parent({ styleClass: 'run-dialog', this.parent({ styleClass: 'run-dialog' });
destroyOnClose: false });
this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA }); this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA });
this._terminalSettings = new Gio.Settings({ schema: TERMINAL_SCHEMA }); this._terminalSettings = new Gio.Settings({ schema: TERMINAL_SCHEMA });

View File

@ -1,12 +1,10 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const AccountsService = imports.gi.AccountsService;
const Cairo = imports.cairo; const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const GnomeDesktop = imports.gi.GnomeDesktop; const GnomeDesktop = imports.gi.GnomeDesktop;
const Gtk = imports.gi.Gtk;
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;
@ -19,14 +17,12 @@ const Background = imports.ui.background;
const GnomeSession = imports.misc.gnomeSession; const GnomeSession = imports.misc.gnomeSession;
const Hash = imports.misc.hash; const Hash = imports.misc.hash;
const Layout = imports.ui.layout; const Layout = imports.ui.layout;
const OVirt = imports.gdm.oVirt;
const LoginManager = imports.misc.loginManager; const LoginManager = imports.misc.loginManager;
const Lightbox = imports.ui.lightbox; const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main; const Main = imports.ui.main;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const ShellDBus = imports.ui.shellDBus; const ShellDBus = imports.ui.shellDBus;
const SmartcardManager = imports.misc.smartcardManager;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const Util = imports.misc.util; const Util = imports.misc.util;
@ -34,7 +30,6 @@ const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const LOCK_ENABLED_KEY = 'lock-enabled'; const LOCK_ENABLED_KEY = 'lock-enabled';
const LOCK_DELAY_KEY = 'lock-delay'; const LOCK_DELAY_KEY = 'lock-delay';
const LOCKED_STATE_STR = 'screenShield.locked';
// fraction of screen height the arrow must reach before completing // fraction of screen height the arrow must reach before completing
// the slide up automatically // the slide up automatically
const ARROW_DRAG_THRESHOLD = 0.1; const ARROW_DRAG_THRESHOLD = 0.1;
@ -53,10 +48,12 @@ const SUMMARY_ICON_SIZE = 48;
// or when cancelling the dialog // or when cancelling the dialog
// - BACKGROUND_FADE_TIME is used when the background changes to crossfade to new background // - BACKGROUND_FADE_TIME is used when the background changes to crossfade to new background
// - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking // - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking
// - INITIAL_FADE_IN_TIME is used for the initial fade in at startup
const STANDARD_FADE_TIME = 10; const STANDARD_FADE_TIME = 10;
const MANUAL_FADE_TIME = 0.3; const MANUAL_FADE_TIME = 0.3;
const BACKGROUND_FADE_TIME = 1.0; const BACKGROUND_FADE_TIME = 1.0;
const CURTAIN_SLIDE_TIME = 0.3; const CURTAIN_SLIDE_TIME = 0.3;
const INITIAL_FADE_IN_TIME = 0.25;
const Clock = new Lang.Class({ const Clock = new Lang.Class({
Name: 'ScreenShieldClock', Name: 'ScreenShieldClock',
@ -106,14 +103,13 @@ const NotificationsBox = new Lang.Class({
this._musicBin = new St.Bin({ style_class: 'screen-shield-notifications-box', this._musicBin = new St.Bin({ style_class: 'screen-shield-notifications-box',
visible: false }); visible: false });
this._scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START, let scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START });
hscrollbar_policy: Gtk.PolicyType.NEVER });
this._notificationBox = new St.BoxLayout({ vertical: true, this._notificationBox = new St.BoxLayout({ vertical: true,
style_class: 'screen-shield-notifications-box' }); style_class: 'screen-shield-notifications-box' });
this._scrollView.add_actor(this._notificationBox); scrollView.add_actor(this._notificationBox);
this.actor.add(this._musicBin); this.actor.add(this._musicBin);
this.actor.add(this._scrollView, { x_fill: true, x_align: St.Align.START }); this.actor.add(scrollView, { x_fill: true, x_align: St.Align.START });
this._sources = new Hash.Map(); this._sources = new Hash.Map();
Main.messageTray.getSources().forEach(Lang.bind(this, function(source) { Main.messageTray.getSources().forEach(Lang.bind(this, function(source) {
@ -218,7 +214,6 @@ const NotificationsBox = new Lang.Class({
if (musicNotification != null && if (musicNotification != null &&
this._musicBin.child == null) { this._musicBin.child == null) {
musicNotification.acknowledged = true;
if (musicNotification.actor.get_parent() != null) if (musicNotification.actor.get_parent() != null)
musicNotification.actor.get_parent().remove_actor(musicNotification.actor); musicNotification.actor.get_parent().remove_actor(musicNotification.actor);
this._musicBin.child = musicNotification.actor; this._musicBin.child = musicNotification.actor;
@ -239,7 +234,7 @@ const NotificationsBox = new Lang.Class({
(source.unseenCount > (musicNotification ? 1 : 0)); (source.unseenCount > (musicNotification ? 1 : 0));
}, },
_sourceAdded: function(tray, source, initial) { _sourceAdded: function(tray, source, dontUpdateVisibility) {
// Ignore transient sources // Ignore transient sources
if (source.isTransient) if (source.isTransient)
return; return;
@ -251,7 +246,6 @@ const NotificationsBox = new Lang.Class({
sourceCountChangedId: 0, sourceCountChangedId: 0,
sourceTitleChangedId: 0, sourceTitleChangedId: 0,
sourceUpdatedId: 0, sourceUpdatedId: 0,
sourceNotifyId: 0,
musicNotification: null, musicNotification: null,
sourceBox: null, sourceBox: null,
titleLabel: null, titleLabel: null,
@ -262,12 +256,6 @@ const NotificationsBox = new Lang.Class({
this._showSource(source, obj, obj.sourceBox); this._showSource(source, obj, obj.sourceBox);
this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START }); this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
if (obj.musicNotification) {
obj.sourceNotifyId = source.connect('notify', Lang.bind(this, function(source, notification) {
notification.acknowledged = true;
}));
}
obj.sourceCountChangedId = source.connect('count-updated', Lang.bind(this, function(source) { obj.sourceCountChangedId = source.connect('count-updated', Lang.bind(this, function(source) {
this._countChanged(source, obj); this._countChanged(source, obj);
})); }));
@ -286,29 +274,8 @@ const NotificationsBox = new Lang.Class({
this._sources.set(source, obj); this._sources.set(source, obj);
if (!initial) { if (!dontUpdateVisibility)
// block scrollbars while animating, if they're not needed now
let boxHeight = this._notificationBox.height;
if (this._scrollView.height >= boxHeight)
this._scrollView.vscrollbar_policy = Gtk.PolicyType.NEVER;
let widget = obj.sourceBox;
let [, natHeight] = widget.get_preferred_height(-1);
widget.height = 0;
Tweener.addTween(widget,
{ height: natHeight,
transition: 'easeOutQuad',
time: 0.25,
onComplete: function() {
this._scrollView.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC;
widget.set_height(-1);
},
onCompleteScope: this
});
this._updateVisibility(); this._updateVisibility();
Shell.util_wake_up_screen();
}
}, },
_titleChanged: function(source, obj) { _titleChanged: function(source, obj) {
@ -330,10 +297,7 @@ const NotificationsBox = new Lang.Class({
obj.sourceBox.visible = obj.visible && obj.sourceBox.visible = obj.visible &&
(source.unseenCount > (obj.musicNotification ? 1 : 0)); (source.unseenCount > (obj.musicNotification ? 1 : 0));
this._updateVisibility(); this._updateVisibility();
if (obj.sourceBox.visible)
Shell.util_wake_up_screen();
}, },
_visibleChanged: function(source, obj) { _visibleChanged: function(source, obj) {
@ -347,8 +311,6 @@ const NotificationsBox = new Lang.Class({
source.unseenCount > (obj.musicNotification ? 1 : 0); source.unseenCount > (obj.musicNotification ? 1 : 0);
this._updateVisibility(); this._updateVisibility();
if (obj.sourceBox.visible)
Shell.util_wake_up_screen();
}, },
_detailedChanged: function(source, obj) { _detailedChanged: function(source, obj) {
@ -374,8 +336,6 @@ const NotificationsBox = new Lang.Class({
if (obj.musicNotification) { if (obj.musicNotification) {
this._musicBin.child = null; this._musicBin.child = null;
obj.musicNotification = null; obj.musicNotification = null;
source.disconnect(obj.sourceNotifyId);
} }
source.disconnect(obj.sourceDestroyId); source.disconnect(obj.sourceDestroyId);
@ -518,10 +478,16 @@ const ScreenShield = new Lang.Class({
this._lockDialogGroup = new St.Widget({ x_expand: true, this._lockDialogGroup = new St.Widget({ x_expand: true,
y_expand: true, y_expand: true,
reactive: true, opacity: 0,
pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }), pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }),
name: 'lockDialogGroup' }); name: 'lockDialogGroup' });
Tweener.addTween(this._lockDialogGroup,
{ opacity: 255,
time: INITIAL_FADE_IN_TIME,
transition: 'easeInQuad',
});
this.actor.add_actor(this._lockDialogGroup); this.actor.add_actor(this._lockDialogGroup);
this.actor.add_actor(this._lockScreenGroup); this.actor.add_actor(this._lockScreenGroup);
@ -539,20 +505,6 @@ const ScreenShield = new Lang.Class({
this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this); this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this);
this._smartcardManager = SmartcardManager.getSmartcardManager();
this._smartcardManager.connect('smartcard-inserted',
Lang.bind(this, function(token) {
if (this._isLocked && token.UsedToLogin)
this._liftShield(true, 0);
}));
this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager();
this._oVirtCredentialsManager.connect('user-authenticated',
Lang.bind(this, function() {
if (this._isLocked)
this._liftShield(true, 0);
}));
this._inhibitor = null; this._inhibitor = null;
this._aboutToSuspend = false; this._aboutToSuspend = false;
this._loginManager = LoginManager.getLoginManager(); this._loginManager = LoginManager.getLoginManager();
@ -579,20 +531,13 @@ const ScreenShield = new Lang.Class({
this._becameActiveId = 0; this._becameActiveId = 0;
this._lockTimeoutId = 0; this._lockTimeoutId = 0;
// The "long" lightbox is used for the longer (20 seconds) fade from session this._lightbox = new Lightbox.Lightbox(Main.uiGroup,
// to idle status, the "short" is used for quickly fading to black when locking { inhibitEvents: true,
// manually fadeInTime: STANDARD_FADE_TIME,
this._longLightbox = new Lightbox.Lightbox(Main.uiGroup, fadeFactor: 1 });
{ inhibitEvents: true, this._lightbox.connect('shown', Lang.bind(this, this._onLightboxShown));
fadeFactor: 1 });
this._longLightbox.connect('shown', Lang.bind(this, this._onLongLightboxShown));
this._shortLightbox = new Lightbox.Lightbox(Main.uiGroup,
{ inhibitEvents: true,
fadeFactor: 1 });
this._shortLightbox.connect('shown', Lang.bind(this, this._onShortLightboxShown));
this.idleMonitor = Meta.IdleMonitor.get_core(); this.idleMonitor = new GnomeDesktop.IdleMonitor();
this._cursorTracker = Meta.CursorTracker.get_for_screen(global.screen);
}, },
_createBackground: function(monitorIndex) { _createBackground: function(monitorIndex) {
@ -605,8 +550,7 @@ const ScreenShield = new Lang.Class({
let bgManager = new Background.BackgroundManager({ container: widget, let bgManager = new Background.BackgroundManager({ container: widget,
monitorIndex: monitorIndex, monitorIndex: monitorIndex,
controlPosition: false, controlPosition: false });
settingsSchema: SCREENSAVER_SCHEMA });
this._bgManagers.push(bgManager); this._bgManagers.push(bgManager);
@ -618,7 +562,6 @@ const ScreenShield = new Lang.Class({
this._bgManagers[i].destroy(); this._bgManagers[i].destroy();
this._bgManagers = []; this._bgManagers = [];
this._backgroundGroup.destroy_all_children();
for (let i = 0; i < Main.layoutManager.monitors.length; i++) for (let i = 0; i < Main.layoutManager.monitors.length; i++)
this._createBackground(i); this._createBackground(i);
@ -626,28 +569,13 @@ const ScreenShield = new Lang.Class({
_liftShield: function(onPrimary, velocity) { _liftShield: function(onPrimary, velocity) {
if (this._isLocked) { if (this._isLocked) {
if (this._ensureUnlockDialog(onPrimary, true /* allowCancel */)) this._ensureUnlockDialog(onPrimary, true /* allowCancel */);
this._hideLockScreen(true /* animate */, velocity); this._hideLockScreen(true /* animate */, velocity);
} else { } else {
this.deactivate(true /* animate */); this.deactivate(true /* animate */);
} }
}, },
_maybeCancelDialog: function() {
if (!this._dialog)
return;
this._dialog.cancel();
if (this._isGreeter) {
// LoginDialog.cancel() will grab the key focus
// on its own, so ensure it stays on lock screen
// instead
this._lockScreenGroup.grab_key_focus();
} else {
this._dialog = null;
}
},
_becomeModal: function() { _becomeModal: function() {
if (this._isModal) if (this._isModal)
return true; return true;
@ -679,9 +607,9 @@ const ScreenShield = new Lang.Class({
if (!isEnter && !(GLib.unichar_isprint(unichar) || symbol == Clutter.KEY_Escape)) if (!isEnter && !(GLib.unichar_isprint(unichar) || symbol == Clutter.KEY_Escape))
return false; return false;
if (this._isLocked && this._ensureUnlockDialog(true, true);
this._ensureUnlockDialog(true, true) &&
GLib.unichar_isgraph(unichar)) if (GLib.unichar_isgraph(unichar))
this._dialog.addCharacter(unichar); this._dialog.addCharacter(unichar);
this._liftShield(true, 0); this._liftShield(true, 0);
@ -732,8 +660,6 @@ const ScreenShield = new Lang.Class({
this.lock(true); this.lock(true);
} else { } else {
this._inhibitSuspend(); this._inhibitSuspend();
this._onUserBecameActive();
} }
}, },
@ -782,8 +708,6 @@ const ScreenShield = new Lang.Class({
}, },
_onDragEnd: function(action, actor, eventX, eventY, modifiers) { _onDragEnd: function(action, actor, eventX, eventY, modifiers) {
if (this._lockScreenState != MessageTray.State.HIDING)
return;
if (this._lockScreenGroup.y < -(ARROW_DRAG_THRESHOLD * global.stage.height)) { if (this._lockScreenGroup.y < -(ARROW_DRAG_THRESHOLD * global.stage.height)) {
// Complete motion automatically // Complete motion automatically
let [velocity, velocityX, velocityY] = this._dragAction.get_velocity(0); let [velocity, velocityX, velocityY] = this._dragAction.get_velocity(0);
@ -805,7 +729,13 @@ const ScreenShield = new Lang.Class({
onCompleteScope: this, onCompleteScope: this,
}); });
this._maybeCancelDialog(); // If we have a unlock dialog, cancel it
if (this._dialog) {
this._dialog.cancel();
if (!this._isGreeter) {
this._dialog = null;
}
}
} }
}, },
@ -813,9 +743,27 @@ const ScreenShield = new Lang.Class({
if (status != GnomeSession.PresenceStatus.IDLE) if (status != GnomeSession.PresenceStatus.IDLE)
return; return;
this._maybeCancelDialog(); if (this._dialog) {
this._dialog.cancel();
if (!this._isGreeter) {
this._dialog = null;
}
}
if (this._longLightbox.actor.visible || 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._lightbox.actor.visible ||
this._isActive) { this._isActive) {
// We're either shown and active, or in the process of // We're either shown and active, or in the process of
// showing. // showing.
@ -828,22 +776,13 @@ const ScreenShield = new Lang.Class({
return; return;
} }
if (!this._becomeModal()) { this._lightbox.show();
// 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._activationTime == 0) if (this._activationTime == 0)
this._activationTime = GLib.get_monotonic_time(); this._activationTime = GLib.get_monotonic_time();
this._becameActiveId = this.idleMonitor.add_user_active_watch(Lang.bind(this, this._onUserBecameActive));
let shouldLock = this._settings.get_boolean(LOCK_ENABLED_KEY) && !this._isLocked; let shouldLock = this._settings.get_boolean(LOCK_ENABLED_KEY) && !this._isLocked;
if (shouldLock) { if (shouldLock) {
@ -851,59 +790,48 @@ const ScreenShield = new Lang.Class({
this._lockTimeoutId = Mainloop.timeout_add(lockTimeout * 1000, this._lockTimeoutId = Mainloop.timeout_add(lockTimeout * 1000,
Lang.bind(this, function() { Lang.bind(this, function() {
this._lockTimeoutId = 0; this._lockTimeoutId = 0;
this.lock(false); this.lock(true);
return false; return false;
})); }));
} }
this._activateFade(this._longLightbox, STANDARD_FADE_TIME);
},
_activateFade: function(lightbox, time) {
lightbox.show(time);
if (this._becameActiveId == 0)
this._becameActiveId = this.idleMonitor.add_user_active_watch(Lang.bind(this, this._onUserBecameActive));
}, },
_onUserBecameActive: function() { _onUserBecameActive: function() {
// This function gets called here when the user becomes active // This function gets called here when the user becomes active
// after we activated a lightbox // after gnome-session changed the status to IDLE
// There are two possibilities here: // There are four possibilities here:
// - we're called when already locked/active; isLocked or isActive is true, // - we're called when already locked; isActive and isLocked are true,
// we just go back to the lock screen curtain // we just go back to the lock screen curtain
// (isActive == isLocked == true: normal case // - we're called before the lightbox is fully shown; at this point
// isActive == false, isLocked == true: during the fade for manual locking // isActive is false, so we just hide the ligthbox, reset the activationTime
// isActive == true, isLocked == false: after session idle, before lock-delay) // and go back to the unlocked desktop
// - we're called because the session is IDLE but before the lightbox // - we're called after showing the lightbox, but before the lock
// is fully shown; at this point isActive is false, so we just hide // delay; this is mostly like the case above, but isActive is true now
// the lightbox, reset the activationTime and go back to the unlocked // so we need to notify gnome-settings-daemon to go back to the normal
// desktop // policies for blanking
// using deactivate() is a little of overkill, but it ensures we // (they're handled by the same code, and we emit one extra ActiveChanged
// don't forget of some bit like modal, DBus properties or idle watches // signal in the case above)
// // - we're called after showing the lightbox and after lock-delay; the
// Note: if the (long) lightbox is shown then we're necessarily // session is effectivelly locked now, it's time to build and show
// active, because we call activate() without animation. // the lock screen
this.idleMonitor.remove_watch(this._becameActiveId); this.idleMonitor.remove_watch(this._becameActiveId);
this._becameActiveId = 0; this._becameActiveId = 0;
if (this._isActive || this._isLocked) { let lightboxWasShown = this._lightbox.shown;
this._longLightbox.hide(); this._lightbox.hide();
this._shortLightbox.hide();
} else { // Shortcircuit in case the mouse was moved before the fade completed
if (!lightboxWasShown) {
this.deactivate(false); this.deactivate(false);
return;
} }
}, },
_onLongLightboxShown: function() { _onLightboxShown: function() {
this.activate(false); this.activate(false);
}, },
_onShortLightboxShown: function() {
this._completeLockScreenShown();
},
showDialog: function() { showDialog: function() {
// Ensure that the stage window is mapped, before taking a grab // Ensure that the stage window is mapped, before taking a grab
// otherwise X errors out // otherwise X errors out
@ -920,8 +848,8 @@ const ScreenShield = new Lang.Class({
this.actor.show(); this.actor.show();
this._isGreeter = Main.sessionMode.isGreeter; this._isGreeter = Main.sessionMode.isGreeter;
this._isLocked = true; this._isLocked = true;
if (this._ensureUnlockDialog(true, true)) this._ensureUnlockDialog(true, true);
this._hideLockScreen(false, 0); this._hideLockScreen(false, 0);
}, },
_hideLockScreenComplete: function() { _hideLockScreenComplete: function() {
@ -938,8 +866,6 @@ const ScreenShield = new Lang.Class({
this._lockScreenState = MessageTray.State.HIDING; this._lockScreenState = MessageTray.State.HIDING;
Tweener.removeTweens(this._lockScreenGroup);
if (animate) { if (animate) {
// Tween the lock screen out of screen // Tween the lock screen out of screen
// if velocity is not specified (i.e. we come here from pressing ESC), // if velocity is not specified (i.e. we come here from pressing ESC),
@ -952,6 +878,7 @@ const ScreenShield = new Lang.Class({
velocity = Math.max(min_velocity, velocity); velocity = Math.max(min_velocity, velocity);
let time = (delta / velocity) / 1000; let time = (delta / velocity) / 1000;
Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup, Tweener.addTween(this._lockScreenGroup,
{ y: -h, { y: -h,
time: time, time: time,
@ -962,7 +889,7 @@ const ScreenShield = new Lang.Class({
this._hideLockScreenComplete(); this._hideLockScreenComplete();
} }
this._cursorTracker.set_pointer_visible(true); global.stage.show_cursor();
}, },
_ensureUnlockDialog: function(onPrimary, allowCancel) { _ensureUnlockDialog: function(onPrimary, allowCancel) {
@ -971,7 +898,7 @@ const ScreenShield = new Lang.Class({
if (!constructor) { if (!constructor) {
// This session mode has no locking capabilities // This session mode has no locking capabilities
this.deactivate(true); this.deactivate(true);
return false; return;
} }
this._dialog = new constructor(this._lockDialogGroup); this._dialog = new constructor(this._lockDialogGroup);
@ -983,22 +910,24 @@ const ScreenShield = new Lang.Class({
// by the time we reach this... // by the time we reach this...
log('Could not open login dialog: failed to acquire grab'); log('Could not open login dialog: failed to acquire grab');
this.deactivate(true); this.deactivate(true);
return false;
} }
this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed)); this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed));
this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded));
} }
this._dialog.allowCancel = allowCancel; this._dialog.allowCancel = allowCancel;
return true;
}, },
_onUnlockFailed: function() { _onUnlockFailed: function() {
this._resetLockScreen({ animateLockScreen: true, this._resetLockScreen(true, false);
fadeToBlack: false });
}, },
_resetLockScreen: function(params) { _onUnlockSucceded: function() {
this.deactivate(true);
},
_resetLockScreen: function(animateLockScreen, animateLockDialog) {
// Don't reset the lock screen unless it is completely hidden // Don't reset the lock screen unless it is completely hidden
// This prevents the shield going down if the lock-delay timeout // This prevents the shield going down if the lock-delay timeout
// fires while the user is dragging (which has the potential // fires while the user is dragging (which has the potential
@ -1013,9 +942,7 @@ const ScreenShield = new Lang.Class({
this._lockScreenGroup.show(); this._lockScreenGroup.show();
this._lockScreenState = MessageTray.State.SHOWING; this._lockScreenState = MessageTray.State.SHOWING;
let fadeToBlack = params.fadeToBlack; if (animateLockScreen) {
if (params.animateLockScreen) {
this._lockScreenGroup.y = -global.screen_height; this._lockScreenGroup.y = -global.screen_height;
Tweener.removeTweens(this._lockScreenGroup); Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup, Tweener.addTween(this._lockScreenGroup,
@ -1023,15 +950,24 @@ const ScreenShield = new Lang.Class({
time: MANUAL_FADE_TIME, time: MANUAL_FADE_TIME,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: function() { onComplete: function() {
this._lockScreenShown({ fadeToBlack: fadeToBlack, this._lockScreenShown();
animateFade: true });
}, },
onCompleteScope: this onCompleteScope: this
}); });
} else { } else {
this._lockScreenGroup.fixed_position_set = false; this._lockScreenGroup.fixed_position_set = false;
this._lockScreenShown({ fadeToBlack: fadeToBlack, this._lockScreenShown();
animateFade: false }); }
if (animateLockDialog) {
this._lockDialogGroup.opacity = 0;
Tweener.removeTweens(this._lockDialogGroup);
Tweener.addTween(this._lockDialogGroup,
{ opacity: 255,
time: MANUAL_FADE_TIME,
transition: 'easeOutQuad' });
} else {
this._lockDialogGroup.opacity = 255;
} }
this._lockScreenGroup.grab_key_focus(); this._lockScreenGroup.grab_key_focus();
@ -1087,7 +1023,7 @@ const ScreenShield = new Lang.Class({
this._pauseArrowAnimation(); this._pauseArrowAnimation();
}, },
_lockScreenShown: function(params) { _lockScreenShown: function() {
if (this._dialog && !this._isGreeter) { if (this._dialog && !this._isGreeter) {
this._dialog.destroy(); this._dialog.destroy();
this._dialog = null; this._dialog = null;
@ -1095,35 +1031,20 @@ const ScreenShield = new Lang.Class({
this._checkArrowAnimation(); this._checkArrowAnimation();
let motionId = global.stage.connect('captured-event', Lang.bind(this, function(stage, event) { let motionId = global.stage.connect('captured-event', function(stage, event) {
if (event.type() == Clutter.EventType.MOTION) { if (event.type() == Clutter.EventType.MOTION) {
this._cursorTracker.set_pointer_visible(true); global.stage.show_cursor();
global.stage.disconnect(motionId); global.stage.disconnect(motionId);
} }
return false; return false;
})); });
this._cursorTracker.set_pointer_visible(false); global.stage.hide_cursor();
this._lockScreenState = MessageTray.State.SHOWN; this._lockScreenState = MessageTray.State.SHOWN;
this._lockScreenGroup.fixed_position_set = false; this._lockScreenGroup.fixed_position_set = false;
this._lockScreenScrollCounter = 0; this._lockScreenScrollCounter = 0;
if (params.fadeToBlack && params.animateFade) {
// Take a beat
Mainloop.timeout_add(1000 * MANUAL_FADE_TIME, Lang.bind(this, function() {
this._activateFade(this._shortLightbox, MANUAL_FADE_TIME);
}));
} else {
if (params.fadeToBlack)
this._activateFade(this._shortLightbox, 0);
this._completeLockScreenShown();
}
},
_completeLockScreenShown: function() {
let prevIsActive = this._isActive; let prevIsActive = this._isActive;
this._isActive = true; this._isActive = true;
@ -1191,47 +1112,13 @@ const ScreenShield = new Lang.Class({
}, },
deactivate: function(animate) { deactivate: function(animate) {
if (this._dialog)
this._dialog.finish(Lang.bind(this, function() {
this._continueDeactivate(animate);
}));
else
this._continueDeactivate(animate);
},
_continueDeactivate: function(animate) {
this._hideLockScreen(animate, 0); this._hideLockScreen(animate, 0);
if (this._hasLockScreen)
this._clearLockScreen();
if (Main.sessionMode.currentMode == 'lock-screen') if (Main.sessionMode.currentMode == 'lock-screen')
Main.sessionMode.popMode('lock-screen'); Main.sessionMode.popMode('lock-screen');
if (Main.sessionMode.currentMode == 'unlock-dialog') if (Main.sessionMode.currentMode == 'unlock-dialog')
Main.sessionMode.popMode('unlock-dialog'); Main.sessionMode.popMode('unlock-dialog');
if (this._isGreeter) {
// We don't want to "deactivate" any more than
// this. In particular, we don't want to drop
// the modal, hide ourselves or destroy the dialog
// But we do want to set isActive to false, so that
// gnome-session will reset the idle counter, and
// gnome-settings-daemon will stop blanking the screen
this._activationTime = 0;
this._isActive = false;
this.emit('active-changed');
return;
}
if (this._dialog && !this._isGreeter)
this._dialog.popModal();
if (this._isModal) {
Main.popModal(this.actor);
this._isModal = false;
}
Tweener.addTween(this._lockDialogGroup, { Tweener.addTween(this._lockDialogGroup, {
scale_x: 0, scale_x: 0,
scale_y: 0, scale_y: 0,
@ -1243,13 +1130,21 @@ const ScreenShield = new Lang.Class({
}, },
_completeDeactivate: function() { _completeDeactivate: function() {
if (this._dialog) { if (this._hasLockScreen)
this._clearLockScreen();
if (this._dialog && !this._isGreeter) {
this._dialog.destroy(); this._dialog.destroy();
this._dialog = null; this._dialog = null;
} }
this._longLightbox.hide(); this._lightbox.hide();
this._shortLightbox.hide();
if (this._isModal) {
Main.popModal(this.actor);
this._isModal = false;
}
this.actor.hide(); this.actor.hide();
if (this._becameActiveId != 0) { if (this._becameActiveId != 0) {
@ -1267,7 +1162,6 @@ const ScreenShield = new Lang.Class({
this._isLocked = false; this._isLocked = false;
this.emit('active-changed'); this.emit('active-changed');
this.emit('locked-changed'); this.emit('locked-changed');
global.set_runtime_state(LOCKED_STATE_STR, null);
}, },
activate: function(animate) { activate: function(animate) {
@ -1283,9 +1177,7 @@ const ScreenShield = new Lang.Class({
Main.sessionMode.pushMode('unlock-dialog'); Main.sessionMode.pushMode('unlock-dialog');
} }
this._resetLockScreen({ animateLockScreen: animate, this._resetLockScreen(animate, animate);
fadeToBlack: true });
global.set_runtime_state(LOCKED_STATE_STR, GLib.Variant.new('b', true));
// We used to set isActive and emit active-changed here, // We used to set isActive and emit active-changed here,
// but now we do that from lockScreenShown, which means // but now we do that from lockScreenShown, which means
@ -1307,33 +1199,10 @@ const ScreenShield = new Lang.Class({
return; return;
} }
// Clear the clipboard - otherwise, its contents may be leaked this._isLocked = true;
// to unauthorized parties by pasting into the unlock dialog's
// password entry and unmasking the entry
St.Clipboard.get_default().set_text(St.ClipboardType.CLIPBOARD, '');
St.Clipboard.get_default().set_text(St.ClipboardType.PRIMARY, '');
let userManager = AccountsService.UserManager.get_default();
let user = userManager.get_user(GLib.get_user_name());
if (this._isGreeter)
this._isLocked = true;
else
this._isLocked = user.password_mode != AccountsService.UserPasswordMode.NONE;
this.activate(animate); this.activate(animate);
this.emit('locked-changed'); this.emit('locked-changed');
}, },
// If the previous shell crashed, and gnome-session restarted us, then re-lock
lockIfWasLocked: function() {
let wasLocked = global.get_runtime_state('b', LOCKED_STATE_STR);
if (wasLocked === null)
return;
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
this.lock(false);
}));
}
}); });
Signals.addSignalMethods(ScreenShield.prototype); Signals.addSignalMethods(ScreenShield.prototype);

View File

@ -1,149 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const Hash = imports.misc.hash;
const Main = imports.ui.main;
const ScreencastIface = <interface name="org.gnome.Shell.Screencast">
<method name="Screencast">
<arg type="s" direction="in" name="file_template"/>
<arg type="a{sv}" direction="in" name="options"/>
<arg type="b" direction="out" name="success"/>
<arg type="s" direction="out" name="filename_used"/>
</method>
<method name="ScreencastArea">
<arg type="i" direction="in" name="x"/>
<arg type="i" direction="in" name="y"/>
<arg type="i" direction="in" name="width"/>
<arg type="i" direction="in" name="height"/>
<arg type="s" direction="in" name="file_template"/>
<arg type="a{sv}" direction="in" name="options"/>
<arg type="b" direction="out" name="success"/>
<arg type="s" direction="out" name="filename_used"/>
</method>
<method name="StopScreencast">
<arg type="b" direction="out" name="success"/>
</method>
</interface>;
const ScreencastService = new Lang.Class({
Name: 'ScreencastService',
_init: function() {
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreencastIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screencast');
Gio.DBus.session.own_name('org.gnome.Shell.Screencast', Gio.BusNameOwnerFlags.REPLACE, null, null);
this._recorders = new Hash.Map();
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
},
get isRecording() {
return this._recorders.size() > 0;
},
_ensureRecorderForSender: function(sender) {
let recorder = this._recorders.get(sender);
if (!recorder) {
recorder = new Shell.Recorder({ stage: global.stage,
screen: global.screen });
recorder._watchNameId =
Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
Lang.bind(this, this._onNameVanished));
this._recorders.set(sender, recorder);
this.emit('updated');
}
return recorder;
},
_sessionUpdated: function() {
if (Main.sessionMode.allowScreencast)
return;
for (let sender in this._recorders.keys())
this._recorders.delete(sender);
this.emit('updated');
},
_onNameVanished: function(connection, name) {
this._stopRecordingForSender(name);
},
_stopRecordingForSender: function(sender) {
let recorder = this._recorders.get(sender);
if (!recorder)
return false;
Gio.bus_unwatch_name(recorder._watchNameId);
recorder.close();
this._recorders.delete(sender);
this.emit('updated');
return true;
},
_applyOptionalParameters: function(recorder, options) {
for (let option in options)
options[option] = options[option].deep_unpack();
if (options['pipeline'])
recorder.set_pipeline(options['pipeline']);
if (options['framerate'])
recorder.set_framerate(options['framerate']);
if (options['draw-cursor'])
recorder.set_draw_cursor(options['draw-cursor']);
},
ScreencastAsync: function(params, invocation) {
let returnValue = [false, ''];
if (!Main.sessionMode.allowScreencast)
invocation.return_value(GLib.Variant.new('(bs)', returnValue));
let sender = invocation.get_sender();
let recorder = this._ensureRecorderForSender(sender);
if (!recorder.is_recording()) {
let [fileTemplate, options] = params;
recorder.set_file_template(fileTemplate);
this._applyOptionalParameters(recorder, options);
let [success, fileName] = recorder.record();
returnValue = [success, fileName ? fileName : ''];
}
invocation.return_value(GLib.Variant.new('(bs)', returnValue));
},
ScreencastAreaAsync: function(params, invocation) {
let returnValue = [false, ''];
if (!Main.sessionMode.allowScreencast)
invocation.return_value(GLib.Variant.new('(bs)', returnValue));
let sender = invocation.get_sender();
let recorder = this._ensureRecorderForSender(sender);
if (!recorder.is_recording()) {
let [x, y, width, height, fileTemplate, options] = params;
recorder.set_file_template(fileTemplate);
recorder.set_area(x, y, width, height);
this._applyOptionalParameters(recorder, options);
let [success, fileName] = recorder.record();
returnValue = [success, fileName ? fileName : ''];
}
invocation.return_value(GLib.Variant.new('(bs)', returnValue));
},
StopScreencastAsync: function(params, invocation) {
let success = this._stopRecordingForSender(invocation.get_sender());
invocation.return_value(GLib.Variant.new('(b)', [success]));
}
});
Signals.addSignalMethods(ScreencastService.prototype);

View File

@ -6,7 +6,6 @@ const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const St = imports.gi.St; const St = imports.gi.St;
@ -168,7 +167,7 @@ const SelectArea = new Lang.Class({
if (!Main.pushModal(this._group) || this._group.visible) if (!Main.pushModal(this._group) || this._group.visible)
return; return;
global.screen.set_cursor(Meta.Cursor.CROSSHAIR); global.set_cursor(Shell.Cursor.CROSSHAIR);
this._group.visible = true; this._group.visible = true;
}, },
@ -239,7 +238,7 @@ const SelectArea = new Lang.Class({
function() { function() {
Main.popModal(this._group); Main.popModal(this._group);
this._group.destroy(); this._group.destroy();
global.screen.set_cursor(Meta.Cursor.DEFAULT); global.unset_cursor();
this.emit('finished', geometry); this.emit('finished', geometry);
}) })

View File

@ -31,7 +31,7 @@ const SearchSystem = new Lang.Class({
let remoteIndex = this._remoteProviders.indexOf(provider); let remoteIndex = this._remoteProviders.indexOf(provider);
if (remoteIndex != -1) if (remoteIndex != -1)
this._remoteProviders.splice(remoteIndex, 1); this._remoteProviders.splice(index, 1);
}, },
getProviders: function() { getProviders: function() {
@ -51,7 +51,7 @@ const SearchSystem = new Lang.Class({
this._previousResults = []; this._previousResults = [];
}, },
setResults: function(provider, results) { pushResults: function(provider, results) {
let i = this._providers.indexOf(provider); let i = this._providers.indexOf(provider);
if (i == -1) if (i == -1)
return; return;

View File

@ -126,25 +126,23 @@ const GridSearchResult = new Lang.Class({
this.actor.style_class = 'grid-search-result'; this.actor.style_class = 'grid-search-result';
let content = provider.createResultObject(metaInfo, terms); let content = provider.createResultActor(metaInfo, terms);
let dragSource = null; let dragSource = null;
if (content == null) { if (content == null) {
let actor = new St.Bin(); content = new St.Bin();
let icon = new IconGrid.BaseIcon(this.metaInfo['name'], let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
{ createIcon: this.metaInfo['createIcon'] }); { createIcon: this.metaInfo['createIcon'] });
actor.set_child(icon.actor); content.set_child(icon.actor);
actor.label_actor = icon.label; content.label_actor = icon.label;
dragSource = icon.icon; dragSource = icon.icon;
content = { actor: actor, icon: icon };
} else { } else {
if (content._delegate && content._delegate.getDragActorSource) if (content._delegate && content._delegate.getDragActorSource)
dragSource = content._delegate.getDragActorSource(); dragSource = content._delegate.getDragActorSource();
} }
this.actor.set_child(content.actor); this.actor.set_child(content);
this.actor.label_actor = content.actor.label_actor; this.actor.label_actor = content.label_actor;
this.icon = content.icon;
let draggable = DND.makeDraggable(this.actor); let draggable = DND.makeDraggable(this.actor);
draggable.connect('drag-begin', draggable.connect('drag-begin',
@ -182,111 +180,62 @@ const GridSearchResult = new Lang.Class({
} }
}); });
const SearchResultsBase = new Lang.Class({ const ListSearchResults = new Lang.Class({
Name: 'SearchResultsBase', Name: 'ListSearchResults',
_init: function(provider) { _init: function(provider) {
this.provider = provider; this.provider = provider;
this._terms = []; this.actor = new St.BoxLayout({ style_class: 'search-section-content' });
this.actor = new St.BoxLayout({ style_class: 'search-section',
vertical: true });
this._resultDisplayBin = new St.Bin({ x_fill: true,
y_fill: true });
this.actor.add(this._resultDisplayBin, { expand: true });
let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' });
this.actor.add(separator.actor);
},
destroy: function() {
this.actor.destroy();
this._terms = [];
},
_clearResultDisplay: function() {
},
clear: function() {
this._clearResultDisplay();
this.actor.hide();
},
_keyFocusIn: function(actor) {
this.emit('key-focus-in', actor);
},
_setMoreIconVisible: function(visible) {
},
updateSearch: function(providerResults, terms, callback) {
this._terms = terms;
if (providerResults.length == 0) {
this._clearResultDisplay();
this.actor.hide();
callback();
} else {
let maxResults = this._getMaxDisplayedResults();
let results = this.provider.filterResults(providerResults, maxResults);
let hasMoreResults = results.length < providerResults.length;
this.provider.getResultMetas(results, Lang.bind(this, function(metas) {
this.clear();
// To avoid CSS transitions causing flickering when
// the first search result stays the same, we hide the
// content while filling in the results.
this.actor.hide();
this._clearResultDisplay();
this._renderResults(metas);
this._setMoreIconVisible(hasMoreResults && this.provider.canLaunchSearch);
this.actor.show();
callback();
}));
}
}
});
const ListSearchResults = new Lang.Class({
Name: 'ListSearchResults',
Extends: SearchResultsBase,
_init: function(provider) {
this.parent(provider);
this._container = new St.BoxLayout({ style_class: 'search-section-content' });
this.providerIcon = new ProviderIcon(provider); this.providerIcon = new ProviderIcon(provider);
this.providerIcon.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
this.providerIcon.connect('clicked', Lang.bind(this, this.providerIcon.connect('clicked', Lang.bind(this,
function() { function() {
provider.launchSearch(this._terms); provider.launchSearch(this._terms);
Main.overview.toggle(); Main.overview.toggle();
})); }));
this._container.add(this.providerIcon, { x_fill: false, this.actor.add(this.providerIcon, { x_fill: false,
y_fill: false, y_fill: false,
x_align: St.Align.START, x_align: St.Align.START,
y_align: St.Align.START }); y_align: St.Align.START });
this._content = new St.BoxLayout({ style_class: 'list-search-results', this._content = new St.BoxLayout({ style_class: 'list-search-results',
vertical: true }); vertical: true });
this._container.add(this._content, { expand: true }); this.actor.add(this._content, { expand: true });
this._resultDisplayBin.set_child(this._container); this._notDisplayedResult = [];
this._terms = [];
this._pendingClear = false;
}, },
_setMoreIconVisible: function(visible) { getResultsForDisplay: function() {
this.providerIcon.moreIcon.visible = true; let alreadyVisible = this._pendingClear ? 0 : this.getVisibleResultCount();
let canDisplay = MAX_LIST_SEARCH_RESULTS_ROWS - alreadyVisible;
let newResults = this._notDisplayedResult.splice(0, canDisplay);
return newResults;
}, },
_getMaxDisplayedResults: function() { getVisibleResultCount: function() {
return MAX_LIST_SEARCH_RESULTS_ROWS; return this._content.get_n_children();
}, },
_renderResults: function(metas) { hasMoreResults: function() {
return this._notDisplayedResult.length > 0;
},
setResults: function(results, terms) {
// copy the lists
this._notDisplayedResult = results.slice(0);
this._terms = terms.slice(0);
this._pendingClear = true;
},
_keyFocusIn: function(icon) {
this.emit('key-focus-in', icon);
},
renderResults: function(metas) {
for (let i = 0; i < metas.length; i++) { for (let i = 0; i < metas.length; i++) {
let display = new ListSearchResult(this.provider, metas[i], this._terms); let display = new ListSearchResult(this.provider, metas[i], this._terms);
display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
@ -294,12 +243,13 @@ const ListSearchResults = new Lang.Class({
} }
}, },
_clearResultDisplay: function () { clear: function () {
this._content.destroy_all_children(); this._content.destroy_all_children();
this._pendingClear = false;
}, },
getFirstResult: function() { getFirstResult: function() {
if (this._content.get_n_children() > 0) if (this.getVisibleResultCount() > 0)
return this._content.get_child_at_index(0)._delegate; return this._content.get_child_at_index(0)._delegate;
else else
return null; return null;
@ -309,37 +259,64 @@ Signals.addSignalMethods(ListSearchResults.prototype);
const GridSearchResults = new Lang.Class({ const GridSearchResults = new Lang.Class({
Name: 'GridSearchResults', Name: 'GridSearchResults',
Extends: SearchResultsBase,
_init: function(provider) { _init: function(provider) {
this.parent(provider); this.provider = provider;
this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS, this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
xAlign: St.Align.START }); xAlign: St.Align.START });
this._bin = new St.Bin({ x_align: St.Align.MIDDLE }); this.actor = new St.Bin({ x_align: St.Align.MIDDLE });
this._bin.set_child(this._grid.actor);
this._resultDisplayBin.set_child(this._bin); this.actor.set_child(this._grid.actor);
this._notDisplayedResult = [];
this._terms = [];
this._pendingClear = false;
}, },
_getMaxDisplayedResults: function() { getResultsForDisplay: function() {
return this._grid.columnsForWidth(this._bin.width) * this._grid.getRowLimit(); let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
let canDisplay = this._grid.childrenInRow(this.actor.width) * this._grid.getRowLimit()
- alreadyVisible;
let newResults = this._notDisplayedResult.splice(0, canDisplay);
return newResults;
}, },
_renderResults: function(metas) { getVisibleResultCount: function() {
return this._grid.visibleItemsCount();
},
hasMoreResults: function() {
return this._notDisplayedResult.length > 0;
},
setResults: function(results, terms) {
// copy the lists
this._notDisplayedResult = results.slice(0);
this._terms = terms.slice(0);
this._pendingClear = true;
},
_keyFocusIn: function(icon) {
this.emit('key-focus-in', icon);
},
renderResults: function(metas) {
for (let i = 0; i < metas.length; i++) { for (let i = 0; i < metas.length; i++) {
let display = new GridSearchResult(this.provider, metas[i], this._terms); let display = new GridSearchResult(this.provider, metas[i], this._terms);
display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
this._grid.addItem(display); this._grid.addItem(display.actor);
} }
}, },
_clearResultDisplay: function () { clear: function () {
this._grid.removeAll(); this._grid.removeAll();
this._pendingClear = false;
}, },
getFirstResult: function() { getFirstResult: function() {
if (this._grid.visibleItemsCount() > 0) if (this.getVisibleResultCount() > 0)
return this._grid.getItemAtIndex(0)._delegate; return this._grid.getItemAtIndex(0)._delegate;
else else
return null; return null;
@ -389,9 +366,9 @@ const SearchResults = new Lang.Class({
this._content.add(this._statusBin, { expand: true }); this._content.add(this._statusBin, { expand: true });
this._statusBin.add_actor(this._statusText); this._statusBin.add_actor(this._statusText);
this._providers = this._searchSystem.getProviders(); this._providers = this._searchSystem.getProviders();
this._providerDisplays = {}; this._providerMeta = [];
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providers.length; i++) {
this.createProviderDisplay(this._providers[i]); this.createProviderMeta(this._providers[i]);
} }
this._highlightDefault = false; this._highlightDefault = false;
@ -405,37 +382,65 @@ const SearchResults = new Lang.Class({
return false; return false;
}, },
_keyFocusIn: function(provider, actor) { _keyFocusIn: function(provider, icon) {
Util.ensureActorVisibleInScrollView(this._scrollView, actor); Util.ensureActorVisibleInScrollView(this._scrollView, icon);
}, },
createProviderDisplay: function(provider) { createProviderMeta: function(provider) {
let providerDisplay = null; let providerBox = new St.BoxLayout({ style_class: 'search-section',
vertical: true });
let providerIcon = null;
let resultDisplay = null;
if (provider.appInfo) { if (provider.appInfo) {
providerDisplay = new ListSearchResults(provider); resultDisplay = new ListSearchResults(provider);
providerIcon = resultDisplay.providerIcon;
} else { } else {
providerDisplay = new GridSearchResults(provider); resultDisplay = new GridSearchResults(provider);
} }
providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); resultDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
this._providerDisplays[provider.id] = providerDisplay;
this._content.add(providerDisplay.actor); let resultDisplayBin = new St.Bin({ child: resultDisplay.actor,
x_fill: true,
y_fill: true });
providerBox.add(resultDisplayBin, { expand: true });
let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' });
providerBox.add(separator.actor);
this._providerMeta.push({ provider: provider,
actor: providerBox,
icon: providerIcon,
resultDisplay: resultDisplay });
this._content.add(providerBox);
}, },
destroyProviderDisplay: function(provider) { destroyProviderMeta: function(provider) {
this._providerDisplays[provider.id].destroy(); for (let i=0; i < this._providerMeta.length; i++) {
delete this._providerDisplays[provider.id]; let meta = this._providerMeta[i];
if (meta.provider == provider) {
meta.actor.destroy();
this._providerMeta.splice(i, 1);
break;
}
}
}, },
_clearDisplay: function() { _clearDisplay: function() {
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providerMeta.length; i++) {
let provider = this._providers[i]; let meta = this._providerMeta[i];
let providerDisplay = this._providerDisplays[provider.id]; meta.resultDisplay.clear();
providerDisplay.clear(); meta.actor.hide();
} }
}, },
_clearDisplayForProvider: function(provider) {
let meta = this._metaForProvider(provider);
meta.resultDisplay.clear();
meta.actor.hide();
},
reset: function() { reset: function() {
this._searchSystem.reset(); this._searchSystem.reset();
this._statusBin.hide(); this._statusBin.hide();
@ -449,17 +454,20 @@ const SearchResults = new Lang.Class({
this._statusBin.show(); this._statusBin.show();
}, },
_metaForProvider: function(provider) {
return this._providerMeta[this._providers.indexOf(provider)];
},
_maybeSetInitialSelection: function() { _maybeSetInitialSelection: function() {
let newDefaultResult = null; let newDefaultResult = null;
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providerMeta.length; i++) {
let provider = this._providers[i]; let meta = this._providerMeta[i];
let display = this._providerDisplays[provider.id];
if (!display.actor.visible) if (!meta.actor.visible)
continue; continue;
let firstResult = display.getFirstResult(); let firstResult = meta.resultDisplay.getFirstResult();
if (firstResult) { if (firstResult) {
newDefaultResult = firstResult; newDefaultResult = firstResult;
break; // select this one! break; // select this one!
@ -479,14 +487,11 @@ const SearchResults = new Lang.Class({
_updateStatusText: function () { _updateStatusText: function () {
let haveResults = false; let haveResults = false;
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providerMeta.length; ++i)
let provider = this._providers[i]; if (this._providerMeta[i].resultDisplay.getFirstResult()) {
let display = this._providerDisplays[provider.id];
if (display.getFirstResult()) {
haveResults = true; haveResults = true;
break; break;
} }
}
if (!haveResults) { if (!haveResults) {
this._statusText.set_text(_("No results.")); this._statusText.set_text(_("No results."));
@ -499,12 +504,42 @@ const SearchResults = new Lang.Class({
_updateResults: function(searchSystem, results) { _updateResults: function(searchSystem, results) {
let terms = searchSystem.getTerms(); let terms = searchSystem.getTerms();
let [provider, providerResults] = results; let [provider, providerResults] = results;
let display = this._providerDisplays[provider.id]; let meta = this._metaForProvider(provider);
display.updateSearch(providerResults, terms, Lang.bind(this, function() { if (providerResults.length == 0) {
this._clearDisplayForProvider(provider);
meta.resultDisplay.setResults([], []);
this._maybeSetInitialSelection(); this._maybeSetInitialSelection();
this._updateStatusText(); this._updateStatusText();
})); } else {
meta.resultDisplay.setResults(providerResults, terms);
let results = meta.resultDisplay.getResultsForDisplay();
if (meta.icon)
meta.icon.moreIcon.visible =
meta.resultDisplay.hasMoreResults() &&
provider.canLaunchSearch;
provider.getResultMetas(results, Lang.bind(this, function(metas) {
this._clearDisplayForProvider(provider);
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);
this._maybeSetInitialSelection();
this._updateStatusText();
this._content.show();
if (this._content.contains(focus))
global.stage.set_key_focus(focus);
}));
}
}, },
activateDefault: function() { activateDefault: function() {

View File

@ -16,12 +16,10 @@ const _modes = {
'restrictive': { 'restrictive': {
parentMode: null, parentMode: null,
stylesheetName: 'gnome-shell.css', stylesheetName: 'gnome-shell.css',
overridesSchema: 'org.gnome.shell.overrides',
hasOverview: false, hasOverview: false,
showCalendarEvents: false, showCalendarEvents: false,
allowSettings: false, allowSettings: false,
allowExtensions: false, allowExtensions: false,
allowScreencast: false,
enabledExtensions: [], enabledExtensions: [],
hasRunDialog: false, hasRunDialog: false,
hasWorkspaces: false, hasWorkspaces: false,
@ -47,9 +45,10 @@ const _modes = {
unlockDialog: imports.gdm.loginDialog.LoginDialog, unlockDialog: imports.gdm.loginDialog.LoginDialog,
components: ['polkitAgent'], components: ['polkitAgent'],
panel: { panel: {
left: [], left: ['logo'],
center: ['dateMenu'], center: ['dateMenu'],
right: ['a11yGreeter', 'keyboard', 'aggregateMenu'], right: ['a11yGreeter', 'display', 'keyboard',
'volume', 'battery', 'powerMenu']
}, },
panelStyle: 'login-screen' panelStyle: 'login-screen'
}, },
@ -60,9 +59,9 @@ const _modes = {
unlockDialog: undefined, unlockDialog: undefined,
components: ['polkitAgent', 'telepathyClient'], components: ['polkitAgent', 'telepathyClient'],
panel: { panel: {
left: [], left: ['userMenu'],
center: [], center: [],
right: ['aggregateMenu'] right: ['lockScreen']
}, },
panelStyle: 'lock-screen' panelStyle: 'lock-screen'
}, },
@ -72,19 +71,29 @@ const _modes = {
unlockDialog: undefined, unlockDialog: undefined,
components: ['polkitAgent', 'telepathyClient'], components: ['polkitAgent', 'telepathyClient'],
panel: { panel: {
left: [], left: ['userMenu'],
center: [], center: [],
right: ['a11y', 'keyboard', 'aggregateMenu'] right: ['a11y', 'keyboard', 'lockScreen']
}, },
panelStyle: 'unlock-screen' panelStyle: 'unlock-screen'
}, },
'initial-setup': {
hasWindows: true,
isPrimary: true,
components: ['keyring'],
panel: {
left: [],
center: ['dateMenu'],
right: ['a11yGreeter', 'keyboard', 'volume', 'battery']
}
},
'user': { 'user': {
hasOverview: true, hasOverview: true,
showCalendarEvents: true, showCalendarEvents: true,
allowSettings: true, allowSettings: true,
allowExtensions: true, allowExtensions: true,
allowScreencast: true,
hasRunDialog: true, hasRunDialog: true,
hasWorkspaces: true, hasWorkspaces: true,
hasWindows: true, hasWindows: true,
@ -93,11 +102,12 @@ const _modes = {
isPrimary: true, isPrimary: true,
unlockDialog: imports.ui.unlockDialog.UnlockDialog, unlockDialog: imports.ui.unlockDialog.UnlockDialog,
components: ['networkAgent', 'polkitAgent', 'telepathyClient', components: ['networkAgent', 'polkitAgent', 'telepathyClient',
'keyring', 'autorunManager', 'automountManager'], 'keyring', 'recorder', 'autorunManager', 'automountManager'],
panel: { panel: {
left: ['activities', 'appMenu'], left: ['activities', 'appMenu'],
center: ['dateMenu'], center: ['dateMenu'],
right: ['a11y', 'keyboard', 'aggregateMenu'] right: ['a11y', 'keyboard', 'volume', 'bluetooth',
'network', 'battery', 'userMenu']
} }
} }
}; };

View File

@ -13,7 +13,6 @@ const ExtensionUtils = imports.misc.extensionUtils;
const Hash = imports.misc.hash; const Hash = imports.misc.hash;
const Main = imports.ui.main; const Main = imports.ui.main;
const Screenshot = imports.ui.screenshot; const Screenshot = imports.ui.screenshot;
const ViewSelector = imports.ui.viewSelector;
const GnomeShellIface = <interface name="org.gnome.Shell"> const GnomeShellIface = <interface name="org.gnome.Shell">
<method name="Eval"> <method name="Eval">
@ -21,14 +20,9 @@ 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="FocusSearch"/>
<method name="ShowOSD"> <method name="ShowOSD">
<arg type="a{sv}" direction="in" name="params"/> <arg type="a{sv}" direction="in" name="params"/>
</method> </method>
<method name="FocusApp">
<arg type="s" direction="in" name="id"/>
</method>
<method name="ShowApplications" />
<method name="GrabAccelerator"> <method name="GrabAccelerator">
<arg type="s" direction="in" name="accelerator"/> <arg type="s" direction="in" name="accelerator"/>
<arg type="u" direction="in" name="flags"/> <arg type="u" direction="in" name="flags"/>
@ -45,7 +39,6 @@ const GnomeShellIface = <interface name="org.gnome.Shell">
<signal name="AcceleratorActivated"> <signal name="AcceleratorActivated">
<arg name="action" type="u" /> <arg name="action" type="u" />
<arg name="deviceid" type="u" /> <arg name="deviceid" type="u" />
<arg name="timestamp" type="u" />
</signal> </signal>
<property name="Mode" type="s" access="read" /> <property name="Mode" type="s" access="read" />
<property name="OverviewActive" type="b" access="readwrite" /> <property name="OverviewActive" type="b" access="readwrite" />
@ -83,8 +76,8 @@ const GnomeShell = new Lang.Class({
this._grabbers = new Hash.Map(); this._grabbers = new Hash.Map();
global.display.connect('accelerator-activated', Lang.bind(this, global.display.connect('accelerator-activated', Lang.bind(this,
function(display, action, deviceid, timestamp) { function(display, action, deviceid) {
this._emitAcceleratorActivated(action, deviceid, timestamp); this._emitAcceleratorActivated(action, deviceid);
})); }));
}, },
@ -104,7 +97,7 @@ const GnomeShell = new Lang.Class({
*/ */
Eval: function(code) { Eval: function(code) {
if (!global.settings.get_boolean('development-tools')) if (!global.settings.get_boolean('development-tools'))
return [false, '']; return [false, null];
let returnValue; let returnValue;
let success; let success;
@ -121,10 +114,6 @@ const GnomeShell = new Lang.Class({
return [success, returnValue]; return [success, returnValue];
}, },
FocusSearch: function() {
Main.overview.focusSearch();
},
ShowOSD: function(params) { ShowOSD: function(params) {
for (let param in params) for (let param in params)
params[param] = params[param].deep_unpack(); params[param] = params[param].deep_unpack();
@ -140,15 +129,6 @@ const GnomeShell = new Lang.Class({
Main.osdWindow.show(); Main.osdWindow.show();
}, },
FocusApp: function(id) {
this.ShowApplications();
Main.overview.viewSelector.appDisplay.selectApp(id);
},
ShowApplications: function() {
Main.overview.viewSelector.showApps();
},
GrabAcceleratorAsync: function(params, invocation) { GrabAcceleratorAsync: function(params, invocation) {
let [accel, flags] = params; let [accel, flags] = params;
let sender = invocation.get_sender(); let sender = invocation.get_sender();
@ -179,7 +159,7 @@ const GnomeShell = new Lang.Class({
return invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded])); return invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded]));
}, },
_emitAcceleratorActivated: function(action, deviceid, timestamp) { _emitAcceleratorActivated: function(action, deviceid) {
let destination = this._grabbedAccelerators.get(action); let destination = this._grabbedAccelerators.get(action);
if (!destination) if (!destination)
return; return;
@ -190,7 +170,7 @@ const GnomeShell = new Lang.Class({
this._dbusImpl.get_object_path(), this._dbusImpl.get_object_path(),
info ? info.name : null, info ? info.name : null,
'AcceleratorActivated', 'AcceleratorActivated',
GLib.Variant.new('(uuu)', [action, deviceid, timestamp])); GLib.Variant.new('(uu)', [action, deviceid]));
}, },
_grabAcceleratorForSender: function(accelerator, flags, sender) { _grabAcceleratorForSender: function(accelerator, flags, sender) {

View File

@ -14,7 +14,9 @@ const EntryMenu = new Lang.Class({
Name: 'ShellEntryMenu', Name: 'ShellEntryMenu',
Extends: PopupMenu.PopupMenu, Extends: PopupMenu.PopupMenu,
_init: function(entry) { _init: function(entry, params) {
params = Params.parse (params, { isPassword: false });
this.parent(entry, 0, St.Side.TOP); this.parent(entry, 0, St.Side.TOP);
this.actor.add_style_class_name('entry-context-menu'); this.actor.add_style_class_name('entry-context-menu');
@ -35,6 +37,8 @@ const EntryMenu = new Lang.Class({
this._pasteItem = item; this._pasteItem = item;
this._passwordItem = null; this._passwordItem = null;
if (params.isPassword)
this._makePasswordItem();
Main.uiGroup.add_actor(this.actor); Main.uiGroup.add_actor(this.actor);
this.actor.hide(); this.actor.hide();
@ -49,21 +53,19 @@ const EntryMenu = new Lang.Class({
}, },
get isPassword() { get isPassword() {
return this._passwordItem != null; return this._passwordItem != null;
}, },
set isPassword(v) { set isPassword(v) {
if (v == this.isPassword) if (v == this.isPassword)
return; return;
if (v) { if (v)
this._makePasswordItem(); this._makePasswordItem();
this._entry.input_purpose = Gtk.InputPurpose.PASSWORD; else {
} else { this._passwordItem.destroy();
this._passwordItem.destroy(); this._passwordItem = null;
this._passwordItem = null; }
this._entry.input_purpose = Gtk.InputPurpose.FREE_FORM;
}
}, },
open: function(animate) { open: function(animate) {
@ -80,6 +82,11 @@ const EntryMenu = new Lang.Class({
this.actor.grab_key_focus(); this.actor.grab_key_focus();
}, },
close: function(animate) {
this._entry.grab_key_focus();
this.parent(animate);
},
_updateCopyItem: function() { _updateCopyItem: function() {
let selection = this._entry.clutter_text.get_selection(); let selection = this._entry.clutter_text.get_selection();
this._copyItem.setSensitive(!this._entry.clutter_text.password_char && this._copyItem.setSensitive(!this._entry.clutter_text.password_char &&
@ -153,10 +160,7 @@ function addContextMenu(entry, params) {
if (entry.menu) if (entry.menu)
return; return;
params = Params.parse (params, { isPassword: false }); entry.menu = new EntryMenu(entry, params);
entry.menu = new EntryMenu(entry);
entry.menu.isPassword = params.isPassword;
entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry }); entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry });
entry._menuManager.addMenu(entry.menu); entry._menuManager.addMenu(entry.menu);

View File

@ -1,247 +0,0 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const St = imports.gi.St;
const Signals = imports.signals;
const Atk = imports.gi.Atk;
const SLIDER_SCROLL_STEP = 0.05; /* Slider scrolling step in % */
const Slider = new Lang.Class({
Name: "Slider",
_init: function(value) {
if (isNaN(value))
// Avoid spreading NaNs around
throw TypeError('The slider value must be a number');
this._value = Math.max(Math.min(value, 1), 0);
this.actor = new St.DrawingArea({ style_class: 'slider',
can_focus: true,
reactive: true,
accessible_role: Atk.Role.SLIDER });
this.actor.connect('repaint', Lang.bind(this, this._sliderRepaint));
this.actor.connect('button-press-event', Lang.bind(this, this._startDragging));
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
this.actor.connect('key-press-event', Lang.bind(this, this.onKeyPressEvent));
this._releaseId = this._motionId = 0;
this._dragging = false;
this._customAccessible = St.GenericAccessible.new_for_actor(this.actor);
this.actor.set_accessible(this._customAccessible);
this._customAccessible.connect('get-current-value', Lang.bind(this, this._getCurrentValue));
this._customAccessible.connect('get-minimum-value', Lang.bind(this, this._getMinimumValue));
this._customAccessible.connect('get-maximum-value', Lang.bind(this, this._getMaximumValue));
this._customAccessible.connect('get-minimum-increment', Lang.bind(this, this._getMinimumIncrement));
this._customAccessible.connect('set-current-value', Lang.bind(this, this._setCurrentValue));
this.connect('value-changed', Lang.bind(this, this._valueChanged));
},
setValue: function(value) {
if (isNaN(value))
throw TypeError('The slider value must be a number');
this._value = Math.max(Math.min(value, 1), 0);
this.actor.queue_repaint();
},
_sliderRepaint: function(area) {
let cr = area.get_context();
let themeNode = area.get_theme_node();
let [width, height] = area.get_surface_size();
let handleRadius = themeNode.get_length('-slider-handle-radius');
let handleBorderWidth = themeNode.get_length('-slider-handle-border-width');
let [hasHandleColor, handleBorderColor] =
themeNode.lookup_color('-slider-handle-border-color', false);
let sliderHeight = themeNode.get_length('-slider-height');
let sliderBorderWidth = themeNode.get_length('-slider-border-width');
let sliderBorderRadius = Math.min(width, sliderHeight) / 2;
let sliderBorderColor = themeNode.get_color('-slider-border-color');
let sliderColor = themeNode.get_color('-slider-background-color');
let sliderActiveBorderColor = themeNode.get_color('-slider-active-border-color');
let sliderActiveColor = themeNode.get_color('-slider-active-background-color');
const TAU = Math.PI * 2;
let handleX = handleRadius + (width - 2 * handleRadius) * this._value;
cr.arc(sliderBorderRadius + sliderBorderWidth, height / 2, sliderBorderRadius, TAU * 1/4, TAU * 3/4);
cr.lineTo(handleX, (height - sliderHeight) / 2);
cr.lineTo(handleX, (height + sliderHeight) / 2);
cr.lineTo(sliderBorderRadius + sliderBorderWidth, (height + sliderHeight) / 2);
Clutter.cairo_set_source_color(cr, sliderActiveColor);
cr.fillPreserve();
Clutter.cairo_set_source_color(cr, sliderActiveBorderColor);
cr.setLineWidth(sliderBorderWidth);
cr.stroke();
cr.arc(width - sliderBorderRadius - sliderBorderWidth, height / 2, sliderBorderRadius, TAU * 3/4, TAU * 1/4);
cr.lineTo(handleX, (height + sliderHeight) / 2);
cr.lineTo(handleX, (height - sliderHeight) / 2);
cr.lineTo(width - sliderBorderRadius - sliderBorderWidth, (height - sliderHeight) / 2);
Clutter.cairo_set_source_color(cr, sliderColor);
cr.fillPreserve();
Clutter.cairo_set_source_color(cr, sliderBorderColor);
cr.setLineWidth(sliderBorderWidth);
cr.stroke();
let handleY = height / 2;
let color = themeNode.get_foreground_color();
Clutter.cairo_set_source_color(cr, color);
cr.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI);
cr.fillPreserve();
if (hasHandleColor && handleBorderWidth) {
Clutter.cairo_set_source_color(cr, handleBorderColor);
cr.setLineWidth(handleBorderWidth);
cr.stroke();
}
cr.$dispose();
},
_startDragging: function(actor, event) {
this.startDragging(event);
},
startDragging: function(event) {
if (this._dragging)
return false;
this._dragging = true;
let device = event.get_device();
device.grab(this.actor);
this._grabbedDevice = device;
this._releaseId = this.actor.connect('button-release-event', Lang.bind(this, this._endDragging));
this._motionId = this.actor.connect('motion-event', Lang.bind(this, this._motionEvent));
let absX, absY;
[absX, absY] = event.get_coords();
this._moveHandle(absX, absY);
return true;
},
_endDragging: function() {
if (this._dragging) {
this.actor.disconnect(this._releaseId);
this.actor.disconnect(this._motionId);
this._grabbedDevice.ungrab();
this._grabbedDevice = null;
this._dragging = false;
this.emit('drag-end');
}
return true;
},
scroll: function(event) {
let direction = event.get_scroll_direction();
let delta;
if (event.is_pointer_emulated())
return;
if (direction == Clutter.ScrollDirection.DOWN) {
delta = -SLIDER_SCROLL_STEP;
} else if (direction == Clutter.ScrollDirection.UP) {
delta = +SLIDER_SCROLL_STEP;
} else if (direction == Clutter.ScrollDirection.SMOOTH) {
let [dx, dy] = event.get_scroll_delta();
// Even though the slider is horizontal, use dy to match
// the UP/DOWN above.
delta = -dy / 10;
}
this._value = Math.min(Math.max(0, this._value + delta), 1);
this.actor.queue_repaint();
this.emit('value-changed', this._value);
},
_onScrollEvent: function(actor, event) {
this.scroll(event);
},
_motionEvent: function(actor, event) {
let absX, absY;
[absX, absY] = event.get_coords();
this._moveHandle(absX, absY);
return true;
},
onKeyPressEvent: function (actor, event) {
let key = event.get_key_symbol();
if (key == Clutter.KEY_Right || key == Clutter.KEY_Left) {
let delta = key == Clutter.KEY_Right ? 0.1 : -0.1;
this._value = Math.max(0, Math.min(this._value + delta, 1));
this.actor.queue_repaint();
this.emit('value-changed', this._value);
this.emit('drag-end');
return true;
}
return false;
},
_moveHandle: function(absX, absY) {
let relX, relY, sliderX, sliderY;
[sliderX, sliderY] = this.actor.get_transformed_position();
relX = absX - sliderX;
relY = absY - sliderY;
let width = this.actor.width;
let handleRadius = this.actor.get_theme_node().get_length('-slider-handle-radius');
let newvalue;
if (relX < handleRadius)
newvalue = 0;
else if (relX > width - handleRadius)
newvalue = 1;
else
newvalue = (relX - handleRadius) / (width - 2 * handleRadius);
this._value = newvalue;
this.actor.queue_repaint();
this.emit('value-changed', this._value);
},
_getCurrentValue: function (actor) {
return this._value;
},
_getMinimumValue: function (actor) {
return 0;
},
_getMaximumValue: function (actor) {
return 1;
},
_getMinimumIncrement: function (actor) {
return 0.1;
},
_setCurrentValue: function (actor, value) {
this._value = value;
},
_valueChanged: function (slider, value, property) {
this._customAccessible.notify ("accessible-value");
},
get value() {
return this._value;
}
});
Signals.addSignalMethods(Slider.prototype);

View File

@ -1,54 +1,39 @@
// -*- 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 Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const St = imports.gi.St;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const A11Y_SCHEMA = 'org.gnome.desktop.a11y'; const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
const KEY_ALWAYS_SHOW = 'always-show-universal-access-status'; const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable';
const KEY_BOUNCE_KEYS_ENABLED = 'bouncekeys-enable';
const KEY_SLOW_KEYS_ENABLED = 'slowkeys-enable';
const KEY_MOUSE_KEYS_ENABLED = 'mousekeys-enable';
const A11Y_KEYBOARD_SCHEMA = 'org.gnome.desktop.a11y.keyboard'; const APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications';
const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable';
const KEY_BOUNCE_KEYS_ENABLED = 'bouncekeys-enable';
const KEY_SLOW_KEYS_ENABLED = 'slowkeys-enable';
const KEY_MOUSE_KEYS_ENABLED = 'mousekeys-enable';
const APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications'; const DPI_FACTOR_LARGE = 1.25;
const DPI_FACTOR_LARGE = 1.25; const WM_SCHEMA = 'org.gnome.desktop.wm.preferences';
const KEY_VISUAL_BELL = 'visual-bell';
const WM_SCHEMA = 'org.gnome.desktop.wm.preferences'; const DESKTOP_INTERFACE_SCHEMA = 'org.gnome.desktop.interface';
const KEY_VISUAL_BELL = 'visual-bell'; const KEY_GTK_THEME = 'gtk-theme';
const KEY_ICON_THEME = 'icon-theme';
const KEY_WM_THEME = 'theme';
const KEY_TEXT_SCALING_FACTOR = 'text-scaling-factor';
const DESKTOP_INTERFACE_SCHEMA = 'org.gnome.desktop.interface'; const HIGH_CONTRAST_THEME = 'HighContrast';
const KEY_GTK_THEME = 'gtk-theme';
const KEY_ICON_THEME = 'icon-theme';
const KEY_WM_THEME = 'theme';
const KEY_TEXT_SCALING_FACTOR = 'text-scaling-factor';
const HIGH_CONTRAST_THEME = 'HighContrast';
const ATIndicator = new Lang.Class({ const ATIndicator = new Lang.Class({
Name: 'ATIndicator', Name: 'ATIndicator',
Extends: PanelMenu.Button, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
this.parent(0.0, _("Accessibility")); this.parent('preferences-desktop-accessibility-symbolic', _("Accessibility"));
this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
this._hbox.add_child(new St.Icon({ style_class: 'system-status-icon',
icon_name: 'preferences-desktop-accessibility-symbolic' }));
this._hbox.add_child(PopupMenu.unicodeArrow(St.Side.BOTTOM));
this.actor.add_child(this._hbox);
this._a11ySettings = new Gio.Settings({ schema: A11Y_SCHEMA });
this._a11ySettings.connect('changed::' + KEY_ALWAYS_SHOW, Lang.bind(this, this._queueSyncMenuVisibility));
let highContrast = this._buildHCItem(); let highContrast = this._buildHCItem();
this.menu.addMenuItem(highContrast); this.menu.addMenuItem(highContrast);
@ -71,28 +56,30 @@ const ATIndicator = new Lang.Class({
let visualBell = this._buildItem(_("Visual Alerts"), WM_SCHEMA, KEY_VISUAL_BELL); let visualBell = this._buildItem(_("Visual Alerts"), WM_SCHEMA, KEY_VISUAL_BELL);
this.menu.addMenuItem(visualBell); this.menu.addMenuItem(visualBell);
let stickyKeys = this._buildItem(_("Sticky Keys"), A11Y_KEYBOARD_SCHEMA, KEY_STICKY_KEYS_ENABLED); let stickyKeys = this._buildItem(_("Sticky Keys"), A11Y_SCHEMA, KEY_STICKY_KEYS_ENABLED);
this.menu.addMenuItem(stickyKeys); this.menu.addMenuItem(stickyKeys);
let slowKeys = this._buildItem(_("Slow Keys"), A11Y_KEYBOARD_SCHEMA, KEY_SLOW_KEYS_ENABLED); let slowKeys = this._buildItem(_("Slow Keys"), A11Y_SCHEMA, KEY_SLOW_KEYS_ENABLED);
this.menu.addMenuItem(slowKeys); this.menu.addMenuItem(slowKeys);
let bounceKeys = this._buildItem(_("Bounce Keys"), A11Y_KEYBOARD_SCHEMA, KEY_BOUNCE_KEYS_ENABLED); let bounceKeys = this._buildItem(_("Bounce Keys"), A11Y_SCHEMA, KEY_BOUNCE_KEYS_ENABLED);
this.menu.addMenuItem(bounceKeys); this.menu.addMenuItem(bounceKeys);
let mouseKeys = this._buildItem(_("Mouse Keys"), A11Y_KEYBOARD_SCHEMA, KEY_MOUSE_KEYS_ENABLED); let mouseKeys = this._buildItem(_("Mouse Keys"), A11Y_SCHEMA, KEY_MOUSE_KEYS_ENABLED);
this.menu.addMenuItem(mouseKeys); this.menu.addMenuItem(mouseKeys);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addSettingsAction(_("Universal Access Settings"), 'gnome-universal-access-panel.desktop');
this._syncMenuVisibility(); this._syncMenuVisibility();
}, },
_syncMenuVisibility: function() { _syncMenuVisibility: function() {
this._syncMenuVisibilityIdle = 0; this._syncMenuVisibilityIdle = 0;
let alwaysShow = this._a11ySettings.get_boolean(KEY_ALWAYS_SHOW);
let items = this.menu._getMenuItems(); let items = this.menu._getMenuItems();
this.actor.visible = alwaysShow || items.some(function(f) { return !!f.state; }); this.actor.visible = items.some(function(f) { return !!f.state; });
return false; return false;
}, },

View File

@ -9,70 +9,292 @@ const St = imports.gi.St;
const Main = imports.ui.main; const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const NotificationDaemon = imports.ui.notificationDaemon;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const ConnectionState = {
DISCONNECTED: 0,
CONNECTED: 1,
DISCONNECTING: 2,
CONNECTING: 3
}
const Indicator = new Lang.Class({ const Indicator = new Lang.Class({
Name: 'BTIndicator', Name: 'BTIndicator',
Extends: PanelMenu.SystemIndicator, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
this.parent(); this.parent('bluetooth-disabled-symbolic', _("Bluetooth"));
this._indicator = this._addIndicator();
this._indicator.icon_name = 'bluetooth-active-symbolic';
// The Bluetooth menu only appears when Bluetooth is in use,
// so just statically build it with a "Turn Off" menu item.
this._item = new PopupMenu.PopupSubMenuMenuItem(_("Bluetooth"), true);
this._item.icon.icon_name = 'bluetooth-active-symbolic';
this._item.menu.addAction(_("Turn Off"), Lang.bind(this, function() {
this._applet.killswitch_state = GnomeBluetooth.KillswitchState.SOFT_BLOCKED;
}));
this._item.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-bluetooth-panel.desktop');
this.menu.addMenuItem(this._item);
this._applet = new GnomeBluetoothApplet.Applet(); this._applet = new GnomeBluetoothApplet.Applet();
this._applet.connect('devices-changed', Lang.bind(this, this._sync));
this._sync(); this._killswitch = new PopupMenu.PopupSwitchMenuItem(_("Bluetooth"), false);
this._applet.connect('notify::killswitch-state', Lang.bind(this, this._updateKillswitch));
this._killswitch.connect('toggled', Lang.bind(this, function() {
let current_state = this._applet.killswitch_state;
if (current_state != GnomeBluetooth.KillswitchState.HARD_BLOCKED &&
current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER) {
this._applet.killswitch_state = this._killswitch.state ?
GnomeBluetooth.KillswitchState.UNBLOCKED:
GnomeBluetooth.KillswitchState.SOFT_BLOCKED;
} else
this._killswitch.setToggleState(false);
}));
this._discoverable = new PopupMenu.PopupSwitchMenuItem(_("Visibility"), this._applet.discoverable);
this._applet.connect('notify::discoverable', Lang.bind(this, function() {
this._discoverable.setToggleState(this._applet.discoverable);
}));
this._discoverable.connect('toggled', Lang.bind(this, function() {
this._applet.discoverable = this._discoverable.state;
}));
this._updateKillswitch();
this.menu.addMenuItem(this._killswitch);
this.menu.addMenuItem(this._discoverable);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._fullMenuItems = [new PopupMenu.PopupSeparatorMenuItem(),
new PopupMenu.PopupMenuItem(_("Send Files to Device…")),
new PopupMenu.PopupMenuItem(_("Set Up a New Device…")),
new PopupMenu.PopupSeparatorMenuItem()];
this._hasDevices = false;
this._fullMenuItems[1].connect('activate', function() {
GLib.spawn_command_line_async('bluetooth-sendto');
});
this._fullMenuItems[2].connect('activate', function() {
GLib.spawn_command_line_async('bluetooth-wizard');
});
for (let i = 0; i < this._fullMenuItems.length; i++) {
let item = this._fullMenuItems[i];
this.menu.addMenuItem(item);
}
this._deviceItemPosition = 3;
this._deviceItems = [];
this._applet.connect('devices-changed', Lang.bind(this, this._updateDevices));
this._updateDevices();
this._applet.connect('notify::show-full-menu', Lang.bind(this, this._updateFullMenu));
this._updateFullMenu();
this.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-bluetooth-panel.desktop');
this._applet.connect('pincode-request', Lang.bind(this, this._pinRequest)); this._applet.connect('pincode-request', Lang.bind(this, this._pinRequest));
this._applet.connect('confirm-request', Lang.bind(this, this._confirmRequest)); this._applet.connect('confirm-request', Lang.bind(this, this._confirmRequest));
this._applet.connect('auth-request', Lang.bind(this, this._authRequest)); this._applet.connect('auth-request', Lang.bind(this, this._authRequest));
this._applet.connect('auth-service-request', Lang.bind(this, this._authServiceRequest));
this._applet.connect('cancel-request', Lang.bind(this, this._cancelRequest)); this._applet.connect('cancel-request', Lang.bind(this, this._cancelRequest));
}, },
_sync: function() { _updateKillswitch: function() {
let connectedDevices = this._applet.get_devices().filter(function(device) { let current_state = this._applet.killswitch_state;
return device.connected; let on = current_state == GnomeBluetooth.KillswitchState.UNBLOCKED;
}); let has_adapter = current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER;
let nDevices = connectedDevices.length; let can_toggle = current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER &&
current_state != GnomeBluetooth.KillswitchState.HARD_BLOCKED;
let on = nDevices > 0; this._killswitch.setToggleState(on);
this._indicator.visible = on; if (can_toggle)
this._item.actor.visible = on; this._killswitch.setStatus(null);
else
/* TRANSLATORS: this means that bluetooth was disabled by hardware rfkill */
this._killswitch.setStatus(_("hardware disabled"));
if (on) this.actor.visible = has_adapter;
this._item.status.text = ngettext("%d Connected Device", "%d Connected Devices", nDevices).format(nDevices);
if (on) {
this._discoverable.actor.show();
this.setIcon('bluetooth-active-symbolic');
} else {
this._discoverable.actor.hide();
this.setIcon('bluetooth-disabled-symbolic');
}
},
_updateDevices: function() {
let devices = this._applet.get_devices();
let newlist = [ ];
for (let i = 0; i < this._deviceItems.length; i++) {
let item = this._deviceItems[i];
let destroy = true;
for (let j = 0; j < devices.length; j++) {
if (item._device.device_path == devices[j].device_path) {
this._updateDeviceItem(item, devices[j]);
destroy = false;
break;
}
}
if (destroy)
item.destroy();
else
newlist.push(item);
}
this._deviceItems = newlist;
this._hasDevices = newlist.length > 0;
for (let i = 0; i < devices.length; i++) {
let d = devices[i];
if (d._item)
continue;
let item = this._createDeviceItem(d);
if (item) {
this.menu.addMenuItem(item, this._deviceItemPosition + this._deviceItems.length);
this._deviceItems.push(item);
this._hasDevices = true;
}
}
},
_updateDeviceItem: function(item, device) {
if (!device.can_connect && device.capabilities == GnomeBluetoothApplet.Capabilities.NONE) {
item.destroy();
return;
}
let prevDevice = item._device;
let prevCapabilities = prevDevice.capabilities;
let prevCanConnect = prevDevice.can_connect;
// adopt the new device object
item._device = device;
device._item = item;
// update properties
item.label.text = device.alias;
if (prevCapabilities != device.capabilities ||
prevCanConnect != device.can_connect) {
// need to rebuild the submenu
item.menu.removeAll();
this._buildDeviceSubMenu(item, device);
}
// update connected property
if (device.can_connect)
item._connectedMenuItem.setToggleState(device.connected);
},
_createDeviceItem: function(device) {
if (!device.can_connect && device.capabilities == GnomeBluetoothApplet.Capabilities.NONE)
return null;
let item = new PopupMenu.PopupSubMenuMenuItem(device.alias);
// adopt the device object, and add a back link
item._device = device;
device._item = item;
this._buildDeviceSubMenu(item, device);
return item;
},
_buildDeviceSubMenu: function(item, device) {
if (device.can_connect) {
let menuitem = new PopupMenu.PopupSwitchMenuItem(_("Connection"), device.connected);
item._connected = device.connected;
item._connectedMenuItem = menuitem;
menuitem.connect('toggled', Lang.bind(this, function() {
if (item._connected > ConnectionState.CONNECTED) {
// operation already in progress, revert
// (should not happen anyway)
menuitem.setToggleState(menuitem.state);
}
if (item._connected) {
item._connected = ConnectionState.DISCONNECTING;
menuitem.setStatus(_("disconnecting..."));
this._applet.disconnect_device(item._device.device_path, function(applet, success) {
if (success) { // apply
item._connected = ConnectionState.DISCONNECTED;
menuitem.setToggleState(false);
} else { // revert
item._connected = ConnectionState.CONNECTED;
menuitem.setToggleState(true);
}
menuitem.setStatus(null);
});
} else {
item._connected = ConnectionState.CONNECTING;
menuitem.setStatus(_("connecting..."));
this._applet.connect_device(item._device.device_path, function(applet, success) {
if (success) { // apply
item._connected = ConnectionState.CONNECTED;
menuitem.setToggleState(true);
} else { // revert
item._connected = ConnectionState.DISCONNECTED;
menuitem.setToggleState(false);
}
menuitem.setStatus(null);
});
}
}));
item.menu.addMenuItem(menuitem);
}
if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_PUSH) {
item.menu.addAction(_("Send Files…"), Lang.bind(this, function() {
this._applet.send_to_address(device.bdaddr, device.alias);
}));
}
switch (device.type) {
case GnomeBluetoothApplet.Type.KEYBOARD:
item.menu.addSettingsAction(_("Keyboard Settings"), 'gnome-keyboard-panel.desktop');
break;
case GnomeBluetoothApplet.Type.MOUSE:
item.menu.addSettingsAction(_("Mouse Settings"), 'gnome-mouse-panel.desktop');
break;
case GnomeBluetoothApplet.Type.HEADSET:
case GnomeBluetoothApplet.Type.HEADPHONES:
case GnomeBluetoothApplet.Type.OTHER_AUDIO:
item.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
break;
default:
break;
}
},
_updateFullMenu: function() {
if (this._applet.show_full_menu) {
this._showAll(this._fullMenuItems);
if (this._hasDevices)
this._showAll(this._deviceItems);
} else {
this._hideAll(this._fullMenuItems);
this._hideAll(this._deviceItems);
}
},
_showAll: function(items) {
for (let i = 0; i < items.length; i++)
items[i].actor.show();
},
_hideAll: function(items) {
for (let i = 0; i < items.length; i++)
items[i].actor.hide();
},
_destroyAll: function(items) {
for (let i = 0; i < items.length; i++)
items[i].destroy();
}, },
_ensureSource: function() { _ensureSource: function() {
if (!this._source) { if (!this._source) {
this._source = new MessageTray.Source(_("Bluetooth"), 'bluetooth-active'); this._source = new MessageTray.Source(_("Bluetooth"), 'bluetooth-active');
this._source.policy = new MessageTray.NotificationApplicationPolicy('gnome-bluetooth-panel'); this._source.policy = new NotificationDaemon.NotificationApplicationPolicy('gnome-bluetooth-panel');
Main.messageTray.add(this._source); Main.messageTray.add(this._source);
} }
}, },
_authRequest: function(applet, device_path, name, long_name) { _authRequest: function(applet, device_path, name, long_name, uuid) {
this._ensureSource(); this._ensureSource();
this._source.notify(new AuthNotification(this._source, this._applet, device_path, name, long_name)); this._source.notify(new AuthNotification(this._source, this._applet, device_path, name, long_name, uuid));
},
_authServiceRequest: function(applet, device_path, name, long_name, uuid) {
this._ensureSource();
this._source.notify(new AuthServiceNotification(this._source, this._applet, device_path, name, long_name, uuid));
}, },
_confirmRequest: function(applet, device_path, name, long_name, pin) { _confirmRequest: function(applet, device_path, name, long_name, pin) {
@ -94,34 +316,6 @@ const AuthNotification = new Lang.Class({
Name: 'AuthNotification', Name: 'AuthNotification',
Extends: MessageTray.Notification, Extends: MessageTray.Notification,
_init: function(source, applet, device_path, name, long_name) {
this.parent(source,
_("Bluetooth"),
_("Authorization request from %s").format(name),
{ customContent: true });
this.setResident(true);
this._applet = applet;
this._devicePath = device_path;
this.addBody(_("Device %s wants to pair with this computer").format(long_name));
this.addButton('allow', _("Allow"));
this.addButton('deny', _("Deny"));
this.connect('action-invoked', Lang.bind(this, function(self, action) {
if (action == 'allow')
this._applet.agent_reply_confirm(this._devicePath, true);
else
this._applet.agent_reply_confirm(this._devicePath, false);
this.destroy();
}));
}
});
const AuthServiceNotification = new Lang.Class({
Name: 'AuthServiceNotification',
Extends: MessageTray.Notification,
_init: function(source, applet, device_path, name, long_name, uuid) { _init: function(source, applet, device_path, name, long_name, uuid) {
this.parent(source, this.parent(source,
_("Bluetooth"), _("Bluetooth"),
@ -140,14 +334,14 @@ const AuthServiceNotification = new Lang.Class({
this.connect('action-invoked', Lang.bind(this, function(self, action) { this.connect('action-invoked', Lang.bind(this, function(self, action) {
switch (action) { switch (action) {
case 'always-grant': case 'always-grant':
this._applet.agent_reply_auth_service(this._devicePath, true, true); this._applet.agent_reply_auth(this._devicePath, true, true);
break; break;
case 'grant': case 'grant':
this._applet.agent_reply_auth_service(this._devicePath, true, false); this._applet.agent_reply_auth(this._devicePath, true, false);
break; break;
case 'reject': case 'reject':
default: default:
this._applet.agent_reply_auth_service(this._devicePath, false, false); this._applet.agent_reply_auth(this._devicePath, false, false);
} }
this.destroy(); this.destroy();
})); }));
@ -169,7 +363,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 Passkey '%06d' matches the one on the device.").format(pin)); this.addBody(_("Please confirm whether the PIN '%06d' matches the one on the device.").format(pin));
/* Translators: this is the verb, not the noun */ /* Translators: this is the verb, not the noun */
this.addButton('matches', _("Matches")); this.addButton('matches', _("Matches"));

View File

@ -1,68 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Gio = imports.gi.Gio;
const St = imports.gi.St;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Slider = imports.ui.slider;
const BUS_NAME = 'org.gnome.SettingsDaemon.Power';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power';
const BrightnessInterface = <interface name="org.gnome.SettingsDaemon.Power.Screen">
<property name='Brightness' type='i' access='readwrite'/>
</interface>;
const BrightnessProxy = Gio.DBusProxy.makeProxyWrapper(BrightnessInterface);
const Indicator = new Lang.Class({
Name: 'BrightnessIndicator',
Extends: PanelMenu.SystemIndicator,
_init: function() {
this.parent('display-brightness-symbolic');
this._proxy = new BrightnessProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,
Lang.bind(this, function(proxy, error) {
if (error) {
log(error.message);
return;
}
this._proxy.connect('g-properties-changed', Lang.bind(this, this._sync));
this._sync();
}));
this._item = new PopupMenu.PopupBaseMenuItem({ activate: false });
this.menu.addMenuItem(this._item);
this._slider = new Slider.Slider(0);
this._slider.connect('value-changed', Lang.bind(this, this._sliderChanged));
this._slider.actor.accessible_name = _("Brightness");
let icon = new St.Icon({ icon_name: 'display-brightness-symbolic',
style_class: 'popup-menu-icon' });
this._item.actor.add(icon);
this._item.actor.add(this._slider.actor, { expand: true });
this._item.actor.connect('button-press-event', Lang.bind(this, function(actor, event) {
this._slider.startDragging(event);
}));
this._item.actor.connect('key-press-event', Lang.bind(this, function(actor, event) {
return this._slider.onKeyPressEvent(actor, event);
}));
},
_sliderChanged: function(slider, value) {
let percent = value * 100;
this._proxy.Brightness = percent;
},
_sync: function() {
let visible = this._proxy.Brightness >= 0;
this._item.actor.visible = visible;
if (visible)
this._slider.setValue(this._proxy.Brightness / 100.0);
},
});

View File

@ -9,7 +9,6 @@ const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const St = imports.gi.St; const St = imports.gi.St;
const Gettext = imports.gettext;
try { try {
var IBus = imports.gi.IBus; var IBus = imports.gi.IBus;
@ -34,14 +33,15 @@ const KEY_INPUT_SOURCES = 'sources';
const INPUT_SOURCE_TYPE_XKB = 'xkb'; const INPUT_SOURCE_TYPE_XKB = 'xkb';
const INPUT_SOURCE_TYPE_IBUS = 'ibus'; const INPUT_SOURCE_TYPE_IBUS = 'ibus';
// This is the longest we'll keep the keyboard frozen until an input // This is longest we'll keep the keyboard frozen until an input
// source is active. // source is active.
const MAX_INPUT_SOURCE_ACTIVATION_TIME = 4000; // ms const MAX_INPUT_SOURCE_ACTIVATION_TIME = 2000; // ms
const BUS_NAME = 'org.gnome.SettingsDaemon.Keyboard'; const BUS_NAME = 'org.gnome.SettingsDaemon.Keyboard';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Keyboard'; const OBJECT_PATH = '/org/gnome/SettingsDaemon/Keyboard';
const KeyboardManagerInterface = <interface name="org.gnome.SettingsDaemon.Keyboard"> const KeyboardManagerInterface =
<interface name="org.gnome.SettingsDaemon.Keyboard">
<method name="SetInputSource"> <method name="SetInputSource">
<arg type="u" direction="in" /> <arg type="u" direction="in" />
</method> </method>
@ -57,7 +57,10 @@ function releaseKeyboard() {
} }
function holdKeyboard() { function holdKeyboard() {
global.freeze_keyboard(global.get_current_time()); if (Main.modalCount > 0)
global.display.freeze_keyboard(global.get_current_time());
else
global.display.grab_keyboard(global.get_current_time());
} }
const IBusManager = new Lang.Class({ const IBusManager = new Lang.Class({
@ -72,24 +75,26 @@ const IBusManager = new Lang.Class({
this._readyCallback = readyCallback; this._readyCallback = readyCallback;
this._candidatePopup = new IBusCandidatePopup.CandidatePopup(); this._candidatePopup = new IBusCandidatePopup.CandidatePopup();
this._ibus = null;
this._panelService = null; this._panelService = null;
this._engines = {}; this._engines = {};
this._ready = false; this._ready = false;
this._registerPropertiesId = 0; this._registerPropertiesId = 0;
this._currentEngineName = null; this._currentEngineName = null;
this._ibus = IBus.Bus.new_async(); this._nameWatcherId = Gio.DBus.session.watch_name(IBus.SERVICE_IBUS,
this._ibus.connect('connected', Lang.bind(this, this._onConnected)); Gio.BusNameWatcherFlags.NONE,
this._ibus.connect('disconnected', Lang.bind(this, this._clear)); Lang.bind(this, this._onNameAppeared),
// Need to set this to get 'global-engine-changed' emitions Lang.bind(this, this._clear));
this._ibus.set_watch_ibus_signal(true);
this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged));
}, },
_clear: function() { _clear: function() {
if (this._panelService) if (this._panelService)
this._panelService.destroy(); this._panelService.destroy();
if (this._ibus)
this._ibus.destroy();
this._ibus = null;
this._panelService = null; this._panelService = null;
this._candidatePopup.setPanelService(null); this._candidatePopup.setPanelService(null);
this._engines = {}; this._engines = {};
@ -101,12 +106,18 @@ const IBusManager = new Lang.Class({
this._readyCallback(false); this._readyCallback(false);
}, },
_onNameAppeared: function() {
this._ibus = IBus.Bus.new_async();
this._ibus.connect('connected', Lang.bind(this, this._onConnected));
},
_onConnected: function() { _onConnected: function() {
this._ibus.list_engines_async(-1, null, Lang.bind(this, this._initEngines)); this._ibus.list_engines_async(-1, null, Lang.bind(this, this._initEngines));
this._ibus.request_name_async(IBus.SERVICE_PANEL, this._ibus.request_name_async(IBus.SERVICE_PANEL,
IBus.BusNameFlag.REPLACE_EXISTING, IBus.BusNameFlag.REPLACE_EXISTING,
-1, null, -1, null,
Lang.bind(this, this._initPanelService)); Lang.bind(this, this._initPanelService));
this._ibus.connect('disconnected', Lang.bind(this, this._clear));
}, },
_initEngines: function(ibus, result) { _initEngines: function(ibus, result) {
@ -128,6 +139,9 @@ const IBusManager = new Lang.Class({
this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(), this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(),
object_path: IBus.PATH_PANEL }); object_path: IBus.PATH_PANEL });
this._candidatePopup.setPanelService(this._panelService); this._candidatePopup.setPanelService(this._panelService);
// Need to set this to get 'global-engine-changed' emitions
this._ibus.set_watch_ibus_signal(true);
this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged));
this._panelService.connect('update-property', Lang.bind(this, this._updateProperty)); this._panelService.connect('update-property', Lang.bind(this, this._updateProperty));
// If an engine is already active we need to get its properties // If an engine is already active we need to get its properties
this._ibus.get_global_engine_async(-1, null, Lang.bind(this, function(i, result) { this._ibus.get_global_engine_async(-1, null, Lang.bind(this, function(i, result) {
@ -156,9 +170,6 @@ const IBusManager = new Lang.Class({
}, },
_engineChanged: function(bus, engineName) { _engineChanged: function(bus, engineName) {
if (!this._ready)
return;
this._currentEngineName = engineName; this._currentEngineName = engineName;
if (this._registerPropertiesId != 0) if (this._registerPropertiesId != 0)
@ -202,8 +213,8 @@ const LayoutMenuItem = new Lang.Class({
this.label = new St.Label({ text: displayName }); this.label = new St.Label({ text: displayName });
this.indicator = new St.Label({ text: shortName }); this.indicator = new St.Label({ text: shortName });
this.actor.add(this.label, { expand: true }); this.addActor(this.label);
this.actor.add(this.indicator); this.addActor(this.indicator);
this.actor.label_actor = this.label; this.actor.label_actor = this.label;
} }
}); });
@ -249,6 +260,7 @@ const InputSource = new Lang.Class({
}, },
activate: function() { activate: function() {
holdKeyboard();
this.emit('activate'); this.emit('activate');
}, },
}); });
@ -281,11 +293,11 @@ const InputSourcePopup = new Lang.Class({
this._select(this._selectedIndex); this._select(this._selectedIndex);
}, },
_keyPressHandler: function(keysym, backwards, action) { _keyPressHandler: function(keysym, _ignored, action) {
if (action == this._action) if (action == this._action)
this._select(backwards ? this._previous() : this._next()); this._select(this._next());
else if (action == this._actionBackward) else if (action == this._actionBackward)
this._select(backwards ? this._next() : this._previous()); this._select(this._previous());
else if (keysym == Clutter.Left) else if (keysym == Clutter.Left)
this._select(this._previous()); this._select(this._previous());
else if (keysym == Clutter.Right) else if (keysym == Clutter.Right)
@ -336,12 +348,7 @@ const InputSourceIndicator = new Lang.Class({
this._container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth)); this._container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth));
this._container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight)); this._container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight));
this._container.connect('allocate', Lang.bind(this, this._containerAllocate)); this._container.connect('allocate', Lang.bind(this, this._containerAllocate));
this.actor.add_actor(this._container);
this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
this._hbox.add_child(this._container);
this._hbox.add_child(PopupMenu.unicodeArrow(St.Side.BOTTOM));
this.actor.add_child(this._hbox);
this.actor.add_style_class_name('panel-status-button'); this.actor.add_style_class_name('panel-status-button');
// All valid input sources currently in the gsettings // All valid input sources currently in the gsettings
@ -360,14 +367,13 @@ const InputSourceIndicator = new Lang.Class({
this._keybindingAction = this._keybindingAction =
Main.wm.addKeybinding('switch-input-source', Main.wm.addKeybinding('switch-input-source',
new Gio.Settings({ schema: "org.gnome.desktop.wm.keybindings" }), new Gio.Settings({ schema: "org.gnome.desktop.wm.keybindings" }),
Meta.KeyBindingFlags.REVERSES, Meta.KeyBindingFlags.NONE,
Shell.KeyBindingMode.ALL, Shell.KeyBindingMode.ALL,
Lang.bind(this, this._switchInputSource)); Lang.bind(this, this._switchInputSource));
this._keybindingActionBackward = this._keybindingActionBackward =
Main.wm.addKeybinding('switch-input-source-backward', Main.wm.addKeybinding('switch-input-source-backward',
new Gio.Settings({ schema: "org.gnome.desktop.wm.keybindings" }), new Gio.Settings({ schema: "org.gnome.desktop.wm.keybindings" }),
Meta.KeyBindingFlags.REVERSES | Meta.KeyBindingFlags.NONE,
Meta.KeyBindingFlags.REVERSED,
Shell.KeyBindingMode.ALL, Shell.KeyBindingMode.ALL,
Lang.bind(this, this._switchInputSource)); Lang.bind(this, this._switchInputSource));
this._settings = new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_SCHEMA }); this._settings = new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_SCHEMA });
@ -393,8 +399,6 @@ const InputSourceIndicator = new Lang.Class({
if (error) if (error)
log(error.message); log(error.message);
}); });
this._keyboardManager.g_default_timeout = MAX_INPUT_SOURCE_ACTIVATION_TIME;
global.display.connect('modifiers-accelerator-activated', Lang.bind(this, this._modifiersSwitcher)); global.display.connect('modifiers-accelerator-activated', Lang.bind(this, this._modifiersSwitcher));
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
@ -403,6 +407,8 @@ const InputSourceIndicator = new Lang.Class({
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
this._sessionUpdated(); this._sessionUpdated();
this.menu.addSettingsAction(_("Region & Language Settings"), 'gnome-region-panel.desktop');
this._sourcesPerWindow = false; this._sourcesPerWindow = false;
this._focusWindowNotifyId = 0; this._focusWindowNotifyId = 0;
this._overviewShowingId = 0; this._overviewShowingId = 0;
@ -432,7 +438,7 @@ const InputSourceIndicator = new Lang.Class({
let sourceIndexes = Object.keys(this._inputSources); let sourceIndexes = Object.keys(this._inputSources);
if (sourceIndexes.length == 0) { if (sourceIndexes.length == 0) {
releaseKeyboard(); releaseKeyboard();
return true; return;
} }
let is = this._currentSource; let is = this._currentSource;
@ -447,7 +453,6 @@ const InputSourceIndicator = new Lang.Class({
nextIndex += 1; nextIndex += 1;
is.activate(); is.activate();
return true;
}, },
_switchInputSource: function(display, screen, window, binding) { _switchInputSource: function(display, screen, window, binding) {
@ -455,19 +460,18 @@ const InputSourceIndicator = new Lang.Class({
return; return;
// HACK: Fall back on simple input source switching since we // HACK: Fall back on simple input source switching since we
// can't show a popup switcher while a GrabHelper grab is in // can't show a popup switcher while the message tray is up
// effect without considerable work to consolidate the usage // without considerable work to consolidate the usage of
// of pushModal/popModal and grabHelper. See // pushModal/popModal and grabHelper. See
// https://bugzilla.gnome.org/show_bug.cgi?id=695143 . // https://bugzilla.gnome.org/show_bug.cgi?id=695143 .
if (Main.keybindingMode == Shell.KeyBindingMode.MESSAGE_TRAY || if (Main.keybindingMode == Shell.KeyBindingMode.MESSAGE_TRAY) {
Main.keybindingMode == Shell.KeyBindingMode.TOPBAR_POPUP) {
this._modifiersSwitcher(); this._modifiersSwitcher();
return; return;
} }
let popup = new InputSourcePopup(this._mruSources, this._keybindingAction, this._keybindingActionBackward); let popup = new InputSourcePopup(this._mruSources, this._keybindingAction, this._keybindingActionBackward);
let modifiers = binding.get_modifiers(); let modifiers = binding.get_modifiers();
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK; let backwards = false; // Not using this
if (!popup.show(backwards, binding.get_name(), binding.get_mask())) if (!popup.show(backwards, binding.get_name(), binding.get_mask()))
popup.destroy(); popup.destroy();
}, },
@ -481,7 +485,7 @@ const InputSourceIndicator = new Lang.Class({
[oldSource, this._currentSource] = [this._currentSource, newSource]; [oldSource, this._currentSource] = [this._currentSource, newSource];
if (oldSource) { if (oldSource) {
oldSource.menuItem.setOrnament(PopupMenu.Ornament.NONE); oldSource.menuItem.setShowDot(false);
oldSource.indicatorLabel.hide(); oldSource.indicatorLabel.hide();
} }
@ -499,7 +503,7 @@ const InputSourceIndicator = new Lang.Class({
this.actor.show(); this.actor.show();
newSource.menuItem.setOrnament(PopupMenu.Ornament.DOT); newSource.menuItem.setShowDot(true);
newSource.indicatorLabel.show(); newSource.indicatorLabel.show();
this._buildPropSection(newSource.properties); this._buildPropSection(newSource.properties);
@ -523,7 +527,6 @@ const InputSourceIndicator = new Lang.Class({
this._inputSources = {}; this._inputSources = {};
this._ibusSources = {}; this._ibusSources = {};
this._currentSource = null;
let inputSourcesByShortName = {}; let inputSourcesByShortName = {};
@ -540,12 +543,8 @@ const InputSourceIndicator = new Lang.Class({
let engineDesc = this._ibusManager.getEngineDesc(id); let engineDesc = this._ibusManager.getEngineDesc(id);
if (engineDesc) { if (engineDesc) {
let language = IBus.get_language_name(engineDesc.get_language()); let language = IBus.get_language_name(engineDesc.get_language());
let longName = engineDesc.get_longname();
let textdomain = engineDesc.get_textdomain();
if (textdomain != '')
longName = Gettext.dgettext(textdomain, longName);
exists = true; exists = true;
displayName = '%s (%s)'.format(language, longName); displayName = language + ' (' + engineDesc.get_longname() + ')';
shortName = this._makeEngineShortName(engineDesc); shortName = this._makeEngineShortName(engineDesc);
} }
} }
@ -556,8 +555,10 @@ const InputSourceIndicator = new Lang.Class({
let is = new InputSource(type, id, displayName, shortName, i); let is = new InputSource(type, id, displayName, shortName, i);
is.connect('activate', Lang.bind(this, function() { is.connect('activate', Lang.bind(this, function() {
holdKeyboard(); let inVariant = new GLib.Variant('(u)', [is.index]);
this._keyboardManager.SetInputSourceRemote(is.index, releaseKeyboard); this._keyboardManager.call('SetInputSource', inVariant, 0,
MAX_INPUT_SOURCE_ACTIVATION_TIME,
null, releaseKeyboard);
})); }));
if (!(is.shortName in inputSourcesByShortName)) if (!(is.shortName in inputSourcesByShortName))
@ -727,8 +728,7 @@ const InputSourceIndicator = new Lang.Class({
item.prop = prop; item.prop = prop;
radioGroup.push(item); radioGroup.push(item);
item.radioGroup = radioGroup; item.radioGroup = radioGroup;
item.setOrnament(prop.get_state() == IBus.PropState.CHECKED ? item.setShowDot(prop.get_state() == IBus.PropState.CHECKED);
PopupMenu.Ornament.DOT : PopupMenu.Ornament.NONE);
item.connect('activate', Lang.bind(this, function() { item.connect('activate', Lang.bind(this, function() {
if (item.prop.get_state() == IBus.PropState.CHECKED) if (item.prop.get_state() == IBus.PropState.CHECKED)
return; return;
@ -736,12 +736,12 @@ const InputSourceIndicator = new Lang.Class({
let group = item.radioGroup; let group = item.radioGroup;
for (let i = 0; i < group.length; ++i) { for (let i = 0; i < group.length; ++i) {
if (group[i] == item) { if (group[i] == item) {
item.setOrnament(PopupMenu.Ornament.DOT); item.setShowDot(true);
item.prop.set_state(IBus.PropState.CHECKED); item.prop.set_state(IBus.PropState.CHECKED);
this._ibusManager.activateProperty(item.prop.get_key(), this._ibusManager.activateProperty(item.prop.get_key(),
IBus.PropState.CHECKED); IBus.PropState.CHECKED);
} else { } else {
group[i].setOrnament(PopupMenu.Ornament.NONE); group[i].setShowDot(false);
group[i].prop.set_state(IBus.PropState.UNCHECKED); group[i].prop.set_state(IBus.PropState.UNCHECKED);
this._ibusManager.activateProperty(group[i].prop.get_key(), this._ibusManager.activateProperty(group[i].prop.get_key(),
IBus.PropState.UNCHECKED); IBus.PropState.UNCHECKED);
@ -905,7 +905,7 @@ const InputSourceIndicator = new Lang.Class({
for (let i in this._inputSources) { for (let i in this._inputSources) {
let is = this._inputSources[i]; let is = this._inputSources[i];
is.indicatorLabel.allocate_align_fill(box, 0.5, 0.5, false, false, flags); is.indicatorLabel.allocate_align_fill(box, 0.5, 0, false, false, flags);
} }
} }
}); });

View File

@ -0,0 +1,62 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const GObject = imports.gi.GObject;
const Lang = imports.lang;
const St = imports.gi.St;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const VolumeMenu = imports.ui.status.volume;
const FakeStatusIcon = new Lang.Class({
Name: 'FakeStatusIcon',
_init: function(button) {
this.actor = new St.BoxLayout({ style_class: 'panel-status-button-box' });
this._button = button;
this._button.connect('icons-updated', Lang.bind(this, this._reconstructIcons));
this._button.actor.bind_property('visible', this.actor, 'visible',
GObject.BindingFlags.SYNC_CREATE);
this._reconstructIcons();
},
_reconstructIcons: function() {
this.actor.destroy_all_children();
this._button.icons.forEach(Lang.bind(this, function(icon) {
let newIcon = new St.Icon({ style_class: 'system-status-icon' });
icon.bind_property('gicon', newIcon, 'gicon',
GObject.BindingFlags.SYNC_CREATE);
icon.bind_property('visible', newIcon, 'visible',
GObject.BindingFlags.SYNC_CREATE);
this.actor.add_actor(newIcon);
}));
}
});
const Indicator = new Lang.Class({
Name: 'LockScreenMenuIndicator',
Extends: PanelMenu.SystemStatusButton,
_init: function() {
this.parent(null, _("Volume, network, battery"));
this._box.style_class = 'lock-screen-status-button-box';
this._volumeControl = VolumeMenu.getMixerControl();
this._volumeMenu = new VolumeMenu.VolumeMenu(this._volumeControl);
this.menu.addMenuItem(this._volumeMenu);
this._volume = new FakeStatusIcon(Main.panel.statusArea.volume);
this._box.add_child(this._volume.actor);
// Network may not exist if the user doesn't have NetworkManager
if (Main.panel.statusArea.network) {
this._network = new FakeStatusIcon(Main.panel.statusArea.network);
this._box.add_child(this._network.actor);
}
this._battery = new FakeStatusIcon(Main.panel.statusArea.battery);
this._box.add_child(this._battery.actor);
}
});

File diff suppressed because it is too large Load Diff

View File

@ -2,15 +2,39 @@
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const Lang = imports.lang; const Lang = imports.lang;
const UPower = imports.gi.UPowerGlib; 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 BUS_NAME = 'org.gnome.SettingsDaemon.Power'; const BUS_NAME = 'org.gnome.SettingsDaemon.Power';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power'; const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power';
const UPDeviceType = {
UNKNOWN: 0,
AC_POWER: 1,
BATTERY: 2,
UPS: 3,
MONITOR: 4,
MOUSE: 5,
KEYBOARD: 6,
PDA: 7,
PHONE: 8,
MEDIA_PLAYER: 9,
TABLET: 10,
COMPUTER: 11
};
const UPDeviceState = {
UNKNOWN: 0,
CHARGING: 1,
DISCHARGING: 2,
EMPTY: 3,
FULLY_CHARGED: 4,
PENDING_CHARGE: 5,
PENDING_DISCHARGE: 6
};
const PowerManagerInterface = <interface name="org.gnome.SettingsDaemon.Power"> const PowerManagerInterface = <interface name="org.gnome.SettingsDaemon.Power">
<method name="GetDevices"> <method name="GetDevices">
<arg type="a(susdut)" direction="out" /> <arg type="a(susdut)" direction="out" />
@ -25,91 +49,178 @@ const PowerManagerProxy = Gio.DBusProxy.makeProxyWrapper(PowerManagerInterface);
const Indicator = new Lang.Class({ const Indicator = new Lang.Class({
Name: 'PowerIndicator', Name: 'PowerIndicator',
Extends: PanelMenu.SystemIndicator, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
this.parent(); this.parent('battery-missing-symbolic', _("Battery"));
this._indicator = this._addIndicator();
this._proxy = new PowerManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH, this._proxy = new PowerManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,
Lang.bind(this, function(proxy, error) { Lang.bind(this, function(proxy, error) {
if (error) { if (error) {
log(error.message); log(error.message);
return; return;
} }
this._proxy.connect('g-properties-changed', this._proxy.connect('g-properties-changed',
Lang.bind(this, this._sync)); Lang.bind(this, this._devicesChanged));
this._sync(); this._devicesChanged();
})); }));
this._item = new PopupMenu.PopupSubMenuMenuItem(_("Battery"), true); this._deviceItems = [ ];
this._item.menu.addSettingsAction(_("Power Settings"), 'gnome-power-panel.desktop'); this._hasPrimary = false;
this.menu.addMenuItem(this._item); this._primaryDeviceId = null;
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); this._batteryItem = new PopupMenu.PopupMenuItem('', { reactive: false });
this._sessionUpdated(); this._primaryPercentage = new St.Label({ style_class: 'popup-battery-percentage' });
this._batteryItem.addActor(this._primaryPercentage, { align: St.Align.END });
this.menu.addMenuItem(this._batteryItem);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._otherDevicePosition = 2;
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addSettingsAction(_("Power Settings"), 'gnome-power-panel.desktop');
}, },
_sessionUpdated: function() { _readPrimaryDevice: function() {
let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
this.menu.setSensitive(sensitive);
},
_statusForDevice: function(device) {
let [device_id, device_type, icon, percentage, state, seconds] = device;
if (state == UPower.DeviceState.FULLY_CHARGED)
return _("Fully Charged");
let time = Math.round(seconds / 60);
if (time == 0) {
// 0 is reported when UPower does not have enough data
// to estimate battery life
return _("Estimating…");
}
let minutes = time % 60;
let hours = Math.floor(time / 60);
if (state == UPower.DeviceState.DISCHARGING) {
// Translators: this is <hours>:<minutes> Remaining (<percentage>)
return _("%d\u2236%02d Remaining (%d%%)").format(hours, minutes, percentage);
}
if (state == UPower.DeviceState.CHARGING) {
// Translators: this is <hours>:<minutes> Until Full (<percentage>)
return _("%d\u2236%02d Until Full (%d%%)").format(hours, minutes, percentage);
}
// state is one of PENDING_CHARGING, PENDING_DISCHARGING
return _("Estimating…");
},
_sync: function() {
function isBattery(result) {
if (!result)
return false;
let [device] = result;
let [, deviceType] = device;
return (deviceType == UPower.DeviceKind.BATTERY);
}
this._proxy.GetPrimaryDeviceRemote(Lang.bind(this, function(result, error) { this._proxy.GetPrimaryDeviceRemote(Lang.bind(this, function(result, error) {
if (isBattery(result)) { if (error) {
let [device] = result; this._hasPrimary = false;
let [,, icon] = device; this._primaryDeviceId = null;
let gicon = Gio.icon_new_for_string(icon); this._batteryItem.actor.hide();
this._indicator.gicon = gicon; return;
this._item.icon.gicon = gicon; }
this._item.status.text = this._statusForDevice(device); let [[device_id, device_type, icon, percentage, state, seconds]] = result;
this._item.actor.show(); if (device_type == UPDeviceType.BATTERY) {
this._hasPrimary = true;
let time = Math.round(seconds / 60);
if (time == 0) {
// 0 is reported when UPower does not have enough data
// to estimate battery life
this._batteryItem.label.text = _("Estimating…");
} else {
let minutes = time % 60;
let hours = Math.floor(time / 60);
let timestring;
if (time >= 60) {
if (minutes == 0) {
timestring = ngettext("%d hour remaining", "%d hours remaining", hours).format(hours);
} else {
/* TRANSLATORS: this is a time string, as in "%d hours %d minutes remaining" */
let template = _("%d %s %d %s remaining");
timestring = template.format (hours, ngettext("hour", "hours", hours), minutes, ngettext("minute", "minutes", minutes));
}
} else
timestring = ngettext("%d minute remaining", "%d minutes remaining", minutes).format(minutes);
this._batteryItem.label.text = timestring;
}
this._primaryPercentage.text = C_("percent of battery remaining", "%d%%").format(Math.round(percentage));
this._batteryItem.actor.show();
} else { } else {
// If there's no battery, then we use the power icon. this._hasPrimary = false;
this._indicator.icon_name = 'system-shutdown-symbolic'; this._batteryItem.actor.hide();
this._item.actor.hide(); }
this._primaryDeviceId = device_id;
}));
},
_readOtherDevices: function() {
this._proxy.GetDevicesRemote(Lang.bind(this, function(result, error) {
this._deviceItems.forEach(function(i) { i.destroy(); });
this._deviceItems = [];
if (error) {
return;
}
let position = 0;
let [devices] = result;
for (let i = 0; i < devices.length; i++) {
let [device_id, device_type] = devices[i];
if (device_type == UPDeviceType.AC_POWER || device_id == this._primaryDeviceId)
continue;
let item = new DeviceItem (devices[i]);
this._deviceItems.push(item);
this.menu.addMenuItem(item, this._otherDevicePosition + position);
position++;
} }
})); }));
}, },
_syncIcon: function() {
let icon = this._proxy.Icon;
let hasIcon = false;
if (icon) {
let gicon = Gio.icon_new_for_string(icon);
this.setGIcon(gicon);
hasIcon = true;
}
this.mainIcon.visible = hasIcon;
this.actor.visible = hasIcon;
},
_devicesChanged: function() {
this._syncIcon();
this._readPrimaryDevice();
this._readOtherDevices();
}
});
const DeviceItem = new Lang.Class({
Name: 'DeviceItem',
Extends: PopupMenu.PopupBaseMenuItem,
_init: function(device) {
this.parent({ reactive: false });
let [device_id, device_type, icon, percentage, state, time] = device;
this._box = new St.BoxLayout({ style_class: 'popup-device-menu-item' });
this._label = new St.Label({ text: this._deviceTypeToString(device_type) });
this._icon = new St.Icon({ gicon: Gio.icon_new_for_string(icon),
style_class: 'popup-menu-icon' });
this._box.add_actor(this._icon);
this._box.add_actor(this._label);
this.addActor(this._box);
let percentLabel = new St.Label({ text: C_("percent of battery remaining", "%d%%").format(Math.round(percentage)),
style_class: 'popup-battery-percentage' });
this.addActor(percentLabel, { align: St.Align.END });
//FIXME: ideally we would like to expose this._label and percentLabel
this.actor.label_actor = percentLabel;
},
_deviceTypeToString: function(type) {
switch (type) {
case UPDeviceType.AC_POWER:
return _("AC Adapter");
case UPDeviceType.BATTERY:
return _("Laptop Battery");
case UPDeviceType.UPS:
return _("UPS");
case UPDeviceType.MONITOR:
return _("Monitor");
case UPDeviceType.MOUSE:
return _("Mouse");
case UPDeviceType.KEYBOARD:
return _("Keyboard");
case UPDeviceType.PDA:
return _("PDA");
case UPDeviceType.PHONE:
return _("Cell Phone");
case UPDeviceType.MEDIA_PLAYER:
return _("Media Player");
case UPDeviceType.TABLET:
return _("Tablet");
case UPDeviceType.COMPUTER:
return _("Computer");
default:
return C_("device", "Unknown");
}
}
}); });

View File

@ -1,58 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const BUS_NAME = 'org.gnome.SettingsDaemon.Rfkill';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Rfkill';
const RfkillManagerInterface = <interface name="org.gnome.SettingsDaemon.Rfkill">
<property name="AirplaneMode" type="b" access="readwrite" />
</interface>;
const RfkillManagerProxy = Gio.DBusProxy.makeProxyWrapper(RfkillManagerInterface);
const Indicator = new Lang.Class({
Name: 'RfkillIndicator',
Extends: PanelMenu.SystemIndicator,
_init: function() {
this.parent();
this._proxy = new RfkillManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,
Lang.bind(this, function(proxy, error) {
if (error) {
log(error.message);
return;
}
this._proxy.connect('g-properties-changed',
Lang.bind(this, this._sync));
this._sync();
}));
this._indicator = this._addIndicator();
this._indicator.icon_name = 'airplane-mode-symbolic';
this._indicator.hide();
// The menu only appears when airplane mode is on, so just
// statically build it as if it was on, rather than dynamically
// changing the menu contents.
this._item = new PopupMenu.PopupSubMenuMenuItem(_("Airplane Mode"), true);
this._item.icon.icon_name = 'airplane-mode-symbolic';
this._item.status.text = _("On");
this._item.menu.addAction(_("Turn Off"), Lang.bind(this, function() {
this._proxy.AirplaneMode = false;
}));
this._item.menu.addSettingsAction(_("Network Settings"), 'gnome-network-panel.desktop');
this.menu.addMenuItem(this._item);
},
_sync: function() {
let airplaneMode = this._proxy.AirplaneMode;
this._indicator.visible = airplaneMode;
this._item.actor.visible = airplaneMode;
},
});

View File

@ -1,26 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const Indicator = new Lang.Class({
Name: 'ScreencastIndicator',
Extends: PanelMenu.SystemIndicator,
_init: function() {
this.parent();
this._indicator = this._addIndicator();
this._indicator.icon_name = 'media-record-symbolic';
this._indicator.add_style_class_name('screencast-indicator');
this._sync();
Main.screencastService.connect('updated', Lang.bind(this, this._sync));
},
_sync: function() {
this._indicator.visible = Main.screencastService.isRecording;
},
});

View File

@ -1,397 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const AccountsService = imports.gi.AccountsService;
const Clutter = imports.gi.Clutter;
const Gdm = imports.gi.Gdm;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const BoxPointer = imports.ui.boxpointer;
const GnomeSession = imports.misc.gnomeSession;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const PRIVACY_SCHEMA = 'org.gnome.desktop.privacy'
const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen';
const DISABLE_LOG_OUT_KEY = 'disable-log-out';
const ALWAYS_SHOW_LOG_OUT_KEY = 'always-show-log-out';
const AltSwitcher = new Lang.Class({
Name: 'AltSwitcher',
_init: function(standard, alternate) {
this._standard = standard;
this._standard.connect('notify::visible', Lang.bind(this, this._sync));
this._alternate = alternate;
this._alternate.connect('notify::visible', Lang.bind(this, this._sync));
this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
this.actor = new St.Bin();
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
},
_sync: function() {
let childToShow = null;
if (this._standard.visible && this._alternate.visible) {
let [x, y, mods] = global.get_pointer();
let altPressed = (mods & Clutter.ModifierType.MOD1_MASK) != 0;
childToShow = altPressed ? this._alternate : this._standard;
} else if (this._standard.visible) {
childToShow = this._standard;
} else if (this._alternate.visible) {
childToShow = this._alternate;
}
if (this.actor.get_child() != childToShow) {
this.actor.set_child(childToShow);
// The actors might respond to hover, so
// sync the pointer to make sure they update.
global.sync_pointer();
}
this.actor.visible = (childToShow != null);
},
_onDestroy: function() {
if (this._capturedEventId > 0) {
global.stage.disconnect(this._capturedEventId);
this._capturedEventId = 0;
}
},
_onCapturedEvent: function(actor, event) {
let type = event.type();
if (type == Clutter.EventType.KEY_PRESS || type == Clutter.EventType.KEY_RELEASE) {
let key = event.get_key_symbol();
if (key == Clutter.KEY_Alt_L || key == Clutter.KEY_Alt_R)
this._sync();
}
return false;
},
});
const Indicator = new Lang.Class({
Name: 'SystemIndicator',
Extends: PanelMenu.SystemIndicator,
_init: function() {
this.parent();
this._screenSaverSettings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA });
this._privacySettings = new Gio.Settings({ schema: PRIVACY_SCHEMA });
this._orientationSettings = new Gio.Settings({ schema: 'org.gnome.settings-daemon.peripherals.touchscreen' });
this._session = new GnomeSession.SessionManager();
this._loginManager = LoginManager.getLoginManager();
this._haveShutdown = true;
this._haveSuspend = true;
this._userManager = AccountsService.UserManager.get_default();
this._user = this._userManager.get_user(GLib.get_user_name());
this._createSubMenu();
this._userManager.connect('notify::is-loaded',
Lang.bind(this, this._updateMultiUser));
this._userManager.connect('notify::has-multiple-users',
Lang.bind(this, this._updateMultiUser));
this._userManager.connect('user-added',
Lang.bind(this, this._updateMultiUser));
this._userManager.connect('user-removed',
Lang.bind(this, this._updateMultiUser));
this._lockdownSettings.connect('changed::' + DISABLE_USER_SWITCH_KEY,
Lang.bind(this, this._updateMultiUser));
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
Lang.bind(this, this._updateMultiUser));
this._lockdownSettings.connect('changed::' + DISABLE_LOCK_SCREEN_KEY,
Lang.bind(this, this._updateLockScreen));
global.settings.connect('changed::' + ALWAYS_SHOW_LOG_OUT_KEY,
Lang.bind(this, this._updateMultiUser));
this._updateSwitchUser();
this._updateMultiUser();
// Whether shutdown is available or not depends on both lockdown
// settings (disable-log-out) and Polkit policy - the latter doesn't
// notify, so we update the menu item each time the menu opens or
// the lockdown setting changes, which should be close enough.
this.menu.connect('open-state-changed', Lang.bind(this,
function(menu, open) {
if (!open)
return;
this._updateHaveShutdown();
this._updateHaveSuspend();
}));
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
Lang.bind(this, this._updateHaveShutdown));
this._orientationSettings.connect('changed::orientation-lock',
Lang.bind(this, this._updateOrientationLock));
this._orientationExists = false;
Gio.DBus.session.watch_name('org.gnome.SettingsDaemon.Orientation',
Gio.BusNameWatcherFlags.NONE,
Lang.bind(this, function() {
this._orentationExists = true;
this._updateOrientationLock();
}),
Lang.bind(this, function() {
this._orentationExists = false;
this._updateOrientationLock();
}));
this._updateOrientationLock();
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
this._sessionUpdated();
},
_updateActionsVisibility: function() {
let visible = (this._settingsAction.visible ||
this._orientationLockAction.visible ||
this._lockScreenAction.visible ||
this._altSwitcher.actor.visible);
this._actionsItem.actor.visible = visible;
},
_sessionUpdated: function() {
this._updateLockScreen();
this._updatePowerOff();
this._updateSuspend();
this._updateMultiUser();
this._settingsAction.visible = Main.sessionMode.allowSettings;
this._updateActionsVisibility();
},
_updateMultiUser: function() {
let shouldShowInMode = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
let hasSwitchUser = this._updateSwitchUser();
let hasLogout = this._updateLogout();
this._switchUserSubMenu.actor.visible = shouldShowInMode && (hasSwitchUser || hasLogout);
},
_updateSwitchUser: function() {
let allowSwitch = !this._lockdownSettings.get_boolean(DISABLE_USER_SWITCH_KEY);
let multiUser = this._userManager.can_switch() && this._userManager.has_multiple_users;
let visible = allowSwitch && multiUser;
this._loginScreenItem.actor.visible = visible;
return visible;
},
_updateLogout: function() {
let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY);
let alwaysShow = global.settings.get_boolean(ALWAYS_SHOW_LOG_OUT_KEY);
let systemAccount = this._user.system_account;
let localAccount = this._user.local_account;
let multiUser = this._userManager.has_multiple_users;
let multiSession = Gdm.get_session_ids().length > 1;
let visible = allowLogout && (alwaysShow || multiUser || multiSession || systemAccount || !localAccount);
this._logoutItem.actor.visible = visible;
return visible;
},
_updateSwitchUserSubMenu: function() {
this._switchUserSubMenu.label.text = this._user.get_real_name();
let clutterText = this._switchUserSubMenu.label.clutter_text;
// XXX -- for some reason, the ClutterText's width changes
// rapidly unless we force a relayout of the actor. Probably
// a size cache issue or something. Moving this to be a layout
// manager would be a much better idea.
clutterText.get_allocation_box();
let layout = clutterText.get_layout();
if (layout.is_ellipsized())
this._switchUserSubMenu.label.text = this._user.get_user_name();
let iconFile = this._user.get_icon_file();
if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS))
iconFile = null;
if (iconFile) {
let file = Gio.File.new_for_path(iconFile);
let gicon = new Gio.FileIcon({ file: file });
this._switchUserSubMenu.icon.gicon = gicon;
} else {
this._switchUserSubMenu.icon.icon_name = 'avatar-default-symbolic';
}
},
_updateOrientationLock: function() {
this._orientationLockAction.visible = this._orientationExists;
let locked = this._orientationSettings.get_boolean('orientation-lock');
let icon = this._orientationLockAction.child;
icon.icon_name = locked ? 'rotation-locked-symbolic' : 'rotation-allowed-symbolic';
this._updateActionsVisibility();
},
_updateLockScreen: function() {
let showLock = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY);
this._lockScreenAction.visible = showLock && allowLockScreen && LoginManager.canLock();
this._updateActionsVisibility();
},
_updateHaveShutdown: function() {
this._session.CanShutdownRemote(Lang.bind(this, function(result, error) {
if (error)
return;
this._haveShutdown = result[0];
this._updatePowerOff();
}));
},
_updatePowerOff: function() {
this._powerOffAction.visible = this._haveShutdown && !Main.sessionMode.isLocked;
this._updateActionsVisibility();
},
_updateHaveSuspend: function() {
this._loginManager.canSuspend(Lang.bind(this, function(result) {
this._haveSuspend = result;
this._updateSuspend();
}));
},
_updateSuspend: function() {
this._suspendAction.visible = this._haveSuspend && !Main.sessionMode.isLocked;
this._updateActionsVisibility();
},
_createActionButton: function(iconName, accessibleName) {
let icon = new St.Button({ reactive: true,
can_focus: true,
track_hover: true,
accessible_name: accessibleName,
style_class: 'system-menu-action' });
icon.child = new St.Icon({ icon_name: iconName });
return icon;
},
_createSubMenu: function() {
let item;
this._switchUserSubMenu = new PopupMenu.PopupSubMenuMenuItem('', true);
this._switchUserSubMenu.icon.style_class = 'system-switch-user-submenu-icon';
// Since the label of the switch user submenu depends on the width of
// the popup menu, and we can't easily connect on allocation-changed
// or notify::width without creating layout cycles, simply update the
// label whenever the menu is opened.
this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
if (isOpen)
this._updateSwitchUserSubMenu();
}));
item = new PopupMenu.PopupMenuItem(_("Switch User"));
item.connect('activate', Lang.bind(this, this._onLoginScreenActivate));
this._switchUserSubMenu.menu.addMenuItem(item);
this._loginScreenItem = item;
item = new PopupMenu.PopupMenuItem(_("Log Out"));
item.connect('activate', Lang.bind(this, this._onQuitSessionActivate));
this._switchUserSubMenu.menu.addMenuItem(item);
this._logoutItem = item;
this._user.connect('notify::is-loaded', Lang.bind(this, this._updateSwitchUserSubMenu));
this._user.connect('changed', Lang.bind(this, this._updateSwitchUserSubMenu));
this.menu.addMenuItem(this._switchUserSubMenu);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
item = new PopupMenu.PopupBaseMenuItem({ reactive: false,
can_focus: false });
this._settingsAction = this._createActionButton('preferences-system-symbolic', _("Settings"));
this._settingsAction.connect('clicked', Lang.bind(this, this._onSettingsClicked));
item.actor.add(this._settingsAction, { expand: true, x_fill: false });
this._orientationLockAction = this._createActionButton('', _("Orientation Lock"));
this._orientationLockAction.connect('clicked', Lang.bind(this, this._onOrientationLockClicked));
item.actor.add(this._orientationLockAction, { expand: true, x_fill: false });
this._lockScreenAction = this._createActionButton('changes-prevent-symbolic', _("Lock"));
this._lockScreenAction.connect('clicked', Lang.bind(this, this._onLockScreenClicked));
item.actor.add(this._lockScreenAction, { expand: true, x_fill: false });
this._suspendAction = this._createActionButton('media-playback-pause-symbolic', _("Suspend"));
this._suspendAction.connect('clicked', Lang.bind(this, this._onSuspendClicked));
this._powerOffAction = this._createActionButton('system-shutdown-symbolic', _("Power Off"));
this._powerOffAction.connect('clicked', Lang.bind(this, this._onPowerOffClicked));
this._altSwitcher = new AltSwitcher(this._powerOffAction, this._suspendAction);
item.actor.add(this._altSwitcher.actor, { expand: true, x_fill: false });
this._actionsItem = item;
this.menu.addMenuItem(item);
},
_onSettingsClicked: function() {
this.menu.itemActivated();
let app = Shell.AppSystem.get_default().lookup_app('gnome-control-center.desktop');
Main.overview.hide();
app.activate();
},
_onOrientationLockClicked: function() {
this.menu.itemActivated();
let locked = this._orientationSettings.get_boolean('orientation-lock');
this._orientationSettings.set_boolean('orientation-lock', !locked);
this._updateOrientationLock();
},
_onLockScreenClicked: function() {
this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
Main.overview.hide();
Main.screenShield.lock(true);
},
_onLoginScreenActivate: function() {
this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
Main.overview.hide();
if (Main.screenShield)
Main.screenShield.lock(false);
Clutter.threads_add_repaint_func_full(Clutter.RepaintFlags.POST_PAINT, function() {
Gdm.goto_login_session_sync(null);
return false;
});
},
_onQuitSessionActivate: function() {
Main.overview.hide();
this._session.LogoutRemote(0);
},
_onPowerOffClicked: function() {
this.menu.itemActivated();
Main.overview.hide();
this._session.ShutdownRemote(0);
},
_onSuspendClicked: function() {
this.menu.itemActivated();
this._loginManager.suspend();
},
});

View File

@ -9,7 +9,8 @@ const Signals = imports.signals;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Slider = imports.ui.slider;
const VOLUME_ADJUSTMENT_STEP = 0.05; /* Volume adjustment step in % */
const VOLUME_NOTIFY_ID = 1; const VOLUME_NOTIFY_ID = 1;
@ -29,26 +30,21 @@ function getMixerControl() {
const StreamSlider = new Lang.Class({ const StreamSlider = new Lang.Class({
Name: 'StreamSlider', Name: 'StreamSlider',
_init: function(control) { _init: function(control, title) {
this._control = control; this._control = control;
this.item = new PopupMenu.PopupBaseMenuItem({ activate: false }); this.item = new PopupMenu.PopupMenuSection();
this._slider = new Slider.Slider(0); this._title = new PopupMenu.PopupMenuItem(title, { reactive: false });
this._slider = new PopupMenu.PopupSliderMenuItem(0);
this._slider.connect('value-changed', Lang.bind(this, this._sliderChanged)); this._slider.connect('value-changed', Lang.bind(this, this._sliderChanged));
this._slider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange)); this._slider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
this._icon = new St.Icon({ style_class: 'popup-menu-icon' }); this.item.addMenuItem(this._title);
this.item.actor.add(this._icon); this.item.addMenuItem(this._slider);
this.item.actor.add(this._slider.actor, { expand: true });
this.item.actor.connect('button-press-event', Lang.bind(this, function(actor, event) {
this._slider.startDragging(event);
}));
this.item.actor.connect('key-press-event', Lang.bind(this, function(actor, event) {
return this._slider.onKeyPressEvent(actor, event);
}));
this._stream = null; this._stream = null;
this._shouldShow = true;
}, },
get stream() { get stream() {
@ -90,7 +86,8 @@ const StreamSlider = new Lang.Class({
_updateVisibility: function() { _updateVisibility: function() {
let visible = this._shouldBeVisible(); let visible = this._shouldBeVisible();
this.item.actor.visible = visible; this._title.actor.visible = visible;
this._slider.actor.visible = visible;
}, },
scroll: function(event) { scroll: function(event) {
@ -157,11 +154,6 @@ const OutputStreamSlider = new Lang.Class({
Name: 'OutputStreamSlider', Name: 'OutputStreamSlider',
Extends: StreamSlider, Extends: StreamSlider,
_init: function(control) {
this.parent(control);
this._slider.actor.accessible_name = _("Volume");
},
_connectStream: function(stream) { _connectStream: function(stream) {
this.parent(stream); this.parent(stream);
this._portChangedId = stream.connect('notify::port', Lang.bind(this, this._portChanged)); this._portChangedId = stream.connect('notify::port', Lang.bind(this, this._portChanged));
@ -189,17 +181,11 @@ const OutputStreamSlider = new Lang.Class({
this._portChangedId = 0; this._portChangedId = 0;
}, },
_updateSliderIcon: function() {
this._icon.icon_name = (this._hasHeadphones ?
'audio-headphones-symbolic' :
'audio-speakers-symbolic');
},
_portChanged: function() { _portChanged: function() {
let hasHeadphones = this._findHeadphones(this._stream); let hasHeadphones = this._findHeadphones(this._stream);
if (hasHeadphones != this._hasHeadphones) { if (hasHeadphones != this._hasHeadphones) {
this._hasHeadphones = hasHeadphones; this._hasHeadphones = hasHeadphones;
this._updateSliderIcon(); this.emit('headphones-changed', this._hasHeadphones);
} }
} }
}); });
@ -208,12 +194,10 @@ const InputStreamSlider = new Lang.Class({
Name: 'InputStreamSlider', Name: 'InputStreamSlider',
Extends: StreamSlider, Extends: StreamSlider,
_init: function(control) { _init: function(control, title) {
this.parent(control); this.parent(control, title);
this._slider.actor.accessible_name = _("Microphone");
this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput)); this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput));
this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput)); this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput));
this._icon.icon_name = 'audio-input-microphone-symbolic';
}, },
_connectStream: function(stream) { _connectStream: function(stream) {
@ -261,13 +245,17 @@ const VolumeMenu = new Lang.Class({
this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput)); this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput));
this._control.connect('default-source-changed', Lang.bind(this, this._readInput)); this._control.connect('default-source-changed', Lang.bind(this, this._readInput));
this._output = new OutputStreamSlider(this._control); /* Translators: This is the label for audio volume */
this._output = new OutputStreamSlider(this._control, _("Volume"));
this._output.connect('stream-updated', Lang.bind(this, function() { this._output.connect('stream-updated', Lang.bind(this, function() {
this.emit('icon-changed'); this.emit('icon-changed');
})); }));
this._output.connect('headphones-changed', Lang.bind(this, function(stream, value) {
this.emit('headphones-changed', value);
}));
this.addMenuItem(this._output.item); this.addMenuItem(this._output.item);
this._input = new InputStreamSlider(this._control); this._input = new InputStreamSlider(this._control, _("Microphone"));
this.addMenuItem(this._input.item); this.addMenuItem(this._input.item);
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
@ -303,29 +291,31 @@ const VolumeMenu = new Lang.Class({
const Indicator = new Lang.Class({ const Indicator = new Lang.Class({
Name: 'VolumeIndicator', Name: 'VolumeIndicator',
Extends: PanelMenu.SystemIndicator, Extends: PanelMenu.SystemStatusButton,
_init: function() { _init: function() {
this.parent(); this.parent('audio-volume-muted-symbolic', _("Volume"));
this._primaryIndicator = this._addIndicator();
this._control = getMixerControl(); this._control = getMixerControl();
this._volumeMenu = new VolumeMenu(this._control); this._volumeMenu = new VolumeMenu(this._control);
this._volumeMenu.connect('icon-changed', Lang.bind(this, function(menu) { this._volumeMenu.connect('icon-changed', Lang.bind(this, function(menu) {
let icon = this._volumeMenu.getIcon(); let icon = this._volumeMenu.getIcon();
this.actor.visible = (icon != null);
if (icon != null) { this.setIcon(icon);
this.indicators.show();
this._primaryIndicator.icon_name = icon;
} else {
this.indicators.hide();
}
})); }));
this._volumeMenu.connect('headphones-changed', Lang.bind(this, function(menu, value) {
this._headphoneIcon.visible = value;
}));
this._headphoneIcon = this.addIcon(new Gio.ThemedIcon({ name: 'headphones-symbolic' }));
this._headphoneIcon.visible = false;
this.menu.addMenuItem(this._volumeMenu); this.menu.addMenuItem(this._volumeMenu);
this.indicators.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
}, },
_onScrollEvent: function(actor, event) { _onScrollEvent: function(actor, event) {

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 AccountsService = imports.gi.AccountsService; const AccountsService = imports.gi.AccountsService;
const Atk = imports.gi.Atk;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gdm = imports.gi.Gdm; const Gdm = imports.gi.Gdm;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
@ -9,18 +8,18 @@ const GLib = imports.gi.GLib;
const GnomeDesktop = imports.gi.GnomeDesktop; const GnomeDesktop = imports.gi.GnomeDesktop;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Signals = imports.signals; const Signals = imports.signals;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const Layout = imports.ui.layout;
const Main = imports.ui.main; const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const UserMenu = imports.ui.userMenu;
const UserWidget = imports.ui.userWidget; const UserWidget = imports.ui.userWidget;
const AuthPrompt = imports.gdm.authPrompt;
const Batch = imports.gdm.batch; const Batch = imports.gdm.batch;
const GdmUtil = imports.gdm.util; const GdmUtil = imports.gdm.util;
const LoginDialog = imports.gdm.loginDialog; const LoginDialog = imports.gdm.loginDialog;
@ -30,37 +29,95 @@ const IDLE_TIMEOUT = 2 * 60;
const UnlockDialog = new Lang.Class({ const UnlockDialog = new Lang.Class({
Name: 'UnlockDialog', Name: 'UnlockDialog',
Extends: ModalDialog.ModalDialog,
_init: function(parentActor) { _init: function(parentActor) {
this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, this.parent({ shellReactive: true,
style_class: 'login-dialog', styleClass: 'login-dialog',
layout_manager: new Clutter.BoxLayout(), keybindingMode: Shell.KeyBindingMode.UNLOCK_SCREEN,
visible: false }); parentActor: parentActor
});
this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true }));
parentActor.add_child(this.actor);
this._userManager = AccountsService.UserManager.get_default(); this._userManager = AccountsService.UserManager.get_default();
this._userName = GLib.get_user_name(); this._userName = GLib.get_user_name();
this._user = this._userManager.get_user(this._userName); this._user = this._userManager.get_user(this._userName);
this._promptBox = new St.BoxLayout({ vertical: true, this._failCounter = 0;
x_align: Clutter.ActorAlign.CENTER, this._firstQuestion = true;
y_align: Clutter.ActorAlign.CENTER,
x_expand: true,
y_expand: true });
this.actor.add_child(this._promptBox);
this._authPrompt = new AuthPrompt.AuthPrompt(new Gdm.Client(), AuthPrompt.AuthPromptMode.UNLOCK_ONLY); this._greeterClient = new Gdm.Client();
this._authPrompt.connect('failed', Lang.bind(this, this._fail)); this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true });
this._authPrompt.connect('cancelled', Lang.bind(this, this._fail)); this._userVerified = false;
this._authPrompt.connect('reset', Lang.bind(this, this._onReset));
this._authPrompt.setPasswordChar('\u25cf');
this._authPrompt.nextButton.label = _("Unlock");
this._promptBox.add_child(this._authPrompt.actor); this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion));
this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage));
this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed));
this._userVerifier.connect('reset', Lang.bind(this, this._onReset));
this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint));
this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint));
this._userWidget = new UserWidget.UserWidget(this._user);
this.contentLayout.add_actor(this._userWidget.actor);
this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
vertical: true });
this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' });
this._promptLayout.add(this._promptLabel,
{ x_align: St.Align.START });
this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
can_focus: true });
this._promptEntry.clutter_text.connect('activate', Lang.bind(this, this._doUnlock));
this._promptEntry.clutter_text.set_password_char('\u25cf');
ShellEntry.addContextMenu(this._promptEntry, { isPassword: true });
this.setInitialKeyFocus(this._promptEntry);
this._promptEntry.clutter_text.connect('text-changed', Lang.bind(this, function() {
this._updateOkButtonSensitivity(this._promptEntry.text.length > 0);
}));
this._promptLayout.add(this._promptEntry,
{ expand: true,
x_fill: true });
this.contentLayout.add_actor(this._promptLayout);
this._promptMessage = new St.Label({ visible: false });
this.contentLayout.add(this._promptMessage, { x_fill: true });
this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint' });
this._promptLoginHint.hide();
this.contentLayout.add_actor(this._promptLoginHint);
this._workSpinner = new Panel.AnimatedIcon('process-working.svg', LoginDialog.WORK_SPINNER_ICON_SIZE);
this._workSpinner.actor.opacity = 0;
this.allowCancel = false; this.allowCancel = false;
this.buttonLayout.visible = true;
this.addButton({ label: _("Cancel"),
action: Lang.bind(this, this._escape),
key: Clutter.KEY_Escape },
{ expand: true,
x_fill: false,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
this.buttonLayout.add(this._workSpinner.actor,
{ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
this._okButton = this.addButton({ label: _("Unlock"),
action: Lang.bind(this, this._doUnlock),
default: true },
{ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' }); let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' });
if (screenSaverSettings.get_boolean('user-switch-enabled')) { if (screenSaverSettings.get_boolean('user-switch-enabled')) {
@ -73,100 +130,196 @@ const UnlockDialog = new Lang.Class({
x_align: St.Align.START, x_align: St.Align.START,
x_fill: true }); x_fill: true });
this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked)); this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked));
this._promptBox.add_child(this._otherUserButton); this.dialogLayout.add(this._otherUserButton,
{ x_align: St.Align.START,
x_fill: false });
} else { } else {
this._otherUserButton = null; this._otherUserButton = null;
} }
this._authPrompt.reset();
this._updateSensitivity(true); this._updateSensitivity(true);
Main.ctrlAltTabManager.addGroup(this.actor, _("Unlock Window"), 'dialog-password-symbolic'); let batch = new Batch.Hold();
this._userVerifier.begin(this._userName, batch);
this._idleMonitor = Meta.IdleMonitor.get_core(); Main.ctrlAltTabManager.addGroup(this.dialogLayout, _("Unlock Window"), 'dialog-password-symbolic');
this._idleMonitor = new GnomeDesktop.IdleMonitor();
this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape)); this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape));
}, },
_updateSensitivity: function(sensitive) { _updateSensitivity: function(sensitive) {
this._authPrompt.updateSensitivity(sensitive); this._promptEntry.reactive = sensitive;
this._promptEntry.clutter_text.editable = sensitive;
this._updateOkButtonSensitivity(sensitive && this._promptEntry.text.length > 0);
if (this._otherUserButton) { if (this._otherUserButton) {
this._otherUserButton.reactive = sensitive; this._otherUserButton.reactive = sensitive;
this._otherUserButton.can_focus = sensitive; this._otherUserButton.can_focus = sensitive;
} }
}, },
_fail: function() { _updateOkButtonSensitivity: function(sensitive) {
this.emit('failed'); this._okButton.reactive = sensitive;
this._okButton.can_focus = sensitive;
}, },
_onReset: function(authPrompt, beginRequest) { _setWorking: function(working) {
let userName; if (working) {
if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { this._workSpinner.play();
this._authPrompt.setUser(this._user); Tweener.addTween(this._workSpinner.actor,
userName = this._userName; { opacity: 255,
delay: LoginDialog.WORK_SPINNER_ANIMATION_DELAY,
time: LoginDialog.WORK_SPINNER_ANIMATION_TIME,
transition: 'linear'
});
} else { } else {
userName = null; Tweener.addTween(this._workSpinner.actor,
{ opacity: 0,
time: LoginDialog.WORK_SPINNER_ANIMATION_TIME,
transition: 'linear',
onCompleteScope: this,
onComplete: function() {
this._workSpinner.stop();
}
});
}
},
_showMessage: function(userVerifier, message, styleClass) {
if (message) {
this._promptMessage.text = message;
this._promptMessage.styleClass = styleClass;
GdmUtil.fadeInActor(this._promptMessage);
} else {
GdmUtil.fadeOutActor(this._promptMessage);
}
},
_onAskQuestion: function(verifier, serviceName, question, passwordChar) {
if (this._firstQuestion && this._firstQuestionAnswer) {
this._userVerifier.answerQuery(serviceName, this._firstQuestionAnswer);
this._firstQuestionAnswer = null;
this._firstQuestion = false;
return;
} }
this._authPrompt.begin({ userName: userName }); this._promptLabel.text = question;
if (!this._firstQuestion)
this._promptEntry.text = '';
else
this._firstQuestion = false;
this._promptEntry.clutter_text.set_password_char(passwordChar);
this._promptEntry.menu.isPassword = passwordChar != '';
this._currentQuery = serviceName;
this._updateSensitivity(true);
this._setWorking(false);
},
_showLoginHint: function(verifier, message) {
this._promptLoginHint.set_text(message)
GdmUtil.fadeInActor(this._promptLoginHint);
},
_hideLoginHint: function() {
GdmUtil.fadeOutActor(this._promptLoginHint);
},
_doUnlock: function() {
if (this._firstQuestion) {
// we haven't received a query yet, so stash the answer
// and make ourself non-reactive
// the actual reply to GDM will be sent as soon as asked
this._firstQuestionAnswer = this._promptEntry.text;
this._updateSensitivity(false);
this._setWorking(true);
return;
}
if (!this._currentQuery)
return;
let query = this._currentQuery;
this._currentQuery = null;
this._updateSensitivity(false);
this._setWorking(true);
this._userVerifier.answerQuery(query, this._promptEntry.text);
},
_finishUnlock: function() {
this._userVerifier.clear();
this.emit('unlocked');
},
_onVerificationComplete: function() {
this._userVerified = true;
if (!this._userVerifier.hasPendingMessages) {
this._finishUnlock();
} else {
let signalId = this._userVerifier.connect('no-more-messages',
Lang.bind(this, function() {
this._userVerifier.disconnect(signalId);
this._finishUnlock();
}));
}
},
_onReset: function() {
if (!this._userVerified) {
this._userVerifier.clear();
this.emit('failed');
}
},
_onVerificationFailed: function() {
this._currentQuery = null;
this._firstQuestion = true;
this._userVerified = false;
this._promptEntry.text = '';
this._promptEntry.clutter_text.set_password_char('\u25cf');
this._promptEntry.menu.isPassword = true;
this._updateSensitivity(false);
this._setWorking(false);
}, },
_escape: function() { _escape: function() {
if (this.allowCancel) if (this.allowCancel) {
this._authPrompt.cancel(); this._userVerifier.cancel();
this.emit('failed');
}
}, },
_otherUserClicked: function(button, event) { _otherUserClicked: function(button, event) {
Gdm.goto_login_session_sync(null); Gdm.goto_login_session_sync(null);
this._authPrompt.cancel(); this._userVerifier.cancel();
this.emit('failed');
}, },
destroy: function() { destroy: function() {
this.popModal(); this._userVerifier.clear();
this.actor.destroy();
if (this._idleWatchId) { if (this._idleWatchId) {
this._idleMonitor.remove_watch(this._idleWatchId); this._idleMonitor.remove_watch(this._idleWatchId);
this._idleWatchId = 0; this._idleWatchId = 0;
} }
this.parent();
}, },
cancel: function() { cancel: function() {
this._authPrompt.cancel(); this._userVerifier.cancel(null);
this.destroy(); this.destroy();
}, },
addCharacter: function(unichar) { addCharacter: function(unichar) {
this._authPrompt.addCharacter(unichar); this._promptEntry.clutter_text.insert_unichar(unichar);
}, },
finish: function(onComplete) {
this._authPrompt.finish(onComplete);
},
open: function(timestamp) {
this.actor.show();
if (this._isModal)
return true;
if (!Main.pushModal(this.actor, { timestamp: timestamp,
keybindingMode: Shell.KeyBindingMode.UNLOCK_SCREEN }))
return false;
this._isModal = true;
return true;
},
popModal: function(timestamp) {
if (this._isModal) {
Main.popModal(this.actor, timestamp);
this._isModal = false;
}
}
}); });
Signals.addSignalMethods(UnlockDialog.prototype);

998
js/ui/userMenu.js Normal file
View File

@ -0,0 +1,998 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const AccountsService = imports.gi.AccountsService;
const Gdm = imports.gi.Gdm;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Pango = imports.gi.Pango;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Tp = imports.gi.TelepathyGLib;
const Atk = imports.gi.Atk;
const Clutter = imports.gi.Clutter;
const BoxPointer = imports.ui.boxpointer;
const GnomeSession = imports.misc.gnomeSession;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Params = imports.misc.params;
const Util = imports.misc.util;
const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const PRIVACY_SCHEMA = 'org.gnome.desktop.privacy'
const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen';
const DISABLE_LOG_OUT_KEY = 'disable-log-out';
const ALWAYS_SHOW_LOG_OUT_KEY = 'always-show-log-out';
const SHOW_FULL_NAME_IN_TOP_BAR_KEY = 'show-full-name-in-top-bar';
const DIALOG_ICON_SIZE = 64;
const MAX_USERS_IN_SESSION_DIALOG = 5;
const IMStatus = {
AVAILABLE: 0,
BUSY: 1,
HIDDEN: 2,
AWAY: 3,
IDLE: 4,
OFFLINE: 5,
LAST: 6
};
const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'>
<property name="Id" type="s" access="read"/>
<property name="Remote" type="b" access="read"/>
<property name="Class" type="s" access="read"/>
<property name="Type" type="s" access="read"/>
<property name="State" type="s" access="read"/>
</interface>;
const SystemdLoginSession = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface);
// Adapted from gdm/gui/user-switch-applet/applet.c
//
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
// Copyright (C) 2008,2009 Red Hat, Inc.
const UserAvatarWidget = new Lang.Class({
Name: 'UserAvatarWidget',
_init: function(user, params) {
this._user = user;
params = Params.parse(params, { reactive: false,
iconSize: DIALOG_ICON_SIZE,
styleClass: 'status-chooser-user-icon' });
this._iconSize = params.iconSize;
this.actor = new St.Bin({ style_class: params.styleClass,
track_hover: params.reactive,
reactive: params.reactive });
},
setSensitive: function(sensitive) {
this.actor.can_focus = sensitive;
this.actor.reactive = sensitive;
},
update: function() {
let iconFile = this._user.get_icon_file();
if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS))
iconFile = null;
if (iconFile) {
let file = Gio.File.new_for_path(iconFile);
this.actor.child = null;
this.actor.style = 'background-image: url("%s");'.format(iconFile);
} else {
this.actor.style = null;
this.actor.child = new St.Icon({ icon_name: 'avatar-default-symbolic',
icon_size: this._iconSize });
}
}
});
const IMStatusItem = new Lang.Class({
Name: 'IMStatusItem',
Extends: PopupMenu.PopupBaseMenuItem,
_init: function(label, iconName) {
this.parent();
this.actor.add_style_class_name('status-chooser-status-item');
this._icon = new St.Icon({ style_class: 'popup-menu-icon' });
this.addActor(this._icon);
if (iconName)
this._icon.icon_name = iconName;
this.label = new St.Label({ text: label });
this.actor.label_actor = this.label;
this.addActor(this.label);
}
});
const IMUserNameItem = new Lang.Class({
Name: 'IMUserNameItem',
Extends: PopupMenu.PopupBaseMenuItem,
_init: function() {
this.parent({ reactive: false,
can_focus: false,
style_class: 'status-chooser-user-name' });
this._wrapper = new Shell.GenericContainer();
this._wrapper.connect('get-preferred-width',
Lang.bind(this, this._wrapperGetPreferredWidth));
this._wrapper.connect('get-preferred-height',
Lang.bind(this, this._wrapperGetPreferredHeight));
this._wrapper.connect('allocate',
Lang.bind(this, this._wrapperAllocate));
this.addActor(this._wrapper, { expand: true, span: -1 });
this.label = new St.Label();
this.label.clutter_text.set_line_wrap(true);
this.label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
this._wrapper.add_actor(this.label);
},
_wrapperGetPreferredWidth: function(actor, forHeight, alloc) {
alloc.min_size = 1;
alloc.natural_size = 1;
},
_wrapperGetPreferredHeight: function(actor, forWidth, alloc) {
[alloc.min_size, alloc.natural_size] = this.label.get_preferred_height(forWidth);
},
_wrapperAllocate: function(actor, box, flags) {
this.label.allocate(box, flags);
}
});
const IMStatusChooserItem = new Lang.Class({
Name: 'IMStatusChooserItem',
Extends: PopupMenu.PopupBaseMenuItem,
_init: function() {
this.parent({ reactive: false,
can_focus: false,
style_class: 'status-chooser' });
this._userManager = AccountsService.UserManager.get_default();
this._user = this._userManager.get_user(GLib.get_user_name());
this._avatar = new UserAvatarWidget(this._user, { reactive: true });
this._iconBin = new St.Button({ child: this._avatar.actor });
this.addActor(this._iconBin);
this._iconBin.connect('clicked', Lang.bind(this,
function() {
this.activate();
}));
this._section = new PopupMenu.PopupMenuSection();
this.addActor(this._section.actor);
this._name = new IMUserNameItem();
this._section.addMenuItem(this._name);
this._combo = new PopupMenu.PopupComboBoxMenuItem({ style_class: 'status-chooser-combo' });
this._section.addMenuItem(this._combo);
let item;
item = new IMStatusItem(_("Available"), 'user-available-symbolic');
this._combo.addMenuItem(item, IMStatus.AVAILABLE);
item = new IMStatusItem(_("Busy"), 'user-busy-symbolic');
this._combo.addMenuItem(item, IMStatus.BUSY);
item = new IMStatusItem(_("Invisible"), 'user-invisible-symbolic');
this._combo.addMenuItem(item, IMStatus.HIDDEN);
item = new IMStatusItem(_("Away"), 'user-away-symbolic');
this._combo.addMenuItem(item, IMStatus.AWAY);
item = new IMStatusItem(_("Idle"), 'user-idle-symbolic');
this._combo.addMenuItem(item, IMStatus.IDLE);
item = new IMStatusItem(_("Offline"), 'user-offline-symbolic');
this._combo.addMenuItem(item, IMStatus.OFFLINE);
this._combo.connect('active-item-changed',
Lang.bind(this, this._changeIMStatus));
this._presence = new GnomeSession.Presence();
this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) {
this._sessionStatusChanged(status);
}));
this._sessionPresenceRestored = false;
this._imPresenceRestored = false;
this._currentPresence = undefined;
this._accountMgr = Tp.AccountManager.dup();
this._accountMgr.connect('most-available-presence-changed',
Lang.bind(this, this._IMStatusChanged));
this._accountMgr.connect('account-enabled',
Lang.bind(this, this._IMAccountsChanged));
this._accountMgr.connect('account-disabled',
Lang.bind(this, this._IMAccountsChanged));
this._accountMgr.connect('account-removed',
Lang.bind(this, this._IMAccountsChanged));
this._accountMgr.connect('account-validity-changed',
Lang.bind(this, this._IMAccountsChanged));
this._accountMgr.prepare_async(null, Lang.bind(this,
function(mgr) {
this._IMAccountsChanged(mgr);
if (this._networkMonitor.network_available)
this._restorePresence();
else
this._setComboboxPresence(Tp.ConnectionPresenceType.OFFLINE);
}));
this._networkMonitor = Gio.NetworkMonitor.get_default();
this._networkMonitor.connect('network-changed',
Lang.bind(this, function(monitor, available) {
this._IMAccountsChanged(this._accountMgr);
if (available && !this._imPresenceRestored)
this._restorePresence();
}));
this._userLoadedId = this._user.connect('notify::is-loaded',
Lang.bind(this,
this._updateUser));
this._userChangedId = this._user.connect('changed',
Lang.bind(this,
this._updateUser));
this.actor.connect('notify::mapped', Lang.bind(this, function() {
if (this.actor.mapped)
this._updateUser();
}));
this.connect('sensitive-changed', function(sensitive) {
this._avatar.setSensitive(sensitive);
});
},
_restorePresence: function() {
let [presence, status, msg] = this._accountMgr.get_most_available_presence();
let savedPresence = global.settings.get_int('saved-im-presence');
if (savedPresence == presence) {
this._IMStatusChanged(this._accountMgr, presence, status, msg);
} else {
this._setComboboxPresence(savedPresence);
status = this._statusForPresence(savedPresence);
msg = msg ? msg : '';
this._accountMgr.set_all_requested_presences(savedPresence, status, msg);
}
},
destroy: function() {
// clean up signal handlers
if (this._userLoadedId != 0) {
this._user.disconnect(this._userLoadedId);
this._userLoadedId = 0;
}
if (this._userChangedId != 0) {
this._user.disconnect(this._userChangedId);
this._userChangedId = 0;
}
this.parent();
},
// Override getColumnWidths()/setColumnWidths() to make the item
// independent from the overall column layout of the menu
getColumnWidths: function() {
return [];
},
setColumnWidths: function(widths) {
},
_updateUser: function() {
if (this._user.is_loaded)
this._name.label.set_text(this._user.get_real_name());
else
this._name.label.set_text("");
this._avatar.update();
},
_statusForPresence: function(presence) {
switch(presence) {
case Tp.ConnectionPresenceType.AVAILABLE:
return 'available';
case Tp.ConnectionPresenceType.BUSY:
return 'busy';
case Tp.ConnectionPresenceType.OFFLINE:
return 'offline';
case Tp.ConnectionPresenceType.HIDDEN:
return 'hidden';
case Tp.ConnectionPresenceType.AWAY:
return 'away';
case Tp.ConnectionPresenceType.EXTENDED_AWAY:
return 'xa';
default:
return 'unknown';
}
},
_IMAccountsChanged: function(mgr) {
let accounts = mgr.get_valid_accounts().filter(function(account) {
return account.enabled;
});
let sensitive = accounts.length > 0 && this._networkMonitor.network_available;
this._combo.setSensitive(sensitive);
},
_IMStatusChanged: function(accountMgr, presence, status, message) {
if (!this._imPresenceRestored)
this._imPresenceRestored = true;
if (presence == this._currentPresence)
return;
this._currentPresence = presence;
this._setComboboxPresence(presence);
if (!this._sessionPresenceRestored) {
this._sessionStatusChanged(this._presence.status);
return;
}
if (presence == Tp.ConnectionPresenceType.AVAILABLE)
this._presence.status = GnomeSession.PresenceStatus.AVAILABLE;
// We ignore the actual value of _expectedPresence and never safe
// the first presence change after an "automatic" change, assuming
// that it is the response to our request; this is to account for
// mission control falling back to "similar" presences if an account
// type does not implement the requested presence.
if (!this._expectedPresence)
global.settings.set_int('saved-im-presence', presence);
else
this._expectedPresence = undefined;
},
_setComboboxPresence: function(presence) {
let activatedItem;
if (presence == Tp.ConnectionPresenceType.AVAILABLE)
activatedItem = IMStatus.AVAILABLE;
else if (presence == Tp.ConnectionPresenceType.BUSY)
activatedItem = IMStatus.BUSY;
else if (presence == Tp.ConnectionPresenceType.HIDDEN)
activatedItem = IMStatus.HIDDEN;
else if (presence == Tp.ConnectionPresenceType.AWAY)
activatedItem = IMStatus.AWAY;
else if (presence == Tp.ConnectionPresenceType.EXTENDED_AWAY)
activatedItem = IMStatus.IDLE;
else
activatedItem = IMStatus.OFFLINE;
this._combo.setActiveItem(activatedItem);
for (let i = 0; i < IMStatus.LAST; i++) {
if (i == IMStatus.AVAILABLE || i == IMStatus.OFFLINE)
continue; // always visible
this._combo.setItemVisible(i, i == activatedItem);
}
},
_changeIMStatus: function(menuItem, id) {
let [presence, s, msg] = this._accountMgr.get_most_available_presence();
let newPresence, status;
if (id == IMStatus.AVAILABLE) {
newPresence = Tp.ConnectionPresenceType.AVAILABLE;
} else if (id == IMStatus.OFFLINE) {
newPresence = Tp.ConnectionPresenceType.OFFLINE;
} else
return;
status = this._statusForPresence(newPresence);
msg = msg ? msg : '';
this._accountMgr.set_all_requested_presences(newPresence, status, msg);
},
getIMPresenceForSessionStatus: function(sessionStatus) {
// Restore the last user-set presence when coming back from
// BUSY/IDLE (otherwise the last user-set presence matches
// the current one)
if (sessionStatus == GnomeSession.PresenceStatus.AVAILABLE)
return global.settings.get_int('saved-im-presence');
if (sessionStatus == GnomeSession.PresenceStatus.BUSY) {
// Only change presence if the current one is "more present" than
// busy, or if coming back from idle
if (this._currentPresence == Tp.ConnectionPresenceType.AVAILABLE ||
this._currentPresence == Tp.ConnectionPresenceType.EXTENDED_AWAY)
return Tp.ConnectionPresenceType.BUSY;
}
if (sessionStatus == GnomeSession.PresenceStatus.IDLE) {
// Only change presence if the current one is "more present" than
// idle
if (this._currentPresence != Tp.ConnectionPresenceType.OFFLINE &&
this._currentPresence != Tp.ConnectionPresenceType.HIDDEN)
return Tp.ConnectionPresenceType.EXTENDED_AWAY;
}
return this._currentPresence;
},
_sessionStatusChanged: function(sessionStatus) {
if (!this._imPresenceRestored)
return;
let savedStatus = global.settings.get_int('saved-session-presence');
if (!this._sessionPresenceRestored) {
// We should never save/restore a status other than AVAILABLE
// or BUSY
if (savedStatus != GnomeSession.PresenceStatus.AVAILABLE &&
savedStatus != GnomeSession.PresenceStatus.BUSY)
savedStatus = GnomeSession.PresenceStatus.AVAILABLE;
if (sessionStatus != savedStatus) {
this._presence.status = savedStatus;
return;
}
this._sessionPresenceRestored = true;
}
if ((sessionStatus == GnomeSession.PresenceStatus.AVAILABLE ||
sessionStatus == GnomeSession.PresenceStatus.BUSY) &&
savedStatus != sessionStatus)
global.settings.set_int('saved-session-presence', sessionStatus);
let [presence, s, msg] = this._accountMgr.get_most_available_presence();
let newPresence, status;
let newPresence = this.getIMPresenceForSessionStatus(sessionStatus);
if (!newPresence || newPresence == presence)
return;
status = this._statusForPresence(newPresence);
msg = msg ? msg : '';
this._expectedPresence = newPresence;
this._accountMgr.set_all_requested_presences(newPresence, status, msg);
}
});
const UserMenuButton = new Lang.Class({
Name: 'UserMenuButton',
Extends: PanelMenu.Button,
_init: function() {
this.parent(0.0);
this.actor.accessible_role = Atk.Role.MENU;
let box = new St.BoxLayout({ name: 'panelUserMenu' });
this.actor.add_actor(box);
this._screenSaverSettings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA });
this._privacySettings = new Gio.Settings({ schema: PRIVACY_SCHEMA });
this._userManager = AccountsService.UserManager.get_default();
this._user = this._userManager.get_user(GLib.get_user_name());
this._presence = new GnomeSession.Presence();
this._session = new GnomeSession.SessionManager();
this._haveShutdown = true;
this._haveSuspend = true;
this._accountMgr = Tp.AccountManager.dup();
this._loginManager = LoginManager.getLoginManager();
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this._iconBox = new St.Bin();
box.add(this._iconBox, { y_align: St.Align.MIDDLE, y_fill: false });
let textureCache = St.TextureCache.get_default();
this._offlineIcon = new St.Icon({ icon_name: 'user-offline-symbolic',
style_class: 'popup-menu-icon' });
this._availableIcon = new St.Icon({ icon_name: 'user-available-symbolic',
style_class: 'popup-menu-icon' });
this._busyIcon = new St.Icon({ icon_name: 'user-busy-symbolic',
style_class: 'popup-menu-icon' });
this._invisibleIcon = new St.Icon({ icon_name: 'user-invisible-symbolic',
style_class: 'popup-menu-icon' });
this._awayIcon = new St.Icon({ icon_name: 'user-away-symbolic',
style_class: 'popup-menu-icon' });
this._idleIcon = new St.Icon({ icon_name: 'user-idle-symbolic',
style_class: 'popup-menu-icon' });
this._pendingIcon = new St.Icon({ icon_name: 'user-status-pending-symbolic',
style_class: 'popup-menu-icon' });
this._lockedIcon = new St.Icon({ icon_name: 'changes-prevent-symbolic',
style_class: 'popup-menu-icon' });
this._accountMgr.connect('most-available-presence-changed',
Lang.bind(this, this._updatePresenceIcon));
this._accountMgr.connect('account-enabled',
Lang.bind(this, this._onAccountEnabled));
this._accountMgr.connect('account-removed',
Lang.bind(this, this._onAccountRemoved));
this._accountMgr.prepare_async(null, Lang.bind(this,
function(mgr) {
let [presence, s, msg] = mgr.get_most_available_presence();
this._updatePresenceIcon(mgr, presence, s, msg);
this._setupAccounts();
}));
this._name = new St.Label();
this.actor.label_actor = this._name;
box.add(this._name, { y_align: St.Align.MIDDLE, y_fill: false });
this._userLoadedId = this._user.connect('notify::is-loaded', Lang.bind(this, this._updateUserName));
this._userChangedId = this._user.connect('changed', Lang.bind(this, this._updateUserName));
this._updateUserName();
this._createSubMenu();
this._updateSwitch(this._presence.status);
this._presence.connectSignal('StatusChanged', Lang.bind(this, function (proxy, senderName, [status]) {
this._updateSwitch(status);
}));
this._userManager.connect('notify::is-loaded',
Lang.bind(this, this._updateMultiUser));
this._userManager.connect('notify::has-multiple-users',
Lang.bind(this, this._updateMultiUser));
this._userManager.connect('user-added',
Lang.bind(this, this._updateMultiUser));
this._userManager.connect('user-removed',
Lang.bind(this, this._updateMultiUser));
this._lockdownSettings.connect('changed::' + DISABLE_USER_SWITCH_KEY,
Lang.bind(this, this._updateSwitchUser));
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
Lang.bind(this, this._updateLogout));
this._lockdownSettings.connect('changed::' + DISABLE_LOCK_SCREEN_KEY,
Lang.bind(this, this._updateLockScreen));
global.settings.connect('changed::' + ALWAYS_SHOW_LOG_OUT_KEY,
Lang.bind(this, this._updateLogout));
this._screenSaverSettings.connect('changed::' + SHOW_FULL_NAME_IN_TOP_BAR_KEY,
Lang.bind(this, this._updateUserName));
this._privacySettings.connect('changed::' + SHOW_FULL_NAME_IN_TOP_BAR_KEY,
Lang.bind(this, this._updateUserName));
this._updateSwitchUser();
this._updateLogout();
this._updateLockScreen();
this._updatesFile = Gio.File.new_for_path('/var/lib/PackageKit/prepared-update');
this._updatesMonitor = this._updatesFile.monitor(Gio.FileMonitorFlags.NONE, null);
this._updatesMonitor.connect('changed', Lang.bind(this, this._updateInstallUpdates));
// Whether shutdown is available or not depends on both lockdown
// settings (disable-log-out) and Polkit policy - the latter doesn't
// notify, so we update the menu item each time the menu opens or
// the lockdown setting changes, which should be close enough.
this.menu.connect('open-state-changed', Lang.bind(this,
function(menu, open) {
if (!open)
return;
this._updateHaveShutdown();
this._updateHaveSuspend();
}));
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
Lang.bind(this, this._updateHaveShutdown));
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
if (Main.screenShield)
Main.screenShield.connect('locked-changed', Lang.bind(this, this._updatePresenceIcon));
this._sessionUpdated();
},
_sessionUpdated: function() {
this.actor.visible = !Main.sessionMode.isGreeter;
let allowSettings = Main.sessionMode.allowSettings;
this._statusChooser.setSensitive(allowSettings);
this._systemSettings.visible = allowSettings;
this.setSensitive(!Main.sessionMode.isLocked);
this._updatePresenceIcon();
this._updateUserName();
},
_onDestroy: function() {
this._user.disconnect(this._userLoadedId);
this._user.disconnect(this._userChangedId);
},
_updateUserName: function() {
let settings = this._privacySettings;
if (Main.sessionMode.isLocked)
settings = this._screenSaverSettings;
if (this._user.is_loaded && settings.get_boolean(SHOW_FULL_NAME_IN_TOP_BAR_KEY))
this._name.set_text(this._user.get_real_name());
else
this._name.set_text("");
},
_updateMultiUser: function() {
this._updateSwitchUser();
this._updateLogout();
},
_updateSwitchUser: function() {
let allowSwitch = !this._lockdownSettings.get_boolean(DISABLE_USER_SWITCH_KEY);
let multiUser = this._userManager.can_switch() && this._userManager.has_multiple_users;
this._loginScreenItem.actor.visible = allowSwitch && multiUser;
},
_updateLogout: function() {
let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY);
let alwaysShow = global.settings.get_boolean(ALWAYS_SHOW_LOG_OUT_KEY);
let systemAccount = this._user.system_account;
let localAccount = this._user.local_account;
let multiUser = this._userManager.has_multiple_users;
let multiSession = Gdm.get_session_ids().length > 1;
this._logoutItem.actor.visible = allowLogout && (alwaysShow || multiUser || multiSession || systemAccount || !localAccount);
},
_updateLockScreen: function() {
let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY);
this._lockScreenItem.actor.visible = allowLockScreen && LoginManager.canLock();
},
_updateInstallUpdates: function() {
let haveUpdates = this._updatesFile.query_exists(null);
this._installUpdatesItem.actor.visible = haveUpdates && this._haveShutdown;
},
_updateHaveShutdown: function() {
this._session.CanShutdownRemote(Lang.bind(this,
function(result, error) {
if (!error) {
this._haveShutdown = result[0];
this._updateInstallUpdates();
this._updateSuspendOrPowerOff();
}
}));
},
_updateHaveSuspend: function() {
this._loginManager.canSuspend(Lang.bind(this,
function(result) {
this._haveSuspend = result;
this._updateSuspendOrPowerOff();
}));
},
_updateSuspendOrPowerOff: function() {
if (!this._suspendOrPowerOffItem)
return;
this._suspendOrPowerOffItem.actor.visible = this._haveShutdown || this._haveSuspend;
// If we can't power off show Suspend instead
// and disable the alt key
if (!this._haveShutdown) {
this._suspendOrPowerOffItem.updateText(_("Suspend"), null);
} else if (!this._haveSuspend) {
this._suspendOrPowerOffItem.updateText(_("Power Off"), null);
} else {
this._suspendOrPowerOffItem.updateText(_("Power Off"), _("Suspend"));
}
},
_updateSwitch: function(status) {
let active = status == GnomeSession.PresenceStatus.AVAILABLE;
this._notificationsSwitch.setToggleState(active);
},
_updatePresenceIcon: function(accountMgr, presence, status, message) {
if (Main.sessionMode.isLocked)
this._iconBox.child = this._lockedIcon;
else if (presence == Tp.ConnectionPresenceType.AVAILABLE)
this._iconBox.child = this._availableIcon;
else if (presence == Tp.ConnectionPresenceType.BUSY)
this._iconBox.child = this._busyIcon;
else if (presence == Tp.ConnectionPresenceType.HIDDEN)
this._iconBox.child = this._invisibleIcon;
else if (presence == Tp.ConnectionPresenceType.AWAY)
this._iconBox.child = this._awayIcon;
else if (presence == Tp.ConnectionPresenceType.EXTENDED_AWAY)
this._iconBox.child = this._idleIcon;
else
this._iconBox.child = this._offlineIcon;
if (Main.sessionMode.isLocked)
this._iconBox.visible = Main.screenShield.locked;
else
this._iconBox.visible = true;
},
_setupAccounts: function() {
let accounts = this._accountMgr.get_valid_accounts();
for (let i = 0; i < accounts.length; i++) {
accounts[i]._changingId = accounts[i].connect('notify::connection-status',
Lang.bind(this, this._updateChangingPresence));
}
this._updateChangingPresence();
},
_onAccountEnabled: function(accountMgr, account) {
if (!account._changingId)
account._changingId = account.connect('notify::connection-status',
Lang.bind(this, this._updateChangingPresence));
this._updateChangingPresence();
},
_onAccountRemoved: function(accountMgr, account) {
if (account._changingId) {
account.disconnect(account._changingId);
account._changingId = 0;
}
this._updateChangingPresence();
},
_updateChangingPresence: function() {
let accounts = this._accountMgr.get_valid_accounts();
let changing = false;
for (let i = 0; i < accounts.length; i++) {
if (accounts[i].connection_status == Tp.ConnectionStatus.CONNECTING) {
changing = true;
break;
}
}
if (changing) {
this._iconBox.child = this._pendingIcon;
} else {
let [presence, s, msg] = this._accountMgr.get_most_available_presence();
this._updatePresenceIcon(this._accountMgr, presence, s, msg);
}
},
_createSubMenu: function() {
let item;
item = new IMStatusChooserItem();
item.connect('activate', Lang.bind(this, this._onMyAccountActivate));
this.menu.addMenuItem(item);
this._statusChooser = item;
item = new PopupMenu.PopupSwitchMenuItem(_("Notifications"));
item.connect('toggled', Lang.bind(this, this._updatePresenceStatus));
this.menu.addMenuItem(item);
this._notificationsSwitch = item;
item = new PopupMenu.PopupSeparatorMenuItem();
this.menu.addMenuItem(item);
item = new PopupMenu.PopupMenuItem(_("Settings"));
item.connect('activate', Lang.bind(this, this._onPreferencesActivate));
this.menu.addMenuItem(item);
this._systemSettings = item;
item = new PopupMenu.PopupSeparatorMenuItem();
this.menu.addMenuItem(item);
item = new PopupMenu.PopupMenuItem(_("Switch User"));
item.connect('activate', Lang.bind(this, this._onLoginScreenActivate));
this.menu.addMenuItem(item);
this._loginScreenItem = item;
item = new PopupMenu.PopupMenuItem(_("Log Out"));
item.connect('activate', Lang.bind(this, this._onQuitSessionActivate));
this.menu.addMenuItem(item);
this._logoutItem = item;
item = new PopupMenu.PopupMenuItem(_("Lock"));
item.connect('activate', Lang.bind(this, this._onLockScreenActivate));
this.menu.addMenuItem(item);
this._lockScreenItem = item;
item = new PopupMenu.PopupSeparatorMenuItem();
this.menu.addMenuItem(item);
item = new PopupMenu.PopupAlternatingMenuItem(_("Power Off"),
_("Suspend"));
this.menu.addMenuItem(item);
item.connect('activate', Lang.bind(this, this._onSuspendOrPowerOffActivate));
this._suspendOrPowerOffItem = item;
this._updateSuspendOrPowerOff();
item = new PopupMenu.PopupMenuItem(_("Install Updates & Restart"));
item.connect('activate', Lang.bind(this, this._onInstallUpdatesActivate));
this.menu.addMenuItem(item);
this._installUpdatesItem = item;
},
_updatePresenceStatus: function(item, event) {
let status;
if (item.state) {
status = GnomeSession.PresenceStatus.AVAILABLE;
} else {
status = GnomeSession.PresenceStatus.BUSY;
let [presence, s, msg] = this._accountMgr.get_most_available_presence();
let newPresence = this._statusChooser.getIMPresenceForSessionStatus(status);
if (newPresence != presence &&
newPresence == Tp.ConnectionPresenceType.BUSY)
Main.notify(_("Your chat status will be set to busy"),
_("Notifications are now disabled, including chat messages. Your online status has been adjusted to let others know that you might not see their messages."));
}
this._presence.status = status;
},
_onMyAccountActivate: function() {
Main.overview.hide();
let app = Shell.AppSystem.get_default().lookup_app('gnome-user-accounts-panel.desktop');
app.activate();
},
_onPreferencesActivate: function() {
Main.overview.hide();
let app = Shell.AppSystem.get_default().lookup_app('gnome-control-center.desktop');
app.activate();
},
_onLockScreenActivate: function() {
this.menu.close(BoxPointer.PopupAnimation.NONE);
Main.overview.hide();
Main.screenShield.lock(true);
},
_onLoginScreenActivate: function() {
this.menu.close(BoxPointer.PopupAnimation.NONE);
Main.overview.hide();
if (Main.screenShield)
Main.screenShield.lock(false);
Gdm.goto_login_session_sync(null);
},
_onQuitSessionActivate: function() {
Main.overview.hide();
this._session.LogoutRemote(0);
},
_onInstallUpdatesActivate: function() {
Main.overview.hide();
Util.spawn(['pkexec', '/usr/libexec/pk-trigger-offline-update']);
this._session.RebootRemote();
},
_openSessionWarnDialog: function(sessions) {
let dialog = new ModalDialog.ModalDialog();
let subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject',
text: _("Other users are logged in.") });
dialog.contentLayout.add(subjectLabel, { y_fill: true,
y_align: St.Align.START });
let descriptionLabel = new St.Label({ style_class: 'end-session-dialog-description'});
descriptionLabel.set_text(_("Shutting down might cause them to lose unsaved work."));
descriptionLabel.clutter_text.line_wrap = true;
dialog.contentLayout.add(descriptionLabel, { x_fill: true,
y_fill: true,
y_align: St.Align.START });
let scrollView = new St.ScrollView({ style_class: 'end-session-dialog-app-list' });
scrollView.add_style_class_name('vfade');
scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
dialog.contentLayout.add(scrollView, { x_fill: true, y_fill: true });
let userList = new St.BoxLayout({ vertical: true });
scrollView.add_actor(userList);
for (let i = 0; i < sessions.length; i++) {
let session = sessions[i];
let userEntry = new St.BoxLayout({ style_class: 'login-dialog-user-list-item',
vertical: false });
let avatar = new UserAvatarWidget(session.user);
avatar.update();
userEntry.add(avatar.actor);
let userLabelText = "";;
let userName = session.user.get_real_name() ?
session.user.get_real_name() : session.username;
if (session.info.remote)
/* Translators: Remote here refers to a remote session, like a ssh login */
userLabelText = _("%s (remote)").format(userName);
else if (session.info.type == "tty")
/* Translators: Console here refers to a tty like a VT console */
userLabelText = _("%s (console)").format(userName);
else
userLabelText = userName;
let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box',
vertical: true });
textLayout.add(new St.Label({ text: userLabelText }),
{ y_fill: false,
y_align: St.Align.MIDDLE,
expand: true });
userEntry.add(textLayout, { expand: true });
userList.add(userEntry, { x_fill: true });
}
let cancelButton = { label: _("Cancel"),
action: function() { dialog.close(); },
key: Clutter.Escape };
let powerOffButton = { label: _("Power Off"), action: Lang.bind(this, function() {
dialog.close();
this._session.ShutdownRemote();
}), default: true };
dialog.setButtons([cancelButton, powerOffButton]);
dialog.open();
},
_onSuspendOrPowerOffActivate: function() {
Main.overview.hide();
if (this._haveShutdown &&
this._suspendOrPowerOffItem.state == PopupMenu.PopupAlternatingMenuItemState.DEFAULT) {
this._loginManager.listSessions(Lang.bind(this,
function(result) {
let sessions = [];
let n = 0;
for (let i = 0; i < result.length; i++) {
let[id, uid, userName, seat, sessionPath] = result[i];
let proxy = new SystemdLoginSession(Gio.DBus.system,
'org.freedesktop.login1',
sessionPath);
if (proxy.Class != 'user')
continue;
if (proxy.State == 'closing')
continue;
if (proxy.Id == GLib.getenv('XDG_SESSION_ID'))
continue;
sessions.push({ user: this._userManager.get_user(userName),
username: userName,
info: { type: proxy.Type,
remote: proxy.Remote }
});
// limit the number of entries
n++;
if (n == MAX_USERS_IN_SESSION_DIALOG)
break;
}
if (n != 0)
this._openSessionWarnDialog(sessions);
else
this._session.ShutdownRemote();
}));
} else {
this.menu.close(BoxPointer.PopupAnimation.NONE);
this._loginManager.suspend();
}
}
});

View File

@ -3,58 +3,10 @@
// //
// A widget showing the user avatar and name // A widget showing the user avatar and name
const AccountsService = imports.gi.AccountsService; const AccountsService = imports.gi.AccountsService;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Lang = imports.lang; const Lang = imports.lang;
const St = imports.gi.St; const St = imports.gi.St;
const Params = imports.misc.params; const UserMenu = imports.ui.userMenu;
const AVATAR_ICON_SIZE = 64;
// Adapted from gdm/gui/user-switch-applet/applet.c
//
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
// Copyright (C) 2008,2009 Red Hat, Inc.
const Avatar = new Lang.Class({
Name: 'Avatar',
_init: function(user, params) {
this._user = user;
params = Params.parse(params, { reactive: false,
iconSize: AVATAR_ICON_SIZE,
styleClass: 'framed-user-icon' });
this._iconSize = params.iconSize;
this.actor = new St.Bin({ style_class: params.styleClass,
track_hover: params.reactive,
reactive: params.reactive,
width: this._iconSize,
height: this._iconSize });
},
setSensitive: function(sensitive) {
this.actor.can_focus = sensitive;
this.actor.reactive = sensitive;
},
update: function() {
let iconFile = this._user.get_icon_file();
if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS))
iconFile = null;
if (iconFile) {
let file = Gio.File.new_for_path(iconFile);
this.actor.child = null;
this.actor.style = 'background-image: url("%s");'.format(iconFile);
} else {
this.actor.style = null;
this.actor.child = new St.Icon({ icon_name: 'avatar-default-symbolic',
icon_size: this._iconSize });
}
}
});
const UserWidget = new Lang.Class({ const UserWidget = new Lang.Class({
Name: 'UserWidget', Name: 'UserWidget',
@ -64,9 +16,8 @@ const UserWidget = new Lang.Class({
this.actor = new St.BoxLayout({ style_class: 'user-widget', this.actor = new St.BoxLayout({ style_class: 'user-widget',
vertical: false }); vertical: false });
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this._avatar = new Avatar(user); this._avatar = new UserMenu.UserAvatarWidget(user);
this.actor.add(this._avatar.actor, this.actor.add(this._avatar.actor,
{ x_fill: true, y_fill: true }); { x_fill: true, y_fill: true });
@ -85,7 +36,7 @@ const UserWidget = new Lang.Class({
this._updateUser(); this._updateUser();
}, },
_onDestroy: function() { destroy: function() {
if (this._userLoadedId != 0) { if (this._userLoadedId != 0) {
this._user.disconnect(this._userLoadedId); this._user.disconnect(this._userLoadedId);
this._userLoadedId = 0; this._userLoadedId = 0;
@ -95,6 +46,8 @@ const UserWidget = new Lang.Class({
this._user.disconnect(this._userChangedId); this._user.disconnect(this._userChangedId);
this._userChangedId = 0; this._userChangedId = 0;
} }
this.actor.destroy();
}, },
_updateUser: function() { _updateUser: function() {

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