Compare commits
	
		
			2 Commits
		
	
	
		
			3.11.5
			...
			wip/gcampa
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 6d4ca1fcc8 | ||
|  | 93b1af401f | 
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -19,8 +19,6 @@ configure | ||||
| data/50-gnome-shell-*.xml | ||||
| data/gnome-shell.desktop | ||||
| 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.in | ||||
| data/gschemas.compiled | ||||
| @@ -43,8 +41,6 @@ docs/reference/*/xml/ | ||||
| docs/reference/shell/doc-gen-* | ||||
| gtk-doc.make | ||||
| js/misc/config.js | ||||
| js/js-resources.c | ||||
| js/js-resources.h | ||||
| intltool-extract.in | ||||
| intltool-merge.in | ||||
| intltool-update.in | ||||
| @@ -75,14 +71,13 @@ src/calendar-server/evolution-calendar.desktop.in | ||||
| src/calendar-server/org.gnome.Shell.CalendarServer.service | ||||
| src/gnome-shell | ||||
| src/gnome-shell-calendar-server | ||||
| src/gnome-shell-extension-prefs | ||||
| src/gnome-shell-extension-tool | ||||
| src/gnome-shell-extension-prefs | ||||
| src/gnome-shell-hotplug-sniffer | ||||
| src/gnome-shell-jhbuild | ||||
| src/gnome-shell-perf-helper | ||||
| src/gnome-shell-perf-tool | ||||
| src/gnome-shell-real | ||||
| src/gnome-shell-wayland | ||||
| src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service | ||||
| src/run-js-test | ||||
| src/test-recorder | ||||
|   | ||||
							
								
								
									
										41
									
								
								COPYING
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								COPYING
									
									
									
									
									
								
							| @@ -1,12 +1,12 @@ | ||||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|                        Version 2, June 1991 | ||||
| 		    GNU GENERAL PUBLIC LICENSE | ||||
| 		       Version 2, June 1991 | ||||
|  | ||||
|  Copyright (C) 1989, 1991 Free Software Foundation, Inc., | ||||
|  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  Copyright (C) 1989, 1991 Free Software Foundation, Inc. | ||||
|      59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
|  | ||||
|                             Preamble | ||||
| 			    Preamble | ||||
|  | ||||
|   The licenses for most software are designed to take away your | ||||
| freedom to share and change it.  By contrast, the GNU General Public | ||||
| @@ -15,7 +15,7 @@ software--to make sure the software is free for all its users.  This | ||||
| General Public License applies to most of the Free Software | ||||
| Foundation's software and to any other program whose authors commit to | ||||
| using it.  (Some other Free Software Foundation software is covered by | ||||
| the GNU Lesser General Public License instead.)  You can apply it to | ||||
| the GNU Library General Public License instead.)  You can apply it to | ||||
| your programs, too. | ||||
|  | ||||
|   When we speak of free software, we are referring to freedom, not | ||||
| @@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all. | ||||
|  | ||||
|   The precise terms and conditions for copying, distribution and | ||||
| modification follow. | ||||
|  | ||||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|  | ||||
| 		    GNU GENERAL PUBLIC LICENSE | ||||
|    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | ||||
|  | ||||
|   0. This License applies to any program or other work which contains | ||||
| @@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions: | ||||
|     License.  (Exception: if the Program itself is interactive but | ||||
|     does not normally print such an announcement, your work based on | ||||
|     the Program is not required to print an announcement.) | ||||
|  | ||||
|  | ||||
| These requirements apply to the modified work as a whole.  If | ||||
| identifiable sections of that work are not derived from the Program, | ||||
| and can be reasonably considered independent and separate works in | ||||
| @@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent | ||||
| access to copy the source code from the same place counts as | ||||
| distribution of the source code, even though third parties are not | ||||
| compelled to copy the source along with the object code. | ||||
|  | ||||
|  | ||||
|   4. You may not copy, modify, sublicense, or distribute the Program | ||||
| except as expressly provided under this License.  Any attempt | ||||
| otherwise to copy, modify, sublicense or distribute the Program is | ||||
| @@ -225,7 +225,7 @@ impose that choice. | ||||
|  | ||||
| This section is intended to make thoroughly clear what is believed to | ||||
| be a consequence of the rest of this License. | ||||
|  | ||||
|  | ||||
|   8. If the distribution and/or use of the Program is restricted in | ||||
| certain countries either by patents or by copyrighted interfaces, the | ||||
| original copyright holder who places the Program under this License | ||||
| @@ -255,7 +255,7 @@ make exceptions for this.  Our decision will be guided by the two goals | ||||
| of preserving the free status of all derivatives of our free software and | ||||
| of promoting the sharing and reuse of software generally. | ||||
|  | ||||
|                             NO WARRANTY | ||||
| 			    NO WARRANTY | ||||
|  | ||||
|   11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | ||||
| FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN | ||||
| @@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | ||||
| PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | ||||
| POSSIBILITY OF SUCH DAMAGES. | ||||
|  | ||||
|                      END OF TERMS AND CONDITIONS | ||||
|  | ||||
|             How to Apply These Terms to Your New Programs | ||||
| 		     END OF TERMS AND CONDITIONS | ||||
|  | ||||
| 	    How to Apply These Terms to Your New Programs | ||||
|  | ||||
|   If you develop a new program, and you want it to be of the greatest | ||||
| possible use to the public, the best way to achieve this is to make it | ||||
| @@ -303,16 +303,17 @@ the "copyright" line and a pointer to where the full notice is found. | ||||
|     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., | ||||
|     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|     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 | ||||
|  | ||||
|  | ||||
| Also add information on how to contact you by electronic and paper mail. | ||||
|  | ||||
| If the program is interactive, make it output a short notice like this | ||||
| when it starts in an interactive mode: | ||||
|  | ||||
|     Gnomovision version 69, Copyright (C) year name of author | ||||
|     Gnomovision version 69, Copyright (C) year  name of author | ||||
|     Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||||
|     This is free software, and you are welcome to redistribute it | ||||
|     under certain conditions; type `show c' for details. | ||||
| @@ -335,5 +336,5 @@ necessary.  Here is a sample; alter the names: | ||||
| This General Public License does not permit incorporating your program into | ||||
| proprietary programs.  If your program is a subroutine library, you may | ||||
| consider it more useful to permit linking proprietary applications with the | ||||
| library.  If this is what you want to do, use the GNU Lesser General | ||||
| library.  If this is what you want to do, use the GNU Library General | ||||
| Public License instead of this License. | ||||
|   | ||||
							
								
								
									
										4
									
								
								HACKING
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								HACKING
									
									
									
									
									
								
							| @@ -138,8 +138,8 @@ GObjects, although this feature isn't used very often in the Shell itself. | ||||
|  | ||||
|         _init: function(icon, label) { | ||||
|             this.parent({ reactive: false }); | ||||
|             this.actor.add_child(icon); | ||||
|             this.actor.add_child(label); | ||||
|             this.addActor(icon); | ||||
|             this.addActor(label); | ||||
|         }, | ||||
|  | ||||
|         open: function() { | ||||
|   | ||||
| @@ -1,11 +1,7 @@ | ||||
| # Point to our macro directory and pick up user flags from the environment | ||||
| ACLOCAL_AMFLAGS  = -I m4 ${ACLOCAL_FLAGS} | ||||
|  | ||||
| SUBDIRS = data js src  tests po docs | ||||
|  | ||||
| if BUILD_BROWSER_PLUGIN | ||||
| SUBDIRS += browser-plugin | ||||
| endif | ||||
| SUBDIRS = data js src browser-plugin tests po docs | ||||
|  | ||||
| if ENABLE_MAN | ||||
| SUBDIRS += man | ||||
|   | ||||
							
								
								
									
										638
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										638
									
								
								NEWS
									
									
									
									
									
								
							| @@ -1,641 +1,3 @@ | ||||
| 3.11.5 | ||||
| ====== | ||||
| * Fix extension preference tool [Florian; #722334] | ||||
| * Fix keyboard activation of legacy tray icons [Giovanni; #721267] | ||||
| * Add radial background shade for modal dialogs [Giovanni; #669798] | ||||
| * Show attached modal windows in the overview [Giovanni; #650843] | ||||
| * Add support for desktop actions [Giovanni; #669603] | ||||
| * Indicate in system status when location service is used [Zeeshan; #709372] | ||||
| * Add support for extended app folder schema [Jasper; #723179] | ||||
| * Show status icon for wired network connections [Jasper; #708966] | ||||
| * Indicate airplane mode in network selection dialog [Giovanni; #709128] | ||||
| * Misc bug fixes and cleanups [Florian, Sebastian, Giovanni, Tim, Matt, Jasper; | ||||
|   #722417, #722494, #722547, #722593, #722434, #722787, #722690, #722840, | ||||
|   #722660, #722812, #723197, #722927, #723306, #723308, #723523, #709685, | ||||
|   #723570] | ||||
|  | ||||
| Contributors: | ||||
|   Zeeshan Ali (Khattak), Magdalen Berns, Giovanni Campagna, William Jon McCann, | ||||
|   Sebastian Keller, Tim Lunn, Florian Müllner, Carlos Soriano, | ||||
|   Jasper St. Pierre, Rico Tzschichholz, Matt Watson | ||||
|  | ||||
| Translations: | ||||
|   Marek Černocký [cs], Mattias Põldaru [et], Tong Hui [zh_CN], | ||||
|   Victor Ibragimov [tg], Enrico Nicoletto [pt_BR], Daniel Mustieles [es], | ||||
|   Fran Diéguez [gl], Kjartan Maraas [nb], Nilamdyuti Goswami [as], | ||||
|   Aurimas Černius [lt], Stas Solovey [ru], Yosef Or Boczko [he], | ||||
|   Jorge Pérez Pérez [an], Dimitris Spingos [el], Baurzhan Muftakhidinov [kk], | ||||
|   Chao-Hsiung Liao [zh_HK, zh_TW], Shankar Prasad [kn], Yaron Shahrabani [he], | ||||
|   Andika Triwidada [id] | ||||
|  | ||||
| 3.11.4 | ||||
| ====== | ||||
| * Fix removal of workspacaes that are not at the end [Giovanni; #721417] | ||||
| * Allow session mode to be specified in the environment [Ray; #720894] | ||||
| * Special-case launching of terminals [Debarshi; #695010] | ||||
| * Always show arrow if app switcher is scrollable [Jonh; #711467] | ||||
| * Implement new app folders system [Jasper; #722117] | ||||
| * Remove arrow from background menu [Tarun; #699608] | ||||
| * Misc bug fixes and cleanups [Giovanni, Andika, Florian, Ray; #721039, | ||||
|   #721439, #721507, #721629, #721868, #722210] | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Piotr Drąg, Tarun Kumar Joshi, Florian Müllner, | ||||
|   Debarshi Ray, Jasper St. Pierre, Ray Strode, Andika Triwidada, Jonh Wendell | ||||
|  | ||||
| Translations: | ||||
|   Dušan Kazik [sk], Tong Hui [zh_CN], Benjamin Steinwender [de], | ||||
|   Matej Urbančič [sl], Jorge Pérez Pérez [an], Kjartan Maraas [nb], | ||||
|   Milo Casagrande [it], Rafael Ferreira [pt_BR], Marek Černocký [cs], | ||||
|   Daniel Mustieles [es], Adorilson Bezerra [pt_BR], Christian Kirbach [de], | ||||
|   Aurimas Černius [lt], Andika Triwidada [id], Baurzhan Muftakhidinov [kk], | ||||
|   Victor Ibragimov [tg], Yosef Or Boczko [he], Dimitris Spingos [el], | ||||
|   Fran Diéguez [gl] | ||||
|  | ||||
| 3.11.3 | ||||
| ====== | ||||
| * Fix fade effect of desktop icons [Florian; #707671] | ||||
| * Fix issues with background management code [Jasper; #709313] | ||||
| * Use new Glib facilities for application search [Jasper; #711631] | ||||
| * Add focus indication to session menu button [Sebastien; #710539] | ||||
| * Fix hover tracking for StEntries [Jasper; #706749] | ||||
| * Fix reentrancy issue in message tray [Jasper; #711694] | ||||
| * Tone down zoom animation on login/unlock [Jasper; #712362] | ||||
| * Allow specifying monitor for OSD [Carlos; #712664] | ||||
| * Fix resetting prompt on user switch [Ray; #710456] | ||||
| * Stop using gnome-bluetooth-applet [Bastien; #719341] | ||||
| * Add support for EAP-FAST password requests [Dan; #719813] | ||||
| * Fix entry focus of chat notifications [Jasper; #709853] | ||||
| * Make window previews keyboard navigatable [Jasper; #644306] | ||||
| * Fix app switcher order with dialog windows [Florian; #719824] | ||||
| * Allow remote search providers without icons [Debarshi; #719965] | ||||
| * Fix various alignment issues in RTL locales [Yosef; #712638, #712596, | ||||
|   #712594, #712600, #712579] | ||||
| * Misc. bug fixes and cleanups [Jasper, Florian, Giovanni, Dan; #712727, | ||||
|   #712753, #719378, #719730, #719803, #710115, #720017, #719815, #719567, | ||||
|   #720298] | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Carlos Garnacho, Sebastien Lafargue, Tim Lunn, | ||||
|   Florian Müllner, Bastien Nocera, Yosef Or Boczko, Debarshi Ray, | ||||
|   Jasper St. Pierre, Ray Strode, Dan Williams | ||||
|  | ||||
| Translations: | ||||
|   Kjartan Maraas [nb], Reinout van Schouwen [nl], Rafael Ferreira [pt_BR], | ||||
|   Mattias Põldaru [et], Emin Tufan Çetin [tr], Jiri Grönroos [fi], | ||||
|   Khaled Hosny [ar], Fran Diéguez [gl], Victor Ibragimov [tg], | ||||
|   Daniel Mustieles [es] | ||||
|  | ||||
| 3.11.2 | ||||
| ====== | ||||
| * Cache search result display actors [Jasper; #704912] | ||||
| * Use username in userWidget if real name doesn't fit [Jasper; #706851] | ||||
| * Support shell_global_reexec_self() on OpenBSD [Antoine; #709571] | ||||
| * Support disabling browser plugin [Colin; #711218] | ||||
| * Restore support for 'disable-restart-buttons' [Florian; #711244] | ||||
| * Validate parameters of exposed DBus methods [Florian; #699752] | ||||
| * Connect applications to systemd journal if available [Colin; #711626] | ||||
| * Misc bug fixes and cleanups [Florian, Jasper; #711205, #698486, #711416, | ||||
|   #644306, #711555, #709806, #711631, #711732] | ||||
|  | ||||
| Contributors: | ||||
|   Cosimo Cecchi, Antoine Jacoutot, Florian Müllner, Jasper St. Pierre, | ||||
|   Rico Tzschichholz, Colin Walters | ||||
|  | ||||
| Translations: | ||||
|   Yuri Myasoedov [ru], Kjartan Maraas [nb], Efstathios Iosifidis [el], | ||||
|   Benjamin Steinwender [de], eternalhui [zh_CN], Shantha kumar [ta] | ||||
|  | ||||
| 3.11.1 | ||||
| ====== | ||||
| * power: Use UPower directly instead of gnome-settings-daemon [Bastien; #710273] | ||||
| * Implement support for new GTK+ notification API [Jasper, Giovanni, Florian; | ||||
|   #710137, #710596] | ||||
| * gdm: Don't allow user-list to fill up the entire screen [Florian; #710555] | ||||
| * Don't autostart remote search providers at login [Giovanni; #708830] | ||||
| * Fix spacing in end-session dialog [Sebastien; #710543] | ||||
| * Prepare for js24 [Tim; #711052] | ||||
| * Misc bug fixes and cleanups [Jasper, Florian, Adel, Tim, Sebastien; #710347, | ||||
|   #710144, #710541, #691409, #710745, #688331, #704912] | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Adel Gadllah, Sebastien Lafargue, Tim Lunn, | ||||
|   Florian Müllner, Bastien Nocera, Jasper St. Pierre, Rico Tzschichholz | ||||
|  | ||||
| Translations: | ||||
|   Stas Solovey [ru], Yosef Or Boczko [he], Rafael Ferreira [pt_BR] | ||||
|  | ||||
| 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 | ||||
| ======= | ||||
| * Background bug fixes [Ray; #696712] | ||||
|  | ||||
| 3.8.0 | ||||
| ===== | ||||
| * Remove blur and desaturation from lock screen [Jasper; #696322] | ||||
| * Remove scroll view fade near edges [Adel; #696404] | ||||
| * dateMenu: Open calendar component when using Evolution [Florian; #696432] | ||||
| * Fix unlocking on fast user switch [Cosimo; #696287] | ||||
| * Tweak screen shield animation [Rui; #696380] | ||||
| * Fix major memory leak when changing backgrounds [Ray; #696157] | ||||
| * Miscellaneous bug fixes [Jasper, Adel, Florian; #696199, #696212, #696422, | ||||
|   #696447, #696235] | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Cosimo Cecchi, Adel Gadllah, Rui Matos, Florian Müllner, | ||||
|   Jasper St. Pierre, Ray Strode | ||||
|  | ||||
| Translations: | ||||
|   Alexandre Franke [fr], Victor Ibragimov [tg], Arash Mousavi [fa], | ||||
|   Gabor Kelemen [hu], Sandeep Sheshrao Shedmake [mr], ManojKumar Giri [or], | ||||
|   Shantha kumar [ta], Rajesh Ranjan [hi], Stas Solovey [ru], | ||||
|   Shankar Prasad [kn], Dušan Kazik [sk], Ihar Hrachyshka [be], | ||||
|   Wouter Bolsterlee [nl], Kris Thomsen [da], Jiro Matsuzawa [ja], | ||||
|   Daniel Korostil [uk], Ani Peter [ml], Krishnababu Krothapalli [te], | ||||
|   Mantas Kriaučiūnas [lt], Praveen Illa [te] | ||||
|  | ||||
| 3.7.92 | ||||
| ====== | ||||
| * Drop fallback lock implementation [Florian; #693403] | ||||
| * Don't let the user trigger message-tray when in fullscreen [Jasper; #694997] | ||||
| * Scroll search results when using keynav [Jasper; #689681] | ||||
| * Allow raising the shield by starting to type the password [Jasper; #686740] | ||||
| * Improve tracking of fullscreen windows [Owen; #649748] | ||||
| * Improve animation of new windows in overview [Giovanni; #695582] | ||||
| * workspace switcher: Animate new workspaces created by DND [Giovanni; #685285] | ||||
| * Give user time to read messages on login screen [Ray; #694688] | ||||
| * Misc bug fixes and cleanups [Jasper, Ray, Florian, Cosimo, Giovanni, Adel, | ||||
|   Stef, Takao, Rui, Neil; #695154, #694993, #695272, #691578, #694321, #695338, | ||||
|   #695409, #695458, #695526, #695601, #695471, #695324, #695650, #695656, | ||||
|   #695659, #695485, #695395, #694951, #695824, #695841, #695801, #694321, | ||||
|   #693708, #695800, #695607, #695882, #691578, #685851, #694371, #690857, | ||||
|   #694092, #695747, #696007, #693438, #696064, #696102 | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Cosimo Cecchi, Allan Day, Takao Fujiwara, Adel Gadllah, | ||||
|   Tim Lunn, Rui Matos, Florian Müllner, Neil Roberts, Jasper St. Pierre, | ||||
|   Ray Strode, Stef Walter, Colin Walters, Owen W. Taylor | ||||
|  | ||||
| Translations: | ||||
|   Nilamdyuti Goswami [as], Chao-Hsiung Liao [zh_HK, zh_TW], | ||||
|   Yuri Myasoedov [ru], Gheyret Kenji [ug], Baurzhan Muftakhidinov [kk], | ||||
|   Ville-Pekka Vainio [fi], Matej Urbančič [sl], | ||||
|   Мирослав Николић [sr, sr@latin], Rūdolfs Mazurs [lv], Christian Kirbach [de], | ||||
|   Andika Triwidada [id], Gil Forcada [ca], Mattias Põldaru [et], | ||||
|   Duarte Loreto [pt], Adam Matoušek [cs], Changwoo Ryu [ko], | ||||
|   Ihar Hrachyshka [be], Carles Ferrando [ca@valencia], Sweta Kothari [gu] | ||||
|  | ||||
| 3.7.91 | ||||
| ====== | ||||
| * overview: Fade out controls during DND that are not targets [Cosimo; #686984] | ||||
| * overview: Keep open when a Control key is held [Florian; #686984] | ||||
| * Improve login screen => session transition [Ray; #694321] | ||||
| * Center application grid horizontally [Florian; #694261] | ||||
| * Fix hiding panel when fullscreen windows span multiple monitors [Adel; 646861] | ||||
| * Tweak thresholds of pressure barrier [Jasper; #694467] | ||||
| * Tweak window picker layout [Jasper; #694902] | ||||
| * Expose key grab DBus API to gnome-settings-daemon [Florian; #643111] | ||||
| * Don't always show message tray in overview, add message indicator | ||||
|   [Cosimo; #687787] | ||||
| * Tweak startup animation [Ray; #694326] | ||||
| * Add OSD popups and expose them to gnome-settings-daemon [Florian; #613543] | ||||
| * Move loupe icon to the start of the search entry [Jasper; #695069] | ||||
| * Don't show the input switcher with less than 2 items [Rui; 695000] | ||||
| * Fix auto-completion of system modals immediately upon display [Stef; #692937] | ||||
| * Ignore workspaces in alt-tab [Florian; #661156] | ||||
| * Disable copying text from password entries [Florian; #695104] | ||||
| * Use standard styling for ibus candidate popups [Allan; #694796] | ||||
| * Fix calendar changing height on month changes [Giovanni; #641383] | ||||
| * Port the hot corner to use pressure barriers [Jasper; #663661] | ||||
| * Misc bug fixes and cleanups: [Hashem, Giovanni, Alban, Jasper, Cosimo, | ||||
|   Florian, Adel, Daniel, Matthias, Ray, Rui, Guillaume, Stef; #685849, #690643, | ||||
|   #694292, #693814, #694234, #694365, #694287, #694336, #694256, #694261, | ||||
|   #663601, #694441, #694284, #694463, #694475, #687248, #694394, #694320, | ||||
|   #694701, #694784, #694858, #694906, #694327, #694876, #694905, #694969, | ||||
|   #694970, #694988, #695006, #695001, #694998, #695023, #695002, #695073, | ||||
|   #695126, #687748, #694837, #693907, #679851, #694988] | ||||
|  | ||||
| Contributors: | ||||
|   Giovanni Campagna, Cosimo Cecchi, Matthias Clasen, Alban Crequy, Allan Day, | ||||
|   Guillaume Desmottes, Adel Gadllah, Rui Matos, Daniel Mustieles, | ||||
|   Hashem Nasarat, Jasper St. Pierre, Ray Strode, Stef Walter | ||||
|  | ||||
| Translations: | ||||
|   Yuri Myasoedov [ru], Adam Matoušek [cs], Piotr Drąg [pl], Matej Urbančič [sl], | ||||
|   Sweta Kothari [gu], Kjartan Maraas [nb], Nguyễn Thái Ngọc Duy [vi], | ||||
|   Chao-Hsiung Liao [zh_HK, zh_TW], Dimitris Spingos [el], | ||||
|   Inaki Larranaga Murgoitio [eu], Luca Ferretti [it], A S Alam [pa], | ||||
|   Gheyret Kenji [ug], Stas Solovey [ru], Enrico Nicoletto [pt_BR], | ||||
|   Fran Diéguez [gl], Daniel Mustieles [es], Aurimas Černius [lt] | ||||
|  | ||||
| 3.7.90 | ||||
| ====== | ||||
| * Let GNOME Shell work on EGL and GLES2 [Neil; #693225, #693438, #693339] | ||||
|   | ||||
| @@ -17,4 +17,5 @@ libgnome_shell_browser_plugin_la_SOURCES = 	\ | ||||
|  | ||||
| libgnome_shell_browser_plugin_la_CFLAGS = 	\ | ||||
| 	$(BROWSER_PLUGIN_CFLAGS)		\ | ||||
| 	-DG_DISABLE_DEPRECATED			\ | ||||
| 	-DG_LOG_DOMAIN=\"GnomeShellBrowserPlugin\" | ||||
|   | ||||
| @@ -13,7 +13,9 @@ | ||||
|  * General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  * | ||||
|  * Authors: | ||||
|  *      Jasper St. Pierre <jstpierre@mecheye.net> | ||||
|   | ||||
							
								
								
									
										122
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								configure.ac
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| AC_PREREQ(2.63) | ||||
| AC_INIT([gnome-shell],[3.11.5],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell]) | ||||
| AC_INIT([gnome-shell],[3.7.90],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell]) | ||||
|  | ||||
| AC_CONFIG_HEADERS([config.h]) | ||||
| AC_CONFIG_SRCDIR([src/shell-global.c]) | ||||
| @@ -16,7 +16,6 @@ m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) | ||||
|  | ||||
| # Checks for programs. | ||||
| AC_PROG_CC | ||||
| AC_PROG_CXX | ||||
|  | ||||
| # Initialize libtool | ||||
| LT_PREREQ([2.2.6]) | ||||
| @@ -25,6 +24,9 @@ LT_INIT([disable-static]) | ||||
| # i18n | ||||
| IT_PROG_INTLTOOL([0.40]) | ||||
|  | ||||
| AM_GNU_GETTEXT([external]) | ||||
| AM_GNU_GETTEXT_VERSION([0.17]) | ||||
|  | ||||
| GETTEXT_PACKAGE=gnome-shell | ||||
| AC_SUBST(GETTEXT_PACKAGE) | ||||
| AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", | ||||
| @@ -51,100 +53,74 @@ if $PKG_CONFIG --exists gstreamer-1.0 '>=' $GSTREAMER_MIN_VERSION ; then | ||||
|    AC_MSG_RESULT(yes) | ||||
|    build_recorder=true | ||||
|    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 | ||||
|    AC_MSG_RESULT(no) | ||||
| fi | ||||
|  | ||||
| AM_CONDITIONAL(BUILD_RECORDER, $build_recorder) | ||||
|  | ||||
| AC_ARG_ENABLE([systemd], | ||||
|               AS_HELP_STRING([--enable-systemd], [Use systemd]), | ||||
|               [enable_systemd=$enableval], | ||||
|               [enable_systemd=auto]) | ||||
| AS_IF([test x$enable_systemd != xno], [ | ||||
|   AC_MSG_CHECKING([for libsystemd-journal]) | ||||
|   PKG_CHECK_EXISTS([libsystemd-journal], | ||||
|                    [have_systemd=yes | ||||
|                     AC_DEFINE([HAVE_SYSTEMD], [1], [Define if we have systemd])], | ||||
|                    [have_systemd=no]) | ||||
|   AC_MSG_RESULT($have_systemd) | ||||
| ]) | ||||
|  | ||||
| AC_MSG_RESULT($enable_systemd) | ||||
|  | ||||
| CLUTTER_MIN_VERSION=1.15.90 | ||||
| CLUTTER_MIN_VERSION=1.13.4 | ||||
| GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1 | ||||
| GJS_MIN_VERSION=1.39.0 | ||||
| MUTTER_MIN_VERSION=3.11.5 | ||||
| GJS_MIN_VERSION=1.35.4 | ||||
| MUTTER_MIN_VERSION=3.7.90 | ||||
| GTK_MIN_VERSION=3.7.9 | ||||
| GIO_MIN_VERSION=2.37.0 | ||||
| GIO_MIN_VERSION=2.35.0 | ||||
| LIBECAL_MIN_VERSION=3.5.3 | ||||
| LIBEDATASERVER_MIN_VERSION=3.5.3 | ||||
| TELEPATHY_GLIB_MIN_VERSION=0.17.5 | ||||
| POLKIT_MIN_VERSION=0.100 | ||||
| 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 | ||||
| NETWORKMANAGER_MIN_VERSION=0.9.8 | ||||
| GNOME_MENUS_REQUIRED_VERSION=3.5.3 | ||||
| NETWORKMANAGER_MIN_VERSION=0.9.6 | ||||
| PULSE_MIN_VERS=2.0 | ||||
|  | ||||
| # Collect more than 20 libraries for a prize! | ||||
| SHARED_PCS="gio-unix-2.0 >= $GIO_MIN_VERSION | ||||
|             libxml-2.0 | ||||
|             gtk+-3.0 >= $GTK_MIN_VERSION | ||||
|             atk-bridge-2.0 | ||||
|             gjs-internals-1.0 >= $GJS_MIN_VERSION | ||||
|             $recorder_modules | ||||
|             gdk-x11-3.0 libsoup-2.4 | ||||
|             xtst | ||||
|             clutter-x11-1.0 >= $CLUTTER_MIN_VERSION | ||||
|             clutter-glx-1.0 >= $CLUTTER_MIN_VERSION | ||||
|             libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION | ||||
|             gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION | ||||
|             libcanberra libcanberra-gtk3 | ||||
|             telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION | ||||
|             polkit-agent-1 >= $POLKIT_MIN_VERSION | ||||
|             libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION | ||||
|             libnm-gtk >= $NETWORKMANAGER_MIN_VERSION | ||||
|             libsecret-unstable gcr-base-3 >= $GCR_MIN_VERSION" | ||||
| if test x$have_systemd = xyes; then | ||||
|   SHARED_PCS="${SHARED_PCS} libsystemd-journal" | ||||
| fi | ||||
|  | ||||
| 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, gio-unix-2.0 >= $GIO_MIN_VERSION | ||||
| 			       libxml-2.0 | ||||
|                                gtk+-3.0 >= $GTK_MIN_VERSION | ||||
|                                atk-bridge-2.0 | ||||
|                                libmutter >= $MUTTER_MIN_VERSION | ||||
|                                gjs-internals-1.0 >= $GJS_MIN_VERSION | ||||
| 			       libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION | ||||
|                                $recorder_modules | ||||
|                                gdk-x11-3.0 libsoup-2.4 | ||||
| 			       clutter-x11-1.0 >= $CLUTTER_MIN_VERSION | ||||
| 			       clutter-glx-1.0 >= $CLUTTER_MIN_VERSION | ||||
|                                libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION | ||||
|                                gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION | ||||
| 			       libcanberra libcanberra-gtk3 | ||||
|                                telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION | ||||
|                                polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes | ||||
|                                libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION | ||||
|                                libnm-gtk >= $NETWORKMANAGER_MIN_VERSION | ||||
|                                gnome-keyring-1 gcr-3 >= $GCR_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(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0) | ||||
| PKG_CHECK_MODULES(SHELL_HOTPLUG_SNIFFER, gio-2.0 gdk-pixbuf-2.0) | ||||
| PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2) | ||||
| PKG_CHECK_MODULES(TRAY, gtk+-3.0) | ||||
| PKG_CHECK_MODULES(GVC, libpulse >= $PULSE_MIN_VERS libpulse-mainloop-glib gobject-2.0) | ||||
| PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.7.4) | ||||
| PKG_CHECK_MODULES(CARIBOU, caribou-1.0 >= 0.4.8) | ||||
|  | ||||
| AC_ARG_ENABLE(browser-plugin, | ||||
|               [AS_HELP_STRING([--enable-browser-plugin], | ||||
|                               [Enable browser plugin [default=yes]])],, | ||||
|               enable_browser_plugin=yes) | ||||
| AS_IF([test x$enable_browser_plugin = xyes], [ | ||||
|   PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2) | ||||
| ]) | ||||
| AM_CONDITIONAL(BUILD_BROWSER_PLUGIN, test x$enable_browser_plugin = xyes) | ||||
|  | ||||
| PKG_CHECK_MODULES(BLUETOOTH, gnome-bluetooth-1.0 >= 3.9.0, | ||||
|         [AC_DEFINE([HAVE_BLUETOOTH],[1],[Define if you have libgnome-bluetooth-applet]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[1])], | ||||
| AC_MSG_CHECKING([for bluetooth support]) | ||||
| PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0], | ||||
|         [BLUETOOTH_DIR=`$PKG_CONFIG --variable=applet_libdir gnome-bluetooth-1.0` | ||||
| 	 BLUETOOTH_LIBS=`$PKG_CONFIG --variable=applet_libs gnome-bluetooth-1.0` | ||||
| 	 AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"]) | ||||
| 	 AC_SUBST([BLUETOOTH_DIR],["$BLUETOOTH_DIR"]) | ||||
| 	 AC_DEFINE_UNQUOTED([BLUETOOTH_DIR],["$BLUETOOTH_DIR"],[Path to installed GnomeBluetooth typelib and library]) | ||||
| 	 AC_DEFINE([HAVE_BLUETOOTH],[1],[Define if you have libgnome-bluetooth-applet]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[1]) | ||||
| 	 AC_MSG_RESULT([yes])], | ||||
| 	[AC_DEFINE([HAVE_BLUETOOTH],[0]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[0])]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[0]) | ||||
| 	 AC_MSG_RESULT([no])]) | ||||
|  | ||||
| PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION gio-2.0) | ||||
| AC_SUBST(CALENDAR_SERVER_CFLAGS) | ||||
| @@ -156,17 +132,13 @@ AC_SUBST([GNOME_KEYBINDINGS_KEYSDIR]) | ||||
| GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION]) | ||||
|  | ||||
| MUTTER_GIR_DIR=`$PKG_CONFIG --variable=girdir libmutter` | ||||
| AC_SUBST(MUTTER_GIR_DIR) | ||||
|  | ||||
| MUTTER_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter` | ||||
| AC_SUBST(MUTTER_GIR_DIR) | ||||
| AC_SUBST(MUTTER_TYPELIB_DIR) | ||||
|  | ||||
| GJS_CONSOLE=`$PKG_CONFIG --variable=gjs_console gjs-1.0` | ||||
| AC_SUBST(GJS_CONSOLE) | ||||
|  | ||||
| GLIB_COMPILE_RESOURCES=`$PKG_CONFIG --variable glib_compile_resources gio-2.0` | ||||
| AC_SUBST(GLIB_COMPILE_RESOURCES) | ||||
|  | ||||
| AC_CHECK_FUNCS(fdwalk) | ||||
| AC_CHECK_FUNCS(mallinfo) | ||||
| AC_CHECK_HEADERS([sys/resource.h]) | ||||
| @@ -201,6 +173,10 @@ AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no) | ||||
|  | ||||
| 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"}" | ||||
| AC_ARG_VAR([BROWSER_PLUGIN_DIR],[Where to install the plugin to]) | ||||
|  | ||||
|   | ||||
							
								
								
									
										12
									
								
								data/50-gnome-shell-screenshot.xml.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								data/50-gnome-shell-screenshot.xml.in
									
									
									
									
									
										Normal 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> | ||||
|  | ||||
| @@ -11,9 +11,6 @@ | ||||
| 	<KeyListEntry name="focus-active-notification" | ||||
|                       _description="Focus the active notification"/> | ||||
|  | ||||
| 	<KeyListEntry name="toggle-overview" | ||||
|                       _description="Show the overview"/> | ||||
|  | ||||
| 	<KeyListEntry name="toggle-application-view" | ||||
|                       _description="Show all applications"/> | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,5 @@ | ||||
| desktopdir=$(datadir)/applications | ||||
| 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 | ||||
| # file when built in a non-system prefix | ||||
| @@ -16,7 +12,6 @@ endif HAVE_MUTTER_WAYLAND | ||||
|  | ||||
| introspectiondir = $(datadir)/dbus-1/interfaces | ||||
| introspection_DATA =				\ | ||||
| 	org.gnome.Shell.Screencast.xml		\ | ||||
| 	org.gnome.Shell.Screenshot.xml		\ | ||||
| 	org.gnome.ShellSearchProvider.xml	\ | ||||
| 	org.gnome.ShellSearchProvider2.xml | ||||
| @@ -42,10 +37,6 @@ dist_theme_DATA =				\ | ||||
| 	theme/message-tray-background.png	\ | ||||
| 	theme/more-results.svg			\ | ||||
| 	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-highlight-narrow.svg	\ | ||||
| 	theme/panel-button-highlight-wide.svg	\ | ||||
| @@ -61,7 +52,10 @@ dist_theme_DATA =				\ | ||||
| 	theme/ws-switch-arrow-down.png | ||||
|  | ||||
| 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) | ||||
|  | ||||
| gsettings_SCHEMAS = org.gnome.shell.gschema.xml | ||||
| @@ -86,7 +80,6 @@ convert_DATA = gnome-shell-overrides.convert | ||||
|  | ||||
| EXTRA_DIST =						\ | ||||
| 	gnome-shell.desktop.in.in			\ | ||||
| 	gnome-shell-wayland.desktop.in.in		\ | ||||
| 	gnome-shell-extension-prefs.desktop.in.in	\ | ||||
| 	$(introspection_DATA)				\ | ||||
| 	$(menu_DATA)					\ | ||||
| @@ -96,7 +89,6 @@ EXTRA_DIST =						\ | ||||
|  | ||||
| CLEANFILES =						\ | ||||
| 	gnome-shell.desktop.in				\ | ||||
| 	gnome-shell-wayland.desktop.in			\ | ||||
| 	gnome-shell-extension-prefs.in			\ | ||||
| 	$(desktop_DATA)					\ | ||||
| 	$(keys_DATA)					\ | ||||
|   | ||||
| @@ -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 | ||||
| @@ -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> | ||||
| @@ -46,7 +46,7 @@ | ||||
|     <!-- | ||||
|         GetResultMetas: | ||||
|         @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 | ||||
|     --> | ||||
|   | ||||
| @@ -13,12 +13,22 @@ | ||||
|     </key> | ||||
|     <key name="enabled-extensions" type="as"> | ||||
|       <default>[]</default> | ||||
|       <_summary>UUIDs of extensions to enable</_summary> | ||||
|       <_summary>Uuids of extensions to enable</_summary> | ||||
|       <_description> | ||||
|         GNOME Shell extensions have a UUID property; this key lists extensions | ||||
|         GNOME Shell extensions have a uuid property; this key lists extensions | ||||
|         which should be loaded. Any extension that wants to be loaded needs | ||||
|         to be in this list. You can also manipulate this list with the | ||||
|         EnableExtension and DisableExtension D-Bus methods on org.gnome.Shell. | ||||
|         EnableExtension and DisableExtension DBus methods on org.gnome.Shell. | ||||
|       </_description> | ||||
|     </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"> | ||||
| @@ -29,12 +39,13 @@ | ||||
|         will be displayed in the favorites area. | ||||
|       </_description> | ||||
|     </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 name="app-folder-categories" type="as"> | ||||
|       <default>[ 'Utilities', 'Sundry' ]</default> | ||||
|       <_summary>List of categories that should be displayed as folders</_summary> | ||||
|       <_description> | ||||
|         Each category name in this list will be represented as folder in the | ||||
|         application view, rather than being displayed inline in the main view. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="command-history" type="as"> | ||||
|       <default>[]</default> | ||||
| @@ -44,12 +55,22 @@ | ||||
|       <default>[]</default> | ||||
|       <_summary>History for the looking glass dialog</_summary> | ||||
|     </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"> | ||||
|       <default>false</default> | ||||
|       <_summary>Always show the 'Log out' menu item in the user menu.</_summary> | ||||
|       <_summary>Always show the 'Log out' menuitem in the user menu.</_summary> | ||||
|       <_description> | ||||
|         This key overrides the automatic hiding of the 'Log out' | ||||
|         menu item in single-user, single-session situations. | ||||
|         menuitem in single-user, single-session situations. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="remember-mount-password" type="b"> | ||||
| @@ -63,6 +84,7 @@ | ||||
|       </_description> | ||||
|     </key> | ||||
|     <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="keyboard" schema="org.gnome.shell.keyboard"/> | ||||
|   </schema> | ||||
| @@ -95,13 +117,6 @@ | ||||
|         Overview. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="toggle-overview" type="as"> | ||||
|       <default>["<Super>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"> | ||||
|       <default>["<Super>m"]</default> | ||||
|       <_summary>Keybinding to toggle the visibility of the message tray</_summary> | ||||
| @@ -116,10 +131,12 @@ | ||||
|         Keybinding to focus the active notification. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="pause-resume-tweens" type="as"> | ||||
|       <default>[]</default> | ||||
|       <summary>Keybinding that pauses and resumes all running tweens, for debugging purposes</summary> | ||||
|       <description></description> | ||||
|     <key 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> | ||||
|  | ||||
| @@ -134,16 +151,41 @@ | ||||
|     </key> | ||||
|   </schema> | ||||
|  | ||||
|   <schema id="org.gnome.shell.app-switcher" | ||||
|           path="/org/gnome/shell/app-switcher/" | ||||
|   <schema id="org.gnome.shell.recorder" path="/org/gnome/shell/recorder/" | ||||
|           gettext-domain="@GETTEXT_PACKAGE@"> | ||||
|     <key type="b" name="current-workspace-only"> | ||||
|       <default>false</default> | ||||
|       <summary>Limit switcher to current workspace.</summary> | ||||
|       <description> | ||||
| 	If true, only applications that have windows on the current workspace are shown in the switcher. | ||||
| 	Otherwise, all applications are included. | ||||
|       </description> | ||||
|     <key name="framerate" type="i"> | ||||
|       <default>30</default> | ||||
|       <_summary>Framerate used for recording screencasts.</_summary> | ||||
|       <_description> | ||||
|         The framerate of the resulting screencast recordered | ||||
|         by GNOME Shell's screencast recorder in frames-per-second. | ||||
|       </_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> | ||||
|   </schema> | ||||
|  | ||||
| @@ -165,7 +207,7 @@ | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key type="b" name="current-workspace-only"> | ||||
|       <default>true</default> | ||||
|       <default>false</default> | ||||
|       <summary>Limit switcher to current workspace.</summary> | ||||
|       <description> | ||||
| 	If true, only windows from the current workspace are shown in the switcher. | ||||
| @@ -220,10 +262,10 @@ | ||||
|  | ||||
|     <key name="focus-change-on-pointer-rest" type="b"> | ||||
|       <default>true</default> | ||||
|       <_summary>Delay focus changes in mouse mode until the pointer stops moving</_summary> | ||||
|       <_description> | ||||
|       <summary>Delay focus changes in mouse mode until the pointer stops moving</summary> | ||||
|       <description> | ||||
|         This key overrides the key in org.gnome.mutter when running GNOME Shell. | ||||
|       </_description> | ||||
|       </description> | ||||
|     </key> | ||||
|   </schema> | ||||
| </schemalist> | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -14,7 +14,7 @@ | ||||
|    height="16" | ||||
|    id="svg12430" | ||||
|    version="1.1" | ||||
|    inkscape:version="0.48.4 r9939" | ||||
|    inkscape:version="0.48.3.1 r9886" | ||||
|    sodipodi:docname="more-results.svg"> | ||||
|   <defs | ||||
|      id="defs12432" /> | ||||
| @@ -25,18 +25,18 @@ | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="1" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="90.509668" | ||||
|      inkscape:cx="6.5009792" | ||||
|      inkscape:cy="8.3589595" | ||||
|      inkscape:zoom="1" | ||||
|      inkscape:cx="8.3155237" | ||||
|      inkscape:cy="0.89548874" | ||||
|      inkscape:document-units="px" | ||||
|      inkscape:current-layer="g14642-3-0" | ||||
|      showgrid="false" | ||||
|      borderlayer="true" | ||||
|      inkscape:showpageshadow="false" | ||||
|      inkscape:window-width="1440" | ||||
|      inkscape:window-height="840" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="27" | ||||
|      inkscape:window-width="2560" | ||||
|      inkscape:window-height="1376" | ||||
|      inkscape:window-x="1200" | ||||
|      inkscape:window-y="187" | ||||
|      inkscape:window-maximized="1"> | ||||
|     <inkscape:grid | ||||
|        type="xygrid" | ||||
| @@ -90,11 +90,6 @@ | ||||
|          transform="translate(-2,0)" | ||||
|          width="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 | ||||
|          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" | ||||
|   | ||||
| Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.1 KiB | 
| @@ -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 | 
| @@ -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 | 
| @@ -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 | 
| @@ -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 | 
| @@ -112,7 +112,7 @@ expand_content_files= | ||||
| # e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS) | ||||
| # e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) | ||||
| GTKDOC_CFLAGS=$(GNOME_SHELL_CFLAGS) | ||||
| GTKDOC_LIBS=$(GNOME_SHELL_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. | ||||
| include $(top_srcdir)/gtk-doc.make | ||||
|   | ||||
| @@ -46,8 +46,8 @@ | ||||
|     <xi:include href="doc-gen-org.gnome.Shell.SearchProvider.xml"/> | ||||
|     <xi:include href="doc-gen-org.gnome.Shell.SearchProvider2.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-xfixes-cursor.xml"/> | ||||
|     <xi:include href="xml/shell-util.xml"/> | ||||
|     <xi:include href="xml/shell-mount-operation.xml"/> | ||||
|     <xi:include href="xml/shell-network-agent.xml"/> | ||||
|   | ||||
| @@ -66,4 +66,11 @@ its dependencies to build from tarballs.</description> | ||||
|       <gnome:userid>fmuellner</gnome:userid> | ||||
|     </foaf:Person> | ||||
|   </maintainer> | ||||
|   <maintainer> | ||||
|     <foaf:Person> | ||||
|       <foaf:name>Ray Strode</foaf:name> | ||||
|       <foaf:mbox rdf:resource="mailto:halfline@gmail.com" /> | ||||
|       <gnome:userid>halfline</gnome:userid> | ||||
|     </foaf:Person> | ||||
|   </maintainer> | ||||
| </Project> | ||||
|   | ||||
							
								
								
									
										123
									
								
								js/Makefile.am
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								js/Makefile.am
									
									
									
									
									
								
							| @@ -1,5 +1,7 @@ | ||||
| NULL = | ||||
| BUILT_SOURCES = | ||||
|  | ||||
| EXTRA_DIST = misc/config.js.in | ||||
| CLEANFILES = misc/config.js | ||||
|  | ||||
| misc/config.js: misc/config.js.in Makefile | ||||
| 	[ -d $(@D) ] || $(mkdir_p) $(@D) ; \ | ||||
| @@ -12,26 +14,103 @@ misc/config.js: misc/config.js.in Makefile | ||||
| 	    -e "s|[@]sysconfdir@|$(sysconfdir)|g" \ | ||||
|                $< > $@ | ||||
|  | ||||
| js_resource_files = $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(srcdir) --generate-dependencies $(srcdir)/js-resources.gresource.xml) | ||||
| js-resources.h: js-resources.gresource.xml $(js_resource_files) misc/config.js | ||||
| 	$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --sourcedir=$(builddir) --generate --c-name shell_js_resources $< | ||||
| js-resources.c: js-resources.gresource.xml $(js_resource_files) misc/config.js | ||||
| 	$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --sourcedir=$(builddir) --generate --c-name shell_js_resources $< | ||||
| jsdir = $(pkgdatadir)/js | ||||
|  | ||||
| js_built_sources = js-resources.c js-resources.h | ||||
|  | ||||
| BUILT_SOURCES += $(js_built_sources) | ||||
|  | ||||
| all-local: $(js_built_sources) | ||||
|  | ||||
| js_resource_dist_files = $(filter-out misc/config.js, $(js_resource_files)) | ||||
|  | ||||
| EXTRA_DIST = \ | ||||
| 	$(js_resource_dist_files) \ | ||||
| 	js-resources.gresource.xml \ | ||||
| 	misc/config.js.in \ | ||||
| 	$(NULL) | ||||
|  | ||||
| CLEANFILES = \ | ||||
| 	$(js_built_sources) \ | ||||
| nobase_dist_js_DATA = 	\ | ||||
| 	gdm/batch.js		\ | ||||
| 	gdm/fingerprint.js	\ | ||||
| 	gdm/loginDialog.js	\ | ||||
| 	gdm/powerMenu.js	\ | ||||
| 	gdm/realmd.js		\ | ||||
| 	gdm/util.js		\ | ||||
| 	extensionPrefs/main.js	\ | ||||
| 	misc/config.js		\ | ||||
| 	misc/extensionUtils.js	\ | ||||
| 	misc/fileUtils.js	\ | ||||
| 	misc/gnomeSession.js	\ | ||||
| 	misc/hash.js		\ | ||||
| 	misc/history.js		\ | ||||
| 	misc/jsParse.js		\ | ||||
| 	misc/loginManager.js	\ | ||||
| 	misc/modemManager.js	\ | ||||
| 	misc/params.js		\ | ||||
| 	misc/util.js		\ | ||||
| 	perf/core.js		\ | ||||
| 	ui/altTab.js		\ | ||||
| 	ui/appDisplay.js	\ | ||||
| 	ui/appFavorites.js	\ | ||||
| 	ui/backgroundMenu.js	\ | ||||
| 	ui/background.js	\ | ||||
| 	ui/boxpointer.js	\ | ||||
| 	ui/calendar.js		\ | ||||
| 	ui/checkBox.js		\ | ||||
| 	ui/ctrlAltTab.js	\ | ||||
| 	ui/dash.js		\ | ||||
| 	ui/dateMenu.js		\ | ||||
| 	ui/dnd.js		\ | ||||
| 	ui/endSessionDialog.js	\ | ||||
| 	ui/extensionSystem.js	\ | ||||
| 	ui/extensionDownloader.js \ | ||||
| 	ui/environment.js	\ | ||||
| 	ui/ibusCandidatePopup.js\ | ||||
| 	ui/grabHelper.js	\ | ||||
| 	ui/iconGrid.js		\ | ||||
| 	ui/keyboard.js		\ | ||||
| 	ui/layout.js		\ | ||||
| 	ui/lightbox.js		\ | ||||
| 	ui/lookingGlass.js	\ | ||||
| 	ui/magnifier.js		\ | ||||
| 	ui/magnifierDBus.js	\ | ||||
| 	ui/main.js		\ | ||||
| 	ui/messageTray.js	\ | ||||
| 	ui/modalDialog.js	\ | ||||
| 	ui/separator.js		\ | ||||
| 	ui/sessionMode.js	\ | ||||
| 	ui/shellEntry.js	\ | ||||
| 	ui/shellMountOperation.js \ | ||||
| 	ui/notificationDaemon.js \ | ||||
| 	ui/osdWindow.js		\ | ||||
| 	ui/overview.js		\ | ||||
| 	ui/overviewControls.js	\ | ||||
| 	ui/panel.js		\ | ||||
| 	ui/panelMenu.js		\ | ||||
| 	ui/pointerWatcher.js    \ | ||||
| 	ui/popupMenu.js		\ | ||||
| 	ui/remoteSearch.js	\ | ||||
| 	ui/runDialog.js		\ | ||||
| 	ui/screenshot.js	\ | ||||
|         ui/screenShield.js	\ | ||||
| 	ui/scripting.js		\ | ||||
| 	ui/search.js		\ | ||||
| 	ui/searchDisplay.js	\ | ||||
| 	ui/shellDBus.js		\ | ||||
| 	ui/status/accessibility.js	\ | ||||
| 	ui/status/keyboard.js	\ | ||||
| 	ui/status/lockScreenMenu.js	\ | ||||
| 	ui/status/network.js	\ | ||||
| 	ui/status/power.js	\ | ||||
| 	ui/status/volume.js	\ | ||||
| 	ui/status/bluetooth.js	\ | ||||
| 	ui/switcherPopup.js	\ | ||||
| 	ui/tweener.js		\ | ||||
| 	ui/unlockDialog.js	\ | ||||
| 	ui/userMenu.js		\ | ||||
| 	ui/userWidget.js	\ | ||||
| 	ui/viewSelector.js	\ | ||||
| 	ui/wanda.js		\ | ||||
| 	ui/windowAttentionHandler.js	\ | ||||
| 	ui/windowManager.js	\ | ||||
| 	ui/workspace.js		\ | ||||
| 	ui/workspaceThumbnail.js	\ | ||||
| 	ui/workspacesView.js	\ | ||||
| 	ui/workspaceSwitcherPopup.js    \ | ||||
| 	ui/xdndHandler.js	\ | ||||
| 	ui/components/__init__.js		\ | ||||
| 	ui/components/autorunManager.js		\ | ||||
| 	ui/components/automountManager.js	\ | ||||
| 	ui/components/networkAgent.js		\ | ||||
| 	ui/components/polkitAgent.js		\ | ||||
| 	ui/components/recorder.js		\ | ||||
| 	ui/components/telepathyClient.js	\ | ||||
| 	ui/components/keyring.js		\ | ||||
| 	$(NULL) | ||||
|   | ||||
| @@ -13,15 +13,13 @@ const _ = Gettext.gettext; | ||||
| const Config = imports.misc.config; | ||||
| const ExtensionUtils = imports.misc.extensionUtils; | ||||
|  | ||||
| const GnomeShellIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.Extensions"> \ | ||||
| <signal name="ExtensionStatusChanged"> \ | ||||
|     <arg type="s" name="uuid"/> \ | ||||
|     <arg type="i" name="state"/> \ | ||||
|     <arg type="s" name="error"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const GnomeShellIface = <interface name="org.gnome.Shell.Extensions"> | ||||
| <signal name="ExtensionStatusChanged"> | ||||
|     <arg type="s" name="uuid"/> | ||||
|     <arg type="i" name="state"/> | ||||
|     <arg type="s" name="error"/> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface); | ||||
|  | ||||
| @@ -206,11 +204,11 @@ const Application = new Lang.Class({ | ||||
|     _scanExtensions: function() { | ||||
|         let finder = new ExtensionUtils.ExtensionFinder(); | ||||
|         finder.connect('extension-found', Lang.bind(this, this._extensionFound)); | ||||
|         finder.connect('extensions-loaded', Lang.bind(this, this._extensionsLoaded)); | ||||
|         finder.scanExtensions(); | ||||
|         this._extensionsLoaded(); | ||||
|     }, | ||||
|  | ||||
|     _extensionFound: function(finder, extension) { | ||||
|     _extensionFound: function(signals, extension) { | ||||
|         let iter = this._model.append(); | ||||
|         this._model.set(iter, [0, 1], [extension.uuid, extension.metadata.name]); | ||||
|         this._extensionIters[extension.uuid] = iter; | ||||
|   | ||||
| @@ -1,506 +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(); | ||||
|                                } | ||||
|                                return Clutter.EVENT_PROPAGATE; | ||||
|                            })); | ||||
|  | ||||
|         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: false, | ||||
|                          y_fill: true, | ||||
|                          x_align: St.Align.START }); | ||||
|         this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', | ||||
|                                      can_focus: true }); | ||||
|         ShellEntry.addContextMenu(this._entry, { isPassword: true }); | ||||
|  | ||||
|         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: false, x_align: St.Align.START, 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() { | ||||
|         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); | ||||
| @@ -13,7 +13,9 @@ | ||||
|  * 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, see <http://www.gnu.org/licenses/>. | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  */ | ||||
|  | ||||
| const Lang = imports.lang; | ||||
|   | ||||
| @@ -5,13 +5,11 @@ const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const FprintManagerIface = '<node> \ | ||||
| <interface name="net.reactivated.Fprint.Manager"> \ | ||||
| <method name="GetDefaultDevice"> \ | ||||
|     <arg type="o" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const FprintManagerIface = <interface name='net.reactivated.Fprint.Manager'> | ||||
| <method name='GetDefaultDevice'> | ||||
|     <arg type='o' direction='out' /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const FprintManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(FprintManagerIface); | ||||
|  | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,64 +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 = '<node> \ | ||||
| <interface name="org.ovirt.vdsm.Credentials"> \ | ||||
| <signal name="UserAuthenticated"> \ | ||||
|     <arg type="s" name="token"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| 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
									
								
							
							
						
						
									
										129
									
								
								js/gdm/powerMenu.js
									
									
									
									
									
										Normal 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(); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										106
									
								
								js/gdm/realmd.js
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								js/gdm/realmd.js
									
									
									
									
									
								
							| @@ -5,58 +5,52 @@ const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const ProviderIface = '<node> \ | ||||
| <interface name="org.freedesktop.realmd.Provider"> \ | ||||
|     <property name="Name" type="s" access="read"/> \ | ||||
|     <property name="Version" type="s" access="read"/> \ | ||||
|     <property name="Realms" type="ao" access="read"/> \ | ||||
|     <method name="Discover"> \ | ||||
|         <arg name="string" type="s" direction="in"/> \ | ||||
|         <arg name="options" type="a{sv}" direction="in"/> \ | ||||
|         <arg name="relevance" type="i" direction="out"/> \ | ||||
|         <arg name="realm" type="ao" direction="out"/> \ | ||||
|     </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ProviderIface = <interface name='org.freedesktop.realmd.Provider'> | ||||
|     <property name="Name" type="s" access="read"/> | ||||
|     <property name="Version" type="s" access="read"/> | ||||
|     <property name="Realms" type="ao" access="read"/> | ||||
|     <method name="Discover"> | ||||
|         <arg name="string" type="s" direction="in"/> | ||||
|         <arg name="options" type="a{sv}" direction="in"/> | ||||
|         <arg name="relevance" type="i" direction="out"/> | ||||
|         <arg name="realm" type="ao" direction="out"/> | ||||
|     </method> | ||||
| </interface>; | ||||
| const Provider = Gio.DBusProxy.makeProxyWrapper(ProviderIface); | ||||
|  | ||||
| const ServiceIface = '<node> \ | ||||
| <interface name="org.freedesktop.realmd.Service"> \ | ||||
|     <method name="Cancel"> \ | ||||
|         <arg name="operation" type="s" direction="in"/> \ | ||||
|     </method> \ | ||||
|     <method name="Release" /> \ | ||||
|     <method name="SetLocale"> \ | ||||
|         <arg name="locale" type="s" direction="in"/> \ | ||||
|     </method> \ | ||||
|     <signal name="Diagnostics"> \ | ||||
|         <arg name="data" type="s"/> \ | ||||
|         <arg name="operation" type="s"/> \ | ||||
|     </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ServiceIface = <interface name="org.freedesktop.realmd.Service"> | ||||
|     <method name="Cancel"> | ||||
|         <arg name="operation" type="s" direction="in"/> | ||||
|     </method> | ||||
|     <method name="Release" /> | ||||
|     <method name="SetLocale"> | ||||
|         <arg name="locale" type="s" direction="in"/> | ||||
|     </method> | ||||
|     <signal name="Diagnostics"> | ||||
|         <arg name="data" type="s"/> | ||||
|         <arg name="operation" type="s"/> | ||||
|     </signal> | ||||
| </interface>; | ||||
| const Service = Gio.DBusProxy.makeProxyWrapper(ServiceIface); | ||||
|  | ||||
| const RealmIface = '<node> \ | ||||
| <interface name="org.freedesktop.realmd.Realm"> \ | ||||
|     <property name="Name" type="s" access="read"/> \ | ||||
|     <property name="Configured" type="s" access="read"/> \ | ||||
|     <property name="Details" type="a(ss)" access="read"/> \ | ||||
|     <property name="LoginFormats" type="as" access="read"/> \ | ||||
|     <property name="LoginPolicy" type="s" access="read"/> \ | ||||
|     <property name="PermittedLogins" type="as" access="read"/> \ | ||||
|     <property name="SupportedInterfaces" type="as" access="read"/> \ | ||||
|     <method name="ChangeLoginPolicy"> \ | ||||
|         <arg name="login_policy" type="s" direction="in"/> \ | ||||
|         <arg name="permitted_add" type="as" direction="in"/> \ | ||||
|         <arg name="permitted_remove" type="as" direction="in"/> \ | ||||
|         <arg name="options" type="a{sv}" direction="in"/> \ | ||||
|     </method> \ | ||||
|     <method name="Deconfigure"> \ | ||||
|         <arg name="options" type="a{sv}" direction="in"/> \ | ||||
|     </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const RealmIface = <interface name="org.freedesktop.realmd.Realm"> | ||||
|     <property name="Name" type="s" access="read"/> | ||||
|     <property name="Configured" type="s" access="read"/> | ||||
|     <property name="Details" type="a(ss)" access="read"/> | ||||
|     <property name="LoginFormats" type="as" access="read"/> | ||||
|     <property name="LoginPolicy" type="s" access="read"/> | ||||
|     <property name="PermittedLogins" type="as" access="read"/> | ||||
|     <property name="SupportedInterfaces" type="as" access="read"/> | ||||
|     <method name="ChangeLoginPolicy"> | ||||
|         <arg name="login_policy" type="s" direction="in"/> | ||||
|         <arg name="permitted_add" type="as" direction="in"/> | ||||
|         <arg name="permitted_remove" type="as" direction="in"/> | ||||
|         <arg name="options" type="a{sv}" direction="in"/> | ||||
|     </method> | ||||
|     <method name="Deconfigure"> | ||||
|         <arg name="options" type="a{sv}" direction="in"/> | ||||
|     </method> | ||||
| </interface>; | ||||
| const Realm = Gio.DBusProxy.makeProxyWrapper(RealmIface); | ||||
|  | ||||
| const Manager = new Lang.Class({ | ||||
| @@ -69,7 +63,7 @@ const Manager = new Lang.Class({ | ||||
|                                            Lang.bind(this, this._reloadRealms)) | ||||
|         this._realms = {}; | ||||
|  | ||||
|         this._signalId = this._aggregateProvider.connect('g-properties-changed', | ||||
|         this._aggregateProvider.connect('g-properties-changed', | ||||
|                                         Lang.bind(this, function(proxy, properties) { | ||||
|                                             if ('Realms' in properties.deep_unpack()) | ||||
|                                                 this._reloadRealms(); | ||||
| @@ -112,7 +106,7 @@ const Manager = new Lang.Class({ | ||||
|         realm.connect('g-properties-changed', | ||||
|                       Lang.bind(this, function(proxy, properties) { | ||||
|                                 if ('Configured' in properties.deep_unpack()) | ||||
|                                     this._reloadRealm(realm); | ||||
|                                     this._reloadRealm(); | ||||
|                                 })); | ||||
|     }, | ||||
|  | ||||
| @@ -140,18 +134,6 @@ const Manager = new Lang.Class({ | ||||
|         this._updateLoginFormat(); | ||||
|  | ||||
|         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) | ||||
|   | ||||
							
								
								
									
										372
									
								
								js/gdm/util.js
									
									
									
									
									
								
							
							
						
						
									
										372
									
								
								js/gdm/util.js
									
									
									
									
									
								
							| @@ -2,32 +2,24 @@ | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Batch = imports.gdm.batch; | ||||
| const Fprint = imports.gdm.fingerprint; | ||||
| const OVirt = imports.gdm.oVirt; | ||||
| const Realmd = imports.gdm.realmd; | ||||
| const Main = imports.ui.main; | ||||
| const Params = imports.misc.params; | ||||
| const ShellEntry = imports.ui.shellEntry; | ||||
| const SmartcardManager = imports.misc.smartcardManager; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const PASSWORD_SERVICE_NAME = 'gdm-password'; | ||||
| 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 CLONE_FADE_ANIMATION_TIME = 0.25; | ||||
|  | ||||
| const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; | ||||
| const PASSWORD_AUTHENTICATION_KEY = 'enable-password-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_TEXT_KEY = 'banner-message-text'; | ||||
| const ALLOWED_FAILURES_KEY = 'allowed-failures'; | ||||
| @@ -35,16 +27,6 @@ const ALLOWED_FAILURES_KEY = 'allowed-failures'; | ||||
| const LOGO_KEY = 'logo'; | ||||
| const DISABLE_USER_LIST_KEY = 'disable-user-list'; | ||||
|  | ||||
| // Give user 16ms to read each character of a PAM message | ||||
| const USER_READ_TIME = 16 | ||||
|  | ||||
| const MessageType = { | ||||
|     NONE: 0, | ||||
|     ERROR: 1, | ||||
|     INFO: 2, | ||||
|     HINT: 3 | ||||
| }; | ||||
|  | ||||
| function fadeInActor(actor) { | ||||
|     if (actor.opacity == 255 && actor.visible) | ||||
|         return null; | ||||
| @@ -129,45 +111,17 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|         this._client = client; | ||||
|  | ||||
|         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._smartcardManager = SmartcardManager.getSmartcardManager(); | ||||
|  | ||||
|         // 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._messageQueueTimeoutId = 0; | ||||
|         this.hasPendingMessages = false; | ||||
|         this.reauthenticating = false; | ||||
|         this._realmManager = new Realmd.Manager(); | ||||
|  | ||||
|         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) { | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|         this._hold = hold; | ||||
|         this._userName = userName; | ||||
|         this.reauthenticating = false; | ||||
|  | ||||
|         this._checkForFingerprintReader(); | ||||
|  | ||||
| @@ -185,10 +139,8 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|         if (this._cancellable) | ||||
|             this._cancellable.cancel(); | ||||
|  | ||||
|         if (this._userVerifier) { | ||||
|         if (this._userVerifier) | ||||
|             this._userVerifier.call_cancel_sync(null); | ||||
|             this.clear(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     clear: function() { | ||||
| @@ -201,125 +153,33 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|             this._userVerifier.run_dispose(); | ||||
|             this._userVerifier = null; | ||||
|         } | ||||
|  | ||||
|         this._clearMessageQueue(); | ||||
|     }, | ||||
|  | ||||
|     answerQuery: function(serviceName, answer) { | ||||
|         if (!this.hasPendingMessages) { | ||||
|             this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); | ||||
|         } else { | ||||
|             let signalId = this.connect('no-more-messages', | ||||
|                                         Lang.bind(this, function() { | ||||
|                                             this.disconnect(signalId); | ||||
|                                             this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); | ||||
|                                         })); | ||||
|         } | ||||
|     }, | ||||
|         // Clear any previous message | ||||
|         this.emit('show-message', null, null); | ||||
|  | ||||
|     _getIntervalForMessage: function(message) { | ||||
|         // We probably could be smarter here | ||||
|         return message.length * USER_READ_TIME; | ||||
|     }, | ||||
|  | ||||
|     finishMessageQueue: function() { | ||||
|         if (!this.hasPendingMessages) | ||||
|             return; | ||||
|  | ||||
|         this._messageQueue = []; | ||||
|  | ||||
|         this.hasPendingMessages = false; | ||||
|         this.emit('no-more-messages'); | ||||
|     }, | ||||
|  | ||||
|     _queueMessageTimeout: function() { | ||||
|         if (this._messageQueue.length == 0) { | ||||
|             this.finishMessageQueue(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (this._messageQueueTimeoutId != 0) | ||||
|             return; | ||||
|  | ||||
|         let message = this._messageQueue.shift(); | ||||
|  | ||||
|         this.emit('show-message', message.text, message.type); | ||||
|  | ||||
|         this._messageQueueTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, | ||||
|                                                        message.interval, | ||||
|                                                        Lang.bind(this, function() { | ||||
|                                                            this._messageQueueTimeoutId = 0; | ||||
|                                                            this._queueMessageTimeout(); | ||||
|                                                            return GLib.SOURCE_REMOVE; | ||||
|                                                        })); | ||||
|     }, | ||||
|  | ||||
|     _queueMessage: function(message, messageType) { | ||||
|         let interval = this._getIntervalForMessage(message); | ||||
|  | ||||
|         this.hasPendingMessages = true; | ||||
|         this._messageQueue.push({ text: message, type: messageType, interval: interval }); | ||||
|         this._queueMessageTimeout(); | ||||
|     }, | ||||
|  | ||||
|     _clearMessageQueue: function() { | ||||
|         this.finishMessageQueue(); | ||||
|  | ||||
|         if (this._messageQueueTimeoutId != 0) { | ||||
|             GLib.source_remove(this._messageQueueTimeoutId); | ||||
|             this._messageQueueTimeoutId = 0; | ||||
|         } | ||||
|         this.emit('show-message', null, MessageType.NONE); | ||||
|         this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); | ||||
|     }, | ||||
|  | ||||
|     _checkForFingerprintReader: function() { | ||||
|         this._haveFingerprintReader = false; | ||||
|  | ||||
|         if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) { | ||||
|             this._updateDefaultService(); | ||||
|         if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this, | ||||
|             function(device, error) { | ||||
|                 if (!error && device) | ||||
|                     this._haveFingerprintReader = true; | ||||
|                     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) { | ||||
|         logError(error, where); | ||||
|         this._hold.release(); | ||||
|  | ||||
|         this._queueMessage(_("Authentication error"), MessageType.ERROR); | ||||
|         this.emit('show-message', _("Authentication error"), 'login-dialog-message-warning'); | ||||
|         this._verificationFailed(false); | ||||
|     }, | ||||
|  | ||||
| @@ -340,7 +200,6 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.reauthenticating = true; | ||||
|         this._connectSignals(); | ||||
|         this._beginVerification(); | ||||
|         this._hold.release(); | ||||
| @@ -371,119 +230,126 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|         this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); | ||||
|     }, | ||||
|  | ||||
|     _getForegroundService: 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) { | ||||
|     _beginVerification: function() { | ||||
|         this._hold.acquire(); | ||||
|  | ||||
|         if (this._userName) { | ||||
|            this._userVerifier.call_begin_verification_for_user(serviceName, | ||||
|                                                                this._userName, | ||||
|                                                                this._cancellable, | ||||
|                                                                Lang.bind(this, function(obj, result) { | ||||
|                try { | ||||
|                    obj.call_begin_verification_for_user_finish(result); | ||||
|                } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|                    return; | ||||
|                } catch(e) { | ||||
|                    this._reportInitError('Failed to start verification for user', e); | ||||
|                    return; | ||||
|                } | ||||
|             this._userVerifier.call_begin_verification_for_user(PASSWORD_SERVICE_NAME, | ||||
|                                                                 this._userName, | ||||
|                                                                 this._cancellable, | ||||
|                                                                 Lang.bind(this, function(obj, result) { | ||||
|                 try { | ||||
|                     obj.call_begin_verification_for_user_finish(result); | ||||
|                 } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|                     return; | ||||
|                 } catch(e) { | ||||
|                     this._reportInitError('Failed to start verification for user', e); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                this._hold.release(); | ||||
|            })); | ||||
|                 this._hold.release(); | ||||
|             })); | ||||
|  | ||||
|             if (this._haveFingerprintReader) { | ||||
|                 this._hold.acquire(); | ||||
|  | ||||
|                 this._userVerifier.call_begin_verification_for_user(FINGERPRINT_SERVICE_NAME, | ||||
|                                                                     this._userName, | ||||
|                                                                     this._cancellable, | ||||
|                                                                     Lang.bind(this, function(obj, result) { | ||||
|                     try { | ||||
|                         obj.call_begin_verification_for_user_finish(result); | ||||
|                     } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|                         return; | ||||
|                     } catch(e) { | ||||
|                         this._reportInitError('Failed to start fingerprint verification for user', e); | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
|                     this._hold.release(); | ||||
|                 })); | ||||
|             } | ||||
|         } else { | ||||
|            this._userVerifier.call_begin_verification(serviceName, | ||||
|                                                       this._cancellable, | ||||
|                                                       Lang.bind(this, function(obj, result) { | ||||
|                try { | ||||
|                    obj.call_begin_verification_finish(result); | ||||
|                } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|                    return; | ||||
|                } catch(e) { | ||||
|                    this._reportInitError('Failed to start verification', e); | ||||
|                    return; | ||||
|                } | ||||
|             this._userVerifier.call_begin_verification(PASSWORD_SERVICE_NAME, | ||||
|                                                        this._cancellable, | ||||
|                                                        Lang.bind(this, function(obj, result) { | ||||
|                 try { | ||||
|                     obj.call_begin_verification_finish(result); | ||||
|                 } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|                     return; | ||||
|                 } catch(e) { | ||||
|                     this._reportInitError('Failed to start verification', e); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                this._hold.release(); | ||||
|            })); | ||||
|                 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) { | ||||
|         if (this.serviceIsForeground(serviceName)) { | ||||
|             this._queueMessage(info, MessageType.INFO); | ||||
|         } else if (serviceName == FINGERPRINT_SERVICE_NAME && | ||||
|         // We don't display fingerprint messages, because they | ||||
|         // have words like UPEK in them. Instead we use the messages | ||||
|         // as a cue to display our own message. | ||||
|         if (serviceName == FINGERPRINT_SERVICE_NAME && | ||||
|             this._haveFingerprintReader) { | ||||
|             // 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 | ||||
|             // 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.emit('show-message', info, 'login-dialog-message-info'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _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; | ||||
|         this.emit('show-message', 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) { | ||||
|         if (!this.serviceIsForeground(serviceName)) | ||||
|         // We only expect questions to come from the main auth service | ||||
|         if (serviceName != PASSWORD_SERVICE_NAME) | ||||
|             return; | ||||
|  | ||||
|         this._showRealmLoginHint(); | ||||
|         this._realmLoginHintSignalId = this._realmManager.connect('login-format-changed', | ||||
|                                                                   Lang.bind(this, this._showRealmLoginHint)); | ||||
|  | ||||
|         this.emit('ask-question', serviceName, question, ''); | ||||
|     }, | ||||
|  | ||||
|     _onSecretInfoQuery: function(client, serviceName, secretQuestion) { | ||||
|         if (!this.serviceIsForeground(serviceName)) | ||||
|         // We only expect secret requests to come from the main auth service | ||||
|         if (serviceName != PASSWORD_SERVICE_NAME) | ||||
|             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'); | ||||
|     }, | ||||
|  | ||||
|     _onReset: function() { | ||||
|         this.clear(); | ||||
|  | ||||
|         // Clear previous attempts to authenticate | ||||
|         this._failCounter = 0; | ||||
|         this._updateDefaultService(); | ||||
|  | ||||
|         this.emit('reset'); | ||||
|     }, | ||||
| @@ -492,15 +358,6 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|         this.emit('verification-complete'); | ||||
|     }, | ||||
|  | ||||
|     _cancelAndReset: function() { | ||||
|         this.cancel(); | ||||
|         this._onReset(); | ||||
|     }, | ||||
|  | ||||
|     _retry: function() { | ||||
|         this.begin(this._userName, new Batch.Hold()); | ||||
|     }, | ||||
|  | ||||
|     _verificationFailed: function(retry) { | ||||
|         // For Not Listed / enterprise logins, immediately reset | ||||
|         // the dialog | ||||
| @@ -512,47 +369,34 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|             this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY); | ||||
|  | ||||
|         if (canRetry) { | ||||
|             if (!this.hasPendingMessages) { | ||||
|                 this._retry(); | ||||
|             } else { | ||||
|                 let signalId = this.connect('no-more-messages', | ||||
|                                             Lang.bind(this, function() { | ||||
|                                                 this.disconnect(signalId); | ||||
|                                                 this._retry(); | ||||
|                                             })); | ||||
|             } | ||||
|             this.clear(); | ||||
|             this.begin(this._userName, new Batch.Hold()); | ||||
|         } else { | ||||
|             if (!this.hasPendingMessages) { | ||||
|                 this._cancelAndReset(); | ||||
|             } else { | ||||
|                 let signalId = this.connect('no-more-messages', | ||||
|                                             Lang.bind(this, function() { | ||||
|                                                 this.disconnect(signalId); | ||||
|                                                 this._cancelAndReset(); | ||||
|                                             })); | ||||
|             } | ||||
|             // Allow some time to see the message, then reset everything | ||||
|             Mainloop.timeout_add(3000, Lang.bind(this, function() { | ||||
|                 this.cancel(); | ||||
|  | ||||
|                 this._onReset(); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         this.emit('verification-failed'); | ||||
|     }, | ||||
|  | ||||
|     _onConversationStopped: function(client, serviceName) { | ||||
|         // If the 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. | ||||
|         // But if, e.g., fingerprint fails, still give | ||||
|         // password authentication a chance to succeed | ||||
|         if (this.serviceIsForeground(serviceName)) { | ||||
|         if (serviceName == PASSWORD_SERVICE_NAME) { | ||||
|             this._verificationFailed(true); | ||||
|         } | ||||
|  | ||||
|         this.emit('hide-login-hint'); | ||||
|  | ||||
|         if (this._realmLoginHintSignalId) { | ||||
|             this._realmManager.disconnect(this._realmLoginHintSignalId); | ||||
|             this._realmLoginHintSignalId = 0; | ||||
|         } | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(ShellUserVerifier.prototype); | ||||
|   | ||||
| @@ -1,114 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <gresources> | ||||
|   <gresource prefix="/org/gnome/shell"> | ||||
|     <file>gdm/authPrompt.js</file> | ||||
|     <file>gdm/batch.js</file> | ||||
|     <file>gdm/fingerprint.js</file> | ||||
|     <file>gdm/loginDialog.js</file> | ||||
|     <file>gdm/oVirt.js</file> | ||||
|     <file>gdm/realmd.js</file> | ||||
|     <file>gdm/util.js</file> | ||||
|  | ||||
|     <file>extensionPrefs/main.js</file> | ||||
|  | ||||
|     <file>misc/config.js</file> | ||||
|     <file>misc/extensionUtils.js</file> | ||||
|     <file>misc/fileUtils.js</file> | ||||
|     <file>misc/gnomeSession.js</file> | ||||
|     <file>misc/history.js</file> | ||||
|     <file>misc/jsParse.js</file> | ||||
|     <file>misc/loginManager.js</file> | ||||
|     <file>misc/modemManager.js</file> | ||||
|     <file>misc/objectManager.js</file> | ||||
|     <file>misc/params.js</file> | ||||
|     <file>misc/smartcardManager.js</file> | ||||
|     <file>misc/util.js</file> | ||||
|  | ||||
|     <file>perf/core.js</file> | ||||
|  | ||||
|     <file>ui/altTab.js</file> | ||||
|     <file>ui/animation.js</file> | ||||
|     <file>ui/appDisplay.js</file> | ||||
|     <file>ui/appFavorites.js</file> | ||||
|     <file>ui/backgroundMenu.js</file> | ||||
|     <file>ui/background.js</file> | ||||
|     <file>ui/boxpointer.js</file> | ||||
|     <file>ui/calendar.js</file> | ||||
|     <file>ui/checkBox.js</file> | ||||
|     <file>ui/ctrlAltTab.js</file> | ||||
|     <file>ui/dash.js</file> | ||||
|     <file>ui/dateMenu.js</file> | ||||
|     <file>ui/dnd.js</file> | ||||
|     <file>ui/endSessionDialog.js</file> | ||||
|     <file>ui/environment.js</file> | ||||
|     <file>ui/extensionDownloader.js</file> | ||||
|     <file>ui/extensionSystem.js</file> | ||||
|     <file>ui/focusCaretTracker.js</file> | ||||
|     <file>ui/grabHelper.js</file> | ||||
|     <file>ui/ibusCandidatePopup.js</file> | ||||
|     <file>ui/iconGrid.js</file> | ||||
|     <file>ui/keyboard.js</file> | ||||
|     <file>ui/layout.js</file> | ||||
|     <file>ui/lightbox.js</file> | ||||
|     <file>ui/lookingGlass.js</file> | ||||
|     <file>ui/magnifier.js</file> | ||||
|     <file>ui/magnifierDBus.js</file> | ||||
|     <file>ui/main.js</file> | ||||
|     <file>ui/messageTray.js</file> | ||||
|     <file>ui/modalDialog.js</file> | ||||
|     <file>ui/notificationDaemon.js</file> | ||||
|     <file>ui/osdWindow.js</file> | ||||
|     <file>ui/overview.js</file> | ||||
|     <file>ui/overviewControls.js</file> | ||||
|     <file>ui/panel.js</file> | ||||
|     <file>ui/panelMenu.js</file> | ||||
|     <file>ui/pointerWatcher.js</file> | ||||
|     <file>ui/popupMenu.js</file> | ||||
|     <file>ui/remoteMenu.js</file> | ||||
|     <file>ui/remoteSearch.js</file> | ||||
|     <file>ui/runDialog.js</file> | ||||
|     <file>ui/screenShield.js</file> | ||||
|     <file>ui/screencast.js</file> | ||||
|     <file>ui/screenshot.js</file> | ||||
|     <file>ui/scripting.js</file> | ||||
|     <file>ui/search.js</file> | ||||
|     <file>ui/separator.js</file> | ||||
|     <file>ui/sessionMode.js</file> | ||||
|     <file>ui/shellDBus.js</file> | ||||
|     <file>ui/shellEntry.js</file> | ||||
|     <file>ui/shellMountOperation.js</file> | ||||
|     <file>ui/slider.js</file> | ||||
|     <file>ui/switcherPopup.js</file> | ||||
|     <file>ui/tweener.js</file> | ||||
|     <file>ui/unlockDialog.js</file> | ||||
|     <file>ui/userWidget.js</file> | ||||
|     <file>ui/viewSelector.js</file> | ||||
|     <file>ui/windowAttentionHandler.js</file> | ||||
|     <file>ui/windowManager.js</file> | ||||
|     <file>ui/workspace.js</file> | ||||
|     <file>ui/workspaceSwitcherPopup.js</file> | ||||
|     <file>ui/workspaceThumbnail.js</file> | ||||
|     <file>ui/workspacesView.js</file> | ||||
|     <file>ui/xdndHandler.js</file> | ||||
|  | ||||
|     <file>ui/components/__init__.js</file> | ||||
|     <file>ui/components/autorunManager.js</file> | ||||
|     <file>ui/components/automountManager.js</file> | ||||
|     <file>ui/components/networkAgent.js</file> | ||||
|     <file>ui/components/polkitAgent.js</file> | ||||
|     <file>ui/components/telepathyClient.js</file> | ||||
|     <file>ui/components/keyring.js</file> | ||||
|  | ||||
|     <file>ui/status/accessibility.js</file> | ||||
|     <file>ui/status/brightness.js</file> | ||||
|     <file>ui/status/location.js</file> | ||||
|     <file>ui/status/keyboard.js</file> | ||||
|     <file>ui/status/network.js</file> | ||||
|     <file>ui/status/power.js</file> | ||||
|     <file>ui/status/rfkill.js</file> | ||||
|     <file>ui/status/volume.js</file> | ||||
|     <file>ui/status/bluetooth.js</file> | ||||
|     <file>ui/status/screencast.js</file> | ||||
|     <file>ui/status/system.js</file> | ||||
|   </gresource> | ||||
| </gresources> | ||||
| @@ -174,9 +174,17 @@ const ExtensionFinder = new Lang.Class({ | ||||
|         this.emit('extension-found', extension); | ||||
|     }, | ||||
|  | ||||
|     _extensionsLoaded: function() { | ||||
|         this.emit('extensions-loaded'); | ||||
|     }, | ||||
|  | ||||
|     scanExtensions: function() { | ||||
|         let perUserDir = Gio.File.new_for_path(global.userdatadir); | ||||
|         FileUtils.collectFromDatadirs('extensions', true, Lang.bind(this, this._loadExtension, perUserDir)); | ||||
|         FileUtils.collectFromDatadirsAsync('extensions', | ||||
|                                            { processFile: Lang.bind(this, this._loadExtension), | ||||
|                                              loadedCallback: Lang.bind(this, this._extensionsLoaded), | ||||
|                                              includeUserDir: true, | ||||
|                                              data: perUserDir }); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ExtensionFinder.prototype); | ||||
|   | ||||
| @@ -5,27 +5,80 @@ const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Params = imports.misc.params; | ||||
|  | ||||
| function collectFromDatadirs(subdir, includeUserDir, processFile) { | ||||
| function listDirAsync(file, callback) { | ||||
|     let allFiles = []; | ||||
|     file.enumerate_children_async('standard::name,standard::type', | ||||
|                                   Gio.FileQueryInfoFlags.NONE, | ||||
|                                   GLib.PRIORITY_LOW, null, function (obj, res) { | ||||
|         let enumerator = obj.enumerate_children_finish(res); | ||||
|         function onNextFileComplete(obj, res) { | ||||
|             let files = obj.next_files_finish(res); | ||||
|             if (files.length) { | ||||
|                 allFiles = allFiles.concat(files); | ||||
|                 enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, onNextFileComplete); | ||||
|             } else { | ||||
|                 enumerator.close(null); | ||||
|                 callback(allFiles); | ||||
|             } | ||||
|         } | ||||
|         enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, onNextFileComplete); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function _collectFromDirectoryAsync(dir, loadState) { | ||||
|     function done() { | ||||
|         loadState.numLoading--; | ||||
|         if (loadState.loadedCallback && | ||||
|             loadState.numLoading == 0) | ||||
|             loadState.loadedCallback(loadState.data); | ||||
|     } | ||||
|  | ||||
|     dir.query_info_async('standard::type', Gio.FileQueryInfoFlags.NONE, | ||||
|         GLib.PRIORITY_DEFAULT, null, function(object, res) { | ||||
|             try { | ||||
|                 object.query_info_finish(res); | ||||
|             } catch (e) { | ||||
|                 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) | ||||
|                     log(e.message); | ||||
|                 done(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             listDirAsync(dir, Lang.bind(this, function(infos) { | ||||
|                 for (let i = 0; i < infos.length; i++) | ||||
|                     loadState.processFile(dir.get_child(infos[i].get_name()), | ||||
|                                           infos[i], loadState.data); | ||||
|                 done(); | ||||
|             })); | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function collectFromDatadirsAsync(subdir, params) { | ||||
|     params = Params.parse(params, { includeUserDir: false, | ||||
|                                     processFile: null, | ||||
|                                     loadedCallback: null, | ||||
|                                     data: null }); | ||||
|     let loadState = { data: params.data, | ||||
|                       numLoading: 0, | ||||
|                       loadedCallback: params.loadedCallback, | ||||
|                       processFile: params.processFile }; | ||||
|  | ||||
|     if (params.processFile == null) { | ||||
|         if (params.loadedCallback) | ||||
|             params.loadedCallback(params.data); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     let dataDirs = GLib.get_system_data_dirs(); | ||||
|     if (includeUserDir) | ||||
|     if (params.includeUserDir) | ||||
|         dataDirs.unshift(GLib.get_user_data_dir()); | ||||
|     loadState.numLoading = dataDirs.length; | ||||
|  | ||||
|     for (let i = 0; i < dataDirs.length; i++) { | ||||
|         let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', subdir]); | ||||
|         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))) | ||||
|                 processFile(fileEnum.get_child(info), info); | ||||
|         } | ||||
|         _collectFromDirectoryAsync(dir, loadState); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -64,6 +117,7 @@ function recursivelyMoveDir(srcDir, destDir) { | ||||
|         let type = info.get_file_type(); | ||||
|         let srcChild = srcDir.get_child(info.get_name()); | ||||
|         let destChild = destDir.get_child(info.get_name()); | ||||
|         log([srcChild.get_path(), destChild.get_path()]); | ||||
|         if (type == Gio.FileType.REGULAR) | ||||
|             srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null); | ||||
|         else if (type == Gio.FileType.DIRECTORY) | ||||
|   | ||||
| @@ -4,17 +4,15 @@ const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const PresenceIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager.Presence"> \ | ||||
| <method name="SetStatus"> \ | ||||
|     <arg type="u" direction="in"/> \ | ||||
| </method> \ | ||||
| <property name="status" type="u" access="readwrite"/> \ | ||||
| <signal name="StatusChanged"> \ | ||||
|     <arg type="u" direction="out"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const PresenceIface = <interface name="org.gnome.SessionManager.Presence"> | ||||
| <method name="SetStatus"> | ||||
|     <arg type="u" direction="in"/> | ||||
| </method> | ||||
| <property name="status" type="u" access="readwrite"/> | ||||
| <signal name="StatusChanged"> | ||||
|     <arg type="u" direction="out"/> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const PresenceStatus = { | ||||
|     AVAILABLE: 0, | ||||
| @@ -32,16 +30,14 @@ function Presence(initCallback, cancellable) { | ||||
| // Note inhibitors are immutable objects, so they don't | ||||
| // change at runtime (changes always come in the form | ||||
| // of new inhibitors) | ||||
| const InhibitorIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager.Inhibitor"> \ | ||||
| <method name="GetAppId"> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetReason"> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const InhibitorIface = <interface name="org.gnome.SessionManager.Inhibitor"> | ||||
| <method name="GetAppId"> | ||||
|     <arg type="s" direction="out" /> | ||||
| </method> | ||||
| <method name="GetReason"> | ||||
|     <arg type="s" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| var InhibitorProxy = Gio.DBusProxy.makeProxyWrapper(InhibitorIface); | ||||
| function Inhibitor(objectPath, initCallback, cancellable) { | ||||
| @@ -49,29 +45,27 @@ function Inhibitor(objectPath, initCallback, cancellable) { | ||||
| } | ||||
|  | ||||
| // Not the full interface, only the methods we use | ||||
| const SessionManagerIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager"> \ | ||||
| <method name="Logout"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Shutdown" /> \ | ||||
| <method name="Reboot" /> \ | ||||
| <method name="CanShutdown"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="IsInhibited"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <property name="SessionIsActive" type="b" access="read"/> \ | ||||
| <signal name="InhibitorAdded"> \ | ||||
|     <arg type="o" direction="out"/> \ | ||||
| </signal> \ | ||||
| <signal name="InhibitorRemoved"> \ | ||||
|     <arg type="o" direction="out"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const SessionManagerIface = <interface name="org.gnome.SessionManager"> | ||||
| <method name="Logout"> | ||||
|     <arg type="u" direction="in" /> | ||||
| </method> | ||||
| <method name="Shutdown" /> | ||||
| <method name="Reboot" /> | ||||
| <method name="CanShutdown"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="IsInhibited"> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <property name="SessionIsActive" type="b" access="read"/> | ||||
| <signal name="InhibitorAdded"> | ||||
|     <arg type="o" direction="out"/> | ||||
| </signal> | ||||
| <signal name="InhibitorRemoved"> | ||||
|     <arg type="o" direction="out"/> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| var SessionManagerProxy = Gio.DBusProxy.makeProxyWrapper(SessionManagerIface); | ||||
| function SessionManager(initCallback, cancellable) { | ||||
|   | ||||
							
								
								
									
										141
									
								
								js/misc/hash.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								js/misc/hash.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const System = imports.system; | ||||
|  | ||||
| const Params = imports.misc.params; | ||||
|  | ||||
| // This is an implementation of EcmaScript SameValue algorithm, | ||||
| // which returns true if two values are not observably distinguishable. | ||||
| // It was taken from http://wiki.ecmascript.org/doku.php?id=harmony:egal | ||||
| // | ||||
| // In the future, we may want to use the 'is' operator instead. | ||||
| function _sameValue(x, y) { | ||||
|     if (x === y) { | ||||
|         // 0 === -0, but they are not identical | ||||
|         return x !== 0 || 1 / x === 1 / y; | ||||
|     } | ||||
|  | ||||
|     // NaN !== NaN, but they are identical. | ||||
|     // NaNs are the only non-reflexive value, i.e., if x !== x, | ||||
|     // then x is a NaN. | ||||
|     // isNaN is broken: it converts its argument to number, so | ||||
|     // isNaN("foo") => true | ||||
|     return x !== x && y !== y; | ||||
| } | ||||
|  | ||||
| const _hashers = { | ||||
|     object: function(o) { return o ? System.addressOf(o) : 'null'; }, | ||||
|     function: function(f) { return System.addressOf(f); }, | ||||
|     string: function(s) { return s; }, | ||||
|     number: function(n) { return String(n); }, | ||||
|     undefined: function() { return 'undefined'; }, | ||||
| }; | ||||
|  | ||||
| /* Map is meant to be similar in usage to ES6 Map, which is | ||||
|    described at http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets, | ||||
|    without requiring more than ES5 + Gjs extensions. | ||||
|  | ||||
|    Known differences from other implementations: | ||||
|    Polyfills around the web usually implement HashMaps for | ||||
|    primitive values and reversed WeakMaps for object keys, | ||||
|    but we want real maps with real O(1) semantics in all cases, | ||||
|    and the easiest way is to have different hashers for different | ||||
|    types. | ||||
|  | ||||
|    Known differences from the ES6 specification: | ||||
|    - Map is a Lang.Class, not a ES6 class, so inheritance, | ||||
|      prototype, sealing, etc. work differently. | ||||
|    - items(), keys() and values() don't return iterators, | ||||
|      they return actual arrays, so they incur a full copy everytime | ||||
|      they're called, and they don't see changes if you mutate | ||||
|      the table while iterating | ||||
|      (admittedly, the ES6 spec is a bit unclear on this, and | ||||
|      the reference code would just blow up) | ||||
| */ | ||||
| const Map = new Lang.Class({ | ||||
|     Name: 'Map', | ||||
|  | ||||
|     _init: function(iterable) { | ||||
|         this._pool = { }; | ||||
|  | ||||
|         if (iterable) { | ||||
|             for (let i = 0; i < iterable.length; i++) { | ||||
|                 let [key, value] = iterable[i]; | ||||
|                 this.set(key, value); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _hashKey: function(key) { | ||||
|         let type = typeof(key); | ||||
|         return type + ':' + _hashers[type](key); | ||||
|     }, | ||||
|  | ||||
|     _internalGet: function(key) { | ||||
|         let hash = this._hashKey(key); | ||||
|         let node = this._pool[hash]; | ||||
|  | ||||
|         if (node && _sameValue(node.key, key)) | ||||
|             return [true, node.value]; | ||||
|         else | ||||
|             return [false, null]; | ||||
|     }, | ||||
|  | ||||
|     get: function(key) { | ||||
|         return this._internalGet(key)[1]; | ||||
|     }, | ||||
|  | ||||
|     has: function(key) { | ||||
|         return this._internalGet(key)[0]; | ||||
|     }, | ||||
|  | ||||
|     set: function(key, value) { | ||||
|         let hash = this._hashKey(key); | ||||
|         let node = this._pool[hash]; | ||||
|  | ||||
|         if (node) { | ||||
|             node.key = key; | ||||
|             node.value = value; | ||||
|         } else { | ||||
|             this._pool[hash] = { key: key, value: value }; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     delete: function(key) { | ||||
|         let hash = this._hashKey(key); | ||||
|         let node = this._pool[hash]; | ||||
|  | ||||
|         if (node && _sameValue(node.key, key)) { | ||||
|             delete this._pool[hash]; | ||||
|             return [node.key, node.value]; | ||||
|         } else { | ||||
|             return [null, null]; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     keys: function() { | ||||
|         let pool = this._pool; | ||||
|         return Object.getOwnPropertyNames(pool).map(function(hash) { | ||||
|             return pool[hash].key; | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     values: function() { | ||||
|         let pool = this._pool; | ||||
|         return Object.getOwnPropertyNames(pool).map(function(hash) { | ||||
|             return pool[hash].value; | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     items: function() { | ||||
|         let pool = this._pool; | ||||
|         return Object.getOwnPropertyNames(pool).map(function(hash) { | ||||
|             return [pool[hash].key, pool[hash].value]; | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     size: function() { | ||||
|         return Object.getOwnPropertyNames(this._pool).length; | ||||
|     }, | ||||
| }); | ||||
| @@ -89,7 +89,7 @@ const HistoryManager = new Lang.Class({ | ||||
|         } else if (symbol == Clutter.KEY_Down) { | ||||
|             return this._setNextItem(entry.get_text()); | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _indexChanged: function() { | ||||
|   | ||||
| @@ -7,103 +7,72 @@ const Mainloop = imports.mainloop; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const SystemdLoginManagerIface = '<node> \ | ||||
| <interface name="org.freedesktop.login1.Manager"> \ | ||||
| <method name="Suspend"> \ | ||||
|     <arg type="b" direction="in"/> \ | ||||
| </method> \ | ||||
| <method name="CanSuspend"> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="Inhibit"> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="h" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="GetSession"> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="o" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="ListSessions"> \ | ||||
|     <arg name="sessions" type="a(susso)" direction="out"/> \ | ||||
| </method> \ | ||||
| <signal name="PrepareForSleep"> \ | ||||
|     <arg type="b" direction="out"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| 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'> | ||||
|     <arg type='b' direction='in'/> | ||||
| </method> | ||||
| <method name='CanPowerOff'> | ||||
|     <arg type='s' direction='out'/> | ||||
| </method> | ||||
| <method name='CanReboot'> | ||||
|     <arg type='s' direction='out'/> | ||||
| </method> | ||||
| <method name='CanSuspend'> | ||||
|     <arg type='s' direction='out'/> | ||||
| </method> | ||||
| <method name='Inhibit'> | ||||
|     <arg type='s' direction='in'/> | ||||
|     <arg type='s' direction='in'/> | ||||
|     <arg type='s' direction='in'/> | ||||
|     <arg type='s' direction='in'/> | ||||
|     <arg type='h' direction='out'/> | ||||
| </method> | ||||
| <method name='ListSessions'> | ||||
|     <arg name='sessions' type='a(susso)' direction='out'/> | ||||
| </method> | ||||
| <signal name='PrepareForSleep'> | ||||
|     <arg type='b' direction='out'/> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const SystemdLoginSessionIface = '<node> \ | ||||
| <interface name="org.freedesktop.login1.Session"> \ | ||||
| <signal name="Lock" /> \ | ||||
| <signal name="Unlock" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'> | ||||
| <signal name='Lock' /> | ||||
| <signal name='Unlock' /> | ||||
| </interface>; | ||||
|  | ||||
| const SystemdLoginManager = Gio.DBusProxy.makeProxyWrapper(SystemdLoginManagerIface); | ||||
| const SystemdLoginSession = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface); | ||||
|  | ||||
| const ConsoleKitManagerIface = '<node> \ | ||||
| <interface name="org.freedesktop.ConsoleKit.Manager"> \ | ||||
| <method name="CanRestart"> \ | ||||
|     <arg type="b" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="CanStop"> \ | ||||
|     <arg type="b" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="Restart" /> \ | ||||
| <method name="Stop" /> \ | ||||
| <method name="GetCurrentSession"> \ | ||||
|     <arg type="o" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ConsoleKitManagerIface = <interface name='org.freedesktop.ConsoleKit.Manager'> | ||||
| <method name='CanRestart'> | ||||
|     <arg type='b' direction='out'/> | ||||
| </method> | ||||
| <method name='CanStop'> | ||||
|     <arg type='b' direction='out'/> | ||||
| </method> | ||||
| <method name='Restart' /> | ||||
| <method name='Stop' /> | ||||
| <method name='GetCurrentSession'> | ||||
|     <arg type='o' direction='out' /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const ConsoleKitSessionIface = '<node> \ | ||||
| <interface name="org.freedesktop.ConsoleKit.Session"> \ | ||||
| <signal name="Lock" /> \ | ||||
| <signal name="Unlock" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ConsoleKitSessionIface = <interface name='org.freedesktop.ConsoleKit.Session'> | ||||
| <signal name='Lock' /> | ||||
| <signal name='Unlock' /> | ||||
| </interface>; | ||||
|  | ||||
| const ConsoleKitSession = Gio.DBusProxy.makeProxyWrapper(ConsoleKitSessionIface); | ||||
| const ConsoleKitManager = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface); | ||||
|  | ||||
| function haveSystemd() { | ||||
|     return GLib.access("/run/systemd/seats", 0) >= 0; | ||||
| } | ||||
|  | ||||
| function versionCompare(required, reference) { | ||||
|     required = required.split('.'); | ||||
|     reference = reference.split('.'); | ||||
|  | ||||
|     for (let i = 0; i < required.length; i++) { | ||||
|         let requiredInt = parseInt(required[i]); | ||||
|         let referenceInt = parseInt(reference[i]); | ||||
|         if (requiredInt != referenceInt) | ||||
|             return requiredInt < referenceInt; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function canLock() { | ||||
|     try { | ||||
|         let params = GLib.Variant.new('(ss)', ['org.gnome.DisplayManager.Manager', 'Version']); | ||||
|         let result = Gio.DBus.system.call_sync('org.gnome.DisplayManager', | ||||
|                                                '/org/gnome/DisplayManager/Manager', | ||||
|                                                'org.freedesktop.DBus.Properties', | ||||
|                                                'Get', params, null, | ||||
|                                                Gio.DBusCallFlags.NONE, | ||||
|                                                -1, null); | ||||
|  | ||||
|         let version = result.deep_unpack()[0].deep_unpack(); | ||||
|         return versionCompare('3.5.91', version); | ||||
|     } catch(e) { | ||||
|         return false; | ||||
|     } | ||||
|     return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0; | ||||
| } | ||||
|  | ||||
| let _loginManager = null; | ||||
| @@ -138,23 +107,33 @@ const LoginManagerSystemd = new Lang.Class({ | ||||
|     // Having this function is a bit of a hack since the Systemd and ConsoleKit | ||||
|     // session objects have different interfaces - but in both cases there are | ||||
|     // Lock/Unlock signals, and that's all we count upon at the moment. | ||||
|     getCurrentSessionProxy: function(callback) { | ||||
|         if (this._currentSession) { | ||||
|             callback (this._currentSession); | ||||
|             return; | ||||
|     getCurrentSessionProxy: function() { | ||||
|         if (!this._currentSession) { | ||||
|             this._currentSession = new SystemdLoginSession(Gio.DBus.system, | ||||
|                                                            'org.freedesktop.login1', | ||||
|                                                            '/org/freedesktop/login1/session/' + | ||||
|                                                            GLib.getenv('XDG_SESSION_ID')); | ||||
|         } | ||||
|  | ||||
|         this._proxy.GetSessionRemote(GLib.getenv('XDG_SESSION_ID'), Lang.bind(this, | ||||
|             function(result, error) { | ||||
|                 if (error) { | ||||
|                     logError(error, 'Could not get a proxy for the current session'); | ||||
|                 } else { | ||||
|                     this._currentSession = new SystemdLoginSession(Gio.DBus.system, | ||||
|                                                                    'org.freedesktop.login1', | ||||
|                                                                    result[0]); | ||||
|                     callback(this._currentSession); | ||||
|                 } | ||||
|             })); | ||||
|         return this._currentSession; | ||||
|     }, | ||||
|  | ||||
|     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) { | ||||
| @@ -175,6 +154,14 @@ const LoginManagerSystemd = new Lang.Class({ | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     powerOff: function() { | ||||
|         this._proxy.PowerOffRemote(true); | ||||
|     }, | ||||
|  | ||||
|     reboot: function() { | ||||
|         this._proxy.RebootRemote(true); | ||||
|     }, | ||||
|  | ||||
|     suspend: function() { | ||||
|         this._proxy.SuspendRemote(true); | ||||
|     }, | ||||
| @@ -217,23 +204,33 @@ const LoginManagerConsoleKit = new Lang.Class({ | ||||
|     // Having this function is a bit of a hack since the Systemd and ConsoleKit | ||||
|     // session objects have different interfaces - but in both cases there are | ||||
|     // Lock/Unlock signals, and that's all we count upon at the moment. | ||||
|     getCurrentSessionProxy: function(callback) { | ||||
|         if (this._currentSession) { | ||||
|             callback (this._currentSession); | ||||
|             return; | ||||
|     getCurrentSessionProxy: function() { | ||||
|         if (!this._currentSession) { | ||||
|             let [currentSessionId] = this._proxy.GetCurrentSessionSync(); | ||||
|             this._currentSession = new ConsoleKitSession(Gio.DBus.system, | ||||
|                                                          'org.freedesktop.ConsoleKit', | ||||
|                                                          currentSessionId); | ||||
|         } | ||||
|  | ||||
|         this._proxy.GetCurrentSessionRemote(Lang.bind(this, | ||||
|             function(result, error) { | ||||
|                 if (error) { | ||||
|                     logError(error, 'Could not get a proxy for the current session'); | ||||
|                 } else { | ||||
|                     this._currentSession = new ConsoleKitSession(Gio.DBus.system, | ||||
|                                                                  'org.freedesktop.ConsoleKit', | ||||
|                                                                  result[0]); | ||||
|                     callback(this._currentSession); | ||||
|                 } | ||||
|             })); | ||||
|         return this._currentSession; | ||||
|     }, | ||||
|  | ||||
|     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) { | ||||
| @@ -244,6 +241,14 @@ const LoginManagerConsoleKit = new Lang.Class({ | ||||
|         asyncCallback([]); | ||||
|     }, | ||||
|  | ||||
|     powerOff: function() { | ||||
|         this._proxy.StopRemote(); | ||||
|     }, | ||||
|  | ||||
|     reboot: function() { | ||||
|         this._proxy.RestartRemote(); | ||||
|     }, | ||||
|  | ||||
|     suspend: function() { | ||||
|         this.emit('prepare-for-sleep', true); | ||||
|         this.emit('prepare-for-sleep', false); | ||||
|   | ||||
| @@ -92,41 +92,37 @@ function _findProviderForSid(sid) { | ||||
| // The following are not the complete interfaces, just the methods we need | ||||
| // (or may need in the future) | ||||
|  | ||||
| const ModemGsmNetworkInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager.Modem.Gsm.Network"> \ | ||||
| <method name="GetRegistrationInfo"> \ | ||||
|     <arg type="(uss)" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetSignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| <property name="AccessTechnology" type="u" access="read" /> \ | ||||
| <signal name="SignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </signal> \ | ||||
| <signal name="RegistrationInfo"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ModemGsmNetworkInterface = <interface name="org.freedesktop.ModemManager.Modem.Gsm.Network"> | ||||
| <method name="GetRegistrationInfo"> | ||||
|     <arg type="(uss)" direction="out" /> | ||||
| </method> | ||||
| <method name="GetSignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| <property name="AccessTechnology" type="u" access="read" /> | ||||
| <signal name="SignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </signal> | ||||
| <signal name="RegistrationInfo"> | ||||
|     <arg type="u" direction="out" /> | ||||
|     <arg type="s" direction="out" /> | ||||
|     <arg type="s" direction="out" /> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const ModemGsmNetworkProxy = Gio.DBusProxy.makeProxyWrapper(ModemGsmNetworkInterface); | ||||
|  | ||||
| const ModemCdmaInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager.Modem.Cdma"> \ | ||||
| <method name="GetSignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetServingSystem"> \ | ||||
|     <arg type="(usu)" direction="out" /> \ | ||||
| </method> \ | ||||
| <signal name="SignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ModemCdmaInterface = <interface name="org.freedesktop.ModemManager.Modem.Cdma"> | ||||
| <method name="GetSignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| <method name="GetServingSystem"> | ||||
|     <arg type="(usu)" direction="out" /> | ||||
| </method> | ||||
| <signal name="SignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const ModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(ModemCdmaInterface); | ||||
|  | ||||
| @@ -222,26 +218,20 @@ Signals.addSignalMethods(ModemCdma.prototype); | ||||
| // Support for the new ModemManager1 interface (MM >= 0.7) | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const BroadbandModemInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager1.Modem"> \ | ||||
| <property name="SignalQuality" type="(ub)" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const BroadbandModemInterface = <interface name="org.freedesktop.ModemManager1.Modem"> | ||||
| <property name="SignalQuality" type="(ub)" access="read" /> | ||||
| </interface>; | ||||
| const BroadbandModemProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemInterface); | ||||
|  | ||||
| const BroadbandModem3gppInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager1.Modem.Modem3gpp"> \ | ||||
| <property name="OperatorCode" type="s" access="read" /> \ | ||||
| <property name="OperatorName" type="s" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const BroadbandModem3gppInterface = <interface name="org.freedesktop.ModemManager1.Modem.Modem3gpp"> | ||||
| <property name="OperatorCode" type="s" access="read" /> | ||||
| <property name="OperatorName" type="s" access="read" /> | ||||
| </interface>; | ||||
| const BroadbandModem3gppProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModem3gppInterface); | ||||
|  | ||||
| const BroadbandModemCdmaInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager1.Modem.ModemCdma"> \ | ||||
| <property name="Sid" type="u" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const BroadbandModemCdmaInterface = <interface name="org.freedesktop.ModemManager1.Modem.ModemCdma"> | ||||
| <property name="Sid" type="u" access="read" /> | ||||
| </interface>; | ||||
| const BroadbandModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemCdmaInterface); | ||||
|  | ||||
| const BroadbandModem = new Lang.Class({ | ||||
|   | ||||
| @@ -1,259 +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 = '<node> \ | ||||
| <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> \ | ||||
| </node>'; | ||||
|  | ||||
| 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); | ||||
| @@ -1,119 +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 = '<node> \ | ||||
| <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> \ | ||||
| </node>'; | ||||
|  | ||||
| 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); | ||||
							
								
								
									
										151
									
								
								js/misc/util.js
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								js/misc/util.js
									
									
									
									
									
								
							| @@ -1,15 +1,10 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const SCROLL_TIME = 0.1; | ||||
|  | ||||
| // http://daringfireball.net/2010/07/improved_regex_for_matching_urls | ||||
| const _balancedParens = '\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))*\\)'; | ||||
| @@ -20,7 +15,7 @@ const _urlRegexp = new RegExp( | ||||
|     '(^|' + _leadingJunk + ')' + | ||||
|     '(' + | ||||
|         '(?:' + | ||||
|             '(?:http|https|ftp)://' +             // scheme:// | ||||
|             '[a-z][\\w-]+://' +                   // scheme:// | ||||
|             '|' + | ||||
|             'www\\d{0,3}[.]' +                    // www. | ||||
|             '|' + | ||||
| @@ -80,22 +75,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(0, -1); | ||||
|         app.launch([], context); | ||||
|     } catch(err) { | ||||
|         _handleSpawnError(argv[0], err); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // trySpawn: | ||||
| // @argv: an argv array | ||||
| // | ||||
| @@ -153,10 +132,35 @@ function trySpawnCommandLine(command_line) { | ||||
| } | ||||
|  | ||||
| function _handleSpawnError(command, err) { | ||||
|     let title = _("Execution of “%s” failed:").format(command); | ||||
|     let title = _("Execution of '%s' failed:").format(command); | ||||
|     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: | ||||
| // @array: an array or array-like object, already sorted | ||||
| //         according to @cmp | ||||
| @@ -207,91 +211,26 @@ function insertSorted(array, val, cmp) { | ||||
|     return pos; | ||||
| } | ||||
|  | ||||
| const CloseButton = new Lang.Class({ | ||||
|     Name: 'CloseButton', | ||||
|     Extends: St.Button, | ||||
| function makeCloseButton() { | ||||
|     let closeButton = new St.Button({ style_class: 'notification-close'}); | ||||
|  | ||||
|     _init: function(boxpointer) { | ||||
|         this.parent({ style_class: 'notification-close'}); | ||||
|     // This is a bit tricky. St.Bin has its own x-align/y-align properties | ||||
|     // that compete with Clutter's properties. This should be fixed for | ||||
|     // Clutter 2.0. Since St.Bin doesn't define its own setters, the | ||||
|     // setters are a workaround to get Clutter's version. | ||||
|     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 | ||||
|         // that compete with Clutter's properties. This should be fixed for | ||||
|         // Clutter 2.0. Since St.Bin doesn't define its own setters, the | ||||
|         // setters are a workaround to get Clutter's version. | ||||
|         this.set_x_align(Clutter.ActorAlign.END); | ||||
|         this.set_y_align(Clutter.ActorAlign.START); | ||||
|     // XXX Clutter 2.0 workaround: ClutterBinLayout needs expand | ||||
|     // to respect the alignments. | ||||
|     closeButton.set_x_expand(true); | ||||
|     closeButton.set_y_expand(true); | ||||
|  | ||||
|         // XXX Clutter 2.0 workaround: ClutterBinLayout needs expand | ||||
|         // to respect the alignments. | ||||
|         this.set_x_expand(true); | ||||
|         this.set_y_expand(true); | ||||
|     closeButton.connect('style-changed', function() { | ||||
|         let themeNode = closeButton.get_theme_node(); | ||||
|         closeButton.translation_x = themeNode.get_length('-shell-close-overlap-x'); | ||||
|         closeButton.translation_y = themeNode.get_length('-shell-close-overlap-y'); | ||||
|     }); | ||||
|  | ||||
|         this._boxPointer = boxpointer; | ||||
|         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) { | ||||
|     let adjustment = scrollView.vscroll.adjustment; | ||||
|     let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values(); | ||||
|  | ||||
|     let offset = 0; | ||||
|     let vfade = scrollView.get_effect("fade"); | ||||
|     if (vfade) | ||||
|         offset = vfade.vfade_offset; | ||||
|  | ||||
|     let box = actor.get_allocation_box(); | ||||
|     let y1 = box.y1, y2 = box.y2; | ||||
|  | ||||
|     let parent = actor.get_parent(); | ||||
|     while (parent != scrollView) { | ||||
|         if (!parent) | ||||
|             throw new Error("actor not in scroll view"); | ||||
|  | ||||
|         let box = parent.get_allocation_box(); | ||||
|         y1 += box.y1; | ||||
|         y2 += box.y1; | ||||
|         parent = parent.get_parent(); | ||||
|     } | ||||
|  | ||||
|     if (y1 < value + offset) | ||||
|         value = Math.max(0, y1 - offset); | ||||
|     else if (y2 > value + pageSize - offset) | ||||
|         value = Math.min(upper, y2 + offset - pageSize); | ||||
|     else | ||||
|         return; | ||||
|  | ||||
|     Tweener.addTween(adjustment, | ||||
|                      { value: value, | ||||
|                        time: SCROLL_TIME, | ||||
|                        transition: 'easeOutQuad' }); | ||||
|     return closeButton; | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,6 @@ | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| @@ -107,8 +106,6 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|  | ||||
|         this._switcherList = new AppSwitcher(apps, this); | ||||
|         this._items = this._switcherList.icons; | ||||
|         if (this._items.length == 0) | ||||
|             return false; | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
| @@ -235,13 +232,11 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _finish : function(timestamp) { | ||||
|         let appIcon = this._items[this._selectedIndex]; | ||||
|         if (this._currentWindow < 0) | ||||
|             appIcon.app.activate_window(appIcon.cachedWindows[0], timestamp); | ||||
|         else | ||||
|             Main.activateWindow(appIcon.cachedWindows[this._currentWindow], timestamp); | ||||
|  | ||||
|         this.parent(); | ||||
|  | ||||
|         let appIcon = this._items[this._selectedIndex]; | ||||
|         let window = this._currentWindow > 0 ? this._currentWindow : 0; | ||||
|         appIcon.app.activate_window(appIcon.cachedWindows[window], timestamp); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy : function() { | ||||
| @@ -313,7 +308,7 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|             this._createThumbnails(); | ||||
|         this._thumbnailTimeoutId = 0; | ||||
|         this._thumbnailsFocused = false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _destroyThumbnails : function() { | ||||
| @@ -358,13 +353,10 @@ const WindowSwitcherPopup = new Lang.Class({ | ||||
|     Name: 'WindowSwitcherPopup', | ||||
|     Extends: SwitcherPopup.SwitcherPopup, | ||||
|  | ||||
|     _init: function(items) { | ||||
|         this.parent(items); | ||||
|         this._settings = new Gio.Settings({ schema: 'org.gnome.shell.window-switcher' }); | ||||
|     }, | ||||
|  | ||||
|     _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); | ||||
|     }, | ||||
|  | ||||
| @@ -374,13 +366,9 @@ const WindowSwitcherPopup = new Lang.Class({ | ||||
|         if (windows.length == 0) | ||||
|             return false; | ||||
|  | ||||
|         let mode = this._settings.get_enum('app-icon-mode'); | ||||
|         this._switcherList = new WindowList(windows, mode); | ||||
|         this._switcherList = new WindowList(windows); | ||||
|         this._items = this._switcherList.icons; | ||||
|  | ||||
|         if (this._items.length == 0) | ||||
|             return false; | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
| @@ -407,9 +395,9 @@ const WindowSwitcherPopup = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _finish: function() { | ||||
|         Main.activateWindow(this._items[this._selectedIndex].window); | ||||
|  | ||||
|         this.parent(); | ||||
|  | ||||
|         Main.activateWindow(this._items[this._selectedIndex].window); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -446,11 +434,8 @@ const AppSwitcher = new Lang.Class({ | ||||
|         this._arrows = []; | ||||
|  | ||||
|         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, | ||||
|                                                      global.screen, workspace); | ||||
|                                                      global.screen, null); | ||||
|  | ||||
|         // Construct the AppIcons, add to the popup | ||||
|         for (let i = 0; i < apps.length; i++) { | ||||
| @@ -460,10 +445,7 @@ const AppSwitcher = new Lang.Class({ | ||||
|             appIcon.cachedWindows = allWindows.filter(function(w) { | ||||
|                 return windowTracker.get_window_app (w) == appIcon.app; | ||||
|             }); | ||||
|             if (appIcon.cachedWindows.length > 0) | ||||
|                 this._addIcon(appIcon); | ||||
|             else if (workspace == null) | ||||
|                 throw new Error('%s appears to be running, but doesn\'t have any windows'.format(appIcon.app.get_name())); | ||||
|             this._addIcon(appIcon); | ||||
|         } | ||||
|  | ||||
|         this._curApp = -1; | ||||
| @@ -549,7 +531,7 @@ const AppSwitcher = new Lang.Class({ | ||||
|                                                         Lang.bind(this, function () { | ||||
|                                                                             this._enterItem(index); | ||||
|                                                                             this._mouseTimeOutId = 0; | ||||
|                                                                             return GLib.SOURCE_REMOVE; | ||||
|                                                                             return false; | ||||
|                                                         })); | ||||
|         } else | ||||
|            this._itemEntered(index); | ||||
| @@ -674,7 +656,7 @@ const ThumbnailList = new Lang.Class({ | ||||
| const WindowIcon = new Lang.Class({ | ||||
|     Name: 'WindowIcon', | ||||
|  | ||||
|     _init: function(window, mode) { | ||||
|     _init: function(window) { | ||||
|         this.window = window; | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ style_class: 'alt-tab-app', | ||||
| @@ -692,7 +674,8 @@ const WindowIcon = new Lang.Class({ | ||||
|  | ||||
|         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: | ||||
|                 size = WINDOW_PREVIEW_SIZE; | ||||
|                 this._icon.add_actor(_createWindowClone(mutterWindow, WINDOW_PREVIEW_SIZE)); | ||||
| @@ -730,7 +713,7 @@ const WindowList = new Lang.Class({ | ||||
|     Name: 'WindowList', | ||||
|     Extends: SwitcherPopup.SwitcherList, | ||||
|  | ||||
|     _init : function(windows, mode) { | ||||
|     _init : function(windows) { | ||||
|         this.parent(true); | ||||
|  | ||||
|         this._label = new St.Label({ x_align: Clutter.ActorAlign.CENTER, | ||||
| @@ -742,7 +725,7 @@ const WindowList = new Lang.Class({ | ||||
|  | ||||
|         for (let i = 0; i < windows.length; i++) { | ||||
|             let win = windows[i]; | ||||
|             let icon = new WindowIcon(win, mode); | ||||
|             let icon = new WindowIcon(win); | ||||
|  | ||||
|             this.addItem(icon.actor, icon.label); | ||||
|             this.icons.push(icon); | ||||
|   | ||||
| @@ -1,85 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const GLib = imports.gi.GLib; | ||||
| 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 GLib.SOURCE_CONTINUE; | ||||
|     }, | ||||
|  | ||||
|     _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); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										1310
									
								
								js/ui/appDisplay.js
									
									
									
									
									
								
							
							
						
						
									
										1310
									
								
								js/ui/appDisplay.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -14,15 +14,15 @@ const AppFavorites = new Lang.Class({ | ||||
|     _init: function() { | ||||
|         this._favorites = {}; | ||||
|         global.settings.connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged)); | ||||
|         this.reload(); | ||||
|         this._reload(); | ||||
|     }, | ||||
|  | ||||
|     _onFavsChanged: function() { | ||||
|         this.reload(); | ||||
|         this._reload(); | ||||
|         this.emit('changed'); | ||||
|     }, | ||||
|  | ||||
|     reload: function() { | ||||
|     _reload: function() { | ||||
|         let ids = global.settings.get_strv(this.FAVORITE_APPS_KEY); | ||||
|         let appSys = Shell.AppSystem.get_default(); | ||||
|         let apps = ids.map(function (id) { | ||||
|   | ||||
| @@ -38,7 +38,6 @@ const BackgroundCache = new Lang.Class({ | ||||
|     _init: function() { | ||||
|        this._patterns = []; | ||||
|        this._images = []; | ||||
|        this._pendingFileLoads = []; | ||||
|        this._fileMonitors = {}; | ||||
|     }, | ||||
|  | ||||
| @@ -50,9 +49,11 @@ const BackgroundCache = new Lang.Class({ | ||||
|                                         effects: Meta.BackgroundEffects.NONE }); | ||||
|  | ||||
|         let content = null; | ||||
|  | ||||
|         let candidateContent = null; | ||||
|         for (let i = 0; i < this._patterns.length; i++) { | ||||
|             if (!this._patterns[i]) | ||||
|                 continue; | ||||
|  | ||||
|             if (this._patterns[i].get_shading() != params.shadingType) | ||||
|                 continue; | ||||
|  | ||||
| @@ -83,9 +84,10 @@ const BackgroundCache = new Lang.Class({ | ||||
|             } else { | ||||
|                 content.load_gradient(params.shadingType, params.color, params.secondColor); | ||||
|             } | ||||
|  | ||||
|             this._patterns.push(content); | ||||
|         } | ||||
|  | ||||
|         this._patterns.push(content); | ||||
|         return content; | ||||
|     }, | ||||
|  | ||||
| @@ -113,9 +115,9 @@ const BackgroundCache = new Lang.Class({ | ||||
|  | ||||
|     _removeContent: function(contentList, content) { | ||||
|         let index = contentList.indexOf(content); | ||||
|         if (index < 0) | ||||
|             throw new Error("Trying to remove invalid content: " + content); | ||||
|         contentList.splice(index, 1); | ||||
|  | ||||
|         if (index >= 0) | ||||
|             contentList.splice(index, 1); | ||||
|     }, | ||||
|  | ||||
|     removePatternContent: function(content) { | ||||
| @@ -123,86 +125,9 @@ const BackgroundCache = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     removeImageContent: function(content) { | ||||
|         let filename = content.get_filename(); | ||||
|  | ||||
|         let hasOtherUsers = this._images.some(function(content) { return filename == content.get_filename(); }); | ||||
|         if (!hasOtherUsers) | ||||
|             delete this._fileMonitors[filename]; | ||||
|  | ||||
|         this._removeContent(this._images, content); | ||||
|     }, | ||||
|  | ||||
|     _loadImageContent: function(params) { | ||||
|         params = Params.parse(params, { monitorIndex: 0, | ||||
|                                         style: null, | ||||
|                                         filename: null, | ||||
|                                         effects: Meta.BackgroundEffects.NONE, | ||||
|                                         cancellable: null, | ||||
|                                         onFinished: null }); | ||||
|  | ||||
|         for (let i = 0; i < this._pendingFileLoads.length; i++) { | ||||
|             if (this._pendingFileLoads[i].filename == params.filename && | ||||
|                 this._pendingFileLoads[i].style == params.style) { | ||||
|                 this._pendingFileLoads[i].callers.push({ shouldCopy: true, | ||||
|                                                          monitorIndex: params.monitorIndex, | ||||
|                                                          effects: params.effects, | ||||
|                                                          onFinished: params.onFinished }); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._pendingFileLoads.push({ filename: params.filename, | ||||
|                                       style: params.style, | ||||
|                                       callers: [{ shouldCopy: false, | ||||
|                                                   monitorIndex: params.monitorIndex, | ||||
|                                                   effects: params.effects, | ||||
|                                                   onFinished: params.onFinished }] }); | ||||
|  | ||||
|         let content = new Meta.Background({ meta_screen: global.screen, | ||||
|                                             monitor: params.monitorIndex, | ||||
|                                             effects: params.effects }); | ||||
|  | ||||
|         content.load_file_async(params.filename, | ||||
|                                 params.style, | ||||
|                                 params.cancellable, | ||||
|                                 Lang.bind(this, | ||||
|                                           function(object, result) { | ||||
|                                               try { | ||||
|                                                   content.load_file_finish(result); | ||||
|  | ||||
|                                                   this._monitorFile(params.filename); | ||||
|                                                   this._images.push(content); | ||||
|                                               } catch(e) { | ||||
|                                                   content = null; | ||||
|                                               } | ||||
|  | ||||
|                                               for (let i = 0; i < this._pendingFileLoads.length; i++) { | ||||
|                                                   let pendingLoad = this._pendingFileLoads[i]; | ||||
|                                                   if (pendingLoad.filename != params.filename || | ||||
|                                                       pendingLoad.style != params.style) | ||||
|                                                       continue; | ||||
|  | ||||
|                                                   for (let j = 0; j < pendingLoad.callers.length; j++) { | ||||
|                                                       if (pendingLoad.callers[j].onFinished) { | ||||
|                                                           let newContent; | ||||
|  | ||||
|                                                           if (content && pendingLoad.callers[j].shouldCopy) { | ||||
|                                                               newContent = content.copy(pendingLoad.callers[j].monitorIndex, | ||||
|                                                                                         pendingLoad.callers[j].effects); | ||||
|                                                               this._images.push(newContent); | ||||
|                                                           } else { | ||||
|                                                               newContent = content; | ||||
|                                                           } | ||||
|  | ||||
|                                                           pendingLoad.callers[j].onFinished(newContent); | ||||
|                                                       } | ||||
|                                                   } | ||||
|  | ||||
|                                                   this._pendingFileLoads.splice(i, 1); | ||||
|                                               } | ||||
|                                           })); | ||||
|     }, | ||||
|  | ||||
|     getImageContent: function(params) { | ||||
|         params = Params.parse(params, { monitorIndex: 0, | ||||
|                                         style: null, | ||||
| @@ -212,9 +137,11 @@ const BackgroundCache = new Lang.Class({ | ||||
|                                         onFinished: null }); | ||||
|  | ||||
|         let content = null; | ||||
|  | ||||
|         let candidateContent = null; | ||||
|         for (let i = 0; i < this._images.length; i++) { | ||||
|             if (!this._images[i]) | ||||
|                 continue; | ||||
|  | ||||
|             if (this._images[i].get_style() != params.style) | ||||
|                 continue; | ||||
|  | ||||
| @@ -222,7 +149,7 @@ const BackgroundCache = new Lang.Class({ | ||||
|                 continue; | ||||
|  | ||||
|             if (params.style == GDesktopEnums.BackgroundStyle.SPANNED && | ||||
|                 this._images[i].monitor != params.monitorIndex) | ||||
|                 this._images[i].monitor_index != this._monitorIndex) | ||||
|                 continue; | ||||
|  | ||||
|             candidateContent = this._images[i]; | ||||
| @@ -238,19 +165,31 @@ const BackgroundCache = new Lang.Class({ | ||||
|  | ||||
|             if (params.cancellable && params.cancellable.is_cancelled()) | ||||
|                 content = null; | ||||
|             else | ||||
|                 this._images.push(content); | ||||
|  | ||||
|             if (params.onFinished) | ||||
|                 params.onFinished(content); | ||||
|         } else { | ||||
|             this._loadImageContent({ filename: params.filename, | ||||
|                                      style: params.style, | ||||
|                                      effects: params.effects, | ||||
|                                      monitorIndex: params.monitorIndex, | ||||
|                                      cancellable: params.cancellable, | ||||
|                                      onFinished: params.onFinished }); | ||||
|             content = new Meta.Background({ meta_screen: global.screen, | ||||
|                                             monitor: params.monitorIndex, | ||||
|                                             effects: params.effects }); | ||||
|  | ||||
|             content.load_file_async(params.filename, | ||||
|                                     params.style, | ||||
|                                     params.cancellable, | ||||
|                                     Lang.bind(this, | ||||
|                                               function(object, result) { | ||||
|                                                   try { | ||||
|                                                       content.load_file_finish(result); | ||||
|  | ||||
|                                                       this._monitorFile(params.filename); | ||||
|                                                       this._images.push(content); | ||||
|                                                   } catch(e) { | ||||
|                                                        content = null; | ||||
|                                                   } | ||||
|  | ||||
|                                                   if (params.onFinished) | ||||
|                                                       params.onFinished(content); | ||||
|                                               })); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -262,7 +201,6 @@ const BackgroundCache = new Lang.Class({ | ||||
|             if (params.onLoaded) { | ||||
|                 GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|                     params.onLoaded(this._animation); | ||||
|                     return GLib.SOURCE_REMOVE; | ||||
|                 })); | ||||
|             } | ||||
|         } | ||||
| @@ -277,7 +215,6 @@ const BackgroundCache = new Lang.Class({ | ||||
|                            if (params.onLoaded) { | ||||
|                                GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|                                    params.onLoaded(this._animation); | ||||
|                                    return GLib.SOURCE_REMOVE; | ||||
|                                })); | ||||
|                            } | ||||
|                        })); | ||||
| @@ -297,15 +234,14 @@ const Background = new Lang.Class({ | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { monitorIndex: 0, | ||||
|                                         layoutManager: Main.layoutManager, | ||||
|                                         effects: Meta.BackgroundEffects.NONE, | ||||
|                                         settings: null }); | ||||
|                                         effects: Meta.BackgroundEffects.NONE }); | ||||
|         this.actor = new Meta.BackgroundGroup(); | ||||
|         this.actor._delegate = this; | ||||
|  | ||||
|         this._destroySignalId = this.actor.connect('destroy', | ||||
|                                                    Lang.bind(this, this._destroy)); | ||||
|  | ||||
|         this._settings = params.settings; | ||||
|         this._settings = new Gio.Settings({ schema: BACKGROUND_SCHEMA }); | ||||
|         this._monitorIndex = params.monitorIndex; | ||||
|         this._layoutManager = params.layoutManager; | ||||
|         this._effects = params.effects; | ||||
| @@ -317,12 +253,13 @@ const Background = new Lang.Class({ | ||||
|  | ||||
|         this._brightness = 1.0; | ||||
|         this._vignetteSharpness = 0.2; | ||||
|         this._saturation = 1.0; | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|         this.isLoaded = false; | ||||
|  | ||||
|         this._settingsChangedSignalId = this._settings.connect('changed', Lang.bind(this, function() { | ||||
|                                             this.emit('changed'); | ||||
|                                         })); | ||||
|         this._settings.connect('changed', Lang.bind(this, function() { | ||||
|                                    this.emit('changed'); | ||||
|                                })); | ||||
|  | ||||
|         this._load(); | ||||
|     }, | ||||
| @@ -330,9 +267,9 @@ const Background = new Lang.Class({ | ||||
|     _destroy: function() { | ||||
|         this._cancellable.cancel(); | ||||
|  | ||||
|         if (this._updateAnimationTimeoutId) { | ||||
|             GLib.source_remove (this._updateAnimationTimeoutId); | ||||
|             this._updateAnimationTimeoutId = 0; | ||||
|         if (this._animationUpdateTimeoutId) { | ||||
|             GLib.source_remove (this._animationUpdateTimeoutId); | ||||
|             this._animationUpdateTimeoutId = 0 | ||||
|         } | ||||
|  | ||||
|         let i; | ||||
| @@ -363,10 +300,6 @@ const Background = new Lang.Class({ | ||||
|  | ||||
|         this.actor.disconnect(this._destroySignalId); | ||||
|         this._destroySignalId = 0; | ||||
|  | ||||
|         if (this._settingsChangedSignalId != 0) | ||||
|             this._settings.disconnect(this._settingsChangedSignalId); | ||||
|         this._settingsChangedSignalId = 0; | ||||
|     }, | ||||
|  | ||||
|     _setLoaded: function() { | ||||
| @@ -377,7 +310,7 @@ const Background = new Lang.Class({ | ||||
|  | ||||
|         GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|             this.emit('loaded'); | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|             return false; | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
| @@ -416,45 +349,45 @@ const Background = new Lang.Class({ | ||||
|         this._fileWatches[filename] = signalId; | ||||
|     }, | ||||
|  | ||||
|     _ensureImage: function(index) { | ||||
|         if (this._images[index]) | ||||
|             return; | ||||
|  | ||||
|         let actor = new Meta.BackgroundActor(); | ||||
|  | ||||
|         // The background pattern is the first actor in | ||||
|         // the group, and all images should be above that. | ||||
|         this.actor.insert_child_at_index(actor, index + 1); | ||||
|         this._images[index] = actor; | ||||
|     }, | ||||
|  | ||||
|     _updateImage: function(index, content, filename) { | ||||
|     _addImage: function(content, index, filename) { | ||||
|         content.saturation = this._saturation; | ||||
|         content.brightness = this._brightness; | ||||
|         content.vignette_sharpness = this._vignetteSharpness; | ||||
|  | ||||
|         let image = this._images[index]; | ||||
|         if (image.content) | ||||
|             this._cache.removeImageContent(content); | ||||
|         image.content = content; | ||||
|         let actor = new Meta.BackgroundActor(); | ||||
|         actor.content = content; | ||||
|         this.actor.add_child(actor); | ||||
|  | ||||
|         this._images[index] = actor; | ||||
|         this._watchCacheFile(filename); | ||||
|     }, | ||||
|  | ||||
|     _updateImage: function(content, index, filename) { | ||||
|         content.saturation = this._saturation; | ||||
|         content.brightness = this._brightness; | ||||
|         content.vignette_sharpness = this._vignetteSharpness; | ||||
|  | ||||
|         this._images[index].content = content; | ||||
|         this._watchCacheFile(filename); | ||||
|     }, | ||||
|  | ||||
|     _updateAnimationProgress: function() { | ||||
|         if (this._images[1]) | ||||
|         if (this._images[1]) { | ||||
|             this._images[1].raise_top(); | ||||
|             this._images[1].opacity = this._animation.transitionProgress * 255; | ||||
|         } | ||||
|  | ||||
|         this._queueUpdateAnimation(); | ||||
|         this._queueAnimationUpdate(); | ||||
|     }, | ||||
|  | ||||
|     _updateAnimation: function() { | ||||
|         this._updateAnimationTimeoutId = 0; | ||||
|         this._animationUpdateTimeoutId = 0; | ||||
|  | ||||
|         this._animation.update(this._layoutManager.monitors[this._monitorIndex]); | ||||
|         let files = this._animation.keyFrameFiles; | ||||
|         let files = this._animation.getKeyFrameFiles(this._layoutManager.monitors[this._monitorIndex]); | ||||
|  | ||||
|         if (files.length == 0) { | ||||
|         if (!files) { | ||||
|             this._setLoaded(); | ||||
|             this._queueUpdateAnimation(); | ||||
|             this._queueAnimationUpdate(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @@ -473,7 +406,7 @@ const Background = new Lang.Class({ | ||||
|                                           style: this._style, | ||||
|                                           filename: files[i], | ||||
|                                           cancellable: this._cancellable, | ||||
|                                           onFinished: Lang.bind(this, function(content, i) { | ||||
|                                           onFinished: Lang.bind(this, function(content) { | ||||
|                                               numPendingImages--; | ||||
|  | ||||
|                                               if (!content) { | ||||
| @@ -483,43 +416,39 @@ const Background = new Lang.Class({ | ||||
|                                                   return; | ||||
|                                               } | ||||
|  | ||||
|                                               this._ensureImage(i); | ||||
|                                               this._updateImage(i, content, files[i]); | ||||
|                                               if (!this._images[i]) { | ||||
|                                                   this._addImage(content, i, files[i]); | ||||
|                                               } else { | ||||
|                                                   this._updateImage(content, i, files[i]); | ||||
|                                               } | ||||
|  | ||||
|                                               if (numPendingImages == 0) { | ||||
|                                                   this._setLoaded(); | ||||
|                                                   this._updateAnimationProgress(); | ||||
|                                               } | ||||
|                                           }, i) | ||||
|                                           }) | ||||
|                                         }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _queueUpdateAnimation: function() { | ||||
|         if (this._updateAnimationTimeoutId != 0) | ||||
|     _queueAnimationUpdate: function() { | ||||
|         if (this._animationUpdateTimeoutId != 0) | ||||
|             return; | ||||
|  | ||||
|         if (!this._cancellable || this._cancellable.is_cancelled()) | ||||
|             return; | ||||
|  | ||||
|         if (!this._animation.transitionDuration) | ||||
|         if (!this._animation.duration) | ||||
|             return; | ||||
|  | ||||
|         let nSteps = 255 / ANIMATION_OPACITY_STEP_INCREMENT; | ||||
|         let timePerStep = (this._animation.transitionDuration * 1000) / nSteps; | ||||
|  | ||||
|         let interval = Math.max(ANIMATION_MIN_WAKEUP_INTERVAL * 1000, | ||||
|                                 timePerStep); | ||||
|  | ||||
|         if (interval > GLib.MAXUINT32) | ||||
|             return; | ||||
|  | ||||
|         this._updateAnimationTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, | ||||
|                                 ANIMATION_OPACITY_STEP_INCREMENT / this._animation.duration); | ||||
|         this._animationUpdateTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, | ||||
|                                                       interval, | ||||
|                                                       Lang.bind(this, function() { | ||||
|                                                                     this._updateAnimationTimeoutId = 0; | ||||
|                                                                     this._animationUpdateTimeoutId = 0; | ||||
|                                                                     this._updateAnimation(); | ||||
|                                                                     return GLib.SOURCE_REMOVE; | ||||
|                                                                     return false; | ||||
|                                                                 })); | ||||
|     }, | ||||
|  | ||||
| @@ -539,33 +468,30 @@ const Background = new Lang.Class({ | ||||
|                                            }); | ||||
|     }, | ||||
|  | ||||
|     _loadImage: function(filename) { | ||||
|     _loadFile: function(filename) { | ||||
|         this._cache.getImageContent({ monitorIndex: this._monitorIndex, | ||||
|                                       effects: this._effects, | ||||
|                                       style: this._style, | ||||
|                                       filename: filename, | ||||
|                                       cancellable: this._cancellable, | ||||
|                                       onFinished: Lang.bind(this, function(content) { | ||||
|                                           if (content) { | ||||
|                                               this._ensureImage(0); | ||||
|                                               this._updateImage(0, content, filename); | ||||
|                                           if (!content) { | ||||
|                                               if (!this._cancellable.is_cancelled()) | ||||
|                                                   this._loadAnimation(filename); | ||||
|                                               return; | ||||
|                                           } | ||||
|  | ||||
|                                           this._addImage(content, 0, filename); | ||||
|                                           this._setLoaded(); | ||||
|                                       }) | ||||
|                                     }); | ||||
|     }, | ||||
|  | ||||
|     _loadFile: function(filename) { | ||||
|         if (filename.endsWith('.xml')) | ||||
|             this._loadAnimation(filename); | ||||
|         else | ||||
|             this._loadImage(filename); | ||||
|     }, | ||||
|  | ||||
|     _load: function () { | ||||
|         this._cache = getBackgroundCache(); | ||||
|  | ||||
|         this._loadPattern(); | ||||
|         this._loadPattern(this._cache); | ||||
|  | ||||
|         this._style = this._settings.get_enum(BACKGROUND_STYLE_KEY); | ||||
|         if (this._style == GDesktopEnums.BackgroundStyle.NONE) { | ||||
| @@ -574,20 +500,29 @@ const Background = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         let uri = this._settings.get_string(PICTURE_URI_KEY); | ||||
|         let filename; | ||||
|         if (GLib.uri_parse_scheme(uri) != null) | ||||
|             filename = Gio.File.new_for_uri(uri).get_path(); | ||||
|         else | ||||
|             filename = uri; | ||||
|  | ||||
|         if (!filename) { | ||||
|             this._setLoaded(); | ||||
|             return; | ||||
|         } | ||||
|         let filename = Gio.File.new_for_uri(uri).get_path(); | ||||
|  | ||||
|         this._loadFile(filename); | ||||
|     }, | ||||
|  | ||||
|     get saturation() { | ||||
|         return this._saturation; | ||||
|     }, | ||||
|  | ||||
|     set saturation(saturation) { | ||||
|         this._saturation = saturation; | ||||
|  | ||||
|         if (this._pattern && this._pattern.content) | ||||
|             this._pattern.content.saturation = saturation; | ||||
|  | ||||
|         let keys = Object.keys(this._images); | ||||
|         for (let i = 0; i < keys.length; i++) { | ||||
|             let image = this._images[keys[i]]; | ||||
|             if (image && image.content) | ||||
|                 image.content.saturation = saturation; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     get brightness() { | ||||
|         return this._brightness; | ||||
|     }, | ||||
| @@ -639,13 +574,7 @@ const SystemBackground = new Lang.Class({ | ||||
|                                           this.emit('loaded'); | ||||
|                                       }) | ||||
|                                     }); | ||||
|  | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy: function() { | ||||
|         this._cache.removeImageContent(this.actor.content); | ||||
|     }, | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(SystemBackground.prototype); | ||||
|  | ||||
| @@ -656,9 +585,9 @@ const Animation = new Lang.Class({ | ||||
|         params = Params.parse(params, { filename: null }); | ||||
|  | ||||
|         this.filename = params.filename; | ||||
|         this.keyFrameFiles = []; | ||||
|         this._keyFrames = []; | ||||
|         this.duration = 0.0; | ||||
|         this.transitionProgress = 0.0; | ||||
|         this.transitionDuration = 0.0; | ||||
|         this.loaded = false; | ||||
|     }, | ||||
|  | ||||
| @@ -670,31 +599,33 @@ const Animation = new Lang.Class({ | ||||
|         this._show.load_async(null, | ||||
|                               Lang.bind(this, | ||||
|                                         function(object, result) { | ||||
|                                             this.duration = this._show.get_total_duration(); | ||||
|                                             this.loaded = true; | ||||
|                                             if (callback) | ||||
|                                                 callback(); | ||||
|                                         })); | ||||
|     }, | ||||
|  | ||||
|     update: function(monitor) { | ||||
|         this.keyFrameFiles = []; | ||||
|  | ||||
|     getKeyFrameFiles: function(monitor) { | ||||
|         if (!this._show) | ||||
|             return; | ||||
|             return null; | ||||
|  | ||||
|         if (this._show.get_num_slides() < 1) | ||||
|             return; | ||||
|             return null; | ||||
|  | ||||
|         let [progress, duration, isFixed, file1, file2] = this._show.get_current_slide(monitor.width, monitor.height); | ||||
|  | ||||
|         this.transitionDuration = duration; | ||||
|         this.transitionProgress = progress; | ||||
|  | ||||
|         let files = []; | ||||
|  | ||||
|         if (file1) | ||||
|             this.keyFrameFiles.push(file1); | ||||
|             files.push(file1); | ||||
|  | ||||
|         if (file2) | ||||
|             this.keyFrameFiles.push(file2); | ||||
|             files.push(file2); | ||||
|  | ||||
|         return files; | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(Animation.prototype); | ||||
| @@ -707,10 +638,8 @@ const BackgroundManager = new Lang.Class({ | ||||
|                                         layoutManager: Main.layoutManager, | ||||
|                                         monitorIndex: null, | ||||
|                                         effects: Meta.BackgroundEffects.NONE, | ||||
|                                         controlPosition: true, | ||||
|                                         settingsSchema: BACKGROUND_SCHEMA }); | ||||
|                                         controlPosition: true }); | ||||
|  | ||||
|         this._settings = new Gio.Settings({ schema: params.settingsSchema }); | ||||
|         this._container = params.container; | ||||
|         this._layoutManager = params.layoutManager; | ||||
|         this._effects = params.effects; | ||||
| @@ -719,9 +648,17 @@ const BackgroundManager = new Lang.Class({ | ||||
|  | ||||
|         this.background = this._createBackground(); | ||||
|         this._newBackground = null; | ||||
|         this._loadedSignalId = 0; | ||||
|         this._changedSignalId = 0; | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         if (this._loadedSignalId) | ||||
|             this._newBackground.disconnect(this._loadedSignalId); | ||||
|  | ||||
|         if (this._changedSignalId) | ||||
|             this.background.disconnect(this._changedSignalId); | ||||
|  | ||||
|         if (this._newBackground) { | ||||
|             this._newBackground.actor.destroy(); | ||||
|             this._newBackground = null; | ||||
| @@ -733,35 +670,29 @@ const BackgroundManager = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateBackground: function() { | ||||
|         let newBackground = this._createBackground(); | ||||
|         newBackground.vignetteSharpness = this.background.vignetteSharpness; | ||||
|         newBackground.brightness = this.background.brightness; | ||||
|         newBackground.visible = this.background.visible; | ||||
|     _updateBackground: function(background, monitorIndex) { | ||||
|         let newBackground = this._createBackground(monitorIndex); | ||||
|         newBackground.vignetteSharpness = background.vignetteSharpness; | ||||
|         newBackground.brightness = background.brightness; | ||||
|         newBackground.saturation = background.saturation; | ||||
|         newBackground.visible = background.visible; | ||||
|  | ||||
|         newBackground.loadedSignalId = newBackground.connect('loaded', | ||||
|         let signalId = newBackground.connect('loaded', | ||||
|             Lang.bind(this, function() { | ||||
|                 newBackground.disconnect(newBackground.loadedSignalId); | ||||
|                 newBackground.loadedSignalId = 0; | ||||
|                 Tweener.addTween(this.background.actor, | ||||
|                 newBackground.disconnect(signalId); | ||||
|                 Tweener.addTween(background.actor, | ||||
|                                  { opacity: 0, | ||||
|                                    time: FADE_ANIMATION_TIME, | ||||
|                                    transition: 'easeOutQuad', | ||||
|                                    onComplete: Lang.bind(this, function() { | ||||
|                                        if (this._newBackground != newBackground) { | ||||
|                                            /* Not interesting, we queued another load */ | ||||
|                                            newBackground.actor.destroy(); | ||||
|                                            return; | ||||
|                                        } | ||||
|  | ||||
|                                        this.background.actor.destroy(); | ||||
|                                        this.background = newBackground; | ||||
|                                        this._newBackground = null; | ||||
|  | ||||
|                                        background.actor.destroy(); | ||||
|                                        this.emit('changed'); | ||||
|                                    }) | ||||
|                                  }); | ||||
|         })); | ||||
|         this._loadedSignalId = signalId; | ||||
|  | ||||
|         this._newBackground = newBackground; | ||||
|     }, | ||||
| @@ -769,8 +700,7 @@ const BackgroundManager = new Lang.Class({ | ||||
|     _createBackground: function() { | ||||
|         let background = new Background({ monitorIndex: this._monitorIndex, | ||||
|                                           layoutManager: this._layoutManager, | ||||
|                                           effects: this._effects, | ||||
|                                           settings: this._settings }); | ||||
|                                           effects: this._effects }); | ||||
|         this._container.add_child(background.actor); | ||||
|  | ||||
|         let monitor = this._layoutManager.monitors[this._monitorIndex]; | ||||
| @@ -781,19 +711,12 @@ const BackgroundManager = new Lang.Class({ | ||||
|             background.actor.lower_bottom(); | ||||
|         } | ||||
|  | ||||
|         background.changeSignalId = background.connect('changed', Lang.bind(this, function() { | ||||
|             background.disconnect(background.changeSignalId); | ||||
|             background.changeSignalId = 0; | ||||
|             this._updateBackground(); | ||||
|         let signalId = background.connect('changed', Lang.bind(this, function() { | ||||
|             background.disconnect(signalId); | ||||
|             this._updateBackground(background, this._monitorIndex); | ||||
|         })); | ||||
|  | ||||
|         background.actor.connect('destroy', Lang.bind(this, function() { | ||||
|             if (background.changeSignalId) | ||||
|                 background.disconnect(background.changeSignalId); | ||||
|  | ||||
|             if (background.loadedSignalId) | ||||
|                 background.disconnect(background.loadedSignalId); | ||||
|         })); | ||||
|         this._changedSignalId = signalId; | ||||
|  | ||||
|         return background; | ||||
|     }, | ||||
|   | ||||
| @@ -13,8 +13,8 @@ const BackgroundMenu = new Lang.Class({ | ||||
|     Name: 'BackgroundMenu', | ||||
|     Extends: PopupMenu.PopupMenu, | ||||
|  | ||||
|     _init: function(layoutManager) { | ||||
|         this.parent(layoutManager.dummyCursor, 0, St.Side.TOP); | ||||
|     _init: function(source) { | ||||
|         this.parent(source, 0, St.Side.TOP); | ||||
|  | ||||
|         this.addSettingsAction(_("Settings"), 'gnome-control-center.desktop'); | ||||
|         this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
| @@ -22,20 +22,23 @@ const BackgroundMenu = new Lang.Class({ | ||||
|  | ||||
|         this.actor.add_style_class_name('background-menu'); | ||||
|  | ||||
|         layoutManager.uiGroup.add_actor(this.actor); | ||||
|         Main.uiGroup.add_actor(this.actor); | ||||
|         this.actor.hide(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| function addBackgroundMenu(actor, layoutManager) { | ||||
| function addBackgroundMenu(actor) { | ||||
|     let cursor = new St.Bin({ opacity: 0 }); | ||||
|     Main.uiGroup.add_actor(cursor); | ||||
|  | ||||
|     actor.reactive = true; | ||||
|     actor._backgroundMenu = new BackgroundMenu(layoutManager); | ||||
|     actor._backgroundMenu = new BackgroundMenu(cursor); | ||||
|     actor._backgroundManager = new PopupMenu.PopupMenuManager({ actor: actor }); | ||||
|     actor._backgroundManager.addMenu(actor._backgroundMenu); | ||||
|  | ||||
|     function openMenu() { | ||||
|         let [x, y] = global.get_pointer(); | ||||
|         Main.layoutManager.setDummyCursorPosition(x, y); | ||||
|         cursor.set_position(x, y); | ||||
|         actor._backgroundMenu.open(BoxPointer.PopupAnimation.NONE); | ||||
|     } | ||||
|  | ||||
| @@ -43,10 +46,8 @@ function addBackgroundMenu(actor, layoutManager) { | ||||
|     clickAction.connect('long-press', function(action, actor, state) { | ||||
|         if (state == Clutter.LongPressState.QUERY) | ||||
|             return action.get_button() == 1 && !actor._backgroundMenu.isOpen; | ||||
|         if (state == Clutter.LongPressState.ACTIVATE) { | ||||
|         if (state == Clutter.LongPressState.ACTIVATE) | ||||
|             openMenu(); | ||||
|             actor._backgroundManager.ignoreRelease(); | ||||
|         } | ||||
|         return true; | ||||
|     }); | ||||
|     clickAction.connect('clicked', function(action) { | ||||
| @@ -54,10 +55,4 @@ function addBackgroundMenu(actor, layoutManager) { | ||||
|             openMenu(); | ||||
|     }); | ||||
|     actor.add_action(clickAction); | ||||
|  | ||||
|     actor.connect('destroy', function() { | ||||
|         actor._backgroundMenu.destroy(); | ||||
|         actor._backgroundMenu = null; | ||||
|         actor._backgroundManager = null; | ||||
|     }); | ||||
| } | ||||
|   | ||||
| @@ -3,9 +3,8 @@ | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const Tweener = imports.ui.tweener; | ||||
| @@ -62,14 +61,10 @@ const BoxPointer = new Lang.Class({ | ||||
|         this._muteInput(); | ||||
|     }, | ||||
|  | ||||
|     get arrowSide() { | ||||
|         return this._arrowSide; | ||||
|     }, | ||||
|  | ||||
|     _muteInput: function() { | ||||
|         if (this._capturedEventId == 0) | ||||
|             this._capturedEventId = this.actor.connect('captured-event', | ||||
|                                                        function() { return Clutter.EVENT_STOP; }); | ||||
|                                                        function() { return true; }); | ||||
|     }, | ||||
|  | ||||
|     _unmuteInput: function() { | ||||
| @@ -121,9 +116,6 @@ const BoxPointer = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     hide: function(animate, onComplete) { | ||||
|         if (!this.actor.visible) | ||||
|             return; | ||||
|  | ||||
|         let xOffset = 0; | ||||
|         let yOffset = 0; | ||||
|         let themeNode = this.actor.get_theme_node(); | ||||
| @@ -188,9 +180,7 @@ const BoxPointer = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _getPreferredHeight: function(actor, forWidth, alloc) { | ||||
|         let themeNode = this.actor.get_theme_node(); | ||||
|         let borderWidth = themeNode.get_length('-arrow-border-width'); | ||||
|         let [minSize, naturalSize] = this.bin.get_preferred_height(forWidth - 2 * borderWidth); | ||||
|         let [minSize, naturalSize] = this.bin.get_preferred_height(forWidth); | ||||
|         alloc.min_size = minSize; | ||||
|         alloc.natural_size = naturalSize; | ||||
|         this._adjustAllocationForArrow(false, alloc); | ||||
| @@ -287,40 +277,38 @@ const BoxPointer = new Lang.Class({ | ||||
|         let skipBottomLeft = false; | ||||
|         let skipBottomRight = false; | ||||
|  | ||||
|         if (rise) { | ||||
|             switch (this._arrowSide) { | ||||
|             case St.Side.TOP: | ||||
|                 if (this._arrowOrigin == x1) | ||||
|                     skipTopLeft = true; | ||||
|                 else if (this._arrowOrigin == x2) | ||||
|                     skipTopRight = true; | ||||
|                 break; | ||||
|         switch (this._arrowSide) { | ||||
|         case St.Side.TOP: | ||||
|             if (this._arrowOrigin == x1) | ||||
|                 skipTopLeft = true; | ||||
|             else if (this._arrowOrigin == x2) | ||||
|                 skipTopRight = true; | ||||
|             break; | ||||
|  | ||||
|             case St.Side.RIGHT: | ||||
|                 if (this._arrowOrigin == y1) | ||||
|                     skipTopRight = true; | ||||
|                 else if (this._arrowOrigin == y2) | ||||
|                     skipBottomRight = true; | ||||
|                 break; | ||||
|         case St.Side.RIGHT: | ||||
|             if (this._arrowOrigin == y1) | ||||
|                 skipTopRight = true; | ||||
|             else if (this._arrowOrigin == y2) | ||||
|                 skipBottomRight = true; | ||||
|             break; | ||||
|  | ||||
|             case St.Side.BOTTOM: | ||||
|                 if (this._arrowOrigin == x1) | ||||
|                     skipBottomLeft = true; | ||||
|                 else if (this._arrowOrigin == x2) | ||||
|                     skipBottomRight = true; | ||||
|                 break; | ||||
|         case St.Side.BOTTOM: | ||||
|             if (this._arrowOrigin == x1) | ||||
|                 skipBottomLeft = true; | ||||
|             else if (this._arrowOrigin == x2) | ||||
|                 skipBottomRight = true; | ||||
|             break; | ||||
|  | ||||
|             case St.Side.LEFT: | ||||
|                 if (this._arrowOrigin == y1) | ||||
|                     skipTopLeft = true; | ||||
|                 else if (this._arrowOrigin == y2) | ||||
|                     skipBottomLeft = true; | ||||
|                 break; | ||||
|             } | ||||
|         case St.Side.LEFT: | ||||
|             if (this._arrowOrigin == y1) | ||||
|                 skipTopLeft = true; | ||||
|             else if (this._arrowOrigin == y2) | ||||
|                 skipBottomLeft = true; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         cr.moveTo(x1 + borderRadius, y1); | ||||
|         if (this._arrowSide == St.Side.TOP && rise) { | ||||
|         if (this._arrowSide == St.Side.TOP) { | ||||
|             if (skipTopLeft) { | ||||
|                 cr.moveTo(x1, y2 - borderRadius); | ||||
|                 cr.lineTo(x1, y1 - rise); | ||||
| @@ -342,7 +330,7 @@ const BoxPointer = new Lang.Class({ | ||||
|                    3*Math.PI/2, Math.PI*2); | ||||
|         } | ||||
|  | ||||
|         if (this._arrowSide == St.Side.RIGHT && rise) { | ||||
|         if (this._arrowSide == St.Side.RIGHT) { | ||||
|             if (skipTopRight) { | ||||
|                 cr.lineTo(x2 + rise, y1); | ||||
|                 cr.lineTo(x2 + rise, y1 + halfBase); | ||||
| @@ -363,7 +351,7 @@ const BoxPointer = new Lang.Class({ | ||||
|                    0, Math.PI/2); | ||||
|         } | ||||
|  | ||||
|         if (this._arrowSide == St.Side.BOTTOM && rise) { | ||||
|         if (this._arrowSide == St.Side.BOTTOM) { | ||||
|             if (skipBottomLeft) { | ||||
|                 cr.lineTo(x1 + halfBase, y2); | ||||
|                 cr.lineTo(x1, y2 + rise); | ||||
| @@ -384,7 +372,7 @@ const BoxPointer = new Lang.Class({ | ||||
|                    Math.PI/2, Math.PI); | ||||
|         } | ||||
|  | ||||
|         if (this._arrowSide == St.Side.LEFT && rise) { | ||||
|         if (this._arrowSide == St.Side.LEFT) { | ||||
|             if (skipTopLeft) { | ||||
|                 cr.lineTo(x1, y1 + halfBase); | ||||
|                 cr.lineTo(x1 - rise, y1); | ||||
| @@ -601,12 +589,12 @@ const BoxPointer = new Lang.Class({ | ||||
|                 return St.Side.TOP; | ||||
|             break; | ||||
|         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) | ||||
|                 return St.Side.RIGHT; | ||||
|             break; | ||||
|         case St.Side.RIGHT: | ||||
|             if (sourceAllocation.x1 - boxWidth < monitor.x && | ||||
|             if (sourceAllocation.y1 - boxWidth < monitor.x && | ||||
|                 boxWidth < monitor.x + monitor.width - sourceAllocation.x2) | ||||
|                 return St.Side.LEFT; | ||||
|             break; | ||||
| @@ -624,8 +612,6 @@ const BoxPointer = new Lang.Class({ | ||||
|                 this._container.queue_relayout(); | ||||
|                 return false; | ||||
|             })); | ||||
|  | ||||
|             this.emit('arrow-side-changed'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -653,21 +639,5 @@ const BoxPointer = new Lang.Class({ | ||||
|  | ||||
|     get 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); | ||||
|   | ||||
| @@ -17,18 +17,16 @@ const SHOW_WEEKDATE_KEY = 'show-weekdate'; | ||||
| // in org.gnome.desktop.interface | ||||
| const CLOCK_FORMAT_KEY        = 'clock-format'; | ||||
|  | ||||
| function _sameDay(dateA, dateB) { | ||||
|     return (dateA.getDate() == dateB.getDate() && | ||||
|             dateA.getMonth() == dateB.getMonth() && | ||||
|             dateA.getYear() == dateB.getYear()); | ||||
| } | ||||
|  | ||||
| function _sameYear(dateA, dateB) { | ||||
|     return (dateA.getYear() == dateB.getYear()); | ||||
| } | ||||
|  | ||||
| function _sameMonth(dateA, dateB) { | ||||
|     return _sameYear(dateA, dateB) && (dateA.getMonth() == dateB.getMonth()); | ||||
| } | ||||
|  | ||||
| function _sameDay(dateA, dateB) { | ||||
|     return _sameMonth(dateA, dateB) && (dateA.getDate() == dateB.getDate()); | ||||
| } | ||||
|  | ||||
| /* TODO: maybe needs config - right now we assume that Saturday and | ||||
|  * Sunday are non-work days (not true in e.g. Israel, it's Sunday and | ||||
|  * Monday there) | ||||
| @@ -73,7 +71,7 @@ function _formatEventTime(event, clockFormat) { | ||||
|         default: | ||||
|             /* explicit fall-through */ | ||||
|         case '12h': | ||||
|             /* Translators: Shown in calendar event list, if 12h format, | ||||
|             /* Transators: Shown in calendar event list, if 12h format, | ||||
|                \u2236 is a ratio character, similar to : and \u2009 is | ||||
|                a thin space */ | ||||
|             ret = event.date.toLocaleFormat(C_("event list time", "%l\u2236%M\u2009%p")); | ||||
| @@ -170,12 +168,6 @@ const EmptyEventSource = new Lang.Class({ | ||||
|     Name: 'EmptyEventSource', | ||||
|  | ||||
|     _init: function() { | ||||
|         this.isLoading = false; | ||||
|         this.isDummy = true; | ||||
|         this.hasCalendars = false; | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|     }, | ||||
|  | ||||
|     requestRange: function(begin, end) { | ||||
| @@ -192,18 +184,15 @@ const EmptyEventSource = new Lang.Class({ | ||||
| }); | ||||
| Signals.addSignalMethods(EmptyEventSource.prototype); | ||||
|  | ||||
| const CalendarServerIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.CalendarServer"> \ | ||||
| <method name="GetEvents"> \ | ||||
|     <arg type="x" direction="in" /> \ | ||||
|     <arg type="x" direction="in" /> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
|     <arg type="a(sssbxxa{sv})" direction="out" /> \ | ||||
| </method> \ | ||||
| <property name="HasCalendars" type="b" access="read" /> \ | ||||
| <signal name="Changed" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const CalendarServerIface = <interface name="org.gnome.Shell.CalendarServer"> | ||||
| <method name="GetEvents"> | ||||
|     <arg type="x" direction="in" /> | ||||
|     <arg type="x" direction="in" /> | ||||
|     <arg type="b" direction="in" /> | ||||
|     <arg type="a(sssbxxa{sv})" direction="out" /> | ||||
| </method> | ||||
| <signal name="Changed" /> | ||||
| </interface>; | ||||
|  | ||||
| const CalendarServerInfo  = Gio.DBusInterfaceInfo.new_for_xml(CalendarServerIface); | ||||
|  | ||||
| @@ -212,7 +201,8 @@ function CalendarServer() { | ||||
|                                g_interface_name: CalendarServerInfo.name, | ||||
|                                g_interface_info: CalendarServerInfo, | ||||
|                                g_name: 'org.gnome.Shell.CalendarServer', | ||||
|                                g_object_path: '/org/gnome/Shell/CalendarServer' }); | ||||
|                                g_object_path: '/org/gnome/Shell/CalendarServer', | ||||
|                                g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES }); | ||||
| } | ||||
|  | ||||
| function _datesEqual(a, b) { | ||||
| @@ -239,8 +229,6 @@ const DBusEventSource = new Lang.Class({ | ||||
|  | ||||
|     _init: function() { | ||||
|         this._resetCache(); | ||||
|         this.isLoading = false; | ||||
|         this.isDummy = false; | ||||
|  | ||||
|         this._initialized = false; | ||||
|         this._dbusProxy = new CalendarServer(); | ||||
| @@ -261,27 +249,11 @@ const DBusEventSource = new Lang.Class({ | ||||
|                     this._onNameVanished(); | ||||
|             })); | ||||
|  | ||||
|             this._dbusProxy.connect('g-properties-changed', Lang.bind(this, function() { | ||||
|                 this.emit('notify::has-calendars'); | ||||
|             })); | ||||
|  | ||||
|             this._initialized = true; | ||||
|             this.emit('notify::has-calendars'); | ||||
|             this._onNameAppeared(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this._dbusProxy.run_dispose(); | ||||
|     }, | ||||
|  | ||||
|     get hasCalendars() { | ||||
|         if (this._initialized) | ||||
|             return this._dbusProxy.HasCalendars; | ||||
|         else | ||||
|             return false; | ||||
|     }, | ||||
|  | ||||
|     _resetCache: function() { | ||||
|         this._events = []; | ||||
|         this._lastRequestBegin = null; | ||||
| @@ -321,7 +293,6 @@ const DBusEventSource = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         this._events = newEvents; | ||||
|         this.isLoading = false; | ||||
|         this.emit('changed'); | ||||
|     }, | ||||
|  | ||||
| @@ -331,22 +302,24 @@ const DBusEventSource = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         if (this._curRequestBegin && this._curRequestEnd){ | ||||
|             let callFlags = Gio.DBusCallFlags.NO_AUTO_START; | ||||
|             if (forceReload) | ||||
|                 callFlags = Gio.DBusCallFlags.NONE; | ||||
|             this._dbusProxy.GetEventsRemote(this._curRequestBegin.getTime() / 1000, | ||||
|                                             this._curRequestEnd.getTime() / 1000, | ||||
|                                             forceReload, | ||||
|                                             Lang.bind(this, this._onEventsReceived), | ||||
|                                             Gio.DBusCallFlags.NONE); | ||||
|                                             callFlags); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     requestRange: function(begin, end) { | ||||
|         if (!(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) { | ||||
|             this.isLoading = true; | ||||
|     requestRange: function(begin, end, forceReload) { | ||||
|         if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) { | ||||
|             this._lastRequestBegin = begin; | ||||
|             this._lastRequestEnd = end; | ||||
|             this._curRequestBegin = begin; | ||||
|             this._curRequestEnd = end; | ||||
|             this._loadEvents(false); | ||||
|             this._loadEvents(forceReload); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -416,21 +389,31 @@ const Calendar = new Lang.Class({ | ||||
|     // @eventSource: is an object implementing the EventSource API, e.g. the | ||||
|     // requestRange(), getEvents(), hasEvents() methods and the ::changed signal. | ||||
|     setEventSource: function(eventSource) { | ||||
|         if (this._eventSource) { | ||||
|             this._eventSource.disconnect(this._eventSourceChangedId); | ||||
|             this._eventSource = null; | ||||
|         } | ||||
|  | ||||
|         this._eventSource = eventSource; | ||||
|         this._eventSource.connect('changed', Lang.bind(this, function() { | ||||
|             this._update(); | ||||
|         })); | ||||
|         this._update(); | ||||
|  | ||||
|         if (this._eventSource) { | ||||
|             this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, function() { | ||||
|                 this._update(false); | ||||
|             })); | ||||
|             this._update(true); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     // Sets the calendar to show a specific date | ||||
|     setDate: function(date) { | ||||
|         if (_sameDay(date, this._selectedDate)) | ||||
|             return; | ||||
|  | ||||
|         this._selectedDate = date; | ||||
|         this._update(); | ||||
|         this.emit('selected-date-changed', new Date(this._selectedDate)); | ||||
|     setDate: function(date, forceReload) { | ||||
|         if (!_sameDay(date, this._selectedDate)) { | ||||
|             this._selectedDate = date; | ||||
|             this._update(forceReload); | ||||
|             this.emit('selected-date-changed', new Date(this._selectedDate)); | ||||
|         } else { | ||||
|             if (forceReload) | ||||
|                 this._update(forceReload); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _buildHeader: function() { | ||||
| @@ -442,21 +425,16 @@ const Calendar = new Lang.Class({ | ||||
|         this.actor.add(this._topBox, | ||||
|                        { row: 0, col: 0, col_span: offsetCols + 7 }); | ||||
|  | ||||
|         this._backButton = new St.Button({ style_class: 'calendar-change-month-back', | ||||
|                                            accessible_name: _("Previous month"), | ||||
|                                            can_focus: true }); | ||||
|         this._topBox.add(this._backButton); | ||||
|         this._backButton.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked)); | ||||
|         let back = new St.Button({ style_class: 'calendar-change-month-back' }); | ||||
|         this._topBox.add(back); | ||||
|         back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked)); | ||||
|  | ||||
|         this._monthLabel = new St.Label({style_class: 'calendar-month-label', | ||||
|                                          can_focus: true }); | ||||
|         this._monthLabel = new St.Label({style_class: 'calendar-month-label'}); | ||||
|         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', | ||||
|                                               accessible_name: _("Next month"), | ||||
|                                               can_focus: true }); | ||||
|         this._topBox.add(this._forwardButton); | ||||
|         this._forwardButton.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked)); | ||||
|         let forward = new St.Button({ style_class: 'calendar-change-month-forward' }); | ||||
|         this._topBox.add(forward); | ||||
|         forward.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked)); | ||||
|  | ||||
|         // Add weekday labels... | ||||
|         // | ||||
| @@ -494,7 +472,6 @@ const Calendar = new Lang.Class({ | ||||
|             this._onNextMonthButtonClicked(); | ||||
|             break; | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onPrevMonthButtonClicked: function() { | ||||
| @@ -516,12 +493,10 @@ const Calendar = new Lang.Class({ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._backButton.grab_key_focus(); | ||||
|         this.setDate(newDate, false); | ||||
|    }, | ||||
|  | ||||
|         this.setDate(newDate); | ||||
|     }, | ||||
|  | ||||
|     _onNextMonthButtonClicked: function() { | ||||
|    _onNextMonthButtonClicked: function() { | ||||
|         let newDate = new Date(this._selectedDate); | ||||
|         let oldMonth = newDate.getMonth(); | ||||
|         if (oldMonth == 11) { | ||||
| @@ -540,83 +515,57 @@ const Calendar = new Lang.Class({ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._forwardButton.grab_key_focus(); | ||||
|  | ||||
|         this.setDate(newDate); | ||||
|        this.setDate(newDate, false); | ||||
|     }, | ||||
|  | ||||
|     _onSettingsChange: function() { | ||||
|         this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY); | ||||
|         this._buildHeader(); | ||||
|         this._update(); | ||||
|         this._update(false); | ||||
|     }, | ||||
|  | ||||
|     _rebuildCalendar: function() { | ||||
|     _update: function(forceReload) { | ||||
|         let now = new Date(); | ||||
|  | ||||
|         if (_sameYear(this._selectedDate, now)) | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormatWithoutYear); | ||||
|         else | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormat); | ||||
|  | ||||
|         // Remove everything but the topBox and the weekday labels | ||||
|         let children = this.actor.get_children(); | ||||
|         for (let i = this._firstDayIndex; i < children.length; i++) | ||||
|             children[i].destroy(); | ||||
|  | ||||
|         this._buttons = []; | ||||
|  | ||||
|         // Start at the beginning of the week before the start of the month | ||||
|         // | ||||
|         // We want to show always 6 weeks (to keep the calendar menu at the same | ||||
|         // height if there are no events), so we pad it according to the following | ||||
|         // policy: | ||||
|         // | ||||
|         // 1 - If a month has 6 weeks, we place no padding (example: Dec 2012) | ||||
|         // 2 - If a month has 5 weeks and it starts on week start, we pad one week | ||||
|         //     before it (example: Apr 2012) | ||||
|         // 3 - If a month has 5 weeks and it starts on any other day, we pad one week | ||||
|         //     after it (example: Nov 2012) | ||||
|         // 4 - If a month has 4 weeks, we pad one week before and one after it | ||||
|         //     (example: Feb 2010) | ||||
|         // | ||||
|         // Actually computing the number of weeks is complex, but we know that the | ||||
|         // problematic categories (2 and 4) always start on week start, and that | ||||
|         // all months at the end have 6 weeks. | ||||
|         let beginDate = new Date(this._selectedDate); | ||||
|         beginDate.setDate(1); | ||||
|         beginDate.setSeconds(0); | ||||
|         beginDate.setHours(12); | ||||
|  | ||||
|         this._calendarBegin = new Date(beginDate); | ||||
|  | ||||
|         let year = beginDate.getYear(); | ||||
|  | ||||
|         let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7; | ||||
|         let startsOnWeekStart = daysToWeekStart == 0; | ||||
|         let weekPadding = startsOnWeekStart ? 7 : 0; | ||||
|  | ||||
|         beginDate.setTime(beginDate.getTime() - (weekPadding + daysToWeekStart) * MSECS_IN_DAY); | ||||
|         beginDate.setTime(beginDate.getTime() - daysToWeekStart * MSECS_IN_DAY); | ||||
|  | ||||
|         let iter = new Date(beginDate); | ||||
|         let row = 2; | ||||
|         // nRows here means 6 weeks + one header + one navbar | ||||
|         let nRows = 8; | ||||
|         while (row < 8) { | ||||
|             let button = new St.Button({ label: iter.getDate().toString(), | ||||
|                                          can_focus: true }); | ||||
|         while (true) { | ||||
|             let button = new St.Button({ label: iter.getDate().toString() }); | ||||
|             let rtl = button.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|  | ||||
|             if (this._eventSource.isDummy) | ||||
|             if (!this._eventSource) | ||||
|                 button.reactive = false; | ||||
|  | ||||
|             button._date = new Date(iter); | ||||
|             let iterStr = iter.toUTCString(); | ||||
|             button.connect('clicked', Lang.bind(this, function() { | ||||
|                 this.setDate(button._date); | ||||
|                 let newlySelectedDate = new Date(iterStr); | ||||
|                 this.setDate(newlySelectedDate, false); | ||||
|             })); | ||||
|  | ||||
|             let hasEvents = this._eventSource.hasEvents(iter); | ||||
|             let hasEvents = this._eventSource && this._eventSource.hasEvents(iter); | ||||
|             let styleClass = 'calendar-day-base calendar-day'; | ||||
|  | ||||
|             if (_isWorkDay(iter)) | ||||
|                 styleClass += ' calendar-work-day'; | ||||
|                 styleClass += ' calendar-work-day' | ||||
|             else | ||||
|                 styleClass += ' calendar-nonwork-day'; | ||||
|                 styleClass += ' calendar-nonwork-day' | ||||
|  | ||||
|             // Hack used in lieu of border-collapse - see gnome-shell.css | ||||
|             if (row == 2) | ||||
| @@ -632,8 +581,11 @@ const Calendar = new Lang.Class({ | ||||
|             else if (iter.getMonth() != this._selectedDate.getMonth()) | ||||
|                 styleClass += ' calendar-other-month-day'; | ||||
|  | ||||
|             if (_sameDay(this._selectedDate, iter)) | ||||
|                 button.add_style_pseudo_class('active'); | ||||
|  | ||||
|             if (hasEvents) | ||||
|                 styleClass += ' calendar-day-with-events'; | ||||
|                 styleClass += ' calendar-day-with-events' | ||||
|  | ||||
|             button.style_class = styleClass; | ||||
|  | ||||
| @@ -641,8 +593,6 @@ const Calendar = new Lang.Class({ | ||||
|             this.actor.add(button, | ||||
|                            { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 }); | ||||
|  | ||||
|             this._buttons.push(button); | ||||
|  | ||||
|             if (this._useWeekdate && iter.getDay() == 4) { | ||||
|                 let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(), | ||||
|                                            style_class: 'calendar-day-base calendar-week-number'}); | ||||
| @@ -651,33 +601,17 @@ const Calendar = new Lang.Class({ | ||||
|             } | ||||
|  | ||||
|             iter.setTime(iter.getTime() + MSECS_IN_DAY); | ||||
|  | ||||
|             if (iter.getDay() == this._weekStart) | ||||
|             if (iter.getDay() == this._weekStart) { | ||||
|                 // We stop on the first "first day of the week" after the month we are displaying | ||||
|                 if (iter.getMonth() > this._selectedDate.getMonth() || iter.getYear() > this._selectedDate.getYear()) | ||||
|                     break; | ||||
|                 row++; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Signal to the event source that we are interested in events | ||||
|         // only from this date range | ||||
|         this._eventSource.requestRange(beginDate, iter); | ||||
|     }, | ||||
|  | ||||
|     _update: function() { | ||||
|         let now = new Date(); | ||||
|  | ||||
|         if (_sameYear(this._selectedDate, now)) | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormatWithoutYear); | ||||
|         else | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormat); | ||||
|  | ||||
|         if (!this._calendarBegin || !_sameMonth(this._selectedDate, this._calendarBegin)) | ||||
|             this._rebuildCalendar(); | ||||
|  | ||||
|         this._buttons.forEach(Lang.bind(this, function(button) { | ||||
|             if (_sameDay(button._date, this._selectedDate)) | ||||
|                 button.add_style_pseudo_class('active'); | ||||
|             else | ||||
|                 button.remove_style_pseudo_class('active'); | ||||
|         })); | ||||
|         if (this._eventSource) | ||||
|             this._eventSource.requestRange(beginDate, iter, forceReload); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -687,7 +621,7 @@ const EventsList = new Lang.Class({ | ||||
|     Name: 'EventsList', | ||||
|  | ||||
|     _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._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' }); | ||||
|         this._desktopSettings.connect('changed', Lang.bind(this, this._update)); | ||||
| @@ -695,76 +629,70 @@ const EventsList = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     setEventSource: function(eventSource) { | ||||
|         if (this._eventSource) { | ||||
|             this._eventSource.disconnect(this._eventSourceChangedId); | ||||
|             this._eventSource = null; | ||||
|         } | ||||
|  | ||||
|         this._eventSource = eventSource; | ||||
|         this._eventSource.connect('changed', Lang.bind(this, this._update)); | ||||
|  | ||||
|         if (this._eventSource) { | ||||
|             this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, this._update)); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _addEvent: function(event, index, includeDayName) { | ||||
|         let dayString; | ||||
|         if (includeDayName) | ||||
|             dayString = _getEventDayAbbreviation(event.date.getDay()); | ||||
|         else | ||||
|             dayString = ''; | ||||
|  | ||||
|         let dayLabel = new St.Label({ style_class: 'events-day-dayname', | ||||
|                                       text: dayString }); | ||||
|         dayLabel.clutter_text.line_wrap = false; | ||||
|         dayLabel.clutter_text.ellipsize = false; | ||||
|  | ||||
|         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 }); | ||||
|     _addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) { | ||||
|         if (includeDayName) { | ||||
|             dayNameBox.add(new St.Label( { style_class: 'events-day-dayname', | ||||
|                                            text: day } ), | ||||
|                            { x_fill: true } ); | ||||
|         } | ||||
|         timeBox.add(new St.Label( { style_class: 'events-day-time', | ||||
|                                     text: time} ), | ||||
|                     { x_fill: true } ); | ||||
|         eventTitleBox.add(new St.Label( { style_class: 'events-day-task', | ||||
|                                           text: desc} )); | ||||
|     }, | ||||
|  | ||||
|     _addPeriod: function(header, index, begin, end, includeDayName, showNothingScheduled) { | ||||
|     _addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) { | ||||
|         if (!this._eventSource) | ||||
|             return; | ||||
|  | ||||
|         let events = this._eventSource.getEvents(begin, end); | ||||
|  | ||||
|         if (events.length == 0 && !showNothingScheduled) | ||||
|             return index; | ||||
|         let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);; | ||||
|  | ||||
|         this.actor.add(new St.Label({ style_class: 'events-day-header', text: header }), | ||||
|                        { row: index, col: 0, col_span: 3, | ||||
|                          // In theory, x_expand should be true here, but x_expand | ||||
|                          // is a property of the column for StTable, ie all day cells | ||||
|                          // get it too | ||||
|                          x_expand: false, x_align: St.Align.START, | ||||
|                          y_fill: false, y_align: St.Align.START }); | ||||
|         index++; | ||||
|         if (events.length == 0 && !showNothingScheduled) | ||||
|             return; | ||||
|  | ||||
|         let vbox = new St.BoxLayout( {vertical: true} ); | ||||
|         this.actor.add(vbox); | ||||
|  | ||||
|         vbox.add(new St.Label({ style_class: 'events-day-header', text: header })); | ||||
|         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++) { | ||||
|             this._addEvent(events[n], index, includeDayName); | ||||
|             index++; | ||||
|             let event = events[n]; | ||||
|             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) { | ||||
|             let now = new Date(); | ||||
|             /* Translators: Text to show if there are no events */ | ||||
|             let nothingEvent = new CalendarEvent(now, now, _("Nothing Scheduled"), true); | ||||
|             this._addEvent(nothingEvent, index, false); | ||||
|             index++; | ||||
|             let timeString = _formatEventTime(nothingEvent, clockFormat); | ||||
|             this._addEvent(dayNameBox, timeBox, eventTitleBox, false, "", timeString, nothingEvent.summary); | ||||
|         } | ||||
|  | ||||
|         return index; | ||||
|     }, | ||||
|  | ||||
|     _showOtherDay: function(day) { | ||||
| @@ -781,21 +709,20 @@ const EventsList = new Lang.Class({ | ||||
|         else | ||||
|             /* Translators: Shown on calendar heading when selected day occurs on different year */ | ||||
|             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() { | ||||
|         this.actor.destroy_all_children(); | ||||
|         let index = 0; | ||||
|  | ||||
|         let now = new Date(); | ||||
|         let dayBegin = _getBeginningOfDay(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 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; | ||||
|  | ||||
| @@ -806,7 +733,7 @@ const EventsList = new Lang.Class({ | ||||
|              */ | ||||
|             let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 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 { | ||||
|             /* otherwise it's one of the two last days of the week ... show | ||||
|              * "Next week" and include events up until and including *next* | ||||
| @@ -814,7 +741,7 @@ const EventsList = new Lang.Class({ | ||||
|              */ | ||||
|             let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 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); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -827,9 +754,6 @@ const EventsList = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _update: function() { | ||||
|         if (this._eventSource.isLoading) | ||||
|             return; | ||||
|  | ||||
|         let today = new Date(); | ||||
|         if (_sameDay (this._date, today)) { | ||||
|             this._showToday(); | ||||
|   | ||||
| @@ -1,40 +1,115 @@ | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Pango = imports.gi.Pango; | ||||
| const Shell = imports.gi.Shell; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| 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({ | ||||
|     Name: 'CheckBox', | ||||
|  | ||||
|     _init: function(label) { | ||||
|         let container = new St.BoxLayout(); | ||||
|         this.actor = new St.Button({ style_class: 'check-box', | ||||
|                                      child: container, | ||||
|                                      button_mask: St.ButtonMask.ONE, | ||||
|                                      toggle_mode: true, | ||||
|                                      can_focus: true, | ||||
|                                      x_fill: true, | ||||
|                                      y_fill: true }); | ||||
|  | ||||
|         this._box = new St.Bin(); | ||||
|         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); | ||||
|         this._container = new CheckBoxContainer(); | ||||
|         this.actor.set_child(this._container.actor); | ||||
|  | ||||
|         if (label) | ||||
|             this.setLabel(label); | ||||
|     }, | ||||
|  | ||||
|     setLabel: function(label) { | ||||
|         this._label.set_text(label); | ||||
|         this._container.label.set_text(label); | ||||
|     }, | ||||
|  | ||||
|     getLabelActor: function() { | ||||
|         return this._label; | ||||
|         return this._container.label; | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -77,7 +77,7 @@ const AutomountManager = new Lang.Class({ | ||||
|         })); | ||||
|  | ||||
|         this._mountAllId = 0; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onDriveConnected: function() { | ||||
| @@ -236,7 +236,7 @@ const AutomountManager = new Lang.Class({ | ||||
|     _allowAutorunExpire: function(volume) { | ||||
|         Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, function() { | ||||
|             volume.allowAutorun = false; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|             return false; | ||||
|         }); | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -31,7 +31,7 @@ function shouldAutorunMount(mount, forTransient) { | ||||
|     if (!volume || (!volume.allowAutorun && forTransient)) | ||||
|         return false; | ||||
|  | ||||
|     if (root.is_native() && isMountRootHidden(root)) | ||||
|     if (!root.is_native() || isMountRootHidden(root)) | ||||
|         return false; | ||||
|  | ||||
|     return true; | ||||
| @@ -64,7 +64,7 @@ function startAppForMount(app, mount) { | ||||
|  | ||||
|     try { | ||||
|         retval = app.launch(files,  | ||||
|                             global.create_app_launch_context(0, -1)) | ||||
|                             global.create_app_launch_context()) | ||||
|     } catch (e) { | ||||
|         log('Unable to launch the application ' + app.get_name() | ||||
|             + ': ' + e.toString()); | ||||
| @@ -75,14 +75,12 @@ function startAppForMount(app, mount) { | ||||
|  | ||||
| /******************************************/ | ||||
|  | ||||
| const HotplugSnifferIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.HotplugSniffer"> \ | ||||
| <method name="SniffURI"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="as" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const HotplugSnifferIface = <interface name="org.gnome.Shell.HotplugSniffer"> | ||||
| <method name="SniffURI"> | ||||
|     <arg type="s" direction="in" /> | ||||
|     <arg type="as" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const HotplugSnifferProxy = Gio.DBusProxy.makeProxyWrapper(HotplugSnifferIface); | ||||
| function HotplugSniffer() { | ||||
| @@ -294,7 +292,6 @@ const AutorunResidentSource = new Lang.Class({ | ||||
|  | ||||
|     _init: function(manager) { | ||||
|         this.parent(_("Removable Devices"), 'media-removable'); | ||||
|         this.resident = true; | ||||
|  | ||||
|         this._mounts = []; | ||||
|  | ||||
|   | ||||
| @@ -13,6 +13,8 @@ const ModalDialog = imports.ui.modalDialog; | ||||
| const ShellEntry = imports.ui.shellEntry; | ||||
| const CheckBox = imports.ui.checkBox; | ||||
|  | ||||
| let prompter = null; | ||||
|  | ||||
| const KeyringDialog = new Lang.Class({ | ||||
|     Name: 'KeyringDialog', | ||||
|     Extends: ModalDialog.ModalDialog, | ||||
| @@ -23,7 +25,7 @@ const KeyringDialog = new Lang.Class({ | ||||
|         this.prompt = new Shell.KeyringPrompt(); | ||||
|         this.prompt.connect('show-password', Lang.bind(this, this._onShowPassword)); | ||||
|         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', | ||||
|                                                 vertical: false }); | ||||
| @@ -45,9 +47,7 @@ const KeyringDialog = new Lang.Class({ | ||||
|         this.prompt.bind_property('message', subject, 'text', GObject.BindingFlags.SYNC_CREATE); | ||||
|  | ||||
|         this._messageBox.add(subject, | ||||
|                              { x_fill: false, | ||||
|                                y_fill:  false, | ||||
|                                x_align: St.Align.START, | ||||
|                              { y_fill:  false, | ||||
|                                y_align: St.Align.START }); | ||||
|  | ||||
|         let description = new St.Label({ style_class: 'prompt-dialog-description' }); | ||||
| @@ -63,43 +63,34 @@ const KeyringDialog = new Lang.Class({ | ||||
|  | ||||
|         this._cancelButton = this.addButton({ label: '', | ||||
|                                               action: Lang.bind(this, this._onCancelButton), | ||||
|                                               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 }); | ||||
|                                               key: Clutter.Escape }); | ||||
|         this._continueButton = this.addButton({ label: '', | ||||
|                                                 action: Lang.bind(this, this._onContinueButton), | ||||
|                                                 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('continue-label', this._continueButton, 'label', GObject.BindingFlags.SYNC_CREATE); | ||||
|     }, | ||||
|  | ||||
|     _buildControlTable: function() { | ||||
|         let layout = new Clutter.TableLayout(); | ||||
|         let table = new St.Widget({ style_class: 'keyring-dialog-control-table', | ||||
|                                     layout_manager: layout }); | ||||
|         layout.hookup_style(table); | ||||
|         let table = new St.Table({ style_class: 'keyring-dialog-control-table' }); | ||||
|         let row = 0; | ||||
|  | ||||
|         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.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|             layout.pack(label, 0, row); | ||||
|             layout.child_set(label, { x_expand: false, y_fill: false, | ||||
|                                       x_align: Clutter.TableAlignment.START }); | ||||
|             table.add(label, { row: row, col: 0, | ||||
|                                x_expand: false, x_fill: true, | ||||
|                                x_align: St.Align.START, | ||||
|                                y_fill: false, y_align: St.Align.MIDDLE }); | ||||
|             this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry', | ||||
|                                                  text: '', | ||||
|                                                  can_focus: true }); | ||||
|                                                  can_focus: true}); | ||||
|             this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE | ||||
|             ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true }); | ||||
|             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++; | ||||
|         } else { | ||||
|             this._passwordEntry = null; | ||||
| @@ -108,16 +99,17 @@ const KeyringDialog = new Lang.Class({ | ||||
|         if (this.prompt.confirm_visible) { | ||||
|             var label = new St.Label(({ style_class: 'prompt-dialog-password-label' })); | ||||
|             label.set_text(_("Type again:")); | ||||
|             layout.pack(label, 0, row); | ||||
|             layout.child_set(label, { x_expand: false, y_fill: false, | ||||
|                                       x_align: Clutter.TableAlignment.START }); | ||||
|             table.add(label, { row: row, col: 0, | ||||
|                                x_expand: false, x_fill: true, | ||||
|                                x_align: St.Align.START, | ||||
|                                y_fill: false, y_align: St.Align.MIDDLE }); | ||||
|             this._confirmEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry', | ||||
|                                                 text: '', | ||||
|                                                 can_focus: true }); | ||||
|                                                 can_focus: true}); | ||||
|             this._confirmEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE | ||||
|             ShellEntry.addContextMenu(this._confirmEntry, { isPassword: true }); | ||||
|             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++; | ||||
|         } else { | ||||
|             this._confirmEntry = null; | ||||
| @@ -130,15 +122,14 @@ const KeyringDialog = new Lang.Class({ | ||||
|             let choice = new CheckBox.CheckBox(); | ||||
|             this.prompt.bind_property('choice-label', choice.getLabelActor(), 'text', GObject.BindingFlags.SYNC_CREATE); | ||||
|             this.prompt.bind_property('choice-chosen', choice.actor, 'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL); | ||||
|             layout.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++; | ||||
|         } | ||||
|  | ||||
|         let warning = new St.Label({ style_class: 'prompt-dialog-error-label' }); | ||||
|         warning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|         warning.clutter_text.line_wrap = true; | ||||
|         layout.pack(warning, 1, row); | ||||
|         layout.child_set(warning, { x_fill: false, x_align: Clutter.TableAlignment.START }); | ||||
|         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', warning, 'text', GObject.BindingFlags.SYNC_CREATE); | ||||
|  | ||||
| @@ -152,19 +143,11 @@ const KeyringDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _updateSensitivity: function(sensitive) { | ||||
|         if (this._passwordEntry) { | ||||
|             this._passwordEntry.reactive = sensitive; | ||||
|             this._passwordEntry.clutter_text.editable = sensitive; | ||||
|         } | ||||
|  | ||||
|         if (this._confirmEntry) { | ||||
|             this._confirmEntry.reactive = sensitive; | ||||
|             this._confirmEntry.clutter_text.editable = sensitive; | ||||
|         } | ||||
|         this._passwordEntry.reactive = sensitive; | ||||
|         this._passwordEntry.clutter_text.editable = sensitive; | ||||
|  | ||||
|         this._continueButton.can_focus = sensitive; | ||||
|         this._continueButton.reactive = sensitive; | ||||
|         this.setWorking(!sensitive); | ||||
|     }, | ||||
|  | ||||
|     _ensureOpen: function() { | ||||
| @@ -224,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({ | ||||
|     Name: 'KeyringPrompter', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._prompter = new Gcr.SystemPrompter(); | ||||
|         this._prompter.connect('new-prompt', Lang.bind(this, | ||||
|             function() { | ||||
|                 let dialog = this._enabled ? new KeyringDialog() | ||||
|                                            : new KeyringDummyDialog(); | ||||
|                 this._currentPrompt = dialog.prompt; | ||||
|                 return this._currentPrompt; | ||||
|             })); | ||||
|         this._prompter.connect('new-prompt', function(prompter) { | ||||
|             let dialog = new KeyringDialog(); | ||||
|             return dialog.prompt; | ||||
|         }); | ||||
|         this._dbusId = null; | ||||
|         this._registered = false; | ||||
|         this._enabled = false; | ||||
|         this._currentPrompt = null; | ||||
|     }, | ||||
|  | ||||
|     enable: function() { | ||||
|         if (!this._registered) { | ||||
|             this._prompter.register(Gio.DBus.session); | ||||
|             this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter', | ||||
|                                                      Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null); | ||||
|             this._registered = true; | ||||
|         } | ||||
|         this._enabled = true; | ||||
|         this._prompter.register(Gio.DBus.session); | ||||
|         this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter', | ||||
|                                                  Gio.BusNameOwnerFlags.REPLACE, null, null); | ||||
|     }, | ||||
|  | ||||
|     disable: function() { | ||||
|         this._enabled = false; | ||||
|  | ||||
|         if (this._prompter.prompting) | ||||
|             this._currentPrompt.cancel(); | ||||
|         this._currentPrompt = null; | ||||
|         this._prompter.unregister(false); | ||||
|         Gio.DBus.session.unown_name(this._dbusId); | ||||
|     } | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -62,9 +62,14 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|  | ||||
|         if (this._content.message != null) { | ||||
|             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.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|  | ||||
|             messageBox.add(descriptionLabel, | ||||
|                            { y_fill:  true, | ||||
| @@ -72,18 +77,13 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|                              expand: true }); | ||||
|         } | ||||
|  | ||||
|         let layout = new Clutter.TableLayout(); | ||||
|         let secretTable = new St.Widget({ style_class: 'network-dialog-secret-table', | ||||
|                                           layout_manager: layout }); | ||||
|         layout.hookup_style(secretTable); | ||||
|  | ||||
|         let secretTable = new St.Table({ style_class: 'network-dialog-secret-table' }); | ||||
|         let initialFocusSet = false; | ||||
|         let pos = 0; | ||||
|         for (let i = 0; i < this._content.secrets.length; i++) { | ||||
|             let secret = this._content.secrets[i]; | ||||
|             let label = new St.Label({ style_class: 'prompt-dialog-password-label', | ||||
|                                        text: secret.label }); | ||||
|             label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|  | ||||
|             let reactive = secret.key != null; | ||||
|  | ||||
| @@ -116,10 +116,11 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|             } else | ||||
|                 secret.valid = true; | ||||
|  | ||||
|             layout.pack(label, 0, pos); | ||||
|             layout.child_set(label, { x_expand: false, y_fill: false, | ||||
|                                       x_align: Clutter.TableAlignment.START }); | ||||
|             layout.pack(secret.entry, 1, pos); | ||||
|             secretTable.add(label, { row: pos, col: 0, | ||||
|                                      x_expand: false, x_fill: true, | ||||
|                                      x_align: St.Align.START, | ||||
|                                      y_fill: false, y_align: St.Align.MIDDLE }); | ||||
|             secretTable.add(secret.entry, { row: pos, col: 1, x_expand: true, x_fill: true, y_align: St.Align.END }); | ||||
|             pos++; | ||||
|  | ||||
|             if (secret.password) | ||||
| @@ -138,8 +139,6 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|                            key:    Clutter.KEY_Escape, | ||||
|                          }, | ||||
|                          this._okButton]); | ||||
|  | ||||
|         this._updateOkButton(); | ||||
|     }, | ||||
|  | ||||
|     _updateOkButton: function() { | ||||
| @@ -255,7 +254,6 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|         case 'leap': | ||||
|         case 'ttls': | ||||
|         case 'peap': | ||||
|         case 'fast': | ||||
|             // TTLS and PEAP are actually much more complicated, but this complication | ||||
|             // is not visible here since we only care about phase2 authentication | ||||
|             // (and don't even care of which one) | ||||
| @@ -309,7 +307,7 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|             wirelessSetting = this._connection.get_setting_wireless(); | ||||
|             ssid = NetworkManager.utils_ssid_to_utf8(wirelessSetting.get_ssid()); | ||||
|             content.title = _("Authentication required by wireless network"); | ||||
|             content.message = _("Passwords or encryption keys are required to access the wireless network “%s”.").format(ssid); | ||||
|             content.message = _("Passwords or encryption keys are required to access the wireless network '%s'.").format(ssid); | ||||
|             this._getWirelessSecrets(content.secrets, wirelessSetting); | ||||
|             break; | ||||
|         case '802-3-ethernet': | ||||
| @@ -336,7 +334,7 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|         case 'cdma': | ||||
|         case 'bluetooth': | ||||
|             content.title = _("Mobile broadband network password"); | ||||
|             content.message = _("A password is required to connect to “%s”.").format(connectionSetting.get_id()); | ||||
|             content.message = _("A password is required to connect to '%s'.").format(connectionSetting.get_id()); | ||||
|             this._getMobileSecrets(content.secrets, connectionType); | ||||
|             break; | ||||
|         default: | ||||
| @@ -387,7 +385,11 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|             this._childPid = pid; | ||||
|             this._stdin = new Gio.UnixOutputStream({ fd: stdin, 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 }); | ||||
|  | ||||
|             if (this._newStylePlugin) | ||||
| @@ -435,7 +437,6 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _vpnChildFinished: function(pid, status, requestObj) { | ||||
|         this._childWatch = 0; | ||||
|         if (this._newStylePlugin) { | ||||
|             // For new style plugin, all work is done in the async reading functions | ||||
|             // Just reap the process here | ||||
|   | ||||
| @@ -16,7 +16,7 @@ const PolkitAgent = imports.gi.PolkitAgent; | ||||
| const Components = imports.ui.components; | ||||
| const ModalDialog = imports.ui.modalDialog; | ||||
| const ShellEntry = imports.ui.shellEntry; | ||||
| const UserWidget = imports.ui.userWidget; | ||||
| const UserMenu = imports.ui.userMenu; | ||||
|  | ||||
| const DIALOG_ICON_SIZE = 48; | ||||
|  | ||||
| @@ -31,6 +31,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         this.message = message; | ||||
|         this.userNames = userNames; | ||||
|         this._wasDismissed = false; | ||||
|         this._completed = false; | ||||
|  | ||||
|         let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout', | ||||
|                                                 vertical: false }); | ||||
| @@ -54,9 +55,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|                                             text: _("Authentication Required") }); | ||||
|  | ||||
|         messageBox.add(this._subjectLabel, | ||||
|                        { x_fill: false, | ||||
|                          y_fill:  false, | ||||
|                          x_align: St.Align.START, | ||||
|                        { y_fill:  false, | ||||
|                          y_align: St.Align.START }); | ||||
|  | ||||
|         this._descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description', | ||||
| @@ -65,9 +64,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         this._descriptionLabel.clutter_text.line_wrap = true; | ||||
|  | ||||
|         messageBox.add(this._descriptionLabel, | ||||
|                        { x_fill: false, | ||||
|                          y_fill:  true, | ||||
|                          x_align: St.Align.START, | ||||
|                        { y_fill:  true, | ||||
|                          y_align: St.Align.START }); | ||||
|  | ||||
|         if (userNames.length > 1) { | ||||
| @@ -99,15 +96,14 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         if (userIsRoot) { | ||||
|             let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-root-label', | ||||
|                                             text: userRealName })); | ||||
|             messageBox.add(userLabel, { x_fill: false, | ||||
|                                         x_align: St.Align.START }); | ||||
|             messageBox.add(userLabel); | ||||
|         } else { | ||||
|             let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout', | ||||
|                                              vertical: false }); | ||||
|             messageBox.add(userBox); | ||||
|             this._userAvatar = new UserWidget.Avatar(this._user, | ||||
|                                                      { iconSize: DIALOG_ICON_SIZE, | ||||
|                                                        styleClass: 'polkit-dialog-user-icon' }); | ||||
|             this._userAvatar = new UserMenu.UserAvatarWidget(this._user, | ||||
|                                                              { iconSize: DIALOG_ICON_SIZE, | ||||
|                                                                styleClass: 'polkit-dialog-user-icon' }); | ||||
|             this._userAvatar.actor.hide(); | ||||
|             userBox.add(this._userAvatar.actor, | ||||
|                         { x_fill:  true, | ||||
| @@ -142,7 +138,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label' }); | ||||
|         this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|         this._errorMessageLabel.clutter_text.line_wrap = true; | ||||
|         messageBox.add(this._errorMessageLabel, { x_fill: false, x_align: St.Align.START }); | ||||
|         messageBox.add(this._errorMessageLabel); | ||||
|         this._errorMessageLabel.hide(); | ||||
|  | ||||
|         this._infoMessageLabel = new St.Label({ style_class: 'prompt-dialog-info-label' }); | ||||
| @@ -165,32 +161,26 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|  | ||||
|         this._cancelButton = this.addButton({ label: _("Cancel"), | ||||
|                                               action: Lang.bind(this, this.cancel), | ||||
|                                               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 }); | ||||
|                                               key: Clutter.Escape }); | ||||
|         this._okButton = this.addButton({ label:  _("Authenticate"), | ||||
|                                           action: Lang.bind(this, this._onAuthenticateButtonPressed), | ||||
|                                           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._identityToAuth = Polkit.UnixUser.new_for_name(userName); | ||||
|         this._cookie = cookie; | ||||
|     }, | ||||
|  | ||||
|     performAuthentication: function() { | ||||
|         this.destroySession(); | ||||
|         this._session = new PolkitAgent.Session({ identity: this._identityToAuth, | ||||
|                                                   cookie: this._cookie }); | ||||
|         this._session.connect('completed', Lang.bind(this, this._onSessionCompleted)); | ||||
|         this._session.connect('request', Lang.bind(this, this._onSessionRequest)); | ||||
|         this._session.connect('show-error', Lang.bind(this, this._onSessionShowError)); | ||||
|         this._session.connect('show-info', Lang.bind(this, this._onSessionShowInfo)); | ||||
|     }, | ||||
|  | ||||
|     startAuthentication: function() { | ||||
|         this._session.initiate(); | ||||
|     }, | ||||
|  | ||||
| @@ -212,14 +202,14 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|             log('polkitAuthenticationAgent: Failed to show modal dialog.' + | ||||
|                 ' Dismissing authentication request for action-id ' + this.actionId + | ||||
|                 ' cookie ' + this._cookie); | ||||
|             this._emitDone(true); | ||||
|             this._emitDone(false, true); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _emitDone: function(dismissed) { | ||||
|     _emitDone: function(keepVisible, dismissed) { | ||||
|         if (!this._doneEmitted) { | ||||
|             this._doneEmitted = true; | ||||
|             this.emit('done', dismissed); | ||||
|             this.emit('done', keepVisible, dismissed); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -229,7 +219,6 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|  | ||||
|         this._okButton.can_focus = sensitive; | ||||
|         this._okButton.reactive = sensitive; | ||||
|         this.setWorking(!sensitive); | ||||
|     }, | ||||
|  | ||||
|     _onEntryActivate: function() { | ||||
| @@ -248,16 +237,12 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onSessionCompleted: function(session, gainedAuthorization) { | ||||
|         if (this._completed || this._doneEmitted) | ||||
|         if (this._completed) | ||||
|             return; | ||||
|  | ||||
|         this._completed = true; | ||||
|  | ||||
|         /* Yay, all done */ | ||||
|         if (gainedAuthorization) { | ||||
|             this._emitDone(false); | ||||
|  | ||||
|         } else { | ||||
|         if (!gainedAuthorization) { | ||||
|             /* Unless we are showing an existing error message from the PAM | ||||
|              * module (the PAM module could be reporting the authentication | ||||
|              * error providing authentication-method specific information), | ||||
| @@ -273,10 +258,8 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|                 this._infoMessageLabel.hide(); | ||||
|                 this._nullMessageLabel.hide(); | ||||
|             } | ||||
|  | ||||
|             /* Try and authenticate again */ | ||||
|             this.performAuthentication(); | ||||
|         } | ||||
|         this._emitDone(!gainedAuthorization, false); | ||||
|     }, | ||||
|  | ||||
|     _onSessionRequest: function(session, request, echo_on) { | ||||
| @@ -320,7 +303,6 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         if (this._session) { | ||||
|             if (!this._completed) | ||||
|                 this._session.cancel(); | ||||
|             this._completed = false; | ||||
|             this._session = null; | ||||
|         } | ||||
|     }, | ||||
| @@ -335,7 +317,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|     cancel: function() { | ||||
|         this._wasDismissed = true; | ||||
|         this.close(global.get_current_time()); | ||||
|         this._emitDone(true); | ||||
|         this._emitDone(false, true); | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(AuthenticationDialog.prototype); | ||||
| @@ -345,6 +327,7 @@ const AuthenticationAgent = new Lang.Class({ | ||||
|  | ||||
|     _init: function() { | ||||
|         this._currentDialog = null; | ||||
|         this._isCompleting = false; | ||||
|         this._handle = null; | ||||
|         this._native = new Shell.PolkitAuthenticationAgent(); | ||||
|         this._native.connect('initiate', Lang.bind(this, this._onInitiate)); | ||||
| @@ -381,24 +364,45 @@ const AuthenticationAgent = new Lang.Class({ | ||||
|         // discussion. | ||||
|  | ||||
|         this._currentDialog.connect('done', Lang.bind(this, this._onDialogDone)); | ||||
|         this._currentDialog.performAuthentication(); | ||||
|         this._currentDialog.startAuthentication(); | ||||
|     }, | ||||
|  | ||||
|     _onCancel: function(nativeAgent) { | ||||
|         this._completeRequest(false); | ||||
|         this._completeRequest(false, false); | ||||
|     }, | ||||
|  | ||||
|     _onDialogDone: function(dialog, dismissed) { | ||||
|         this._completeRequest(dismissed); | ||||
|     _onDialogDone: function(dialog, keepVisible, dismissed) { | ||||
|         this._completeRequest(keepVisible, dismissed); | ||||
|     }, | ||||
|  | ||||
|     _completeRequest: function(dismissed) { | ||||
|     _reallyCompleteRequest: function(dismissed) { | ||||
|         this._currentDialog.close(); | ||||
|         this._currentDialog.destroySession(); | ||||
|         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; | ||||
|   | ||||
							
								
								
									
										62
									
								
								js/ui/components/recorder.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								js/ui/components/recorder.js
									
									
									
									
									
										Normal 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; | ||||
| @@ -13,11 +13,12 @@ const Tp = imports.gi.TelepathyGLib; | ||||
| const History = imports.misc.history; | ||||
| const Main = imports.ui.main; | ||||
| const MessageTray = imports.ui.messageTray; | ||||
| const NotificationDaemon = imports.ui.notificationDaemon; | ||||
| const Params = imports.misc.params; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| // 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_LENGTH = 20; | ||||
| const SCROLLBACK_IDLE_LENGTH = 5; | ||||
| @@ -415,7 +416,7 @@ const TelepathyClient = new Lang.Class({ | ||||
|     _ensureAppSource: function() { | ||||
|         if (this._appSource == null) { | ||||
|             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); | ||||
|             this._appSource.connect('destroy', Lang.bind(this, function () { | ||||
| @@ -446,7 +447,6 @@ const ChatSource = new Lang.Class({ | ||||
|         this._closedId = this._channel.connect('invalidated', Lang.bind(this, this._channelClosed)); | ||||
|  | ||||
|         this._notification = new ChatNotification(this); | ||||
|         this._notification.connect('clicked', Lang.bind(this, this.open)); | ||||
|         this._notification.setUrgency(MessageTray.Urgency.HIGH); | ||||
|         this._notifyTimeoutId = 0; | ||||
|  | ||||
| @@ -488,7 +488,7 @@ const ChatSource = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationApplicationPolicy('empathy'); | ||||
|         return new NotificationDaemon.NotificationApplicationPolicy('empathy'); | ||||
|     }, | ||||
|  | ||||
|     _updateAlias: function() { | ||||
| @@ -545,19 +545,20 @@ const ChatSource = new Lang.Class({ | ||||
|         this._notification.update(this._notification.title, null, { customContent: true }); | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
|         if (this._client.is_handling_channel(this._channel)) { | ||||
|             // We are handling the channel, try to pass it to Empathy | ||||
|             this._client.delegate_channels_async([this._channel], | ||||
|                                                  global.get_current_time(), | ||||
|                                                  'org.freedesktop.Telepathy.Client.Empathy.Chat', null); | ||||
|         } else { | ||||
|             // We are not the handler, just ask to present the channel | ||||
|             let dbus = Tp.DBusDaemon.dup(); | ||||
|             let cd = Tp.ChannelDispatcher.new(dbus); | ||||
|     open: function(notification) { | ||||
|           if (this._client.is_handling_channel(this._channel)) { | ||||
|               // We are handling the channel, try to pass it to Empathy | ||||
|               this._client.delegate_channels_async([this._channel], | ||||
|                   global.get_current_time(), | ||||
|                   'org.freedesktop.Telepathy.Client.Empathy.Chat', null); | ||||
|           } | ||||
|           else { | ||||
|               // We are not the handler, just ask to present the channel | ||||
|               let dbus = Tp.DBusDaemon.dup(); | ||||
|               let cd = Tp.ChannelDispatcher.new(dbus); | ||||
|  | ||||
|             cd.present_channel_async(this._channel, global.get_current_time(), null); | ||||
|         } | ||||
|               cd.present_channel_async(this._channel, global.get_current_time(), null); | ||||
|           } | ||||
|     }, | ||||
|  | ||||
|     _getLogMessages: function() { | ||||
| @@ -621,11 +622,7 @@ const ChatSource = new Lang.Class({ | ||||
|             this.notify(); | ||||
|     }, | ||||
|  | ||||
|     destroy: function(reason) { | ||||
|         if (this._destroyed) | ||||
|             return; | ||||
|  | ||||
|         this._destroyed = true; | ||||
|     _channelClosed: function() { | ||||
|         this._channel.disconnect(this._closedId); | ||||
|         this._channel.disconnect(this._receivedId); | ||||
|         this._channel.disconnect(this._pendingId); | ||||
| @@ -635,14 +632,7 @@ const ChatSource = new Lang.Class({ | ||||
|         this._contact.disconnect(this._notifyAvatarId); | ||||
|         this._contact.disconnect(this._presenceChangedId); | ||||
|  | ||||
|         if (this._timestampTimeoutId) | ||||
|             Mainloop.source_remove(this._timestampTimeoutId); | ||||
|  | ||||
|         this.parent(reason); | ||||
|     }, | ||||
|  | ||||
|     _channelClosed: function() { | ||||
|         this.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED); | ||||
|         this.destroy(); | ||||
|     }, | ||||
|  | ||||
|     /* All messages are new messages for Telepathy sources */ | ||||
| @@ -686,7 +676,7 @@ const ChatSource = new Lang.Class({ | ||||
|  | ||||
|         this._notifyTimeoutId = 0; | ||||
|  | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     // This is called for both messages we send from | ||||
| @@ -971,22 +961,19 @@ const ChatNotification = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     appendTimestamp: function() { | ||||
|         this._timestampTimeoutId = 0; | ||||
|  | ||||
|         let lastMessageTime = this._history[0].time; | ||||
|         let lastMessageDate = new Date(lastMessageTime * 1000); | ||||
|  | ||||
|         let timeLabel = this._append({ body: this._formatTimestamp(lastMessageDate), | ||||
|                                        group: 'meta', | ||||
|                                        styles: ['chat-meta-message'], | ||||
|                                        childProps: { expand: true, x_fill: false, | ||||
|                                                      x_align: St.Align.END }, | ||||
|                                        childProps: { expand: true, x_fill: false }, | ||||
|                                        noTimestamp: true, | ||||
|                                        timestamp: lastMessageTime }); | ||||
|  | ||||
|         this._filterMessages(); | ||||
|  | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     appendAliasChange: function(oldAlias, newAlias) { | ||||
| @@ -1024,7 +1011,7 @@ const ChatNotification = new Lang.Class({ | ||||
|  | ||||
|         this.source.setChatState(Tp.ChannelChatState.PAUSED); | ||||
|  | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onEntryChanged: function() { | ||||
| @@ -1073,7 +1060,7 @@ const ApproverSource = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationApplicationPolicy('empathy'); | ||||
|         return new NotificationDaemon.NotificationApplicationPolicy('empathy'); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
| @@ -1108,16 +1095,22 @@ const RoomInviteNotification = new Lang.Class({ | ||||
|          * for example. */ | ||||
|         this.addBody(_("%s is inviting you to join %s").format(inviter.get_alias(), channel.get_identifier())); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|         this.addButton('decline', _("Decline")); | ||||
|         this.addButton('accept', _("Accept")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'decline': | ||||
|                 dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, | ||||
|                                                 '', function(src, result) { | ||||
|                     src.leave_channels_finish(result)}); | ||||
|                 break; | ||||
|             case 'accept': | ||||
|                 dispatchOp.handle_with_time_async('', global.get_current_time(), | ||||
|                                                   function(src, result) { | ||||
|                     src.handle_with_time_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| @@ -1141,19 +1134,23 @@ const AudioVideoNotification = new Lang.Class({ | ||||
|         this.parent(source, title, null, { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this.setUrgency(MessageTray.Urgency.CRITICAL); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         this.addButton('reject', _("Decline")); | ||||
|         /* translators: this is a button label (verb), not a noun */ | ||||
|         this.addAction(_("Answer"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|         this.addButton('answer', _("Answer")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'reject': | ||||
|                 dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, | ||||
|                                                 '', function(src, result) { | ||||
|                     src.leave_channels_finish(result)}); | ||||
|                 break; | ||||
|             case 'answer': | ||||
|                 dispatchOp.handle_with_time_async('', global.get_current_time(), | ||||
|                                                   function(src, result) { | ||||
|                     src.handle_with_time_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| @@ -1177,16 +1174,22 @@ const FileTransferNotification = new Lang.Class({ | ||||
|                     { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|         this.addButton('decline', _("Decline")); | ||||
|         this.addButton('accept', _("Accept")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'decline': | ||||
|                 dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, | ||||
|                                                 '', function(src, result) { | ||||
|                     src.leave_channels_finish(result)}); | ||||
|                 break; | ||||
|             case 'accept': | ||||
|                 dispatchOp.handle_with_time_async('', global.get_current_time(), | ||||
|                                                   function(src, result) { | ||||
|                     src.handle_with_time_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| @@ -1234,20 +1237,27 @@ const SubscriptionRequestNotification = new Lang.Class({ | ||||
|  | ||||
|         this.addActor(layout); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             contact.remove_async(function(src, result) { | ||||
|                 src.remove_finish(result); | ||||
|             }); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             // Authorize the contact and request to see his status as well | ||||
|             contact.authorize_publication_async(function(src, result) { | ||||
|                 src.authorize_publication_finish(result); | ||||
|             }); | ||||
|         this.addButton('decline', _("Decline")); | ||||
|         this.addButton('accept', _("Accept")); | ||||
|  | ||||
|             contact.request_subscription_async('', function(src, result) { | ||||
|                 src.request_subscription_finish(result); | ||||
|             }); | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'decline': | ||||
|                 contact.remove_async(function(src, result) { | ||||
|                     src.remove_finish(result)}); | ||||
|                 break; | ||||
|             case 'accept': | ||||
|                 // Authorize the contact and request to see his status as well | ||||
|                 contact.authorize_publication_async(function(src, result) { | ||||
|                     src.authorize_publication_finish(result)}); | ||||
|  | ||||
|                 contact.request_subscription_async('', function(src, result) { | ||||
|                     src.request_subscription_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             // rely on _subscriptionStatesChangedCb to destroy the | ||||
|             // notification | ||||
|         })); | ||||
|  | ||||
|         this._changedId = contact.connect('subscription-states-changed', | ||||
| @@ -1346,11 +1356,18 @@ const AccountNotification = new Lang.Class({ | ||||
|  | ||||
|         this._account = account; | ||||
|  | ||||
|         this.addAction(_("View account"), Lang.bind(this, function() { | ||||
|             let cmd = 'empathy-accounts --select-account=' + | ||||
|                 account.get_path_suffix(); | ||||
|             let app_info = Gio.app_info_create_from_commandline(cmd, null, 0); | ||||
|             app_info.launch([], global.create_app_launch_context(0, -1)); | ||||
|         this.addButton('view', _("View account")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'view': | ||||
|                 let cmd = 'empathy-accounts --select-account=' + | ||||
|                           account.get_path_suffix(); | ||||
|                 let app_info = Gio.app_info_create_from_commandline(cmd, null, 0); | ||||
|                 app_info.launch([], global.create_app_launch_context()); | ||||
|                 break; | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
|  | ||||
|         this._enabledId = account.connect('notify::enabled', | ||||
|   | ||||
| @@ -58,10 +58,15 @@ const CtrlAltTabManager = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     focusGroup: function(item, timestamp) { | ||||
|         if (item.focusCallback) | ||||
|         if (item.focusCallback) { | ||||
|             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); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     // Sort the items into a consistent order; panel first, tray last, | ||||
| @@ -84,33 +89,21 @@ const CtrlAltTabManager = new Lang.Class({ | ||||
|         let items = this._items.filter(function (item) { return item.proxy.mapped; }); | ||||
|  | ||||
|         // 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 display = screen.get_display(); | ||||
|             let windows = display.get_tab_list(Meta.TabList.DOCKS, screen, screen.get_active_workspace ()); | ||||
|             let windowTracker = Shell.WindowTracker.get_default(); | ||||
|             let textureCache = St.TextureCache.get_default(); | ||||
|             for (let i = 0; i < windows.length; i++) { | ||||
|                 let icon = null; | ||||
|                 let iconName = null; | ||||
|                 if (windows[i].get_window_type () == Meta.WindowType.DESKTOP) { | ||||
|                     iconName = 'video-display-symbolic'; | ||||
|                 } else { | ||||
|                     let app = windowTracker.get_window_app(windows[i]); | ||||
|                     if (app) | ||||
|                         icon = app.create_icon_texture(POPUP_APPICON_SIZE); | ||||
|                     else | ||||
|                         icon = textureCache.bind_pixbuf_property(windows[i], 'icon'); | ||||
|                 } | ||||
|  | ||||
|                 let icon; | ||||
|                 let app = windowTracker.get_window_app(windows[i]); | ||||
|                 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, | ||||
|                              proxy: windows[i].get_compositor_private(), | ||||
|                              focusCallback: Lang.bind(windows[i], | ||||
|                                  function(timestamp) { | ||||
|                                      Main.activateWindow(this, timestamp); | ||||
|                                  }), | ||||
|                              iconActor: icon, | ||||
|                              iconName: iconName, | ||||
|                              sortGroup: SortGroup.MIDDLE }); | ||||
|             } | ||||
|         } | ||||
| @@ -132,6 +125,8 @@ const CtrlAltTabManager = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _focusWindows: function(timestamp) { | ||||
|         global.set_stage_input_mode(Shell.StageInputMode.NORMAL); | ||||
|         global.stage.key_focus = null; | ||||
|         global.screen.focus_default_window(timestamp); | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Signals = imports.signals; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| @@ -288,7 +287,13 @@ const ShowAppsIcon = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     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.MOVE_DROP; | ||||
| @@ -296,7 +301,7 @@ const ShowAppsIcon = new Lang.Class({ | ||||
|  | ||||
|     acceptDrop: function(source, actor, x, y, time) { | ||||
|         let app = getAppFromSource(source); | ||||
|         if (!this._canRemoveApp(app)) | ||||
|         if (app == null) | ||||
|             return false; | ||||
|  | ||||
|         let id = app.get_id(); | ||||
| @@ -321,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({ | ||||
|     Name: 'DashActor', | ||||
|     Extends: St.Widget, | ||||
| @@ -424,10 +419,7 @@ const Dash = new Lang.Class({ | ||||
|  | ||||
|         this._appSystem = Shell.AppSystem.get_default(); | ||||
|  | ||||
|         this._appSystem.connect('installed-changed', Lang.bind(this, function() { | ||||
|             AppFavorites.getAppFavorites().reload(); | ||||
|             this._queueRedisplay(); | ||||
|         })); | ||||
|         this._appSystem.connect('installed-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)); | ||||
|  | ||||
| @@ -449,12 +441,6 @@ const Dash = new Lang.Class({ | ||||
|             dragMotion: Lang.bind(this, this._onDragMotion) | ||||
|         }; | ||||
|         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() { | ||||
| @@ -471,7 +457,6 @@ const Dash = new Lang.Class({ | ||||
|  | ||||
|     _endDrag: function() { | ||||
|         this._clearDragPlaceholder(); | ||||
|         this._clearEmptyDropTarget(); | ||||
|         this._showAppsIcon.setDragApp(null); | ||||
|         DND.removeDragMonitor(this._dragMonitor); | ||||
|     }, | ||||
| @@ -506,21 +491,15 @@ const Dash = new Lang.Class({ | ||||
|         Main.queueDeferredWork(this._workId); | ||||
|     }, | ||||
|  | ||||
|     _hookUpLabel: function(item, appIcon) { | ||||
|     _hookUpLabel: function(item) { | ||||
|         item.child.connect('notify::hover', Lang.bind(this, function() { | ||||
|             this._syncLabel(item, appIcon); | ||||
|             this._onHover(item); | ||||
|         })); | ||||
|  | ||||
|         Main.overview.connect('hiding', Lang.bind(this, function() { | ||||
|             this._labelShowing = false; | ||||
|             item.hideLabel(); | ||||
|         })); | ||||
|  | ||||
|         if (appIcon) { | ||||
|             appIcon.connect('sync-tooltip', Lang.bind(this, function() { | ||||
|                 this._syncLabel(item, appIcon); | ||||
|             })); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _createAppItem: function(app) { | ||||
| @@ -549,7 +528,7 @@ const Dash = new Lang.Class({ | ||||
|         item.setLabelText(app.get_name()); | ||||
|  | ||||
|         appIcon.icon.setIconSize(this.iconSize); | ||||
|         this._hookUpLabel(item, appIcon); | ||||
|         this._hookUpLabel(item); | ||||
|  | ||||
|         return item; | ||||
|     }, | ||||
| @@ -567,18 +546,15 @@ const Dash = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _syncLabel: function (item, appIcon) { | ||||
|         let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover(); | ||||
|  | ||||
|         if (shouldShow) { | ||||
|     _onHover: function (item) { | ||||
|         if (item.child.get_hover()) { | ||||
|             if (this._showLabelTimeoutId == 0) { | ||||
|                 let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT; | ||||
|                 this._showLabelTimeoutId = Mainloop.timeout_add(timeout, | ||||
|                     Lang.bind(this, function() { | ||||
|                         this._labelShowing = true; | ||||
|                         item.showLabel(); | ||||
|                         this._showLabelTimeoutId = 0; | ||||
|                         return GLib.SOURCE_REMOVE; | ||||
|                         return false; | ||||
|                     })); | ||||
|                 if (this._resetHoverTimeoutId > 0) { | ||||
|                     Mainloop.source_remove(this._resetHoverTimeoutId); | ||||
| @@ -594,8 +570,7 @@ const Dash = new Lang.Class({ | ||||
|                 this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT, | ||||
|                     Lang.bind(this, function() { | ||||
|                         this._labelShowing = false; | ||||
|                         this._resetHoverTimeoutId = 0; | ||||
|                         return GLib.SOURCE_REMOVE; | ||||
|                         return false; | ||||
|                     })); | ||||
|             } | ||||
|         } | ||||
| @@ -822,21 +797,9 @@ const Dash = new Lang.Class({ | ||||
|  | ||||
|     _clearDragPlaceholder: function() { | ||||
|         if (this._dragPlaceholder) { | ||||
|             this._animatingPlaceholdersCount++; | ||||
|             this._dragPlaceholder.animateOutAndDestroy(); | ||||
|             this._dragPlaceholder.connect('destroy', | ||||
|                 Lang.bind(this, function() { | ||||
|                     this._animatingPlaceholdersCount--; | ||||
|                 })); | ||||
|             this._dragPlaceholder = null; | ||||
|         } | ||||
|         this._dragPlaceholderPos = -1; | ||||
|     }, | ||||
|  | ||||
|     _clearEmptyDropTarget: function() { | ||||
|         if (this._emptyDropTarget) { | ||||
|             this._emptyDropTarget.animateOutAndDestroy(); | ||||
|             this._emptyDropTarget = null; | ||||
|             this._dragPlaceholderPos = -1; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -864,18 +827,23 @@ const Dash = new Lang.Class({ | ||||
|             numChildren--; | ||||
|         } | ||||
|  | ||||
|         let pos; | ||||
|         if (!this._emptyDropTarget) | ||||
|             pos = Math.floor(y * numChildren / boxHeight); | ||||
|         else | ||||
|             pos = 0; // always insert at the top when dash is empty | ||||
|         let pos = Math.floor(y * numChildren / boxHeight); | ||||
|  | ||||
|         if (pos != this._dragPlaceholderPos && pos <= numFavorites && this._animatingPlaceholdersCount == 0) { | ||||
|             this._dragPlaceholderPos = pos; | ||||
|  | ||||
|             // Don't allow positioning before or after self | ||||
|             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; | ||||
|             } | ||||
|  | ||||
| @@ -900,9 +868,9 @@ const Dash = new Lang.Class({ | ||||
|  | ||||
|         // Remove the drag placeholder if we are not in the | ||||
|         // "favorites zone" | ||||
|         if (pos > numFavorites) | ||||
|         if (pos > numFavorites && this._dragPlaceholder) { | ||||
|             this._clearDragPlaceholder(); | ||||
|  | ||||
|         } | ||||
|         if (!this._dragPlaceholder) | ||||
|             return DND.DragMotionResult.NO_DROP; | ||||
|  | ||||
|   | ||||
| @@ -49,13 +49,16 @@ const DateMenuButton = new Lang.Class({ | ||||
|             menuAlignment = 1.0 - menuAlignment; | ||||
|         this.parent(menuAlignment); | ||||
|  | ||||
|         this._clockDisplay = new St.Label({ y_align: Clutter.ActorAlign.CENTER }); | ||||
|         this.actor.label_actor = this._clockDisplay; | ||||
|         this.actor.add_actor(this._clockDisplay); | ||||
|         this.actor.add_style_class_name ('clock-display'); | ||||
|         // At this moment calendar menu is not keyboard navigable at | ||||
|         // all (so not accessible), so it doesn't make sense to set as | ||||
|         // role ATK_ROLE_MENU like other elements of the panel. | ||||
|         this.actor.accessible_role = Atk.Role.LABEL; | ||||
|  | ||||
|         hbox = new St.BoxLayout({ name: 'calendarArea' }); | ||||
|         this.menu.box.add_child(hbox); | ||||
|         this._clockDisplay = new St.Label(); | ||||
|         this.actor.add_actor(this._clockDisplay); | ||||
|  | ||||
|         hbox = new St.BoxLayout({name: 'calendarArea' }); | ||||
|         this.menu.addActor(hbox); | ||||
|  | ||||
|         // Fill up the first column | ||||
|  | ||||
| @@ -63,8 +66,9 @@ const DateMenuButton = new Lang.Class({ | ||||
|         hbox.add(vbox); | ||||
|  | ||||
|         // Date | ||||
|         this._date = new St.Label({ style_class: 'datemenu-date-label', | ||||
|                                     can_focus: true }); | ||||
|         this._date = new St.Label(); | ||||
|         this.actor.label_actor = this._clockDisplay; | ||||
|         this._date.style_class = 'datemenu-date-label'; | ||||
|         vbox.add(this._date); | ||||
|  | ||||
|         this._eventList = new Calendar.EventsList(); | ||||
| @@ -81,22 +85,27 @@ const DateMenuButton = new Lang.Class({ | ||||
|         vbox.add(this._calendar.actor); | ||||
|  | ||||
|         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.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}); | ||||
|  | ||||
|         this._openClocksItem = new PopupMenu.PopupMenuItem(_("Open Clocks")); | ||||
|         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}); | ||||
|  | ||||
|         Shell.AppSystem.get_default().connect('installed-changed', | ||||
|                                               Lang.bind(this, this._appInstalledChanged)); | ||||
|         this._appInstalledChanged(); | ||||
|  | ||||
|         item = this.menu.addSettingsAction(_("Date & Time Settings"), 'gnome-datetime-panel.desktop'); | ||||
|         if (item) { | ||||
|             item.actor.show_on_set_parent = false; | ||||
|             item.actor.can_focus = false; | ||||
|             item.actor.reparent(vbox); | ||||
|             this._dateAndTimeSeparator = separator; | ||||
|         } | ||||
| @@ -107,13 +116,33 @@ const DateMenuButton = new Lang.Class({ | ||||
|         hbox.add(this._separator); | ||||
|  | ||||
|         // 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 | ||||
|         this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) { | ||||
|             if (isOpen) { | ||||
|                 let now = new Date(); | ||||
|                 this._calendar.setDate(now); | ||||
|                 /* Passing true to setDate() forces events to be reloaded. We | ||||
|                  * want this behavior, because | ||||
|                  * | ||||
|                  *   o It will cause activation of the calendar server which is | ||||
|                  *     useful if it has crashed | ||||
|                  * | ||||
|                  *   o It will cause the calendar server to reload events which | ||||
|                  *     is useful if dynamic updates are not supported or not | ||||
|                  *     properly working | ||||
|                  * | ||||
|                  * Since this only happens when the menu is opened, the cost | ||||
|                  * isn't very big. | ||||
|                  */ | ||||
|                 this._calendar.setDate(now, true); | ||||
|                 // No need to update this._eventList as ::selected-date-changed | ||||
|                 // signal will fire | ||||
|             } | ||||
|         })); | ||||
|  | ||||
| @@ -128,39 +157,28 @@ const DateMenuButton = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _appInstalledChanged: function() { | ||||
|         this._calendarApp = undefined; | ||||
|         this._updateEventsVisibility(); | ||||
|         let app = Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop'); | ||||
|         this._openClocksItem.actor.visible = app !== null; | ||||
|     }, | ||||
|  | ||||
|     _updateEventsVisibility: function() { | ||||
|         let visible = this._eventSource.hasCalendars; | ||||
|         this._openCalendarItem.actor.visible = visible && | ||||
|             (this._getCalendarApp() != null); | ||||
|         this._openClocksItem.actor.visible = visible && | ||||
|             (this._getClockApp() != null); | ||||
|     _setEventsVisibility: function(visible) { | ||||
|         this._openCalendarItem.actor.visible = visible; | ||||
|         this._separator.visible = visible; | ||||
|         this._eventList.actor.visible = visible; | ||||
|         if (visible) { | ||||
|             let alignment = 0.25; | ||||
|             if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) | ||||
|                 alignment = 1.0 - alignment; | ||||
|             this.menu._arrowAlignment = alignment; | ||||
|           let alignment = 0.25; | ||||
|           if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) | ||||
|             alignment = 1.0 - alignment; | ||||
|           this.menu._arrowAlignment = alignment; | ||||
|           this._eventList.actor.get_parent().show(); | ||||
|         } else { | ||||
|             this.menu._arrowAlignment = 0.5; | ||||
|           this.menu._arrowAlignment = 0.5; | ||||
|           this._eventList.actor.get_parent().hide(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _setEventSource: function(eventSource) { | ||||
|         if (this._eventSource) | ||||
|             this._eventSource.destroy(); | ||||
|  | ||||
|         this._calendar.setEventSource(eventSource); | ||||
|         this._eventList.setEventSource(eventSource); | ||||
|  | ||||
|         this._eventSource = eventSource; | ||||
|         this._eventSource.connect('notify::has-calendars', Lang.bind(this, function() { | ||||
|             this._updateEventsVisibility(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _sessionUpdated: function() { | ||||
| @@ -169,10 +187,10 @@ const DateMenuButton = new Lang.Class({ | ||||
|         if (showEvents) { | ||||
|             eventSource = new Calendar.DBusEventSource(); | ||||
|         } else { | ||||
|             eventSource = new Calendar.EmptyEventSource(); | ||||
|             eventSource = null; | ||||
|         } | ||||
|         this._setEventSource(eventSource); | ||||
|         this._updateEventsVisibility(); | ||||
|         this._setEventsVisibility(showEvents); | ||||
|  | ||||
|         // This needs to be handled manually, as the code to | ||||
|         // autohide separators doesn't work across the vbox | ||||
| @@ -189,34 +207,16 @@ const DateMenuButton = new Lang.Class({ | ||||
|         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() { | ||||
|         this.menu.close(); | ||||
|  | ||||
|         let app = this._getCalendarApp(); | ||||
|         if (app.get_id() == 'evolution.desktop') | ||||
|             app = Gio.DesktopAppInfo.new('evolution-calendar.desktop'); | ||||
|         app.launch([], global.create_app_launch_context(0, -1)); | ||||
|         let app = Gio.AppInfo.get_default_for_type('text/calendar', false); | ||||
|         app.launch([], global.create_app_launch_context()); | ||||
|     }, | ||||
|  | ||||
|     _onOpenClocksActivate: function() { | ||||
|         this.menu.close(); | ||||
|         let app = this._getClockApp(); | ||||
|         let app = Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop'); | ||||
|         app.activate(); | ||||
|     } | ||||
| }); | ||||
|   | ||||
							
								
								
									
										218
									
								
								js/ui/dnd.js
									
									
									
									
									
								
							
							
						
						
									
										218
									
								
								js/ui/dnd.js
									
									
									
									
									
								
							| @@ -1,11 +1,9 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const St = imports.gi.St; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| const Tweener = imports.ui.tweener; | ||||
| @@ -28,9 +26,9 @@ const DragMotionResult = { | ||||
| }; | ||||
|  | ||||
| const DRAG_CURSOR_MAP = { | ||||
|     0: Meta.Cursor.DND_UNSUPPORTED_TARGET, | ||||
|     1: Meta.Cursor.DND_COPY, | ||||
|     2: Meta.Cursor.DND_MOVE | ||||
|     0: Shell.Cursor.DND_UNSUPPORTED_TARGET, | ||||
|     1: Shell.Cursor.DND_COPY, | ||||
|     2: Shell.Cursor.DND_MOVE | ||||
| }; | ||||
|  | ||||
| const DragDropResult = { | ||||
| @@ -45,7 +43,9 @@ let dragMonitors = []; | ||||
|  | ||||
| function _getEventHandlerActor() { | ||||
|     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); | ||||
|         // We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen | ||||
|         // when you've grabbed the pointer. | ||||
| @@ -85,8 +85,11 @@ const _Draggable = new Lang.Class({ | ||||
|  | ||||
|         this.actor.connect('destroy', Lang.bind(this, function() { | ||||
|             this._actorDestroyed = true; | ||||
|  | ||||
|             if (this._dragInProgress && this._dragCancellable) | ||||
|             // 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._cancelDrag(global.get_current_time()); | ||||
|             this.disconnectAll(); | ||||
|         })); | ||||
| @@ -99,17 +102,22 @@ const _Draggable = new Lang.Class({ | ||||
|         this._buttonDown = false; // The mouse button has been pressed and has not yet been released. | ||||
|         this._dragInProgress = false; // The drag has been started, and has not been dropped or cancelled yet. | ||||
|         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; | ||||
|  | ||||
|         // 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; | ||||
|     }, | ||||
|  | ||||
|     _onButtonPress : function (actor, event) { | ||||
|         if (event.get_button() != 1) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         if (Tweener.getTweenCount(actor)) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         this._buttonDown = true; | ||||
|         this._grabActor(); | ||||
| @@ -118,7 +126,7 @@ const _Draggable = new Lang.Class({ | ||||
|         this._dragStartX = stageX; | ||||
|         this._dragStartY = stageY; | ||||
|  | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _grabActor: function() { | ||||
| @@ -138,16 +146,16 @@ const _Draggable = new Lang.Class({ | ||||
|  | ||||
|     _grabEvents: function() { | ||||
|         if (!this._eventsGrabbed) { | ||||
|             this._eventsGrabbed = Main.pushModal(_getEventHandlerActor()); | ||||
|             if (this._eventsGrabbed) | ||||
|                 Clutter.grab_pointer(_getEventHandlerActor()); | ||||
|             Clutter.grab_pointer(_getEventHandlerActor()); | ||||
|             Clutter.grab_keyboard(_getEventHandlerActor()); | ||||
|             this._eventsGrabbed = true; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _ungrabEvents: function() { | ||||
|         if (this._eventsGrabbed) { | ||||
|             Clutter.ungrab_pointer(); | ||||
|             Main.popModal(_getEventHandlerActor()); | ||||
|             Clutter.ungrab_keyboard(); | ||||
|             this._eventsGrabbed = false; | ||||
|         } | ||||
|     }, | ||||
| @@ -164,11 +172,11 @@ const _Draggable = new Lang.Class({ | ||||
|             } else if (this._dragActor != null && !this._animationInProgress) { | ||||
|                 // Drag must have been cancelled with Esc. | ||||
|                 this._dragComplete(); | ||||
|                 return Clutter.EVENT_STOP; | ||||
|                 return true; | ||||
|             } else { | ||||
|                 // Drag has never started. | ||||
|                 this._ungrabActor(); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|                 return false; | ||||
|             } | ||||
|         // We intercept MOTION event to figure out if the drag has started and to draw | ||||
|         // this._dragActor under the pointer when dragging is in progress | ||||
| @@ -184,11 +192,16 @@ const _Draggable = new Lang.Class({ | ||||
|             let symbol = event.get_key_symbol(); | ||||
|             if (symbol == Clutter.Escape) { | ||||
|                 this._cancelDrag(event.get_time()); | ||||
|                 return Clutter.EVENT_STOP; | ||||
|                 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 Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -229,14 +242,14 @@ const _Draggable = new Lang.Class({ | ||||
|         if (this._onEventId) | ||||
|             this._ungrabActor(); | ||||
|         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._dragY = this._dragStartY = stageY; | ||||
|  | ||||
|         if (this.actor._delegate && this.actor._delegate.getDragActor) { | ||||
|             this._dragActor = this.actor._delegate.getDragActor(); | ||||
|             Main.uiGroup.add_child(this._dragActor); | ||||
|             this._dragActor.reparent(Main.uiGroup); | ||||
|             this._dragActor.raise_top(); | ||||
|             Shell.util_set_hidden_from_pick(this._dragActor, true); | ||||
|  | ||||
| @@ -275,20 +288,19 @@ const _Draggable = new Lang.Class({ | ||||
|             this._dragOrigY = this._dragActor.y; | ||||
|             this._dragOrigScale = this._dragActor.scale_x; | ||||
|  | ||||
|             // Set the actor's scale such that it will keep the same | ||||
|             // transformed size when it's reparented to the uiGroup | ||||
|             let [scaledWidth, scaledHeight] = this.actor.get_transformed_size(); | ||||
|             this._dragActor.set_scale(scaledWidth / this.actor.width, | ||||
|                                       scaledHeight / this.actor.height); | ||||
|             this._dragActor.reparent(Main.uiGroup); | ||||
|             this._dragActor.raise_top(); | ||||
|             Shell.util_set_hidden_from_pick(this._dragActor, true); | ||||
|  | ||||
|             let [actorStageX, actorStageY] = this.actor.get_transformed_position(); | ||||
|             this._dragOffsetX = actorStageX - this._dragStartX; | ||||
|             this._dragOffsetY = actorStageY - this._dragStartY; | ||||
|  | ||||
|             this._dragOrigParent.remove_actor(this._dragActor); | ||||
|             Main.uiGroup.add_child(this._dragActor); | ||||
|             this._dragActor.raise_top(); | ||||
|             Shell.util_set_hidden_from_pick(this._dragActor, true); | ||||
|             // Set the actor's scale such that it will keep the same | ||||
|             // transformed size when it's reparented to the uiGroup | ||||
|             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; | ||||
| @@ -345,66 +357,60 @@ const _Draggable = new Lang.Class({ | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _updateDragHover : function () { | ||||
|         this._updateHoverId = 0; | ||||
|         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 GLib.SOURCE_REMOVE; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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 GLib.SOURCE_REMOVE; | ||||
|                 } | ||||
|             } | ||||
|             target = target.get_parent(); | ||||
|         } | ||||
|         global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG); | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _queueUpdateDragHover: function() { | ||||
|         if (this._updateHoverId) | ||||
|             return; | ||||
|  | ||||
|         this._updateHoverId = GLib.idle_add(GLib.PRIORITY_DEFAULT, | ||||
|                                             Lang.bind(this, this._updateDragHover)); | ||||
|     }, | ||||
|  | ||||
|     _updateDragPosition : function (event) { | ||||
|         let [stageX, stageY] = event.get_coords(); | ||||
|         this._dragX = stageX; | ||||
|         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; | ||||
|     }, | ||||
|  | ||||
| @@ -433,11 +439,6 @@ const _Draggable = new Lang.Class({ | ||||
|                 } | ||||
|         } | ||||
|  | ||||
|         // At this point it is too late to cancel a drag by destroying | ||||
|         // the actor, the fate of which is decided by acceptDrop and its | ||||
|         // side-effects | ||||
|         this._dragCancellable = false; | ||||
|  | ||||
|         while (target) { | ||||
|             if (target._delegate && target._delegate.acceptDrop) { | ||||
|                 let [r, targX, targY] = target.transform_stage_point(dropX, dropY); | ||||
| @@ -446,6 +447,8 @@ const _Draggable = new Lang.Class({ | ||||
|                                                 targX, | ||||
|                                                 targY, | ||||
|                                                 event.get_time())) { | ||||
|                     if (this._actorDestroyed) | ||||
|                         return true; | ||||
|                     // If it accepted the drop without taking the actor, | ||||
|                     // handle it ourselves. | ||||
|                     if (this._dragActor.get_parent() == Main.uiGroup) { | ||||
| @@ -457,7 +460,7 @@ const _Draggable = new Lang.Class({ | ||||
|                     } | ||||
|  | ||||
|                     this._dragInProgress = false; | ||||
|                     global.screen.set_cursor(Meta.Cursor.DEFAULT); | ||||
|                     global.unset_cursor(); | ||||
|                     this.emit('drag-end', event.get_time(), true); | ||||
|                     this._dragComplete(); | ||||
|                     return true; | ||||
| @@ -509,7 +512,7 @@ const _Draggable = new Lang.Class({ | ||||
|         let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation(); | ||||
|  | ||||
|         if (this._actorDestroyed) { | ||||
|             global.screen.set_cursor(Meta.Cursor.DEFAULT); | ||||
|             global.unset_cursor(); | ||||
|             if (!this._buttonDown) | ||||
|                 this._dragComplete(); | ||||
|             this.emit('drag-end', eventTime, false); | ||||
| @@ -557,14 +560,13 @@ const _Draggable = new Lang.Class({ | ||||
|  | ||||
|     _onAnimationComplete : function (dragActor, eventTime) { | ||||
|         if (this._dragOrigParent) { | ||||
|             Main.uiGroup.remove_child(this._dragActor); | ||||
|             this._dragOrigParent.add_actor(this._dragActor); | ||||
|             dragActor.reparent(this._dragOrigParent); | ||||
|             dragActor.set_scale(this._dragOrigScale, this._dragOrigScale); | ||||
|             dragActor.set_position(this._dragOrigX, this._dragOrigY); | ||||
|         } else { | ||||
|             dragActor.destroy(); | ||||
|         } | ||||
|         global.screen.set_cursor(Meta.Cursor.DEFAULT); | ||||
|         global.unset_cursor(); | ||||
|         this.emit('drag-end', eventTime, false); | ||||
|  | ||||
|         this._animationInProgress = false; | ||||
| @@ -572,16 +574,32 @@ const _Draggable = new Lang.Class({ | ||||
|             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() { | ||||
|         if (!this._actorDestroyed) | ||||
|             Shell.util_set_hidden_from_pick(this._dragActor, false); | ||||
|  | ||||
|         this._ungrabEvents(); | ||||
|         global.sync_pointer(); | ||||
|  | ||||
|         if (this._updateHoverId) { | ||||
|             GLib.source_remove(this._updateHoverId); | ||||
|             this._updateHoverId = 0; | ||||
|         if (this._firstLeaveActor) { | ||||
|             this._syncHover(this._firstLeaveActor); | ||||
|             this._firstLeaveActor = null; | ||||
|         } | ||||
|  | ||||
|         if (this._lastEnterActor) { | ||||
|             this._syncHover(this._lastEnterActor); | ||||
|             this._lastEnterActor = null; | ||||
|         } | ||||
|  | ||||
|         this._dragActor = undefined; | ||||
|   | ||||
| @@ -13,11 +13,14 @@ | ||||
|  * 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, see <http://www.gnu.org/licenses/>. | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  */ | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const AccountsService = imports.gi.AccountsService; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| @@ -29,10 +32,10 @@ const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const GnomeSession = imports.misc.gnomeSession; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| const Main = imports.ui.main; | ||||
| const ModalDialog = imports.ui.modalDialog; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const UserWidget = imports.ui.userWidget; | ||||
| const UserMenu = imports.ui.userMenu; | ||||
|  | ||||
| let _endSessionDialog = null; | ||||
|  | ||||
| @@ -41,118 +44,81 @@ const _DIALOG_ICON_SIZE = 32; | ||||
|  | ||||
| const GSM_SESSION_MANAGER_LOGOUT_FORCE = 2; | ||||
|  | ||||
| const EndSessionDialogIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager.EndSessionDialog"> \ | ||||
| <method name="Open"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="ao" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Close" /> \ | ||||
| <signal name="ConfirmedLogout" /> \ | ||||
| <signal name="ConfirmedReboot" /> \ | ||||
| <signal name="ConfirmedShutdown" /> \ | ||||
| <signal name="Canceled" /> \ | ||||
| <signal name="Closed" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const EndSessionDialogIface = <interface name="org.gnome.SessionManager.EndSessionDialog"> | ||||
| <method name="Open"> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="ao" direction="in" /> | ||||
| </method> | ||||
| <method name="Close" /> | ||||
| <signal name="ConfirmedLogout" /> | ||||
| <signal name="ConfirmedReboot" /> | ||||
| <signal name="ConfirmedShutdown" /> | ||||
| <signal name="Canceled" /> | ||||
| <signal name="Closed" /> | ||||
| </interface>; | ||||
|  | ||||
| const logoutDialogContent = { | ||||
|     subjectWithUser: C_("title", "Log Out %s"), | ||||
|     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.", | ||||
|                         "%s will be logged out automatically in %d seconds.", | ||||
|                         seconds).format(user, seconds); | ||||
|     }, | ||||
|     description: function(seconds) { | ||||
|     uninhibitedDescription: function(seconds) { | ||||
|         return ngettext("You will be logged out automatically in %d second.", | ||||
|                         "You will be logged out automatically in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     endDescription: _("Logging out of the system."), | ||||
|     confirmButtons: [{ signal: 'ConfirmedLogout', | ||||
|                        label:  C_("button", "Log Out") }], | ||||
|     iconStyleClass: 'end-session-dialog-logout-icon', | ||||
|     showOtherSessions: false, | ||||
|     iconStyleClass: 'end-session-dialog-logout-icon' | ||||
| }; | ||||
|  | ||||
| const shutdownDialogContent = { | ||||
|     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.", | ||||
|                         "The system will power off automatically in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     endDescription: _("Powering off the system."), | ||||
|     confirmButtons: [{ signal: 'ConfirmedReboot', | ||||
|                        label:  C_("button", "Restart") }, | ||||
|                      { signal: 'ConfirmedShutdown', | ||||
|                        label:  C_("button", "Power Off") }], | ||||
|     iconName: 'system-shutdown-symbolic', | ||||
|     iconStyleClass: 'end-session-dialog-shutdown-icon', | ||||
|     showOtherSessions: true, | ||||
|     iconStyleClass: 'end-session-dialog-shutdown-icon' | ||||
| }; | ||||
|  | ||||
| const restartDialogContent = { | ||||
|     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.", | ||||
|                         "The system will restart automatically in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     endDescription: _("Restarting the system."), | ||||
|     confirmButtons: [{ signal: 'ConfirmedReboot', | ||||
|                        label:  C_("button", "Restart") }], | ||||
|     iconName: 'view-refresh-symbolic', | ||||
|     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, | ||||
|     iconStyleClass: 'end-session-dialog-shutdown-icon' | ||||
| }; | ||||
|  | ||||
| const DialogContent = { | ||||
|     0 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT */: logoutDialogContent, | ||||
|     1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */: shutdownDialogContent, | ||||
|     2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */: restartDialogContent, | ||||
|     3: restartInstallDialogContent | ||||
|     2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */: restartDialogContent | ||||
| }; | ||||
|  | ||||
| const MAX_USERS_IN_SESSION_DIALOG = 5; | ||||
|  | ||||
| const LogindSessionIface = '<node> \ | ||||
| <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> \ | ||||
| </node>'; | ||||
|  | ||||
| const LogindSession = Gio.DBusProxy.makeProxyWrapper(LogindSessionIface); | ||||
|  | ||||
| function findAppFromInhibitor(inhibitor) { | ||||
|     let desktopFile; | ||||
|     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; | ||||
|     } | ||||
|     let [desktopFile] = inhibitor.GetAppIdSync(); | ||||
|  | ||||
|     if (!GLib.str_has_suffix(desktopFile, '.desktop')) | ||||
|         desktopFile += '.desktop'; | ||||
| @@ -160,6 +126,58 @@ function findAppFromInhibitor(inhibitor) { | ||||
|     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 | ||||
| // until the last 10 seconds, then it shows updates every | ||||
| // second.  This function takes a given time and returns | ||||
| @@ -207,26 +225,24 @@ const EndSessionDialog = new Lang.Class({ | ||||
|     Extends: ModalDialog.ModalDialog, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent({ styleClass: 'end-session-dialog', | ||||
|                       destroyOnClose: false }); | ||||
|         this.parent({ styleClass: 'end-session-dialog' }); | ||||
|  | ||||
|         this._loginManager = LoginManager.getLoginManager(); | ||||
|         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._user = AccountsService.UserManager.get_default().get_user(GLib.get_user_name()); | ||||
|  | ||||
|         this._secondsLeft = 0; | ||||
|         this._totalSecondsToStayOpen = 0; | ||||
|         this._applications = []; | ||||
|         this._sessions = []; | ||||
|         this._inhibitors = []; | ||||
|  | ||||
|         this.connect('destroy', | ||||
|                      Lang.bind(this, this._onDestroy)); | ||||
|         this.connect('opened', | ||||
|                      Lang.bind(this, this._onOpened)); | ||||
|  | ||||
|         this._userLoadedId = this._user.connect('notify::is_loaded', Lang.bind(this, this._sync)); | ||||
|         this._userChangedId = this._user.connect('changed', Lang.bind(this, this._sync)); | ||||
|         this._userLoadedId = this._user.connect('notify::is_loaded', | ||||
|                                                 Lang.bind(this, this._updateContent)); | ||||
|  | ||||
|         this._userChangedId = this._user.connect('changed', | ||||
|                                                  Lang.bind(this, this._updateContent)); | ||||
|  | ||||
|         let mainContentLayout = new St.BoxLayout({ vertical: false }); | ||||
|         this.contentLayout.add(mainContentLayout, | ||||
| @@ -247,9 +263,7 @@ const EndSessionDialog = new Lang.Class({ | ||||
|         this._subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject' }); | ||||
|  | ||||
|         messageLayout.add(this._subjectLabel, | ||||
|                           { x_fill: false, | ||||
|                             y_fill:  false, | ||||
|                             x_align: St.Align.START, | ||||
|                           { y_fill:  false, | ||||
|                             y_align: St.Align.START }); | ||||
|  | ||||
|         this._descriptionLabel = new St.Label({ style_class: 'end-session-dialog-description' }); | ||||
| @@ -260,30 +274,28 @@ const EndSessionDialog = new Lang.Class({ | ||||
|                           { y_fill:  true, | ||||
|                             y_align: St.Align.START }); | ||||
|  | ||||
|         this._scrollView = new St.ScrollView({ style_class: 'end-session-dialog-list' }); | ||||
|         this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); | ||||
|         this.contentLayout.add(this._scrollView, | ||||
|         let scrollView = new St.ScrollView({ style_class: 'end-session-dialog-app-list'}); | ||||
|         scrollView.set_policy(Gtk.PolicyType.NEVER, | ||||
|                               Gtk.PolicyType.AUTOMATIC); | ||||
|         this.contentLayout.add(scrollView, | ||||
|                                { x_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._applicationList = new St.BoxLayout({ vertical: true }); | ||||
|         scrollView.add_actor(this._applicationList); | ||||
|  | ||||
|         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({ style_class: 'end-session-dialog-app-list', | ||||
|                                                    vertical: true }); | ||||
|         this._inhibitorSection.add_actor(this._applicationHeader); | ||||
|         this._inhibitorSection.add_actor(this._applicationList); | ||||
|         this._applicationList.connect('actor-added', | ||||
|                                       Lang.bind(this, function() { | ||||
|                                           if (this._applicationList.get_n_children() == 1) | ||||
|                                               scrollView.show(); | ||||
|                                       })); | ||||
|  | ||||
|         this._sessionHeader = new St.Label({ style_class: 'end-session-dialog-list-header', | ||||
|                                              text: _("Other users are logged in.") }); | ||||
|         this._sessionList = new St.BoxLayout({ style_class: 'end-session-dialog-session-list', | ||||
|                                                vertical: true }); | ||||
|         this._inhibitorSection.add_actor(this._sessionHeader); | ||||
|         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.export(Gio.DBus.session, '/org/gnome/SessionManager/EndSessionDialog'); | ||||
| @@ -294,42 +306,52 @@ const EndSessionDialog = new Lang.Class({ | ||||
|         this._user.disconnect(this._userChangedId); | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let open = (this.state == ModalDialog.State.OPENING || this.state == ModalDialog.State.OPENED); | ||||
|         if (!open) | ||||
|     _updateDescription: function() { | ||||
|         if (this.state != ModalDialog.State.OPENING && | ||||
|             this.state != ModalDialog.State.OPENED) | ||||
|             return; | ||||
|  | ||||
|         if (this._type == 2 && this._updatesFile.query_exists(null)) | ||||
|             this._type = 3; | ||||
|  | ||||
|         let dialogContent = DialogContent[this._type]; | ||||
|  | ||||
|         let subject = dialogContent.subject; | ||||
|  | ||||
|         let description; | ||||
|         let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen, | ||||
|                                                   this._secondsLeft, | ||||
|                                                   10); | ||||
|  | ||||
|         if (this._user.is_loaded) { | ||||
|             let realName = this._user.get_real_name(); | ||||
|         if (this._inhibitors.length > 0) { | ||||
|             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 (dialogContent.subjectWithUser) | ||||
|                     subject = dialogContent.subjectWithUser.format(realName); | ||||
|             if (this._user.is_loaded) { | ||||
|                 let realName = this._user.get_real_name(); | ||||
|  | ||||
|                 if (dialogContent.descriptionWithUser) | ||||
|                     description = dialogContent.descriptionWithUser(realName, displayTime); | ||||
|                 else | ||||
|                     description = dialogContent.description(displayTime); | ||||
|                 if (realName != null) { | ||||
|                     if (dialogContent.subjectWithUser) | ||||
|                         subject = dialogContent.subjectWithUser.format(realName); | ||||
|  | ||||
|                     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._descriptionLabel, description); | ||||
|     }, | ||||
|  | ||||
|     _updateContent: function() { | ||||
|         if (this.state != ModalDialog.State.OPENING && | ||||
|             this.state != ModalDialog.State.OPENED) | ||||
|             return; | ||||
|  | ||||
|         let dialogContent = DialogContent[this._type]; | ||||
|         if (dialogContent.iconName) { | ||||
| @@ -337,18 +359,14 @@ const EndSessionDialog = new Lang.Class({ | ||||
|                                                 icon_size: _DIALOG_ICON_SIZE, | ||||
|                                                 style_class: dialogContent.iconStyleClass }); | ||||
|         } else { | ||||
|             let avatarWidget = new UserWidget.Avatar(this._user, | ||||
|                                                      { iconSize: _DIALOG_ICON_SIZE, | ||||
|                                                        styleClass: dialogContent.iconStyleClass }); | ||||
|             let avatarWidget = new UserMenu.UserAvatarWidget(this._user, | ||||
|                                                              { iconSize: _DIALOG_ICON_SIZE, | ||||
|                                                                styleClass: dialogContent.iconStyleClass }); | ||||
|             this._iconBin.child = avatarWidget.actor; | ||||
|             avatarWidget.update(); | ||||
|         } | ||||
|  | ||||
|         let hasApplications = this._applications.length > 0; | ||||
|         let hasSessions = this._sessions.length > 0; | ||||
|         this._scrollView.visible = hasApplications || hasSessions; | ||||
|         this._applicationHeader.visible = hasApplications; | ||||
|         this._sessionHeader.visible = hasSessions; | ||||
|         this._updateDescription(); | ||||
|     }, | ||||
|  | ||||
|     _updateButtons: function() { | ||||
| @@ -394,7 +412,8 @@ const EndSessionDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onOpened: function() { | ||||
|         this._sync(); | ||||
|         if (this._inhibitors.length == 0) | ||||
|             this._startTimer(); | ||||
|     }, | ||||
|  | ||||
|     _startTimer: function() { | ||||
| @@ -408,21 +427,20 @@ const EndSessionDialog = new Lang.Class({ | ||||
|  | ||||
|                 this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed; | ||||
|                 if (this._secondsLeft > 0) { | ||||
|                     this._sync(); | ||||
|                     return GLib.SOURCE_CONTINUE; | ||||
|                     this._updateDescription(); | ||||
|                     return true; | ||||
|                 } | ||||
|  | ||||
|                 let dialogContent = DialogContent[this._type]; | ||||
|                 let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1]; | ||||
|                 this._confirm(button.signal); | ||||
|                 this._timerId = 0; | ||||
|  | ||||
|                 return GLib.SOURCE_REMOVE; | ||||
|                 return false; | ||||
|             })); | ||||
|     }, | ||||
|  | ||||
|     _stopTimer: function() { | ||||
|         if (this._timerId > 0) { | ||||
|         if (this._timerId != 0) { | ||||
|             Mainloop.source_remove(this._timerId); | ||||
|             this._timerId = 0; | ||||
|         } | ||||
| @@ -430,33 +448,8 @@ const EndSessionDialog = new Lang.Class({ | ||||
|         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) { | ||||
|         if (this._applications.indexOf(inhibitor) < 0) { | ||||
|         if (this._inhibitors.indexOf(inhibitor) < 0) { | ||||
|             // Stale inhibitor | ||||
|             return; | ||||
|         } | ||||
| @@ -464,91 +457,28 @@ const EndSessionDialog = new Lang.Class({ | ||||
|         let app = findAppFromInhibitor(inhibitor); | ||||
|  | ||||
|         if (app) { | ||||
|             let actor = this._constructListItemForApp(inhibitor, app); | ||||
|             this._applicationList.add(actor); | ||||
|             let [reason] = inhibitor.GetReasonSync(); | ||||
|             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 { | ||||
|             // 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(); | ||||
|     }, | ||||
|  | ||||
|     _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(); | ||||
|         })); | ||||
|         this._updateContent(); | ||||
|     }, | ||||
|  | ||||
|     OpenAsync: function(parameters, invocation) { | ||||
|         let [type, timestamp, totalSecondsToStayOpen, inhibitorObjectPaths] = parameters; | ||||
|         this._totalSecondsToStayOpen = totalSecondsToStayOpen; | ||||
|         this._type = type; | ||||
|  | ||||
|         this._applications = []; | ||||
|         this._inhibitors = []; | ||||
|         this._applicationList.destroy_all_children(); | ||||
|  | ||||
|         this._sessions = []; | ||||
|         this._sessionList.destroy_all_children(); | ||||
|         this._type = type; | ||||
|  | ||||
|         if (!(this._type in DialogContent)) { | ||||
|             invocation.return_dbus_error('org.gnome.Shell.ModalDialog.TypeError', | ||||
| @@ -561,12 +491,9 @@ const EndSessionDialog = new Lang.Class({ | ||||
|                 this._onInhibitorLoaded(proxy); | ||||
|             })); | ||||
|  | ||||
|             this._applications.push(inhibitor); | ||||
|             this._inhibitors.push(inhibitor); | ||||
|         } | ||||
|  | ||||
|         if (DialogContent[type].showOtherSessions) | ||||
|             this._loadSessions(); | ||||
|  | ||||
|         this._updateButtons(); | ||||
|  | ||||
|         if (!this.open(timestamp)) { | ||||
| @@ -575,8 +502,7 @@ const EndSessionDialog = new Lang.Class({ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._startTimer(); | ||||
|         this._sync(); | ||||
|         this._updateContent(); | ||||
|  | ||||
|         let signalId = this.connect('opened', | ||||
|                                     Lang.bind(this, function() { | ||||
|   | ||||
| @@ -10,7 +10,6 @@ const Clutter = imports.gi.Clutter;; | ||||
| const Gettext = imports.gettext; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| 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) { | ||||
|     return function() { | ||||
|         return func([].join.call(arguments, ', ')); | ||||
| @@ -77,12 +60,6 @@ function init() { | ||||
|     _patchContainerClass(St.BoxLayout); | ||||
|     _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() { | ||||
|         return St.describe_actor(this); | ||||
|     }; | ||||
|   | ||||
| @@ -201,7 +201,7 @@ const InstallExtensionDialog = new Lang.Class({ | ||||
|                            default: true | ||||
|                          }]); | ||||
|  | ||||
|         let message = _("Download and install “%s” from extensions.gnome.org?").format(info.name); | ||||
|         let message = _("Download and install '%s' from extensions.gnome.org?").format(info.name); | ||||
|  | ||||
|         let box = new St.BoxLayout(); | ||||
|         this.contentLayout.add(box); | ||||
|   | ||||
| @@ -76,11 +76,7 @@ function disableExtension(uuid) { | ||||
|         theme.unload_stylesheet(extension.stylesheet.get_path()); | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|         extension.stateObj.disable(); | ||||
|     } catch(e) { | ||||
|         logExtensionError(uuid, e); | ||||
|     } | ||||
|     extension.stateObj.disable(); | ||||
|  | ||||
|     for (let i = 0; i < order.length; i++) { | ||||
|         let uuid = order[i]; | ||||
| @@ -93,10 +89,8 @@ function disableExtension(uuid) { | ||||
|  | ||||
|     extensionOrder.splice(orderIdx, 1); | ||||
|  | ||||
|     if ( extension.state != ExtensionState.ERROR ) { | ||||
|         extension.state = ExtensionState.DISABLED; | ||||
|         _signals.emit('extension-state-changed', extension); | ||||
|     } | ||||
|     extension.state = ExtensionState.DISABLED; | ||||
|     _signals.emit('extension-state-changed', extension); | ||||
| } | ||||
|  | ||||
| function enableExtension(uuid) { | ||||
| @@ -123,15 +117,10 @@ function enableExtension(uuid) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|         extension.stateObj.enable(); | ||||
|         extension.state = ExtensionState.ENABLED; | ||||
|         _signals.emit('extension-state-changed', extension); | ||||
|         return; | ||||
|     } catch(e) { | ||||
|         logExtensionError(uuid, e); | ||||
|         return; | ||||
|     } | ||||
|     extension.stateObj.enable(); | ||||
|  | ||||
|     extension.state = ExtensionState.ENABLED; | ||||
|     _signals.emit('extension-state-changed', extension); | ||||
| } | ||||
|  | ||||
| function logExtensionError(uuid, error) { | ||||
| @@ -161,8 +150,7 @@ function loadExtension(extension) { | ||||
|     } else { | ||||
|         let enabled = enabledExtensions.indexOf(extension.uuid) != -1; | ||||
|         if (enabled) { | ||||
|             if (!initExtension(extension.uuid)) | ||||
|                 return; | ||||
|             initExtension(extension.uuid); | ||||
|             if (extension.state == ExtensionState.DISABLED) | ||||
|                 enableExtension(extension.uuid); | ||||
|         } else { | ||||
| @@ -217,12 +205,7 @@ function initExtension(uuid) { | ||||
|     extensionModule = extension.imports.extension; | ||||
|  | ||||
|     if (extensionModule.init) { | ||||
|         try { | ||||
|             extensionState = extensionModule.init(extension); | ||||
|         } catch(e) { | ||||
|             logExtensionError(uuid, e); | ||||
|             return false; | ||||
|         } | ||||
|         extensionState = extensionModule.init(extension); | ||||
|     } | ||||
|  | ||||
|     if (!extensionState) | ||||
| @@ -231,7 +214,6 @@ function initExtension(uuid) { | ||||
|  | ||||
|     extension.state = ExtensionState.DISABLED; | ||||
|     _signals.emit('extension-loaded', uuid); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function getEnabledExtensions() { | ||||
| @@ -253,7 +235,11 @@ function onEnabledExtensionsChanged() { | ||||
|     newEnabledExtensions.filter(function(uuid) { | ||||
|         return enabledExtensions.indexOf(uuid) == -1; | ||||
|     }).forEach(function(uuid) { | ||||
|         enableExtension(uuid); | ||||
|         try { | ||||
|             enableExtension(uuid); | ||||
|         } catch(e) { | ||||
|             logExtensionError(uuid, e); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     // Find and disable all the newly disabled extensions: UUIDs found in the | ||||
| @@ -261,7 +247,11 @@ function onEnabledExtensionsChanged() { | ||||
|     enabledExtensions.filter(function(item) { | ||||
|         return newEnabledExtensions.indexOf(item) == -1; | ||||
|     }).forEach(function(uuid) { | ||||
|         disableExtension(uuid); | ||||
|         try { | ||||
|             disableExtension(uuid); | ||||
|         } catch(e) { | ||||
|             logExtensionError(uuid, e); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     enabledExtensions = newEnabledExtensions; | ||||
| @@ -272,8 +262,12 @@ function _loadExtensions() { | ||||
|     enabledExtensions = getEnabledExtensions(); | ||||
|  | ||||
|     let finder = new ExtensionUtils.ExtensionFinder(); | ||||
|     finder.connect('extension-found', function(finder, extension) { | ||||
|         loadExtension(extension); | ||||
|     finder.connect('extension-found', function(signals, extension) { | ||||
|         try { | ||||
|             loadExtension(extension); | ||||
|         } catch(e) { | ||||
|             logExtensionError(extension.uuid, e); | ||||
|         } | ||||
|     }); | ||||
|     finder.scanExtensions(); | ||||
| } | ||||
| @@ -298,7 +292,7 @@ function disableAllExtensions() { | ||||
|         return; | ||||
|  | ||||
|     if (initted) { | ||||
|         extensionOrder.slice().reverse().forEach(function(uuid) { | ||||
|         enabledExtensions.forEach(function(uuid) { | ||||
|             disableExtension(uuid); | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -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); | ||||
| @@ -10,31 +10,6 @@ const St = imports.gi.St; | ||||
| const Main = imports.ui.main; | ||||
| 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: | ||||
| // @owner: the actor that owns the GrabHelper | ||||
| // @params: optional parameters to pass to Main.pushModal() | ||||
| @@ -56,9 +31,14 @@ const GrabHelper = new Lang.Class({ | ||||
|         this._grabStack = []; | ||||
|  | ||||
|         this._actors = []; | ||||
|         this._capturedEventId = 0; | ||||
|         this._keyFocusNotifyId = 0; | ||||
|         this._focusWindowChangedId = 0; | ||||
|         this._ignoreRelease = false; | ||||
|         this._isUngrabbingCount = 0; | ||||
|  | ||||
|         this._modalCount = 0; | ||||
|         this._grabFocusCount = 0; | ||||
|     }, | ||||
|  | ||||
|     // addActor: | ||||
| @@ -138,36 +118,38 @@ const GrabHelper = new Lang.Class({ | ||||
|     // grab: | ||||
|     // @params: A bunch of parameters, see below | ||||
|     // | ||||
|     // The general effect of a "grab" is to ensure that the passed in actor | ||||
|     // and all actors inside the grab get exclusive control of the mouse and | ||||
|     // keyboard, with the grab automatically being dropped if the user tries | ||||
|     // to dismiss it. The actor is passed in through @params.actor. | ||||
|     // Grabs the mouse and keyboard, according to the GrabHelper's | ||||
|     // parameters. If @newFocus is not %null, then the keyboard focus | ||||
|     // is moved to the first #StWidget:can-focus widget inside it. | ||||
|     // | ||||
|     // grab() can be called multiple times, with the scope of the grab being | ||||
|     // changed to a different actor every time. A nested grab does not have | ||||
|     // to have its grabbed actor inside the parent grab actors. | ||||
|     // The grab will automatically be dropped if: | ||||
|     //   - The user clicks outside the grabbed actors | ||||
|     //   - The user types Escape | ||||
|     //   - The keyboard focus is moved outside the grabbed actors | ||||
|     //   - A window is focused | ||||
|     // | ||||
|     // Grabs can be automatically dropped if the user tries to dismiss it | ||||
|     // in one of two ways: the user clicking outside the currently grabbed | ||||
|     // actor, or the user typing the Escape key. | ||||
|     // If @params.actor is not null, then it will be focused as the | ||||
|     // new actor. If you attempt to grab an already focused actor, the | ||||
|     // request to be focused will be ignored. The actor will not be | ||||
|     // added to the grab stack, so do not call a paired ungrab(). | ||||
|     // | ||||
|     // If the user clicks outside the grabbed actors, and the clicked on | ||||
|     // actor is part of a previous grab in the stack, grabs will be popped | ||||
|     // until that grab is active. However, the click event will not be | ||||
|     // replayed to the actor. | ||||
|     // If @params contains { modal: true }, then grab() will push a modal | ||||
|     // on the owner of the GrabHelper. As long as there is at least one | ||||
|     // { modal: true } actor on the grab stack, the grab will be kept. | ||||
|     // When the last { modal: true } actor is ungrabbed, then the modal | ||||
|     // will be dropped. A modal grab can fail if there is already a grab | ||||
|     // in effect from aother application; in this case the function returns | ||||
|     // false and nothing happens. Non-modal grabs can never fail. | ||||
|     // | ||||
|     // If the user types the Escape key, one grab from the grab stack will | ||||
|     // be popped. | ||||
|     // | ||||
|     // When a grab is popped by user interacting as described above, if you | ||||
|     // pass a callback as @params.onUngrab, it will be called with %true. | ||||
|     // | ||||
|     // 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. | ||||
|     // If @params contains { grabFocus: true }, then if you call grab() | ||||
|     // while the shell is outside the overview, it will set the stage | ||||
|     // input mode to %Shell.StageInputMode.FOCUSED, and ungrab() will | ||||
|     // revert it back, and re-focus the previously-focused window (if | ||||
|     // another window hasn't been explicitly focused before then). | ||||
|     grab: function(params) { | ||||
|         params = Params.parse(params, { actor: null, | ||||
|                                         modal: false, | ||||
|                                         grabFocus: false, | ||||
|                                         focus: null, | ||||
|                                         onUngrab: null }); | ||||
|  | ||||
| @@ -180,18 +162,24 @@ const GrabHelper = new Lang.Class({ | ||||
|  | ||||
|         params.savedFocus = focus; | ||||
|  | ||||
|         if (!this._takeModalGrab()) | ||||
|         if (params.modal && !this._takeModalGrab()) | ||||
|             return false; | ||||
|  | ||||
|         if (params.grabFocus && !this._takeFocusGrab(hadFocus)) | ||||
|             return false; | ||||
|  | ||||
|         this._grabStack.push(params); | ||||
|  | ||||
|         if (params.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)) | ||||
|                 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; | ||||
|     }, | ||||
|  | ||||
| @@ -200,8 +188,6 @@ const GrabHelper = new Lang.Class({ | ||||
|         if (firstGrab) { | ||||
|             if (!Main.pushModal(this._owner, this._modalParams)) | ||||
|                 return false; | ||||
|  | ||||
|             _pushGrabHelper(this); | ||||
|         } | ||||
|  | ||||
|         this._modalCount++; | ||||
| @@ -213,14 +199,58 @@ const GrabHelper = new Lang.Class({ | ||||
|         if (this._modalCount > 0) | ||||
|             return; | ||||
|  | ||||
|         _popGrabHelper(this); | ||||
|  | ||||
|         this._ignoreRelease = false; | ||||
|  | ||||
|         Main.popModal(this._owner); | ||||
|         global.sync_pointer(); | ||||
|     }, | ||||
|  | ||||
|     _takeFocusGrab: function(hadFocus) { | ||||
|         let firstGrab = (this._grabFocusCount == 0); | ||||
|         if (firstGrab) { | ||||
|             let metaDisplay = global.screen.get_display(); | ||||
|  | ||||
|             this._grabbedFromKeynav = hadFocus; | ||||
|             this._preGrabInputMode = global.stage_input_mode; | ||||
|  | ||||
|             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.get_current_time()); | ||||
|     }, | ||||
|  | ||||
|     // ignoreRelease: | ||||
|     // | ||||
|     // Make sure that the next button release event evaluated by the | ||||
| @@ -234,14 +264,10 @@ const GrabHelper = new Lang.Class({ | ||||
|     // ungrab: | ||||
|     // @params: The parameters for the grab; see below. | ||||
|     // | ||||
|     // Pops @params.actor from the grab stack, potentially dropping | ||||
|     // the grab. If the actor is not on the grab stack, this call is | ||||
|     // ignored with no ill effects. | ||||
|     // Pops an actor from the grab stack, potentially dropping the grab. | ||||
|     // | ||||
|     // If the actor is not at the top of the grab stack, grabs are | ||||
|     // popped until the grabbed actor is at the top of the grab stack. | ||||
|     // The onUngrab callback for every grab is called for every popped | ||||
|     // grab with the parameter %false. | ||||
|     // If the actor that was popped from the grab stack was not the actor | ||||
|     // That was passed in, this call is ignored. | ||||
|     ungrab: function(params) { | ||||
|         params = Params.parse(params, { actor: this.currentGrab.actor, | ||||
|                                         isUser: false }); | ||||
| @@ -250,6 +276,14 @@ const GrabHelper = new Lang.Class({ | ||||
|         if (grabStackIndex < 0) | ||||
|             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 hadFocus = focus && this._isWithinGrabbedActor(focus); | ||||
|  | ||||
| @@ -264,7 +298,16 @@ const GrabHelper = new Lang.Class({ | ||||
|             if (poppedGrab.onUngrab) | ||||
|                 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; | ||||
|         } | ||||
|  | ||||
|         if (hadFocus) { | ||||
| @@ -272,15 +315,17 @@ const GrabHelper = new Lang.Class({ | ||||
|             if (poppedGrab.savedFocus) | ||||
|                 poppedGrab.savedFocus.grab_key_focus(); | ||||
|         } | ||||
|  | ||||
|         this._isUngrabbingCount--; | ||||
|     }, | ||||
|  | ||||
|     onCapturedEvent: function(event) { | ||||
|     _onCapturedEvent: function(actor, event) { | ||||
|         let type = event.type(); | ||||
|  | ||||
|         if (type == Clutter.EventType.KEY_PRESS && | ||||
|             event.get_key_symbol() == Clutter.KEY_Escape) { | ||||
|             this.ungrab({ isUser: true }); | ||||
|             return Clutter.EVENT_STOP; | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         let press = type == Clutter.EventType.BUTTON_PRESS; | ||||
| @@ -289,14 +334,17 @@ const GrabHelper = new Lang.Class({ | ||||
|  | ||||
|         if (release && this._ignoreRelease) { | ||||
|             this._ignoreRelease = false; | ||||
|             return Clutter.EVENT_STOP; | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (!button && this._modalCount == 0) | ||||
|             return false; | ||||
|  | ||||
|         if (this._isWithinGrabbedActor(event.get_source())) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         if (Main.keyboard.shouldTakeEvent(event)) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         if (button) { | ||||
|             // If we have a press event, ignore the next event, | ||||
| @@ -305,9 +353,23 @@ const GrabHelper = new Lang.Class({ | ||||
|                 this._ignoreRelease = true; | ||||
|             let i = this._actorInGrabStack(event.get_source()) + 1; | ||||
|             this.ungrab({ actor: this._grabStack[i].actor, isUser: true }); | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|         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 }); | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -32,7 +32,6 @@ const CandidateArea = new Lang.Class({ | ||||
|             let j = i; | ||||
|             box.connect('button-release-event', Lang.bind(this, function(actor, event) { | ||||
|                 this.emit('candidate-clicked', j, event.get_button(), event.get_state()); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|             })); | ||||
|         } | ||||
|  | ||||
| @@ -115,6 +114,9 @@ const CandidatePopup = new Lang.Class({ | ||||
|     Name: 'CandidatePopup', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._cursor = new St.Bin({ opacity: 0 }); | ||||
|         Main.uiGroup.add_actor(this._cursor); | ||||
|  | ||||
|         this._boxPointer = new BoxPointer.BoxPointer(St.Side.TOP); | ||||
|         this._boxPointer.actor.visible = false; | ||||
|         this._boxPointer.actor.style_class = 'candidate-popup-boxpointer'; | ||||
| @@ -155,9 +157,10 @@ const CandidatePopup = new Lang.Class({ | ||||
|  | ||||
|         panelService.connect('set-cursor-location', | ||||
|                              Lang.bind(this, function(ps, x, y, w, h) { | ||||
|                                  Main.layoutManager.setDummyCursorPosition(x, y); | ||||
|                                  this._cursor.set_position(x, y); | ||||
|                                  this._cursor.set_size(w, h); | ||||
|                                  if (this._boxPointer.actor.visible) | ||||
|                                      this._boxPointer.setPosition(Main.layoutManager.dummyCursor, 0); | ||||
|                                      this._boxPointer.setPosition(this._cursor, 0); | ||||
|                              })); | ||||
|         panelService.connect('update-preedit-text', | ||||
|                              Lang.bind(this, function(ps, text, cursorPosition, visible) { | ||||
| @@ -249,7 +252,7 @@ const CandidatePopup = new Lang.Class({ | ||||
|                          this._candidateArea.actor.visible); | ||||
|  | ||||
|         if (isVisible) { | ||||
|             this._boxPointer.setPosition(Main.layoutManager.dummyCursor, 0); | ||||
|             this._boxPointer.setPosition(this._cursor, 0); | ||||
|             this._boxPointer.show(BoxPointer.PopupAnimation.NONE); | ||||
|             this._boxPointer.actor.raise_top(); | ||||
|         } else { | ||||
|   | ||||
| @@ -1,20 +1,14 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Params = imports.misc.params; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const ICON_SIZE = 96; | ||||
| const MIN_ICON_SIZE = 16; | ||||
| const ICON_SIZE = 48; | ||||
|  | ||||
| const EXTRA_SPACE_ANIMATION_TIME = 0.25; | ||||
|  | ||||
| const BaseIcon = new Lang.Class({ | ||||
|     Name: 'BaseIcon', | ||||
| @@ -23,12 +17,7 @@ const BaseIcon = new Lang.Class({ | ||||
|         params = Params.parse(params, { createIcon: null, | ||||
|                                         setSizeManually: false, | ||||
|                                         showLabel: true }); | ||||
|  | ||||
|         let styleClass = 'overview-icon'; | ||||
|         if (params.showLabel) | ||||
|             styleClass += ' overview-icon-with-label'; | ||||
|  | ||||
|         this.actor = new St.Bin({ style_class: styleClass, | ||||
|         this.actor = new St.Bin({ style_class: 'overview-icon', | ||||
|                                   x_fill: true, | ||||
|                                   y_fill: true }); | ||||
|         this.actor._delegate = this; | ||||
| @@ -187,31 +176,19 @@ const IconGrid = new Lang.Class({ | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { rowLimit: null, | ||||
|                                         columnLimit: null, | ||||
|                                         minRows: 1, | ||||
|                                         minColumns: 1, | ||||
|                                         fillParent: false, | ||||
|                                         xAlign: St.Align.MIDDLE, | ||||
|                                         padWithSpacing: false }); | ||||
|                                         xAlign: St.Align.MIDDLE }); | ||||
|         this._rowLimit = params.rowLimit; | ||||
|         this._colLimit = params.columnLimit; | ||||
|         this._minRows = params.minRows; | ||||
|         this._minColumns = params.minColumns; | ||||
|         this._xAlign = params.xAlign; | ||||
|         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', | ||||
|                                         vertical: true }); | ||||
|         this._items = []; | ||||
|  | ||||
|         // Pulled from CSS, but hardcode some defaults here | ||||
|         this._spacing = 0; | ||||
|         this._hItemSize = this._vItemSize = ICON_SIZE; | ||||
|         this._fixedHItemSize = this._fixedVItemSize = undefined; | ||||
|         this._grid = new Shell.GenericContainer(); | ||||
|         this.actor.add(this._grid, { expand: true, y_align: St.Align.START }); | ||||
|         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 | ||||
|             return; | ||||
|  | ||||
|         let nChildren = this._grid.get_n_children(); | ||||
|         let children = this._grid.get_children(); | ||||
|         let nColumns = this._colLimit ? Math.min(this._colLimit, | ||||
|                                                  nChildren) | ||||
|                                       : nChildren; | ||||
|         let totalSpacing = Math.max(0, nColumns - 1) * this._getSpacing(); | ||||
|                                                  children.length) | ||||
|                                       : children.length; | ||||
|         let totalSpacing = Math.max(0, nColumns - 1) * this._spacing; | ||||
|         // Kind of a lie, but not really an issue right now.  If | ||||
|         // we wanted to support some sort of hidden/overflow that would | ||||
|         // need higher level design | ||||
|         alloc.min_size = this._getHItemSize() + this.leftPadding + this.rightPadding; | ||||
|         alloc.natural_size = nColumns * this._getHItemSize() + totalSpacing + this.leftPadding + this.rightPadding; | ||||
|         alloc.min_size = this._hItemSize; | ||||
|         alloc.natural_size = nColumns * this._hItemSize + totalSpacing; | ||||
|     }, | ||||
|  | ||||
|     _getVisibleChildren: function() { | ||||
| @@ -254,11 +231,13 @@ const IconGrid = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         let children = this._getVisibleChildren(); | ||||
|         let nColumns; | ||||
|         if (forWidth < 0) | ||||
|         let nColumns, spacing; | ||||
|         if (forWidth < 0) { | ||||
|             nColumns = children.length; | ||||
|         else | ||||
|             [nColumns, ] = this._computeLayout(forWidth); | ||||
|             spacing = this._spacing; | ||||
|         } else { | ||||
|             [nColumns, , spacing] = this._computeLayout(forWidth); | ||||
|         } | ||||
|  | ||||
|         let nRows; | ||||
|         if (nColumns > 0) | ||||
| @@ -267,8 +246,8 @@ const IconGrid = new Lang.Class({ | ||||
|             nRows = 0; | ||||
|         if (this._rowLimit) | ||||
|             nRows = Math.min(nRows, this._rowLimit); | ||||
|         let totalSpacing = Math.max(0, nRows - 1) * this._getSpacing(); | ||||
|         let height = nRows * this._getVItemSize() + totalSpacing + this.topPadding + this.bottomPadding; | ||||
|         let totalSpacing = Math.max(0, nRows - 1) * spacing; | ||||
|         let height = nRows * this._vItemSize + totalSpacing; | ||||
|         alloc.min_size = height; | ||||
|         alloc.natural_size = height; | ||||
|     }, | ||||
| @@ -284,30 +263,48 @@ const IconGrid = new Lang.Class({ | ||||
|         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; | ||||
|         let [nColumns, usedWidth, spacing] = this._computeLayout(availWidth); | ||||
|  | ||||
|         let leftPadding; | ||||
|         switch(this._xAlign) { | ||||
|             case St.Align.START: | ||||
|                 leftEmptySpace = 0; | ||||
|                 leftPadding = 0; | ||||
|                 break; | ||||
|             case St.Align.MIDDLE: | ||||
|                 leftEmptySpace = Math.floor((availWidth - usedWidth) / 2); | ||||
|                 leftPadding = Math.floor((availWidth - usedWidth) / 2); | ||||
|                 break; | ||||
|             case St.Align.END: | ||||
|                 leftEmptySpace = availWidth - usedWidth; | ||||
|                 leftPadding = availWidth - usedWidth; | ||||
|         } | ||||
|  | ||||
|         let x = box.x1 + leftEmptySpace + this.leftPadding; | ||||
|         let y = box.y1 + this.topPadding; | ||||
|         let x = box.x1 + leftPadding; | ||||
|         let y = box.y1; | ||||
|         let columnIndex = 0; | ||||
|         let rowIndex = 0; | ||||
|         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 || | ||||
|                 this._fillParent && childBox.y2 > availHeight - this.bottomPadding) { | ||||
|                 this._fillParent && childBox.y2 > availHeight) { | ||||
|                 this._grid.set_skip_paint(children[i], true); | ||||
|             } else { | ||||
|                 children[i].allocate(childBox, flags); | ||||
| @@ -321,38 +318,15 @@ const IconGrid = new Lang.Class({ | ||||
|             } | ||||
|  | ||||
|             if (columnIndex == 0) { | ||||
|                 y += this._getVItemSize() + spacing; | ||||
|                 x = box.x1 + leftEmptySpace + this.leftPadding; | ||||
|                 y += this._vItemSize + spacing; | ||||
|                 x = box.x1 + leftPadding; | ||||
|             } else { | ||||
|                 x += this._getHItemSize() + spacing; | ||||
|                 x += this._hItemSize + spacing; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _calculateChildBox: function(child, x, y, box) { | ||||
|         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) { | ||||
|     childrenInRow: function(rowWidth) { | ||||
|         return this._computeLayout(rowWidth)[0]; | ||||
|     }, | ||||
|  | ||||
| @@ -362,19 +336,26 @@ const IconGrid = new Lang.Class({ | ||||
|  | ||||
|     _computeLayout: function (forWidth) { | ||||
|         let nColumns = 0; | ||||
|         let usedWidth = this.leftPadding + this.rightPadding; | ||||
|         let spacing = this._getSpacing(); | ||||
|         let usedWidth = 0; | ||||
|         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) && | ||||
|                (usedWidth + this._getHItemSize() <= forWidth)) { | ||||
|             usedWidth += this._getHItemSize() + spacing; | ||||
|                (usedWidth + this._hItemSize <= forWidth)) { | ||||
|             usedWidth += this._hItemSize + spacing; | ||||
|             nColumns += 1; | ||||
|         } | ||||
|  | ||||
|         if (nColumns > 0) | ||||
|             usedWidth -= spacing; | ||||
|  | ||||
|         return [nColumns, usedWidth]; | ||||
|         return [nColumns, usedWidth, spacing]; | ||||
|     }, | ||||
|  | ||||
|     _onStyleChanged: function() { | ||||
| @@ -385,56 +366,15 @@ const IconGrid = new Lang.Class({ | ||||
|         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() { | ||||
|         this._items = []; | ||||
|         this._grid.remove_all_children(); | ||||
|     }, | ||||
|  | ||||
|     destroyAll: function() { | ||||
|         this._items = []; | ||||
|         this._grid.destroy_all_children(); | ||||
|     }, | ||||
|  | ||||
|     addItem: function(item, index) { | ||||
|         if (!item.icon instanceof BaseIcon) | ||||
|             throw new Error('Only items with a BaseIcon icon property can be added to IconGrid'); | ||||
|  | ||||
|         this._items.push(item); | ||||
|     addItem: function(actor, index) { | ||||
|         if (index !== undefined) | ||||
|             this._grid.insert_child_at_index(item.actor, index); | ||||
|             this._grid.insert_child_at_index(actor, index); | ||||
|         else | ||||
|             this._grid.add_actor(item.actor); | ||||
|     }, | ||||
|  | ||||
|     removeItem: function(item) { | ||||
|         this._grid.remove_child(item.actor); | ||||
|             this._grid.add_actor(actor); | ||||
|     }, | ||||
|  | ||||
|     getItemAtIndex: function(index) { | ||||
| @@ -443,311 +383,5 @@ const IconGrid = new Lang.Class({ | ||||
|  | ||||
|     visibleItemsCount: function() { | ||||
|         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); | ||||
|   | ||||
| @@ -23,29 +23,27 @@ const KEYBOARD_TYPE = 'keyboard-type'; | ||||
| const A11Y_APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications'; | ||||
| const SHOW_KEYBOARD = 'screen-keyboard-enabled'; | ||||
|  | ||||
| const CaribouKeyboardIface = '<node> \ | ||||
| <interface name="org.gnome.Caribou.Keyboard"> \ | ||||
| <method name="Show"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Hide"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="SetCursorLocation"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="SetEntryLocation"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <property name="Name" access="read" type="s" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const CaribouKeyboardIface = <interface name='org.gnome.Caribou.Keyboard'> | ||||
| <method name='Show'> | ||||
|     <arg type='u' direction='in' /> | ||||
| </method> | ||||
| <method name='Hide'> | ||||
|     <arg type='u' direction='in' /> | ||||
| </method> | ||||
| <method name='SetCursorLocation'> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
| </method> | ||||
| <method name='SetEntryLocation'> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
| </method> | ||||
| <property name='Name' access='read' type='s' /> | ||||
| </interface>; | ||||
|  | ||||
| const Key = new Lang.Class({ | ||||
|     Name: 'Key', | ||||
| @@ -82,16 +80,8 @@ const Key = new Lang.Class({ | ||||
|                                       style_class: 'keyboard-key' }); | ||||
|  | ||||
|         button.key_width = this._key.width; | ||||
|         button.connect('button-press-event', Lang.bind(this, | ||||
|             function () { | ||||
|                 this._key.press(); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|             })); | ||||
|         button.connect('button-release-event', Lang.bind(this, | ||||
|             function () { | ||||
|                 this._key.release(); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|             })); | ||||
|         button.connect('button-press-event', Lang.bind(this, function () { this._key.press(); })); | ||||
|         button.connect('button-release-event', Lang.bind(this, function () { this._key.release(); })); | ||||
|  | ||||
|         return button; | ||||
|     }, | ||||
| @@ -114,16 +104,8 @@ const Key = new Lang.Class({ | ||||
|             let label = this._getUnichar(extended_key); | ||||
|             let key = new St.Button({ label: label, style_class: 'keyboard-key' }); | ||||
|             key.extended_key = extended_key; | ||||
|             key.connect('button-press-event', Lang.bind(this, | ||||
|                 function () { | ||||
|                     extended_key.press(); | ||||
|                     return Clutter.EVENT_PROPAGATE; | ||||
|                 })); | ||||
|             key.connect('button-release-event', Lang.bind(this, | ||||
|                 function () { | ||||
|                     extended_key.release(); | ||||
|                     return Clutter.EVENT_PROPAGATE; | ||||
|                 })); | ||||
|             key.connect('button-press-event', Lang.bind(this, function () { extended_key.press(); })); | ||||
|             key.connect('button-release-event', Lang.bind(this, function () { extended_key.release(); })); | ||||
|             this._extended_keyboard.add(key); | ||||
|         } | ||||
|         this._boxPointer.bin.add_actor(this._extended_keyboard); | ||||
| @@ -268,10 +250,7 @@ const Keyboard = new Lang.Class({ | ||||
|  | ||||
|         if (!this._showIdleId) | ||||
|             this._showIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, | ||||
|                                              Lang.bind(this, function() { | ||||
|                                                  this.Show(time); | ||||
|                                                  return GLib.SOURCE_REMOVE; | ||||
|                                              })); | ||||
|                                              Lang.bind(this, function() { this.Show(time); })); | ||||
|     }, | ||||
|  | ||||
|     _createLayersForGroup: function (gname) { | ||||
| @@ -313,7 +292,7 @@ const Keyboard = new Lang.Class({ | ||||
|         else if (release && this._capturedPress) | ||||
|             this._hideSubkeys(); | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _addRows : function (keys, layout) { | ||||
| @@ -457,6 +436,7 @@ const Keyboard = new Lang.Class({ | ||||
|     _createSource: function () { | ||||
|         if (this._source == null) { | ||||
|             this._source = new KeyboardSource(this); | ||||
|             this._source.setTransient(true); | ||||
|             Main.messageTray.add(this._source); | ||||
|         } | ||||
|     }, | ||||
| @@ -498,7 +478,6 @@ const Keyboard = new Lang.Class({ | ||||
|                                                    Lang.bind(this, function() { | ||||
|                                                        this._clearKeyboardRestTimer(); | ||||
|                                                        this._show(monitor); | ||||
|                                                        return GLib.SOURCE_REMOVE; | ||||
|                                                    })); | ||||
|     }, | ||||
|  | ||||
| @@ -524,7 +503,6 @@ const Keyboard = new Lang.Class({ | ||||
|                                                    Lang.bind(this, function() { | ||||
|                                                        this._clearKeyboardRestTimer(); | ||||
|                                                        this._hide(); | ||||
|                                                        return GLib.SOURCE_REMOVE; | ||||
|                                                    })); | ||||
|     }, | ||||
|  | ||||
|   | ||||
							
								
								
									
										753
									
								
								js/ui/layout.js
									
									
									
									
									
								
							
							
						
						
									
										753
									
								
								js/ui/layout.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -5,38 +5,12 @@ const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const Params = imports.misc.params; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const DEFAULT_FADE_FACTOR = 0.4; | ||||
|  | ||||
| const GLSL_DIM_EFFECT_DECLARATIONS = '\ | ||||
| float compute_dim_factor (const vec2 coords) {\ | ||||
|    vec2 dist = coords - vec2(0.5, 0.5); \ | ||||
|    float elipse_radius = 0.5; \ | ||||
|    /* interpolate darkening value, based on distance from screen center */ \ | ||||
|    float val = min(length(dist), elipse_radius); \ | ||||
|    return mix(0.3, 1.0, val / elipse_radius) * 0.4; \ | ||||
| }'; | ||||
| const GLSL_DIM_EFFECT_CODE = '\ | ||||
|    float a = compute_dim_factor (cogl_tex_coord0_in.xy);\ | ||||
|    cogl_color_out = vec4(0, 0, 0, cogl_color_in.a * a);' | ||||
| ; | ||||
|  | ||||
| const RadialShaderQuad = new Lang.Class({ | ||||
|     Name: 'RadialShaderQuad', | ||||
|     Extends: Shell.GLSLQuad, | ||||
|  | ||||
|     vfunc_build_pipeline: function() { | ||||
|         this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, | ||||
|                               GLSL_DIM_EFFECT_DECLARATIONS, | ||||
|                               GLSL_DIM_EFFECT_CODE, | ||||
|                               true); | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * Lightbox: | ||||
|  * @container: parent Clutter.Container | ||||
| @@ -68,22 +42,20 @@ const Lightbox = new Lang.Class({ | ||||
|         params = Params.parse(params, { inhibitEvents: false, | ||||
|                                         width: null, | ||||
|                                         height: null, | ||||
|                                         fadeFactor: DEFAULT_FADE_FACTOR, | ||||
|                                         radialEffect: false, | ||||
|                                         fadeInTime: null, | ||||
|                                         fadeOutTime: null, | ||||
|                                         fadeFactor: DEFAULT_FADE_FACTOR | ||||
|                                       }); | ||||
|  | ||||
|         this._container = container; | ||||
|         this._children = container.get_children(); | ||||
|         this._fadeInTime = params.fadeInTime; | ||||
|         this._fadeOutTime = params.fadeOutTime; | ||||
|         this._fadeFactor = params.fadeFactor; | ||||
|         if (params.radialEffect) | ||||
|             this.actor = new RadialShaderQuad({ x: 0, | ||||
|                                                 y: 0, | ||||
|                                                 reactive: params.inhibitEvents }); | ||||
|         else | ||||
|             this.actor = new St.Bin({ x: 0, | ||||
|                                       y: 0, | ||||
|                                       style_class: 'lightbox', | ||||
|                                       reactive: params.inhibitEvents }); | ||||
|         this.actor = new St.Bin({ x: 0, | ||||
|                                   y: 0, | ||||
|                                   style_class: 'lightbox', | ||||
|                                   reactive: params.inhibitEvents }); | ||||
|  | ||||
|         container.add_actor(this.actor); | ||||
|         this.actor.raise_top(); | ||||
| @@ -129,16 +101,14 @@ const Lightbox = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     show: function(fadeInTime) { | ||||
|         fadeInTime = fadeInTime || 0; | ||||
|  | ||||
|     show: function() { | ||||
|         Tweener.removeTweens(this.actor); | ||||
|         if (fadeInTime != 0) { | ||||
|         if (this._fadeInTime) { | ||||
|             this.shown = false; | ||||
|             this.actor.opacity = 0; | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { opacity: 255 * this._fadeFactor, | ||||
|                                time: fadeInTime, | ||||
|                                time: this._fadeInTime, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: Lang.bind(this, function() { | ||||
|                                    this.shown = true; | ||||
| @@ -153,15 +123,13 @@ const Lightbox = new Lang.Class({ | ||||
|         this.actor.show(); | ||||
|     }, | ||||
|  | ||||
|     hide: function(fadeOutTime) { | ||||
|         fadeOutTime = fadeOutTime || 0; | ||||
|  | ||||
|     hide: function() { | ||||
|         this.shown = false; | ||||
|         Tweener.removeTweens(this.actor); | ||||
|         if (fadeOutTime != 0) { | ||||
|         if (this._fadeOutTime) { | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { opacity: 0, | ||||
|                                time: fadeOutTime, | ||||
|                                time: this._fadeOutTime, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: Lang.bind(this, function() { | ||||
|                                    this.actor.hide(); | ||||
|   | ||||
| @@ -109,7 +109,6 @@ const AutoComplete = new Lang.Class({ | ||||
|             } | ||||
|             this._lastTabTime = currTime; | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     // Insert characters of text not already included in head at cursor position.  i.e., if text="abc" and head="a", | ||||
| @@ -309,6 +308,10 @@ const Result = new Lang.Class({ | ||||
|         box.add(resultTxt); | ||||
|         let objLink = new ObjLink(this._lookingGlass, o); | ||||
|         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); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -559,7 +562,7 @@ const Inspector = new Lang.Class({ | ||||
|     _onKeyPressEvent: function (actor, event) { | ||||
|         if (event.get_key_symbol() == Clutter.Escape) | ||||
|             this._close(); | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _onButtonPressEvent: function (actor, event) { | ||||
| @@ -568,7 +571,7 @@ const Inspector = new Lang.Class({ | ||||
|             this.emit('target', this._target, stageX, stageY); | ||||
|         } | ||||
|         this._close(); | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _onScrollEvent: function (actor, event) { | ||||
| @@ -602,12 +605,12 @@ const Inspector = new Lang.Class({ | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _onMotionEvent: function (actor, event) { | ||||
|         this._update(event); | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _update: function(event) { | ||||
| @@ -630,6 +633,55 @@ const Inspector = new Lang.Class({ | ||||
|  | ||||
| Signals.addSignalMethods(Inspector.prototype); | ||||
|  | ||||
| const Memory = new Lang.Class({ | ||||
|     Name: 'Memory', | ||||
|  | ||||
|     _init: function() { | ||||
|         this.actor = new St.BoxLayout({ vertical: true }); | ||||
|         this._glibc_uordblks = new St.Label(); | ||||
|         this.actor.add(this._glibc_uordblks); | ||||
|  | ||||
|         this._js_bytes = new St.Label(); | ||||
|         this.actor.add(this._js_bytes); | ||||
|  | ||||
|         this._gjs_boxed = new St.Label(); | ||||
|         this.actor.add(this._gjs_boxed); | ||||
|  | ||||
|         this._gjs_gobject = new St.Label(); | ||||
|         this.actor.add(this._gjs_gobject); | ||||
|  | ||||
|         this._gjs_function = new St.Label(); | ||||
|         this.actor.add(this._gjs_function); | ||||
|  | ||||
|         this._gjs_closure = new St.Label(); | ||||
|         this.actor.add(this._gjs_closure); | ||||
|  | ||||
|         this._last_gc_seconds_ago = new St.Label(); | ||||
|         this.actor.add(this._last_gc_seconds_ago); | ||||
|  | ||||
|         this._gcbutton = new St.Button({ label: 'Full GC', | ||||
|                                          style_class: 'lg-obj-inspector-button' }); | ||||
|         this._gcbutton.connect('clicked', Lang.bind(this, function () { System.gc(); this._renderText(); })); | ||||
|         this.actor.add(this._gcbutton, { x_align: St.Align.START, | ||||
|                                          x_fill: false }); | ||||
|  | ||||
|         this.actor.connect('notify::mapped', Lang.bind(this, this._renderText)); | ||||
|     }, | ||||
|  | ||||
|     _renderText: function() { | ||||
|         if (!this.actor.mapped) | ||||
|             return; | ||||
|         let memInfo = global.get_memory_info(); | ||||
|         this._glibc_uordblks.text = 'glibc_uordblks: ' + memInfo.glibc_uordblks; | ||||
|         this._js_bytes.text = 'js bytes: ' + memInfo.js_bytes; | ||||
|         this._gjs_boxed.text = 'gjs_boxed: ' + memInfo.gjs_boxed; | ||||
|         this._gjs_gobject.text = 'gjs_gobject: ' + memInfo.gjs_gobject; | ||||
|         this._gjs_function.text = 'gjs_function: ' + memInfo.gjs_function; | ||||
|         this._gjs_closure.text = 'gjs_closure: ' + memInfo.gjs_closure; | ||||
|         this._last_gc_seconds_ago.text = 'last_gc_seconds_ago: ' + memInfo.last_gc_seconds_ago; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const Extensions = new Lang.Class({ | ||||
|     Name: 'Extensions', | ||||
|  | ||||
| @@ -670,13 +722,13 @@ const Extensions = new Lang.Class({ | ||||
|     _onViewSource: function (actor) { | ||||
|         let extension = actor._extension; | ||||
|         let uri = extension.dir.get_uri(); | ||||
|         Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context(0, -1)); | ||||
|         Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context()); | ||||
|         this._lookingGlass.close(); | ||||
|     }, | ||||
|  | ||||
|     _onWebPage: function (actor) { | ||||
|         let extension = actor._extension; | ||||
|         Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context(0, -1)); | ||||
|         Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context()); | ||||
|         this._lookingGlass.close(); | ||||
|     }, | ||||
|  | ||||
| @@ -801,9 +853,8 @@ const LookingGlass = new Lang.Class({ | ||||
|         this._updateFont(); | ||||
|  | ||||
|         // We want it to appear to slide out from underneath the panel | ||||
|         Main.uiGroup.add_actor(this.actor); | ||||
|         Main.uiGroup.set_child_below_sibling(this.actor, | ||||
|                                              Main.layoutManager.panelBox); | ||||
|         Main.layoutManager.panelBox.add_actor(this.actor); | ||||
|         this.actor.lower_bottom(); | ||||
|         Main.layoutManager.panelBox.connect('allocation-changed', | ||||
|                                             Lang.bind(this, this._queueResize)); | ||||
|         Main.layoutManager.keyboardBox.connect('allocation-changed', | ||||
| @@ -829,22 +880,7 @@ const LookingGlass = new Lang.Class({ | ||||
|                 global.stage.set_key_focus(this._entry); | ||||
|             })); | ||||
|             this.actor.hide(); | ||||
|             return Clutter.EVENT_STOP; | ||||
|         })); | ||||
|  | ||||
|         let gcIcon = new St.Icon({ icon_name: 'gnome-fs-trash-full', | ||||
|                                    icon_size: 24 }); | ||||
|         toolbar.add_actor(gcIcon); | ||||
|         gcIcon.reactive = true; | ||||
|         gcIcon.connect('button-press-event', Lang.bind(this, function () { | ||||
|            gcIcon.icon_name = 'gnome-fs-trash-empty'; | ||||
|            System.gc(); | ||||
|            this._timeoutId = Mainloop.timeout_add(500, Lang.bind(this, function () { | ||||
|                 gcIcon.icon_name = 'gnome-fs-trash-full'; | ||||
|                 Mainloop.source_remove(this._timeoutId); | ||||
|                 return GLib.SOURCE_REMOVE; | ||||
|            })); | ||||
|            return Clutter.EVENT_PROPAGATE; | ||||
|             return true; | ||||
|         })); | ||||
|  | ||||
|         let notebook = new Notebook(); | ||||
| @@ -874,6 +910,9 @@ const LookingGlass = new Lang.Class({ | ||||
|         this._windowList = new WindowList(this); | ||||
|         notebook.appendPage('Windows', this._windowList.actor); | ||||
|  | ||||
|         this._memory = new Memory(); | ||||
|         notebook.appendPage('Memory', this._memory.actor); | ||||
|  | ||||
|         this._extensions = new Extensions(this); | ||||
|         notebook.appendPage('Extensions', this._extensions.actor); | ||||
|  | ||||
| @@ -884,7 +923,7 @@ const LookingGlass = new Lang.Class({ | ||||
|             let text = o.get_text(); | ||||
|             // Ensure we don't get newlines in the command; the history file is | ||||
|             // newline-separated. | ||||
|             text = text.replace('\n', ' '); | ||||
|             text.replace('\n', ' '); | ||||
|             // Strip leading and trailing whitespace | ||||
|             text = text.replace(/^\s+/g, '').replace(/\s+$/g, ''); | ||||
|             if (text == '') | ||||
| @@ -950,18 +989,28 @@ const LookingGlass = new Lang.Class({ | ||||
|  | ||||
|     _showCompletions: function(completions) { | ||||
|         if (!this._completionActor) { | ||||
|             this._completionActor = new St.Label({ name: 'LookingGlassAutoCompletionText', style_class: 'lg-completions-text' }); | ||||
|             this._completionActor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|             this._completionActor.clutter_text.line_wrap = true; | ||||
|             let actor = new St.BoxLayout({ vertical: 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._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 | ||||
|         // whatever was last given in set_height by Tweener. | ||||
|         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 | ||||
|         if (this._completionActor.visible) { | ||||
| @@ -1036,15 +1085,15 @@ const LookingGlass = new Lang.Class({ | ||||
|         let myWidth = primary.width * 0.7; | ||||
|         let availableHeight = primary.height - Main.layoutManager.keyboardBox.height; | ||||
|         let myHeight = Math.min(primary.height * 0.7, availableHeight * 0.9); | ||||
|         this.actor.x = primary.x + (primary.width - myWidth) / 2; | ||||
|         this._hiddenY = primary.y + Main.layoutManager.panelBox.height - myHeight - 4; // -4 to hide the top corners | ||||
|         this.actor.x = (primary.width - myWidth) / 2; | ||||
|         this._hiddenY = this.actor.get_parent().height - myHeight - 4; // -4 to hide the top corners | ||||
|         this._targetY = this._hiddenY + myHeight; | ||||
|         this.actor.y = this._hiddenY; | ||||
|         this.actor.width = myWidth; | ||||
|         this.actor.height = myHeight; | ||||
|         this._objInspector.actor.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8)); | ||||
|         this._objInspector.actor.set_position(this.actor.x + Math.floor(myWidth * 0.1), | ||||
|                                               this._targetY + Math.floor(myHeight * 0.1)); | ||||
|         this._objInspector.actor.set_position(primary.x + this.actor.x + Math.floor(myWidth * 0.1), | ||||
|                                               primary.y + this._targetY + Math.floor(myHeight * 0.1)); | ||||
|     }, | ||||
|  | ||||
|     insertObject: function(obj) { | ||||
| @@ -1066,7 +1115,7 @@ const LookingGlass = new Lang.Class({ | ||||
|             } else { | ||||
|                 this.close(); | ||||
|             } | ||||
|             return Clutter.EVENT_STOP; | ||||
|             return true; | ||||
|         } | ||||
|         // Ctrl+PgUp and Ctrl+PgDown switches tabs in the notebook view | ||||
|         if (modifierState & Clutter.ModifierType.CONTROL_MASK) { | ||||
| @@ -1076,7 +1125,7 @@ const LookingGlass = new Lang.Class({ | ||||
|                 this._notebook.nextTab(); | ||||
|             } | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     open : function() { | ||||
| @@ -1114,7 +1163,7 @@ const LookingGlass = new Lang.Class({ | ||||
|  | ||||
|         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', | ||||
|                                        y: this._hiddenY, | ||||
|                                        onComplete: Lang.bind(this, function () { | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Atspi = imports.gi.Atspi; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GDesktopEnums = imports.gi.GDesktopEnums; | ||||
| const Gio = imports.gi.Gio; | ||||
| @@ -8,10 +7,8 @@ const Shell = imports.gi.Shell; | ||||
| const St = imports.gi.St; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const FocusCaretTracker = imports.ui.focusCaretTracker; | ||||
| const Main = imports.ui.main; | ||||
| const MagnifierDBus = imports.ui.magnifierDBus; | ||||
| const Params = imports.misc.params; | ||||
| @@ -39,8 +36,6 @@ const CONTRAST_BLUE_KEY         = 'contrast-blue'; | ||||
| const LENS_MODE_KEY             = 'lens-mode'; | ||||
| const CLAMP_MODE_KEY            = 'scroll-at-edges'; | ||||
| 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 CROSS_HAIRS_THICKNESS_KEY = 'cross-hairs-thickness'; | ||||
| const CROSS_HAIRS_COLOR_KEY     = 'cross-hairs-color'; | ||||
| @@ -57,24 +52,10 @@ const Magnifier = new Lang.Class({ | ||||
|         // Magnifier is a manager of 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. | ||||
|         let cursorTracker = Meta.CursorTracker.get_for_screen(global.screen); | ||||
|         let xfixesCursor = Shell.XFixesCursor.get_for_stage(global.stage); | ||||
|         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.add_actor(this._mouseSprite); | ||||
|  | ||||
| @@ -86,11 +67,15 @@ const Magnifier = new Lang.Class({ | ||||
|  | ||||
|         let aZoomRegion = new ZoomRegion(this, this._cursorRoot); | ||||
|         this._zoomRegions.push(aZoomRegion); | ||||
|         this._settingsInitRegion(aZoomRegion); | ||||
|         let showAtLaunch = this._settingsInit(aZoomRegion); | ||||
|         aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse); | ||||
|  | ||||
|         cursorTracker.connect('cursor-changed', Lang.bind(this, this._updateMouseSprite)); | ||||
|         this._cursorTracker = cursorTracker; | ||||
|         xfixesCursor.connect('cursor-change', Lang.bind(this, this._updateMouseSprite)); | ||||
|         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. | ||||
|      */ | ||||
|     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. | ||||
|      */ | ||||
|     hideSystemCursor: function() { | ||||
|         this._cursorTracker.set_pointer_visible(false); | ||||
|         this._xfixesCursor.hide(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -115,29 +100,19 @@ const Magnifier = new Lang.Class({ | ||||
|      * @activate:   Boolean to activate or de-activate the magnifier. | ||||
|      */ | ||||
|     setActive: function(activate) { | ||||
|         if (activate == this.isActive()) | ||||
|             return; | ||||
|  | ||||
|         if (activate) | ||||
|             this._initialize(); | ||||
|  | ||||
|         this._zoomRegions.forEach (function(zoomRegion, index, array) { | ||||
|             zoomRegion.setActive(activate); | ||||
|         }); | ||||
|  | ||||
|         if (activate) { | ||||
|             Meta.disable_unredirect_for_screen(global.screen); | ||||
|         if (activate) | ||||
|             this.startTrackingMouse(); | ||||
|         } | ||||
|         else { | ||||
|             Meta.enable_unredirect_for_screen(global.screen); | ||||
|         else | ||||
|             this.stopTrackingMouse(); | ||||
|         } | ||||
|  | ||||
|         // Make sure system mouse pointer is shown when all zoom regions are | ||||
|         // invisible. | ||||
|         if (!activate) | ||||
|             this._cursorTracker.set_pointer_visible(true); | ||||
|             this._xfixesCursor.show(); | ||||
|  | ||||
|         // Notify interested parties of this change | ||||
|         this.emit('active-changed', activate); | ||||
| @@ -447,73 +422,62 @@ const Magnifier = new Lang.Class({ | ||||
|     //// Private methods //// | ||||
|  | ||||
|     _updateMouseSprite: function() { | ||||
|         Shell.util_cursor_tracker_to_clutter(this._cursorTracker, this._mouseSprite); | ||||
|         let [xHot, yHot] = this._cursorTracker.get_hot(); | ||||
|         this._xfixesCursor.update_texture_image(this._mouseSprite); | ||||
|         let xHot = this._xfixesCursor.get_hot_x(); | ||||
|         let yHot = this._xfixesCursor.get_hot_y(); | ||||
|         this._mouseSprite.set_anchor_point(xHot, yHot); | ||||
|     }, | ||||
|  | ||||
|     _settingsInitRegion: 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() { | ||||
|     _settingsInit: function(zoomRegion) { | ||||
|         this._appSettings = new Gio.Settings({ schema: APPLICATIONS_SCHEMA }); | ||||
|         this._settings = new Gio.Settings({ schema: MAGNIFIER_SCHEMA }); | ||||
|  | ||||
|         this._appSettings.connect('changed::' + SHOW_KEY, Lang.bind(this, function() { | ||||
|             let active = this._appSettings.get_boolean(SHOW_KEY); | ||||
|             this.setActive(active); | ||||
|         })); | ||||
|         if (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); | ||||
|  | ||||
|         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); | ||||
|         this.addCrosshairs(); | ||||
|         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, | ||||
|                                Lang.bind(this, this._updateScreenPosition)); | ||||
|         this._settings.connect('changed::' + MAG_FACTOR_KEY, | ||||
| @@ -524,10 +488,6 @@ const Magnifier = new Lang.Class({ | ||||
|                                Lang.bind(this, this._updateClampMode)); | ||||
|         this._settings.connect('changed::' + MOUSE_TRACKING_KEY, | ||||
|                                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, | ||||
|                                Lang.bind(this, this._updateInvertLightness)); | ||||
| @@ -577,6 +537,8 @@ const Magnifier = new Lang.Class({ | ||||
|                                Lang.bind(this, function() { | ||||
|             this.setCrosshairsClip(this._settings.get_boolean(CROSS_HAIRS_CLIP_KEY)); | ||||
|         })); | ||||
|  | ||||
|         return this._appSettings.get_boolean(SHOW_KEY); | ||||
|    }, | ||||
|  | ||||
|     _updateScreenPosition: function() { | ||||
| @@ -623,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() { | ||||
|         // Applies only to the first zoom region. | ||||
|         if (this._zoomRegions.length) { | ||||
| @@ -679,7 +623,7 @@ const Magnifier = new Lang.Class({ | ||||
|             contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY); | ||||
|             this._zoomRegions[0].setContrast(contrast); | ||||
|         } | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(Magnifier.prototype); | ||||
|  | ||||
| @@ -688,11 +632,8 @@ const ZoomRegion = new Lang.Class({ | ||||
|  | ||||
|     _init: function(magnifier, mouseSourceActor) { | ||||
|         this._magnifier = magnifier; | ||||
|         this._focusCaretTracker = new FocusCaretTracker.FocusCaretTracker(); | ||||
|  | ||||
|         this._mouseTrackingMode = GDesktopEnums.MagnifierMouseTrackingMode.NONE; | ||||
|         this._focusTrackingMode = GDesktopEnums.MagnifierFocusTrackingMode.NONE; | ||||
|         this._caretTrackingMode = GDesktopEnums.MagnifierCaretTrackingMode.NONE; | ||||
|         this._clampScrollingAtEdges = false; | ||||
|         this._lensMode = false; | ||||
|         this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN; | ||||
| @@ -718,35 +659,9 @@ const ZoomRegion = new Lang.Class({ | ||||
|         this._xMagFactor = 1; | ||||
|         this._yMagFactor = 1; | ||||
|         this._followingCursor = false; | ||||
|         this._xFocus = 0; | ||||
|         this._yFocus = 0; | ||||
|         this._xCaret = 0; | ||||
|         this._yCaret = 0; | ||||
|  | ||||
|         Main.layoutManager.connect('monitors-changed', | ||||
|                                    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(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -754,17 +669,14 @@ const ZoomRegion = new Lang.Class({ | ||||
|      * @activate:   Boolean to show/hide the ZoomRegion. | ||||
|      */ | ||||
|     setActive: function(activate) { | ||||
|         if (activate == this.isActive()) | ||||
|             return; | ||||
|  | ||||
|         if (activate) { | ||||
|         if (activate && !this.isActive()) { | ||||
|             this._createActors(); | ||||
|             if (this._isMouseOverRegion()) | ||||
|                 this._magnifier.hideSystemCursor(); | ||||
|             this._updateMagViewGeometry(); | ||||
|             this._updateCloneGeometry(); | ||||
|             this._updateMousePosition(); | ||||
|         } else { | ||||
|         } else if (!activate && this.isActive()) { | ||||
|             this._destroyActors(); | ||||
|         } | ||||
|     }, | ||||
| @@ -820,30 +732,6 @@ const ZoomRegion = new Lang.Class({ | ||||
|         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 | ||||
|      * Sets the position and size of the ZoomRegion on screen. | ||||
| @@ -1135,6 +1023,20 @@ const ZoomRegion = new Lang.Class({ | ||||
|             this._magShaderEffects.setBrightness(this._brightness); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * getBrightness: | ||||
|      * Retrive the current brightness of the Zoom Region. | ||||
|      * @return  Object containing the brightness change for the red, green, | ||||
|      *          and blue channels. | ||||
|      */ | ||||
|     getBrightness: function() { | ||||
|         let brightness = {}; | ||||
|         brightness.r = this._brightness.r; | ||||
|         brightness.g = this._brightness.g; | ||||
|         brightness.b = this._brightness.b; | ||||
|         return brightness; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * setContrast: | ||||
|      * Alter the contrast of the magnified view. | ||||
| @@ -1341,47 +1243,19 @@ const ZoomRegion = new Lang.Class({ | ||||
|         let yMouse = this._magnifier.yMouse; | ||||
|  | ||||
|         if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) { | ||||
|             return this._centerFromPointProportional(xMouse, yMouse); | ||||
|             return this._centerFromMouseProportional(xMouse, yMouse); | ||||
|         } | ||||
|         else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) { | ||||
|             return this._centerFromPointPush(xMouse, yMouse); | ||||
|             return this._centerFromMousePush(xMouse, yMouse); | ||||
|         } | ||||
|         else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) { | ||||
|             return this._centerFromPointCentered(xMouse, yMouse); | ||||
|             return this._centerFromMouseCentered(xMouse, yMouse); | ||||
|         } | ||||
|  | ||||
|         return null; // Should never be hit | ||||
|     }, | ||||
|  | ||||
|     _centerFromCaretPosition: function() { | ||||
|         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) { | ||||
|     _centerFromMousePush: function(xMouse, yMouse) { | ||||
|         let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI(); | ||||
|         let [cursorWidth, cursorHeight] = this._mouseSourceActor.get_size(); | ||||
|         let xPos = xRoi + widthRoi / 2; | ||||
| @@ -1389,20 +1263,20 @@ const ZoomRegion = new Lang.Class({ | ||||
|         let xRoiRight = xRoi + widthRoi - cursorWidth; | ||||
|         let yRoiBottom = yRoi + heightRoi - cursorHeight; | ||||
|  | ||||
|         if (xPoint < xRoi) | ||||
|             xPos -= (xRoi - xPoint); | ||||
|         else if (xPoint > xRoiRight) | ||||
|             xPos += (xPoint - xRoiRight); | ||||
|         if (xMouse < xRoi) | ||||
|             xPos -= (xRoi - xMouse); | ||||
|         else if (xMouse > xRoiRight) | ||||
|             xPos += (xMouse - xRoiRight); | ||||
|  | ||||
|         if (yPoint < yRoi) | ||||
|             yPos -= (yRoi - yPoint); | ||||
|         else if (yPoint > yRoiBottom) | ||||
|             yPos += (yPoint - yRoiBottom); | ||||
|         if (yMouse < yRoi) | ||||
|             yPos -= (yRoi - yMouse); | ||||
|         else if (yMouse > yRoiBottom) | ||||
|             yPos += (yMouse - yRoiBottom); | ||||
|  | ||||
|         return [xPos, yPos]; | ||||
|     }, | ||||
|  | ||||
|     _centerFromPointProportional: function(xPoint, yPoint) { | ||||
|     _centerFromMouseProportional: function(xMouse, yMouse) { | ||||
|         let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI(); | ||||
|         let halfScreenWidth = global.screen_width / 2; | ||||
|         let halfScreenHeight = global.screen_height / 2; | ||||
| @@ -1411,16 +1285,16 @@ const ZoomRegion = new Lang.Class({ | ||||
|         let unscaledPadding = Math.min(this._viewPortWidth, this._viewPortHeight) / 5; | ||||
|         let xPadding = unscaledPadding / this._xMagFactor; | ||||
|         let yPadding = unscaledPadding / this._yMagFactor; | ||||
|         let xProportion = (xPoint - halfScreenWidth) / halfScreenWidth;   // -1 ... 1 | ||||
|         let yProportion = (yPoint - halfScreenHeight) / halfScreenHeight; // -1 ... 1 | ||||
|         let xPos = xPoint - xProportion * (widthRoi / 2 - xPadding); | ||||
|         let yPos = yPoint - yProportion * (heightRoi /2 - yPadding); | ||||
|         let xProportion = (xMouse - halfScreenWidth) / halfScreenWidth;   // -1 ... 1 | ||||
|         let yProportion = (yMouse - halfScreenHeight) / halfScreenHeight; // -1 ... 1 | ||||
|         let xPos = xMouse - xProportion * (widthRoi / 2 - xPadding); | ||||
|         let yPos = yMouse - yProportion * (heightRoi /2 - yPadding); | ||||
|  | ||||
|         return [xPos, yPos]; | ||||
|     }, | ||||
|  | ||||
|     _centerFromPointCentered: function(xPoint, yPoint) { | ||||
|         return [xPoint, yPoint]; | ||||
|     _centerFromMouseCentered: function(xMouse, yMouse) { | ||||
|         return [xMouse, yMouse]; | ||||
|     }, | ||||
|  | ||||
|     _screenToViewPort: function(screenX, screenY) { | ||||
| @@ -1637,6 +1511,15 @@ const Crosshairs = new Lang.Class({ | ||||
|         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: | ||||
|      * Set the length of the vertical and horizontal lines in the crosshairs. | ||||
| @@ -1680,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 the crosshairs. | ||||
| @@ -1775,10 +1667,23 @@ const MagShaderEffects = new Lang.Class({ | ||||
|         this._inverse.set_enabled(invertFlag); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * getInvertLightness: | ||||
|      * Report whether the inversion effect is enabled. | ||||
|      * @return:     Boolean. | ||||
|      */ | ||||
|     getInvertLightness: function() { | ||||
|         return this._inverse.get_enabled(); | ||||
|     }, | ||||
|  | ||||
|     setColorSaturation: function(factor) { | ||||
|         this._colorDesaturation.set_factor(1.0 - factor); | ||||
|     }, | ||||
|  | ||||
|     getColorSaturation: function() { | ||||
|         return 1.0 - this._colorDesaturation.get_factor(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * setBrightness: | ||||
|      * Set the brightness of the magnified view. | ||||
| @@ -1803,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. | ||||
|      * @contrast:   Object containing the contrast for the red, green, | ||||
| @@ -1827,4 +1750,21 @@ const MagShaderEffects = new Lang.Class({ | ||||
|              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; | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -4,94 +4,92 @@ const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Main = imports.ui.main; | ||||
|  | ||||
| const MAG_SERVICE_NAME = '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'; | ||||
|  | ||||
| // Subset of gnome-mag's Magnifier dbus interface -- to be expanded.  See: | ||||
| // http://git.gnome.org/browse/gnome-mag/tree/xml/...Magnifier.xml | ||||
| const MagnifierIface = '<node> \ | ||||
| <interface name="org.gnome.Magnifier"> \ | ||||
| <method name="setActive"> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="isActive"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="showCursor" /> \ | ||||
| <method name="hideCursor" /> \ | ||||
| <method name="createZoomRegion"> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
|     <arg type="o" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="addZoomRegion"> \ | ||||
|     <arg type="o" direction="in" /> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="getZoomRegions"> \ | ||||
|     <arg type="ao" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="clearAllZoomRegions" /> \ | ||||
| <method name="fullScreenCapable"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireSize"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireSize"> \ | ||||
|     <arg type="i" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireLength"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireLength"> \ | ||||
|     <arg type="i" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireClip"> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireClip"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireColor"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireColor"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const MagnifierIface = <interface name={MAG_SERVICE_NAME}> | ||||
| <method name="setActive"> | ||||
|     <arg type="b" direction="in" /> | ||||
| </method> | ||||
| <method name="isActive"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="showCursor" /> | ||||
| <method name="hideCursor" /> | ||||
| <method name="createZoomRegion"> | ||||
|     <arg type="d" direction="in" /> | ||||
|     <arg type="d" direction="in" /> | ||||
|     <arg type="ai" direction="in" /> | ||||
|     <arg type="ai" direction="in" /> | ||||
|     <arg type="o" direction="out" /> | ||||
| </method> | ||||
| <method name="addZoomRegion"> | ||||
|     <arg type="o" direction="in" /> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="getZoomRegions"> | ||||
|     <arg type="ao" direction="out" /> | ||||
| </method> | ||||
| <method name="clearAllZoomRegions" /> | ||||
| <method name="fullScreenCapable"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireSize"> | ||||
|     <arg type="i" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireSize"> | ||||
|     <arg type="i" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireLength"> | ||||
|     <arg type="i" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireLength"> | ||||
|     <arg type="i" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireClip"> | ||||
|     <arg type="b" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireClip"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireColor"> | ||||
|     <arg type="u" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireColor"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| // Subset of gnome-mag's ZoomRegion dbus interface -- to be expanded.  See: | ||||
| // http://git.gnome.org/browse/gnome-mag/tree/xml/...ZoomRegion.xml | ||||
| const ZoomRegionIface = '<node> \ | ||||
| <interface name="org.gnome.Magnifier.ZoomRegion"> \ | ||||
| <method name="setMagFactor"> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getMagFactor"> \ | ||||
|     <arg type="d" direction="out" /> \ | ||||
|     <arg type="d" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setRoi"> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getRoi"> \ | ||||
|     <arg type="ai" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="shiftContentsTo"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="moveResize"> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ZoomRegionIface = <interface name={ZOOM_SERVICE_NAME}> | ||||
| <method name="setMagFactor"> | ||||
|     <arg type="d" direction="in" /> | ||||
|     <arg type="d" direction="in" /> | ||||
| </method> | ||||
| <method name="getMagFactor"> | ||||
|     <arg type="d" direction="out" /> | ||||
|     <arg type="d" direction="out" /> | ||||
| </method> | ||||
| <method name="setRoi"> | ||||
|     <arg type="ai" direction="in" /> | ||||
| </method> | ||||
| <method name="getRoi"> | ||||
|     <arg type="ai" direction="out" /> | ||||
| </method> | ||||
| <method name="shiftContentsTo"> | ||||
|     <arg type="i" direction="in" /> | ||||
|     <arg type="i" direction="in" /> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="moveResize"> | ||||
|     <arg type="ai" direction="in" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| // For making unique ZoomRegion DBus proxy object paths of the form: | ||||
| // '/org/gnome/Magnifier/ZoomRegion/zoomer0', | ||||
|   | ||||
							
								
								
									
										297
									
								
								js/ui/main.js
									
									
									
									
									
								
							
							
						
						
									
										297
									
								
								js/ui/main.js
									
									
									
									
									
								
							| @@ -24,26 +24,23 @@ const Panel = imports.ui.panel; | ||||
| const Params = imports.misc.params; | ||||
| const RunDialog = imports.ui.runDialog; | ||||
| const Layout = imports.ui.layout; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| const LookingGlass = imports.ui.lookingGlass; | ||||
| const NotificationDaemon = imports.ui.notificationDaemon; | ||||
| const WindowAttentionHandler = imports.ui.windowAttentionHandler; | ||||
| const Screencast = imports.ui.screencast; | ||||
| const ScreenShield = imports.ui.screenShield; | ||||
| const Scripting = imports.ui.scripting; | ||||
| const SessionMode = imports.ui.sessionMode; | ||||
| const ShellDBus = imports.ui.shellDBus; | ||||
| const ShellMountOperation = imports.ui.shellMountOperation; | ||||
| const UnlockDialog = imports.ui.unlockDialog; | ||||
| const WindowManager = imports.ui.windowManager; | ||||
| const Magnifier = imports.ui.magnifier; | ||||
| const XdndHandler = imports.ui.xdndHandler; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides'; | ||||
| 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 panel = null; | ||||
| let overview = null; | ||||
| @@ -60,9 +57,8 @@ let sessionMode = null; | ||||
| let shellDBusService = null; | ||||
| let shellMountOpDBusService = null; | ||||
| let screenSaverDBus = null; | ||||
| let screencastService = null; | ||||
| let modalCount = 0; | ||||
| let keybindingMode = Shell.KeyBindingMode.NONE; | ||||
| let keybindingMode = Shell.KeyBindingMode.NORMAL; | ||||
| let modalActorFocusStack = []; | ||||
| let uiGroup = null; | ||||
| let magnifier = null; | ||||
| @@ -72,12 +68,9 @@ let layoutManager = null; | ||||
| let _startDate; | ||||
| let _defaultCssStylesheet = null; | ||||
| let _cssStylesheet = null; | ||||
| let _a11ySettings = null; | ||||
| let dynamicWorkspacesSchema = null; | ||||
| let _overridesSettings = null; | ||||
|  | ||||
| function _sessionUpdated() { | ||||
|     _loadDefaultStylesheet(); | ||||
|  | ||||
|     wm.setCustomKeybindingHandler('panel-main-menu', | ||||
|                                   Shell.KeyBindingMode.NORMAL | | ||||
|                                   Shell.KeyBindingMode.OVERVIEW, | ||||
| @@ -89,13 +82,8 @@ function _sessionUpdated() { | ||||
|                                   Shell.KeyBindingMode.NORMAL | | ||||
|                                   Shell.KeyBindingMode.OVERVIEW, | ||||
|                                   sessionMode.hasRunDialog ? openRunDialog : null); | ||||
|  | ||||
|     if (!sessionMode.hasRunDialog) { | ||||
|         if (runDialog) | ||||
|             runDialog.close(); | ||||
|         if (lookingGlass) | ||||
|             lookingGlass.close(); | ||||
|     } | ||||
|     if (sessionMode.isGreeter) | ||||
|         screenShield.showDialog(); | ||||
| } | ||||
|  | ||||
| function start() { | ||||
| @@ -103,8 +91,8 @@ function start() { | ||||
|     global.logError = window.log; | ||||
|     global.log = window.log; | ||||
|  | ||||
|     if (!Meta.is_wayland_compositor) | ||||
|         Meta.is_wayland_compositor = function () { return false; }; | ||||
|     // Hide the stage until we're ready for it | ||||
|     global.stage.hide(); | ||||
|  | ||||
|     // Chain up async errors reported from C | ||||
|     global.connect('notify-error', function (global, msg, detail) { notifyError(msg, detail); }); | ||||
| @@ -112,28 +100,20 @@ function start() { | ||||
|     Gio.DesktopAppInfo.set_desktop_env('GNOME'); | ||||
|  | ||||
|     sessionMode = new SessionMode.SessionMode(); | ||||
|     sessionMode.connect('updated', _sessionUpdated); | ||||
|     _initializePrefs(); | ||||
|     _initializeUI(); | ||||
|  | ||||
|     // start session after we know what mode we're running in | ||||
|     let signalId = sessionMode.connect('updated', function() { | ||||
|                                            sessionMode.disconnect(signalId); | ||||
|                                            startSession(); | ||||
|                                        }); | ||||
| } | ||||
|  | ||||
| function startSession() { | ||||
|     sessionMode.connect('updated', _loadDefaultStylesheet); | ||||
|  | ||||
|     shellDBusService = new ShellDBus.GnomeShell(); | ||||
|     shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler(); | ||||
|  | ||||
|     _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() { | ||||
|     // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will | ||||
|     // also initialize ShellAppSystem first.  ShellAppSystem | ||||
|     // needs to load all the .desktop files, and ShellWindowTracker | ||||
| @@ -142,9 +122,11 @@ function _initializeUI() { | ||||
|     // and recalculate application associations, so to avoid | ||||
|     // races for now we initialize it here.  It's better to | ||||
|     // be predictable anyways. | ||||
|     Shell.WindowTracker.get_default(); | ||||
|     let tracker = Shell.WindowTracker.get_default(); | ||||
|     Shell.AppUsage.get_default(); | ||||
|  | ||||
|     tracker.connect('startup-sequence-changed', _queueCheckWorkspaces); | ||||
|  | ||||
|     _loadDefaultStylesheet(); | ||||
|  | ||||
|     // Setup the stage hierarchy early | ||||
| @@ -155,16 +137,19 @@ function _initializeUI() { | ||||
|     // working until it's updated. | ||||
|     uiGroup = layoutManager.uiGroup; | ||||
|  | ||||
|     screencastService = new Screencast.ScreencastService(); | ||||
|     xdndHandler = new XdndHandler.XdndHandler(); | ||||
|     ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); | ||||
|     osdWindow = new OsdWindow.OsdWindow(); | ||||
|     overview = new Overview.Overview(); | ||||
|     wm = new WindowManager.WindowManager(); | ||||
|     magnifier = new Magnifier.Magnifier(); | ||||
|     if (LoginManager.canLock()) | ||||
|     if (UnlockDialog.isSupported()) | ||||
|         screenShield = new ScreenShield.ScreenShield(); | ||||
|     else | ||||
|         screenShield = new ScreenShield.ScreenShieldFallback(); | ||||
|  | ||||
|     // The message tray relies on being constructed | ||||
|     // after the panel. | ||||
|     panel = new Panel.Panel(); | ||||
|     messageTray = new MessageTray.MessageTray(); | ||||
|     keyboard = new Keyboard.Keyboard(); | ||||
| @@ -173,14 +158,14 @@ function _initializeUI() { | ||||
|     componentManager = new Components.ComponentManager(); | ||||
|  | ||||
|     layoutManager.init(); | ||||
|     layoutManager.prepareStartupAnimation(); | ||||
|     overview.init(); | ||||
|  | ||||
|     _a11ySettings = new Gio.Settings({ schema: A11Y_SCHEMA }); | ||||
|  | ||||
|     global.display.connect('overlay-key', Lang.bind(overview, function () { | ||||
|         if (!_a11ySettings.get_boolean (STICKY_KEYS_ENABLE)) | ||||
|             overview.toggle(); | ||||
|     })); | ||||
|     global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT, | ||||
|                                             false, -1, 1); | ||||
|     global.display.connect('overlay-key', Lang.bind(overview, overview.toggle)); | ||||
|     sessionMode.connect('updated', _sessionUpdated); | ||||
|     _sessionUpdated(); | ||||
|  | ||||
|     // Provide the bus object for gnome-session to | ||||
|     // initiate logouts. | ||||
| @@ -200,23 +185,204 @@ function _initializeUI() { | ||||
|         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(); | ||||
|     ExtensionSystem.init(); | ||||
|  | ||||
|     if (sessionMode.isGreeter && screenShield) { | ||||
|         layoutManager.connect('startup-prepared', function() { | ||||
|             screenShield.showDialog(); | ||||
|         }); | ||||
|     layoutManager.connect('startup-prepared', function() { | ||||
|                               layoutManager.startupAnimation(); | ||||
|                           }); | ||||
| } | ||||
|  | ||||
| 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; | ||||
|     } | ||||
|  | ||||
|     layoutManager.connect('startup-complete', function() { | ||||
|                               if (keybindingMode == Shell.KeyBindingMode.NONE) { | ||||
|                                   keybindingMode = Shell.KeyBindingMode.NORMAL; | ||||
|                               } | ||||
|                               if (screenShield) { | ||||
|                                   screenShield.lockIfWasLocked(); | ||||
|                               } | ||||
|                           }); | ||||
|     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() { | ||||
| @@ -265,8 +431,11 @@ function loadTheme() { | ||||
|     let themeContext = St.ThemeContext.get_for_stage (global.stage); | ||||
|     let previousTheme = themeContext.get_theme(); | ||||
|  | ||||
|     let theme = new St.Theme ({ application_stylesheet: _cssStylesheet, | ||||
|                                 default_stylesheet: _defaultCssStylesheet }); | ||||
|     let cssStylesheet = _defaultCssStylesheet; | ||||
|     if (_cssStylesheet != null) | ||||
|         cssStylesheet = _cssStylesheet; | ||||
|  | ||||
|     let theme = new St.Theme ({ application_stylesheet: cssStylesheet }); | ||||
|  | ||||
|     if (previousTheme) { | ||||
|         let customStylesheets = previousTheme.get_custom_stylesheets(); | ||||
| @@ -357,6 +526,8 @@ function pushModal(actor, params) { | ||||
|         Meta.disable_unredirect_for_screen(global.screen); | ||||
|     } | ||||
|  | ||||
|     global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN); | ||||
|  | ||||
|     modalCount += 1; | ||||
|     let actorDestroyId = actor.connect('destroy', function() { | ||||
|         let index = _findModal(actor); | ||||
| @@ -405,6 +576,7 @@ function popModal(actor, timestamp) { | ||||
|     if (focusIndex < 0) { | ||||
|         global.stage.set_key_focus(null); | ||||
|         global.end_modal(timestamp); | ||||
|         global.set_stage_input_mode(Shell.StageInputMode.NORMAL); | ||||
|         keybindingMode = Shell.KeyBindingMode.NORMAL; | ||||
|  | ||||
|         throw new Error('incorrect pop'); | ||||
| @@ -452,6 +624,7 @@ function popModal(actor, timestamp) { | ||||
|         return; | ||||
|  | ||||
|     global.end_modal(timestamp); | ||||
|     global.set_stage_input_mode(Shell.StageInputMode.NORMAL); | ||||
|     Meta.enable_unredirect_for_screen(global.screen); | ||||
|     keybindingMode = Shell.KeyBindingMode.NORMAL; | ||||
| } | ||||
| @@ -609,7 +782,7 @@ function queueDeferredWork(workId) { | ||||
|         _deferredTimeoutId = Mainloop.timeout_add_seconds(DEFERRED_TIMEOUT_SECONDS, function () { | ||||
|             _runAllDeferredWork(); | ||||
|             _deferredTimeoutId = 0; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|             return false; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										1237
									
								
								js/ui/messageTray.js
									
									
									
									
									
								
							
							
						
						
									
										1237
									
								
								js/ui/messageTray.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -14,7 +14,6 @@ const Atk = imports.gi.Atk; | ||||
|  | ||||
| const Params = imports.misc.params; | ||||
|  | ||||
| const Animation = imports.ui.animation; | ||||
| const Layout = imports.ui.layout; | ||||
| const Lightbox = imports.ui.lightbox; | ||||
| const Main = imports.ui.main; | ||||
| @@ -23,10 +22,6 @@ const Tweener = imports.ui.tweener; | ||||
| const OPEN_AND_CLOSE_TIME = 0.1; | ||||
| 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 = { | ||||
|     OPENED: 0, | ||||
|     CLOSED: 1, | ||||
| @@ -43,15 +38,13 @@ const ModalDialog = new Lang.Class({ | ||||
|                                         styleClass: null, | ||||
|                                         parentActor: Main.uiGroup, | ||||
|                                         keybindingMode: Shell.KeyBindingMode.SYSTEM_MODAL, | ||||
|                                         shouldFadeIn: true, | ||||
|                                         destroyOnClose: true }); | ||||
|                                         shouldFadeIn: true }); | ||||
|  | ||||
|         this.state = State.CLOSED; | ||||
|         this._hasModal = false; | ||||
|         this._keybindingMode = params.keybindingMode; | ||||
|         this._shellReactive = params.shellReactive; | ||||
|         this._shouldFadeIn = params.shouldFadeIn; | ||||
|         this._destroyOnClose = params.destroyOnClose; | ||||
|  | ||||
|         this._group = new St.Widget({ visible: false, | ||||
|                                       x: 0, | ||||
| @@ -70,39 +63,36 @@ const ModalDialog = new Lang.Class({ | ||||
|         this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); | ||||
|         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({ child: this.backgroundStack, | ||||
|                                            x_fill: true, y_fill: true }); | ||||
|         this._backgroundBin = new St.Bin(); | ||||
|         this._monitorConstraint = new Layout.MonitorConstraint(); | ||||
|         this._backgroundBin.add_constraint(this._monitorConstraint); | ||||
|         this._group.add_actor(this._backgroundBin); | ||||
|  | ||||
|         this.dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog', | ||||
|                                                vertical:    true }); | ||||
|         // modal dialogs are fixed width and grow vertically; set the request | ||||
|         // mode accordingly so wrapped labels are handled correctly during | ||||
|         // size requests. | ||||
|         this.dialogLayout.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH; | ||||
|  | ||||
|         if (params.styleClass != null) | ||||
|         if (params.styleClass != null) { | ||||
|             this.dialogLayout.add_style_class_name(params.styleClass); | ||||
|         } | ||||
|  | ||||
|         if (!this._shellReactive) { | ||||
|             this._lightbox = new Lightbox.Lightbox(this._group, | ||||
|                                                    { inhibitEvents: true, | ||||
|                                                      radialEffect: true }); | ||||
|                                                    { inhibitEvents: true }); | ||||
|             this._lightbox.highlight(this._backgroundBin); | ||||
|  | ||||
|             let stack = new Shell.Stack(); | ||||
|             this._backgroundBin.child = stack; | ||||
|  | ||||
|             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.dialogLayout.add(this.contentLayout, | ||||
|                               { expand:  true, | ||||
|                                 x_fill:  true, | ||||
|                               { x_fill:  true, | ||||
|                                 y_fill:  true, | ||||
|                                 x_align: St.Align.MIDDLE, | ||||
|                                 y_align: St.Align.START }); | ||||
| @@ -110,15 +100,14 @@ const ModalDialog = new Lang.Class({ | ||||
|         this.buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box', | ||||
|                                                vertical: false }); | ||||
|         this.dialogLayout.add(this.buttonLayout, | ||||
|                               { x_align: St.Align.MIDDLE, | ||||
|                               { expand:  true, | ||||
|                                 x_align: St.Align.MIDDLE, | ||||
|                                 y_align: St.Align.END }); | ||||
|  | ||||
|         global.focus_manager.add_group(this.dialogLayout); | ||||
|         this._initialKeyFocus = this.dialogLayout; | ||||
|         this._initialKeyFocusDestroyId = 0; | ||||
|         this._savedKeyFocus = null; | ||||
|  | ||||
|         this._workSpinner = null; | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
| @@ -192,45 +181,8 @@ const ModalDialog = new Lang.Class({ | ||||
|         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) { | ||||
|         this._pressedKey = event.get_key_symbol(); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onKeyReleaseEvent: function(object, event) { | ||||
| @@ -239,21 +191,21 @@ const ModalDialog = new Lang.Class({ | ||||
|  | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         if (symbol != pressedKey) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         let buttonInfo = this._buttonKeys[symbol]; | ||||
|         if (!buttonInfo) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         let button = buttonInfo['button']; | ||||
|         let action = buttonInfo['action']; | ||||
|  | ||||
|         if (action && button.reactive) { | ||||
|             action(); | ||||
|             return Clutter.EVENT_STOP; | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onGroupDestroy: function() { | ||||
| @@ -325,9 +277,6 @@ const ModalDialog = new Lang.Class({ | ||||
|                                    this.state = State.CLOSED; | ||||
|                                    this._group.hide(); | ||||
|                                    this.emit('closed'); | ||||
|  | ||||
|                                    if (this._destroyOnClose) | ||||
|                                        this.destroy(); | ||||
|                                }) | ||||
|                          }); | ||||
|     }, | ||||
| @@ -363,9 +312,8 @@ const ModalDialog = new Lang.Class({ | ||||
|         if (this._savedKeyFocus) { | ||||
|             this._savedKeyFocus.grab_key_focus(); | ||||
|             this._savedKeyFocus = null; | ||||
|         } else { | ||||
|         } else | ||||
|             this._initialKeyFocus.grab_key_focus(); | ||||
|         } | ||||
|  | ||||
|         if (!this._shellReactive) | ||||
|             this._eventBlocker.lower_bottom(); | ||||
|   | ||||
| @@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter; | ||||
| const GdkPixbuf = imports.gi.GdkPixbuf; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Mainloop = imports.mainloop; | ||||
| @@ -16,56 +15,54 @@ const MessageTray = imports.ui.messageTray; | ||||
| const Params = imports.misc.params; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| let nextNotificationId = 1; | ||||
|  | ||||
| // Should really be defined in Gio.js | ||||
| const BusIface = '<node> \ | ||||
| <interface name="org.freedesktop.DBus"> \ | ||||
| <method name="GetConnectionUnixProcessID"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const BusIface = <interface name="org.freedesktop.DBus"> | ||||
| <method name="GetConnectionUnixProcessID"> | ||||
|     <arg type="s" direction="in" /> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| var BusProxy = Gio.DBusProxy.makeProxyWrapper(BusIface); | ||||
| function Bus() { | ||||
|     return new BusProxy(Gio.DBus.session, 'org.freedesktop.DBus', '/org/freedesktop/DBus'); | ||||
| } | ||||
|  | ||||
| const FdoNotificationsIface = '<node> \ | ||||
| <interface name="org.freedesktop.Notifications"> \ | ||||
| <method name="Notify"> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="u" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="as" direction="in"/> \ | ||||
|     <arg type="a{sv}" direction="in"/> \ | ||||
|     <arg type="i" direction="in"/> \ | ||||
|     <arg type="u" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="CloseNotification"> \ | ||||
|     <arg type="u" direction="in"/> \ | ||||
| </method> \ | ||||
| <method name="GetCapabilities"> \ | ||||
|     <arg type="as" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="GetServerInformation"> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
| </method> \ | ||||
| <signal name="NotificationClosed"> \ | ||||
|     <arg type="u"/> \ | ||||
|     <arg type="u"/> \ | ||||
| </signal> \ | ||||
| <signal name="ActionInvoked"> \ | ||||
|     <arg type="u"/> \ | ||||
|     <arg type="s"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const NotificationDaemonIface = <interface name="org.freedesktop.Notifications"> | ||||
| <method name="Notify"> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="u" direction="in"/> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="as" direction="in"/> | ||||
|     <arg type="a{sv}" direction="in"/> | ||||
|     <arg type="i" direction="in"/> | ||||
|     <arg type="u" direction="out"/> | ||||
| </method> | ||||
| <method name="CloseNotification"> | ||||
|     <arg type="u" direction="in"/> | ||||
| </method> | ||||
| <method name="GetCapabilities"> | ||||
|     <arg type="as" direction="out"/> | ||||
| </method> | ||||
| <method name="GetServerInformation"> | ||||
|     <arg type="s" direction="out"/> | ||||
|     <arg type="s" direction="out"/> | ||||
|     <arg type="s" direction="out"/> | ||||
|     <arg type="s" direction="out"/> | ||||
| </method> | ||||
| <signal name="NotificationClosed"> | ||||
|     <arg type="u"/> | ||||
|     <arg type="u"/> | ||||
| </signal> | ||||
| <signal name="ActionInvoked"> | ||||
|     <arg type="u"/> | ||||
|     <arg type="s"/> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const NotificationClosedReason = { | ||||
|     EXPIRED: 1, | ||||
| @@ -106,11 +103,131 @@ const STANDARD_TRAY_ICON_IMPLEMENTATIONS = { | ||||
|     'ibus-ui-gtk': 'keyboard' | ||||
| }; | ||||
|  | ||||
| const FdoNotificationDaemon = new Lang.Class({ | ||||
|     Name: 'FdoNotificationDaemon', | ||||
| const NotificationGenericPolicy = new Lang.Class({ | ||||
|     Name: 'NotificationGenericPolicy', | ||||
|     Extends: MessageTray.NotificationPolicy, | ||||
|  | ||||
|     _init: function() { | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(FdoNotificationsIface, this); | ||||
|         // 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({ | ||||
|     Name: 'NotificationDaemon', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(NotificationDaemonIface, this); | ||||
|         this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/Notifications'); | ||||
|  | ||||
|         this._sources = []; | ||||
| @@ -118,8 +235,6 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         this._notifications = {}; | ||||
|         this._busProxy = new Bus(); | ||||
|  | ||||
|         this._nextNotificationId = 1; | ||||
|  | ||||
|         this._trayManager = new Shell.TrayManager(); | ||||
|         this._trayIconAddedId = this._trayManager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded)); | ||||
|         this._trayIconRemovedId = this._trayManager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); | ||||
| @@ -129,7 +244,7 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         Main.overview.connect('hidden', | ||||
|             Lang.bind(this, this._onFocusAppChanged)); | ||||
|  | ||||
|         this._trayManager.manage_screen(global.screen, Main.messageTray.actor); | ||||
|         this._trayManager.manage_stage(global.stage, Main.messageTray.actor); | ||||
|     }, | ||||
|  | ||||
|     _imageForNotificationData: function(hints) { | ||||
| @@ -181,10 +296,12 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     // Returns the source associated with ndata.notification if it is set. | ||||
|     // If the existing or requested source is associated with a tray icon | ||||
|     // and passed in pid matches a pid of an existing source, the title | ||||
|     // match is ignored to enable representing a tray icon and notifications | ||||
|     // from the same application with a single source. | ||||
|     // Otherwise, returns the source associated with the title and pid if | ||||
|     // such source is stored in this._sources and the notification is not | ||||
|     // transient. If the existing or requested source is associated with | ||||
|     // a tray icon and passed in pid matches a pid of an existing source, | ||||
|     // the title match is ignored to enable representing a tray icon and | ||||
|     // notifications from the same application with a single source. | ||||
|     // | ||||
|     // If no existing source is found, a new source is created as long as | ||||
|     // pid is provided. | ||||
| @@ -202,20 +319,32 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         if (ndata && ndata.notification) | ||||
|             return ndata.notification.source; | ||||
|  | ||||
|         let source = this._lookupSource(title, pid, trayIcon); | ||||
|         if (source) { | ||||
|             source.setTitle(title); | ||||
|             return source; | ||||
|         let isForTransientNotification = (ndata && ndata.hints['transient'] == true); | ||||
|  | ||||
|         // We don't want to override a persistent notification | ||||
|         // with a transient one from the same sender, so we | ||||
|         // always create a new source object for new transient notifications | ||||
|         // and never add it to this._sources . | ||||
|         if (!isForTransientNotification) { | ||||
|             let source = this._lookupSource(title, pid, trayIcon); | ||||
|             if (source) { | ||||
|                 source.setTitle(title); | ||||
|                 return source; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let source = new FdoNotificationDaemonSource(title, pid, sender, trayIcon, ndata ? ndata.hints['desktop-entry'] : null); | ||||
|         let source = new Source(title, pid, sender, trayIcon, ndata ? ndata.hints['desktop-entry'] : null); | ||||
|         source.setTransient(isForTransientNotification); | ||||
|  | ||||
|         this._sources.push(source); | ||||
|         source.connect('destroy', Lang.bind(this, function() { | ||||
|             let index = this._sources.indexOf(source); | ||||
|             if (index >= 0) | ||||
|                 this._sources.splice(index, 1); | ||||
|         })); | ||||
|         if (!isForTransientNotification) { | ||||
|             this._sources.push(source); | ||||
|             source.connect('destroy', Lang.bind(this, | ||||
|                 function() { | ||||
|                     let index = this._sources.indexOf(source); | ||||
|                     if (index >= 0) | ||||
|                         this._sources.splice(index, 1); | ||||
|                 })); | ||||
|         } | ||||
|  | ||||
|         Main.messageTray.add(source); | ||||
|         return source; | ||||
| @@ -243,11 +372,11 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|               hints['category'] == 'presence.offline')) { | ||||
|             // Ignore replacesId since we already sent back a | ||||
|             // NotificationClosed for that id. | ||||
|             id = this._nextNotificationId++; | ||||
|             id = nextNotificationId++; | ||||
|             Mainloop.idle_add(Lang.bind(this, | ||||
|                                         function () { | ||||
|                                             this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED); | ||||
|                                             return GLib.SOURCE_REMOVE; | ||||
|                                             return false; | ||||
|                                         })); | ||||
|             return invocation.return_value(GLib.Variant.new('(u)', [id])); | ||||
|         } | ||||
| @@ -267,13 +396,12 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         if (!hints['image-path'] && hints['image_path']) | ||||
|             hints['image-path'] = hints['image_path']; // version 1.1 of the spec | ||||
|  | ||||
|         if (!hints['image-data']) { | ||||
|         if (!hints['image-data']) | ||||
|             if (hints['image_data']) | ||||
|                 hints['image-data'] = hints['image_data']; // version 1.1 of the spec | ||||
|             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 | ||||
|                 hints['image-data'] = hints['icon_data']; | ||||
|         } | ||||
|  | ||||
|         let ndata = { appName: appName, | ||||
|                       icon: icon, | ||||
| @@ -287,7 +415,7 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|             ndata.notification = this._notifications[replacesId].notification; | ||||
|         } else { | ||||
|             replacesId = 0; | ||||
|             ndata.id = id = this._nextNotificationId++; | ||||
|             ndata.id = id = nextNotificationId++; | ||||
|         } | ||||
|         this._notifications[id] = ndata; | ||||
|  | ||||
| @@ -322,29 +450,26 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|             let [pid] = result; | ||||
|             source = this._getSource(appName, pid, ndata, sender, null); | ||||
|  | ||||
|             this._senderToPid[sender] = pid; | ||||
|             source.connect('destroy', Lang.bind(this, function() { | ||||
|                 delete this._senderToPid[sender]; | ||||
|             })); | ||||
|             // We only store sender-pid entries for persistent sources. | ||||
|             // Removing the entries once the source is destroyed | ||||
|             // would result in the entries associated with transient | ||||
|             // sources removed once the notification is shown anyway. | ||||
|             // However, keeping these pairs would mean that we would | ||||
|             // possibly remove an entry associated with a persistent | ||||
|             // source when a transient source for the same sender is | ||||
|             // distroyed. | ||||
|             if (!source.isTransient) { | ||||
|                 this._senderToPid[sender] = pid; | ||||
|                 source.connect('destroy', Lang.bind(this, function() { | ||||
|                     delete this._senderToPid[sender]; | ||||
|                 })); | ||||
|             } | ||||
|             this._notifyForSource(source, ndata); | ||||
|         })); | ||||
|  | ||||
|         return invocation.return_value(GLib.Variant.new('(u)', [id])); | ||||
|     }, | ||||
|  | ||||
|     _makeButton: function(id, label, useActionIcons) { | ||||
|         let button = new St.Button({ can_focus: true }); | ||||
|         let iconName = id.endsWith('-symbolic') ? id : id + '-symbolic'; | ||||
|         if (useActionIcons && Gtk.IconTheme.get_default().has_icon(iconName)) { | ||||
|             button.add_style_class_name('notification-icon-button'); | ||||
|             button.child = new St.Icon({ icon_name: iconName }); | ||||
|         } else { | ||||
|             button.add_style_class_name('notification-button'); | ||||
|             button.label = label; | ||||
|         } | ||||
|         return button; | ||||
|     }, | ||||
|  | ||||
|     _notifyForSource: function(source, ndata) { | ||||
|         let [id, icon, summary, body, actions, hints, notification] = | ||||
|             [ndata.id, ndata.icon, ndata.summary, ndata.body, | ||||
| @@ -370,6 +495,10 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|                     } | ||||
|                     this._emitNotificationClosed(ndata.id, notificationClosedReason); | ||||
|                 })); | ||||
|             notification.connect('action-invoked', Lang.bind(this, | ||||
|                 function(n, actionId) { | ||||
|                     this._emitActionInvoked(ndata.id, actionId); | ||||
|                 })); | ||||
|         } | ||||
|  | ||||
|         // Mark music notifications so they can be shown in the screen shield | ||||
| @@ -403,33 +532,18 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|                                              soundName: hints['sound-name'] }); | ||||
|         notification.setImage(image); | ||||
|  | ||||
|         let hasDefaultAction = false; | ||||
|  | ||||
|         if (actions.length) { | ||||
|             let useActionIcons = (hints['action-icons'] == true); | ||||
|  | ||||
|             notification.setUseActionIcons(hints['action-icons'] == true); | ||||
|             for (let i = 0; i < actions.length - 1; i += 2) { | ||||
|                 let [actionId, label] = [actions[i], actions[i+1]]; | ||||
|                 if (actionId == 'default') { | ||||
|                     hasDefaultAction = true; | ||||
|                 } else { | ||||
|                     notification.addButton(this._makeButton(actionId, label, useActionIcons), Lang.bind(this, function() { | ||||
|                         this._emitActionInvoked(ndata.id, actionId); | ||||
|                     })); | ||||
|                 } | ||||
|                 if (actions[i] == 'default') | ||||
|                     notification.connect('clicked', Lang.bind(this, | ||||
|                         function() { | ||||
|                             this._emitActionInvoked(ndata.id, "default"); | ||||
|                         })); | ||||
|                 else | ||||
|                     notification.addButton(actions[i], actions[i + 1]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (hasDefaultAction) { | ||||
|             notification.connect('clicked', Lang.bind(this, function() { | ||||
|                 this._emitActionInvoked(ndata.id, 'default'); | ||||
|             })); | ||||
|         } else { | ||||
|             notification.connect('clicked', Lang.bind(this, function() { | ||||
|                 source.open(); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         switch (hints.urgency) { | ||||
|             case Urgency.LOW: | ||||
|                 notification.setUrgency(MessageTray.Urgency.LOW); | ||||
| @@ -522,8 +636,8 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|     Name: 'FdoNotificationDaemonSource', | ||||
| const Source = new Lang.Class({ | ||||
|     Name: 'NotificationDaemonSource', | ||||
|     Extends: MessageTray.Source, | ||||
|  | ||||
|     _init: function(title, pid, sender, trayIcon, appId) { | ||||
| @@ -558,11 +672,11 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         if (this.app && this.app.get_app_info()) { | ||||
|         if (this.app) { | ||||
|             let id = this.app.get_id().replace(/\.desktop$/,''); | ||||
|             return new MessageTray.NotificationApplicationPolicy(id); | ||||
|             return new NotificationApplicationPolicy(id); | ||||
|         } else { | ||||
|             return new MessageTray.NotificationGenericPolicy(); | ||||
|             return new NotificationGenericPolicy(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -603,8 +717,8 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|             this.notifications.length > 0) | ||||
|             return false; | ||||
|  | ||||
|         let id = global.stage.connect('deactivate', Lang.bind(this, function () { | ||||
|             global.stage.disconnect(id); | ||||
|         let id = global.connect('notify::stage-input-mode', Lang.bind(this, function () { | ||||
|             global.disconnect(id); | ||||
|             this.trayIcon.click(event); | ||||
|         })); | ||||
|  | ||||
| @@ -620,11 +734,7 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|             return app; | ||||
|  | ||||
|         if (this.trayIcon) { | ||||
|             app = Shell.AppSystem.get_default().lookup_startup_wmclass(this.trayIcon.wm_class); | ||||
|             if (app != null) | ||||
|                 return app; | ||||
|  | ||||
|             app = Shell.AppSystem.get_default().lookup_desktop_wmclass(this.trayIcon.wm_class); | ||||
|             app = Shell.AppSystem.get_default().lookup_wmclass(this.trayIcon.wm_class); | ||||
|             if (app != null) | ||||
|                 return app; | ||||
|         } | ||||
| @@ -638,6 +748,22 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         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) { | ||||
|         // Do nothing if .app is set, we don't want to override the | ||||
|         // app name with whatever is provided through libnotify (usually | ||||
| @@ -648,9 +774,9 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         this.parent(title); | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
|         this.openApp(); | ||||
|     open: function(notification) { | ||||
|         this.destroyNonResidentNotifications(); | ||||
|         this.openApp(); | ||||
|     }, | ||||
|  | ||||
|     _lastNotificationRemoved: function() { | ||||
| @@ -662,8 +788,11 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         if (this.app == null) | ||||
|             return; | ||||
|  | ||||
|         this.app.activate(); | ||||
|         Main.overview.hide(); | ||||
|         let windows = this.app.get_windows(); | ||||
|         if (windows.length > 0) { | ||||
|             let mostRecentWindow = windows[0]; | ||||
|             Main.activateWindow(mostRecentWindow); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
| @@ -690,276 +819,3 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
|  | ||||
| const GtkNotificationDaemonNotification = new Lang.Class({ | ||||
|     Name: 'GtkNotificationDaemonNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, notification) { | ||||
|         this.parent(source); | ||||
|         this._serialized = GLib.Variant.new('a{sv}', notification); | ||||
|  | ||||
|         let { "title": title, | ||||
|               "body": body, | ||||
|               "icon": gicon, | ||||
|               "urgent": urgent, | ||||
|               "buttons": buttons, | ||||
|               "default-action": defaultAction, | ||||
|               "default-action-target": defaultActionTarget } = notification; | ||||
|  | ||||
|         this.setUrgency(urgent.unpack() ? MessageTray.Urgency.CRITICAL | ||||
|                                         : MessageTray.Urgency.NORMAL); | ||||
|  | ||||
|         if (buttons) { | ||||
|             buttons.deep_unpack().forEach(Lang.bind(this, function(button) { | ||||
|                 this.addAction(button.label.unpack(), | ||||
|                                Lang.bind(this, this._onButtonClicked, button)); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         this._defaultAction = defaultAction ? defaultAction.unpack() : null; | ||||
|         this._defaultActionTarget = defaultActionTarget; | ||||
|  | ||||
|         this.update(title.unpack(), body ? body.unpack() : null, | ||||
|                     { gicon: gicon ? Gio.icon_deserialize(gicon) : null }); | ||||
|     }, | ||||
|  | ||||
|     _activateAction: function(namespacedActionId, target) { | ||||
|         if (namespacedActionId) { | ||||
|             if (namespacedActionId.startsWith('app.')) { | ||||
|                 let actionId = namespacedActionId.slice('app.'.length); | ||||
|                 this.source.activateAction(actionId, target); | ||||
|             } | ||||
|         } else { | ||||
|             this.source.open(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onButtonClicked: function(button) { | ||||
|         let { 'action': action, 'target': actionTarget } = button; | ||||
|         this._activateAction(action.unpack(), actionTarget); | ||||
|     }, | ||||
|  | ||||
|     _onClicked: function() { | ||||
|         this._activateAction(this._defaultAction, this._defaultActionTarget); | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
|     serialize: function() { | ||||
|         return this._serialized; | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const FdoApplicationIface = '<node> \ | ||||
| <interface name="org.freedesktop.Application"> \ | ||||
| <method name="ActivateAction"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="av" direction="in" /> \ | ||||
|     <arg type="a{sv}" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Activate"> \ | ||||
|     <arg type="a{sv}" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const FdoApplicationProxy = Gio.DBusProxy.makeProxyWrapper(FdoApplicationIface); | ||||
|  | ||||
| function objectPathFromAppId(appId) { | ||||
|     return '/' + appId.replace(/\./g, '/'); | ||||
| } | ||||
|  | ||||
| function getPlatformData() { | ||||
|     let startupId = GLib.Variant.new('s', '_TIME' + global.get_current_time()); | ||||
|     return { "desktop-startup-id": startupId }; | ||||
| } | ||||
|  | ||||
| function InvalidAppError() {} | ||||
|  | ||||
| const GtkNotificationDaemonAppSource = new Lang.Class({ | ||||
|     Name: 'GtkNotificationDaemonAppSource', | ||||
|     Extends: MessageTray.Source, | ||||
|  | ||||
|     _init: function(appId) { | ||||
|         this._appId = appId; | ||||
|         this._objectPath = objectPathFromAppId(appId); | ||||
|  | ||||
|         this._app = Shell.AppSystem.get_default().lookup_app(appId + '.desktop'); | ||||
|         if (!this._app) | ||||
|             throw new InvalidAppError(); | ||||
|  | ||||
|         this._notifications = {}; | ||||
|  | ||||
|         this.parent(this._app.get_name()); | ||||
|     }, | ||||
|  | ||||
|     createIcon: function(size) { | ||||
|         return this._app.create_icon_texture(size); | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationApplicationPolicy(this._appId); | ||||
|     }, | ||||
|  | ||||
|     _createApp: function() { | ||||
|         return new FdoApplicationProxy(Gio.DBus.session, this._appId, this._objectPath); | ||||
|     }, | ||||
|  | ||||
|     activateAction: function(actionId, target) { | ||||
|         let app = this._createApp(); | ||||
|         app.ActivateActionRemote(actionId, target ? [target] : [], getPlatformData()); | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
|         let app = this._createApp(); | ||||
|         app.ActivateRemote(getPlatformData()); | ||||
|     }, | ||||
|  | ||||
|     addNotification: function(notificationId, notificationParams, showBanner) { | ||||
|         if (this._notifications[notificationId]) | ||||
|             this._notifications[notificationId].destroy(); | ||||
|  | ||||
|         let notification = new GtkNotificationDaemonNotification(this, notificationParams); | ||||
|         notification.connect('destroy', Lang.bind(this, function() { | ||||
|             delete this._notifications[notificationId]; | ||||
|         })); | ||||
|         this._notifications[notificationId] = notification; | ||||
|  | ||||
|         if (showBanner) | ||||
|             this.notify(notification); | ||||
|         else | ||||
|             this.pushNotification(notification); | ||||
|     }, | ||||
|  | ||||
|     removeNotification: function(notificationId) { | ||||
|         if (this._notifications[notificationId]) | ||||
|             this._notifications[notificationId].destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED); | ||||
|     }, | ||||
|  | ||||
|     serialize: function() { | ||||
|         let notifications = []; | ||||
|         for (let notificationId in this._notifications) { | ||||
|             let notification = this._notifications[notificationId]; | ||||
|             notifications.push([notificationId, notification.serialize()]); | ||||
|         } | ||||
|         return [this._appId, notifications]; | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const GtkNotificationsIface = '<node> \ | ||||
| <interface name="org.gtk.Notifications"> \ | ||||
| <method name="AddNotification"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="a{sv}" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="RemoveNotification"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const GtkNotificationDaemon = new Lang.Class({ | ||||
|     Name: 'GtkNotificationDaemon', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._sources = {}; | ||||
|  | ||||
|         this._loadNotifications(); | ||||
|  | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GtkNotificationsIface, this); | ||||
|         this._dbusImpl.export(Gio.DBus.session, '/org/gtk/Notifications'); | ||||
|  | ||||
|         Gio.DBus.session.own_name('org.gtk.Notifications', Gio.BusNameOwnerFlags.REPLACE, null, null); | ||||
|     }, | ||||
|  | ||||
|     _ensureAppSource: function(appId) { | ||||
|         if (this._sources[appId]) | ||||
|             return this._sources[appId]; | ||||
|  | ||||
|         let source = new GtkNotificationDaemonAppSource(appId); | ||||
|  | ||||
|         source.connect('destroy', Lang.bind(this, function() { | ||||
|             delete this._sources[appId]; | ||||
|             this._saveNotifications(); | ||||
|         })); | ||||
|         source.connect('count-updated', Lang.bind(this, this._saveNotifications)); | ||||
|         Main.messageTray.add(source); | ||||
|         this._sources[appId] = source; | ||||
|         return source; | ||||
|     }, | ||||
|  | ||||
|     _loadNotifications: function() { | ||||
|         this._isLoading = true; | ||||
|  | ||||
|         let value = global.get_persistent_state('a(sa(sv))', 'notifications'); | ||||
|         if (value) { | ||||
|             let sources = value.deep_unpack(); | ||||
|             sources.forEach(Lang.bind(this, function([appId, notifications]) { | ||||
|                 if (notifications.length == 0) | ||||
|                     return; | ||||
|  | ||||
|                 let source; | ||||
|                 try { | ||||
|                     source = this._ensureAppSource(appId); | ||||
|                 } catch(e if e instanceof InvalidAppError) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 notifications.forEach(function([notificationId, notification]) { | ||||
|                     source.addNotification(notificationId, notification.deep_unpack(), false); | ||||
|                 }); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         this._isLoading = false; | ||||
|     }, | ||||
|  | ||||
|     _saveNotifications: function() { | ||||
|         if (this._isLoading) | ||||
|             return; | ||||
|  | ||||
|         let sources = []; | ||||
|         for (let appId in this._sources) { | ||||
|             let source = this._sources[appId]; | ||||
|             sources.push(source.serialize()); | ||||
|         } | ||||
|  | ||||
|         global.set_persistent_state('notifications', new GLib.Variant('a(sa(sv))', sources)); | ||||
|     }, | ||||
|  | ||||
|     AddNotificationAsync: function(params, invocation) { | ||||
|         let [appId, notificationId, notification] = params; | ||||
|  | ||||
|         let source; | ||||
|         try { | ||||
|             source = this._ensureAppSource(appId); | ||||
|         } catch(e if e instanceof InvalidAppError) { | ||||
|             invocation.return_dbus_error('org.gtk.Notifications.InvalidApp', 'The app by ID "%s" could not be found'.format(appId)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         source.addNotification(notificationId, notification, true); | ||||
|  | ||||
|         invocation.return_value(null); | ||||
|     }, | ||||
|  | ||||
|     RemoveNotificationAsync: function(params, invocation) { | ||||
|         let [appId, notificationId] = params; | ||||
|         let source = this._sources[appId]; | ||||
|         if (source) | ||||
|             source.removeNotification(notificationId); | ||||
|  | ||||
|         invocation.return_value(null); | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const NotificationDaemon = new Lang.Class({ | ||||
|     Name: 'NotificationDaemon', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._fdoNotificationDaemon = new FdoNotificationDaemon(); | ||||
|         this._gtkNotificationDaemon = new GtkNotificationDaemon(); | ||||
|     }, | ||||
| }); | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| @@ -9,7 +8,6 @@ const Layout = imports.ui.layout; | ||||
| const Main = imports.ui.main; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const Meta = imports.gi.Meta; | ||||
|  | ||||
| const HIDE_TIMEOUT = 1500; | ||||
| const FADE_TIME = 0.1; | ||||
| @@ -66,7 +64,6 @@ const LevelBar = new Lang.Class({ | ||||
|         cr.arc(radius, h - radius, radius, 0.5 * Math.PI, Math.PI); | ||||
|         cr.arc(radius, radius, radius, Math.PI, 1.5 * Math.PI); | ||||
|         cr.fill(); | ||||
|         cr.$dispose(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -74,26 +71,15 @@ const OsdWindow = new Lang.Class({ | ||||
|     Name: 'OsdWindow', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._popupSize = 0; | ||||
|         this.actor = new St.Widget({ x_expand: true, | ||||
|                                      y_expand: true, | ||||
|                                      x_align: Clutter.ActorAlign.CENTER, | ||||
|                                      y_align: Clutter.ActorAlign.CENTER }); | ||||
|         this._currentMonitor = undefined; | ||||
|         this.setMonitor (-1); | ||||
|         this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); | ||||
|         this._box = new St.BoxLayout({ style_class: 'osd-window', | ||||
|                                        vertical: true }); | ||||
|         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._box.add(this._icon, { expand: true }); | ||||
|  | ||||
| @@ -110,7 +96,7 @@ const OsdWindow = new Lang.Class({ | ||||
|                                    Lang.bind(this, this._monitorsChanged)); | ||||
|         this._monitorsChanged(); | ||||
|  | ||||
|         Main.uiGroup.add_child(this.actor); | ||||
|         Main.layoutManager.addChrome(this.actor, { affectsInputRegion: false }); | ||||
|     }, | ||||
|  | ||||
|     setIcon: function(icon) { | ||||
| @@ -125,15 +111,13 @@ const OsdWindow = new Lang.Class({ | ||||
|  | ||||
|     setLevel: function(level) { | ||||
|         this._level.actor.visible = (level != undefined); | ||||
|         if (level) { | ||||
|             if (this.actor.visible) | ||||
|                 Tweener.addTween(this._level, | ||||
|                                  { level: level, | ||||
|                                    time: LEVEL_ANIMATION_TIME, | ||||
|                                    transition: 'easeOutQuad' }); | ||||
|             else | ||||
|                 this._level.level = level; | ||||
|         } | ||||
|         if (this.actor.visible) | ||||
|             Tweener.addTween(this._level, | ||||
|                              { level: level, | ||||
|                                time: LEVEL_ANIMATION_TIME, | ||||
|                                transition: 'easeOutQuad' }); | ||||
|         else | ||||
|             this._level.level = level; | ||||
|     }, | ||||
|  | ||||
|     show: function() { | ||||
| @@ -141,10 +125,8 @@ const OsdWindow = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         if (!this.actor.visible) { | ||||
|             Meta.disable_unredirect_for_screen(global.screen); | ||||
|             this.actor.show(); | ||||
|             this.actor.opacity = 0; | ||||
|             this.actor.get_parent().set_child_above_sibling(this.actor, null); | ||||
|  | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { opacity: 255, | ||||
| @@ -163,21 +145,16 @@ const OsdWindow = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         Mainloop.source_remove(this._hideTimeoutId); | ||||
|         this._hideTimeoutId = 0; | ||||
|         this._hide(); | ||||
|     }, | ||||
|  | ||||
|     _hide: function() { | ||||
|         this._hideTimeoutId = 0; | ||||
|         Tweener.addTween(this.actor, | ||||
|                          { opacity: 0, | ||||
|                            time: FADE_TIME, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            onComplete: Lang.bind(this, function() { | ||||
|                               this._reset(); | ||||
|                               Meta.enable_unredirect_for_screen(global.screen); | ||||
|                            }) | ||||
|                          }); | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|                            onComplete: Lang.bind(this, this._reset) }); | ||||
|     }, | ||||
|  | ||||
|     _reset: function() { | ||||
| @@ -187,54 +164,16 @@ const OsdWindow = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _monitorsChanged: function() { | ||||
|         /* assume 110x110 on a 640x480 display and scale from there */ | ||||
|         let monitor; | ||||
|  | ||||
|         if (this._currentMonitor >= 0) | ||||
|             monitor = Main.layoutManager.monitors[this._currentMonitor]; | ||||
|         else | ||||
|             monitor = Main.layoutManager.primaryMonitor; | ||||
|  | ||||
|         /* assume 130x130 on a 640x480 display and scale from there */ | ||||
|         let monitor = Main.layoutManager.primaryMonitor; | ||||
|         let scalew = monitor.width / 640.0; | ||||
|         let scaleh = monitor.height / 480.0; | ||||
|         let scale = Math.min(scalew, scaleh); | ||||
|         this._popupSize = 110 * Math.max(1, scale); | ||||
|         let size = 130 * Math.max(1, scale); | ||||
|  | ||||
|         this._box.set_size(size, size); | ||||
|         this._box.translation_y = monitor.height / 4; | ||||
|         this._icon.icon_size = this._popupSize / 2; | ||||
|         this._box.style_changed(); | ||||
|     }, | ||||
|  | ||||
|     _onStyleChanged: function() { | ||||
|         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)); | ||||
|     }, | ||||
|  | ||||
|     setMonitor: function(index) { | ||||
|         let constraint; | ||||
|  | ||||
|         if (index < 0) | ||||
|             index = -1; | ||||
|         if (this._currentMonitor == index) | ||||
|             return; | ||||
|  | ||||
|         if (index < 0) | ||||
|             constraint = new Layout.MonitorConstraint({ primary: true }); | ||||
|         else | ||||
|             constraint = new Layout.MonitorConstraint({ index: index }); | ||||
|  | ||||
|         this.actor.clear_constraints(); | ||||
|         this.actor.add_constraint(constraint); | ||||
|         this._currentMonitor = index; | ||||
|         this._icon.icon_size = size / 2; | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Mainloop = imports.mainloop; | ||||
| @@ -12,6 +11,7 @@ const Shell = imports.gi.Shell; | ||||
| const Gdk = imports.gi.Gdk; | ||||
|  | ||||
| const Background = imports.ui.background; | ||||
| const Dash = imports.ui.dash; | ||||
| const DND = imports.ui.dnd; | ||||
| const LayoutManager = imports.ui.layout; | ||||
| const Main = imports.ui.main; | ||||
| @@ -20,6 +20,7 @@ const OverviewControls = imports.ui.overviewControls; | ||||
| const Panel = imports.ui.panel; | ||||
| const Params = imports.misc.params; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const ViewSelector = imports.ui.viewSelector; | ||||
| const WorkspaceThumbnail = imports.ui.workspaceThumbnail; | ||||
|  | ||||
| // Time for initial animation going into Overview mode | ||||
| @@ -30,9 +31,7 @@ const ANIMATION_TIME = 0.25; | ||||
| // and don't want the shading animation to get cut off | ||||
| const SHADE_ANIMATION_TIME = .20; | ||||
|  | ||||
| const DND_WINDOW_SWITCH_TIMEOUT = 750; | ||||
|  | ||||
| const OVERVIEW_ACTIVATION_TIMEOUT = 0.5; | ||||
| const DND_WINDOW_SWITCH_TIMEOUT = 1250; | ||||
|  | ||||
| const ShellInfo = new Lang.Class({ | ||||
|     Name: 'ShellInfo', | ||||
| @@ -79,8 +78,10 @@ const ShellInfo = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         this._undoCallback = undoCallback; | ||||
|         if (undoCallback) | ||||
|             notification.addAction(_("Undo"), Lang.bind(this, this._onUndoClicked)); | ||||
|         if (undoCallback) { | ||||
|             notification.addButton('system-undo', _("Undo")); | ||||
|             notification.connect('action-invoked', Lang.bind(this, this._onUndoClicked)); | ||||
|         } | ||||
|  | ||||
|         this._source.notify(notification); | ||||
|     } | ||||
| @@ -92,7 +93,9 @@ const Overview = new Lang.Class({ | ||||
|     _init: function() { | ||||
|         this._overviewCreated = false; | ||||
|         this._initCalled = false; | ||||
|         this._controlPressed = false; | ||||
|  | ||||
|         global.stage.connect('captured-event', Lang.bind(this, this._capturedEvent)); | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); | ||||
|         this._sessionUpdated(); | ||||
|     }, | ||||
| @@ -113,6 +116,9 @@ const Overview = new Lang.Class({ | ||||
|         // rendering options without duplicating the texture data. | ||||
|         let monitor = Main.layoutManager.primaryMonitor; | ||||
|  | ||||
|         this._desktopFade = new St.Bin(); | ||||
|         global.overlay_group.add_actor(this._desktopFade); | ||||
|  | ||||
|         let layout = new Clutter.BinLayout(); | ||||
|         this._stack = new Clutter.Actor({ layout_manager: layout }); | ||||
|         this._stack.add_constraint(new LayoutManager.MonitorConstraint({ primary: true })); | ||||
| @@ -127,17 +133,22 @@ const Overview = new Lang.Class({ | ||||
|                                             y_expand: true }); | ||||
|         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(); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._backgroundGroup); | ||||
|         global.overlay_group.add_child(this._backgroundGroup); | ||||
|         this._backgroundGroup.hide(); | ||||
|         this._bgManagers = []; | ||||
|  | ||||
|         this._desktopFade = new St.Widget(); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._desktopFade); | ||||
|  | ||||
|         this._activationTime = 0; | ||||
|  | ||||
|         this.visible = false;           // animating to overview, in overview, animating out | ||||
|         this._shown = false;            // show() and not hide() | ||||
|         this._shownTemporarily = false; // showTemporarily() and not hideTemporarily() | ||||
|         this._modal = false;            // have a modal grab | ||||
|         this.animationInProgress = false; | ||||
|         this.visibleTarget = false; | ||||
| @@ -145,13 +156,14 @@ const Overview = new Lang.Class({ | ||||
|         // 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 | ||||
|         // Dash elements, or mouseover handlers in the workspaces. | ||||
|         this._coverPane = new Clutter.Actor({ opacity: 0, | ||||
|                                               reactive: true }); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._coverPane); | ||||
|         this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return Clutter.EVENT_STOP; })); | ||||
|         this._coverPane = new Clutter.Rectangle({ opacity: 0, | ||||
|                                                   reactive: true }); | ||||
|         this._overview.add_actor(this._coverPane); | ||||
|         this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; })); | ||||
|  | ||||
|         this._stack.hide(); | ||||
|         this._stack.add_actor(this._overview); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._stack); | ||||
|         global.overlay_group.add_actor(this._stack); | ||||
|  | ||||
|         this._coverPane.hide(); | ||||
|  | ||||
| @@ -164,6 +176,7 @@ const Overview = new Lang.Class({ | ||||
|         Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd)); | ||||
|  | ||||
|         global.screen.connect('restacked', Lang.bind(this, this._onRestacked)); | ||||
|         this._group.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); | ||||
|  | ||||
|         this._windowSwitchTimeoutId = 0; | ||||
|         this._windowSwitchTimestamp = 0; | ||||
| @@ -225,6 +238,20 @@ const Overview = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _capturedEvent: function(actor, event) { | ||||
|         let type = event.type(); | ||||
|         if (type != Clutter.EventType.KEY_PRESS && | ||||
|             type != Clutter.EventType.KEY_RELEASE) | ||||
|             return false; | ||||
|  | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         if (symbol == Clutter.KEY_Control_L || | ||||
|             symbol == Clutter.KEY_Control_R) | ||||
|             this._controlPressed = type == Clutter.EventType.KEY_PRESS; | ||||
|  | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _sessionUpdated: function() { | ||||
|         this.isDummy = !Main.sessionMode.hasOverview; | ||||
|         this._createOverview(); | ||||
| @@ -262,13 +289,28 @@ const Overview = new Lang.Class({ | ||||
|         this._overview.add_actor(this._searchEntryBin); | ||||
|  | ||||
|         // Create controls | ||||
|         this._controls = new OverviewControls.ControlsManager(this._searchEntry); | ||||
|         this._dash = this._controls.dash; | ||||
|         this.viewSelector = this._controls.viewSelector; | ||||
|         this._dash = new Dash.Dash(); | ||||
|         this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry, | ||||
|                                                            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 | ||||
|         this._overview.add(this._controls.actor, { y_fill: true, expand: true }); | ||||
|         this._controls.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); | ||||
|         this._overview.add(this._groupStack, { y_fill: true, expand: true }); | ||||
|  | ||||
|         this._stack.add_actor(this._controls.indicatorActor); | ||||
|  | ||||
| @@ -284,11 +326,11 @@ const Overview = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     addSearchProvider: function(provider) { | ||||
|         this.viewSelector.addSearchProvider(provider); | ||||
|         this._viewSelector.addSearchProvider(provider); | ||||
|     }, | ||||
|  | ||||
|     removeSearchProvider: function(provider) { | ||||
|         this.viewSelector.removeSearchProvider(provider); | ||||
|         this._viewSelector.removeSearchProvider(provider); | ||||
|     }, | ||||
|  | ||||
|     // | ||||
| @@ -304,22 +346,18 @@ const Overview = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onDragBegin: function() { | ||||
|         this._inXdndDrag = true; | ||||
|  | ||||
|         DND.addDragMonitor(this._dragMonitor); | ||||
|         // Remember the workspace we started from | ||||
|         this._lastActiveWorkspaceIndex = global.screen.get_active_workspace_index(); | ||||
|     }, | ||||
|  | ||||
|     _onDragEnd: function(time) { | ||||
|         this._inXdndDrag = false; | ||||
|  | ||||
|         // In case the drag was canceled while in the overview | ||||
|         // we have to go back to where we started and hide | ||||
|         // the overview | ||||
|         if (this._shown) { | ||||
|         if (this._shownTemporarily)  { | ||||
|             global.screen.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time); | ||||
|             this.hide(); | ||||
|             this.hideTemporarily(); | ||||
|         } | ||||
|         this._resetWindowSwitchTimeout(); | ||||
|         this._lastHoveredWindow = null; | ||||
| @@ -364,13 +402,11 @@ const Overview = new Lang.Class({ | ||||
|             this._lastHoveredWindow = dragEvent.targetActor._delegate.metaWindow; | ||||
|             this._windowSwitchTimeoutId = Mainloop.timeout_add(DND_WINDOW_SWITCH_TIMEOUT, | ||||
|                                             Lang.bind(this, function() { | ||||
|                                                 this._windowSwitchTimeoutId = 0; | ||||
|                                                 this._needsFakePointerEvent = true; | ||||
|                                                 Main.activateWindow(dragEvent.targetActor._delegate.metaWindow, | ||||
|                                                                     this._windowSwitchTimestamp); | ||||
|                                                 this.hide(); | ||||
|                                                 this.hideTemporarily(); | ||||
|                                                 this._lastHoveredWindow = null; | ||||
|                                                 return GLib.SOURCE_REMOVE; | ||||
|                                             })); | ||||
|         } | ||||
|  | ||||
| @@ -379,7 +415,6 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|     _onScrollEvent: function(actor, event) { | ||||
|         this.emit('scroll-event', event); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     addAction: function(action) { | ||||
| @@ -435,7 +470,6 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|     beginItemDrag: function(source) { | ||||
|         this.emit('item-drag-begin'); | ||||
|         this._inDrag = true; | ||||
|     }, | ||||
|  | ||||
|     cancelledItemDrag: function(source) { | ||||
| @@ -444,45 +478,35 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|     endItemDrag: function(source) { | ||||
|         this.emit('item-drag-end'); | ||||
|         this._inDrag = false; | ||||
|     }, | ||||
|  | ||||
|     beginWindowDrag: function(clone) { | ||||
|         this.emit('window-drag-begin', clone); | ||||
|         this._inDrag = true; | ||||
|     beginWindowDrag: function(source) { | ||||
|         this.emit('window-drag-begin'); | ||||
|     }, | ||||
|  | ||||
|     cancelledWindowDrag: function(clone) { | ||||
|         this.emit('window-drag-cancelled', clone); | ||||
|     cancelledWindowDrag: function(source) { | ||||
|         this.emit('window-drag-cancelled'); | ||||
|     }, | ||||
|  | ||||
|     endWindowDrag: function(clone) { | ||||
|         this.emit('window-drag-end', clone); | ||||
|         this._inDrag = false; | ||||
|     endWindowDrag: function(source) { | ||||
|         this.emit('window-drag-end'); | ||||
|     }, | ||||
|  | ||||
|     // show: | ||||
|     // | ||||
|     // Animates the overview visible and grabs mouse and keyboard input | ||||
|     show: function() { | ||||
|     show : function() { | ||||
|         if (this.isDummy) | ||||
|             return; | ||||
|         if (this._shown) | ||||
|             return; | ||||
|         this._shown = true; | ||||
|  | ||||
|         if (!this._syncGrab()) | ||||
|         this._syncInputMode(); | ||||
|         if (!this._modal) | ||||
|             return; | ||||
|  | ||||
|         Main.layoutManager.showOverview(); | ||||
|         this._animateVisible(); | ||||
|     }, | ||||
|  | ||||
|     focusSearch: function() { | ||||
|         this.show(); | ||||
|         this._searchEntry.grab_key_focus(); | ||||
|     }, | ||||
|  | ||||
|     fadeInDesktop: function() { | ||||
|             this._desktopFade.opacity = 0; | ||||
|             this._desktopFade.show(); | ||||
| @@ -493,13 +517,8 @@ const Overview = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     fadeOutDesktop: function() { | ||||
|         if (!this._desktopFade.get_n_children()) { | ||||
|             let clone = this._getDesktopClone(); | ||||
|             if (!clone) | ||||
|                 return; | ||||
|  | ||||
|             this._desktopFade.add_child(clone); | ||||
|         } | ||||
|         if (!this._desktopFade.child) | ||||
|             this._desktopFade.child = this._getDesktopClone(); | ||||
|  | ||||
|         this._desktopFade.opacity = 255; | ||||
|         this._desktopFade.show(); | ||||
| @@ -517,10 +536,22 @@ const Overview = new Lang.Class({ | ||||
|         this.visible = true; | ||||
|         this.animationInProgress = true; | ||||
|         this.visibleTarget = true; | ||||
|         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); | ||||
|         this.viewSelector.show(); | ||||
|         global.window_group.hide(); | ||||
|         global.top_window_group.hide(); | ||||
|         this._stack.show(); | ||||
|         this._backgroundGroup.show(); | ||||
|         this._viewSelector.show(); | ||||
|  | ||||
|         this._stack.opacity = 0; | ||||
|         Tweener.addTween(this._stack, | ||||
| @@ -537,6 +568,24 @@ const Overview = new Lang.Class({ | ||||
|         this.emit('showing'); | ||||
|     }, | ||||
|  | ||||
|     // showTemporarily: | ||||
|     // | ||||
|     // Animates the overview visible without grabbing mouse and keyboard input; | ||||
|     // if show() has already been called, this has no immediate effect, but | ||||
|     // will result in the overview not being hidden until hideTemporarily() is | ||||
|     // called. | ||||
|     showTemporarily: function() { | ||||
|         if (this.isDummy) | ||||
|             return; | ||||
|  | ||||
|         if (this._shownTemporarily) | ||||
|             return; | ||||
|  | ||||
|         this._syncInputMode(); | ||||
|         this._animateVisible(); | ||||
|         this._shownTemporarily = true; | ||||
|     }, | ||||
|  | ||||
|     // hide: | ||||
|     // | ||||
|     // Reverses the effect of show() | ||||
| @@ -547,20 +596,31 @@ const Overview = new Lang.Class({ | ||||
|         if (!this._shown) | ||||
|             return; | ||||
|  | ||||
|         let event = Clutter.get_current_event(); | ||||
|         if (event) { | ||||
|             let type = event.type(); | ||||
|             let button = (type == Clutter.EventType.BUTTON_PRESS || | ||||
|                           type == Clutter.EventType.BUTTON_RELEASE); | ||||
|             let ctrl = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0; | ||||
|             if (button && ctrl) | ||||
|                 return; | ||||
|         } | ||||
|         if (this._controlPressed) | ||||
|             return; | ||||
|  | ||||
|         this._animateNotVisible(); | ||||
|         if (!this._shownTemporarily) | ||||
|             this._animateNotVisible(); | ||||
|  | ||||
|         this._shown = false; | ||||
|         this._syncGrab(); | ||||
|         this._syncInputMode(); | ||||
|     }, | ||||
|  | ||||
|     // hideTemporarily: | ||||
|     // | ||||
|     // Reverses the effect of showTemporarily() | ||||
|     hideTemporarily: function() { | ||||
|         if (this.isDummy) | ||||
|             return; | ||||
|  | ||||
|         if (!this._shownTemporarily) | ||||
|             return; | ||||
|  | ||||
|         if (!this._shown) | ||||
|             this._animateNotVisible(); | ||||
|  | ||||
|         this._shownTemporarily = false; | ||||
|         this._syncInputMode(); | ||||
|     }, | ||||
|  | ||||
|     toggle: function() { | ||||
| @@ -573,51 +633,37 @@ const Overview = new Lang.Class({ | ||||
|             this.show(); | ||||
|     }, | ||||
|  | ||||
|     // Checks if the Activities button is currently sensitive to | ||||
|     // clicks. The first call to this function within the | ||||
|     // OVERVIEW_ACTIVATION_TIMEOUT time of the hot corner being | ||||
|     // triggered will return false. This avoids opening and closing | ||||
|     // the overview if the user both triggered the hot corner and | ||||
|     // clicked the Activities button. | ||||
|     shouldToggleByCornerOrButton: function() { | ||||
|         if (this.animationInProgress) | ||||
|             return false; | ||||
|         if (this._inDrag) | ||||
|             return false; | ||||
|         if (this._activationTime == 0 || Date.now() / 1000 - this._activationTime > OVERVIEW_ACTIVATION_TIMEOUT) | ||||
|             return true; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     //// Private methods //// | ||||
|  | ||||
|     _syncGrab: function() { | ||||
|         // We delay grab changes during animation so that when removing the | ||||
|     _syncInputMode: function() { | ||||
|         // 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 | ||||
|         // going to an application. | ||||
|         if (this.animationInProgress) | ||||
|             return true; | ||||
|             return; | ||||
|  | ||||
|         if (this._shown) { | ||||
|             let shouldBeModal = !this._inXdndDrag; | ||||
|             if (shouldBeModal) { | ||||
|                 if (!this._modal) { | ||||
|                     if (Main.pushModal(this._overview, | ||||
|                                        { keybindingMode: Shell.KeyBindingMode.OVERVIEW })) { | ||||
|                         this._modal = true; | ||||
|                     } else { | ||||
|                         this.hide(); | ||||
|                         return false; | ||||
|                     } | ||||
|                 } | ||||
|             if (!this._modal) { | ||||
|                 if (Main.pushModal(this._overview, | ||||
|                                    { keybindingMode: Shell.KeyBindingMode.OVERVIEW })) | ||||
|                     this._modal = true; | ||||
|                 else | ||||
|                     this.hide(); | ||||
|             } | ||||
|         } else if (this._shownTemporarily) { | ||||
|             if (this._modal) { | ||||
|                 Main.popModal(this._overview); | ||||
|                 this._modal = false; | ||||
|             } | ||||
|             global.stage_input_mode = Shell.StageInputMode.FULLSCREEN; | ||||
|         } else { | ||||
|             if (this._modal) { | ||||
|                 Main.popModal(this._overview); | ||||
|                 this._modal = false; | ||||
|             } | ||||
|             else if (global.stage_input_mode == Shell.StageInputMode.FULLSCREEN) | ||||
|                 global.stage_input_mode = Shell.StageInputMode.NORMAL; | ||||
|         } | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _animateNotVisible: function() { | ||||
| @@ -627,7 +673,7 @@ const Overview = new Lang.Class({ | ||||
|         this.animationInProgress = true; | ||||
|         this.visibleTarget = false; | ||||
|  | ||||
|         this.viewSelector.zoomFromOverview(); | ||||
|         this._viewSelector.zoomFromOverview(); | ||||
|  | ||||
|         // Make other elements fade out. | ||||
|         Tweener.addTween(this._stack, | ||||
| @@ -651,10 +697,10 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|         this.emit('shown'); | ||||
|         // Handle any calls to hide* while we were showing | ||||
|         if (!this._shown) | ||||
|         if (!this._shown && !this._shownTemporarily) | ||||
|             this._animateNotVisible(); | ||||
|  | ||||
|         this._syncGrab(); | ||||
|         this._syncInputMode(); | ||||
|         global.sync_pointer(); | ||||
|     }, | ||||
|  | ||||
| @@ -662,21 +708,25 @@ const Overview = new Lang.Class({ | ||||
|         // Re-enable unredirection | ||||
|         Meta.enable_unredirect_for_screen(global.screen); | ||||
|  | ||||
|         this.viewSelector.hide(); | ||||
|         global.window_group.show(); | ||||
|         global.top_window_group.show(); | ||||
|  | ||||
|         this._viewSelector.hide(); | ||||
|         this._desktopFade.hide(); | ||||
|         this._coverPane.hide(); | ||||
|         this._backgroundGroup.hide(); | ||||
|         this._stack.hide(); | ||||
|  | ||||
|         this.visible = false; | ||||
|         this.animationInProgress = false; | ||||
|  | ||||
|         this._coverPane.hide(); | ||||
|  | ||||
|         this.emit('hidden'); | ||||
|         // Handle any calls to show* while we were hiding | ||||
|         if (this._shown) | ||||
|         if (this._shown || this._shownTemporarily) | ||||
|             this._animateVisible(); | ||||
|         else | ||||
|             Main.layoutManager.hideOverview(); | ||||
|  | ||||
|         this._syncGrab(); | ||||
|         this._syncInputMode(); | ||||
|  | ||||
|         // Fake a pointer event if requested | ||||
|         if (this._needsFakePointerEvent) { | ||||
|   | ||||
| @@ -1,18 +1,15 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const GObject = imports.gi.GObject; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const Dash = imports.ui.dash; | ||||
| const Main = imports.ui.main; | ||||
| const Params = imports.misc.params; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const ViewSelector = imports.ui.viewSelector; | ||||
| const WorkspaceThumbnail = imports.ui.workspaceThumbnail; | ||||
|  | ||||
| const SIDE_CONTROLS_ANIMATION_TIME = 0.16; | ||||
|  | ||||
| @@ -36,7 +33,6 @@ const SlideLayout = new Lang.Class({ | ||||
|  | ||||
|     _init: function(params) { | ||||
|         this._slideX = 1; | ||||
|         this._translationX = 0; | ||||
|         this._direction = SlideDirection.LEFT; | ||||
|  | ||||
|         this.parent(params); | ||||
| @@ -56,21 +52,18 @@ const SlideLayout = new Lang.Class({ | ||||
|     vfunc_allocate: function(container, box, flags) { | ||||
|         let child = container.get_first_child(); | ||||
|  | ||||
|         let [, , natWidth, natHeight] = child.get_preferred_size(); | ||||
|         let availWidth = Math.round(box.x2 - box.x1); | ||||
|         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 alignX = (realDirection == SlideDirection.LEFT) ? (availWidth - natWidth) : 0; | ||||
|         let translationX = (realDirection == SlideDirection.LEFT) ? | ||||
|             (availWidth - natWidth) : (natWidth - availWidth); | ||||
|  | ||||
|         let actorBox = new Clutter.ActorBox(); | ||||
|         actorBox.x1 = box.x1 + alignX + this._translationX; | ||||
|         actorBox.x2 = actorBox.x1 + (child.x_expand ? availWidth : natWidth); | ||||
|         actorBox.y1 = box.y1; | ||||
|         actorBox.y2 = actorBox.y1 + availHeight; | ||||
|         let actorBox = new Clutter.ActorBox({ x1: translationX, | ||||
|                                               y1: 0, | ||||
|                                               x2: child.x_expand ? availWidth : natWidth, | ||||
|                                               y2: child.y_expand ? availHeight : natHeight }); | ||||
|  | ||||
|         child.allocate(actorBox, flags); | ||||
|     }, | ||||
| @@ -91,16 +84,7 @@ const SlideLayout = new Lang.Class({ | ||||
|  | ||||
|     get slideDirection() { | ||||
|         return this._direction; | ||||
|     }, | ||||
|  | ||||
|     set translationX(value) { | ||||
|         this._translationX = value; | ||||
|         this.layout_changed(); | ||||
|     }, | ||||
|  | ||||
|     get translationX() { | ||||
|         return this._translationX; | ||||
|     }, | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SlidingControl = new Lang.Class({ | ||||
| @@ -109,8 +93,8 @@ const SlidingControl = new Lang.Class({ | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { slideDirection: SlideDirection.LEFT }); | ||||
|  | ||||
|         this._visible = true; | ||||
|         this._inDrag = false; | ||||
|         this.visible = true; | ||||
|         this.inDrag = false; | ||||
|  | ||||
|         this.layout = new SlideLayout(); | ||||
|         this.layout.slideDirection = params.slideDirection; | ||||
| @@ -119,7 +103,6 @@ const SlidingControl = new Lang.Class({ | ||||
|                                      clip_to_allocation: true }); | ||||
|  | ||||
|         Main.overview.connect('showing', Lang.bind(this, this._onOverviewShowing)); | ||||
|         Main.overview.connect('hiding', Lang.bind(this, this._onOverviewHiding)); | ||||
|  | ||||
|         Main.overview.connect('item-drag-begin', Lang.bind(this, this._onDragBegin)); | ||||
|         Main.overview.connect('item-drag-end', Lang.bind(this, this._onDragEnd)); | ||||
| @@ -130,12 +113,12 @@ const SlidingControl = new Lang.Class({ | ||||
|         Main.overview.connect('window-drag-end', Lang.bind(this, this._onWindowDragEnd)); | ||||
|     }, | ||||
|  | ||||
|     _getSlide: function() { | ||||
|     getSlide: function() { | ||||
|         throw new Error('getSlide() must be overridden'); | ||||
|     }, | ||||
|  | ||||
|     _updateSlide: function() { | ||||
|         Tweener.addTween(this.layout, { slideX: this._getSlide(), | ||||
|     updateSlide: function() { | ||||
|         Tweener.addTween(this.layout, { slideX: this.getSlide(), | ||||
|                                         time: SIDE_CONTROLS_ANIMATION_TIME, | ||||
|                                         transition: 'easeOutQuad' }); | ||||
|     }, | ||||
| @@ -162,30 +145,28 @@ const SlidingControl = new Lang.Class({ | ||||
|         let translationEnd = 0; | ||||
|         let translation = this._getTranslation(); | ||||
|  | ||||
|         if (this._visible) { | ||||
|         if (this.visible) { | ||||
|             translationStart = translation; | ||||
|         } else { | ||||
|             translationEnd = translation; | ||||
|         } | ||||
|  | ||||
|         if (this.layout.translationX == translationEnd) | ||||
|         if (this.actor.translation_x == translationEnd) | ||||
|             return; | ||||
|  | ||||
|         this.layout.translationX = translationStart; | ||||
|         Tweener.addTween(this.layout, { translationX: translationEnd, | ||||
|                                         time: SIDE_CONTROLS_ANIMATION_TIME, | ||||
|                                         transition: 'easeOutQuad' }); | ||||
|         this.actor.translation_x = translationStart; | ||||
|         Tweener.addTween(this.actor, { translation_x: translationEnd, | ||||
|                                        time: SIDE_CONTROLS_ANIMATION_TIME, | ||||
|                                        transition: 'easeOutQuad' | ||||
|                                      }); | ||||
|     }, | ||||
|  | ||||
|     _onOverviewShowing: function() { | ||||
|         this._visible = true; | ||||
|         this.layout.slideX = this._getSlide(); | ||||
|         this.layout.translationX = this._getTranslation(); | ||||
|         this.slideIn(); | ||||
|     }, | ||||
|  | ||||
|     _onOverviewHiding: function() { | ||||
|         this.slideOut(); | ||||
|         // reset any translation and make sure the actor is visible when | ||||
|         // entering the overview | ||||
|         this.visible = true; | ||||
|         this.layout.slideX = this.getSlide(); | ||||
|         this.actor.translation_x = 0; | ||||
|     }, | ||||
|  | ||||
|     _onWindowDragBegin: function() { | ||||
| @@ -197,14 +178,14 @@ const SlidingControl = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onDragBegin: function() { | ||||
|         this._inDrag = true; | ||||
|         this.layout.translationX = 0; | ||||
|         this._updateSlide(); | ||||
|         this.inDrag = true; | ||||
|         this.actor.translation_x = 0; | ||||
|         this.updateSlide(); | ||||
|     }, | ||||
|  | ||||
|     _onDragEnd: function() { | ||||
|         this._inDrag = false; | ||||
|         this._updateSlide(); | ||||
|         this.inDrag = false; | ||||
|         this.updateSlide(); | ||||
|     }, | ||||
|  | ||||
|     fadeIn: function() { | ||||
| @@ -222,13 +203,12 @@ const SlidingControl = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     slideIn: function() { | ||||
|         this._visible = true; | ||||
|         this._updateTranslation(); | ||||
|         this.visible = true; | ||||
|         // we will update slideX and the translation from pageEmpty | ||||
|     }, | ||||
|  | ||||
|     slideOut: function() { | ||||
|         this._visible = false; | ||||
|         this.visible = false; | ||||
|         this._updateTranslation(); | ||||
|         // we will update slideX from pageEmpty | ||||
|     }, | ||||
| @@ -238,7 +218,7 @@ const SlidingControl = new Lang.Class({ | ||||
|         // selector; this means we can now safely set the full slide for | ||||
|         // the next page, since slideIn or slideOut might have been called, | ||||
|         // changing the visiblity | ||||
|         this.layout.slideX = this._getSlide(); | ||||
|         this.layout.slideX = this.getSlide(); | ||||
|         this._updateTranslation(); | ||||
|     } | ||||
| }); | ||||
| @@ -252,20 +232,24 @@ const ThumbnailsSlider = new Lang.Class({ | ||||
|  | ||||
|         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.reactive = true; | ||||
|         this.actor.track_hover = true; | ||||
|         this.actor.add_actor(this._thumbnailsBox.actor); | ||||
|  | ||||
|         Main.layoutManager.connect('monitors-changed', 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); | ||||
|         Main.layoutManager.connect('monitors-changed', Lang.bind(this, this.updateSlide)); | ||||
|         this.actor.connect('notify::hover', Lang.bind(this, this.updateSlide)); | ||||
|     }, | ||||
|  | ||||
|     _getAlwaysZoomOut: function() { | ||||
|         // Always show the pager when hover, during a drag, or if workspaces are | ||||
|         // actually used, e.g. there are windows on more than one | ||||
|         let alwaysZoomOut = this.actor.hover || this._inDrag || !Meta.prefs_get_dynamic_workspaces() || global.screen.n_workspaces > 2; | ||||
|         let alwaysZoomOut = this.actor.hover || this.inDrag || !Meta.prefs_get_dynamic_workspaces() || global.screen.n_workspaces > 2; | ||||
|  | ||||
|         if (!alwaysZoomOut) { | ||||
|             let monitors = Main.layoutManager.monitors; | ||||
| @@ -285,13 +269,8 @@ const ThumbnailsSlider = new Lang.Class({ | ||||
|         return alwaysZoomOut; | ||||
|     }, | ||||
|  | ||||
|     getNonExpandedWidth: function() { | ||||
|         let child = this.actor.get_first_child(); | ||||
|         return child.get_theme_node().get_length('visible-width'); | ||||
|     }, | ||||
|  | ||||
|     _getSlide: function() { | ||||
|         if (!this._visible) | ||||
|     getSlide: function() { | ||||
|         if (!this.visible) | ||||
|             return 0; | ||||
|  | ||||
|         let alwaysZoomOut = this._getAlwaysZoomOut(); | ||||
| @@ -301,16 +280,18 @@ const ThumbnailsSlider = new Lang.Class({ | ||||
|         let child = this.actor.get_first_child(); | ||||
|         let preferredHeight = child.get_preferred_height(-1)[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() { | ||||
|         let alwaysZoomOut = this._getAlwaysZoomOut(); | ||||
|         if (alwaysZoomOut) | ||||
|             return this.parent(); | ||||
|         else | ||||
|             return this.getNonExpandedWidth(); | ||||
|  | ||||
|         let child = this.actor.get_first_child(); | ||||
|         return child.get_theme_node().get_length('visible-width'); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -327,18 +308,14 @@ const DashSlider = new Lang.Class({ | ||||
|         // whether to allocate the natural size to its child, or the whole | ||||
|         // available allocation | ||||
|         this._dash.actor.x_expand = true; | ||||
|  | ||||
|         this.actor.x_expand = true; | ||||
|         this.actor.x_align = Clutter.ActorAlign.START; | ||||
|         this.actor.y_expand = true; | ||||
|  | ||||
|         this._dash.actor.y_expand = true; | ||||
|         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)); | ||||
|     }, | ||||
|  | ||||
|     _getSlide: function() { | ||||
|         if (this._visible || this._inDrag) | ||||
|     getSlide: function() { | ||||
|         if (this.visible || this.inDrag) | ||||
|             return 1; | ||||
|         else | ||||
|             return 0; | ||||
| @@ -460,6 +437,9 @@ const MessagesIndicator = new Lang.Class({ | ||||
|         if (source.trayIcon) | ||||
|             return; | ||||
|  | ||||
|         if (source.isTransient) | ||||
|             return; | ||||
|  | ||||
|         source.connect('count-updated', Lang.bind(this, this._updateCount)); | ||||
|         this._sources.push(source); | ||||
|         this._updateCount(); | ||||
| @@ -472,11 +452,9 @@ const MessagesIndicator = new Lang.Class({ | ||||
|  | ||||
|     _updateCount: function() { | ||||
|         let count = 0; | ||||
|         let hasChats = false; | ||||
|         this._sources.forEach(Lang.bind(this, | ||||
|             function(source) { | ||||
|                 count += source.indicatorCount; | ||||
|                 hasChats |= source.isChat; | ||||
|             })); | ||||
|  | ||||
|         this._count = count; | ||||
| @@ -484,7 +462,6 @@ const MessagesIndicator = new Lang.Class({ | ||||
|                                     "%d new messages", | ||||
|                                    count).format(count); | ||||
|  | ||||
|         this._icon.visible = hasChats; | ||||
|         this._updateVisibility(); | ||||
|     }, | ||||
|  | ||||
| @@ -496,92 +473,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({ | ||||
|     Name: 'ControlsManager', | ||||
|  | ||||
|     _init: function(searchEntry) { | ||||
|         this.dash = new Dash.Dash(); | ||||
|         this._dashSlider = new DashSlider(this.dash); | ||||
|         this._dashSpacer = new DashSpacer(); | ||||
|         this._dashSpacer.setDashActor(this._dashSlider.actor); | ||||
|     _init: function(dash, thumbnails, viewSelector) { | ||||
|         this._dashSlider = new DashSlider(dash); | ||||
|         this.dashActor = this._dashSlider.actor; | ||||
|         this.dashSpacer = new DashSpacer(); | ||||
|         this.dashSpacer.setDashActor(this.dashActor); | ||||
|  | ||||
|         this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox(); | ||||
|         this._thumbnailsSlider = new ThumbnailsSlider(this._thumbnailsBox); | ||||
|         this._thumbnailsSlider = new ThumbnailsSlider(thumbnails); | ||||
|         this.thumbnailsActor = this._thumbnailsSlider.actor; | ||||
|  | ||||
|         this.viewSelector = new ViewSelector.ViewSelector(searchEntry, | ||||
|                                                           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._indicator = new MessagesIndicator(viewSelector); | ||||
|         this.indicatorActor = this._indicator.actor; | ||||
|  | ||||
|         let layout = new ControlsLayout(); | ||||
|         this.actor = new St.Widget({ layout_manager: layout, | ||||
|                                      reactive: true, | ||||
|                                      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)); | ||||
|         this._viewSelector = viewSelector; | ||||
|         this._viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility)); | ||||
|         this._viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty)); | ||||
|  | ||||
|         Main.overview.connect('showing', Lang.bind(this, this._updateSpacerVisibility)); | ||||
|         Main.overview.connect('item-drag-begin', Lang.bind(this, | ||||
|             function() { | ||||
|                 let activePage = this.viewSelector.getActivePage(); | ||||
|                 let activePage = this._viewSelector.getActivePage(); | ||||
|                 if (activePage != ViewSelector.ViewPage.WINDOWS) | ||||
|                     this.viewSelector.fadeHalf(); | ||||
|                     this._viewSelector.fadeHalf(); | ||||
|             })); | ||||
|         Main.overview.connect('item-drag-end', Lang.bind(this, | ||||
|             function() { | ||||
|                 this.viewSelector.fadeIn(); | ||||
|                 this._viewSelector.fadeIn(); | ||||
|             })); | ||||
|         Main.overview.connect('item-drag-cancelled', Lang.bind(this, | ||||
|             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() { | ||||
|         // Ignore the case when we're leaving the overview, since | ||||
|         // actors will be made visible again when entering the overview | ||||
| @@ -591,7 +518,7 @@ const ControlsManager = new Lang.Class({ | ||||
|             (Main.overview.animationInProgress && !Main.overview.visibleTarget)) | ||||
|             return; | ||||
|  | ||||
|         let activePage = this.viewSelector.getActivePage(); | ||||
|         let activePage = this._viewSelector.getActivePage(); | ||||
|         let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS || | ||||
|                            activePage == ViewSelector.ViewPage.APPS); | ||||
|         let thumbnailsVisible = (activePage == ViewSelector.ViewPage.WINDOWS); | ||||
| @@ -611,8 +538,8 @@ const ControlsManager = new Lang.Class({ | ||||
|         if (Main.overview.animationInProgress && !Main.overview.visibleTarget) | ||||
|             return; | ||||
|  | ||||
|         let activePage = this.viewSelector.getActivePage(); | ||||
|         this._dashSpacer.visible = (activePage == ViewSelector.ViewPage.WINDOWS); | ||||
|         let activePage = this._viewSelector.getActivePage(); | ||||
|         this.dashSpacer.visible = (activePage == ViewSelector.ViewPage.WINDOWS); | ||||
|     }, | ||||
|  | ||||
|     _onPageEmpty: function() { | ||||
|   | ||||
							
								
								
									
										499
									
								
								js/ui/panel.js
									
									
									
									
									
								
							
							
						
						
									
										499
									
								
								js/ui/panel.js
									
									
									
									
									
								
							| @@ -15,14 +15,13 @@ const Signals = imports.signals; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
|  | ||||
| const Animation = imports.ui.animation; | ||||
| const Config = imports.misc.config; | ||||
| const CtrlAltTab = imports.ui.ctrlAltTab; | ||||
| const DND = imports.ui.dnd; | ||||
| const Layout = imports.ui.layout; | ||||
| const Overview = imports.ui.overview; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const RemoteMenu = imports.ui.remoteMenu; | ||||
| const Main = imports.ui.main; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| @@ -30,6 +29,7 @@ const PANEL_ICON_SIZE = 24; | ||||
|  | ||||
| const BUTTON_DND_ACTIVATION_TIMEOUT = 250; | ||||
|  | ||||
| const ANIMATED_ICON_UPDATE_TIMEOUT = 100; | ||||
| const SPINNER_ANIMATION_TIME = 0.2; | ||||
|  | ||||
| // To make sure the panel corners blend nicely with the panel, | ||||
| @@ -75,6 +75,81 @@ function _unpremultiply(color) { | ||||
|                                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({ | ||||
|     Name: 'TextShadower', | ||||
|  | ||||
| @@ -184,11 +259,11 @@ const AppMenuButton = new Lang.Class({ | ||||
|         this._actionGroupNotifyId = 0; | ||||
|  | ||||
|         let bin = new St.Bin({ name: 'appMenu' }); | ||||
|         bin.connect('style-changed', Lang.bind(this, this._onStyleChanged)); | ||||
|         this.actor.add_actor(bin); | ||||
|  | ||||
|         this.actor.bind_property("reactive", this.actor, "can-focus", 0); | ||||
|         this.actor.reactive = false; | ||||
|         this._targetIsCurrent = false; | ||||
|  | ||||
|         this._container = new Shell.GenericContainer(); | ||||
|         bin.set_child(this._container); | ||||
| @@ -206,36 +281,35 @@ const AppMenuButton = new Lang.Class({ | ||||
|         this._iconBox.connect('notify::allocation', | ||||
|                               Lang.bind(this, this._updateIconBoxClip)); | ||||
|         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.actor.y_align = Clutter.ActorAlign.CENTER; | ||||
|         this._hbox.add_actor(this._label.actor); | ||||
|         this._arrow = PopupMenu.unicodeArrow(St.Side.BOTTOM); | ||||
|         this._hbox.add_actor(this._arrow); | ||||
|         this._container.add_actor(this._label.actor); | ||||
|  | ||||
|         this._iconBottomClip = 0; | ||||
|  | ||||
|         this._visible = !Main.overview.visible; | ||||
|         if (!this._visible) | ||||
|             this.actor.hide(); | ||||
|         this._overviewHidingId = Main.overview.connect('hiding', Lang.bind(this, this._sync)); | ||||
|         this._overviewShowingId = Main.overview.connect('showing', Lang.bind(this, this._sync)); | ||||
|         Main.overview.connect('hiding', Lang.bind(this, function () { | ||||
|             this.show(); | ||||
|         })); | ||||
|         Main.overview.connect('showing', Lang.bind(this, function () { | ||||
|             this.hide(); | ||||
|         })); | ||||
|  | ||||
|         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 appSys = Shell.AppSystem.get_default(); | ||||
|         this._focusAppNotifyId = | ||||
|             tracker.connect('notify::focus-app', Lang.bind(this, this._focusAppChanged)); | ||||
|         this._appStateChangedSignalId = | ||||
|             appSys.connect('app-state-changed', Lang.bind(this, this._onAppStateChanged)); | ||||
|         this._switchWorkspaceNotifyId = | ||||
|             global.window_manager.connect('switch-workspace', Lang.bind(this, this._sync)); | ||||
|         tracker.connect('notify::focus-app', Lang.bind(this, this._focusAppChanged)); | ||||
|         appSys.connect('app-state-changed', Lang.bind(this, this._onAppStateChanged)); | ||||
|  | ||||
|         global.window_manager.connect('switch-workspace', Lang.bind(this, this._sync)); | ||||
|  | ||||
|         this._sync(); | ||||
|     }, | ||||
| @@ -245,8 +319,13 @@ const AppMenuButton = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         this._visible = true; | ||||
|         this.actor.reactive = true; | ||||
|         this.actor.show(); | ||||
|  | ||||
|         if (!this._targetIsCurrent) | ||||
|             return; | ||||
|  | ||||
|         this.actor.reactive = true; | ||||
|  | ||||
|         Tweener.removeTweens(this.actor); | ||||
|         Tweener.addTween(this.actor, | ||||
|                          { opacity: 255, | ||||
| @@ -260,6 +339,11 @@ const AppMenuButton = new Lang.Class({ | ||||
|  | ||||
|         this._visible = false; | ||||
|         this.actor.reactive = false; | ||||
|         if (!this._targetIsCurrent) { | ||||
|             this.actor.hide(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Tweener.removeTweens(this.actor); | ||||
|         Tweener.addTween(this.actor, | ||||
|                          { opacity: 0, | ||||
| @@ -271,36 +355,19 @@ const AppMenuButton = new Lang.Class({ | ||||
|                            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() { | ||||
|         let node = this._iconBox.get_theme_node(); | ||||
|         this._iconBottomClip = node.get_length('app-icon-bottom-clip'); | ||||
|         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() { | ||||
|         if (this._iconBox.child == null) | ||||
|             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() { | ||||
| @@ -318,10 +385,7 @@ const AppMenuButton = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         this._stop = true; | ||||
|  | ||||
|         if (this._spinner == null) | ||||
|             return; | ||||
|  | ||||
|         this.actor.reactive = true; | ||||
|         Tweener.addTween(this._spinner.actor, | ||||
|                          { opacity: 0, | ||||
|                            time: SPINNER_ANIMATION_TIME, | ||||
| @@ -337,10 +401,7 @@ const AppMenuButton = new Lang.Class({ | ||||
|  | ||||
|     startAnimation: function() { | ||||
|         this._stop = false; | ||||
|  | ||||
|         if (this._spinner == null) | ||||
|             return; | ||||
|  | ||||
|         this.actor.reactive = false; | ||||
|         this._spinner.play(); | ||||
|         this._spinner.actor.show(); | ||||
|     }, | ||||
| @@ -349,7 +410,7 @@ const AppMenuButton = new Lang.Class({ | ||||
|         let [minSize, naturalSize] = this._iconBox.get_preferred_width(forHeight); | ||||
|         alloc.min_size = minSize; | ||||
|         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.natural_size = alloc.natural_size + Math.max(0, naturalSize - Math.floor(alloc.natural_size / 2)); | ||||
|     }, | ||||
| @@ -358,7 +419,7 @@ const AppMenuButton = new Lang.Class({ | ||||
|         let [minSize, naturalSize] = this._iconBox.get_preferred_height(forWidth); | ||||
|         alloc.min_size = minSize; | ||||
|         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) | ||||
|             alloc.min_size = minSize; | ||||
|         if (naturalSize > alloc.natural_size) | ||||
| @@ -388,10 +449,11 @@ const AppMenuButton = new Lang.Class({ | ||||
|  | ||||
|         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; | ||||
|         childBox.y2 = allocHeight; | ||||
|         yPadding = Math.floor(Math.max(0, allocHeight - naturalHeight) / 2); | ||||
|         childBox.y1 = yPadding; | ||||
|         childBox.y2 = childBox.y1 + Math.min(naturalHeight, allocHeight); | ||||
|  | ||||
|         if (direction == Clutter.TextDirection.LTR) { | ||||
|             childBox.x1 = Math.floor(iconWidth / 2); | ||||
| @@ -400,7 +462,21 @@ const AppMenuButton = new Lang.Class({ | ||||
|             childBox.x2 = allocWidth - Math.floor(iconWidth / 2); | ||||
|             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) { | ||||
| @@ -426,88 +502,109 @@ const AppMenuButton = new Lang.Class({ | ||||
|             // If the app has just lost focus to the panel, pretend | ||||
|             // nothing happened; otherwise you can't keynav to the | ||||
|             // app menu. | ||||
|             if (global.stage.key_focus != null) | ||||
|             if (global.stage_input_mode == Shell.StageInputMode.FOCUSED) | ||||
|                 return; | ||||
|         } | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     _findTargetApp: function() { | ||||
|         let workspace = global.screen.get_active_workspace(); | ||||
|     _sync: function() { | ||||
|         let tracker = Shell.WindowTracker.get_default(); | ||||
|         let focusedApp = tracker.focus_app; | ||||
|         if (focusedApp && focusedApp.is_on_workspace(workspace)) | ||||
|             return focusedApp; | ||||
|  | ||||
|         let lastStartedApp = null; | ||||
|         let workspace = global.screen.get_active_workspace(); | ||||
|         for (let i = 0; i < this._startingApps.length; i++) | ||||
|             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() { | ||||
|         let targetApp = this._findTargetApp(); | ||||
|         if (targetApp == null) { | ||||
|             if (!this._targetIsCurrent) | ||||
|                 return; | ||||
|  | ||||
|         if (this._targetApp != targetApp) { | ||||
|             if (this._appMenuNotifyId) { | ||||
|                 this._targetApp.disconnect(this._appMenuNotifyId); | ||||
|                 this._appMenuNotifyId = 0; | ||||
|             } | ||||
|             if (this._actionGroupNotifyId) { | ||||
|                 this._targetApp.disconnect(this._actionGroupNotifyId); | ||||
|                 this._actionGroupNotifyId = 0; | ||||
|             } | ||||
|             this.actor.reactive = false; | ||||
|             this._targetIsCurrent = false; | ||||
|  | ||||
|             this._targetApp = targetApp; | ||||
|  | ||||
|             if (this._targetApp) { | ||||
|                 this._appMenuNotifyId = this._targetApp.connect('notify::menu', Lang.bind(this, this._sync)); | ||||
|                 this._actionGroupNotifyId = this._targetApp.connect('notify::action-group', Lang.bind(this, this._sync)); | ||||
|                 this._label.setText(this._targetApp.get_name()); | ||||
|                 this.actor.set_accessible_name(this._targetApp.get_name()); | ||||
|             } | ||||
|             Tweener.removeTweens(this.actor); | ||||
|             Tweener.addTween(this.actor, { opacity: 0, | ||||
|                                            time: Overview.ANIMATION_TIME, | ||||
|                                            transition: 'easeOutQuad' }); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let visible = (this._targetApp != null && !Main.overview.visibleTarget); | ||||
|         if (visible) | ||||
|             this.show(); | ||||
|         else | ||||
|             this.hide(); | ||||
|         if (!targetApp.is_on_workspace(workspace)) | ||||
|             return; | ||||
|  | ||||
|         let isBusy = (this._targetApp != null && | ||||
|                       (this._targetApp.get_state() == Shell.AppState.STARTING || | ||||
|                        this._targetApp.get_state() == Shell.AppState.BUSY)); | ||||
|         if (isBusy) | ||||
|         if (!this._targetIsCurrent) { | ||||
|             this.actor.reactive = true; | ||||
|             this._targetIsCurrent = true; | ||||
|  | ||||
|             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(); | ||||
|         else | ||||
|             this.stopAnimation(); | ||||
|             this._maybeSetMenu(); | ||||
|  | ||||
|         this.actor.reactive = (visible && !isBusy); | ||||
|  | ||||
|         this._syncIcon(); | ||||
|         this._maybeSetMenu(); | ||||
|         this.emit('changed'); | ||||
|     }, | ||||
|  | ||||
|     _maybeSetMenu: function() { | ||||
|         let menu; | ||||
|  | ||||
|         if (this._targetApp == null) { | ||||
|             menu = null; | ||||
|         } else if (this._targetApp.action_group && this._targetApp.menu) { | ||||
|             if (this.menu instanceof RemoteMenu.RemoteMenu && | ||||
|         if (this._targetApp.action_group && this._targetApp.menu) { | ||||
|             if (this.menu instanceof PopupMenu.RemoteMenu && | ||||
|                 this.menu.actionGroup == this._targetApp.action_group) | ||||
|                 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() { | ||||
|                 let win = this._targetApp.get_windows()[0]; | ||||
|                 win.check_alive(global.get_current_time()); | ||||
|             })); | ||||
|  | ||||
|         } else { | ||||
|             if (this.menu && this.menu.isDummyQuitMenu) | ||||
|             if (this.menu.isDummyQuitMenu) | ||||
|                 return; | ||||
|  | ||||
|             // fallback to older menu | ||||
| @@ -519,35 +616,7 @@ const AppMenuButton = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         this.setMenu(menu); | ||||
|         if (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(); | ||||
|         this._menuManager.addMenu(menu); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -561,16 +630,23 @@ const ActivitiesButton = new Lang.Class({ | ||||
|         this.parent(0.0, null, true); | ||||
|         this.actor.accessible_role = Atk.Role.TOGGLE_BUTTON; | ||||
|  | ||||
|         let container = new Shell.GenericContainer(); | ||||
|         container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth)); | ||||
|         container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight)); | ||||
|         container.connect('allocate', Lang.bind(this, this._containerAllocate)); | ||||
|         this.actor.add_actor(container); | ||||
|         this.actor.name = 'panelActivities'; | ||||
|  | ||||
|         /* Translators: If there is no suitable word for "Activities" | ||||
|            in your language, you can use the word for "Overview". */ | ||||
|         this._label = new St.Label({ text: _("Activities"), | ||||
|                                      y_align: Clutter.ActorAlign.CENTER }); | ||||
|         this.actor.add_actor(this._label); | ||||
|         this._label = new St.Label({ text: _("Activities") }); | ||||
|         container.add_actor(this._label); | ||||
|  | ||||
|         this.actor.label_actor = this._label; | ||||
|  | ||||
|         this.hotCorner = new Layout.HotCorner(Main.layoutManager); | ||||
|         container.add_actor(this.hotCorner.actor); | ||||
|  | ||||
|         this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent)); | ||||
|         this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRelease)); | ||||
|         this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease)); | ||||
| @@ -585,6 +661,44 @@ const ActivitiesButton = new Lang.Class({ | ||||
|         })); | ||||
|  | ||||
|         this._xdndTimeOut = 0; | ||||
|  | ||||
|         // Since the hot corner uses stage coordinates, Clutter won't | ||||
|         // queue relayouts for us when the panel moves. Queue a relayout | ||||
|         // when that happens. | ||||
|         Main.layoutManager.connect('panel-box-changed', Lang.bind(this, function() { | ||||
|             container.queue_relayout(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _containerGetPreferredWidth: function(actor, forHeight, alloc) { | ||||
|         [alloc.min_size, alloc.natural_size] = this._label.get_preferred_width(forHeight); | ||||
|     }, | ||||
|  | ||||
|     _containerGetPreferredHeight: function(actor, forWidth, alloc) { | ||||
|         [alloc.min_size, alloc.natural_size] = this._label.get_preferred_height(forWidth); | ||||
|     }, | ||||
|  | ||||
|     _containerAllocate: function(actor, box, flags) { | ||||
|         this._label.allocate(box, flags); | ||||
|  | ||||
|         // The hot corner needs to be outside any padding/alignment | ||||
|         // that has been imposed on us | ||||
|         let primary = Main.layoutManager.primaryMonitor; | ||||
|         let hotBox = new Clutter.ActorBox(); | ||||
|         let ok, x, y; | ||||
|         if (actor.get_text_direction() == Clutter.TextDirection.LTR) { | ||||
|             [ok, x, y] = actor.transform_stage_point(primary.x, primary.y) | ||||
|         } else { | ||||
|             [ok, x, y] = actor.transform_stage_point(primary.x + primary.width, primary.y); | ||||
|             // hotCorner.actor has northeast gravity, so we don't need | ||||
|             // to adjust x for its width | ||||
|         } | ||||
|  | ||||
|         hotBox.x1 = Math.round(x); | ||||
|         hotBox.x2 = hotBox.x1 + this.hotCorner.actor.width; | ||||
|         hotBox.y1 = Math.round(y); | ||||
|         hotBox.y2 = hotBox.y1 + this.hotCorner.actor.height; | ||||
|         this.hotCorner.actor.allocate(hotBox, flags); | ||||
|     }, | ||||
|  | ||||
|     handleDragOver: function(source, actor, x, y, time) { | ||||
| @@ -594,23 +708,21 @@ const ActivitiesButton = new Lang.Class({ | ||||
|         if (this._xdndTimeOut != 0) | ||||
|             Mainloop.source_remove(this._xdndTimeOut); | ||||
|         this._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT, | ||||
|                                                  Lang.bind(this, this._xdndToggleOverview, actor)); | ||||
|                                                  Lang.bind(this, this._xdndShowOverview, actor)); | ||||
|  | ||||
|         return DND.DragMotionResult.CONTINUE; | ||||
|     }, | ||||
|  | ||||
|     _onCapturedEvent: function(actor, event) { | ||||
|         if (event.type() == Clutter.EventType.BUTTON_PRESS) { | ||||
|             if (!Main.overview.shouldToggleByCornerOrButton()) | ||||
|                 return Clutter.EVENT_STOP; | ||||
|             if (!this.hotCorner.shouldToggleOverviewOnClick()) | ||||
|                 return true; | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onButtonRelease: function() { | ||||
|         Main.overview.toggle(); | ||||
|         this.menu.close(); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onKeyRelease: function(actor, event) { | ||||
| @@ -618,19 +730,20 @@ const ActivitiesButton = new Lang.Class({ | ||||
|         if (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_space) { | ||||
|             Main.overview.toggle(); | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _xdndToggleOverview: function(actor) { | ||||
|     _xdndShowOverview: function(actor) { | ||||
|         let [x, y, mask] = global.get_pointer(); | ||||
|         let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y); | ||||
|  | ||||
|         if (pickedActor == this.actor && Main.overview.shouldToggleByCornerOrButton()) | ||||
|             Main.overview.toggle(); | ||||
|         if (pickedActor == this.actor) { | ||||
|             if (!Main.overview.visible && !Main.overview.animationInProgress) { | ||||
|                 Main.overview.showTemporarily(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         Mainloop.source_remove(this._xdndTimeOut); | ||||
|         this._xdndTimeOut = 0; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -801,67 +914,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._location = new imports.ui.status.location.Indicator(); | ||||
|  | ||||
|         this._indicators.add_child(this._screencast.indicators); | ||||
|         this._indicators.add_child(this._location.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 = { | ||||
|     'activities': ActivitiesButton, | ||||
|     'aggregateMenu': AggregateMenu, | ||||
|     'appMenu': AppMenuButton, | ||||
|     'dateMenu': imports.ui.dateMenu.DateMenuButton, | ||||
|     'a11y': imports.ui.status.accessibility.ATIndicator, | ||||
|     '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, | ||||
|     '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({ | ||||
|     Name: 'Panel', | ||||
|  | ||||
| @@ -874,7 +952,7 @@ const Panel = new Lang.Class({ | ||||
|  | ||||
|         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.actor.add_actor(this._leftBox); | ||||
| @@ -988,23 +1066,23 @@ const Panel = new Lang.Class({ | ||||
|  | ||||
|     _onButtonPress: function(actor, event) { | ||||
|         if (Main.modalCount > 0) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         if (event.get_source() != actor) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         let button = event.get_button(); | ||||
|         if (button != 1) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         let focusWindow = global.display.focus_window; | ||||
|         if (!focusWindow) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         let dragWindow = focusWindow.is_attached_dialog() ? focusWindow.get_transient_for() | ||||
|                                                           : focusWindow; | ||||
|         if (!dragWindow) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         let rect = dragWindow.get_outer_rect(); | ||||
|         let [stageX, stageY] = event.get_coords(); | ||||
| @@ -1013,7 +1091,7 @@ const Panel = new Lang.Class({ | ||||
|                         stageX > rect.x && stageX < rect.x + rect.width; | ||||
|  | ||||
|         if (!allowDrag) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         global.display.begin_grab_op(global.screen, | ||||
|                                      dragWindow, | ||||
| @@ -1025,21 +1103,20 @@ const Panel = new Lang.Class({ | ||||
|                                      event.get_time(), | ||||
|                                      stageX, stageY); | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     toggleAppMenu: function() { | ||||
|     openAppMenu: function() { | ||||
|         let indicator = this.statusArea.appMenu; | ||||
|         if (!indicator) // appMenu not supported by current session mode | ||||
|             return; | ||||
|  | ||||
|         let menu = indicator.menu; | ||||
|         if (!indicator.actor.reactive) | ||||
|         if (!indicator.actor.reactive || menu.isOpen) | ||||
|             return; | ||||
|  | ||||
|         menu.toggle(); | ||||
|         if (menu.isOpen) | ||||
|             menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); | ||||
|         menu.open(); | ||||
|         menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); | ||||
|     }, | ||||
|  | ||||
|     set boxOpacity(value) { | ||||
|   | ||||
| @@ -86,8 +86,13 @@ const ButtonBox = new Lang.Class({ | ||||
|             childBox.x2 = availWidth - this._minHPadding; | ||||
|         } | ||||
|  | ||||
|         childBox.y1 = 0; | ||||
|         childBox.y2 = availHeight; | ||||
|         if (natHeight <= availHeight) { | ||||
|             childBox.y1 = Math.floor((availHeight - natHeight) / 2); | ||||
|             childBox.y2 = childBox.y1 + natHeight; | ||||
|         } else { | ||||
|             childBox.y1 = 0; | ||||
|             childBox.y2 = availHeight; | ||||
|         } | ||||
|  | ||||
|         child.allocate(childBox, flags); | ||||
|     }, | ||||
| @@ -101,17 +106,17 @@ const Button = new Lang.Class({ | ||||
|         this.parent({ reactive: true, | ||||
|                       can_focus: true, | ||||
|                       track_hover: true, | ||||
|                       accessible_name: nameText ? nameText : "", | ||||
|                       accessible_role: Atk.Role.MENU }); | ||||
|  | ||||
|         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('notify::visible', Lang.bind(this, this._onVisibilityChanged)); | ||||
|  | ||||
|         if (dontCreateMenu) | ||||
|             this.menu = new PopupMenu.PopupDummyMenu(this.actor); | ||||
|         else | ||||
|             this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0)); | ||||
|  | ||||
|         this.setName(nameText); | ||||
|     }, | ||||
|  | ||||
|     setSensitive: function(sensitive) { | ||||
| @@ -120,6 +125,22 @@ const Button = new Lang.Class({ | ||||
|         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) { | ||||
|         if (this.menu) | ||||
|             this.menu.destroy(); | ||||
| @@ -137,54 +158,42 @@ const Button = new Lang.Class({ | ||||
|  | ||||
|     _onButtonPress: function(actor, event) { | ||||
|         if (!this.menu) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return; | ||||
|  | ||||
|         this.menu.toggle(); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onSourceKeyPress: function(actor, event) { | ||||
|         if (!this.menu) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) { | ||||
|             this.menu.toggle(); | ||||
|             return Clutter.EVENT_STOP; | ||||
|             return true; | ||||
|         } else if (symbol == Clutter.KEY_Escape && this.menu.isOpen) { | ||||
|             this.menu.close(); | ||||
|             return Clutter.EVENT_STOP; | ||||
|             return true; | ||||
|         } else if (symbol == Clutter.KEY_Down) { | ||||
|             if (!this.menu.isOpen) | ||||
|                 this.menu.toggle(); | ||||
|             this.menu.actor.navigate_focus(this.actor, Gtk.DirectionType.DOWN, false); | ||||
|             return Clutter.EVENT_STOP; | ||||
|             return true; | ||||
|         } else | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onVisibilityChanged: function() { | ||||
|         if (!this.menu) | ||||
|             return; | ||||
|  | ||||
|         if (!this.actor.visible) | ||||
|             this.menu.close(); | ||||
|             return false; | ||||
|     }, | ||||
|  | ||||
|     _onMenuKeyPress: function(actor, event) { | ||||
|         if (global.focus_manager.navigate_from_event(event)) | ||||
|             return Clutter.EVENT_STOP; | ||||
|  | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) { | ||||
|             let group = global.focus_manager.get_group(this.actor); | ||||
|             if (group) { | ||||
|                 let direction = (symbol == Clutter.KEY_Left) ? Gtk.DirectionType.LEFT : Gtk.DirectionType.RIGHT; | ||||
|                 group.navigate_focus(this.actor, direction, false); | ||||
|                 return Clutter.EVENT_STOP; | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onOpenStateChanged: function(menu, open) { | ||||
| @@ -212,35 +221,51 @@ const Button = new Lang.Class({ | ||||
| }); | ||||
| Signals.addSignalMethods(Button.prototype); | ||||
|  | ||||
| /* SystemIndicator: | ||||
| /* SystemStatusButton: | ||||
|  * | ||||
|  * This class manages one system indicator, which are the icons | ||||
|  * that you see at the top right. A system indicator is composed | ||||
|  * of an icon and a menu section, which will be composed into the | ||||
|  * aggregate menu. | ||||
|  * This class manages one System Status indicator (network, keyboard, | ||||
|  * volume, bluetooth...), which is just a PanelMenuButton with an | ||||
|  * icon. | ||||
|  */ | ||||
| const SystemIndicator = new Lang.Class({ | ||||
|     Name: 'SystemIndicator', | ||||
| const SystemStatusButton = new Lang.Class({ | ||||
|     Name: 'SystemStatusButton', | ||||
|     Extends: Button, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box', | ||||
|                                              reactive: true }); | ||||
|         this.indicators.hide(); | ||||
|         this.menu = new PopupMenu.PopupMenuSection(); | ||||
|     _init: function(iconName, nameText) { | ||||
|         this.parent(0.0, nameText); | ||||
|         this.actor.add_style_class_name('panel-status-button'); | ||||
|  | ||||
|         this._box = new St.BoxLayout({ style_class: 'panel-status-button-box' }); | ||||
|         this.actor.add_actor(this._box); | ||||
|  | ||||
|         if (iconName) | ||||
|             this.setIcon(iconName); | ||||
|     }, | ||||
|  | ||||
|     _syncIndicatorsVisible: function() { | ||||
|         this.indicators.visible = this.indicators.get_children().some(function(actor) { | ||||
|             return actor.visible; | ||||
|         }); | ||||
|     get icons() { | ||||
|         return this._box.get_children(); | ||||
|     }, | ||||
|  | ||||
|     _addIndicator: function() { | ||||
|         let icon = new St.Icon({ style_class: 'system-status-icon' }); | ||||
|         this.indicators.add_actor(icon); | ||||
|         icon.connect('notify::visible', Lang.bind(this, this._syncIndicatorsVisible)); | ||||
|         this._syncIndicatorsVisible(); | ||||
|     addIcon: function(gicon) { | ||||
|         let icon = new St.Icon({ gicon: gicon, | ||||
|                                  style_class: 'system-status-icon' }); | ||||
|         this._box.add_actor(icon); | ||||
|  | ||||
|         this.emit('icons-changed'); | ||||
|  | ||||
|         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); | ||||
|   | ||||
| @@ -1,9 +1,7 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| const GnomeDesktop = imports.gi.GnomeDesktop; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| @@ -43,7 +41,7 @@ const PointerWatcher = new Lang.Class({ | ||||
|     Name: 'PointerWatcher', | ||||
|  | ||||
|     _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._idle = this._idleMonitor.get_idletime() > IDLE_TIME; | ||||
|         this._watches = []; | ||||
| @@ -111,7 +109,7 @@ const PointerWatcher = new Lang.Class({ | ||||
|  | ||||
|     _onTimeout: function() { | ||||
|         this._updatePointer(); | ||||
|         return GLib.SOURCE_CONTINUE; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _updatePointer: function() { | ||||
|   | ||||
							
								
								
									
										1610
									
								
								js/ui/popupMenu.js
									
									
									
									
									
								
							
							
						
						
									
										1610
									
								
								js/ui/popupMenu.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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(); | ||||
|     }, | ||||
| }); | ||||
| @@ -12,191 +12,193 @@ const Search = imports.ui.search; | ||||
|  | ||||
| const KEY_FILE_GROUP = 'Shell Search Provider'; | ||||
|  | ||||
| const SearchProviderIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.SearchProvider"> \ | ||||
| <method name="GetInitialResultSet"> \ | ||||
|     <arg type="as" direction="in" /> \ | ||||
|     <arg type="as" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetSubsearchResultSet"> \ | ||||
|     <arg type="as" direction="in" /> \ | ||||
|     <arg type="as" direction="in" /> \ | ||||
|     <arg type="as" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetResultMetas"> \ | ||||
|     <arg type="as" direction="in" /> \ | ||||
|     <arg type="aa{sv}" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="ActivateResult"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const SearchProviderIface = <interface name="org.gnome.Shell.SearchProvider"> | ||||
| <method name="GetInitialResultSet"> | ||||
|     <arg type="as" direction="in" /> | ||||
|     <arg type="as" direction="out" /> | ||||
| </method> | ||||
| <method name="GetSubsearchResultSet"> | ||||
|     <arg type="as" direction="in" /> | ||||
|     <arg type="as" direction="in" /> | ||||
|     <arg type="as" direction="out" /> | ||||
| </method> | ||||
| <method name="GetResultMetas"> | ||||
|     <arg type="as" direction="in" /> | ||||
|     <arg type="aa{sv}" direction="out" /> | ||||
| </method> | ||||
| <method name="ActivateResult"> | ||||
|     <arg type="s" direction="in" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const SearchProvider2Iface = '<node> \ | ||||
| <interface name="org.gnome.Shell.SearchProvider2"> \ | ||||
| <method name="GetInitialResultSet"> \ | ||||
|     <arg type="as" direction="in" /> \ | ||||
|     <arg type="as" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetSubsearchResultSet"> \ | ||||
|     <arg type="as" direction="in" /> \ | ||||
|     <arg type="as" direction="in" /> \ | ||||
|     <arg type="as" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetResultMetas"> \ | ||||
|     <arg type="as" direction="in" /> \ | ||||
|     <arg type="aa{sv}" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="ActivateResult"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="as" direction="in" /> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="LaunchSearch"> \ | ||||
|     <arg type="as" direction="in" /> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const SearchProvider2Iface = <interface name="org.gnome.Shell.SearchProvider2"> | ||||
| <method name="GetInitialResultSet"> | ||||
|     <arg type="as" direction="in" /> | ||||
|     <arg type="as" direction="out" /> | ||||
| </method> | ||||
| <method name="GetSubsearchResultSet"> | ||||
|     <arg type="as" direction="in" /> | ||||
|     <arg type="as" direction="in" /> | ||||
|     <arg type="as" direction="out" /> | ||||
| </method> | ||||
| <method name="GetResultMetas"> | ||||
|     <arg type="as" direction="in" /> | ||||
|     <arg type="aa{sv}" direction="out" /> | ||||
| </method> | ||||
| <method name="ActivateResult"> | ||||
|     <arg type="s" direction="in" /> | ||||
|     <arg type="as" direction="in" /> | ||||
|     <arg type="u" direction="in" /> | ||||
| </method> | ||||
| <method name="LaunchSearch"> | ||||
|     <arg type="as" direction="in" /> | ||||
|     <arg type="u" direction="in" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| var SearchProviderProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProviderIface); | ||||
| var SearchProvider2ProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProvider2Iface); | ||||
| var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface); | ||||
| var SearchProvider2Proxy = Gio.DBusProxy.makeProxyWrapper(SearchProvider2Iface); | ||||
|  | ||||
| function loadRemoteSearchProviders(callback) { | ||||
|     let objectPaths = {}; | ||||
|     let loadedProviders = []; | ||||
| function loadRemoteSearchProviders(addProviderCallback) { | ||||
|     let data = { loadedProviders: [], | ||||
|                  objectPaths: {}, | ||||
|                  addProviderCallback: addProviderCallback }; | ||||
|     FileUtils.collectFromDatadirsAsync('search-providers', | ||||
|                                        { loadedCallback: remoteProvidersLoaded, | ||||
|                                          processFile: loadRemoteSearchProvider, | ||||
|                                          data: data | ||||
|                                        }); | ||||
| } | ||||
|  | ||||
|     function loadRemoteSearchProvider(file) { | ||||
|         let keyfile = new GLib.KeyFile(); | ||||
|         let path = file.get_path(); | ||||
| function loadRemoteSearchProvider(file, info, data) { | ||||
|     let keyfile = new GLib.KeyFile(); | ||||
|     let path = file.get_path(); | ||||
|  | ||||
|         try { | ||||
|             keyfile.load_from_file(path, 0); | ||||
|         } catch(e) { | ||||
|             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 searchSettings = new Gio.Settings({ schema: Search.SEARCH_PROVIDERS_SCHEMA }); | ||||
|     if (searchSettings.get_boolean('disable-external')) { | ||||
|         callback([]); | ||||
|     try { | ||||
|         keyfile.load_from_file(path, 0); | ||||
|     } catch(e) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     FileUtils.collectFromDatadirs('search-providers', false, loadRemoteSearchProvider); | ||||
|     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 (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 sortOrder = searchSettings.get_strv('sort-order'); | ||||
|  | ||||
|     // Special case gnome-control-center to be always active and always first | ||||
|     sortOrder.unshift('gnome-control-center.desktop'); | ||||
|  | ||||
|     loadedProviders = loadedProviders.filter(function(provider) { | ||||
|         let appId = provider.appInfo.get_id(); | ||||
|         let disabled = searchSettings.get_strv('disabled'); | ||||
|         return disabled.indexOf(appId) == -1; | ||||
|     }); | ||||
|     let numSorted = sortOrder.length; | ||||
|  | ||||
|     loadedProviders.sort(function(providerA, providerB) { | ||||
|         let idxA, idxB; | ||||
|         let appIdA, appIdB; | ||||
|     loadState.loadedProviders.sort( | ||||
|         function(providerA, providerB) { | ||||
|             let idxA, idxB; | ||||
|             let appIdA, appIdB; | ||||
|  | ||||
|         appIdA = providerA.appInfo.get_id(); | ||||
|         appIdB = providerB.appInfo.get_id(); | ||||
|             appIdA = providerA.appInfo.get_id(); | ||||
|             appIdB = providerB.appInfo.get_id(); | ||||
|  | ||||
|         idxA = sortOrder.indexOf(appIdA); | ||||
|         idxB = sortOrder.indexOf(appIdB); | ||||
|             idxA = sortOrder.indexOf(appIdA); | ||||
|             idxB = sortOrder.indexOf(appIdB); | ||||
|  | ||||
|         // if no provider is found in the order, use alphabetical order | ||||
|         if ((idxA == -1) && (idxB == -1)) { | ||||
|             let nameA = providerA.appInfo.get_name(); | ||||
|             let nameB = providerB.appInfo.get_name(); | ||||
|             // if no provider is found in the order, use alphabetical order | ||||
|             if ((idxA == -1) && (idxB == -1)) { | ||||
|                 let nameA = providerA.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 (idxA == -1) | ||||
|             return 1; | ||||
|             if (numSorted > 1) { | ||||
|                 // if providerA is the last, it goes after everything | ||||
|                 if ((idxA + 1) == numSorted) | ||||
|                     return 1; | ||||
|                 // if providerB is the last, it goes after everything | ||||
|                 else if ((idxB + 1) == numSorted) | ||||
|                     return -1; | ||||
|             } | ||||
|  | ||||
|         // if providerB isn't found, it's sorted after providerA | ||||
|         if (idxB == -1) | ||||
|             return -1; | ||||
|             // if providerA isn't found, it's sorted after providerB | ||||
|             if (idxA == -1) | ||||
|                 return 1; | ||||
|  | ||||
|         // finally, if both providers are found, return their order in the list | ||||
|         return (idxA - idxB); | ||||
|     }); | ||||
|             // if providerB isn't found, it's sorted after providerA | ||||
|             if (idxB == -1) | ||||
|                 return -1; | ||||
|  | ||||
|     callback(loadedProviders); | ||||
|             // finally, if both providers are found, return their order in the list | ||||
|             return (idxA - idxB); | ||||
|         }); | ||||
|  | ||||
|     loadState.loadedProviders.forEach( | ||||
|         function(provider) { | ||||
|             loadState.addProviderCallback(provider); | ||||
|         }); | ||||
| } | ||||
|  | ||||
| const RemoteSearchProvider = new Lang.Class({ | ||||
|     Name: 'RemoteSearchProvider', | ||||
|  | ||||
|     _init: function(appInfo, dbusName, dbusPath, proxyInfo) { | ||||
|         if (!proxyInfo) | ||||
|             proxyInfo = SearchProviderProxyInfo; | ||||
|     _init: function(appInfo, dbusName, dbusPath, proxyType) { | ||||
|         if (!proxyType) | ||||
|             proxyType = SearchProviderProxy; | ||||
|  | ||||
|         this.proxy = new Gio.DBusProxy({ g_bus_type: Gio.BusType.SESSION, | ||||
|                                          g_name: dbusName, | ||||
|                                          g_object_path: dbusPath, | ||||
|                                          g_interface_info: proxyInfo, | ||||
|                                          g_interface_name: proxyInfo.name, | ||||
|                                          g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION | | ||||
|                                                    Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) }); | ||||
|         this.proxy.init_async(GLib.PRIORITY_DEFAULT, null, null); | ||||
|         this.proxy = new proxyType(Gio.DBus.session, | ||||
|                 dbusName, dbusPath, Lang.bind(this, this._onProxyConstructed)); | ||||
|  | ||||
|         this.appInfo = appInfo; | ||||
|         this.id = appInfo.get_id(); | ||||
|         this.isRemoteProvider = true; | ||||
|  | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|     }, | ||||
|  | ||||
|     _onProxyConstructed: function(proxy) { | ||||
|         // Do nothing | ||||
|     }, | ||||
|  | ||||
|     createIcon: function(size, meta) { | ||||
|         let gicon = null; | ||||
|         let icon = null; | ||||
|  | ||||
|         if (meta['icon']) { | ||||
|             gicon = Gio.icon_deserialize(meta['icon']); | ||||
|         } else if (meta['gicon']) { | ||||
|         let gicon; | ||||
|         if (meta['gicon']) { | ||||
|             gicon = Gio.icon_new_for_string(meta['gicon']); | ||||
|         } else if (meta['icon-data']) { | ||||
|             let [width, height, rowStride, hasAlpha, | ||||
| @@ -205,61 +207,52 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|                                                        bitsPerSample, width, height, rowStride); | ||||
|         } | ||||
|  | ||||
|         if (gicon) | ||||
|             icon = new St.Icon({ gicon: gicon, | ||||
|                                  icon_size: size }); | ||||
|         return icon; | ||||
|         return new St.Icon({ gicon: gicon, | ||||
|                              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, callback) { | ||||
|         if (error) { | ||||
|             if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) | ||||
|                 log('Received error from DBus search provider %s: %s'.format(this.id, String(error))); | ||||
|             callback([]); | ||||
|     _getResultsFinished: function(results, error) { | ||||
|         if (error) | ||||
|             return; | ||||
|         this.searchSystem.pushResults(this, results[0]); | ||||
|     }, | ||||
|  | ||||
|     getInitialResultSet: function(terms) { | ||||
|         this._cancellable.cancel(); | ||||
|         this._cancellable.reset(); | ||||
|         try { | ||||
|             this.proxy.GetInitialResultSetRemote(terms, | ||||
|                                                  Lang.bind(this, this._getResultsFinished), | ||||
|                                                  this._cancellable); | ||||
|         } catch(e) { | ||||
|             log('Error calling GetInitialResultSet for provider %s: %s'.format(this.id, e.toString())); | ||||
|             this.searchSystem.pushResults(this, []); | ||||
|         } | ||||
|  | ||||
|         callback(results[0]); | ||||
|     }, | ||||
|  | ||||
|     getInitialResultSet: function(terms, callback, cancellable) { | ||||
|         this.proxy.GetInitialResultSetRemote(terms, | ||||
|                                              Lang.bind(this, this._getResultsFinished, callback), | ||||
|                                              cancellable); | ||||
|     }, | ||||
|  | ||||
|     getSubsearchResultSet: function(previousResults, newTerms, callback, cancellable) { | ||||
|         this.proxy.GetSubsearchResultSetRemote(previousResults, newTerms, | ||||
|                                                Lang.bind(this, this._getResultsFinished, callback), | ||||
|                                                cancellable); | ||||
|     getSubsearchResultSet: function(previousResults, newTerms) { | ||||
|         this._cancellable.cancel(); | ||||
|         this._cancellable.reset(); | ||||
|         try { | ||||
|             this.proxy.GetSubsearchResultSetRemote(previousResults, newTerms, | ||||
|                                                    Lang.bind(this, this._getResultsFinished), | ||||
|                                                    this._cancellable); | ||||
|         } catch(e) { | ||||
|             log('Error calling GetSubsearchResultSet for provider %s: %s'.format(this.id, e.toString())); | ||||
|             this.searchSystem.pushResults(this, []); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _getResultMetasFinished: function(results, error, callback) { | ||||
|         if (error) { | ||||
|             if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) | ||||
|                 log('Received error from DBus search provider %s during GetResultMetas: %s'.format(this.id, String(error))); | ||||
|             callback([]); | ||||
|             return; | ||||
|         } | ||||
|         let metas = results[0]; | ||||
|         let resultMetas = []; | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             for (let prop in metas[i]) { | ||||
|                 // we can use the serialized icon variant directly | ||||
|                 if (prop != 'icon') | ||||
|                     metas[i][prop] = metas[i][prop].deep_unpack(); | ||||
|             } | ||||
|  | ||||
|             for (let prop in metas[i]) | ||||
|                 metas[i][prop] = metas[i][prop].deep_unpack(); | ||||
|             resultMetas.push({ id: metas[i]['id'], | ||||
|                                name: metas[i]['name'], | ||||
|                                description: metas[i]['description'], | ||||
| @@ -269,10 +262,17 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|         callback(resultMetas); | ||||
|     }, | ||||
|  | ||||
|     getResultMetas: function(ids, callback, cancellable) { | ||||
|         this.proxy.GetResultMetasRemote(ids, | ||||
|                                         Lang.bind(this, this._getResultMetasFinished, callback), | ||||
|                                         cancellable); | ||||
|     getResultMetas: function(ids, callback) { | ||||
|         this._cancellable.cancel(); | ||||
|         this._cancellable.reset(); | ||||
|         try { | ||||
|             this.proxy.GetResultMetasRemote(ids, | ||||
|                                             Lang.bind(this, this._getResultMetasFinished, callback), | ||||
|                                             this._cancellable); | ||||
|         } catch(e) { | ||||
|             log('Error calling GetResultMetas for provider %s: %s'.format(this.id, e.toString())); | ||||
|             callback([]); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     activateResult: function(id) { | ||||
| @@ -283,7 +283,7 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|         // the provider is not compatible with the new version of the interface, launch | ||||
|         // the app itself but warn so we can catch the error in logs | ||||
|         log('Search provider ' + this.appInfo.get_id() + ' does not implement LaunchSearch'); | ||||
|         this.appInfo.launch([], global.create_app_launch_context(0, -1)); | ||||
|         this.appInfo.launch([], global.create_app_launch_context()); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -292,7 +292,7 @@ const RemoteSearchProvider2 = new Lang.Class({ | ||||
|     Extends: RemoteSearchProvider, | ||||
|  | ||||
|     _init: function(appInfo, dbusName, dbusPath) { | ||||
|         this.parent(appInfo, dbusName, dbusPath, SearchProvider2ProxyInfo); | ||||
|         this.parent(appInfo, dbusName, dbusPath, SearchProvider2Proxy); | ||||
|  | ||||
|         this.canLaunchSearch = true; | ||||
|     }, | ||||
|   | ||||
| @@ -30,13 +30,144 @@ const EXEC_ARG_KEY = 'exec-arg'; | ||||
|  | ||||
| const DIALOG_GROW_TIME = 0.1; | ||||
|  | ||||
| const CommandCompleter = new Lang.Class({ | ||||
|     Name: 'CommandCompleter', | ||||
|  | ||||
|     _init : function() { | ||||
|         this._changedCount = 0; | ||||
|         this._paths = GLib.getenv('PATH').split(':'); | ||||
|         this._paths.push(GLib.get_home_dir()); | ||||
|         this._valid = false; | ||||
|         this._updateInProgress = false; | ||||
|         this._childs = new Array(this._paths.length); | ||||
|         this._monitors = new Array(this._paths.length); | ||||
|         for (let i = 0; i < this._paths.length; i++) { | ||||
|             this._childs[i] = []; | ||||
|             let file = Gio.file_new_for_path(this._paths[i]); | ||||
|             let info; | ||||
|             try { | ||||
|                 info = file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, null); | ||||
|             } catch (e) { | ||||
|                 // FIXME catchall | ||||
|                 this._paths[i] = null; | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (info.get_attribute_uint32(Gio.FILE_ATTRIBUTE_STANDARD_TYPE) != Gio.FileType.DIRECTORY) | ||||
|                 continue; | ||||
|  | ||||
|             this._paths[i] = file.get_path(); | ||||
|             this._monitors[i] = file.monitor_directory(Gio.FileMonitorFlags.NONE, null); | ||||
|             if (this._monitors[i] != null) { | ||||
|                 this._monitors[i].connect('changed', Lang.bind(this, this._onChanged)); | ||||
|             } | ||||
|         } | ||||
|         this._paths = this._paths.filter(function(a) { | ||||
|             return a != null; | ||||
|         }); | ||||
|         this._update(0); | ||||
|     }, | ||||
|  | ||||
|     update : function() { | ||||
|         if (this._valid) | ||||
|             return; | ||||
|         this._update(0); | ||||
|     }, | ||||
|  | ||||
|     _update : function(i) { | ||||
|         if (i == 0 && this._updateInProgress) | ||||
|             return; | ||||
|         this._updateInProgress = true; | ||||
|         this._changedCount = 0; | ||||
|         this._i = i; | ||||
|         if (i >= this._paths.length) { | ||||
|             this._valid = true; | ||||
|             this._updateInProgress = false; | ||||
|             return; | ||||
|         } | ||||
|         let file = Gio.file_new_for_path(this._paths[i]); | ||||
|         this._childs[this._i] = []; | ||||
|         FileUtils.listDirAsync(file, Lang.bind(this, function (files) { | ||||
|             for (let i = 0; i < files.length; i++) { | ||||
|                 this._childs[this._i].push(files[i].get_name()); | ||||
|             } | ||||
|             this._update(this._i + 1); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _onChanged : function(m, f, of, type) { | ||||
|         if (!this._valid) | ||||
|             return; | ||||
|         let path = f.get_parent().get_path(); | ||||
|         let k = undefined; | ||||
|         for (let i = 0; i < this._paths.length; i++) { | ||||
|             if (this._paths[i] == path) | ||||
|                 k = i; | ||||
|         } | ||||
|         if (k === undefined) { | ||||
|             return; | ||||
|         } | ||||
|         if (type == Gio.FileMonitorEvent.CREATED) { | ||||
|             this._childs[k].push(f.get_basename()); | ||||
|         } | ||||
|         if (type == Gio.FileMonitorEvent.DELETED) { | ||||
|             this._changedCount++; | ||||
|             if (this._changedCount > MAX_FILE_DELETED_BEFORE_INVALID) { | ||||
|                 this._valid = false; | ||||
|             } | ||||
|             let name = f.get_basename(); | ||||
|             this._childs[k] = this._childs[k].filter(function(e) { | ||||
|                 return e != name; | ||||
|             }); | ||||
|         } | ||||
|         if (type == Gio.FileMonitorEvent.UNMOUNTED) { | ||||
|             this._childs[k] = []; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     getCompletion: function(text) { | ||||
|         let common = ''; | ||||
|         let notInit = true; | ||||
|         if (!this._valid) { | ||||
|             this._update(0); | ||||
|             return common; | ||||
|         } | ||||
|         function _getCommon(s1, s2) { | ||||
|             let k = 0; | ||||
|             for (; k < s1.length && k < s2.length; k++) { | ||||
|                 if (s1[k] != s2[k]) | ||||
|                     break; | ||||
|             } | ||||
|             if (k == 0) | ||||
|                 return ''; | ||||
|             return s1.substr(0, k); | ||||
|         } | ||||
|         function _hasPrefix(s1, prefix) { | ||||
|             return s1.indexOf(prefix) == 0; | ||||
|         } | ||||
|         for (let i = 0; i < this._childs.length; i++) { | ||||
|             for (let k = 0; k < this._childs[i].length; k++) { | ||||
|                 if (!_hasPrefix(this._childs[i][k], text)) | ||||
|                     continue; | ||||
|                 if (notInit) { | ||||
|                     common = this._childs[i][k]; | ||||
|                     notInit = false; | ||||
|                 } | ||||
|                 common = _getCommon(common, this._childs[i][k]); | ||||
|             } | ||||
|         } | ||||
|         if (common.length) | ||||
|             return common.substr(text.length); | ||||
|         return common; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const RunDialog = new Lang.Class({ | ||||
|     Name: 'RunDialog', | ||||
|     Extends: ModalDialog.ModalDialog, | ||||
|  | ||||
|     _init : function() { | ||||
|         this.parent({ styleClass: 'run-dialog', | ||||
|                       destroyOnClose: false }); | ||||
|         this.parent({ styleClass: 'run-dialog' }); | ||||
|  | ||||
|         this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA }); | ||||
|         this._terminalSettings = new Gio.Settings({ schema: TERMINAL_SCHEMA }); | ||||
| @@ -73,9 +204,7 @@ const RunDialog = new Lang.Class({ | ||||
|         let label = new St.Label({ style_class: 'run-dialog-label', | ||||
|                                    text: _("Enter a Command") }); | ||||
|  | ||||
|         this.contentLayout.add(label, { x_fill: false, | ||||
|                                         x_align: St.Align.START, | ||||
|                                         y_align: St.Align.START }); | ||||
|         this.contentLayout.add(label, { y_align: St.Align.START }); | ||||
|  | ||||
|         let entry = new St.Entry({ style_class: 'run-dialog-entry', | ||||
|                                    can_focus: true }); | ||||
| @@ -103,8 +232,6 @@ const RunDialog = new Lang.Class({ | ||||
|         this._errorMessage.clutter_text.line_wrap = true; | ||||
|  | ||||
|         this._errorBox.add(this._errorMessage, { expand: true, | ||||
|                                                  x_align: St.Align.START, | ||||
|                                                  x_fill: false, | ||||
|                                                  y_align: St.Align.MIDDLE, | ||||
|                                                  y_fill: false }); | ||||
|  | ||||
| @@ -115,6 +242,8 @@ const RunDialog = new Lang.Class({ | ||||
|                            key: Clutter.Escape }]); | ||||
|  | ||||
|         this._pathCompleter = new Gio.FilenameCompleter(); | ||||
|         this._commandCompleter = new CommandCompleter(); | ||||
|         this._group.connect('notify::visible', Lang.bind(this._commandCompleter, this._commandCompleter.update)); | ||||
|  | ||||
|         this._history = new History.HistoryManager({ gsettingsKey: HISTORY_KEY, | ||||
|                                                      entry: this._entryText }); | ||||
| @@ -128,7 +257,19 @@ const RunDialog = new Lang.Class({ | ||||
|                     !this.pushModal()) | ||||
|                     this.close(); | ||||
|  | ||||
|                 return Clutter.EVENT_STOP; | ||||
|                 return true; | ||||
|             } | ||||
|             if (symbol == Clutter.slash) { | ||||
|                 // Need preload data before get completion. GFilenameCompleter load content of parent directory. | ||||
|                 // Parent directory for /usr/include/ is /usr/. So need to add fake name('a'). | ||||
|                 let text = o.get_text().concat('/a'); | ||||
|                 let prefix; | ||||
|                 if (text.lastIndexOf(' ') == -1) | ||||
|                     prefix = text; | ||||
|                 else | ||||
|                     prefix = text.substr(text.lastIndexOf(' ') + 1); | ||||
|                 this._getCompletion(prefix); | ||||
|                 return false; | ||||
|             } | ||||
|             if (symbol == Clutter.Tab) { | ||||
|                 let text = o.get_text(); | ||||
| @@ -141,60 +282,20 @@ const RunDialog = new Lang.Class({ | ||||
|                 if (postfix != null && postfix.length > 0) { | ||||
|                     o.insert_text(postfix, -1); | ||||
|                     o.set_cursor_position(text.length + postfix.length); | ||||
|                     if (postfix[postfix.length - 1] == '/') | ||||
|                         this._getCompletion(text + postfix + 'a'); | ||||
|                 } | ||||
|                 return Clutter.EVENT_STOP; | ||||
|                 return true; | ||||
|             } | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _getCommandCompletion: function(text) { | ||||
|         function _getCommon(s1, s2) { | ||||
|             if (s1 == null) | ||||
|                 return s2; | ||||
|  | ||||
|             let k = 0; | ||||
|             for (; k < s1.length && k < s2.length; k++) { | ||||
|                 if (s1[k] != s2[k]) | ||||
|                     break; | ||||
|             } | ||||
|             if (k == 0) | ||||
|                 return ''; | ||||
|             return s1.substr(0, k); | ||||
|         } | ||||
|  | ||||
|         let paths = GLib.getenv('PATH').split(':'); | ||||
|         paths.push(GLib.get_home_dir()); | ||||
|         let someResults = paths.map(function(path) { | ||||
|             let results = []; | ||||
|             try { | ||||
|                 let file = Gio.File.new_for_path(path); | ||||
|                 let fileEnum = file.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NONE, null); | ||||
|                 let info; | ||||
|                 while ((info = fileEnum.next_file(null))) { | ||||
|                     let name = info.get_name(); | ||||
|                     if (name.slice(0, text.length) == text) | ||||
|                         results.push(name); | ||||
|                 } | ||||
|             } catch (e if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND) && | ||||
|                            !e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_DIRECTORY))) { | ||||
|                 log(e); | ||||
|             } finally { | ||||
|                 return results; | ||||
|             } | ||||
|         }); | ||||
|         let results = someResults.reduce(function(a, b) { | ||||
|             return a.concat(b); | ||||
|         }, []); | ||||
|         let common = results.reduce(_getCommon, null); | ||||
|         return common.substr(text.length); | ||||
|     }, | ||||
|  | ||||
|     _getCompletion : function(text) { | ||||
|         if (text.indexOf('/') != -1) { | ||||
|             return this._pathCompleter.get_completion_suffix(text); | ||||
|         } else { | ||||
|             return this._getCommandCompletion(text); | ||||
|             return this._commandCompleter.getCompletion(text); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -233,7 +334,7 @@ const RunDialog = new Lang.Class({ | ||||
|                     let file = Gio.file_new_for_path(path); | ||||
|                     try { | ||||
|                         Gio.app_info_launch_default_for_uri(file.get_uri(), | ||||
|                                                             global.create_app_launch_context(0, -1)); | ||||
|                                                             global.create_app_launch_context()); | ||||
|                     } catch (e) { | ||||
|                         // The exception from gjs contains an error string like: | ||||
|                         //     Error invoking Gio.app_info_launch_default_for_uri: No application | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,163 +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 Main = imports.ui.main; | ||||
|  | ||||
| const ScreencastIface = '<node> \ | ||||
| <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> \ | ||||
| </node>'; | ||||
|  | ||||
| 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 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; | ||||
|  | ||||
|         this._recorders.clear(); | ||||
|         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)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         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)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let sender = invocation.get_sender(); | ||||
|         let recorder = this._ensureRecorderForSender(sender); | ||||
|  | ||||
|         if (!recorder.is_recording()) { | ||||
|             let [x, y, width, height, fileTemplate, options] = params; | ||||
|  | ||||
|             if (x < 0 || y < 0 || | ||||
|                 width <= 0 || height <= 0 || | ||||
|                 x + width > global.screen_width || | ||||
|                 y + height > global.screen_height) { | ||||
|                 invocation.return_error_literal(Gio.IOErrorEnum, | ||||
|                                                 Gio.IOErrorEnum.CANCELLED, | ||||
|                                                 "Invalid params"); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             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); | ||||
| @@ -6,7 +6,6 @@ const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| @@ -15,47 +14,45 @@ const Lightbox = imports.ui.lightbox; | ||||
| const Main = imports.ui.main; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const ScreenshotIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.Screenshot"> \ | ||||
| <method name="ScreenshotArea"> \ | ||||
|     <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="b" direction="in" name="flash"/> \ | ||||
|     <arg type="s" direction="in" name="filename"/> \ | ||||
|     <arg type="b" direction="out" name="success"/> \ | ||||
|     <arg type="s" direction="out" name="filename_used"/> \ | ||||
| </method> \ | ||||
| <method name="ScreenshotWindow"> \ | ||||
|     <arg type="b" direction="in" name="include_frame"/> \ | ||||
|     <arg type="b" direction="in" name="include_cursor"/> \ | ||||
|     <arg type="b" direction="in" name="flash"/> \ | ||||
|     <arg type="s" direction="in" name="filename"/> \ | ||||
|     <arg type="b" direction="out" name="success"/> \ | ||||
|     <arg type="s" direction="out" name="filename_used"/> \ | ||||
| </method> \ | ||||
| <method name="Screenshot"> \ | ||||
|     <arg type="b" direction="in" name="include_cursor"/> \ | ||||
|     <arg type="b" direction="in" name="flash"/> \ | ||||
|     <arg type="s" direction="in" name="filename"/> \ | ||||
|     <arg type="b" direction="out" name="success"/> \ | ||||
|     <arg type="s" direction="out" name="filename_used"/> \ | ||||
| </method> \ | ||||
| <method name="SelectArea"> \ | ||||
|     <arg type="i" direction="out" name="x"/> \ | ||||
|     <arg type="i" direction="out" name="y"/> \ | ||||
|     <arg type="i" direction="out" name="width"/> \ | ||||
|     <arg type="i" direction="out" name="height"/> \ | ||||
| </method> \ | ||||
| <method name="FlashArea"> \ | ||||
|     <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"/> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ScreenshotIface = <interface name="org.gnome.Shell.Screenshot"> | ||||
| <method name="ScreenshotArea"> | ||||
|     <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="b" direction="in" name="flash"/> | ||||
|     <arg type="s" direction="in" name="filename"/> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
|     <arg type="s" direction="out" name="filename_used"/> | ||||
| </method> | ||||
| <method name="ScreenshotWindow"> | ||||
|     <arg type="b" direction="in" name="include_frame"/> | ||||
|     <arg type="b" direction="in" name="include_cursor"/> | ||||
|     <arg type="b" direction="in" name="flash"/> | ||||
|     <arg type="s" direction="in" name="filename"/> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
|     <arg type="s" direction="out" name="filename_used"/> | ||||
| </method> | ||||
| <method name="Screenshot"> | ||||
|     <arg type="b" direction="in" name="include_cursor"/> | ||||
|     <arg type="b" direction="in" name="flash"/> | ||||
|     <arg type="s" direction="in" name="filename"/> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
|     <arg type="s" direction="out" name="filename_used"/> | ||||
| </method> | ||||
| <method name="SelectArea"> | ||||
|     <arg type="i" direction="out" name="x"/> | ||||
|     <arg type="i" direction="out" name="y"/> | ||||
|     <arg type="i" direction="out" name="width"/> | ||||
|     <arg type="i" direction="out" name="height"/> | ||||
| </method> | ||||
| <method name="FlashArea"> | ||||
|     <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"/> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const ScreenshotService = new Lang.Class({ | ||||
|     Name: 'ScreenshotService', | ||||
| @@ -79,9 +76,7 @@ const ScreenshotService = new Lang.Class({ | ||||
|  | ||||
|     ScreenshotAreaAsync : function (params, invocation) { | ||||
|         let [x, y, width, height, flash, filename, callback] = params; | ||||
|         if (x < 0 || y < 0 || | ||||
|             width <= 0 || height <= 0 || | ||||
|             x + width > global.screen_width || y + height > global.screen_height) { | ||||
|         if (height <= 0 || width <= 0) { | ||||
|             invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED, | ||||
|                         "Invalid params"); | ||||
|             return; | ||||
| @@ -172,7 +167,7 @@ const SelectArea = new Lang.Class({ | ||||
|         if (!Main.pushModal(this._group) || this._group.visible) | ||||
|             return; | ||||
|  | ||||
|         global.screen.set_cursor(Meta.Cursor.CROSSHAIR); | ||||
|         global.set_cursor(Shell.Cursor.CROSSHAIR); | ||||
|         this._group.visible = true; | ||||
|     }, | ||||
|  | ||||
| @@ -206,12 +201,12 @@ const SelectArea = new Lang.Class({ | ||||
|         if (event.get_key_symbol() == Clutter.Escape) | ||||
|             this._destroy(null, false); | ||||
|  | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return; | ||||
|     }, | ||||
|  | ||||
|     _onMotionEvent: function(actor, event) { | ||||
|         if (this._startX == -1 || this._startY == -1) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         [this._lastX, this._lastY] = event.get_coords(); | ||||
|         let geometry = this._getGeometry(); | ||||
| @@ -219,19 +214,19 @@ const SelectArea = new Lang.Class({ | ||||
|         this._rubberband.set_position(geometry.x, geometry.y); | ||||
|         this._rubberband.set_size(geometry.width, geometry.height); | ||||
|  | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onButtonPress: function(actor, event) { | ||||
|         [this._startX, this._startY] = event.get_coords(); | ||||
|         this._rubberband.set_position(this._startX, this._startY); | ||||
|  | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onButtonRelease: function(actor, event) { | ||||
|         this._destroy(this._getGeometry(), true); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _destroy: function(geometry, fade) { | ||||
| @@ -243,7 +238,7 @@ const SelectArea = new Lang.Class({ | ||||
|                                function() { | ||||
|                                    Main.popModal(this._group); | ||||
|                                    this._group.destroy(); | ||||
|                                    global.screen.set_cursor(Meta.Cursor.DEFAULT); | ||||
|                                    global.unset_cursor(); | ||||
|  | ||||
|                                    this.emit('finished', geometry); | ||||
|                                }) | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| @@ -42,7 +41,7 @@ function sleep(milliseconds) { | ||||
|     Mainloop.timeout_add(milliseconds, function() { | ||||
|                              if (cb) | ||||
|                                  cb(); | ||||
|                              return GLib.SOURCE_REMOVE; | ||||
|                              return false; | ||||
|                          }); | ||||
|  | ||||
|     return function(callback) { | ||||
| @@ -70,18 +69,16 @@ function waitLeisure() { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| const PerfHelperIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.PerfHelper"> \ | ||||
| <method name="CreateWindow"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="WaitWindows" /> \ | ||||
| <method name="DestroyWindows" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const PerfHelperIface = <interface name="org.gnome.Shell.PerfHelper"> | ||||
| <method name="CreateWindow"> | ||||
|     <arg type="i" direction="in" /> | ||||
|     <arg type="i" direction="in" /> | ||||
|     <arg type="b" direction="in" /> | ||||
|     <arg type="b" direction="in" /> | ||||
| </method> | ||||
| <method name="WaitWindows" /> | ||||
| <method name="DestroyWindows" /> | ||||
| </interface>; | ||||
|  | ||||
| var PerfHelperProxy = Gio.DBusProxy.makeProxyWrapper(PerfHelperIface); | ||||
| function PerfHelper() { | ||||
|   | ||||
							
								
								
									
										717
									
								
								js/ui/search.js
									
									
									
									
									
								
							
							
						
						
									
										717
									
								
								js/ui/search.js
									
									
									
									
									
								
							| @@ -1,706 +1,105 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Gio = imports.gi.Gio; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
| const AppDisplay = imports.ui.appDisplay; | ||||
| const DND = imports.ui.dnd; | ||||
| const IconGrid = imports.ui.iconGrid; | ||||
| const Main = imports.ui.main; | ||||
| const Overview = imports.ui.overview; | ||||
| const RemoteSearch = imports.ui.remoteSearch; | ||||
| const Separator = imports.ui.separator; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers'; | ||||
|  | ||||
| const MAX_LIST_SEARCH_RESULTS_ROWS = 3; | ||||
| const MAX_GRID_SEARCH_RESULTS_ROWS = 1; | ||||
|  | ||||
| const SearchSystem = new Lang.Class({ | ||||
|     Name: 'SearchSystem', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._providers = []; | ||||
|  | ||||
|         this._registerProvider(new AppDisplay.AppSearchProvider()); | ||||
|  | ||||
|         this._searchSettings = new Gio.Settings({ schema: SEARCH_PROVIDERS_SCHEMA }); | ||||
|         this._searchSettings.connect('changed::disabled', Lang.bind(this, this._reloadRemoteProviders)); | ||||
|         this._searchSettings.connect('changed::disable-external', Lang.bind(this, this._reloadRemoteProviders)); | ||||
|         this._searchSettings.connect('changed::sort-order', Lang.bind(this, this._reloadRemoteProviders)); | ||||
|  | ||||
|         this._reloadRemoteProviders(); | ||||
|  | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|         this._remoteProviders = []; | ||||
|         this.reset(); | ||||
|     }, | ||||
|  | ||||
|     addProvider: function(provider) { | ||||
|     registerProvider: function (provider) { | ||||
|         provider.searchSystem = this; | ||||
|         this._providers.push(provider); | ||||
|         this.emit('providers-changed'); | ||||
|  | ||||
|         if (provider.isRemoteProvider) | ||||
|             this._remoteProviders.push(provider); | ||||
|     }, | ||||
|  | ||||
|     _reloadRemoteProviders: function() { | ||||
|         let remoteProviders = this._providers.filter(function(provider) { | ||||
|             return provider.isRemoteProvider; | ||||
|         }); | ||||
|         remoteProviders.forEach(Lang.bind(this, function(provider) { | ||||
|             this._unregisterProvider(provider); | ||||
|         })); | ||||
|  | ||||
|         RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, function(providers) { | ||||
|             providers.forEach(Lang.bind(this, this._registerProvider)); | ||||
|         })); | ||||
|  | ||||
|         this.emit('providers-changed'); | ||||
|     }, | ||||
|  | ||||
|     _registerProvider: function (provider) { | ||||
|         this._providers.push(provider); | ||||
|     }, | ||||
|  | ||||
|     _unregisterProvider: function (provider) { | ||||
|     unregisterProvider: function (provider) { | ||||
|         let index = this._providers.indexOf(provider); | ||||
|         if (index == -1) | ||||
|             return; | ||||
|         provider.searchSystem = null; | ||||
|         this._providers.splice(index, 1); | ||||
|  | ||||
|         let remoteIndex = this._remoteProviders.indexOf(provider); | ||||
|         if (remoteIndex != -1) | ||||
|             this._remoteProviders.splice(index, 1); | ||||
|     }, | ||||
|  | ||||
|     getProviders: function() { | ||||
|         return this._providers; | ||||
|     }, | ||||
|  | ||||
|     getRemoteProviders: function() { | ||||
|         return this._remoteProviders; | ||||
|     }, | ||||
|  | ||||
|     getTerms: function() { | ||||
|         return this._terms; | ||||
|         return this._previousTerms; | ||||
|     }, | ||||
|  | ||||
|     reset: function() { | ||||
|         this._terms = []; | ||||
|         this._results = {}; | ||||
|         this._previousTerms = []; | ||||
|         this._previousResults = []; | ||||
|     }, | ||||
|  | ||||
|     _gotResults: function(results, provider) { | ||||
|         this._results[provider.id] = results; | ||||
|         this.emit('search-updated', provider, results); | ||||
|     pushResults: function(provider, results) { | ||||
|         let i = this._providers.indexOf(provider); | ||||
|         if (i == -1) | ||||
|             return; | ||||
|  | ||||
|         this._previousResults[i] = [provider, results]; | ||||
|         this.emit('search-updated', this._previousResults[i]); | ||||
|     }, | ||||
|  | ||||
|     setTerms: function(terms) { | ||||
|         this._cancellable.cancel(); | ||||
|         this._cancellable.reset(); | ||||
|  | ||||
|         let previousResults = this._results; | ||||
|         let previousTerms = this._terms; | ||||
|         this.reset(); | ||||
|  | ||||
|     updateSearchResults: function(terms) { | ||||
|         if (!terms) | ||||
|             return; | ||||
|  | ||||
|         let searchString = terms.join(' '); | ||||
|         let previousSearchString = previousTerms.join(' '); | ||||
|         let previousSearchString = this._previousTerms.join(' '); | ||||
|         if (searchString == previousSearchString) | ||||
|             return; | ||||
|  | ||||
|         let isSubSearch = false; | ||||
|         if (previousTerms.length > 0) | ||||
|         if (this._previousTerms.length > 0) | ||||
|             isSubSearch = searchString.indexOf(previousSearchString) == 0; | ||||
|  | ||||
|         this._terms = terms; | ||||
|         let previousResultsArr = this._previousResults; | ||||
|  | ||||
|         this._providers.forEach(Lang.bind(this, function(provider) { | ||||
|             let previousProviderResults = previousResults[provider.id]; | ||||
|             if (isSubSearch && previousProviderResults) | ||||
|                 provider.getSubsearchResultSet(previousProviderResults, terms, Lang.bind(this, this._gotResults, provider), this._cancellable); | ||||
|             else | ||||
|                 provider.getInitialResultSet(terms, Lang.bind(this, this._gotResults, provider), this._cancellable); | ||||
|         })); | ||||
|         let results = []; | ||||
|         this._previousTerms = terms; | ||||
|         this._previousResults = results; | ||||
|  | ||||
|         if (isSubSearch) { | ||||
|             for (let i = 0; i < this._providers.length; i++) { | ||||
|                 let [provider, previousResults] = previousResultsArr[i]; | ||||
|                 try { | ||||
|                     results.push([provider, []]); | ||||
|                     provider.getSubsearchResultSet(previousResults, terms); | ||||
|                 } catch (error) { | ||||
|                     log('A ' + error.name + ' has occured in ' + provider.id + ': ' + error.message); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             for (let i = 0; i < this._providers.length; i++) { | ||||
|                 let provider = this._providers[i]; | ||||
|                 try { | ||||
|                     results.push([provider, []]); | ||||
|                     provider.getInitialResultSet(terms); | ||||
|                 } catch (error) { | ||||
|                     log('A ' + error.name + ' has occured in ' + provider.id + ': ' + error.message); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(SearchSystem.prototype); | ||||
|  | ||||
| const MaxWidthBin = new Lang.Class({ | ||||
|     Name: 'MaxWidthBin', | ||||
|     Extends: St.Bin, | ||||
|  | ||||
|     vfunc_allocate: function(box, flags) { | ||||
|         let themeNode = this.get_theme_node(); | ||||
|         let maxWidth = themeNode.get_max_width(); | ||||
|         let availWidth = box.x2 - box.x1; | ||||
|         let adjustedBox = box; | ||||
|  | ||||
|         if (availWidth > maxWidth) { | ||||
|             let excessWidth = availWidth - maxWidth; | ||||
|             adjustedBox.x1 += Math.floor(excessWidth / 2); | ||||
|             adjustedBox.x2 -= Math.floor(excessWidth / 2); | ||||
|         } | ||||
|  | ||||
|         this.parent(adjustedBox, flags); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SearchResult = new Lang.Class({ | ||||
|     Name: 'SearchResult', | ||||
|  | ||||
|     _init: function(provider, metaInfo) { | ||||
|         this.provider = provider; | ||||
|         this.metaInfo = metaInfo; | ||||
|  | ||||
|         this.actor = new St.Button({ reactive: true, | ||||
|                                      can_focus: true, | ||||
|                                      track_hover: true, | ||||
|                                      x_align: St.Align.START, | ||||
|                                      y_fill: true }); | ||||
|  | ||||
|         this.actor._delegate = this; | ||||
|         this.actor.connect('clicked', Lang.bind(this, this.activate)); | ||||
|     }, | ||||
|  | ||||
|     activate: function() { | ||||
|         this.emit('activate', this.metaInfo.id); | ||||
|     }, | ||||
|  | ||||
|     setSelected: function(selected) { | ||||
|         if (selected) | ||||
|             this.actor.add_style_pseudo_class('selected'); | ||||
|         else | ||||
|             this.actor.remove_style_pseudo_class('selected'); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(SearchResult.prototype); | ||||
|  | ||||
| const ListSearchResult = new Lang.Class({ | ||||
|     Name: 'ListSearchResult', | ||||
|     Extends: SearchResult, | ||||
|  | ||||
|     ICON_SIZE: 64, | ||||
|  | ||||
|     _init: function(provider, metaInfo) { | ||||
|         this.parent(provider, metaInfo); | ||||
|  | ||||
|         this.actor.style_class = 'list-search-result'; | ||||
|         this.actor.x_fill = true; | ||||
|  | ||||
|         let content = new St.BoxLayout({ style_class: 'list-search-result-content', | ||||
|                                          vertical: false }); | ||||
|         this.actor.set_child(content); | ||||
|  | ||||
|         // An icon for, or thumbnail of, content | ||||
|         let icon = this.metaInfo['createIcon'](this.ICON_SIZE); | ||||
|         if (icon) { | ||||
|             content.add(icon); | ||||
|         } | ||||
|  | ||||
|         let details = new St.BoxLayout({ vertical: true }); | ||||
|         content.add(details, { x_fill: true, | ||||
|                                y_fill: false, | ||||
|                                x_align: St.Align.START, | ||||
|                                y_align: St.Align.MIDDLE }); | ||||
|  | ||||
|         let title = new St.Label({ style_class: 'list-search-result-title', | ||||
|                                    text: this.metaInfo['name'] }) | ||||
|         details.add(title, { x_fill: false, | ||||
|                              y_fill: false, | ||||
|                              x_align: St.Align.START, | ||||
|                              y_align: St.Align.START }); | ||||
|         this.actor.label_actor = title; | ||||
|  | ||||
|         if (this.metaInfo['description']) { | ||||
|             let description = new St.Label({ style_class: 'list-search-result-description' }); | ||||
|             description.clutter_text.set_markup(this.metaInfo['description']); | ||||
|             details.add(description, { x_fill: false, | ||||
|                                        y_fill: false, | ||||
|                                        x_align: St.Align.START, | ||||
|                                        y_align: St.Align.END }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const GridSearchResult = new Lang.Class({ | ||||
|     Name: 'GridSearchResult', | ||||
|     Extends: SearchResult, | ||||
|  | ||||
|     _init: function(provider, metaInfo) { | ||||
|         this.parent(provider, metaInfo); | ||||
|  | ||||
|         this.actor.style_class = 'grid-search-result'; | ||||
|  | ||||
|         let content = provider.createResultObject(metaInfo); | ||||
|         let dragSource = null; | ||||
|  | ||||
|         if (content == null) { | ||||
|             let actor = new St.Bin(); | ||||
|             let icon = new IconGrid.BaseIcon(this.metaInfo['name'], | ||||
|                                              { createIcon: this.metaInfo['createIcon'] }); | ||||
|             actor.set_child(icon.actor); | ||||
|             actor.label_actor = icon.label; | ||||
|             dragSource = icon.icon; | ||||
|             content = { actor: actor, icon: icon }; | ||||
|         } else { | ||||
|             if (content._delegate && content._delegate.getDragActorSource) | ||||
|                 dragSource = content._delegate.getDragActorSource(); | ||||
|         } | ||||
|  | ||||
|         this.actor.set_child(content.actor); | ||||
|         this.actor.label_actor = content.actor.label_actor; | ||||
|         this.icon = content.icon; | ||||
|  | ||||
|         let draggable = DND.makeDraggable(this.actor); | ||||
|         draggable.connect('drag-begin', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.beginItemDrag(this); | ||||
|                           })); | ||||
|         draggable.connect('drag-cancelled', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.cancelledItemDrag(this); | ||||
|                           })); | ||||
|         draggable.connect('drag-end', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.endItemDrag(this); | ||||
|                           })); | ||||
|  | ||||
|         if (!dragSource) | ||||
|             // not exactly right, but alignment problems are hard to notice | ||||
|             dragSource = content; | ||||
|         this._dragActorSource = dragSource; | ||||
|     }, | ||||
|  | ||||
|     getDragActorSource: function() { | ||||
|         return this._dragActorSource; | ||||
|     }, | ||||
|  | ||||
|     getDragActor: function() { | ||||
|         return this.metaInfo['createIcon'](Main.overview.dashIconSize); | ||||
|     }, | ||||
|  | ||||
|     shellWorkspaceLaunch: function(params) { | ||||
|         if (this.provider.dragActivateResult) | ||||
|             this.provider.dragActivateResult(this.metaInfo.id, params); | ||||
|         else | ||||
|             this.provider.activateResult(this.metaInfo.id, this.terms); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SearchResultsBase = new Lang.Class({ | ||||
|     Name: 'SearchResultsBase', | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.provider = provider; | ||||
|  | ||||
|         this._terms = []; | ||||
|  | ||||
|         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); | ||||
|  | ||||
|         this._resultDisplays = {}; | ||||
|  | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this.actor.destroy(); | ||||
|         this._terms = []; | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function() { | ||||
|     }, | ||||
|  | ||||
|     clear: function() { | ||||
|         this._resultDisplays = {}; | ||||
|         this._clearResultDisplay(); | ||||
|         this.actor.hide(); | ||||
|     }, | ||||
|  | ||||
|     _keyFocusIn: function(actor) { | ||||
|         this.emit('key-focus-in', actor); | ||||
|     }, | ||||
|  | ||||
|     _activateResult: function(result, id) { | ||||
|         this.provider.activateResult(id, this._terms); | ||||
|         Main.overview.toggle(); | ||||
|     }, | ||||
|  | ||||
|     _setMoreIconVisible: function(visible) { | ||||
|     }, | ||||
|  | ||||
|     _ensureResultActors: function(results, callback) { | ||||
|         let metasNeeded = results.filter(Lang.bind(this, function(resultId) { | ||||
|             return this._resultDisplays[resultId] === undefined; | ||||
|         })); | ||||
|  | ||||
|         if (metasNeeded.length === 0) { | ||||
|             callback(); | ||||
|         } else { | ||||
|             this._cancellable.cancel(); | ||||
|             this._cancellable.reset(); | ||||
|  | ||||
|             this.provider.getResultMetas(metasNeeded, Lang.bind(this, function(metas) { | ||||
|                 metasNeeded.forEach(Lang.bind(this, function(resultId, i) { | ||||
|                     let meta = metas[i]; | ||||
|                     let display = this._createResultDisplay(meta); | ||||
|                     display.connect('activate', Lang.bind(this, this._activateResult)); | ||||
|                     display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|                     this._resultDisplays[resultId] = display; | ||||
|                 })); | ||||
|                 callback(); | ||||
|             }), this._cancellable); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     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._ensureResultActors(results, Lang.bind(this, function() { | ||||
|                 this._clearResultDisplay(); | ||||
|  | ||||
|                 // 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(); | ||||
|                 results.forEach(Lang.bind(this, function(resultId) { | ||||
|                     this._addItem(this._resultDisplays[resultId]); | ||||
|                 })); | ||||
|                 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.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|         this.providerIcon.connect('clicked', Lang.bind(this, | ||||
|             function() { | ||||
|                 provider.launchSearch(this._terms); | ||||
|                 Main.overview.toggle(); | ||||
|             })); | ||||
|  | ||||
|         this._container.add(this.providerIcon, { x_fill: false, | ||||
|                                                  y_fill: false, | ||||
|                                                  x_align: St.Align.START, | ||||
|                                                  y_align: St.Align.START }); | ||||
|  | ||||
|         this._content = new St.BoxLayout({ style_class: 'list-search-results', | ||||
|                                            vertical: true }); | ||||
|         this._container.add(this._content, { expand: true }); | ||||
|  | ||||
|         this._resultDisplayBin.set_child(this._container); | ||||
|     }, | ||||
|  | ||||
|     _setMoreIconVisible: function(visible) { | ||||
|         this.providerIcon.moreIcon.visible = true; | ||||
|     }, | ||||
|  | ||||
|     _getMaxDisplayedResults: function() { | ||||
|         return MAX_LIST_SEARCH_RESULTS_ROWS; | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function () { | ||||
|         this._content.remove_all_children(); | ||||
|     }, | ||||
|  | ||||
|     _createResultDisplay: function(meta) { | ||||
|         return new ListSearchResult(this.provider, meta); | ||||
|     }, | ||||
|  | ||||
|     _addItem: function(display) { | ||||
|         this._content.add_actor(display.actor); | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this._content.get_n_children() > 0) | ||||
|             return this._content.get_child_at_index(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ListSearchResults.prototype); | ||||
|  | ||||
| const GridSearchResults = new Lang.Class({ | ||||
|     Name: 'GridSearchResults', | ||||
|     Extends: SearchResultsBase, | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.parent(provider); | ||||
|  | ||||
|         this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS, | ||||
|                                              xAlign: St.Align.START }); | ||||
|         this._bin = new St.Bin({ x_align: St.Align.MIDDLE }); | ||||
|         this._bin.set_child(this._grid.actor); | ||||
|  | ||||
|         this._resultDisplayBin.set_child(this._bin); | ||||
|     }, | ||||
|  | ||||
|     _getMaxDisplayedResults: function() { | ||||
|         return this._grid.columnsForWidth(this._bin.width) * this._grid.getRowLimit(); | ||||
|     }, | ||||
|  | ||||
|     _renderResults: function(metas) { | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             let display = new GridSearchResult(this.provider, metas[i]); | ||||
|             display.connect('activate', Lang.bind(this, this._activateResult)); | ||||
|             display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|             this._grid.addItem(display); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function () { | ||||
|         this._grid.removeAll(); | ||||
|     }, | ||||
|  | ||||
|     _createResultDisplay: function(meta) { | ||||
|         return new GridSearchResult(this.provider, meta); | ||||
|     }, | ||||
|  | ||||
|     _addItem: function(display) { | ||||
|         this._grid.addItem(display); | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this._grid.visibleItemsCount() > 0) | ||||
|             return this._grid.getItemAtIndex(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(GridSearchResults.prototype); | ||||
|  | ||||
| const SearchResults = new Lang.Class({ | ||||
|     Name: 'SearchResults', | ||||
|  | ||||
|     _init: function() { | ||||
|         this.actor = new St.BoxLayout({ name: 'searchResults', | ||||
|                                         vertical: true }); | ||||
|  | ||||
|         this._content = new St.BoxLayout({ name: 'searchResultsContent', | ||||
|                                            vertical: true }); | ||||
|         this._contentBin = new MaxWidthBin({ name: 'searchResultsBin', | ||||
|                                              x_fill: true, | ||||
|                                              y_fill: true, | ||||
|                                              child: this._content }); | ||||
|  | ||||
|         let scrollChild = new St.BoxLayout(); | ||||
|         scrollChild.add(this._contentBin, { expand: true }); | ||||
|  | ||||
|         this._scrollView = new St.ScrollView({ x_fill: true, | ||||
|                                                y_fill: false, | ||||
|                                                overlay_scrollbars: true, | ||||
|                                                style_class: 'search-display vfade' }); | ||||
|         this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); | ||||
|         this._scrollView.add_actor(scrollChild); | ||||
|         let action = new Clutter.PanAction({ interpolate: true }); | ||||
|         action.connect('pan', Lang.bind(this, this._onPan)); | ||||
|         this._scrollView.add_action(action); | ||||
|  | ||||
|         this.actor.add(this._scrollView, { x_fill: true, | ||||
|                                            y_fill: true, | ||||
|                                            expand: true, | ||||
|                                            x_align: St.Align.START, | ||||
|                                            y_align: St.Align.START }); | ||||
|  | ||||
|         this._statusText = new St.Label({ style_class: 'search-statustext' }); | ||||
|         this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE, | ||||
|                                        y_align: St.Align.MIDDLE }); | ||||
|         this._content.add(this._statusBin, { expand: true }); | ||||
|         this._statusBin.add_actor(this._statusText); | ||||
|  | ||||
|         this._highlightDefault = false; | ||||
|         this._defaultResult = null; | ||||
|  | ||||
|         this._searchSystem = new SearchSystem(); | ||||
|         this._searchSystem.connect('search-updated', Lang.bind(this, this._updateResults)); | ||||
|         this._searchSystem.connect('providers-changed', Lang.bind(this, this._updateProviderDisplays)); | ||||
|         this._updateProviderDisplays(); | ||||
|     }, | ||||
|  | ||||
|     _onPan: function(action) { | ||||
|         let [dist, dx, dy] = action.get_motion_delta(0); | ||||
|         let adjustment = this._scrollView.vscroll.adjustment; | ||||
|         adjustment.value -= (dy / this.actor.height) * adjustment.page_size; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _keyFocusIn: function(provider, actor) { | ||||
|         Util.ensureActorVisibleInScrollView(this._scrollView, actor); | ||||
|     }, | ||||
|  | ||||
|     _ensureProviderDisplay: function(provider) { | ||||
|         if (provider.display) | ||||
|             return; | ||||
|  | ||||
|         let providerDisplay; | ||||
|         if (provider.appInfo) | ||||
|             providerDisplay = new ListSearchResults(provider); | ||||
|         else | ||||
|             providerDisplay = new GridSearchResults(provider); | ||||
|  | ||||
|         providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|         this._content.add(providerDisplay.actor); | ||||
|         provider.display = providerDisplay; | ||||
|     }, | ||||
|  | ||||
|     _updateProviderDisplays: function() { | ||||
|         this._searchSystem.getProviders().forEach(Lang.bind(this, this._ensureProviderDisplay)); | ||||
|     }, | ||||
|  | ||||
|     _clearDisplay: function() { | ||||
|         this._searchSystem.getProviders().forEach(function(provider) { | ||||
|             provider.display.clear(); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     reset: function() { | ||||
|         this._searchSystem.reset(); | ||||
|         this._statusBin.hide(); | ||||
|         this._clearDisplay(); | ||||
|         this._defaultResult = null; | ||||
|     }, | ||||
|  | ||||
|     startingSearch: function() { | ||||
|         this.reset(); | ||||
|         this._statusText.set_text(_("Searching…")); | ||||
|         this._statusBin.show(); | ||||
|     }, | ||||
|  | ||||
|     setTerms: function(terms) { | ||||
|         this._searchSystem.setTerms(terms); | ||||
|     }, | ||||
|  | ||||
|     _maybeSetInitialSelection: function() { | ||||
|         let newDefaultResult = null; | ||||
|  | ||||
|         let providers = this._searchSystem.getProviders(); | ||||
|         for (let i = 0; i < providers.length; i++) { | ||||
|             let provider = providers[i]; | ||||
|             let display = provider.display; | ||||
|  | ||||
|             if (!display.actor.visible) | ||||
|                 continue; | ||||
|  | ||||
|             let firstResult = display.getFirstResult(); | ||||
|             if (firstResult) { | ||||
|                 newDefaultResult = firstResult; | ||||
|                 break; // select this one! | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (newDefaultResult != this._defaultResult) { | ||||
|             if (this._defaultResult) | ||||
|                 this._defaultResult.setSelected(false); | ||||
|             if (newDefaultResult) | ||||
|                 newDefaultResult.setSelected(this._highlightDefault); | ||||
|  | ||||
|             this._defaultResult = newDefaultResult; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateStatusText: function () { | ||||
|         let haveResults = this._searchSystem.getProviders().some(function(provider) { | ||||
|             let display = provider.display; | ||||
|             return (display.getFirstResult() != null); | ||||
|         }); | ||||
|  | ||||
|         if (!haveResults) { | ||||
|             this._statusText.set_text(_("No results.")); | ||||
|             this._statusBin.show(); | ||||
|         } else { | ||||
|             this._statusBin.hide(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateResults: function(searchSystem, provider, results) { | ||||
|         let terms = searchSystem.getTerms(); | ||||
|         let display = provider.display; | ||||
|  | ||||
|         display.updateSearch(results, terms, Lang.bind(this, function() { | ||||
|             this._maybeSetInitialSelection(); | ||||
|             this._updateStatusText(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     activateDefault: function() { | ||||
|         if (this._defaultResult) | ||||
|             this._defaultResult.activate(); | ||||
|     }, | ||||
|  | ||||
|     highlightDefault: function(highlight) { | ||||
|         this._highlightDefault = highlight; | ||||
|         if (this._defaultResult) | ||||
|             this._defaultResult.setSelected(highlight); | ||||
|     }, | ||||
|  | ||||
|     navigateFocus: function(direction) { | ||||
|         let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|         if (direction == Gtk.DirectionType.TAB_BACKWARD || | ||||
|             direction == (rtl ? Gtk.DirectionType.RIGHT | ||||
|                               : Gtk.DirectionType.LEFT) || | ||||
|             direction == Gtk.DirectionType.UP) { | ||||
|             this.actor.navigate_focus(null, direction, false); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let from = this._defaultResult ? this._defaultResult.actor : null; | ||||
|         this.actor.navigate_focus(from, direction, false); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ProviderIcon = new Lang.Class({ | ||||
|     Name: 'ProviderIcon', | ||||
|     Extends: St.Button, | ||||
|  | ||||
|     PROVIDER_ICON_SIZE: 48, | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.provider = provider; | ||||
|         this.parent({ style_class: 'search-provider-icon', | ||||
|                       reactive: true, | ||||
|                       can_focus: true, | ||||
|                       accessible_name: provider.appInfo.get_name(), | ||||
|                       track_hover: true }); | ||||
|  | ||||
|         this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() }); | ||||
|         this.set_child(this._content); | ||||
|  | ||||
|         let rtl = (this.get_text_direction() == Clutter.TextDirection.RTL); | ||||
|  | ||||
|         this.moreIcon = new St.Widget({ style_class: 'search-provider-icon-more', | ||||
|                                         visible: false, | ||||
|                                         x_align: rtl ? Clutter.ActorAlign.START : Clutter.ActorAlign.END, | ||||
|                                         y_align: Clutter.ActorAlign.END, | ||||
|                                         x_expand: true, | ||||
|                                         y_expand: true }); | ||||
|  | ||||
|         let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE, | ||||
|                                  gicon: provider.appInfo.get_icon() }); | ||||
|         this._content.add_actor(icon); | ||||
|         this._content.add_actor(this.moreIcon); | ||||
|     } | ||||
| }); | ||||
|   | ||||
							
								
								
									
										579
									
								
								js/ui/searchDisplay.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										579
									
								
								js/ui/searchDisplay.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,579 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const St = imports.gi.St; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
| const DND = imports.ui.dnd; | ||||
| const IconGrid = imports.ui.iconGrid; | ||||
| const Main = imports.ui.main; | ||||
| const Overview = imports.ui.overview; | ||||
| const Separator = imports.ui.separator; | ||||
| const Search = imports.ui.search; | ||||
|  | ||||
| const MAX_LIST_SEARCH_RESULTS_ROWS = 3; | ||||
| const MAX_GRID_SEARCH_RESULTS_ROWS = 1; | ||||
|  | ||||
| const MaxWidthBin = new Lang.Class({ | ||||
|     Name: 'MaxWidthBin', | ||||
|     Extends: St.Bin, | ||||
|  | ||||
|     vfunc_allocate: function(box, flags) { | ||||
|         let themeNode = this.get_theme_node(); | ||||
|         let maxWidth = themeNode.get_max_width(); | ||||
|         let availWidth = box.x2 - box.x1; | ||||
|         let adjustedBox = box; | ||||
|  | ||||
|         if (availWidth > maxWidth) { | ||||
|             let excessWidth = availWidth - maxWidth; | ||||
|             adjustedBox.x1 += Math.floor(excessWidth / 2); | ||||
|             adjustedBox.x2 -= Math.floor(excessWidth / 2); | ||||
|         } | ||||
|  | ||||
|         this.parent(adjustedBox, flags); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SearchResult = new Lang.Class({ | ||||
|     Name: 'SearchResult', | ||||
|  | ||||
|     _init: function(provider, metaInfo, terms) { | ||||
|         this.provider = provider; | ||||
|         this.metaInfo = metaInfo; | ||||
|         this.terms = terms; | ||||
|  | ||||
|         this.actor = new St.Button({ reactive: true, | ||||
|                                      can_focus: true, | ||||
|                                      track_hover: true, | ||||
|                                      x_align: St.Align.START, | ||||
|                                      y_fill: true }); | ||||
|  | ||||
|         this.actor._delegate = this; | ||||
|         this.actor.connect('clicked', Lang.bind(this, this.activate)); | ||||
|     }, | ||||
|  | ||||
|     activate: function() { | ||||
|         this.provider.activateResult(this.metaInfo.id, this.terms); | ||||
|         Main.overview.toggle(); | ||||
|     }, | ||||
|  | ||||
|     setSelected: function(selected) { | ||||
|         if (selected) | ||||
|             this.actor.add_style_pseudo_class('selected'); | ||||
|         else | ||||
|             this.actor.remove_style_pseudo_class('selected'); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ListSearchResult = new Lang.Class({ | ||||
|     Name: 'ListSearchResult', | ||||
|     Extends: SearchResult, | ||||
|  | ||||
|     ICON_SIZE: 64, | ||||
|  | ||||
|     _init: function(provider, metaInfo, terms) { | ||||
|         this.parent(provider, metaInfo, terms); | ||||
|  | ||||
|         this.actor.style_class = 'list-search-result'; | ||||
|         this.actor.x_fill = true; | ||||
|  | ||||
|         let content = new St.BoxLayout({ style_class: 'list-search-result-content', | ||||
|                                          vertical: false }); | ||||
|         this.actor.set_child(content); | ||||
|  | ||||
|         // An icon for, or thumbnail of, content | ||||
|         let icon = this.metaInfo['createIcon'](this.ICON_SIZE); | ||||
|         if (icon) { | ||||
|             content.add(icon); | ||||
|         } | ||||
|  | ||||
|         let details = new St.BoxLayout({ vertical: true }); | ||||
|         content.add(details, { x_fill: true, | ||||
|                                y_fill: false, | ||||
|                                x_align: St.Align.START, | ||||
|                                y_align: St.Align.MIDDLE }); | ||||
|  | ||||
|         let title = new St.Label({ style_class: 'list-search-result-title', | ||||
|                                    text: this.metaInfo['name'] }) | ||||
|         details.add(title, { x_fill: false, | ||||
|                              y_fill: false, | ||||
|                              x_align: St.Align.START, | ||||
|                              y_align: St.Align.START }); | ||||
|  | ||||
|         if (this.metaInfo['description']) { | ||||
|             let description = new St.Label({ style_class: 'list-search-result-description' }); | ||||
|             description.clutter_text.set_markup(this.metaInfo['description']); | ||||
|             details.add(description, { x_fill: false, | ||||
|                                        y_fill: false, | ||||
|                                        x_align: St.Align.START, | ||||
|                                        y_align: St.Align.END }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const GridSearchResult = new Lang.Class({ | ||||
|     Name: 'GridSearchResult', | ||||
|     Extends: SearchResult, | ||||
|  | ||||
|     _init: function(provider, metaInfo, terms) { | ||||
|         this.parent(provider, metaInfo, terms); | ||||
|  | ||||
|         this.actor.style_class = 'grid-search-result'; | ||||
|  | ||||
|         let content = provider.createResultActor(metaInfo, terms); | ||||
|         let dragSource = null; | ||||
|  | ||||
|         if (content == null) { | ||||
|             content = new St.Bin(); | ||||
|             let icon = new IconGrid.BaseIcon(this.metaInfo['name'], | ||||
|                                              { createIcon: this.metaInfo['createIcon'] }); | ||||
|             content.set_child(icon.actor); | ||||
|             content.label_actor = icon.label; | ||||
|             dragSource = icon.icon; | ||||
|         } else { | ||||
|             if (content._delegate && content._delegate.getDragActorSource) | ||||
|                 dragSource = content._delegate.getDragActorSource(); | ||||
|         } | ||||
|  | ||||
|         this.actor.set_child(content); | ||||
|  | ||||
|         let draggable = DND.makeDraggable(this.actor); | ||||
|         draggable.connect('drag-begin', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.beginItemDrag(this); | ||||
|                           })); | ||||
|         draggable.connect('drag-cancelled', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.cancelledItemDrag(this); | ||||
|                           })); | ||||
|         draggable.connect('drag-end', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.endItemDrag(this); | ||||
|                           })); | ||||
|  | ||||
|         if (!dragSource) | ||||
|             // not exactly right, but alignment problems are hard to notice | ||||
|             dragSource = content; | ||||
|         this._dragActorSource = dragSource; | ||||
|     }, | ||||
|  | ||||
|     getDragActorSource: function() { | ||||
|         return this._dragActorSource; | ||||
|     }, | ||||
|  | ||||
|     getDragActor: function() { | ||||
|         return this.metaInfo['createIcon'](Main.overview.dashIconSize); | ||||
|     }, | ||||
|  | ||||
|     shellWorkspaceLaunch: function(params) { | ||||
|         if (this.provider.dragActivateResult) | ||||
|             this.provider.dragActivateResult(this.metaInfo.id, params); | ||||
|         else | ||||
|             this.provider.activateResult(this.metaInfo.id, this.terms); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ListSearchResults = new Lang.Class({ | ||||
|     Name: 'ListSearchResults', | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.provider = provider; | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ style_class: 'search-section-content' }); | ||||
|         this.providerIcon = new ProviderIcon(provider); | ||||
|         this.providerIcon.connect('clicked', Lang.bind(this, | ||||
|             function() { | ||||
|                 provider.launchSearch(this._terms); | ||||
|                 Main.overview.toggle(); | ||||
|             })); | ||||
|  | ||||
|         this.actor.add(this.providerIcon, { x_fill: false, | ||||
|                                             y_fill: false, | ||||
|                                             x_align: St.Align.START, | ||||
|                                             y_align: St.Align.START }); | ||||
|  | ||||
|         this._content = new St.BoxLayout({ style_class: 'list-search-results', | ||||
|                                            vertical: true }); | ||||
|         this.actor.add(this._content, { expand: true }); | ||||
|  | ||||
|         this._notDisplayedResult = []; | ||||
|         this._terms = []; | ||||
|         this._pendingClear = false; | ||||
|     }, | ||||
|  | ||||
|     getResultsForDisplay: function() { | ||||
|         let alreadyVisible = this._pendingClear ? 0 : this.getVisibleResultCount(); | ||||
|         let canDisplay = MAX_LIST_SEARCH_RESULTS_ROWS - alreadyVisible; | ||||
|  | ||||
|         let newResults = this._notDisplayedResult.splice(0, canDisplay); | ||||
|         return newResults; | ||||
|     }, | ||||
|  | ||||
|     getVisibleResultCount: function() { | ||||
|         return this._content.get_n_children(); | ||||
|     }, | ||||
|  | ||||
|     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; | ||||
|     }, | ||||
|  | ||||
|     renderResults: function(metas) { | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             let display = new ListSearchResult(this.provider, metas[i], this._terms); | ||||
|             this._content.add_actor(display.actor); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     clear: function () { | ||||
|         this._content.destroy_all_children(); | ||||
|         this._pendingClear = false; | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this.getVisibleResultCount() > 0) | ||||
|             return this._content.get_child_at_index(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const GridSearchResults = new Lang.Class({ | ||||
|     Name: 'GridSearchResults', | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.provider = provider; | ||||
|  | ||||
|         this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS, | ||||
|                                              xAlign: St.Align.START }); | ||||
|         this.actor = new St.Bin({ x_align: St.Align.MIDDLE }); | ||||
|  | ||||
|         this.actor.set_child(this._grid.actor); | ||||
|  | ||||
|         this._notDisplayedResult = []; | ||||
|         this._terms = []; | ||||
|         this._pendingClear = false; | ||||
|     }, | ||||
|  | ||||
|     getResultsForDisplay: function() { | ||||
|         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; | ||||
|     }, | ||||
|  | ||||
|     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; | ||||
|     }, | ||||
|  | ||||
|     renderResults: function(metas) { | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             let display = new GridSearchResult(this.provider, metas[i], this._terms); | ||||
|             this._grid.addItem(display.actor); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     clear: function () { | ||||
|         this._grid.removeAll(); | ||||
|         this._pendingClear = false; | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this.getVisibleResultCount() > 0) | ||||
|             return this._grid.getItemAtIndex(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SearchResults = new Lang.Class({ | ||||
|     Name: 'SearchResults', | ||||
|  | ||||
|     _init: function(searchSystem) { | ||||
|         this._searchSystem = searchSystem; | ||||
|         this._searchSystem.connect('search-updated', Lang.bind(this, this._updateResults)); | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ name: 'searchResults', | ||||
|                                         vertical: true }); | ||||
|  | ||||
|         this._content = new St.BoxLayout({ name: 'searchResultsContent', | ||||
|                                            vertical: true }); | ||||
|         this._contentBin = new MaxWidthBin({ name: 'searchResultsBin', | ||||
|                                              x_fill: true, | ||||
|                                              y_fill: true, | ||||
|                                              child: this._content }); | ||||
|  | ||||
|         let scrollChild = new St.BoxLayout(); | ||||
|         scrollChild.add(this._contentBin, { expand: true }); | ||||
|  | ||||
|         this._scrollView = new St.ScrollView({ x_fill: true, | ||||
|                                                y_fill: false, | ||||
|                                                overlay_scrollbars: true, | ||||
|                                                style_class: 'search-display vfade' }); | ||||
|         this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); | ||||
|         this._scrollView.add_actor(scrollChild); | ||||
|         let action = new Clutter.PanAction({ interpolate: true }); | ||||
|         action.connect('pan', Lang.bind(this, this._onPan)); | ||||
|         this._scrollView.add_action(action); | ||||
|  | ||||
|         this.actor.add(this._scrollView, { x_fill: true, | ||||
|                                            y_fill: true, | ||||
|                                            expand: true, | ||||
|                                            x_align: St.Align.START, | ||||
|                                            y_align: St.Align.START }); | ||||
|  | ||||
|         this._statusText = new St.Label({ style_class: 'search-statustext' }); | ||||
|         this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE, | ||||
|                                        y_align: St.Align.MIDDLE }); | ||||
|         this._content.add(this._statusBin, { expand: true }); | ||||
|         this._statusBin.add_actor(this._statusText); | ||||
|         this._providers = this._searchSystem.getProviders(); | ||||
|         this._providerMeta = []; | ||||
|         for (let i = 0; i < this._providers.length; i++) { | ||||
|             this.createProviderMeta(this._providers[i]); | ||||
|         } | ||||
|  | ||||
|         this._highlightDefault = false; | ||||
|         this._defaultResult = null; | ||||
|     }, | ||||
|  | ||||
|     _onPan: function(action) { | ||||
|         let [dist, dx, dy] = action.get_motion_delta(0); | ||||
|         let adjustment = this._scrollView.vscroll.adjustment; | ||||
|         adjustment.value -= (dy / this.actor.height) * adjustment.page_size; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     createProviderMeta: function(provider) { | ||||
|         let providerBox = new St.BoxLayout({ style_class: 'search-section', | ||||
|                                              vertical: true }); | ||||
|         let providerIcon = null; | ||||
|         let resultDisplay = null; | ||||
|  | ||||
|         if (provider.appInfo) { | ||||
|             resultDisplay = new ListSearchResults(provider); | ||||
|             providerIcon = resultDisplay.providerIcon; | ||||
|         } else { | ||||
|             resultDisplay = new GridSearchResults(provider); | ||||
|         } | ||||
|  | ||||
|         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); | ||||
|     }, | ||||
|  | ||||
|     destroyProviderMeta: function(provider) { | ||||
|         for (let i=0; i < this._providerMeta.length; i++) { | ||||
|             let meta = this._providerMeta[i]; | ||||
|             if (meta.provider == provider) { | ||||
|                 meta.actor.destroy(); | ||||
|                 this._providerMeta.splice(i, 1); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _clearDisplay: function() { | ||||
|         for (let i = 0; i < this._providerMeta.length; i++) { | ||||
|             let meta = this._providerMeta[i]; | ||||
|             meta.resultDisplay.clear(); | ||||
|             meta.actor.hide(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _clearDisplayForProvider: function(provider) { | ||||
|         let meta = this._metaForProvider(provider); | ||||
|         meta.resultDisplay.clear(); | ||||
|         meta.actor.hide(); | ||||
|     }, | ||||
|  | ||||
|     reset: function() { | ||||
|         this._searchSystem.reset(); | ||||
|         this._statusBin.hide(); | ||||
|         this._clearDisplay(); | ||||
|         this._defaultResult = null; | ||||
|     }, | ||||
|  | ||||
|     startingSearch: function() { | ||||
|         this.reset(); | ||||
|         this._statusText.set_text(_("Searching…")); | ||||
|         this._statusBin.show(); | ||||
|     }, | ||||
|  | ||||
|     _metaForProvider: function(provider) { | ||||
|         return this._providerMeta[this._providers.indexOf(provider)]; | ||||
|     }, | ||||
|  | ||||
|     _maybeSetInitialSelection: function() { | ||||
|         let newDefaultResult = null; | ||||
|  | ||||
|         for (let i = 0; i < this._providerMeta.length; i++) { | ||||
|             let meta = this._providerMeta[i]; | ||||
|  | ||||
|             if (!meta.actor.visible) | ||||
|                 continue; | ||||
|  | ||||
|             let firstResult = meta.resultDisplay.getFirstResult(); | ||||
|             if (firstResult) { | ||||
|                 newDefaultResult = firstResult; | ||||
|                 break; // select this one! | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (newDefaultResult != this._defaultResult) { | ||||
|             if (this._defaultResult) | ||||
|                 this._defaultResult.setSelected(false); | ||||
|             if (newDefaultResult) | ||||
|                 newDefaultResult.setSelected(this._highlightDefault); | ||||
|  | ||||
|             this._defaultResult = newDefaultResult; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateStatusText: function () { | ||||
|         let haveResults = false; | ||||
|  | ||||
|         for (let i = 0; i < this._providerMeta.length; ++i) | ||||
|             if (this._providerMeta[i].resultDisplay.getFirstResult()) { | ||||
|                 haveResults = true; | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|         if (!haveResults) { | ||||
|             this._statusText.set_text(_("No results.")); | ||||
|             this._statusBin.show(); | ||||
|         } else { | ||||
|             this._statusBin.hide(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateResults: function(searchSystem, results) { | ||||
|         let terms = searchSystem.getTerms(); | ||||
|         let [provider, providerResults] = results; | ||||
|         let meta = this._metaForProvider(provider); | ||||
|  | ||||
|         if (providerResults.length == 0) { | ||||
|             this._clearDisplayForProvider(provider); | ||||
|             meta.resultDisplay.setResults([], []); | ||||
|             this._maybeSetInitialSelection(); | ||||
|             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() { | ||||
|         if (this._defaultResult) | ||||
|             this._defaultResult.activate(); | ||||
|     }, | ||||
|  | ||||
|     highlightDefault: function(highlight) { | ||||
|         this._highlightDefault = highlight; | ||||
|         if (this._defaultResult) | ||||
|             this._defaultResult.setSelected(highlight); | ||||
|     }, | ||||
|  | ||||
|     navigateFocus: function(direction) { | ||||
|         let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|         if (direction == Gtk.DirectionType.TAB_BACKWARD || | ||||
|             direction == (rtl ? Gtk.DirectionType.RIGHT | ||||
|                               : Gtk.DirectionType.LEFT) || | ||||
|             direction == Gtk.DirectionType.UP) { | ||||
|             this.actor.navigate_focus(null, direction, false); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let from = this._defaultResult ? this._defaultResult.actor : null; | ||||
|         this.actor.navigate_focus(from, direction, false); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ProviderIcon = new Lang.Class({ | ||||
|     Name: 'ProviderIcon', | ||||
|     Extends: St.Button, | ||||
|  | ||||
|     PROVIDER_ICON_SIZE: 48, | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.provider = provider; | ||||
|         this.parent({ style_class: 'search-provider-icon', | ||||
|                       reactive: true, | ||||
|                       can_focus: true, | ||||
|                       track_hover: true }); | ||||
|  | ||||
|         this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() }); | ||||
|         this.set_child(this._content); | ||||
|  | ||||
|         let rtl = (this.get_text_direction() == Clutter.TextDirection.RTL); | ||||
|  | ||||
|         this.moreIcon = new St.Widget({ style_class: 'search-provider-icon-more', | ||||
|                                         visible: false, | ||||
|                                         x_align: rtl ? Clutter.ActorAlign.START : Clutter.ActorAlign.END, | ||||
|                                         y_align: Clutter.ActorAlign.END, | ||||
|                                         x_expand: true, | ||||
|                                         y_expand: true }); | ||||
|  | ||||
|         let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE, | ||||
|                                  gicon: provider.appInfo.get_icon() }); | ||||
|         this._content.add_actor(icon); | ||||
|         this._content.add_actor(this.moreIcon); | ||||
|     } | ||||
| }); | ||||
| @@ -16,12 +16,10 @@ const _modes = { | ||||
|     'restrictive': { | ||||
|         parentMode: null, | ||||
|         stylesheetName: 'gnome-shell.css', | ||||
|         overridesSchema: 'org.gnome.shell.overrides', | ||||
|         hasOverview: false, | ||||
|         showCalendarEvents: false, | ||||
|         allowSettings: false, | ||||
|         allowExtensions: false, | ||||
|         allowScreencast: false, | ||||
|         enabledExtensions: [], | ||||
|         hasRunDialog: false, | ||||
|         hasWorkspaces: false, | ||||
| @@ -47,9 +45,10 @@ const _modes = { | ||||
|         unlockDialog: imports.gdm.loginDialog.LoginDialog, | ||||
|         components: ['polkitAgent'], | ||||
|         panel: { | ||||
|             left: [], | ||||
|             left: ['logo'], | ||||
|             center: ['dateMenu'], | ||||
|             right: ['a11yGreeter', 'keyboard', 'aggregateMenu'], | ||||
|             right: ['a11yGreeter', 'display', 'keyboard', | ||||
|                     'volume', 'battery', 'powerMenu'] | ||||
|         }, | ||||
|         panelStyle: 'login-screen' | ||||
|     }, | ||||
| @@ -60,9 +59,9 @@ const _modes = { | ||||
|         unlockDialog: undefined, | ||||
|         components: ['polkitAgent', 'telepathyClient'], | ||||
|         panel: { | ||||
|             left: [], | ||||
|             left: ['userMenu'], | ||||
|             center: [], | ||||
|             right: ['aggregateMenu'] | ||||
|             right: ['lockScreen'] | ||||
|         }, | ||||
|         panelStyle: 'lock-screen' | ||||
|     }, | ||||
| @@ -72,19 +71,28 @@ const _modes = { | ||||
|         unlockDialog: undefined, | ||||
|         components: ['polkitAgent', 'telepathyClient'], | ||||
|         panel: { | ||||
|             left: [], | ||||
|             left: ['userMenu'], | ||||
|             center: [], | ||||
|             right: ['a11y', 'keyboard', 'aggregateMenu'] | ||||
|             right: ['a11y', 'keyboard', 'lockScreen'] | ||||
|         }, | ||||
|         panelStyle: 'unlock-screen' | ||||
|     }, | ||||
|  | ||||
|     'initial-setup': { | ||||
|         isPrimary: true, | ||||
|         components: ['keyring'], | ||||
|         panel: { | ||||
|             left: [], | ||||
|             center: ['dateMenu'], | ||||
|             right: ['a11yGreeter', 'keyboard', 'volume'] | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     'user': { | ||||
|         hasOverview: true, | ||||
|         showCalendarEvents: true, | ||||
|         allowSettings: true, | ||||
|         allowExtensions: true, | ||||
|         allowScreencast: true, | ||||
|         hasRunDialog: true, | ||||
|         hasWorkspaces: true, | ||||
|         hasWindows: true, | ||||
| @@ -93,21 +101,29 @@ const _modes = { | ||||
|         isPrimary: true, | ||||
|         unlockDialog: imports.ui.unlockDialog.UnlockDialog, | ||||
|         components: ['networkAgent', 'polkitAgent', 'telepathyClient', | ||||
|                      'keyring', 'autorunManager', 'automountManager'], | ||||
|                      'keyring', 'recorder', 'autorunManager', 'automountManager'], | ||||
|         panel: { | ||||
|             left: ['activities', 'appMenu'], | ||||
|             center: ['dateMenu'], | ||||
|             right: ['a11y', 'keyboard', 'aggregateMenu'] | ||||
|             right: ['a11y', 'keyboard', 'volume', 'bluetooth', | ||||
|                     'network', 'battery', 'userMenu'] | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
| function _loadMode(file, info) { | ||||
| function _getModes(modesLoadedCallback) { | ||||
|     FileUtils.collectFromDatadirsAsync('modes', | ||||
|                                        { processFile: _loadMode, | ||||
|                                          loadedCallback: modesLoadedCallback, | ||||
|                                          data: _modes }); | ||||
| } | ||||
|  | ||||
| function _loadMode(file, info, loadedData) { | ||||
|     let name = info.get_name(); | ||||
|     let suffix = name.indexOf('.json'); | ||||
|     let modeName = suffix == -1 ? name : name.slice(name, suffix); | ||||
|  | ||||
|     if (_modes.hasOwnProperty(modeName)) | ||||
|     if (loadedData.hasOwnProperty(modeName)) | ||||
|         return; | ||||
|  | ||||
|     let fileContent, success, tag, newMode; | ||||
| @@ -118,24 +134,19 @@ function _loadMode(file, info) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     _modes[modeName] = {}; | ||||
|     loadedData[modeName] = {}; | ||||
|     let propBlacklist = ['unlockDialog']; | ||||
|     for (let prop in _modes[DEFAULT_MODE]) { | ||||
|     for (let prop in loadedData[DEFAULT_MODE]) { | ||||
|         if (newMode[prop] !== undefined && | ||||
|             propBlacklist.indexOf(prop) == -1) | ||||
|             _modes[modeName][prop] = newMode[prop]; | ||||
|             loadedData[modeName][prop]= newMode[prop]; | ||||
|     } | ||||
|     _modes[modeName]['isPrimary'] = true; | ||||
| } | ||||
|  | ||||
| function _loadModes() { | ||||
|     FileUtils.collectFromDatadirs('modes', false, _loadMode); | ||||
|     loadedData[modeName]['isPrimary'] = true; | ||||
| } | ||||
|  | ||||
| function listModes() { | ||||
|     _loadModes(); | ||||
|     Mainloop.idle_add(function() { | ||||
|         let names = Object.getOwnPropertyNames(_modes); | ||||
|     _getModes(function(modes) { | ||||
|         let names = Object.getOwnPropertyNames(modes); | ||||
|         for (let i = 0; i < names.length; i++) | ||||
|             if (_modes[names[i]].isPrimary) | ||||
|                 print(names[i]); | ||||
| @@ -148,12 +159,19 @@ const SessionMode = new Lang.Class({ | ||||
|     Name: 'SessionMode', | ||||
|  | ||||
|     _init: function() { | ||||
|         _loadModes(); | ||||
|         let isPrimary = (_modes[global.session_mode] && | ||||
|                          _modes[global.session_mode].isPrimary); | ||||
|         let mode = isPrimary ? global.session_mode : 'user'; | ||||
|         this._modeStack = [mode]; | ||||
|         global.connect('notify::session-mode', Lang.bind(this, this._sync)); | ||||
|         this._modes = _modes; | ||||
|         this._modeStack = [DEFAULT_MODE]; | ||||
|         this._sync(); | ||||
|  | ||||
|         _getModes(Lang.bind(this, function(modes) { | ||||
|             this._modes = modes; | ||||
|             let primary = modes[global.session_mode] && | ||||
|                           modes[global.session_mode].isPrimary; | ||||
|             let mode = primary ? global.session_mode : 'user'; | ||||
|             this._modeStack = [mode]; | ||||
|             this._sync(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     pushMode: function(mode) { | ||||
| @@ -180,13 +198,13 @@ const SessionMode = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let params = _modes[this.currentMode]; | ||||
|         let params = this._modes[this.currentMode]; | ||||
|         let defaults; | ||||
|         if (params.parentMode) | ||||
|             defaults = Params.parse(_modes[params.parentMode], | ||||
|                                     _modes[DEFAULT_MODE]); | ||||
|             defaults = Params.parse(this._modes[params.parentMode], | ||||
|                                     this._modes[DEFAULT_MODE]); | ||||
|         else | ||||
|             defaults = _modes[DEFAULT_MODE]; | ||||
|             defaults = this._modes[DEFAULT_MODE]; | ||||
|         params = Params.parse(params, defaults); | ||||
|  | ||||
|         // A simplified version of Lang.copyProperties, handles | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user