Compare commits
	
		
			2 Commits
		
	
	
		
			3.10.2
			...
			wip/gcampa
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 6d4ca1fcc8 | ||
|   | 93b1af401f | 
							
								
								
									
										5
									
								
								.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 | ||||
| @@ -73,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 | ||||
|   | ||||
							
								
								
									
										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() { | ||||
|   | ||||
							
								
								
									
										526
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						| @@ -1,529 +1,3 @@ | ||||
| 3.10.2 | ||||
| ====== | ||||
| * gdm: Don't allow user-list to fill up the entire screen [Florian; #710555] | ||||
| * Restore support for 'disable-restart-buttons' [Florian; #711244] | ||||
| * alidate parameters of exposed DBus methods [Florian; #699752] | ||||
| * Misc. bug fixes [Florian; #709806] | ||||
|  | ||||
| Contributors: | ||||
|   Florian Müllner | ||||
|  | ||||
| Translations: | ||||
|   Ihar Hrachyshka [be], Stas Solovey [ru], Kjartan Maraas [nb], | ||||
|   Dimitris Spingos [el], Rafael Ferreira [pt_BR], Yuri Myasoedov [ru], | ||||
|   Sphinx Jiang [zh_CN], Shantha kumar [ta] | ||||
|  | ||||
| 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\" | ||||
|   | ||||
							
								
								
									
										74
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						| @@ -1,5 +1,5 @@ | ||||
| AC_PREREQ(2.63) | ||||
| AC_INIT([gnome-shell],[3.10.2],[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]) | ||||
| @@ -24,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", | ||||
| @@ -50,7 +53,7 @@ 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 | ||||
| @@ -59,51 +62,41 @@ AM_CONDITIONAL(BUILD_RECORDER, $build_recorder) | ||||
|  | ||||
| CLUTTER_MIN_VERSION=1.13.4 | ||||
| GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1 | ||||
| GJS_MIN_VERSION=1.38.1 | ||||
| MUTTER_MIN_VERSION=3.10.2 | ||||
| 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 | ||||
| GNOME_MENUS_REQUIRED_VERSION=3.5.3 | ||||
| NETWORKMANAGER_MIN_VERSION=0.9.8 | ||||
| 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 | ||||
|             libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_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" | ||||
|  | ||||
| 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) | ||||
| @@ -116,7 +109,7 @@ PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.7.4) | ||||
| PKG_CHECK_MODULES(CARIBOU, caribou-1.0 >= 0.4.8) | ||||
|  | ||||
| AC_MSG_CHECKING([for bluetooth support]) | ||||
| PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.9.0], | ||||
| PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0], | ||||
|         [BLUETOOTH_DIR=`$PKG_CONFIG --variable=applet_libdir gnome-bluetooth-1.0` | ||||
| 	 BLUETOOTH_LIBS=`$PKG_CONFIG --variable=applet_libs gnome-bluetooth-1.0` | ||||
| 	 AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"]) | ||||
| @@ -139,9 +132,8 @@ 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` | ||||
| @@ -181,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
									
								
							
							
						
						| @@ -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,12 +1,5 @@ | ||||
| wandadir = $(pkgdatadir) | ||||
| dist_wanda_DATA = wanda.png | ||||
|  | ||||
| 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 | ||||
| @@ -19,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 | ||||
| @@ -45,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	\ | ||||
| @@ -64,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 | ||||
| @@ -89,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)					\ | ||||
| @@ -99,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 | ||||
|     --> | ||||
|   | ||||
| @@ -21,6 +21,16 @@ | ||||
|         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"> | ||||
|       <default>[ 'epiphany.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'libreoffice-writer.desktop', 'nautilus.desktop', 'gnome-documents.desktop' ]</default> | ||||
|       <_summary>List of desktop file IDs for favorite applications</_summary> | ||||
| @@ -37,13 +47,6 @@ | ||||
|         application view, rather than being displayed inline in the main view. | ||||
|       </_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> | ||||
|     <key name="command-history" type="as"> | ||||
|       <default>[]</default> | ||||
|       <_summary>History for command (Alt-F2) dialog</_summary> | ||||
| @@ -52,6 +55,16 @@ | ||||
|       <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' menuitem in the user menu.</_summary> | ||||
| @@ -71,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> | ||||
| @@ -103,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> | ||||
| @@ -124,6 +131,13 @@ | ||||
|         Keybinding to focus the active notification. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="toggle-recording" type="as"> | ||||
|       <default><![CDATA[['<Control><Shift><Alt>r']]]></default> | ||||
|       <_summary>Keybinding to toggle the screen recorder</_summary> | ||||
|       <_description> | ||||
|         Keybinding to start/stop the builtin screen recorder. | ||||
|       </_description> | ||||
|     </key> | ||||
|   </schema> | ||||
|  | ||||
|   <schema id="org.gnome.shell.keyboard" path="/org/gnome/shell/keyboard/" | ||||
| @@ -137,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> | ||||
|  | ||||
| @@ -168,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. | ||||
| @@ -223,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> | ||||
|   | ||||
| @@ -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 | 
							
								
								
									
										
											BIN
										
									
								
								data/wanda.png
									
									
									
									
									
								
							
							
						
						| Before Width: | Height: | Size: 13 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) $(BLUETOOTH_LIBS) $(top_builddir)/src/libgnome-shell-menu.la $(top_builddir)/src/libgnome-shell-base.la $(top_builddir)/src/libgnome-shell.la | ||||
| GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(BLUETOOTH_LIBS) $(top_builddir)/src/libgnome-shell.la | ||||
|  | ||||
| # This includes the standard gtk-doc make rules, copied by gtkdocize. | ||||
| 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> | ||||
|   | ||||
| @@ -17,11 +17,10 @@ misc/config.js: misc/config.js.in Makefile | ||||
| jsdir = $(pkgdatadir)/js | ||||
|  | ||||
| nobase_dist_js_DATA = 	\ | ||||
| 	gdm/authPrompt.js	\ | ||||
| 	gdm/batch.js		\ | ||||
| 	gdm/fingerprint.js	\ | ||||
| 	gdm/loginDialog.js	\ | ||||
| 	gdm/oVirt.js		\ | ||||
| 	gdm/powerMenu.js	\ | ||||
| 	gdm/realmd.js		\ | ||||
| 	gdm/util.js		\ | ||||
| 	extensionPrefs/main.js	\ | ||||
| @@ -34,13 +33,10 @@ nobase_dist_js_DATA = 	\ | ||||
| 	misc/jsParse.js		\ | ||||
| 	misc/loginManager.js	\ | ||||
| 	misc/modemManager.js	\ | ||||
| 	misc/objectManager.js	\ | ||||
| 	misc/params.js		\ | ||||
| 	misc/smartcardManager.js \ | ||||
| 	misc/util.js		\ | ||||
| 	perf/core.js		\ | ||||
| 	ui/altTab.js		\ | ||||
| 	ui/animation.js		\ | ||||
| 	ui/appDisplay.js	\ | ||||
| 	ui/appFavorites.js	\ | ||||
| 	ui/backgroundMenu.js	\ | ||||
| @@ -56,7 +52,6 @@ nobase_dist_js_DATA = 	\ | ||||
| 	ui/extensionSystem.js	\ | ||||
| 	ui/extensionDownloader.js \ | ||||
| 	ui/environment.js	\ | ||||
| 	ui/focusCaretTracker.js\ | ||||
| 	ui/ibusCandidatePopup.js\ | ||||
| 	ui/grabHelper.js	\ | ||||
| 	ui/iconGrid.js		\ | ||||
| @@ -73,7 +68,6 @@ nobase_dist_js_DATA = 	\ | ||||
| 	ui/sessionMode.js	\ | ||||
| 	ui/shellEntry.js	\ | ||||
| 	ui/shellMountOperation.js \ | ||||
| 	ui/slider.js		\ | ||||
| 	ui/notificationDaemon.js \ | ||||
| 	ui/osdWindow.js		\ | ||||
| 	ui/overview.js		\ | ||||
| @@ -83,9 +77,7 @@ nobase_dist_js_DATA = 	\ | ||||
| 	ui/pointerWatcher.js    \ | ||||
| 	ui/popupMenu.js		\ | ||||
| 	ui/remoteSearch.js	\ | ||||
| 	ui/remoteMenu.js	\ | ||||
| 	ui/runDialog.js		\ | ||||
| 	ui/screencast.js	\ | ||||
| 	ui/screenshot.js	\ | ||||
|         ui/screenShield.js	\ | ||||
| 	ui/scripting.js		\ | ||||
| @@ -93,18 +85,16 @@ nobase_dist_js_DATA = 	\ | ||||
| 	ui/searchDisplay.js	\ | ||||
| 	ui/shellDBus.js		\ | ||||
| 	ui/status/accessibility.js	\ | ||||
| 	ui/status/brightness.js	\ | ||||
| 	ui/status/keyboard.js	\ | ||||
| 	ui/status/lockScreenMenu.js	\ | ||||
| 	ui/status/network.js	\ | ||||
| 	ui/status/power.js	\ | ||||
| 	ui/status/rfkill.js	\ | ||||
| 	ui/status/volume.js	\ | ||||
| 	ui/status/bluetooth.js	\ | ||||
| 	ui/status/screencast.js	\ | ||||
| 	ui/status/system.js	\ | ||||
| 	ui/switcherPopup.js	\ | ||||
| 	ui/tweener.js		\ | ||||
| 	ui/unlockDialog.js	\ | ||||
| 	ui/userMenu.js		\ | ||||
| 	ui/userWidget.js	\ | ||||
| 	ui/viewSelector.js	\ | ||||
| 	ui/wanda.js		\ | ||||
| @@ -120,6 +110,7 @@ nobase_dist_js_DATA = 	\ | ||||
| 	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) | ||||
|   | ||||
| @@ -1,507 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Animation = imports.ui.animation; | ||||
| const Batch = imports.gdm.batch; | ||||
| const GdmUtil = imports.gdm.util; | ||||
| const Params = imports.misc.params; | ||||
| const ShellEntry = imports.ui.shellEntry; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const UserWidget = imports.ui.userWidget; | ||||
|  | ||||
| const DEFAULT_BUTTON_WELL_ICON_SIZE = 24; | ||||
| const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; | ||||
| const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; | ||||
|  | ||||
| const MESSAGE_FADE_OUT_ANIMATION_TIME = 0.5; | ||||
|  | ||||
| const AuthPromptMode = { | ||||
|     UNLOCK_ONLY: 0, | ||||
|     UNLOCK_OR_LOG_IN: 1 | ||||
| }; | ||||
|  | ||||
| const AuthPromptStatus = { | ||||
|     NOT_VERIFYING: 0, | ||||
|     VERIFYING: 1, | ||||
|     VERIFICATION_FAILED: 2, | ||||
|     VERIFICATION_SUCCEEDED: 3 | ||||
| }; | ||||
|  | ||||
| const BeginRequestType = { | ||||
|     PROVIDE_USERNAME: 0, | ||||
|     DONT_PROVIDE_USERNAME: 1 | ||||
| }; | ||||
|  | ||||
| const AuthPrompt = new Lang.Class({ | ||||
|     Name: 'AuthPrompt', | ||||
|  | ||||
|     _init: function(gdmClient, mode) { | ||||
|         this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; | ||||
|  | ||||
|         this._gdmClient = gdmClient; | ||||
|         this._mode = mode; | ||||
|  | ||||
|         let reauthenticationOnly; | ||||
|         if (this._mode == AuthPromptMode.UNLOCK_ONLY) | ||||
|             reauthenticationOnly = true; | ||||
|         else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN) | ||||
|             reauthenticationOnly = false; | ||||
|  | ||||
|         this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly }); | ||||
|  | ||||
|         this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion)); | ||||
|         this._userVerifier.connect('show-message', Lang.bind(this, this._onShowMessage)); | ||||
|         this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed)); | ||||
|         this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); | ||||
|         this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); | ||||
|         this._userVerifier.connect('smartcard-status-changed', Lang.bind(this, this._onSmartcardStatusChanged)); | ||||
|         this._userVerifier.connect('ovirt-user-authenticated', Lang.bind(this, this._onOVirtUserAuthenticated)); | ||||
|         this.smartcardDetected = this._userVerifier.smartcardDetected; | ||||
|  | ||||
|         this.connect('next', Lang.bind(this, function() { | ||||
|                          this.updateSensitivity(false); | ||||
|                          this.startSpinning(); | ||||
|                          if (this._queryingService) { | ||||
|                              this._userVerifier.answerQuery(this._queryingService, this._entry.text); | ||||
|                          } else { | ||||
|                              this._preemptiveAnswer = this._entry.text; | ||||
|                          } | ||||
|                      })); | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', | ||||
|                                         vertical: true }); | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|         this.actor.connect('key-press-event', | ||||
|                            Lang.bind(this, function(actor, event) { | ||||
|                                if (event.get_key_symbol() == Clutter.KEY_Escape) { | ||||
|                                    this.cancel(); | ||||
|                                } | ||||
|                            })); | ||||
|  | ||||
|         this._userWell = new St.Bin({ x_fill: true, | ||||
|                                       x_align: St.Align.START }); | ||||
|         this.actor.add(this._userWell, | ||||
|                        { x_align: St.Align.START, | ||||
|                          x_fill: true, | ||||
|                          y_fill: true, | ||||
|                          expand: true }); | ||||
|         this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); | ||||
|  | ||||
|         this.actor.add(this._label, | ||||
|                        { expand: true, | ||||
|                          x_fill: true, | ||||
|                          y_fill: true, | ||||
|                          x_align: St.Align.START }); | ||||
|         this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', | ||||
|                                      can_focus: true }); | ||||
|         ShellEntry.addContextMenu(this._entry, { isPassword: true }); | ||||
|  | ||||
|         this.actor.add(this._entry, | ||||
|                        { expand: true, | ||||
|                          x_fill: true, | ||||
|                          y_fill: false, | ||||
|                          x_align: St.Align.START }); | ||||
|  | ||||
|         this._entry.grab_key_focus(); | ||||
|  | ||||
|         this._message = new St.Label({ opacity: 0, | ||||
|                                        styleClass: 'login-dialog-message' }); | ||||
|         this._message.clutter_text.line_wrap = true; | ||||
|         this.actor.add(this._message, { x_fill: true, y_align: St.Align.START }); | ||||
|  | ||||
|         this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', | ||||
|                                              vertical: false }); | ||||
|         this.actor.add(this._buttonBox, | ||||
|                        { expand:  true, | ||||
|                          x_align: St.Align.MIDDLE, | ||||
|                          y_align: St.Align.END }); | ||||
|  | ||||
|         this._defaultButtonWell = new St.Widget({ layout_manager: new Clutter.BinLayout() }); | ||||
|         this._defaultButtonWellActor = null; | ||||
|  | ||||
|         this._initButtons(); | ||||
|  | ||||
|         let spinnerIcon = global.datadir + '/theme/process-working.svg'; | ||||
|         this._spinner = new Animation.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); | ||||
|         this._spinner.actor.opacity = 0; | ||||
|         this._spinner.actor.show(); | ||||
|         this._defaultButtonWell.add_child(this._spinner.actor); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy: function() { | ||||
|         this._userVerifier.clear(); | ||||
|         this._userVerifier.disconnectAll(); | ||||
|         this._userVerifier = null; | ||||
|     }, | ||||
|  | ||||
|     _initButtons: function() { | ||||
|         this.cancelButton = new St.Button({ style_class: 'modal-dialog-button', | ||||
|                                             button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, | ||||
|                                             reactive: true, | ||||
|                                             can_focus: true, | ||||
|                                             label: _("Cancel") }); | ||||
|         this.cancelButton.connect('clicked', | ||||
|                                    Lang.bind(this, function() { | ||||
|                                        this.cancel(); | ||||
|                                    })); | ||||
|         this._buttonBox.add(this.cancelButton, | ||||
|                             { expand: false, | ||||
|                               x_fill: false, | ||||
|                               y_fill: false, | ||||
|                               x_align: St.Align.START, | ||||
|                               y_align: St.Align.END }); | ||||
|  | ||||
|         this._buttonBox.add(this._defaultButtonWell, | ||||
|                             { expand: true, | ||||
|                               x_fill: false, | ||||
|                               y_fill: false, | ||||
|                               x_align: St.Align.END, | ||||
|                               y_align: St.Align.MIDDLE }); | ||||
|         this.nextButton = new St.Button({ style_class: 'modal-dialog-button', | ||||
|                                           button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, | ||||
|                                           reactive: true, | ||||
|                                           can_focus: true, | ||||
|                                           label: _("Next") }); | ||||
|         this.nextButton.connect('clicked', | ||||
|                                  Lang.bind(this, function() { | ||||
|                                      this.emit('next'); | ||||
|                                  })); | ||||
|         this.nextButton.add_style_pseudo_class('default'); | ||||
|         this._buttonBox.add(this.nextButton, | ||||
|                             { expand: false, | ||||
|                               x_fill: false, | ||||
|                               y_fill: false, | ||||
|                               x_align: St.Align.END, | ||||
|                               y_align: St.Align.END }); | ||||
|  | ||||
|         this._updateNextButtonSensitivity(this._entry.text.length > 0); | ||||
|  | ||||
|         this._entry.clutter_text.connect('text-changed', | ||||
|                                          Lang.bind(this, function() { | ||||
|                                              if (!this._userVerifier.hasPendingMessages) | ||||
|                                                  this._fadeOutMessage(); | ||||
|  | ||||
|                                              this._updateNextButtonSensitivity(this._entry.text.length > 0); | ||||
|                                          })); | ||||
|         this._entry.clutter_text.connect('activate', Lang.bind(this, function() { | ||||
|             this.emit('next'); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _onAskQuestion: function(verifier, serviceName, question, passwordChar) { | ||||
|         if (this._preemptiveAnswer) { | ||||
|             if (this._queryingService) | ||||
|                 this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer); | ||||
|             this._preemptiveAnswer = null; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (this._queryingService) | ||||
|             this.clear(); | ||||
|  | ||||
|         this._queryingService = serviceName; | ||||
|         this.setPasswordChar(passwordChar); | ||||
|         this.setQuestion(question); | ||||
|  | ||||
|         if (passwordChar) { | ||||
|             if (this._userVerifier.reauthenticating) | ||||
|                 this.nextButton.label = _("Unlock"); | ||||
|             else | ||||
|                 this.nextButton.label = C_("button", "Sign In"); | ||||
|         } else { | ||||
|             this.nextButton.label = _("Next"); | ||||
|         } | ||||
|  | ||||
|         this.updateSensitivity(true); | ||||
|         this.emit('prompted'); | ||||
|     }, | ||||
|  | ||||
|     _onOVirtUserAuthenticated: function() { | ||||
|         if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) | ||||
|             this.reset(); | ||||
|     }, | ||||
|  | ||||
|     _onSmartcardStatusChanged: function() { | ||||
|         this.smartcardDetected = this._userVerifier.smartcardDetected; | ||||
|  | ||||
|         // Most of the time we want to reset if the user inserts or removes | ||||
|         // a smartcard. Smartcard insertion "preempts" what the user was | ||||
|         // doing, and smartcard removal aborts the preemption. | ||||
|         // The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying | ||||
|         //                        with a smartcard | ||||
|         //                     2) Don't reset if we've already succeeded at verification and | ||||
|         //                        the user is getting logged in. | ||||
|         if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) && | ||||
|             this.verificationStatus == AuthPromptStatus.VERIFYING && | ||||
|             this.smartcardDetected) | ||||
|             return; | ||||
|  | ||||
|         if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) | ||||
|             this.reset(); | ||||
|     }, | ||||
|  | ||||
|     _onShowMessage: function(userVerifier, message, type) { | ||||
|         this.setMessage(message, type); | ||||
|         this.emit('prompted'); | ||||
|     }, | ||||
|  | ||||
|     _onVerificationFailed: function() { | ||||
|         this._queryingService = null; | ||||
|         this.clear(); | ||||
|  | ||||
|         this.updateSensitivity(true); | ||||
|         this.setActorInDefaultButtonWell(null); | ||||
|         this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; | ||||
|     }, | ||||
|  | ||||
|     _onVerificationComplete: function() { | ||||
|         this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; | ||||
|     }, | ||||
|  | ||||
|     _onReset: function() { | ||||
|         if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) { | ||||
|             this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; | ||||
|             this.reset(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     addActorToDefaultButtonWell: function(actor) { | ||||
|         this._defaultButtonWell.add_child(actor); | ||||
|     }, | ||||
|  | ||||
|     setActorInDefaultButtonWell: function(actor, animate) { | ||||
|         if (!this._defaultButtonWellActor && | ||||
|             !actor) | ||||
|             return; | ||||
|  | ||||
|         let oldActor = this._defaultButtonWellActor; | ||||
|  | ||||
|         if (oldActor) | ||||
|             Tweener.removeTweens(oldActor); | ||||
|  | ||||
|         let isSpinner; | ||||
|         if (actor == this._spinner.actor) | ||||
|             isSpinner = true; | ||||
|         else | ||||
|             isSpinner = false; | ||||
|  | ||||
|         if (this._defaultButtonWellActor != actor && oldActor) { | ||||
|             if (!animate) { | ||||
|                 oldActor.opacity = 0; | ||||
|             } else { | ||||
|                 Tweener.addTween(oldActor, | ||||
|                                  { opacity: 0, | ||||
|                                    time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, | ||||
|                                    delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, | ||||
|                                    transition: 'linear', | ||||
|                                    onCompleteScope: this, | ||||
|                                    onComplete: function() { | ||||
|                                       if (isSpinner) { | ||||
|                                           if (this._spinner) | ||||
|                                               this._spinner.stop(); | ||||
|                                       } | ||||
|                                    } | ||||
|                                  }); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (actor) { | ||||
|             if (isSpinner) | ||||
|                 this._spinner.play(); | ||||
|  | ||||
|             if (!animate) | ||||
|                 actor.opacity = 255; | ||||
|             else | ||||
|                 Tweener.addTween(actor, | ||||
|                                  { opacity: 255, | ||||
|                                    time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, | ||||
|                                    delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, | ||||
|                                    transition: 'linear' }); | ||||
|         } | ||||
|  | ||||
|         this._defaultButtonWellActor = actor; | ||||
|     }, | ||||
|  | ||||
|     startSpinning: function() { | ||||
|         this.setActorInDefaultButtonWell(this._spinner.actor, true); | ||||
|     }, | ||||
|  | ||||
|     stopSpinning: function() { | ||||
|         this.setActorInDefaultButtonWell(null, false); | ||||
|     }, | ||||
|  | ||||
|     clear: function() { | ||||
|         this._entry.text = ''; | ||||
|         this.stopSpinning(); | ||||
|     }, | ||||
|  | ||||
|     setPasswordChar: function(passwordChar) { | ||||
|         this._entry.clutter_text.set_password_char(passwordChar); | ||||
|         this._entry.menu.isPassword = passwordChar != ''; | ||||
|     }, | ||||
|  | ||||
|     setQuestion: function(question) { | ||||
|         this._label.set_text(question); | ||||
|  | ||||
|         this._label.show(); | ||||
|         this._entry.show(); | ||||
|  | ||||
|         this._entry.grab_key_focus(); | ||||
|     }, | ||||
|  | ||||
|     getAnswer: function() { | ||||
|         let text; | ||||
|  | ||||
|         if (this._preemptiveAnswer) { | ||||
|             text = this._preemptiveAnswer; | ||||
|             this._preemptiveAnswer = null; | ||||
|         } else { | ||||
|             text = this._entry.get_text(); | ||||
|         } | ||||
|  | ||||
|         return text; | ||||
|     }, | ||||
|  | ||||
|     _fadeOutMessage: function() { | ||||
|         if (this._message.opacity == 0) | ||||
|             return; | ||||
|         Tweener.removeTweens(this._message); | ||||
|         Tweener.addTween(this._message, | ||||
|                          { opacity: 0, | ||||
|                            time: MESSAGE_FADE_OUT_ANIMATION_TIME, | ||||
|                            transition: 'easeOutQuad' | ||||
|                          }); | ||||
|     }, | ||||
|  | ||||
|     setMessage: function(message, type) { | ||||
|         if (type == GdmUtil.MessageType.ERROR) | ||||
|             this._message.add_style_class_name('login-dialog-message-warning'); | ||||
|         else | ||||
|             this._message.remove_style_class_name('login-dialog-message-warning'); | ||||
|  | ||||
|         if (type == GdmUtil.MessageType.HINT) | ||||
|             this._message.add_style_class_name('login-dialog-message-hint'); | ||||
|         else | ||||
|             this._message.remove_style_class_name('login-dialog-message-hint'); | ||||
|  | ||||
|         if (message) { | ||||
|             Tweener.removeTweens(this._message); | ||||
|             this._message.text = message; | ||||
|             this._message.opacity = 255; | ||||
|         } else { | ||||
|             this._message.opacity = 0; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateNextButtonSensitivity: function(sensitive) { | ||||
|         this.nextButton.reactive = sensitive; | ||||
|         this.nextButton.can_focus = sensitive; | ||||
|     }, | ||||
|  | ||||
|     updateSensitivity: function(sensitive) { | ||||
|         this._updateNextButtonSensitivity(sensitive); | ||||
|         this._entry.reactive = sensitive; | ||||
|         this._entry.clutter_text.editable = sensitive; | ||||
|     }, | ||||
|  | ||||
|     hide: function() { | ||||
|         this.setActorInDefaultButtonWell(null, true); | ||||
|         this.actor.hide(); | ||||
|         this._message.opacity = 0; | ||||
|  | ||||
|         this.setUser(null); | ||||
|  | ||||
|         this.updateSensitivity(true); | ||||
|         this._entry.set_text(''); | ||||
|     }, | ||||
|  | ||||
|     setUser: function(user) { | ||||
|         if (user) { | ||||
|             let userWidget = new UserWidget.UserWidget(user); | ||||
|             this._userWell.set_child(userWidget.actor); | ||||
|         } else { | ||||
|             this._userWell.set_child(null); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     reset: function() { | ||||
|         let oldStatus = this.verificationStatus; | ||||
|         this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; | ||||
|  | ||||
|         if (oldStatus == AuthPromptStatus.VERIFYING) | ||||
|             this._userVerifier.cancel(); | ||||
|  | ||||
|         this._queryingService = null; | ||||
|         this.clear(); | ||||
|         this._message.opacity = 0; | ||||
|         this.setUser(null); | ||||
|         this.stopSpinning(); | ||||
|  | ||||
|         if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED) | ||||
|             this.emit('failed'); | ||||
|  | ||||
|         let beginRequestType; | ||||
|  | ||||
|         if (this._mode == AuthPromptMode.UNLOCK_ONLY) { | ||||
|             // The user is constant at the unlock screen, so it will immediately | ||||
|             // respond to the request with the username | ||||
|             beginRequestType = BeginRequestType.PROVIDE_USERNAME; | ||||
|         } else if (this._userVerifier.serviceIsForeground(GdmUtil.OVIRT_SERVICE_NAME) || | ||||
|                    (this.smartcardDetected && | ||||
|                     this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME))) { | ||||
|             // We don't need to know the username if the user preempted the login screen | ||||
|             // with a smartcard or with preauthenticated oVirt credentials | ||||
|             beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME; | ||||
|         } else { | ||||
|             // In all other cases, we should get the username up front. | ||||
|             beginRequestType = BeginRequestType.PROVIDE_USERNAME; | ||||
|         } | ||||
|  | ||||
|         this.emit('reset', beginRequestType); | ||||
|     }, | ||||
|  | ||||
|     addCharacter: function(unichar) { | ||||
|         if (!this._entry.visible) | ||||
|             return; | ||||
|  | ||||
|         this._entry.grab_key_focus(); | ||||
|         this._entry.clutter_text.insert_unichar(unichar); | ||||
|     }, | ||||
|  | ||||
|     begin: function(params) { | ||||
|         params = Params.parse(params, { userName: null, | ||||
|                                         hold: null }); | ||||
|  | ||||
|         this.updateSensitivity(false); | ||||
|  | ||||
|         let hold = params.hold; | ||||
|         if (!hold) | ||||
|             hold = new Batch.Hold(); | ||||
|  | ||||
|         this._userVerifier.begin(params.userName, hold); | ||||
|         this.verificationStatus = AuthPromptStatus.VERIFYING; | ||||
|     }, | ||||
|  | ||||
|     finish: function(onComplete) { | ||||
|         if (!this._userVerifier.hasPendingMessages) { | ||||
|             onComplete(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let signalId = this._userVerifier.connect('no-more-messages', | ||||
|                                                   Lang.bind(this, function() { | ||||
|                                                       this._userVerifier.disconnect(signalId); | ||||
|                                                       onComplete(); | ||||
|                                                   })); | ||||
|     }, | ||||
|  | ||||
|     cancel: function() { | ||||
|         this.reset(); | ||||
|         this.emit('cancelled'); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(AuthPrompt.prototype); | ||||
| @@ -1,62 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const OVirtCredentialsIface = <interface name='org.ovirt.vdsm.Credentials'> | ||||
| <signal name="UserAuthenticated"> | ||||
|     <arg type="s" name="token"/> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const OVirtCredentialsInfo = Gio.DBusInterfaceInfo.new_for_xml(OVirtCredentialsIface); | ||||
|  | ||||
| let _oVirtCredentialsManager = null; | ||||
|  | ||||
| function OVirtCredentials() { | ||||
|     var self = new Gio.DBusProxy({ g_connection: Gio.DBus.system, | ||||
|                                    g_interface_name: OVirtCredentialsInfo.name, | ||||
|                                    g_interface_info: OVirtCredentialsInfo, | ||||
|                                    g_name: 'org.ovirt.vdsm.Credentials', | ||||
|                                    g_object_path: '/org/ovirt/vdsm/Credentials', | ||||
|                                    g_flags: (Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) }); | ||||
|     self.init(null); | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| const OVirtCredentialsManager = new Lang.Class({ | ||||
|     Name: 'OVirtCredentialsManager', | ||||
|     _init: function() { | ||||
|         this._token = null; | ||||
|  | ||||
|         this._credentials = new OVirtCredentials(); | ||||
|         this._credentials.connectSignal('UserAuthenticated', | ||||
|                                         Lang.bind(this, this._onUserAuthenticated)); | ||||
|     }, | ||||
|  | ||||
|     _onUserAuthenticated: function(proxy, sender, [token]) { | ||||
|         this._token = token; | ||||
|         this.emit('user-authenticated', token); | ||||
|     }, | ||||
|  | ||||
|     hasToken: function() { | ||||
|         return this._token != null; | ||||
|     }, | ||||
|  | ||||
|     getToken: function() { | ||||
|         return this._token; | ||||
|     }, | ||||
|  | ||||
|     resetToken: function() { | ||||
|         this._token = null; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(OVirtCredentialsManager.prototype); | ||||
|  | ||||
| function getOVirtCredentialsManager() { | ||||
|     if (!_oVirtCredentialsManager) | ||||
|         _oVirtCredentialsManager = new OVirtCredentialsManager(); | ||||
|  | ||||
|     return _oVirtCredentialsManager; | ||||
| } | ||||
							
								
								
									
										129
									
								
								js/gdm/powerMenu.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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(); | ||||
|     } | ||||
| }); | ||||
| @@ -63,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(); | ||||
| @@ -106,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(); | ||||
|                                 })); | ||||
|     }, | ||||
|  | ||||
| @@ -134,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) | ||||
|   | ||||
							
								
								
									
										371
									
								
								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,124 +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(); | ||||
|                                                        })); | ||||
|     }, | ||||
|  | ||||
|     _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); | ||||
|     }, | ||||
|  | ||||
| @@ -339,7 +200,6 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.reauthenticating = true; | ||||
|         this._connectSignals(); | ||||
|         this._beginVerification(); | ||||
|         this._hold.release(); | ||||
| @@ -370,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'); | ||||
|     }, | ||||
| @@ -491,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 | ||||
| @@ -511,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); | ||||
|   | ||||
| @@ -117,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) | ||||
|   | ||||
| @@ -58,7 +58,6 @@ const Map = new Lang.Class({ | ||||
|  | ||||
|     _init: function(iterable) { | ||||
|         this._pool = { }; | ||||
|         this._size = 0; | ||||
|  | ||||
|         if (iterable) { | ||||
|             for (let i = 0; i < iterable.length; i++) { | ||||
| @@ -100,7 +99,6 @@ const Map = new Lang.Class({ | ||||
|             node.value = value; | ||||
|         } else { | ||||
|             this._pool[hash] = { key: key, value: value }; | ||||
|             this._size++; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -110,7 +108,6 @@ const Map = new Lang.Class({ | ||||
|  | ||||
|         if (node && _sameValue(node.key, key)) { | ||||
|             delete this._pool[hash]; | ||||
|             this._size--; | ||||
|             return [node.key, node.value]; | ||||
|         } else { | ||||
|             return [null, null]; | ||||
| @@ -139,6 +136,6 @@ const Map = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     size: function() { | ||||
|         return this._size; | ||||
|         return Object.getOwnPropertyNames(this._pool).length; | ||||
|     }, | ||||
| }); | ||||
|   | ||||
| @@ -8,9 +8,21 @@ const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| 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> | ||||
| @@ -21,10 +33,6 @@ const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager | ||||
|     <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> | ||||
| @@ -64,38 +72,7 @@ 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; | ||||
| @@ -130,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) { | ||||
| @@ -167,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); | ||||
|     }, | ||||
| @@ -209,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) { | ||||
| @@ -236,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); | ||||
|   | ||||
| @@ -1,257 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Params = imports.misc.params; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| // Specified in the D-Bus specification here: | ||||
| // http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager | ||||
| const ObjectManagerIface = <interface name="org.freedesktop.DBus.ObjectManager"> | ||||
|   <method name="GetManagedObjects"> | ||||
|     <arg name="objects" type="a{oa{sa{sv}}}" direction="out"/> | ||||
|   </method> | ||||
|   <signal name="InterfacesAdded"> | ||||
|     <arg name="objectPath" type="o"/> | ||||
|     <arg name="interfaces" type="a{sa{sv}}" /> | ||||
|   </signal> | ||||
|   <signal name="InterfacesRemoved"> | ||||
|     <arg name="objectPath" type="o"/> | ||||
|     <arg name="interfaces" type="as" /> | ||||
|   </signal> | ||||
| </interface>; | ||||
|  | ||||
| const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface); | ||||
|  | ||||
| const ObjectManager = new Lang.Class({ | ||||
|     Name: 'ObjectManager', | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { connection: null, | ||||
|                                         name: null, | ||||
|                                         objectPath: null, | ||||
|                                         knownInterfaces: null, | ||||
|                                         cancellable: null, | ||||
|                                         onLoaded: null }); | ||||
|  | ||||
|         this._connection = params.connection; | ||||
|         this._serviceName = params.name; | ||||
|         this._managerPath = params.objectPath; | ||||
|         this._cancellable = params.cancellable; | ||||
|  | ||||
|         this._managerProxy = new Gio.DBusProxy({ g_connection: this._connection, | ||||
|                                                  g_interface_name: ObjectManagerInfo.name, | ||||
|                                                  g_interface_info: ObjectManagerInfo, | ||||
|                                                  g_name: this._serviceName, | ||||
|                                                  g_object_path: this._managerPath, | ||||
|                                                  g_flags: Gio.DBusProxyFlags.NONE }); | ||||
|  | ||||
|         this._interfaceInfos = {}; | ||||
|         this._objects = {}; | ||||
|         this._interfaces = {}; | ||||
|         this._onLoaded = params.onLoaded; | ||||
|  | ||||
|         if (params.knownInterfaces) | ||||
|             this._registerInterfaces(params.knownInterfaces); | ||||
|  | ||||
|         // Start out inhibiting load until at least the proxy | ||||
|         // manager is loaded and the remote objects are fetched | ||||
|         this._numLoadInhibitors = 1; | ||||
|         this._managerProxy.init_async(GLib.PRIORITY_DEFAULT, | ||||
|                                       this._cancellable, | ||||
|                                       Lang.bind(this, this._onManagerProxyLoaded)); | ||||
|     }, | ||||
|  | ||||
|     _tryToCompleteLoad: function() { | ||||
|         this._numLoadInhibitors--; | ||||
|         if (this._numLoadInhibitors == 0) { | ||||
|             if (this._onLoaded) | ||||
|                 this._onLoaded(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _addInterface: function(objectPath, interfaceName, onFinished) { | ||||
|         let info = this._interfaceInfos[interfaceName]; | ||||
|  | ||||
|         if (!info) { | ||||
|            if (onFinished) | ||||
|                onFinished(); | ||||
|            return; | ||||
|         } | ||||
|  | ||||
|         let proxy = new Gio.DBusProxy({ g_connection: this._connection, | ||||
|                                        g_name: this._serviceName, | ||||
|                                        g_object_path: objectPath, | ||||
|                                        g_interface_name: interfaceName, | ||||
|                                        g_interface_info: info, | ||||
|                                        g_flags: Gio.DBusProxyFlags.NONE }); | ||||
|  | ||||
|         proxy.init_async(GLib.PRIORITY_DEFAULT, | ||||
|                          this._cancellable, | ||||
|                          Lang.bind(this, function(initable, result) { | ||||
|                let error = null; | ||||
|                try { | ||||
|                    initable.init_finish(result); | ||||
|                } catch(e) { | ||||
|                    logError(e, 'could not initialize proxy for interface ' + interfaceName); | ||||
|  | ||||
|                    if (onFinished) | ||||
|                        onFinished(); | ||||
|                    return; | ||||
|                } | ||||
|  | ||||
|                let isNewObject; | ||||
|                if (!this._objects[objectPath]) { | ||||
|                    this._objects[objectPath] = {}; | ||||
|                    isNewObject = true; | ||||
|                } else { | ||||
|                    isNewObject = false; | ||||
|                } | ||||
|  | ||||
|                this._objects[objectPath][interfaceName] = proxy; | ||||
|  | ||||
|                if (!this._interfaces[interfaceName]) | ||||
|                    this._interfaces[interfaceName] = []; | ||||
|  | ||||
|                this._interfaces[interfaceName].push(proxy); | ||||
|  | ||||
|                if (isNewObject) | ||||
|                    this.emit('object-added', objectPath); | ||||
|  | ||||
|                this.emit('interface-added', interfaceName, proxy); | ||||
|  | ||||
|                if (onFinished) | ||||
|                    onFinished(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _removeInterface: function(objectPath, interfaceName) { | ||||
|         if (!this._objects[objectPath]) | ||||
|             return; | ||||
|  | ||||
|         let proxy = this._objects[objectPath][interfaceName]; | ||||
|  | ||||
|         if (this._interfaces[interfaceName]) { | ||||
|             let index = this._interfaces[interfaceName].indexOf(proxy); | ||||
|  | ||||
|             if (index >= 0) | ||||
|                 this._interfaces[interfaceName].splice(index, 1); | ||||
|  | ||||
|             if (this._interfaces[interfaceName].length == 0) | ||||
|                 delete this._interfaces[interfaceName]; | ||||
|         } | ||||
|  | ||||
|         this.emit('interface-removed', interfaceName, proxy); | ||||
|  | ||||
|         this._objects[objectPath][interfaceName] = null; | ||||
|  | ||||
|         if (Object.keys(this._objects[objectPath]).length == 0) { | ||||
|             delete this._objects[objectPath]; | ||||
|             this.emit('object-removed', objectPath); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onManagerProxyLoaded: function(initable, result) { | ||||
|         let error = null; | ||||
|         try { | ||||
|             initable.init_finish(result); | ||||
|         } catch(e) { | ||||
|             logError(e, 'could not initialize object manager for object ' + params.name); | ||||
|  | ||||
|             this._tryToCompleteLoad(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._managerProxy.connectSignal('InterfacesAdded', | ||||
|                                          Lang.bind(this, function(objectManager, sender, [objectPath, interfaces]) { | ||||
|                                              let interfaceNames = Object.keys(interfaces); | ||||
|                                              for (let i = 0; i < interfaceNames.length; i++) | ||||
|                                                  this._addInterface(objectPath, interfaceNames[i]); | ||||
|                                          })); | ||||
|         this._managerProxy.connectSignal('InterfacesRemoved', | ||||
|                                          Lang.bind(this, function(objectManager, sender, [objectPath, interfaceNames]) { | ||||
|                                              for (let i = 0; i < interfaceNames.length; i++) | ||||
|                                                  this._removeInterface(objectPath, interfaceNames[i]); | ||||
|                                          })); | ||||
|  | ||||
|         if (Object.keys(this._interfaceInfos).length == 0) { | ||||
|             this._tryToCompleteLoad(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._managerProxy.GetManagedObjectsRemote(Lang.bind(this, function(result, error) { | ||||
|             if (!result) { | ||||
|                 if (error) { | ||||
|                    logError(error, 'could not get remote objects for service ' + this._serviceName + ' path ' + this._managerPath); | ||||
|                 } | ||||
|  | ||||
|                 this._tryToCompleteLoad(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let [objects] = result; | ||||
|  | ||||
|             let objectPaths = Object.keys(objects); | ||||
|             for (let i = 0; i < objectPaths.length; i++) { | ||||
|                 let objectPath = objectPaths[i]; | ||||
|                 let object = objects[objectPath]; | ||||
|  | ||||
|                 let interfaceNames = Object.getOwnPropertyNames(object); | ||||
|                 for (let j = 0; j < interfaceNames.length; j++) { | ||||
|                     let interfaceName = interfaceNames[j]; | ||||
|  | ||||
|                     // Prevent load from completing until the interface is loaded | ||||
|                     this._numLoadInhibitors++; | ||||
|                     this._addInterface(objectPath, | ||||
|                                        interfaceName, | ||||
|                                        Lang.bind(this, this._tryToCompleteLoad)); | ||||
|                 } | ||||
|             } | ||||
|             this._tryToCompleteLoad(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _registerInterfaces: function(interfaces) { | ||||
|         for (let i = 0; i < interfaces.length; i++) { | ||||
|             let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]); | ||||
|             this._interfaceInfos[info.name] = info; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     getProxy: function(objectPath, interfaceName) { | ||||
|         let object = this._objects[objectPath]; | ||||
|  | ||||
|         if (!object) | ||||
|             return null; | ||||
|  | ||||
|         return object[interfaceName]; | ||||
|     }, | ||||
|  | ||||
|     getProxiesForInterface: function(interfaceName) { | ||||
|         let proxyList = this._interfaces[interfaceName]; | ||||
|  | ||||
|         if (!proxyList) | ||||
|             return []; | ||||
|  | ||||
|         return proxyList; | ||||
|     }, | ||||
|  | ||||
|     getAllProxies: function() { | ||||
|         let proxies = []; | ||||
|  | ||||
|         let objectPaths = Object.keys(this._objects); | ||||
|         for (let i = 0; i < objectPaths.length; i++) { | ||||
|             let object = this._objects[objectPaths]; | ||||
|  | ||||
|             let interfaceNames = Object.keys(object); | ||||
|             for (let j = 0; i < interfaceNames.length; i++) { | ||||
|                 let interfaceName = interfaceNames[i]; | ||||
|                 if (object[interfaceName]) | ||||
|                     proxies.push(object(interfaceName)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return proxies; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ObjectManager.prototype); | ||||
| @@ -1,117 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const ObjectManager = imports.misc.objectManager; | ||||
|  | ||||
| const SmartcardTokenIface = <interface name="org.gnome.SettingsDaemon.Smartcard.Token"> | ||||
|   <property name="Name" type="s" access="read"/> | ||||
|   <property name="Driver" type="o" access="read"/> | ||||
|   <property name="IsInserted" type="b" access="read"/> | ||||
|   <property name="UsedToLogin" type="b" access="read"/> | ||||
| </interface>; | ||||
|  | ||||
| let _smartcardManager = null; | ||||
|  | ||||
| function getSmartcardManager() { | ||||
|     if (_smartcardManager == null) | ||||
|         _smartcardManager = new SmartcardManager(); | ||||
|  | ||||
|     return _smartcardManager; | ||||
| } | ||||
|  | ||||
| const SmartcardManager = new Lang.Class({ | ||||
|     Name: 'SmartcardManager', | ||||
|     _init: function() { | ||||
|         this._objectManager = new ObjectManager.ObjectManager({ connection: Gio.DBus.session, | ||||
|                                                                 name: "org.gnome.SettingsDaemon.Smartcard", | ||||
|                                                                 objectPath: '/org/gnome/SettingsDaemon/Smartcard', | ||||
|                                                                 knownInterfaces: [ SmartcardTokenIface ], | ||||
|                                                                 onLoaded: Lang.bind(this, this._onLoaded) }); | ||||
|         this._insertedTokens = {}; | ||||
|         this._loginToken = null; | ||||
|     }, | ||||
|  | ||||
|     _onLoaded: function() { | ||||
|         let tokens = this._objectManager.getProxiesForInterface('org.gnome.SettingsDaemon.Smartcard.Token'); | ||||
|  | ||||
|         for (let i = 0; i < tokens.length; i++) | ||||
|             this._addToken(tokens[i]); | ||||
|  | ||||
|         this._objectManager.connect('interface-added', Lang.bind(this, function(objectManager, interfaceName, proxy) { | ||||
|             if (interfaceName == 'org.gnome.SettingsDaemon.Smartcard.Token') | ||||
|                 this._addToken(proxy); | ||||
|         })); | ||||
|  | ||||
|         this._objectManager.connect('interface-removed', Lang.bind(this, function(objectManager, interfaceName, proxy) { | ||||
|             if (interfaceName == 'org.gnome.SettingsDaemon.Smartcard.Token') | ||||
|                 this._removeToken(proxy); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _updateToken: function(token) { | ||||
|         let objectPath = token.get_object_path(); | ||||
|  | ||||
|         delete this._insertedTokens[objectPath]; | ||||
|  | ||||
|         if (token.IsInserted) | ||||
|             this._insertedTokens[objectPath] = token; | ||||
|  | ||||
|         if (token.UsedToLogin) | ||||
|             this._loginToken = token; | ||||
|     }, | ||||
|  | ||||
|     _addToken: function(token) { | ||||
|         this._updateToken(token); | ||||
|  | ||||
|         token.connect('g-properties-changed', | ||||
|                       Lang.bind(this, function(proxy, properties) { | ||||
|                           if ('IsInserted' in properties.deep_unpack()) { | ||||
|                               this._updateToken(token); | ||||
|  | ||||
|                               if (token.IsInserted) { | ||||
|                                   this.emit('smartcard-inserted', token); | ||||
|                               } else { | ||||
|                                   this.emit('smartcard-removed', token); | ||||
|                               } | ||||
|                           } | ||||
|                       })); | ||||
|  | ||||
|         // Emit a smartcard-inserted at startup if it's already plugged in | ||||
|         if (token.IsInserted) | ||||
|             this.emit('smartcard-inserted', token); | ||||
|     }, | ||||
|  | ||||
|     _removeToken: function(token) { | ||||
|         let objectPath = token.get_object_path(); | ||||
|  | ||||
|         if (this._insertedTokens[objectPath] == token) { | ||||
|             delete this._insertedTokens[objectPath]; | ||||
|             this.emit('smartcard-removed', token); | ||||
|         } | ||||
|  | ||||
|         if (this._loginToken == token) | ||||
|             this._loginToken = null; | ||||
|  | ||||
|         token.disconnectAll(); | ||||
|     }, | ||||
|  | ||||
|     hasInsertedTokens: function() { | ||||
|         return Object.keys(this._insertedTokens).length > 0; | ||||
|     }, | ||||
|  | ||||
|     hasInsertedLoginToken: function() { | ||||
|         if (!this._loginToken) | ||||
|             return false; | ||||
|  | ||||
|         if (!this._loginToken.IsInserted) | ||||
|             return false; | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| }); | ||||
| Signals.addSignalMethods(SmartcardManager.prototype); | ||||
							
								
								
									
										149
									
								
								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(); | ||||
|         app.launch([], context); | ||||
|     } catch(err) { | ||||
|         _handleSpawnError(argv[0], err); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // trySpawn: | ||||
| // @argv: an argv array | ||||
| // | ||||
| @@ -157,6 +136,31 @@ function _handleSpawnError(command, err) { | ||||
|     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; | ||||
| } | ||||
|   | ||||
| @@ -232,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() { | ||||
| @@ -355,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); | ||||
|     }, | ||||
|  | ||||
| @@ -371,8 +366,7 @@ 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; | ||||
|  | ||||
|         return true; | ||||
| @@ -401,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); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -440,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++) { | ||||
| @@ -454,9 +445,7 @@ const AppSwitcher = new Lang.Class({ | ||||
|             appIcon.cachedWindows = allWindows.filter(function(w) { | ||||
|                 return windowTracker.get_window_app (w) == appIcon.app; | ||||
|             }); | ||||
|             if (workspace == null || appIcon.cachedWindows.length > 0) { | ||||
|                 this._addIcon(appIcon); | ||||
|             } | ||||
|             this._addIcon(appIcon); | ||||
|         } | ||||
|  | ||||
|         this._curApp = -1; | ||||
| @@ -667,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', | ||||
| @@ -685,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)); | ||||
| @@ -723,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, | ||||
| @@ -735,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,84 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const St = imports.gi.St; | ||||
| const Signals = imports.signals; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
| const ANIMATED_ICON_UPDATE_TIMEOUT = 100; | ||||
|  | ||||
| const Animation = new Lang.Class({ | ||||
|     Name: 'Animation', | ||||
|  | ||||
|     _init: function(filename, width, height, speed) { | ||||
|         this.actor = new St.Bin(); | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|         this._speed = speed; | ||||
|  | ||||
|         this._isLoaded = false; | ||||
|         this._isPlaying = false; | ||||
|         this._timeoutId = 0; | ||||
|         this._frame = 0; | ||||
|         this._animations = St.TextureCache.get_default().load_sliced_image (filename, width, height, | ||||
|                                                                             Lang.bind(this, this._animationsLoaded)); | ||||
|         this.actor.set_child(this._animations); | ||||
|     }, | ||||
|  | ||||
|     play: function() { | ||||
|         if (this._isLoaded && this._timeoutId == 0) { | ||||
|             if (this._frame == 0) | ||||
|                 this._showFrame(0); | ||||
|  | ||||
|             this._timeoutId = Mainloop.timeout_add(this._speed, Lang.bind(this, this._update)); | ||||
|         } | ||||
|  | ||||
|         this._isPlaying = true; | ||||
|     }, | ||||
|  | ||||
|     stop: function() { | ||||
|         if (this._timeoutId > 0) { | ||||
|             Mainloop.source_remove(this._timeoutId); | ||||
|             this._timeoutId = 0; | ||||
|         } | ||||
|  | ||||
|         this._isPlaying = false; | ||||
|     }, | ||||
|  | ||||
|     _showFrame: function(frame) { | ||||
|         let oldFrameActor = this._animations.get_child_at_index(this._frame); | ||||
|         if (oldFrameActor) | ||||
|             oldFrameActor.hide(); | ||||
|  | ||||
|         this._frame = (frame % this._animations.get_n_children()); | ||||
|  | ||||
|         let newFrameActor = this._animations.get_child_at_index(this._frame); | ||||
|         if (newFrameActor) | ||||
|             newFrameActor.show(); | ||||
|     }, | ||||
|  | ||||
|     _update: function() { | ||||
|         this._showFrame(this._frame + 1); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _animationsLoaded: function() { | ||||
|         this._isLoaded = true; | ||||
|  | ||||
|         if (this._isPlaying) | ||||
|             this.play(); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy: function() { | ||||
|         this.stop(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const AnimatedIcon = new Lang.Class({ | ||||
|     Name: 'AnimatedIcon', | ||||
|     Extends: Animation, | ||||
|  | ||||
|     _init: function(filename, size) { | ||||
|         this.parent(filename, size, size, ANIMATED_ICON_UPDATE_TIMEOUT); | ||||
|     } | ||||
| }); | ||||
| @@ -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 = {}; | ||||
|     }, | ||||
|  | ||||
| @@ -85,9 +84,9 @@ 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; | ||||
|     }, | ||||
| @@ -126,81 +125,9 @@ const BackgroundCache = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     removeImageContent: function(content) { | ||||
|         let filename = content.get_filename(); | ||||
|  | ||||
|         if (filename && this._fileMonitors[filename]) | ||||
|             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) { | ||||
|                                                           if (content && pendingLoad.callers[j].shouldCopy) { | ||||
|                                                               content = object.copy(pendingLoad.callers[j].monitorIndex, | ||||
|                                                                                     pendingLoad.callers[j].effects); | ||||
|  | ||||
|                                                           } | ||||
|  | ||||
|                                                           pendingLoad.callers[j].onFinished(content); | ||||
|                                                       } | ||||
|                                                   } | ||||
|  | ||||
|                                                   this._pendingFileLoads.splice(i, 1); | ||||
|                                               } | ||||
|                                           })); | ||||
|     }, | ||||
|  | ||||
|     getImageContent: function(params) { | ||||
|         params = Params.parse(params, { monitorIndex: 0, | ||||
|                                         style: null, | ||||
| @@ -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); | ||||
|                                               })); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -295,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; | ||||
| @@ -319,9 +257,9 @@ const Background = new Lang.Class({ | ||||
|         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(); | ||||
|     }, | ||||
| @@ -329,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; | ||||
| @@ -362,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() { | ||||
| @@ -422,10 +356,7 @@ const Background = new Lang.Class({ | ||||
|  | ||||
|         let actor = new Meta.BackgroundActor(); | ||||
|         actor.content = content; | ||||
|  | ||||
|         // 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.actor.add_child(actor); | ||||
|  | ||||
|         this._images[index] = actor; | ||||
|         this._watchCacheFile(filename); | ||||
| @@ -436,27 +367,27 @@ const Background = new Lang.Class({ | ||||
|         content.brightness = this._brightness; | ||||
|         content.vignette_sharpness = this._vignetteSharpness; | ||||
|  | ||||
|         this._cache.removeImageContent(this._images[index].content); | ||||
|         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; | ||||
|         } | ||||
|  | ||||
| @@ -475,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) { | ||||
| @@ -495,34 +426,27 @@ const Background = new Lang.Class({ | ||||
|                                                   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 false; | ||||
|                                                                 })); | ||||
| @@ -576,16 +500,7 @@ 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); | ||||
|     }, | ||||
| @@ -670,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; | ||||
|     }, | ||||
|  | ||||
| @@ -684,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); | ||||
| @@ -721,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; | ||||
| @@ -733,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; | ||||
| @@ -754,28 +677,22 @@ const BackgroundManager = new Lang.Class({ | ||||
|         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; | ||||
|                 newBackground.disconnect(signalId); | ||||
|                 Tweener.addTween(background.actor, | ||||
|                                  { opacity: 0, | ||||
|                                    time: FADE_ANIMATION_TIME, | ||||
|                                    transition: 'easeOutQuad', | ||||
|                                    onComplete: Lang.bind(this, function() { | ||||
|                                        if (this._newBackground == newBackground) { | ||||
|                                            this.background = newBackground; | ||||
|                                            this._newBackground = null; | ||||
|                                        } else { | ||||
|                                            newBackground.actor.destroy(); | ||||
|                                        } | ||||
|  | ||||
|                                        this.background = newBackground; | ||||
|                                        this._newBackground = null; | ||||
|                                        background.actor.destroy(); | ||||
|  | ||||
|                                        this.emit('changed'); | ||||
|                                    }) | ||||
|                                  }); | ||||
|         })); | ||||
|         this._loadedSignalId = signalId; | ||||
|  | ||||
|         this._newBackground = newBackground; | ||||
|     }, | ||||
| @@ -783,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]; | ||||
| @@ -795,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; | ||||
|         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; | ||||
|     }, | ||||
|   | ||||
| @@ -46,10 +46,8 @@ function addBackgroundMenu(actor) { | ||||
|     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) { | ||||
| @@ -57,12 +55,4 @@ function addBackgroundMenu(actor) { | ||||
|             openMenu(); | ||||
|     }); | ||||
|     actor.add_action(clickAction); | ||||
|  | ||||
|     actor.connect('destroy', function() { | ||||
|                       actor._backgroundMenu.destroy(); | ||||
|                       actor._backgroundMenu = null; | ||||
|                       actor._backgroundManager = null; | ||||
|  | ||||
|                       cursor.destroy(); | ||||
|                   }); | ||||
| } | ||||
|   | ||||
| @@ -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,10 +61,6 @@ 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', | ||||
| @@ -185,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); | ||||
| @@ -596,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; | ||||
| @@ -619,8 +612,6 @@ const BoxPointer = new Lang.Class({ | ||||
|                 this._container.queue_relayout(); | ||||
|                 return false; | ||||
|             })); | ||||
|  | ||||
|             this.emit('arrow-side-changed'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -648,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); | ||||
|   | ||||
| @@ -71,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")); | ||||
| @@ -168,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) { | ||||
| @@ -197,7 +191,6 @@ const CalendarServerIface = <interface name="org.gnome.Shell.CalendarServer"> | ||||
|     <arg type="b" direction="in" /> | ||||
|     <arg type="a(sssbxxa{sv})" direction="out" /> | ||||
| </method> | ||||
| <property name="HasCalendars" type="b" access="read" /> | ||||
| <signal name="Changed" /> | ||||
| </interface>; | ||||
|  | ||||
| @@ -208,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) { | ||||
| @@ -235,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(); | ||||
| @@ -257,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; | ||||
| @@ -317,7 +293,6 @@ const DBusEventSource = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         this._events = newEvents; | ||||
|         this.isLoading = false; | ||||
|         this.emit('changed'); | ||||
|     }, | ||||
|  | ||||
| @@ -340,7 +315,6 @@ const DBusEventSource = new Lang.Class({ | ||||
|  | ||||
|     requestRange: function(begin, end, forceReload) { | ||||
|         if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) { | ||||
|             this.isLoading = true; | ||||
|             this._lastRequestBegin = begin; | ||||
|             this._lastRequestEnd = end; | ||||
|             this._curRequestBegin = begin; | ||||
| @@ -415,11 +389,19 @@ 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(false); | ||||
|         })); | ||||
|         this._update(true); | ||||
|  | ||||
|         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 | ||||
| @@ -443,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... | ||||
|         // | ||||
| @@ -516,12 +493,10 @@ const Calendar = new Lang.Class({ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._backButton.grab_key_focus(); | ||||
|  | ||||
|         this.setDate(newDate, false); | ||||
|     }, | ||||
|    }, | ||||
|  | ||||
|     _onNextMonthButtonClicked: function() { | ||||
|    _onNextMonthButtonClicked: function() { | ||||
|         let newDate = new Date(this._selectedDate); | ||||
|         let oldMonth = newDate.getMonth(); | ||||
|         if (oldMonth == 11) { | ||||
| @@ -540,9 +515,7 @@ const Calendar = new Lang.Class({ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._forwardButton.grab_key_focus(); | ||||
|  | ||||
|         this.setDate(newDate, false); | ||||
|        this.setDate(newDate, false); | ||||
|     }, | ||||
|  | ||||
|     _onSettingsChange: function() { | ||||
| @@ -565,60 +538,30 @@ const Calendar = new Lang.Class({ | ||||
|             children[i].destroy(); | ||||
|  | ||||
|         // 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); | ||||
|         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; | ||||
|  | ||||
|             let iterStr = iter.toUTCString(); | ||||
|             button.connect('clicked', Lang.bind(this, function() { | ||||
|                 this._shouldDateGrabFocus = true; | ||||
|  | ||||
|                 let newlySelectedDate = new Date(iterStr); | ||||
|                 this.setDate(newlySelectedDate, false); | ||||
|  | ||||
|                 this._shouldDateGrabFocus = 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' | ||||
|             else | ||||
| @@ -638,6 +581,9 @@ 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' | ||||
|  | ||||
| @@ -647,13 +593,6 @@ const Calendar = new Lang.Class({ | ||||
|             this.actor.add(button, | ||||
|                            { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 }); | ||||
|  | ||||
|             if (_sameDay(this._selectedDate, iter)) { | ||||
|                 button.add_style_pseudo_class('active'); | ||||
|  | ||||
|                 if (this._shouldDateGrabFocus) | ||||
|                     button.grab_key_focus(); | ||||
|             } | ||||
|  | ||||
|             if (this._useWeekdate && iter.getDay() == 4) { | ||||
|                 let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(), | ||||
|                                            style_class: 'calendar-day-base calendar-week-number'}); | ||||
| @@ -662,13 +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, forceReload); | ||||
|         if (this._eventSource) | ||||
|             this._eventSource.requestRange(beginDate, iter, forceReload); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -678,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)); | ||||
| @@ -686,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) { | ||||
| @@ -772,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; | ||||
|  | ||||
| @@ -797,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* | ||||
| @@ -805,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); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -818,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; | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -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; | ||||
| @@ -292,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 }); | ||||
| @@ -61,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; | ||||
| @@ -106,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; | ||||
| @@ -128,14 +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); | ||||
|         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); | ||||
|  | ||||
| @@ -149,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() { | ||||
| @@ -221,56 +207,27 @@ const KeyringDialog = new Lang.Class({ | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const KeyringDummyDialog = new Lang.Class({ | ||||
|     Name: 'KeyringDummyDialog', | ||||
|  | ||||
|     _init: function() { | ||||
|         this.prompt = new Shell.KeyringPrompt(); | ||||
|         this.prompt.connect('show-password', | ||||
|                             Lang.bind(this, this._cancelPrompt)); | ||||
|         this.prompt.connect('show-confirm', Lang.bind(this, | ||||
|                             this._cancelPrompt)); | ||||
|     }, | ||||
|  | ||||
|     _cancelPrompt: function() { | ||||
|         this.prompt.cancel(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const KeyringPrompter = new Lang.Class({ | ||||
|     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) | ||||
| @@ -384,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) | ||||
|   | ||||
| @@ -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 }); | ||||
| @@ -100,9 +101,9 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|             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, | ||||
| @@ -160,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(); | ||||
|     }, | ||||
|  | ||||
| @@ -207,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); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -224,7 +219,6 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|  | ||||
|         this._okButton.can_focus = sensitive; | ||||
|         this._okButton.reactive = sensitive; | ||||
|         this.setWorking(!sensitive); | ||||
|     }, | ||||
|  | ||||
|     _onEntryActivate: function() { | ||||
| @@ -243,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), | ||||
| @@ -268,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) { | ||||
| @@ -315,7 +303,6 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         if (this._session) { | ||||
|             if (!this._completed) | ||||
|                 this._session.cancel(); | ||||
|             this._completed = false; | ||||
|             this._session = null; | ||||
|         } | ||||
|     }, | ||||
| @@ -330,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); | ||||
| @@ -340,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)); | ||||
| @@ -376,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
									
								
							
							
						
						| @@ -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 () { | ||||
| @@ -487,7 +488,7 @@ const ChatSource = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationApplicationPolicy('empathy'); | ||||
|         return new NotificationDaemon.NotificationApplicationPolicy('empathy'); | ||||
|     }, | ||||
|  | ||||
|     _updateAlias: function() { | ||||
| @@ -966,8 +967,7 @@ const ChatNotification = new Lang.Class({ | ||||
|         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 }); | ||||
|  | ||||
| @@ -1060,7 +1060,7 @@ const ApproverSource = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationApplicationPolicy('empathy'); | ||||
|         return new NotificationDaemon.NotificationApplicationPolicy('empathy'); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
| @@ -1134,8 +1134,6 @@ const AudioVideoNotification = new Lang.Class({ | ||||
|         this.parent(source, title, null, { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this.setUrgency(MessageTray.Urgency.CRITICAL); | ||||
|  | ||||
|         this.addButton('reject', _("Decline")); | ||||
|         /* translators: this is a button label (verb), not a noun */ | ||||
|         this.addButton('answer', _("Answer")); | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -287,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; | ||||
| @@ -295,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(); | ||||
| @@ -320,16 +326,6 @@ const DragPlaceholderItem = new Lang.Class({ | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const EmptyDropTargetItem = new Lang.Class({ | ||||
|     Name: 'EmptyDropTargetItem', | ||||
|     Extends: DashItemContainer, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|         this.setChild(new St.Bin({ style_class: 'empty-dash-drop-target' })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const DashActor = new Lang.Class({ | ||||
|     Name: 'DashActor', | ||||
|     Extends: St.Widget, | ||||
| @@ -423,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)); | ||||
|  | ||||
| @@ -448,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() { | ||||
| @@ -470,7 +457,6 @@ const Dash = new Lang.Class({ | ||||
|  | ||||
|     _endDrag: function() { | ||||
|         this._clearDragPlaceholder(); | ||||
|         this._clearEmptyDropTarget(); | ||||
|         this._showAppsIcon.setDragApp(null); | ||||
|         DND.removeDragMonitor(this._dragMonitor); | ||||
|     }, | ||||
| @@ -505,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) { | ||||
| @@ -548,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; | ||||
|     }, | ||||
| @@ -566,10 +546,8 @@ 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, | ||||
| @@ -819,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; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -861,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; | ||||
|             } | ||||
|  | ||||
| @@ -897,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,7 +116,12 @@ 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) { | ||||
| @@ -143,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() { | ||||
| @@ -184,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 | ||||
| @@ -204,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'); | ||||
|         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(); | ||||
|     } | ||||
| }); | ||||
|   | ||||
							
								
								
									
										197
									
								
								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,7 +102,12 @@ 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; | ||||
|     }, | ||||
| @@ -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; | ||||
|         } | ||||
|     }, | ||||
| @@ -186,6 +194,11 @@ const _Draggable = new Lang.Class({ | ||||
|                 this._cancelDrag(event.get_time()); | ||||
|                 return true; | ||||
|             } | ||||
|         } else if (event.type() == Clutter.EventType.LEAVE) { | ||||
|             if (this._firstLeaveActor == null) | ||||
|                 this._firstLeaveActor = event.get_source(); | ||||
|         } else if (event.type() == Clutter.EventType.ENTER) { | ||||
|             this._lastEnterActor = event.get_source(); | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
| @@ -229,7 +242,7 @@ 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; | ||||
| @@ -275,19 +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._dragActor.reparent(Main.uiGroup); | ||||
|             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; | ||||
| @@ -344,65 +357,60 @@ const _Draggable = new Lang.Class({ | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _updateDragHover : function () { | ||||
|         let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL, | ||||
|                                                                   this._dragX, this._dragY); | ||||
|         let dragEvent = { | ||||
|             x: this._dragX, | ||||
|             y: this._dragY, | ||||
|             dragActor: this._dragActor, | ||||
|             source: this.actor._delegate, | ||||
|             targetActor: target | ||||
|         }; | ||||
|         for (let i = 0; i < dragMonitors.length; i++) { | ||||
|             let motionFunc = dragMonitors[i].dragMotion; | ||||
|             if (motionFunc) { | ||||
|                 let result = motionFunc(dragEvent); | ||||
|                 if (result != DragMotionResult.CONTINUE) { | ||||
|                     global.screen.set_cursor(DRAG_CURSOR_MAP[result]); | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         while (target) { | ||||
|             if (target._delegate && target._delegate.handleDragOver) { | ||||
|                 let [r, targX, targY] = target.transform_stage_point(this._dragX, this._dragY); | ||||
|                 // We currently loop through all parents on drag-over even if one of the children has handled it. | ||||
|                 // We can check the return value of the function and break the loop if it's true if we don't want | ||||
|                 // to continue checking the parents. | ||||
|                 let result = target._delegate.handleDragOver(this.actor._delegate, | ||||
|                                                              this._dragActor, | ||||
|                                                              targX, | ||||
|                                                              targY, | ||||
|                                                              0); | ||||
|                 if (result != DragMotionResult.CONTINUE) { | ||||
|                     global.screen.set_cursor(DRAG_CURSOR_MAP[result]); | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             target = target.get_parent(); | ||||
|         } | ||||
|         global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG); | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _queueUpdateDragHover: function() { | ||||
|         if (this._updateHoverId) | ||||
|             GLib.source_remove(this._updateHoverId); | ||||
|  | ||||
|         this._updateHoverId = GLib.idle_add(GLib.PRIORITY_DEFAULT, | ||||
|                                             Lang.bind(this, this._updateDragHover)); | ||||
|     }, | ||||
|  | ||||
|     _updateDragPosition : function (event) { | ||||
|         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; | ||||
|     }, | ||||
|  | ||||
| @@ -431,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); | ||||
| @@ -444,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) { | ||||
| @@ -455,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; | ||||
| @@ -507,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); | ||||
| @@ -561,7 +566,7 @@ const _Draggable = new Lang.Class({ | ||||
|         } else { | ||||
|             dragActor.destroy(); | ||||
|         } | ||||
|         global.screen.set_cursor(Meta.Cursor.DEFAULT); | ||||
|         global.unset_cursor(); | ||||
|         this.emit('drag-end', eventTime, false); | ||||
|  | ||||
|         this._animationInProgress = false; | ||||
| @@ -569,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; | ||||
|   | ||||
| @@ -20,6 +20,7 @@ | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const AccountsService = imports.gi.AccountsService; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| @@ -31,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; | ||||
|  | ||||
| @@ -61,96 +62,63 @@ const EndSessionDialogIface = <interface name="org.gnome.SessionManager.EndSessi | ||||
| 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 = <interface name='org.freedesktop.login1.Session'> | ||||
|     <property name="Id" type="s" access="read"/> | ||||
|     <property name="Remote" type="b" access="read"/> | ||||
|     <property name="Class" type="s" access="read"/> | ||||
|     <property name="Type" type="s" access="read"/> | ||||
|     <property name="State" type="s" access="read"/> | ||||
| </interface>; | ||||
|  | ||||
| const LogindSession = Gio.DBusProxy.makeProxyWrapper(LogindSessionIface); | ||||
|  | ||||
| function findAppFromInhibitor(inhibitor) { | ||||
|     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'; | ||||
| @@ -158,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 | ||||
| @@ -205,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, | ||||
| @@ -256,28 +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._applicationHeader = new St.Label({ style_class: 'end-session-dialog-list-header', | ||||
|                                                  text: _("Some applications are busy or have unsaved work.") }); | ||||
|         this._applicationList = new St.BoxLayout({ vertical: true }); | ||||
|         this._inhibitorSection.add_actor(this._applicationHeader); | ||||
|         this._inhibitorSection.add_actor(this._applicationList); | ||||
|         scrollView.add_actor(this._applicationList); | ||||
|  | ||||
|         this._sessionHeader = new St.Label({ style_class: 'end-session-dialog-list-header', | ||||
|                                              text: _("Other users are logged in.") }); | ||||
|         this._sessionList = new St.BoxLayout({ vertical: true }); | ||||
|         this._inhibitorSection.add_actor(this._sessionHeader); | ||||
|         this._inhibitorSection.add_actor(this._sessionList); | ||||
|         this._applicationList.connect('actor-added', | ||||
|                                       Lang.bind(this, function() { | ||||
|                                           if (this._applicationList.get_n_children() == 1) | ||||
|                                               scrollView.show(); | ||||
|                                       })); | ||||
|  | ||||
|         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'); | ||||
| @@ -288,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) { | ||||
| @@ -331,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() { | ||||
| @@ -388,7 +412,8 @@ const EndSessionDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onOpened: function() { | ||||
|         this._sync(); | ||||
|         if (this._inhibitors.length == 0) | ||||
|             this._startTimer(); | ||||
|     }, | ||||
|  | ||||
|     _startTimer: function() { | ||||
| @@ -402,7 +427,7 @@ const EndSessionDialog = new Lang.Class({ | ||||
|  | ||||
|                 this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed; | ||||
|                 if (this._secondsLeft > 0) { | ||||
|                     this._sync(); | ||||
|                     this._updateDescription(); | ||||
|                     return true; | ||||
|                 } | ||||
|  | ||||
| @@ -415,7 +440,7 @@ const EndSessionDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _stopTimer: function() { | ||||
|         if (this._timerId > 0) { | ||||
|         if (this._timerId != 0) { | ||||
|             Mainloop.source_remove(this._timerId); | ||||
|             this._timerId = 0; | ||||
|         } | ||||
| @@ -423,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; | ||||
|         } | ||||
| @@ -457,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', | ||||
| @@ -554,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)) { | ||||
| @@ -568,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); | ||||
|     }; | ||||
|   | ||||
| @@ -292,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,9 +315,11 @@ 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 && | ||||
| @@ -292,6 +337,9 @@ const GrabHelper = new Lang.Class({ | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (!button && this._modalCount == 0) | ||||
|             return false; | ||||
|  | ||||
|         if (this._isWithinGrabbedActor(event.get_source())) | ||||
|             return false; | ||||
|  | ||||
| @@ -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 true; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|         return this._modalCount > 0; | ||||
|     }, | ||||
|  | ||||
|     _onKeyFocusChanged: function() { | ||||
|         if (this._isUngrabbingCount > 0) | ||||
|             return; | ||||
|  | ||||
|         let focus = global.stage.key_focus; | ||||
|         if (!focus || !this._isWithinGrabbedActor(focus)) | ||||
|             this.ungrab({ isUser: true }); | ||||
|     }, | ||||
|  | ||||
|     _focusWindowChanged: function() { | ||||
|         let metaDisplay = global.screen.get_display(); | ||||
|         if (metaDisplay.focus_window != null) | ||||
|             this.ungrab({ isUser: true }); | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -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,49 +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.destroy_all_children(); | ||||
|     }, | ||||
|  | ||||
|     addItem: function(item, index) { | ||||
|         if (!item.icon || !item.icon instanceof BaseIcon) { | ||||
|             log('Only items with a BaseIcon icon property can be added to IconGrid'); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._items.push(item); | ||||
|     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); | ||||
|             this._grid.add_actor(actor); | ||||
|     }, | ||||
|  | ||||
|     getItemAtIndex: function(index) { | ||||
| @@ -436,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); | ||||
|   | ||||
							
								
								
									
										673
									
								
								js/ui/layout.js
									
									
									
									
									
								
							
							
						
						| @@ -42,11 +42,15 @@ const Lightbox = new Lang.Class({ | ||||
|         params = Params.parse(params, { inhibitEvents: false, | ||||
|                                         width: null, | ||||
|                                         height: null, | ||||
|                                         fadeFactor: DEFAULT_FADE_FACTOR, | ||||
|                                         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; | ||||
|         this.actor = new St.Bin({ x: 0, | ||||
|                                   y: 0, | ||||
| @@ -97,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; | ||||
| @@ -121,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(); | ||||
|   | ||||
| @@ -308,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); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -849,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', | ||||
| @@ -920,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 == '') | ||||
| @@ -986,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) { | ||||
| @@ -1072,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) { | ||||
| @@ -1150,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,12 +100,6 @@ 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); | ||||
|         }); | ||||
| @@ -133,7 +112,7 @@ const Magnifier = new Lang.Class({ | ||||
|         // 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); | ||||
| @@ -443,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, | ||||
| @@ -520,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)); | ||||
| @@ -573,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() { | ||||
| @@ -619,24 +585,6 @@ const Magnifier = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateFocusTrackingMode: function() { | ||||
|         // Applies only to the first zoom region. | ||||
|         if (this._zoomRegions.length) { | ||||
|             this._zoomRegions[0].setFocusTrackingMode( | ||||
|                 this._settings.get_enum(FOCUS_TRACKING_KEY) | ||||
|             ); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateCaretTrackingMode: function() { | ||||
|         // Applies only to the first zoom region. | ||||
|         if (this._zoomRegions.length) { | ||||
|             this._zoomRegions[0].setCaretTrackingMode( | ||||
|                 this._settings.get_enum(CARET_TRACKING_KEY) | ||||
|             ); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateInvertLightness: function() { | ||||
|         // Applies only to the first zoom region. | ||||
|         if (this._zoomRegions.length) { | ||||
| @@ -675,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); | ||||
|  | ||||
| @@ -684,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; | ||||
| @@ -714,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(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -750,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(); | ||||
|         } | ||||
|     }, | ||||
| @@ -816,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. | ||||
| @@ -1131,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. | ||||
| @@ -1337,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; | ||||
| @@ -1385,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; | ||||
| @@ -1407,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) { | ||||
| @@ -1633,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. | ||||
| @@ -1676,6 +1563,15 @@ const Crosshairs = new Lang.Class({ | ||||
|         } | ||||
|      }, | ||||
|  | ||||
|     /** | ||||
|      * getClip: | ||||
|      * Get the dimensions of the clip rectangle. | ||||
|      * @return:   An array of the form [width, height]. | ||||
|      */ | ||||
|     getClip: function() { | ||||
|         return this._clipSize; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * show: | ||||
|      * Show the crosshairs. | ||||
| @@ -1771,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. | ||||
| @@ -1799,6 +1708,24 @@ const MagShaderEffects = new Lang.Class({ | ||||
|         ); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * getBrightness: | ||||
|      * Retrieve current brightness of the magnified view. | ||||
|      * @return: Object containing the brightness for the red, green, | ||||
|      *          and blue channels.  Values of 0.0 represent "standard"  | ||||
|      *          brightness (no change), whereas values less or greater than | ||||
|      *          0.0 indicate decreased or incresaed brightness, respectively. | ||||
|      */ | ||||
|     getBrightness: function() { | ||||
|         let result = {}; | ||||
|         let [bRed, bGreen, bBlue] = this._brightnessContrast.get_brightness(); | ||||
|         result.r = bRed; | ||||
|         result.g = bGreen; | ||||
|         result.b = bBlue; | ||||
|  | ||||
|         return result; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Set the contrast of the magnified view. | ||||
|      * @contrast:   Object containing the contrast for the red, green, | ||||
| @@ -1823,4 +1750,21 @@ const MagShaderEffects = new Lang.Class({ | ||||
|              bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != NO_CHANGE | ||||
|         ); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * 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,12 +4,14 @@ 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 = <interface name="org.gnome.Magnifier"> | ||||
| const MagnifierIface = <interface name={MAG_SERVICE_NAME}> | ||||
| <method name="setActive"> | ||||
|     <arg type="b" direction="in" /> | ||||
| </method> | ||||
| @@ -64,7 +66,7 @@ const MagnifierIface = <interface name="org.gnome.Magnifier"> | ||||
|  | ||||
| // 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 = <interface name="org.gnome.Magnifier.ZoomRegion"> | ||||
| const ZoomRegionIface = <interface name={ZOOM_SERVICE_NAME}> | ||||
| <method name="setMagFactor"> | ||||
|     <arg type="d" direction="in" /> | ||||
|     <arg type="d" direction="in" /> | ||||
|   | ||||
							
								
								
									
										296
									
								
								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,33 +100,20 @@ function start() { | ||||
|     Gio.DesktopAppInfo.set_desktop_env('GNOME'); | ||||
|  | ||||
|     sessionMode = new SessionMode.SessionMode(); | ||||
|     sessionMode.connect('sessions-loaded', _sessionsLoaded); | ||||
|     sessionMode.init(); | ||||
|  | ||||
|     // start session after we know what mode we're running in | ||||
|     let signalId = sessionMode.connect('updated', function() { | ||||
|                                            sessionMode.disconnect(signalId); | ||||
|                                            startSession(); | ||||
|                                        }); | ||||
| } | ||||
|  | ||||
| function _sessionsLoaded() { | ||||
|     sessionMode.connect('updated', _sessionUpdated); | ||||
|     _initializePrefs(); | ||||
|     _initializeUI(); | ||||
| 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 | ||||
| @@ -147,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 | ||||
| @@ -160,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(); | ||||
| @@ -178,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. | ||||
| @@ -205,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() { | ||||
| @@ -270,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(); | ||||
| @@ -362,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); | ||||
| @@ -410,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'); | ||||
| @@ -457,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; | ||||
| } | ||||
|   | ||||
							
								
								
									
										1049
									
								
								js/ui/messageTray.js
									
									
									
									
									
								
							
							
						
						| @@ -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,38 +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 }); | ||||
|             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 }); | ||||
| @@ -109,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() { | ||||
| @@ -191,42 +181,6 @@ 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(); | ||||
|     }, | ||||
| @@ -323,9 +277,6 @@ const ModalDialog = new Lang.Class({ | ||||
|                                    this.state = State.CLOSED; | ||||
|                                    this._group.hide(); | ||||
|                                    this.emit('closed'); | ||||
|  | ||||
|                                    if (this._destroyOnClose) | ||||
|                                        this.destroy(); | ||||
|                                }) | ||||
|                          }); | ||||
|     }, | ||||
| @@ -361,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(); | ||||
|   | ||||
| @@ -103,6 +103,126 @@ const STANDARD_TRAY_ICON_IMPLEMENTATIONS = { | ||||
|     'ibus-ui-gtk': 'keyboard' | ||||
| }; | ||||
|  | ||||
| const NotificationGenericPolicy = new Lang.Class({ | ||||
|     Name: 'NotificationGenericPolicy', | ||||
|     Extends: MessageTray.NotificationPolicy, | ||||
|  | ||||
|     _init: function() { | ||||
|         // Don't chain to parent, it would try setting | ||||
|         // our properties to the defaults | ||||
|  | ||||
|         this.id = 'generic'; | ||||
|  | ||||
|         this._masterSettings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications' }); | ||||
|         this._masterSettings.connect('changed', Lang.bind(this, this._changed)); | ||||
|     }, | ||||
|  | ||||
|     store: function() { }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this._masterSettings.run_dispose(); | ||||
|     }, | ||||
|  | ||||
|     _changed: function(settings, key) { | ||||
|         this.emit('policy-changed', key); | ||||
|     }, | ||||
|  | ||||
|     get enable() { | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     get enableSound() { | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     get showBanners() { | ||||
|         return this._masterSettings.get_boolean('show-banners'); | ||||
|     }, | ||||
|  | ||||
|     get forceExpanded() { | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     get showInLockScreen() { | ||||
|         return this._masterSettings.get_boolean('show-in-lock-screen'); | ||||
|     }, | ||||
|  | ||||
|     get detailsInLockScreen() { | ||||
|         return false; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const NotificationApplicationPolicy = new Lang.Class({ | ||||
|     Name: 'NotificationApplicationPolicy', | ||||
|     Extends: MessageTray.NotificationPolicy, | ||||
|  | ||||
|     _init: function(id) { | ||||
|         // Don't chain to parent, it would try setting | ||||
|         // our properties to the defaults | ||||
|  | ||||
|         this.id = id; | ||||
|         this._canonicalId = this._canonicalizeId(id) | ||||
|  | ||||
|         this._masterSettings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications' }); | ||||
|         this._settings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications.application', | ||||
|                                             path: '/org/gnome/desktop/notifications/application/' + this._canonicalId + '/' }); | ||||
|  | ||||
|         this._masterSettings.connect('changed', Lang.bind(this, this._changed)); | ||||
|         this._settings.connect('changed', Lang.bind(this, this._changed)); | ||||
|     }, | ||||
|  | ||||
|     store: function() { | ||||
|         this._settings.set_string('application-id', this.id + '.desktop'); | ||||
|  | ||||
|         let apps = this._masterSettings.get_strv('application-children'); | ||||
|         if (apps.indexOf(this._canonicalId) < 0) { | ||||
|             apps.push(this._canonicalId); | ||||
|             this._masterSettings.set_strv('application-children', apps); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this._masterSettings.run_dispose(); | ||||
|         this._settings.run_dispose(); | ||||
|     }, | ||||
|  | ||||
|     _changed: function(settings, key) { | ||||
|         this.emit('policy-changed', key); | ||||
|     }, | ||||
|  | ||||
|     _canonicalizeId: function(id) { | ||||
|         // Keys are restricted to lowercase alphanumeric characters and dash, | ||||
|         // and two dashes cannot be in succession | ||||
|         return id.toLowerCase().replace(/[^a-z0-9\-]/g, '-').replace(/--+/g, '-'); | ||||
|     }, | ||||
|  | ||||
|     get enable() { | ||||
|         return this._settings.get_boolean('enable'); | ||||
|     }, | ||||
|  | ||||
|     get enableSound() { | ||||
|         return this._settings.get_boolean('enable-sound-alerts'); | ||||
|     }, | ||||
|  | ||||
|     get showBanners() { | ||||
|         return this._masterSettings.get_boolean('show-banners') && | ||||
|             this._settings.get_boolean('show-banners'); | ||||
|     }, | ||||
|  | ||||
|     get forceExpanded() { | ||||
|         return this._settings.get_boolean('force-expanded'); | ||||
|     }, | ||||
|  | ||||
|     get showInLockScreen() { | ||||
|         return this._masterSettings.get_boolean('show-in-lock-screen') && | ||||
|             this._settings.get_boolean('show-in-lock-screen'); | ||||
|     }, | ||||
|  | ||||
|     get detailsInLockScreen() { | ||||
|         return this._settings.get_boolean('details-in-lock-screen'); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const NotificationDaemon = new Lang.Class({ | ||||
|     Name: 'NotificationDaemon', | ||||
|  | ||||
| @@ -124,7 +244,7 @@ const NotificationDaemon = 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) { | ||||
| @@ -276,13 +396,12 @@ const NotificationDaemon = 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, | ||||
| @@ -555,9 +674,9 @@ const Source = new Lang.Class({ | ||||
|     _createPolicy: function() { | ||||
|         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(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -598,8 +717,8 @@ const Source = 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); | ||||
|         })); | ||||
|  | ||||
| @@ -615,11 +734,7 @@ const Source = 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; | ||||
|         } | ||||
| @@ -633,6 +748,22 @@ const Source = 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 | ||||
| @@ -644,8 +775,8 @@ const Source = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     open: function(notification) { | ||||
|         this.openApp(); | ||||
|         this.destroyNonResidentNotifications(); | ||||
|         this.openApp(); | ||||
|     }, | ||||
|  | ||||
|     _lastNotificationRemoved: function() { | ||||
| @@ -657,8 +788,11 @@ const Source = 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() { | ||||
|   | ||||
| @@ -8,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; | ||||
| @@ -72,7 +71,6 @@ 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, | ||||
| @@ -82,15 +80,6 @@ const OsdWindow = new Lang.Class({ | ||||
|                                        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 }); | ||||
|  | ||||
| @@ -107,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) { | ||||
| @@ -136,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, | ||||
| @@ -158,20 +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); | ||||
|                            }) | ||||
|                          }); | ||||
|                            onComplete: Lang.bind(this, this._reset) }); | ||||
|     }, | ||||
|  | ||||
|     _reset: function() { | ||||
| @@ -181,30 +164,16 @@ const OsdWindow = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _monitorsChanged: function() { | ||||
|         /* assume 110x110 on a 640x480 display and scale from there */ | ||||
|         /* 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)); | ||||
|         this._icon.icon_size = size / 2; | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -11,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; | ||||
| @@ -19,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 | ||||
| @@ -29,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', | ||||
| @@ -93,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(); | ||||
|     }, | ||||
| @@ -115,7 +117,7 @@ const Overview = new Lang.Class({ | ||||
|         let monitor = Main.layoutManager.primaryMonitor; | ||||
|  | ||||
|         this._desktopFade = new St.Bin(); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._desktopFade); | ||||
|         global.overlay_group.add_actor(this._desktopFade); | ||||
|  | ||||
|         let layout = new Clutter.BinLayout(); | ||||
|         this._stack = new Clutter.Actor({ layout_manager: layout }); | ||||
| @@ -131,14 +133,22 @@ const Overview = new Lang.Class({ | ||||
|                                             y_expand: true }); | ||||
|         this._overview._delegate = this; | ||||
|  | ||||
|         this._backgroundGroup = new Meta.BackgroundGroup(); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._backgroundGroup); | ||||
|         this._bgManagers = []; | ||||
|         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._activationTime = 0; | ||||
|         this._backgroundGroup = new Meta.BackgroundGroup(); | ||||
|         global.overlay_group.add_child(this._backgroundGroup); | ||||
|         this._backgroundGroup.hide(); | ||||
|         this._bgManagers = []; | ||||
|  | ||||
|         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; | ||||
| @@ -146,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 = 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(); | ||||
|  | ||||
| @@ -165,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; | ||||
| @@ -226,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(); | ||||
| @@ -263,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); | ||||
|  | ||||
| @@ -285,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); | ||||
|     }, | ||||
|  | ||||
|     // | ||||
| @@ -305,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; | ||||
| @@ -368,7 +405,7 @@ const Overview = new Lang.Class({ | ||||
|                                                 this._needsFakePointerEvent = true; | ||||
|                                                 Main.activateWindow(dragEvent.targetActor._delegate.metaWindow, | ||||
|                                                                     this._windowSwitchTimestamp); | ||||
|                                                 this.hide(); | ||||
|                                                 this.hideTemporarily(); | ||||
|                                                 this._lastHoveredWindow = null; | ||||
|                                             })); | ||||
|         } | ||||
| @@ -433,7 +470,6 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|     beginItemDrag: function(source) { | ||||
|         this.emit('item-drag-begin'); | ||||
|         this._inDrag = true; | ||||
|     }, | ||||
|  | ||||
|     cancelledItemDrag: function(source) { | ||||
| @@ -442,12 +478,10 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|     endItemDrag: function(source) { | ||||
|         this.emit('item-drag-end'); | ||||
|         this._inDrag = false; | ||||
|     }, | ||||
|  | ||||
|     beginWindowDrag: function(source) { | ||||
|         this.emit('window-drag-begin'); | ||||
|         this._inDrag = true; | ||||
|     }, | ||||
|  | ||||
|     cancelledWindowDrag: function(source) { | ||||
| @@ -456,31 +490,23 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|     endWindowDrag: function(source) { | ||||
|         this.emit('window-drag-end'); | ||||
|         this._inDrag = false; | ||||
|     }, | ||||
|  | ||||
|     // 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(); | ||||
| @@ -510,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, | ||||
| @@ -530,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() | ||||
| @@ -540,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() { | ||||
| @@ -566,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() { | ||||
| @@ -620,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, | ||||
| @@ -644,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(); | ||||
|     }, | ||||
|  | ||||
| @@ -655,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; | ||||
|  | ||||
| @@ -55,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 = alignX; | ||||
|         actorBox.x2 = actorBox.x1 + child.x_expand ? availWidth : natWidth; | ||||
|         actorBox.y1 = 0; | ||||
|         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); | ||||
|     }, | ||||
| @@ -210,7 +204,6 @@ const SlidingControl = new Lang.Class({ | ||||
|  | ||||
|     slideIn: function() { | ||||
|         this.visible = true; | ||||
|         this._updateTranslation(); | ||||
|         // we will update slideX and the translation from pageEmpty | ||||
|     }, | ||||
|  | ||||
| @@ -239,15 +232,18 @@ 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)); | ||||
|         Main.overview.connect('hiding', Lang.bind(this, this.slideOut)); | ||||
|         this.actor.connect('notify::hover', Lang.bind(this, this.updateSlide)); | ||||
|         this._thumbnailsBox.actor.bind_property('visible', this.actor, 'visible', GObject.BindingFlags.SYNC_CREATE); | ||||
|     }, | ||||
|  | ||||
|     _getAlwaysZoomOut: function() { | ||||
| @@ -273,18 +269,6 @@ const ThumbnailsSlider = new Lang.Class({ | ||||
|         return alwaysZoomOut; | ||||
|     }, | ||||
|  | ||||
|     _onOverviewShowing: function() { | ||||
|         this.visible = true; | ||||
|         this.layout.slideX = this.getSlide(); | ||||
|         this.actor.translation_x = this._getTranslation(); | ||||
|         this.slideIn(); | ||||
|     }, | ||||
|  | ||||
|     getNonExpandedWidth: function() { | ||||
|         let child = this.actor.get_first_child(); | ||||
|         return child.get_theme_node().get_length('visible-width'); | ||||
|     }, | ||||
|  | ||||
|     getSlide: function() { | ||||
|         if (!this.visible) | ||||
|             return 0; | ||||
| @@ -296,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'); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -322,14 +308,10 @@ 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_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)); | ||||
|         Main.overview.connect('hiding', Lang.bind(this, this.slideOut)); | ||||
|     }, | ||||
|  | ||||
|     getSlide: function() { | ||||
| @@ -339,13 +321,6 @@ const DashSlider = new Lang.Class({ | ||||
|             return 0; | ||||
|     }, | ||||
|  | ||||
|     _onOverviewShowing: function() { | ||||
|         this.visible = true; | ||||
|         this.layout.slideX = this.getSlide(); | ||||
|         this.actor.translation_x = this._getTranslation(); | ||||
|         this.slideIn(); | ||||
|     }, | ||||
|  | ||||
|     _onWindowDragBegin: function() { | ||||
|         this.fadeHalf(); | ||||
|     }, | ||||
| @@ -477,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; | ||||
| @@ -489,7 +462,6 @@ const MessagesIndicator = new Lang.Class({ | ||||
|                                     "%d new messages", | ||||
|                                    count).format(count); | ||||
|  | ||||
|         this._icon.visible = hasChats; | ||||
|         this._updateVisibility(); | ||||
|     }, | ||||
|  | ||||
| @@ -501,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 | ||||
| @@ -596,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); | ||||
| @@ -616,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() { | ||||
|   | ||||
							
								
								
									
										476
									
								
								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,14 +708,14 @@ 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()) | ||||
|             if (!this.hotCorner.shouldToggleOverviewOnClick()) | ||||
|                 return true; | ||||
|         } | ||||
|         return false; | ||||
| @@ -609,7 +723,6 @@ const ActivitiesButton = new Lang.Class({ | ||||
|  | ||||
|     _onButtonRelease: function() { | ||||
|         Main.overview.toggle(); | ||||
|         this.menu.close(); | ||||
|     }, | ||||
|  | ||||
|     _onKeyRelease: function(actor, event) { | ||||
| @@ -619,12 +732,15 @@ const ActivitiesButton = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _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; | ||||
| @@ -798,65 +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._indicators.add_child(this._screencast.indicators); | ||||
|         this._indicators.add_child(this._network.indicators); | ||||
|         if (this._bluetooth) { | ||||
|             this._indicators.add_child(this._bluetooth.indicators); | ||||
|         } | ||||
|         this._indicators.add_child(this._rfkill.indicators); | ||||
|         this._indicators.add_child(this._volume.indicators); | ||||
|         this._indicators.add_child(this._power.indicators); | ||||
|         this._indicators.add_child(PopupMenu.unicodeArrow(St.Side.BOTTOM)); | ||||
|  | ||||
|         this.menu.addMenuItem(this._volume.menu); | ||||
|         this.menu.addMenuItem(this._brightness.menu); | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|         this.menu.addMenuItem(this._network.menu); | ||||
|         if (this._bluetooth) { | ||||
|             this.menu.addMenuItem(this._bluetooth.menu); | ||||
|         } | ||||
|         this.menu.addMenuItem(this._rfkill.menu); | ||||
|         this.menu.addMenuItem(this._power.menu); | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|         this.menu.addMenuItem(this._system.menu); | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const PANEL_ITEM_IMPLEMENTATIONS = { | ||||
|     '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', | ||||
|  | ||||
| @@ -869,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); | ||||
| @@ -1023,18 +1106,17 @@ const Panel = new Lang.Class({ | ||||
|         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(); | ||||
| @@ -162,18 +183,7 @@ const Button = new Lang.Class({ | ||||
|             return false; | ||||
|     }, | ||||
|  | ||||
|     _onVisibilityChanged: function() { | ||||
|         if (!this.menu) | ||||
|             return; | ||||
|  | ||||
|         if (!this.actor.visible) | ||||
|             this.menu.close(); | ||||
|     }, | ||||
|  | ||||
|     _onMenuKeyPress: function(actor, event) { | ||||
|         if (global.focus_manager.navigate_from_event(event)) | ||||
|             return true; | ||||
|  | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) { | ||||
|             let group = global.focus_manager.get_group(this.actor); | ||||
| @@ -211,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); | ||||
|   | ||||
| @@ -2,7 +2,6 @@ | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| const GnomeDesktop = imports.gi.GnomeDesktop; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| @@ -42,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 = []; | ||||
|   | ||||
							
								
								
									
										1587
									
								
								js/ui/popupMenu.js
									
									
									
									
									
								
							
							
						
						| @@ -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(); | ||||
|     }, | ||||
| }); | ||||
| @@ -7,6 +7,7 @@ const Lang = imports.lang; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const FileUtils = imports.misc.fileUtils; | ||||
| const Search = imports.ui.search; | ||||
|  | ||||
| const KEY_FILE_GROUP = 'Shell Search Provider'; | ||||
| @@ -59,114 +60,119 @@ var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface); | ||||
| var SearchProvider2Proxy = Gio.DBusProxy.makeProxyWrapper(SearchProvider2Iface); | ||||
|  | ||||
| function loadRemoteSearchProviders(addProviderCallback) { | ||||
|     let objectPaths = {}; | ||||
|     let loadedProviders = []; | ||||
|     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())); | ||||
|         } | ||||
|     try { | ||||
|         keyfile.load_from_file(path, 0); | ||||
|     } catch(e) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     let dataDirs = GLib.get_system_data_dirs(); | ||||
|     dataDirs.forEach(function(dataDir) { | ||||
|         let path = GLib.build_filenamev([dataDir, 'gnome-shell', 'search-providers']); | ||||
|         let dir = Gio.File.new_for_path(path); | ||||
|         let fileEnum; | ||||
|         try { | ||||
|             fileEnum = dir.enumerate_children('standard::name,standard::type', | ||||
|                                               Gio.FileQueryInfoFlags.NONE, null); | ||||
|         } catch (e) { | ||||
|             fileEnum = null; | ||||
|         } | ||||
|         if (fileEnum != null) { | ||||
|             let info; | ||||
|             while ((info = fileEnum.next_file(null))) | ||||
|                 loadRemoteSearchProvider(fileEnum.get_child(info)); | ||||
|         } | ||||
|     }); | ||||
|     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.sort(function(providerA, providerB) { | ||||
|         let idxA, idxB; | ||||
|         let appIdA, appIdB; | ||||
|     let numSorted = sortOrder.length; | ||||
|  | ||||
|         appIdA = providerA.appInfo.get_id(); | ||||
|         appIdB = providerB.appInfo.get_id(); | ||||
|     loadState.loadedProviders.sort( | ||||
|         function(providerA, providerB) { | ||||
|             let idxA, idxB; | ||||
|             let appIdA, appIdB; | ||||
|  | ||||
|         idxA = sortOrder.indexOf(appIdA); | ||||
|         idxB = sortOrder.indexOf(appIdB); | ||||
|             appIdA = providerA.appInfo.get_id(); | ||||
|             appIdB = providerB.appInfo.get_id(); | ||||
|  | ||||
|         // 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(); | ||||
|             idxA = sortOrder.indexOf(appIdA); | ||||
|             idxB = sortOrder.indexOf(appIdB); | ||||
|  | ||||
|             return GLib.utf8_collate(nameA, nameB); | ||||
|         } | ||||
|             // 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 providerA isn't found, it's sorted after providerB | ||||
|         if (idxA == -1) | ||||
|             return 1; | ||||
|                 return GLib.utf8_collate(nameA, nameB); | ||||
|             } | ||||
|  | ||||
|         // if providerB isn't found, it's sorted after providerA | ||||
|         if (idxB == -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; | ||||
|             } | ||||
|  | ||||
|         // finally, if both providers are found, return their order in the list | ||||
|         return (idxA - idxB); | ||||
|     }); | ||||
|             // if providerA isn't found, it's sorted after providerB | ||||
|             if (idxA == -1) | ||||
|                 return 1; | ||||
|  | ||||
|     loadedProviders.forEach(addProviderCallback); | ||||
|             // if providerB isn't found, it's sorted after providerA | ||||
|             if (idxB == -1) | ||||
|                 return -1; | ||||
|  | ||||
|             // 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({ | ||||
| @@ -192,9 +198,7 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|  | ||||
|     createIcon: function(size, meta) { | ||||
|         let gicon; | ||||
|         if (meta['icon']) { | ||||
|             gicon = Gio.icon_deserialize(meta['icon']); | ||||
|         } else if (meta['gicon']) { | ||||
|         if (meta['gicon']) { | ||||
|             gicon = Gio.icon_new_for_string(meta['gicon']); | ||||
|         } else if (meta['icon-data']) { | ||||
|             let [width, height, rowStride, hasAlpha, | ||||
| @@ -207,20 +211,10 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|                              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) { | ||||
|         if (error) | ||||
|             return; | ||||
|         this.searchSystem.setResults(this, results[0]); | ||||
|         this.searchSystem.pushResults(this, results[0]); | ||||
|     }, | ||||
|  | ||||
|     getInitialResultSet: function(terms) { | ||||
| @@ -232,7 +226,7 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|                                                  this._cancellable); | ||||
|         } catch(e) { | ||||
|             log('Error calling GetInitialResultSet for provider %s: %s'.format(this.id, e.toString())); | ||||
|             this.searchSystem.setResults(this, []); | ||||
|             this.searchSystem.pushResults(this, []); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -245,7 +239,7 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|                                                    this._cancellable); | ||||
|         } catch(e) { | ||||
|             log('Error calling GetSubsearchResultSet for provider %s: %s'.format(this.id, e.toString())); | ||||
|             this.searchSystem.setResults(this, []); | ||||
|             this.searchSystem.pushResults(this, []); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -257,12 +251,8 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|         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'], | ||||
|   | ||||
| @@ -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 }); | ||||
| @@ -111,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 }); | ||||
| @@ -126,6 +259,18 @@ const RunDialog = new Lang.Class({ | ||||
|  | ||||
|                 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(); | ||||
|                 let prefix; | ||||
| @@ -137,6 +282,8 @@ 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 true; | ||||
|             } | ||||
| @@ -144,53 +291,11 @@ const RunDialog = new Lang.Class({ | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _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); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,10 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const AccountsService = imports.gi.AccountsService; | ||||
| const Cairo = imports.cairo; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const GnomeDesktop = imports.gi.GnomeDesktop; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| @@ -19,14 +17,12 @@ const Background = imports.ui.background; | ||||
| const GnomeSession = imports.misc.gnomeSession; | ||||
| const Hash = imports.misc.hash; | ||||
| const Layout = imports.ui.layout; | ||||
| const OVirt = imports.gdm.oVirt; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| const Lightbox = imports.ui.lightbox; | ||||
| const Main = imports.ui.main; | ||||
| const Overview = imports.ui.overview; | ||||
| const MessageTray = imports.ui.messageTray; | ||||
| const ShellDBus = imports.ui.shellDBus; | ||||
| const SmartcardManager = imports.misc.smartcardManager; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| @@ -34,7 +30,6 @@ const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver'; | ||||
| const LOCK_ENABLED_KEY = 'lock-enabled'; | ||||
| const LOCK_DELAY_KEY = 'lock-delay'; | ||||
|  | ||||
| const LOCKED_STATE_STR = 'screenShield.locked'; | ||||
| // fraction of screen height the arrow must reach before completing | ||||
| // the slide up automatically | ||||
| const ARROW_DRAG_THRESHOLD = 0.1; | ||||
| @@ -43,7 +38,11 @@ const ARROW_DRAG_THRESHOLD = 0.1; | ||||
| const N_ARROWS = 3; | ||||
| const ARROW_ANIMATION_TIME = 0.6; | ||||
| const ARROW_ANIMATION_PEAK_OPACITY = 0.4; | ||||
| const ARROW_IDLE_TIME = 30000; // ms | ||||
|  | ||||
| // The distance in px that the lock screen will move to when pressing | ||||
| // a key that has no effect in the lock screen (bumping it) | ||||
| const BUMP_SIZE = 25; | ||||
| const BUMP_TIME = 0.3; | ||||
|  | ||||
| const SUMMARY_ICON_SIZE = 48; | ||||
|  | ||||
| @@ -53,10 +52,12 @@ const SUMMARY_ICON_SIZE = 48; | ||||
| //   or when cancelling the dialog | ||||
| // - BACKGROUND_FADE_TIME is used when the background changes to crossfade to new background | ||||
| // - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking | ||||
| // - INITIAL_FADE_IN_TIME is used for the initial fade in at startup | ||||
| const STANDARD_FADE_TIME = 10; | ||||
| const MANUAL_FADE_TIME = 0.3; | ||||
| const MANUAL_FADE_TIME = 0.8; | ||||
| const BACKGROUND_FADE_TIME = 1.0; | ||||
| const CURTAIN_SLIDE_TIME = 0.3; | ||||
| const INITIAL_FADE_IN_TIME = 0.25; | ||||
|  | ||||
| const Clock = new Lang.Class({ | ||||
|     Name: 'ScreenShieldClock', | ||||
| @@ -106,14 +107,13 @@ const NotificationsBox = new Lang.Class({ | ||||
|         this._musicBin = new St.Bin({ style_class: 'screen-shield-notifications-box', | ||||
|                                       visible: false }); | ||||
|  | ||||
|         this._scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START, | ||||
|                                                hscrollbar_policy: Gtk.PolicyType.NEVER }); | ||||
|         let scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START }); | ||||
|         this._notificationBox = new St.BoxLayout({ vertical: true, | ||||
|                                                    style_class: 'screen-shield-notifications-box' }); | ||||
|         this._scrollView.add_actor(this._notificationBox); | ||||
|         scrollView.add_actor(this._notificationBox); | ||||
|  | ||||
|         this.actor.add(this._musicBin); | ||||
|         this.actor.add(this._scrollView, { x_fill: true, x_align: St.Align.START }); | ||||
|         this.actor.add(scrollView, { x_fill: true, x_align: St.Align.START }); | ||||
|  | ||||
|         this._sources = new Hash.Map(); | ||||
|         Main.messageTray.getSources().forEach(Lang.bind(this, function(source) { | ||||
| @@ -218,7 +218,6 @@ const NotificationsBox = new Lang.Class({ | ||||
|  | ||||
|         if (musicNotification != null && | ||||
|             this._musicBin.child == null) { | ||||
|             musicNotification.acknowledged = true; | ||||
|             if (musicNotification.actor.get_parent() != null) | ||||
|                 musicNotification.actor.get_parent().remove_actor(musicNotification.actor); | ||||
|             this._musicBin.child = musicNotification.actor; | ||||
| @@ -239,7 +238,7 @@ const NotificationsBox = new Lang.Class({ | ||||
|             (source.unseenCount > (musicNotification ? 1 : 0)); | ||||
|     }, | ||||
|  | ||||
|     _sourceAdded: function(tray, source, initial) { | ||||
|     _sourceAdded: function(tray, source, dontUpdateVisibility) { | ||||
|         // Ignore transient sources | ||||
|         if (source.isTransient) | ||||
|             return; | ||||
| @@ -251,7 +250,6 @@ const NotificationsBox = new Lang.Class({ | ||||
|             sourceCountChangedId: 0, | ||||
|             sourceTitleChangedId: 0, | ||||
|             sourceUpdatedId: 0, | ||||
|             sourceNotifyId: 0, | ||||
|             musicNotification: null, | ||||
|             sourceBox: null, | ||||
|             titleLabel: null, | ||||
| @@ -262,12 +260,6 @@ const NotificationsBox = new Lang.Class({ | ||||
|         this._showSource(source, obj, obj.sourceBox); | ||||
|         this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START }); | ||||
|  | ||||
|         if (obj.musicNotification) { | ||||
|             obj.sourceNotifyId = source.connect('notify', Lang.bind(this, function(source, notification) { | ||||
|                 notification.acknowledged = true; | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         obj.sourceCountChangedId = source.connect('count-updated', Lang.bind(this, function(source) { | ||||
|             this._countChanged(source, obj); | ||||
|         })); | ||||
| @@ -286,29 +278,8 @@ const NotificationsBox = new Lang.Class({ | ||||
|  | ||||
|         this._sources.set(source, obj); | ||||
|  | ||||
|         if (!initial) { | ||||
|             // block scrollbars while animating, if they're not needed now | ||||
|             let boxHeight = this._notificationBox.height; | ||||
|             if (this._scrollView.height >= boxHeight) | ||||
|                 this._scrollView.vscrollbar_policy = Gtk.PolicyType.NEVER; | ||||
|  | ||||
|             let widget = obj.sourceBox; | ||||
|             let [, natHeight] = widget.get_preferred_height(-1); | ||||
|             widget.height = 0; | ||||
|             Tweener.addTween(widget, | ||||
|                              { height: natHeight, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                time: 0.25, | ||||
|                                onComplete: function() { | ||||
|                                    this._scrollView.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC; | ||||
|                                    widget.set_height(-1); | ||||
|                                }, | ||||
|                                onCompleteScope: this | ||||
|                              }); | ||||
|  | ||||
|         if (!dontUpdateVisibility) | ||||
|             this._updateVisibility(); | ||||
|             Shell.util_wake_up_screen(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _titleChanged: function(source, obj) { | ||||
| @@ -330,10 +301,7 @@ const NotificationsBox = new Lang.Class({ | ||||
|  | ||||
|         obj.sourceBox.visible = obj.visible && | ||||
|             (source.unseenCount > (obj.musicNotification ? 1 : 0)); | ||||
|  | ||||
|         this._updateVisibility(); | ||||
|         if (obj.sourceBox.visible) | ||||
|             Shell.util_wake_up_screen(); | ||||
|     }, | ||||
|  | ||||
|     _visibleChanged: function(source, obj) { | ||||
| @@ -347,8 +315,6 @@ const NotificationsBox = new Lang.Class({ | ||||
|             source.unseenCount > (obj.musicNotification ? 1 : 0); | ||||
|  | ||||
|         this._updateVisibility(); | ||||
|         if (obj.sourceBox.visible) | ||||
|             Shell.util_wake_up_screen(); | ||||
|     }, | ||||
|  | ||||
|     _detailedChanged: function(source, obj) { | ||||
| @@ -374,8 +340,6 @@ const NotificationsBox = new Lang.Class({ | ||||
|         if (obj.musicNotification) { | ||||
|             this._musicBin.child = null; | ||||
|             obj.musicNotification = null; | ||||
|  | ||||
|             source.disconnect(obj.sourceNotifyId); | ||||
|         } | ||||
|  | ||||
|         source.disconnect(obj.sourceDestroyId); | ||||
| @@ -471,8 +435,8 @@ const ScreenShield = new Lang.Class({ | ||||
|                                                 name: 'lockScreenGroup', | ||||
|                                                 visible: false, | ||||
|                                               }); | ||||
|         this._lockScreenGroup.connect('key-press-event', | ||||
|                                       Lang.bind(this, this._onLockScreenKeyPress)); | ||||
|         this._lockScreenGroup.connect('key-release-event', | ||||
|                                       Lang.bind(this, this._onLockScreenKeyRelease)); | ||||
|         this._lockScreenGroup.connect('scroll-event', | ||||
|                                       Lang.bind(this, this._onLockScreenScroll)); | ||||
|         Main.ctrlAltTabManager.addGroup(this._lockScreenGroup, _("Lock"), 'changes-prevent-symbolic'); | ||||
| @@ -492,9 +456,6 @@ const ScreenShield = new Lang.Class({ | ||||
|         this._updateBackgrounds(); | ||||
|         Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateBackgrounds)); | ||||
|  | ||||
|         this._arrowAnimationId = 0; | ||||
|         this._arrowWatchId = 0; | ||||
|         this._arrowActiveWatchId = 0; | ||||
|         this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows', | ||||
|                                                   vertical: true, | ||||
|                                                   x_align: Clutter.ActorAlign.CENTER, | ||||
| @@ -518,10 +479,16 @@ const ScreenShield = new Lang.Class({ | ||||
|  | ||||
|         this._lockDialogGroup = new St.Widget({ x_expand: true, | ||||
|                                                 y_expand: true, | ||||
|                                                 reactive: true, | ||||
|                                                 opacity: 0, | ||||
|                                                 pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }), | ||||
|                                                 name: 'lockDialogGroup' }); | ||||
|  | ||||
|         Tweener.addTween(this._lockDialogGroup, | ||||
|                          { opacity: 255, | ||||
|                            time: INITIAL_FADE_IN_TIME, | ||||
|                            transition: 'easeInQuad', | ||||
|                          }); | ||||
|  | ||||
|         this.actor.add_actor(this._lockDialogGroup); | ||||
|         this.actor.add_actor(this._lockScreenGroup); | ||||
|  | ||||
| @@ -539,33 +506,15 @@ const ScreenShield = new Lang.Class({ | ||||
|  | ||||
|         this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this); | ||||
|  | ||||
|         this._smartcardManager = SmartcardManager.getSmartcardManager(); | ||||
|         this._smartcardManager.connect('smartcard-inserted', | ||||
|                                        Lang.bind(this, function(token) { | ||||
|                                            if (this._isLocked && token.UsedToLogin) | ||||
|                                                this._liftShield(true, 0); | ||||
|                                        })); | ||||
|  | ||||
|         this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager(); | ||||
|         this._oVirtCredentialsManager.connect('user-authenticated', | ||||
|                                               Lang.bind(this, function() { | ||||
|                                                   if (this._isLocked) | ||||
|                                                       this._liftShield(true, 0); | ||||
|                                               })); | ||||
|  | ||||
|         this._inhibitor = null; | ||||
|         this._aboutToSuspend = false; | ||||
|         this._loginManager = LoginManager.getLoginManager(); | ||||
|         this._loginManager.connect('prepare-for-sleep', | ||||
|                                    Lang.bind(this, this._prepareForSleep)); | ||||
|         this._inhibitSuspend(); | ||||
|  | ||||
|         this._loginManager.getCurrentSessionProxy(Lang.bind(this, | ||||
|             function(sessionProxy) { | ||||
|                 this._loginSession = sessionProxy; | ||||
|                 this._loginSession.connectSignal('Lock', Lang.bind(this, function() { this.lock(false); })); | ||||
|                 this._loginSession.connectSignal('Unlock', Lang.bind(this, function() { this.deactivate(false); })); | ||||
|             })); | ||||
|         this._loginSession = this._loginManager.getCurrentSessionProxy(); | ||||
|         this._loginSession.connectSignal('Lock', Lang.bind(this, function() { this.lock(false); })); | ||||
|         this._loginSession.connectSignal('Unlock', Lang.bind(this, function() { this.deactivate(false); })); | ||||
|  | ||||
|         this._settings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA }); | ||||
|  | ||||
| @@ -579,20 +528,13 @@ const ScreenShield = new Lang.Class({ | ||||
|         this._becameActiveId = 0; | ||||
|         this._lockTimeoutId = 0; | ||||
|  | ||||
|         // The "long" lightbox is used for the longer (20 seconds) fade from session | ||||
|         // to idle status, the "short" is used for quickly fading to black when locking | ||||
|         // manually | ||||
|         this._longLightbox = new Lightbox.Lightbox(Main.uiGroup, | ||||
|                                                    { inhibitEvents: true, | ||||
|                                                      fadeFactor: 1 }); | ||||
|         this._longLightbox.connect('shown', Lang.bind(this, this._onLongLightboxShown)); | ||||
|         this._shortLightbox = new Lightbox.Lightbox(Main.uiGroup, | ||||
|                                                     { inhibitEvents: true, | ||||
|                                                       fadeFactor: 1 }); | ||||
|         this._shortLightbox.connect('shown', Lang.bind(this, this._onShortLightboxShown)); | ||||
|         this._lightbox = new Lightbox.Lightbox(Main.uiGroup, | ||||
|                                                { inhibitEvents: true, | ||||
|                                                  fadeInTime: STANDARD_FADE_TIME, | ||||
|                                                  fadeFactor: 1 }); | ||||
|         this._lightbox.connect('shown', Lang.bind(this, this._onLightboxShown)); | ||||
|  | ||||
|         this.idleMonitor = Meta.IdleMonitor.get_core(); | ||||
|         this._cursorTracker = Meta.CursorTracker.get_for_screen(global.screen); | ||||
|         this.idleMonitor = new GnomeDesktop.IdleMonitor(); | ||||
|     }, | ||||
|  | ||||
|     _createBackground: function(monitorIndex) { | ||||
| @@ -605,8 +547,9 @@ const ScreenShield = new Lang.Class({ | ||||
|  | ||||
|         let bgManager = new Background.BackgroundManager({ container: widget, | ||||
|                                                            monitorIndex: monitorIndex, | ||||
|                                                            controlPosition: false, | ||||
|                                                            settingsSchema: SCREENSAVER_SCHEMA }); | ||||
|                                                            effects: Meta.BackgroundEffects.BLUR | Meta.BackgroundEffects.DESATURATE, | ||||
|                                                            controlPosition: false }); | ||||
|         bgManager.background.saturation = 0.6; | ||||
|  | ||||
|         this._bgManagers.push(bgManager); | ||||
|  | ||||
| @@ -618,7 +561,6 @@ const ScreenShield = new Lang.Class({ | ||||
|             this._bgManagers[i].destroy(); | ||||
|  | ||||
|         this._bgManagers = []; | ||||
|         this._backgroundGroup.destroy_all_children(); | ||||
|  | ||||
|         for (let i = 0; i < Main.layoutManager.monitors.length; i++) | ||||
|             this._createBackground(i); | ||||
| @@ -626,28 +568,13 @@ const ScreenShield = new Lang.Class({ | ||||
|  | ||||
|     _liftShield: function(onPrimary, velocity) { | ||||
|         if (this._isLocked) { | ||||
|             if (this._ensureUnlockDialog(onPrimary, true /* allowCancel */)) | ||||
|                 this._hideLockScreen(true /* animate */, velocity); | ||||
|             this._ensureUnlockDialog(onPrimary, true /* allowCancel */); | ||||
|             this._hideLockScreen(true /* animate */, velocity); | ||||
|         } else { | ||||
|             this.deactivate(true /* animate */); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _maybeCancelDialog: function() { | ||||
|         if (!this._dialog) | ||||
|             return; | ||||
|  | ||||
|         this._dialog.cancel(); | ||||
|         if (this._isGreeter) { | ||||
|             // LoginDialog.cancel() will grab the key focus | ||||
|             // on its own, so ensure it stays on lock screen | ||||
|             // instead | ||||
|             this._lockScreenGroup.grab_key_focus(); | ||||
|         } else { | ||||
|             this._dialog = null; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _becomeModal: function() { | ||||
|         if (this._isModal) | ||||
|             return true; | ||||
| @@ -663,28 +590,28 @@ const ScreenShield = new Lang.Class({ | ||||
|         return this._isModal; | ||||
|     }, | ||||
|  | ||||
|     _onLockScreenKeyPress: function(actor, event) { | ||||
|     _onLockScreenKeyRelease: function(actor, event) { | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         let unichar = event.get_key_unicode(); | ||||
|  | ||||
|         // Do nothing if the lock screen is not fully shown. | ||||
|         // This avoids reusing the previous (and stale) unlock | ||||
|         // dialog if esc is pressed while the curtain is going | ||||
|         // down after cancel. | ||||
|         // Similarly, don't bump if the lock screen is not showing or is | ||||
|         // animating, as the bump overrides the animation and would | ||||
|         // remove any onComplete handler. | ||||
|  | ||||
|         if (this._lockScreenState != MessageTray.State.SHOWN) | ||||
|             return false; | ||||
|  | ||||
|         let isEnter = (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_KP_Enter); | ||||
|         if (!isEnter && !(GLib.unichar_isprint(unichar) || symbol == Clutter.KEY_Escape)) | ||||
|             return false; | ||||
|         if (symbol == Clutter.KEY_Escape || | ||||
|             symbol == Clutter.KEY_Return || | ||||
|             symbol == Clutter.KEY_KP_Enter) { | ||||
|             this._liftShield(false, 0); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (this._isLocked && | ||||
|             this._ensureUnlockDialog(true, true) && | ||||
|             GLib.unichar_isgraph(unichar)) | ||||
|             this._dialog.addCharacter(unichar); | ||||
|  | ||||
|         this._liftShield(true, 0); | ||||
|         this._bumpLockScreen(); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
| @@ -722,8 +649,6 @@ const ScreenShield = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _prepareForSleep: function(loginManager, aboutToSuspend) { | ||||
|         this._aboutToSuspend = aboutToSuspend; | ||||
|  | ||||
|         if (aboutToSuspend) { | ||||
|             if (!this._settings.get_boolean(LOCK_ENABLED_KEY)) { | ||||
|                 this._uninhibitSuspend(); | ||||
| @@ -732,8 +657,6 @@ const ScreenShield = new Lang.Class({ | ||||
|             this.lock(true); | ||||
|         } else { | ||||
|             this._inhibitSuspend(); | ||||
|  | ||||
|             this._onUserBecameActive(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -782,8 +705,6 @@ const ScreenShield = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onDragEnd: function(action, actor, eventX, eventY, modifiers) { | ||||
|         if (this._lockScreenState != MessageTray.State.HIDING) | ||||
|             return; | ||||
|         if (this._lockScreenGroup.y < -(ARROW_DRAG_THRESHOLD * global.stage.height)) { | ||||
|             // Complete motion automatically | ||||
| 	    let [velocity, velocityX, velocityY] = this._dragAction.get_velocity(0); | ||||
| @@ -805,7 +726,13 @@ const ScreenShield = new Lang.Class({ | ||||
|                                onCompleteScope: this, | ||||
|                              }); | ||||
|  | ||||
|             this._maybeCancelDialog(); | ||||
|             // If we have a unlock dialog, cancel it | ||||
|             if (this._dialog) { | ||||
|                 this._dialog.cancel(); | ||||
|                 if (!this._isGreeter) { | ||||
|                     this._dialog = null; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -813,9 +740,27 @@ const ScreenShield = new Lang.Class({ | ||||
|         if (status != GnomeSession.PresenceStatus.IDLE) | ||||
|             return; | ||||
|  | ||||
|         this._maybeCancelDialog(); | ||||
|         if (this._dialog) { | ||||
|             this._dialog.cancel(); | ||||
|             if (!this._isGreeter) { | ||||
|                 this._dialog = null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (this._longLightbox.actor.visible || | ||||
|         if (!this._becomeModal()) { | ||||
|             // We could not become modal, so we can't activate the | ||||
|             // screenshield. The user is probably very upset at this | ||||
|             // point, but any application using global grabs is broken | ||||
|             // Just tell him to stop using this app | ||||
|             //  | ||||
|             // XXX: another option is to kick the user into the gdm login | ||||
|             // screen, where we're not affected by grabs | ||||
|             Main.notifyError(_("Unable to lock"), | ||||
|                              _("Lock was blocked by an application")); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (this._lightbox.actor.visible || | ||||
|             this._isActive) { | ||||
|             // We're either shown and active, or in the process of | ||||
|             // showing. | ||||
| @@ -828,22 +773,13 @@ const ScreenShield = new Lang.Class({ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (!this._becomeModal()) { | ||||
|             // We could not become modal, so we can't activate the | ||||
|             // screenshield. The user is probably very upset at this | ||||
|             // point, but any application using global grabs is broken | ||||
|             // Just tell him to stop using this app | ||||
|             // | ||||
|             // XXX: another option is to kick the user into the gdm login | ||||
|             // screen, where we're not affected by grabs | ||||
|             Main.notifyError(_("Unable to lock"), | ||||
|                              _("Lock was blocked by an application")); | ||||
|             return; | ||||
|         } | ||||
|         this._lightbox.show(); | ||||
|  | ||||
|         if (this._activationTime == 0) | ||||
|             this._activationTime = GLib.get_monotonic_time(); | ||||
|  | ||||
|         this._becameActiveId = this.idleMonitor.add_user_active_watch(Lang.bind(this, this._onUserBecameActive)); | ||||
|  | ||||
|         let shouldLock = this._settings.get_boolean(LOCK_ENABLED_KEY) && !this._isLocked; | ||||
|  | ||||
|         if (shouldLock) { | ||||
| @@ -851,59 +787,48 @@ const ScreenShield = new Lang.Class({ | ||||
|             this._lockTimeoutId = Mainloop.timeout_add(lockTimeout * 1000, | ||||
|                                                        Lang.bind(this, function() { | ||||
|                                                            this._lockTimeoutId = 0; | ||||
|                                                            this.lock(false); | ||||
|                                                            this.lock(true); | ||||
|                                                            return false; | ||||
|                                                        })); | ||||
|         } | ||||
|  | ||||
|         this._activateFade(this._longLightbox, STANDARD_FADE_TIME); | ||||
|     }, | ||||
|  | ||||
|     _activateFade: function(lightbox, time) { | ||||
|         lightbox.show(time); | ||||
|  | ||||
|         if (this._becameActiveId == 0) | ||||
|             this._becameActiveId = this.idleMonitor.add_user_active_watch(Lang.bind(this, this._onUserBecameActive)); | ||||
|     }, | ||||
|  | ||||
|     _onUserBecameActive: function() { | ||||
|         // This function gets called here when the user becomes active | ||||
|         // after we activated a lightbox | ||||
|         // There are two possibilities here: | ||||
|         // - we're called when already locked/active; isLocked or isActive is true, | ||||
|         // after gnome-session changed the status to IDLE | ||||
|         // There are four possibilities here: | ||||
|         // - we're called when already locked; isActive and isLocked are true, | ||||
|         //   we just go back to the lock screen curtain | ||||
|         //   (isActive == isLocked == true: normal case | ||||
|         //    isActive == false, isLocked == true: during the fade for manual locking | ||||
|         //    isActive == true, isLocked == false: after session idle, before lock-delay) | ||||
|         // - we're called because the session is IDLE but before the lightbox | ||||
|         //   is fully shown; at this point isActive is false, so we just hide | ||||
|         //   the lightbox, reset the activationTime and go back to the unlocked | ||||
|         //   desktop | ||||
|         //   using deactivate() is a little of overkill, but it ensures we | ||||
|         //   don't forget of some bit like modal, DBus properties or idle watches | ||||
|         // | ||||
|         // Note: if the (long) lightbox is shown then we're necessarily | ||||
|         // active, because we call activate() without animation. | ||||
|         // - we're called before the lightbox is fully shown; at this point | ||||
|         //   isActive is false, so we just hide the ligthbox, reset the activationTime | ||||
|         //   and go back to the unlocked desktop | ||||
|         // - we're called after showing the lightbox, but before the lock | ||||
|         //   delay; this is mostly like the case above, but isActive is true now | ||||
|         //   so we need to notify gnome-settings-daemon to go back to the normal | ||||
|         //   policies for blanking | ||||
|         //   (they're handled by the same code, and we emit one extra ActiveChanged | ||||
|         //   signal in the case above) | ||||
|         // - we're called after showing the lightbox and after lock-delay; the | ||||
|         //   session is effectivelly locked now, it's time to build and show | ||||
|         //   the lock screen | ||||
|  | ||||
|         this.idleMonitor.remove_watch(this._becameActiveId); | ||||
|         this._becameActiveId = 0; | ||||
|  | ||||
|         if (this._isActive || this._isLocked) { | ||||
|             this._longLightbox.hide(); | ||||
|             this._shortLightbox.hide(); | ||||
|         } else { | ||||
|         let lightboxWasShown = this._lightbox.shown; | ||||
|         this._lightbox.hide(); | ||||
|  | ||||
|         // Shortcircuit in case the mouse was moved before the fade completed | ||||
|         if (!lightboxWasShown) { | ||||
|             this.deactivate(false); | ||||
|             return; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onLongLightboxShown: function() { | ||||
|     _onLightboxShown: function() { | ||||
|         this.activate(false); | ||||
|     }, | ||||
|  | ||||
|     _onShortLightboxShown: function() { | ||||
|         this._completeLockScreenShown(); | ||||
|     }, | ||||
|  | ||||
|     showDialog: function() { | ||||
|         // Ensure that the stage window is mapped, before taking a grab | ||||
|         // otherwise X errors out | ||||
| @@ -920,8 +845,23 @@ const ScreenShield = new Lang.Class({ | ||||
|         this.actor.show(); | ||||
|         this._isGreeter = Main.sessionMode.isGreeter; | ||||
|         this._isLocked = true; | ||||
|         if (this._ensureUnlockDialog(true, true)) | ||||
|             this._hideLockScreen(false, 0); | ||||
|         this._ensureUnlockDialog(true, true); | ||||
|         this._hideLockScreen(false, 0); | ||||
|     }, | ||||
|  | ||||
|     _bumpLockScreen: function() { | ||||
|         Tweener.removeTweens(this._lockScreenGroup); | ||||
|         Tweener.addTween(this._lockScreenGroup, | ||||
|                          { y: -BUMP_SIZE, | ||||
|                            time: BUMP_TIME / 2, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            onComplete: function() { | ||||
|                                Tweener.addTween(this, | ||||
|                                                 { y: 0, | ||||
|                                                   time: BUMP_TIME / 2, | ||||
|                                                   transition: 'easeInQuad' }); | ||||
|                            } | ||||
|                          }); | ||||
|     }, | ||||
|  | ||||
|     _hideLockScreenComplete: function() { | ||||
| @@ -938,8 +878,6 @@ const ScreenShield = new Lang.Class({ | ||||
|  | ||||
|         this._lockScreenState = MessageTray.State.HIDING; | ||||
|  | ||||
|         Tweener.removeTweens(this._lockScreenGroup); | ||||
|  | ||||
|         if (animate) { | ||||
|             // Tween the lock screen out of screen | ||||
|             // if velocity is not specified (i.e. we come here from pressing ESC), | ||||
| @@ -952,6 +890,7 @@ const ScreenShield = new Lang.Class({ | ||||
|             velocity = Math.max(min_velocity, velocity); | ||||
|             let time = (delta / velocity) / 1000; | ||||
|  | ||||
|             Tweener.removeTweens(this._lockScreenGroup); | ||||
|             Tweener.addTween(this._lockScreenGroup, | ||||
|                              { y: -h, | ||||
|                                time: time, | ||||
| @@ -962,7 +901,7 @@ const ScreenShield = new Lang.Class({ | ||||
|             this._hideLockScreenComplete(); | ||||
|         } | ||||
|  | ||||
|         this._cursorTracker.set_pointer_visible(true); | ||||
|         global.stage.show_cursor(); | ||||
|     }, | ||||
|  | ||||
|     _ensureUnlockDialog: function(onPrimary, allowCancel) { | ||||
| @@ -971,34 +910,38 @@ const ScreenShield = new Lang.Class({ | ||||
|             if (!constructor) { | ||||
|                 // This session mode has no locking capabilities | ||||
|                 this.deactivate(true); | ||||
|                 return false; | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this._dialog = new constructor(this._lockDialogGroup); | ||||
|  | ||||
|  | ||||
|             let time = global.get_current_time(); | ||||
|             if (!this._dialog.open(time, onPrimary)) { | ||||
|                 // This is kind of an impossible error: we're already modal | ||||
|                 // by the time we reach this... | ||||
|                 log('Could not open login dialog: failed to acquire grab'); | ||||
|                 this.deactivate(true); | ||||
|                 return false; | ||||
|             } | ||||
|             this._dialog.connect('loaded', Lang.bind(this, function() { | ||||
|                 if (!this._dialog.open(time, onPrimary)) { | ||||
|                     // This is kind of an impossible error: we're already modal | ||||
|                     // by the time we reach this... | ||||
|                     log('Could not open login dialog: failed to acquire grab'); | ||||
|                     this.deactivate(true); | ||||
|                 } | ||||
|             })); | ||||
|  | ||||
|             this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed)); | ||||
|             this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded)); | ||||
|         } | ||||
|  | ||||
|         this._dialog.allowCancel = allowCancel; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _onUnlockFailed: function() { | ||||
|         this._resetLockScreen({ animateLockScreen: true, | ||||
|                                 fadeToBlack: false }); | ||||
|         this._resetLockScreen(true, false); | ||||
|     }, | ||||
|  | ||||
|     _resetLockScreen: function(params) { | ||||
|     _onUnlockSucceded: function() { | ||||
|         this.deactivate(true); | ||||
|     }, | ||||
|  | ||||
|     _resetLockScreen: function(animateLockScreen, animateLockDialog) { | ||||
|         // Don't reset the lock screen unless it is completely hidden | ||||
|         // This prevents the shield going down if the lock-delay timeout | ||||
|         // fires while the user is dragging (which has the potential | ||||
| @@ -1013,9 +956,7 @@ const ScreenShield = new Lang.Class({ | ||||
|         this._lockScreenGroup.show(); | ||||
|         this._lockScreenState = MessageTray.State.SHOWING; | ||||
|  | ||||
|         let fadeToBlack = params.fadeToBlack; | ||||
|  | ||||
|         if (params.animateLockScreen) { | ||||
|         if (animateLockScreen) { | ||||
|             this._lockScreenGroup.y = -global.screen_height; | ||||
|             Tweener.removeTweens(this._lockScreenGroup); | ||||
|             Tweener.addTween(this._lockScreenGroup, | ||||
| @@ -1023,15 +964,24 @@ const ScreenShield = new Lang.Class({ | ||||
|                                time: MANUAL_FADE_TIME, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: function() { | ||||
|                                    this._lockScreenShown({ fadeToBlack: fadeToBlack, | ||||
|                                                            animateFade: true }); | ||||
|                                    this._lockScreenShown(); | ||||
|                                }, | ||||
|                                onCompleteScope: this | ||||
|                              }); | ||||
|         } else { | ||||
|             this._lockScreenGroup.fixed_position_set = false; | ||||
|             this._lockScreenShown({ fadeToBlack: fadeToBlack, | ||||
|                                     animateFade: false }); | ||||
|             this._lockScreenShown(); | ||||
|         } | ||||
|  | ||||
|         if (animateLockDialog) { | ||||
|             this._lockDialogGroup.opacity = 0; | ||||
|             Tweener.removeTweens(this._lockDialogGroup); | ||||
|             Tweener.addTween(this._lockDialogGroup, | ||||
|                              { opacity: 255, | ||||
|                                time: MANUAL_FADE_TIME, | ||||
|                                transition: 'easeOutQuad' }); | ||||
|         } else { | ||||
|             this._lockDialogGroup.opacity = 255; | ||||
|         } | ||||
|  | ||||
|         this._lockScreenGroup.grab_key_focus(); | ||||
| @@ -1040,98 +990,38 @@ const ScreenShield = new Lang.Class({ | ||||
|             Main.sessionMode.pushMode('lock-screen'); | ||||
|     }, | ||||
|  | ||||
|     _startArrowAnimation: function() { | ||||
|         this._arrowActiveWatchId = 0; | ||||
|  | ||||
|         if (!this._arrowAnimationId) { | ||||
|             this._arrowAnimationId = Mainloop.timeout_add(6000, Lang.bind(this, this._animateArrows)); | ||||
|             this._animateArrows(); | ||||
|         } | ||||
|  | ||||
|         if (!this._arrowWatchId) | ||||
|             this._arrowWatchId = this.idleMonitor.add_idle_watch(ARROW_IDLE_TIME, | ||||
|                                                                  Lang.bind(this, this._pauseArrowAnimation)); | ||||
|     }, | ||||
|  | ||||
|     _pauseArrowAnimation: function() { | ||||
|         if (this._arrowAnimationId) { | ||||
|             Mainloop.source_remove(this._arrowAnimationId); | ||||
|             this._arrowAnimationId = 0; | ||||
|         } | ||||
|  | ||||
|         if (!this._arrowActiveWatchId) | ||||
|             this._arrowActiveWatchId = this.idleMonitor.add_user_active_watch(Lang.bind(this, this._startArrowAnimation)); | ||||
|     }, | ||||
|  | ||||
|     _stopArrowAnimation: function() { | ||||
|         if (this._arrowAnimationId) { | ||||
|             Mainloop.source_remove(this._arrowAnimationId); | ||||
|             this._arrowAnimationId = 0; | ||||
|         } | ||||
|         if (this._arrowActiveWatchId) { | ||||
|             this.idleMonitor.remove_watch(this._arrowActiveWatchId); | ||||
|             this._arrowActiveWatchId = 0; | ||||
|         } | ||||
|         if (this._arrowWatchId) { | ||||
|             this.idleMonitor.remove_watch(this._arrowWatchId); | ||||
|             this._arrowWatchId = 0; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _checkArrowAnimation: function() { | ||||
|         let idleTime = this.idleMonitor.get_idletime(); | ||||
|  | ||||
|         if (idleTime < ARROW_IDLE_TIME) | ||||
|             this._startArrowAnimation(); | ||||
|         else | ||||
|             this._pauseArrowAnimation(); | ||||
|     }, | ||||
|  | ||||
|     _lockScreenShown: function(params) { | ||||
|     _lockScreenShown: function() { | ||||
|         if (this._dialog && !this._isGreeter) { | ||||
|             this._dialog.destroy(); | ||||
|             this._dialog = null; | ||||
|         } | ||||
|  | ||||
|         this._checkArrowAnimation(); | ||||
|         if (this._arrowAnimationId) | ||||
|             Mainloop.source_remove(this._arrowAnimationId); | ||||
|         this._arrowAnimationId = Mainloop.timeout_add(6000, Lang.bind(this, this._animateArrows)); | ||||
|         this._animateArrows(); | ||||
|  | ||||
|         let motionId = global.stage.connect('captured-event', Lang.bind(this, function(stage, event) { | ||||
|         let motionId = global.stage.connect('captured-event', function(stage, event) { | ||||
|             if (event.type() == Clutter.EventType.MOTION) { | ||||
|                 this._cursorTracker.set_pointer_visible(true); | ||||
|                 global.stage.show_cursor(); | ||||
|                 global.stage.disconnect(motionId); | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|         })); | ||||
|         this._cursorTracker.set_pointer_visible(false); | ||||
|         }); | ||||
|         global.stage.hide_cursor(); | ||||
|  | ||||
|         this._lockScreenState = MessageTray.State.SHOWN; | ||||
|         this._lockScreenGroup.fixed_position_set = false; | ||||
|         this._lockScreenScrollCounter = 0; | ||||
|  | ||||
|         if (params.fadeToBlack && params.animateFade) { | ||||
|             // Take a beat | ||||
|  | ||||
|             Mainloop.timeout_add(1000 * MANUAL_FADE_TIME, Lang.bind(this, function() { | ||||
|                 this._activateFade(this._shortLightbox, MANUAL_FADE_TIME); | ||||
|             })); | ||||
|         } else { | ||||
|             if (params.fadeToBlack) | ||||
|                 this._activateFade(this._shortLightbox, 0); | ||||
|  | ||||
|             this._completeLockScreenShown(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _completeLockScreenShown: function() { | ||||
|         let prevIsActive = this._isActive; | ||||
|         this._isActive = true; | ||||
|  | ||||
|         if (prevIsActive != this._isActive) | ||||
|             this.emit('active-changed'); | ||||
|  | ||||
|         if (this._aboutToSuspend) | ||||
|             this._uninhibitSuspend(); | ||||
|         this._uninhibitSuspend(); | ||||
|  | ||||
|         this.emit('lock-screen-shown'); | ||||
|     }, | ||||
| @@ -1171,10 +1061,13 @@ const ScreenShield = new Lang.Class({ | ||||
|             this._notificationsBox = null; | ||||
|         } | ||||
|  | ||||
|         this._stopArrowAnimation(); | ||||
|  | ||||
|         this._lockScreenContentsBox.destroy(); | ||||
|  | ||||
|         if (this._arrowAnimationId) { | ||||
|             Mainloop.source_remove(this._arrowAnimationId); | ||||
|             this._arrowAnimationId = 0; | ||||
|         } | ||||
|  | ||||
|         this._hasLockScreen = false; | ||||
|     }, | ||||
|  | ||||
| @@ -1191,47 +1084,13 @@ const ScreenShield = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     deactivate: function(animate) { | ||||
|         if (this._dialog) | ||||
|             this._dialog.finish(Lang.bind(this, function() { | ||||
|                 this._continueDeactivate(animate); | ||||
|             })); | ||||
|         else | ||||
|             this._continueDeactivate(animate); | ||||
|     }, | ||||
|  | ||||
|     _continueDeactivate: function(animate) { | ||||
|         this._hideLockScreen(animate, 0); | ||||
|  | ||||
|         if (this._hasLockScreen) | ||||
|             this._clearLockScreen(); | ||||
|  | ||||
|         if (Main.sessionMode.currentMode == 'lock-screen') | ||||
|             Main.sessionMode.popMode('lock-screen'); | ||||
|         if (Main.sessionMode.currentMode == 'unlock-dialog') | ||||
|             Main.sessionMode.popMode('unlock-dialog'); | ||||
|  | ||||
|         if (this._isGreeter) { | ||||
|             // We don't want to "deactivate" any more than | ||||
|             // this. In particular, we don't want to drop | ||||
|             // the modal, hide ourselves or destroy the dialog | ||||
|             // But we do want to set isActive to false, so that | ||||
|             // gnome-session will reset the idle counter, and | ||||
|             // gnome-settings-daemon will stop blanking the screen | ||||
|  | ||||
|             this._activationTime = 0; | ||||
|             this._isActive = false; | ||||
|             this.emit('active-changed'); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (this._dialog && !this._isGreeter) | ||||
|             this._dialog.popModal(); | ||||
|  | ||||
|         if (this._isModal) { | ||||
|             Main.popModal(this.actor); | ||||
|             this._isModal = false; | ||||
|         } | ||||
|  | ||||
|         Tweener.addTween(this._lockDialogGroup, { | ||||
|             scale_x: 0, | ||||
|             scale_y: 0, | ||||
| @@ -1243,13 +1102,21 @@ const ScreenShield = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _completeDeactivate: function() { | ||||
|         if (this._dialog) { | ||||
|         if (this._hasLockScreen) | ||||
|             this._clearLockScreen(); | ||||
|  | ||||
|         if (this._dialog && !this._isGreeter) { | ||||
|             this._dialog.destroy(); | ||||
|             this._dialog = null; | ||||
|         } | ||||
|  | ||||
|         this._longLightbox.hide(); | ||||
|         this._shortLightbox.hide(); | ||||
|         this._lightbox.hide(); | ||||
|  | ||||
|         if (this._isModal) { | ||||
|             Main.popModal(this.actor); | ||||
|             this._isModal = false; | ||||
|         } | ||||
|  | ||||
|         this.actor.hide(); | ||||
|  | ||||
|         if (this._becameActiveId != 0) { | ||||
| @@ -1267,7 +1134,6 @@ const ScreenShield = new Lang.Class({ | ||||
|         this._isLocked = false; | ||||
|         this.emit('active-changed'); | ||||
|         this.emit('locked-changed'); | ||||
|         global.set_runtime_state(LOCKED_STATE_STR, null); | ||||
|     }, | ||||
|  | ||||
|     activate: function(animate) { | ||||
| @@ -1283,9 +1149,7 @@ const ScreenShield = new Lang.Class({ | ||||
|                 Main.sessionMode.pushMode('unlock-dialog'); | ||||
|         } | ||||
|  | ||||
|         this._resetLockScreen({ animateLockScreen: animate, | ||||
|                                 fadeToBlack: true }); | ||||
|         global.set_runtime_state(LOCKED_STATE_STR, GLib.Variant.new('b', true)); | ||||
|         this._resetLockScreen(animate, animate); | ||||
|  | ||||
|         // We used to set isActive and emit active-changed here, | ||||
|         // but now we do that from lockScreenShown, which means | ||||
| @@ -1307,33 +1171,69 @@ const ScreenShield = new Lang.Class({ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Clear the clipboard - otherwise, its contents may be leaked | ||||
|         // to unauthorized parties by pasting into the unlock dialog's | ||||
|         // password entry and unmasking the entry | ||||
|         St.Clipboard.get_default().set_text(St.ClipboardType.CLIPBOARD, ''); | ||||
|         St.Clipboard.get_default().set_text(St.ClipboardType.PRIMARY, ''); | ||||
|  | ||||
|         let userManager = AccountsService.UserManager.get_default(); | ||||
|         let user = userManager.get_user(GLib.get_user_name()); | ||||
|  | ||||
|         if (this._isGreeter) | ||||
|             this._isLocked = true; | ||||
|         else | ||||
|             this._isLocked = user.password_mode != AccountsService.UserPasswordMode.NONE; | ||||
|  | ||||
|         this._isLocked = true; | ||||
|         this.activate(animate); | ||||
|  | ||||
|         this.emit('locked-changed'); | ||||
|     }, | ||||
|  | ||||
|     // If the previous shell crashed, and gnome-session restarted us, then re-lock | ||||
|     lockIfWasLocked: function() { | ||||
|         let wasLocked = global.get_runtime_state('b', LOCKED_STATE_STR); | ||||
|         if (wasLocked === null) | ||||
|             return; | ||||
|         Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { | ||||
|             this.lock(false); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ScreenShield.prototype); | ||||
|  | ||||
| /* Fallback code to handle session locking using gnome-screensaver, | ||||
|    in case the required GDM dependency is not there | ||||
| */ | ||||
| const ScreenShieldFallback = new Lang.Class({ | ||||
|     Name: 'ScreenShieldFallback', | ||||
|  | ||||
|     _init: function() { | ||||
|         Util.spawn(['gnome-screensaver']); | ||||
|  | ||||
|         this._proxy = new Gio.DBusProxy({ g_connection: Gio.DBus.session, | ||||
|                                           g_name: 'org.gnome.ScreenSaver', | ||||
|                                           g_object_path: '/org/gnome/ScreenSaver', | ||||
|                                           g_interface_name: 'org.gnome.ScreenSaver', | ||||
|                                           g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START | | ||||
|                                                     Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES), | ||||
|                                         }); | ||||
|         this._proxy.init(null); | ||||
|  | ||||
|         this._proxy.connect('g-signal', Lang.bind(this, this._onSignal)); | ||||
|         this._proxy.connect('notify::g-name-owner', Lang.bind(this, this._onNameOwnerChanged)); | ||||
|     }, | ||||
|  | ||||
|     _onNameOwnerChanged: function(object, pspec) { | ||||
|         if (this._proxy.g_name_owner) | ||||
|             [this._locked] = this._proxy.call_sync('GetActive', null, | ||||
|                                                    Gio.DBusCallFlags.NONE, -1, null).deep_unpack(); | ||||
|         else | ||||
|             this._locked = false; | ||||
|  | ||||
|         this.emit('active-changed', this._locked); | ||||
|     }, | ||||
|  | ||||
|     _onSignal: function(proxy, senderName, signalName, params) { | ||||
|         if (signalName == 'ActiveChanged') { | ||||
|             [this._locked] = params.deep_unpack(); | ||||
|             this.emit('active-changed', this._locked); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     get locked() { | ||||
|         return this._locked; | ||||
|     }, | ||||
|  | ||||
|     lock: function() { | ||||
|         this._proxy.call('Lock', null, Gio.DBusCallFlags.NONE, -1, null, | ||||
|                          Lang.bind(this, function(proxy, result) { | ||||
|                              proxy.call_finish(result); | ||||
|  | ||||
|                              this.emit('lock-screen-shown'); | ||||
|                          })); | ||||
|     }, | ||||
|  | ||||
|     unlock: function() { | ||||
|         this._proxy.call('SetActive', GLib.Variant.new('(b)', false), | ||||
|                          Gio.DBusCallFlags.NONE, -1, null, null); | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(ScreenShieldFallback.prototype); | ||||
|   | ||||
| @@ -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 Hash = imports.misc.hash; | ||||
| const Main = imports.ui.main; | ||||
|  | ||||
| const ScreencastIface = <interface name="org.gnome.Shell.Screencast"> | ||||
| <method name="Screencast"> | ||||
|     <arg type="s" direction="in" name="file_template"/> | ||||
|     <arg type="a{sv}" direction="in" name="options"/> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
|     <arg type="s" direction="out" name="filename_used"/> | ||||
| </method> | ||||
| <method name="ScreencastArea"> | ||||
|     <arg type="i" direction="in" name="x"/> | ||||
|     <arg type="i" direction="in" name="y"/> | ||||
|     <arg type="i" direction="in" name="width"/> | ||||
|     <arg type="i" direction="in" name="height"/> | ||||
|     <arg type="s" direction="in" name="file_template"/> | ||||
|     <arg type="a{sv}" direction="in" name="options"/> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
|     <arg type="s" direction="out" name="filename_used"/> | ||||
| </method> | ||||
| <method name="StopScreencast"> | ||||
|     <arg type="b" direction="out" name="success"/> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const ScreencastService = new Lang.Class({ | ||||
|     Name: 'ScreencastService', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreencastIface, this); | ||||
|         this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screencast'); | ||||
|  | ||||
|         Gio.DBus.session.own_name('org.gnome.Shell.Screencast', Gio.BusNameOwnerFlags.REPLACE, null, null); | ||||
|  | ||||
|         this._recorders = new Hash.Map(); | ||||
|  | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); | ||||
|     }, | ||||
|  | ||||
|     get isRecording() { | ||||
|         return this._recorders.size() > 0; | ||||
|     }, | ||||
|  | ||||
|     _ensureRecorderForSender: function(sender) { | ||||
|         let recorder = this._recorders.get(sender); | ||||
|         if (!recorder) { | ||||
|             recorder = new Shell.Recorder({ stage: global.stage, | ||||
|                                             screen: global.screen }); | ||||
|             recorder._watchNameId = | ||||
|                 Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null, | ||||
|                                    Lang.bind(this, this._onNameVanished)); | ||||
|             this._recorders.set(sender, recorder); | ||||
|             this.emit('updated'); | ||||
|         } | ||||
|         return recorder; | ||||
|     }, | ||||
|  | ||||
|     _sessionUpdated: function() { | ||||
|         if (Main.sessionMode.allowScreencast) | ||||
|             return; | ||||
|  | ||||
|         for (let sender in this._recorders.keys()) | ||||
|             this._recorders.delete(sender); | ||||
|         this.emit('updated'); | ||||
|     }, | ||||
|  | ||||
|     _onNameVanished: function(connection, name) { | ||||
|         this._stopRecordingForSender(name); | ||||
|     }, | ||||
|  | ||||
|     _stopRecordingForSender: function(sender) { | ||||
|         let recorder = this._recorders.get(sender); | ||||
|         if (!recorder) | ||||
|             return false; | ||||
|  | ||||
|         Gio.bus_unwatch_name(recorder._watchNameId); | ||||
|         recorder.close(); | ||||
|         this._recorders.delete(sender); | ||||
|         this.emit('updated'); | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _applyOptionalParameters: function(recorder, options) { | ||||
|         for (let option in options) | ||||
|             options[option] = options[option].deep_unpack(); | ||||
|  | ||||
|         if (options['pipeline']) | ||||
|             recorder.set_pipeline(options['pipeline']); | ||||
|         if (options['framerate']) | ||||
|             recorder.set_framerate(options['framerate']); | ||||
|         if (options['draw-cursor']) | ||||
|             recorder.set_draw_cursor(options['draw-cursor']); | ||||
|     }, | ||||
|  | ||||
|     ScreencastAsync: function(params, invocation) { | ||||
|         let returnValue = [false, '']; | ||||
|         if (!Main.sessionMode.allowScreencast) { | ||||
|             invocation.return_value(GLib.Variant.new('(bs)', returnValue)); | ||||
|             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; | ||||
| @@ -77,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; | ||||
| @@ -170,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; | ||||
|     }, | ||||
|  | ||||
| @@ -241,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); | ||||
|                                }) | ||||
|   | ||||
| @@ -31,7 +31,7 @@ const SearchSystem = new Lang.Class({ | ||||
|  | ||||
|         let remoteIndex = this._remoteProviders.indexOf(provider); | ||||
|         if (remoteIndex != -1) | ||||
|             this._remoteProviders.splice(remoteIndex, 1); | ||||
|             this._remoteProviders.splice(index, 1); | ||||
|     }, | ||||
|  | ||||
|     getProviders: function() { | ||||
| @@ -51,7 +51,7 @@ const SearchSystem = new Lang.Class({ | ||||
|         this._previousResults = []; | ||||
|     }, | ||||
|  | ||||
|     setResults: function(provider, results) { | ||||
|     pushResults: function(provider, results) { | ||||
|         let i = this._providers.indexOf(provider); | ||||
|         if (i == -1) | ||||
|             return; | ||||
|   | ||||
| @@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
| @@ -14,7 +13,6 @@ const Main = imports.ui.main; | ||||
| const Overview = imports.ui.overview; | ||||
| const Separator = imports.ui.separator; | ||||
| const Search = imports.ui.search; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| const MAX_LIST_SEARCH_RESULTS_ROWS = 3; | ||||
| const MAX_GRID_SEARCH_RESULTS_ROWS = 1; | ||||
| @@ -104,7 +102,6 @@ const ListSearchResult = new Lang.Class({ | ||||
|                              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' }); | ||||
| @@ -126,25 +123,22 @@ const GridSearchResult = new Lang.Class({ | ||||
|  | ||||
|         this.actor.style_class = 'grid-search-result'; | ||||
|  | ||||
|         let content = provider.createResultObject(metaInfo, terms); | ||||
|         let content = provider.createResultActor(metaInfo, terms); | ||||
|         let dragSource = null; | ||||
|  | ||||
|         if (content == null) { | ||||
|             let actor = new St.Bin(); | ||||
|             content = 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; | ||||
|             content.set_child(icon.actor); | ||||
|             content.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; | ||||
|         this.actor.set_child(content); | ||||
|  | ||||
|         let draggable = DND.makeDraggable(this.actor); | ||||
|         draggable.connect('drag-begin', | ||||
| @@ -182,170 +176,137 @@ const GridSearchResult = new Lang.Class({ | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const SearchResultsBase = new Lang.Class({ | ||||
|     Name: 'SearchResultsBase', | ||||
| const ListSearchResults = new Lang.Class({ | ||||
|     Name: 'ListSearchResults', | ||||
|  | ||||
|     _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); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this.actor.destroy(); | ||||
|         this._terms = []; | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function() { | ||||
|     }, | ||||
|  | ||||
|     clear: function() { | ||||
|         this._clearResultDisplay(); | ||||
|         this.actor.hide(); | ||||
|     }, | ||||
|  | ||||
|     _keyFocusIn: function(actor) { | ||||
|         this.emit('key-focus-in', actor); | ||||
|     }, | ||||
|  | ||||
|     _setMoreIconVisible: function(visible) { | ||||
|     }, | ||||
|  | ||||
|     updateSearch: function(providerResults, terms, callback) { | ||||
|         this._terms = terms; | ||||
|  | ||||
|         if (providerResults.length == 0) { | ||||
|             this._clearResultDisplay(); | ||||
|             this.actor.hide(); | ||||
|             callback(); | ||||
|         } else { | ||||
|             let maxResults = this._getMaxDisplayedResults(); | ||||
|             let results = this.provider.filterResults(providerResults, maxResults); | ||||
|             let hasMoreResults = results.length < providerResults.length; | ||||
|  | ||||
|             this.provider.getResultMetas(results, Lang.bind(this, function(metas) { | ||||
|                 this.clear(); | ||||
|  | ||||
|                 // To avoid CSS transitions causing flickering when | ||||
|                 // the first search result stays the same, we hide the | ||||
|                 // content while filling in the results. | ||||
|                 this.actor.hide(); | ||||
|                 this._clearResultDisplay(); | ||||
|                 this._renderResults(metas); | ||||
|                 this._setMoreIconVisible(hasMoreResults && this.provider.canLaunchSearch); | ||||
|                 this.actor.show(); | ||||
|                 callback(); | ||||
|             })); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ListSearchResults = new Lang.Class({ | ||||
|     Name: 'ListSearchResults', | ||||
|     Extends: SearchResultsBase, | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.parent(provider); | ||||
|  | ||||
|         this._container = new St.BoxLayout({ style_class: 'search-section-content' }); | ||||
|         this.actor = 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.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._container.add(this._content, { expand: true }); | ||||
|         this.actor.add(this._content, { expand: true }); | ||||
|  | ||||
|         this._resultDisplayBin.set_child(this._container); | ||||
|         this._notDisplayedResult = []; | ||||
|         this._terms = []; | ||||
|         this._pendingClear = false; | ||||
|     }, | ||||
|  | ||||
|     _setMoreIconVisible: function(visible) { | ||||
|         this.providerIcon.moreIcon.visible = true; | ||||
|     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; | ||||
|     }, | ||||
|  | ||||
|     _getMaxDisplayedResults: function() { | ||||
|         return MAX_LIST_SEARCH_RESULTS_ROWS; | ||||
|     getVisibleResultCount: function() { | ||||
|         return this._content.get_n_children(); | ||||
|     }, | ||||
|  | ||||
|     _renderResults: function(metas) { | ||||
|     hasMoreResults: function() { | ||||
|         return this._notDisplayedResult.length > 0; | ||||
|     }, | ||||
|  | ||||
|     setResults: function(results, terms) { | ||||
|         // copy the lists | ||||
|         this._notDisplayedResult = results.slice(0); | ||||
|         this._terms = terms.slice(0); | ||||
|         this._pendingClear = true; | ||||
|     }, | ||||
|  | ||||
|     renderResults: function(metas) { | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             let display = new ListSearchResult(this.provider, metas[i], this._terms); | ||||
|             display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|             this._content.add_actor(display.actor); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function () { | ||||
|     clear: function () { | ||||
|         this._content.destroy_all_children(); | ||||
|         this._pendingClear = false; | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this._content.get_n_children() > 0) | ||||
|         if (this.getVisibleResultCount() > 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.provider = 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.actor = new St.Bin({ x_align: St.Align.MIDDLE }); | ||||
|  | ||||
|         this._resultDisplayBin.set_child(this._bin); | ||||
|         this.actor.set_child(this._grid.actor); | ||||
|  | ||||
|         this._notDisplayedResult = []; | ||||
|         this._terms = []; | ||||
|         this._pendingClear = false; | ||||
|     }, | ||||
|  | ||||
|     _getMaxDisplayedResults: function() { | ||||
|         return this._grid.columnsForWidth(this._bin.width) * this._grid.getRowLimit(); | ||||
|     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; | ||||
|     }, | ||||
|  | ||||
|     _renderResults: function(metas) { | ||||
|     getVisibleResultCount: function() { | ||||
|         return this._grid.visibleItemsCount(); | ||||
|     }, | ||||
|  | ||||
|     hasMoreResults: function() { | ||||
|         return this._notDisplayedResult.length > 0; | ||||
|     }, | ||||
|  | ||||
|     setResults: function(results, terms) { | ||||
|         // copy the lists | ||||
|         this._notDisplayedResult = results.slice(0); | ||||
|         this._terms = terms.slice(0); | ||||
|         this._pendingClear = true; | ||||
|     }, | ||||
|  | ||||
|     renderResults: function(metas) { | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             let display = new GridSearchResult(this.provider, metas[i], this._terms); | ||||
|             display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|             this._grid.addItem(display); | ||||
|             this._grid.addItem(display.actor); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _clearResultDisplay: function () { | ||||
|     clear: function () { | ||||
|         this._grid.removeAll(); | ||||
|         this._pendingClear = false; | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this._grid.visibleItemsCount() > 0) | ||||
|         if (this.getVisibleResultCount() > 0) | ||||
|             return this._grid.getItemAtIndex(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(GridSearchResults.prototype); | ||||
|  | ||||
| const SearchResults = new Lang.Class({ | ||||
|     Name: 'SearchResults', | ||||
| @@ -389,9 +350,9 @@ const SearchResults = new Lang.Class({ | ||||
|         this._content.add(this._statusBin, { expand: true }); | ||||
|         this._statusBin.add_actor(this._statusText); | ||||
|         this._providers = this._searchSystem.getProviders(); | ||||
|         this._providerDisplays = {}; | ||||
|         this._providerMeta = []; | ||||
|         for (let i = 0; i < this._providers.length; i++) { | ||||
|             this.createProviderDisplay(this._providers[i]); | ||||
|             this.createProviderMeta(this._providers[i]); | ||||
|         } | ||||
|  | ||||
|         this._highlightDefault = false; | ||||
| @@ -405,37 +366,59 @@ const SearchResults = new Lang.Class({ | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _keyFocusIn: function(provider, actor) { | ||||
|         Util.ensureActorVisibleInScrollView(this._scrollView, actor); | ||||
|     }, | ||||
|  | ||||
|     createProviderDisplay: function(provider) { | ||||
|         let providerDisplay = null; | ||||
|     createProviderMeta: function(provider) { | ||||
|         let providerBox = new St.BoxLayout({ style_class: 'search-section', | ||||
|                                              vertical: true }); | ||||
|         let providerIcon = null; | ||||
|         let resultDisplay = null; | ||||
|  | ||||
|         if (provider.appInfo) { | ||||
|             providerDisplay = new ListSearchResults(provider); | ||||
|             resultDisplay = new ListSearchResults(provider); | ||||
|             providerIcon = resultDisplay.providerIcon; | ||||
|         } else { | ||||
|             providerDisplay = new GridSearchResults(provider); | ||||
|             resultDisplay = new GridSearchResults(provider); | ||||
|         } | ||||
|  | ||||
|         providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|         this._providerDisplays[provider.id] = providerDisplay; | ||||
|         this._content.add(providerDisplay.actor); | ||||
|         let resultDisplayBin = new St.Bin({ child: resultDisplay.actor, | ||||
|                                             x_fill: true, | ||||
|                                             y_fill: true }); | ||||
|         providerBox.add(resultDisplayBin, { expand: true }); | ||||
|  | ||||
|         let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' }); | ||||
|         providerBox.add(separator.actor); | ||||
|  | ||||
|         this._providerMeta.push({ provider: provider, | ||||
|                                   actor: providerBox, | ||||
|                                   icon: providerIcon, | ||||
|                                   resultDisplay: resultDisplay }); | ||||
|         this._content.add(providerBox); | ||||
|     }, | ||||
|  | ||||
|     destroyProviderDisplay: function(provider) { | ||||
|         this._providerDisplays[provider.id].destroy(); | ||||
|         delete this._providerDisplays[provider.id]; | ||||
|     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._providers.length; i++) { | ||||
|             let provider = this._providers[i]; | ||||
|             let providerDisplay = this._providerDisplays[provider.id]; | ||||
|             providerDisplay.clear(); | ||||
|         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(); | ||||
| @@ -449,17 +432,20 @@ const SearchResults = new Lang.Class({ | ||||
|         this._statusBin.show(); | ||||
|     }, | ||||
|  | ||||
|     _metaForProvider: function(provider) { | ||||
|         return this._providerMeta[this._providers.indexOf(provider)]; | ||||
|     }, | ||||
|  | ||||
|     _maybeSetInitialSelection: function() { | ||||
|         let newDefaultResult = null; | ||||
|  | ||||
|         for (let i = 0; i < this._providers.length; i++) { | ||||
|             let provider = this._providers[i]; | ||||
|             let display = this._providerDisplays[provider.id]; | ||||
|         for (let i = 0; i < this._providerMeta.length; i++) { | ||||
|             let meta = this._providerMeta[i]; | ||||
|  | ||||
|             if (!display.actor.visible) | ||||
|             if (!meta.actor.visible) | ||||
|                 continue; | ||||
|  | ||||
|             let firstResult = display.getFirstResult(); | ||||
|             let firstResult = meta.resultDisplay.getFirstResult(); | ||||
|             if (firstResult) { | ||||
|                 newDefaultResult = firstResult; | ||||
|                 break; // select this one! | ||||
| @@ -479,14 +465,11 @@ const SearchResults = new Lang.Class({ | ||||
|     _updateStatusText: function () { | ||||
|         let haveResults = false; | ||||
|  | ||||
|         for (let i = 0; i < this._providers.length; i++) { | ||||
|             let provider = this._providers[i]; | ||||
|             let display = this._providerDisplays[provider.id]; | ||||
|             if (display.getFirstResult()) { | ||||
|         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.")); | ||||
| @@ -499,12 +482,42 @@ const SearchResults = new Lang.Class({ | ||||
|     _updateResults: function(searchSystem, results) { | ||||
|         let terms = searchSystem.getTerms(); | ||||
|         let [provider, providerResults] = results; | ||||
|         let display = this._providerDisplays[provider.id]; | ||||
|         let meta = this._metaForProvider(provider); | ||||
|  | ||||
|         display.updateSearch(providerResults, terms, Lang.bind(this, function() { | ||||
|         if (providerResults.length == 0) { | ||||
|             this._clearDisplayForProvider(provider); | ||||
|             meta.resultDisplay.setResults([], []); | ||||
|             this._maybeSetInitialSelection(); | ||||
|             this._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() { | ||||
| @@ -544,7 +557,6 @@ const ProviderIcon = new Lang.Class({ | ||||
|         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() }); | ||||
|   | ||||
| @@ -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,11 +101,12 @@ 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'] | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| @@ -149,7 +158,12 @@ function listModes() { | ||||
| const SessionMode = new Lang.Class({ | ||||
|     Name: 'SessionMode', | ||||
|  | ||||
|     init: function() { | ||||
|     _init: function() { | ||||
|         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] && | ||||
| @@ -157,8 +171,6 @@ const SessionMode = new Lang.Class({ | ||||
|             let mode = primary ? global.session_mode : 'user'; | ||||
|             this._modeStack = [mode]; | ||||
|             this._sync(); | ||||
|  | ||||
|             this.emit('sessions-loaded'); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,6 @@ const ExtensionUtils = imports.misc.extensionUtils; | ||||
| const Hash = imports.misc.hash; | ||||
| const Main = imports.ui.main; | ||||
| const Screenshot = imports.ui.screenshot; | ||||
| const ViewSelector = imports.ui.viewSelector; | ||||
|  | ||||
| const GnomeShellIface = <interface name="org.gnome.Shell"> | ||||
| <method name="Eval"> | ||||
| @@ -21,14 +20,9 @@ const GnomeShellIface = <interface name="org.gnome.Shell"> | ||||
|     <arg type="b" direction="out" name="success" /> | ||||
|     <arg type="s" direction="out" name="result" /> | ||||
| </method> | ||||
| <method name="FocusSearch"/> | ||||
| <method name="ShowOSD"> | ||||
|     <arg type="a{sv}" direction="in" name="params"/> | ||||
| </method> | ||||
| <method name="FocusApp"> | ||||
|     <arg type="s" direction="in" name="id"/> | ||||
| </method> | ||||
| <method name="ShowApplications" /> | ||||
| <method name="GrabAccelerator"> | ||||
|     <arg type="s" direction="in" name="accelerator"/> | ||||
|     <arg type="u" direction="in" name="flags"/> | ||||
| @@ -45,7 +39,6 @@ const GnomeShellIface = <interface name="org.gnome.Shell"> | ||||
| <signal name="AcceleratorActivated"> | ||||
|     <arg name="action" type="u" /> | ||||
|     <arg name="deviceid" type="u" /> | ||||
|     <arg name="timestamp" type="u" /> | ||||
| </signal> | ||||
| <property name="Mode" type="s" access="read" /> | ||||
| <property name="OverviewActive" type="b" access="readwrite" /> | ||||
| @@ -83,8 +76,8 @@ const GnomeShell = new Lang.Class({ | ||||
|         this._grabbers = new Hash.Map(); | ||||
|  | ||||
|         global.display.connect('accelerator-activated', Lang.bind(this, | ||||
|             function(display, action, deviceid, timestamp) { | ||||
|                 this._emitAcceleratorActivated(action, deviceid, timestamp); | ||||
|             function(display, action, deviceid) { | ||||
|                 this._emitAcceleratorActivated(action, deviceid); | ||||
|             })); | ||||
|     }, | ||||
|  | ||||
| @@ -104,7 +97,7 @@ const GnomeShell = new Lang.Class({ | ||||
|      */ | ||||
|     Eval: function(code) { | ||||
|         if (!global.settings.get_boolean('development-tools')) | ||||
|             return [false, '']; | ||||
|             return [false, null]; | ||||
|  | ||||
|         let returnValue; | ||||
|         let success; | ||||
| @@ -121,10 +114,6 @@ const GnomeShell = new Lang.Class({ | ||||
|         return [success, returnValue]; | ||||
|     }, | ||||
|  | ||||
|     FocusSearch: function() { | ||||
|         Main.overview.focusSearch(); | ||||
|     }, | ||||
|  | ||||
|     ShowOSD: function(params) { | ||||
|         for (let param in params) | ||||
|             params[param] = params[param].deep_unpack(); | ||||
| @@ -140,15 +129,6 @@ const GnomeShell = new Lang.Class({ | ||||
|         Main.osdWindow.show(); | ||||
|     }, | ||||
|  | ||||
|     FocusApp: function(id) { | ||||
|         this.ShowApplications(); | ||||
|         Main.overview.viewSelector.appDisplay.selectApp(id); | ||||
|     }, | ||||
|  | ||||
|     ShowApplications: function() { | ||||
|         Main.overview.viewSelector.showApps(); | ||||
|     }, | ||||
|  | ||||
|     GrabAcceleratorAsync: function(params, invocation) { | ||||
|         let [accel, flags] = params; | ||||
|         let sender = invocation.get_sender(); | ||||
| @@ -179,7 +159,7 @@ const GnomeShell = new Lang.Class({ | ||||
|         return invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded])); | ||||
|     }, | ||||
|  | ||||
|     _emitAcceleratorActivated: function(action, deviceid, timestamp) { | ||||
|     _emitAcceleratorActivated: function(action, deviceid) { | ||||
|         let destination = this._grabbedAccelerators.get(action); | ||||
|         if (!destination) | ||||
|             return; | ||||
| @@ -190,7 +170,7 @@ const GnomeShell = new Lang.Class({ | ||||
|                                this._dbusImpl.get_object_path(), | ||||
|                                info ? info.name : null, | ||||
|                                'AcceleratorActivated', | ||||
|                                GLib.Variant.new('(uuu)', [action, deviceid, timestamp])); | ||||
|                                GLib.Variant.new('(uu)', [action, deviceid])); | ||||
|     }, | ||||
|  | ||||
|     _grabAcceleratorForSender: function(accelerator, flags, sender) { | ||||
| @@ -417,7 +397,7 @@ const ScreenSaverDBus = new Lang.Class({ | ||||
|         if (active) | ||||
|             this._screenShield.activate(true); | ||||
|         else | ||||
|             this._screenShield.deactivate(false); | ||||
|             this._screenShield.unlock(false); | ||||
|     }, | ||||
|  | ||||
|     GetActive: function() { | ||||
|   | ||||
| @@ -14,7 +14,9 @@ const EntryMenu = new Lang.Class({ | ||||
|     Name: 'ShellEntryMenu', | ||||
|     Extends: PopupMenu.PopupMenu, | ||||
|  | ||||
|     _init: function(entry) { | ||||
|     _init: function(entry, params) { | ||||
|         params = Params.parse (params, { isPassword: false }); | ||||
|  | ||||
|         this.parent(entry, 0, St.Side.TOP); | ||||
|  | ||||
|         this.actor.add_style_class_name('entry-context-menu'); | ||||
| @@ -35,6 +37,8 @@ const EntryMenu = new Lang.Class({ | ||||
|         this._pasteItem = item; | ||||
|  | ||||
|         this._passwordItem = null; | ||||
|         if (params.isPassword) | ||||
| 	    this._makePasswordItem(); | ||||
|  | ||||
|         Main.uiGroup.add_actor(this.actor); | ||||
|         this.actor.hide(); | ||||
| @@ -49,21 +53,19 @@ const EntryMenu = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     get isPassword() { | ||||
|         return this._passwordItem != null; | ||||
| 	return this._passwordItem != null; | ||||
|     }, | ||||
|  | ||||
|     set isPassword(v) { | ||||
|         if (v == this.isPassword) | ||||
|             return; | ||||
| 	if (v == this.isPassword) | ||||
| 	    return; | ||||
|  | ||||
|         if (v) { | ||||
|             this._makePasswordItem(); | ||||
|             this._entry.input_purpose = Gtk.InputPurpose.PASSWORD; | ||||
|         } else { | ||||
|             this._passwordItem.destroy(); | ||||
|             this._passwordItem = null; | ||||
|             this._entry.input_purpose = Gtk.InputPurpose.FREE_FORM; | ||||
|         } | ||||
| 	if (v) | ||||
| 	    this._makePasswordItem(); | ||||
| 	else { | ||||
| 	    this._passwordItem.destroy(); | ||||
| 	    this._passwordItem = null; | ||||
| 	} | ||||
|     }, | ||||
|  | ||||
|     open: function(animate) { | ||||
| @@ -80,6 +82,11 @@ const EntryMenu = new Lang.Class({ | ||||
|             this.actor.grab_key_focus(); | ||||
|     }, | ||||
|  | ||||
|     close: function(animate) { | ||||
|         this._entry.grab_key_focus(); | ||||
|         this.parent(animate); | ||||
|     }, | ||||
|  | ||||
|     _updateCopyItem: function() { | ||||
|         let selection = this._entry.clutter_text.get_selection(); | ||||
|         this._copyItem.setSensitive(!this._entry.clutter_text.password_char && | ||||
| @@ -153,10 +160,7 @@ function addContextMenu(entry, params) { | ||||
|     if (entry.menu) | ||||
|         return; | ||||
|  | ||||
|     params = Params.parse (params, { isPassword: false }); | ||||
|  | ||||
|     entry.menu = new EntryMenu(entry); | ||||
|     entry.menu.isPassword = params.isPassword; | ||||
|     entry.menu = new EntryMenu(entry, params); | ||||
|     entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry }); | ||||
|     entry._menuManager.addMenu(entry.menu); | ||||
|  | ||||
| @@ -167,10 +171,4 @@ function addContextMenu(entry, params) { | ||||
|     entry.connect('button-press-event', Lang.bind(null, _onButtonPressEvent, entry)); | ||||
|  | ||||
|     entry.connect('popup-menu', Lang.bind(null, _onPopup, entry)); | ||||
|  | ||||
|     entry.connect('destroy', function() { | ||||
|         entry.menu.destroy(); | ||||
|         entry.menu = null; | ||||
|         entry._menuManager = null; | ||||
|     }); | ||||
| } | ||||
|   | ||||
							
								
								
									
										247
									
								
								js/ui/slider.js
									
									
									
									
									
								
							
							
						
						| @@ -1,247 +0,0 @@ | ||||
| /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ | ||||
|  | ||||
| const Cairo = imports.cairo; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const St = imports.gi.St; | ||||
| const Signals = imports.signals; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
| const SLIDER_SCROLL_STEP = 0.05; /* Slider scrolling step in % */ | ||||
|  | ||||
| const Slider = new Lang.Class({ | ||||
|     Name: "Slider", | ||||
|  | ||||
|     _init: function(value) { | ||||
|         if (isNaN(value)) | ||||
|             // Avoid spreading NaNs around | ||||
|             throw TypeError('The slider value must be a number'); | ||||
|         this._value = Math.max(Math.min(value, 1), 0); | ||||
|  | ||||
|         this.actor = new St.DrawingArea({ style_class: 'slider', | ||||
|                                           can_focus: true, | ||||
|                                           reactive: true, | ||||
|                                           accessible_role: Atk.Role.SLIDER }); | ||||
|         this.actor.connect('repaint', Lang.bind(this, this._sliderRepaint)); | ||||
|         this.actor.connect('button-press-event', Lang.bind(this, this._startDragging)); | ||||
|         this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); | ||||
|         this.actor.connect('key-press-event', Lang.bind(this, this.onKeyPressEvent)); | ||||
|  | ||||
|         this._releaseId = this._motionId = 0; | ||||
|         this._dragging = false; | ||||
|  | ||||
|         this._customAccessible = St.GenericAccessible.new_for_actor(this.actor); | ||||
|         this.actor.set_accessible(this._customAccessible); | ||||
|  | ||||
|         this._customAccessible.connect('get-current-value', Lang.bind(this, this._getCurrentValue)); | ||||
|         this._customAccessible.connect('get-minimum-value', Lang.bind(this, this._getMinimumValue)); | ||||
|         this._customAccessible.connect('get-maximum-value', Lang.bind(this, this._getMaximumValue)); | ||||
|         this._customAccessible.connect('get-minimum-increment', Lang.bind(this, this._getMinimumIncrement)); | ||||
|         this._customAccessible.connect('set-current-value', Lang.bind(this, this._setCurrentValue)); | ||||
|  | ||||
|         this.connect('value-changed', Lang.bind(this, this._valueChanged)); | ||||
|     }, | ||||
|  | ||||
|     setValue: function(value) { | ||||
|         if (isNaN(value)) | ||||
|             throw TypeError('The slider value must be a number'); | ||||
|  | ||||
|         this._value = Math.max(Math.min(value, 1), 0); | ||||
|         this.actor.queue_repaint(); | ||||
|     }, | ||||
|  | ||||
|     _sliderRepaint: function(area) { | ||||
|         let cr = area.get_context(); | ||||
|         let themeNode = area.get_theme_node(); | ||||
|         let [width, height] = area.get_surface_size(); | ||||
|  | ||||
|         let handleRadius = themeNode.get_length('-slider-handle-radius'); | ||||
|  | ||||
|         let handleBorderWidth = themeNode.get_length('-slider-handle-border-width'); | ||||
|         let [hasHandleColor, handleBorderColor] = | ||||
|             themeNode.lookup_color('-slider-handle-border-color', false); | ||||
|  | ||||
|         let sliderHeight = themeNode.get_length('-slider-height'); | ||||
|  | ||||
|         let sliderBorderWidth = themeNode.get_length('-slider-border-width'); | ||||
|         let sliderBorderRadius = Math.min(width, sliderHeight) / 2; | ||||
|  | ||||
|         let sliderBorderColor = themeNode.get_color('-slider-border-color'); | ||||
|         let sliderColor = themeNode.get_color('-slider-background-color'); | ||||
|  | ||||
|         let sliderActiveBorderColor = themeNode.get_color('-slider-active-border-color'); | ||||
|         let sliderActiveColor = themeNode.get_color('-slider-active-background-color'); | ||||
|  | ||||
|         const TAU = Math.PI * 2; | ||||
|  | ||||
|         let handleX = handleRadius + (width - 2 * handleRadius) * this._value; | ||||
|  | ||||
|         cr.arc(sliderBorderRadius + sliderBorderWidth, height / 2, sliderBorderRadius, TAU * 1/4, TAU * 3/4); | ||||
|         cr.lineTo(handleX, (height - sliderHeight) / 2); | ||||
|         cr.lineTo(handleX, (height + sliderHeight) / 2); | ||||
|         cr.lineTo(sliderBorderRadius + sliderBorderWidth, (height + sliderHeight) / 2); | ||||
|         Clutter.cairo_set_source_color(cr, sliderActiveColor); | ||||
|         cr.fillPreserve(); | ||||
|         Clutter.cairo_set_source_color(cr, sliderActiveBorderColor); | ||||
|         cr.setLineWidth(sliderBorderWidth); | ||||
|         cr.stroke(); | ||||
|  | ||||
|         cr.arc(width - sliderBorderRadius - sliderBorderWidth, height / 2, sliderBorderRadius, TAU * 3/4, TAU * 1/4); | ||||
|         cr.lineTo(handleX, (height + sliderHeight) / 2); | ||||
|         cr.lineTo(handleX, (height - sliderHeight) / 2); | ||||
|         cr.lineTo(width - sliderBorderRadius - sliderBorderWidth, (height - sliderHeight) / 2); | ||||
|         Clutter.cairo_set_source_color(cr, sliderColor); | ||||
|         cr.fillPreserve(); | ||||
|         Clutter.cairo_set_source_color(cr, sliderBorderColor); | ||||
|         cr.setLineWidth(sliderBorderWidth); | ||||
|         cr.stroke(); | ||||
|  | ||||
|         let handleY = height / 2; | ||||
|  | ||||
|         let color = themeNode.get_foreground_color(); | ||||
|         Clutter.cairo_set_source_color(cr, color); | ||||
|         cr.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI); | ||||
|         cr.fillPreserve(); | ||||
|         if (hasHandleColor && handleBorderWidth) { | ||||
|             Clutter.cairo_set_source_color(cr, handleBorderColor); | ||||
|             cr.setLineWidth(handleBorderWidth); | ||||
|             cr.stroke(); | ||||
|         } | ||||
|         cr.$dispose(); | ||||
|     }, | ||||
|  | ||||
|     _startDragging: function(actor, event) { | ||||
|         this.startDragging(event); | ||||
|     }, | ||||
|  | ||||
|     startDragging: function(event) { | ||||
|         if (this._dragging) | ||||
|             return false; | ||||
|  | ||||
|         this._dragging = true; | ||||
|  | ||||
|         let device = event.get_device(); | ||||
|         device.grab(this.actor); | ||||
|         this._grabbedDevice = device; | ||||
|  | ||||
|         this._releaseId = this.actor.connect('button-release-event', Lang.bind(this, this._endDragging)); | ||||
|         this._motionId = this.actor.connect('motion-event', Lang.bind(this, this._motionEvent)); | ||||
|         let absX, absY; | ||||
|         [absX, absY] = event.get_coords(); | ||||
|         this._moveHandle(absX, absY); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _endDragging: function() { | ||||
|         if (this._dragging) { | ||||
|             this.actor.disconnect(this._releaseId); | ||||
|             this.actor.disconnect(this._motionId); | ||||
|  | ||||
|             this._grabbedDevice.ungrab(); | ||||
|             this._grabbedDevice = null; | ||||
|             this._dragging = false; | ||||
|  | ||||
|             this.emit('drag-end'); | ||||
|         } | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     scroll: function(event) { | ||||
|         let direction = event.get_scroll_direction(); | ||||
|         let delta; | ||||
|  | ||||
|         if (event.is_pointer_emulated()) | ||||
|             return; | ||||
|  | ||||
|         if (direction == Clutter.ScrollDirection.DOWN) { | ||||
|             delta = -SLIDER_SCROLL_STEP; | ||||
|         } else if (direction == Clutter.ScrollDirection.UP) { | ||||
|             delta = +SLIDER_SCROLL_STEP; | ||||
|         } else if (direction == Clutter.ScrollDirection.SMOOTH) { | ||||
|             let [dx, dy] = event.get_scroll_delta(); | ||||
|             // Even though the slider is horizontal, use dy to match | ||||
|             // the UP/DOWN above. | ||||
|             delta = -dy / 10; | ||||
|         } | ||||
|  | ||||
|         this._value = Math.min(Math.max(0, this._value + delta), 1); | ||||
|  | ||||
|         this.actor.queue_repaint(); | ||||
|         this.emit('value-changed', this._value); | ||||
|     }, | ||||
|  | ||||
|     _onScrollEvent: function(actor, event) { | ||||
|         this.scroll(event); | ||||
|     }, | ||||
|  | ||||
|     _motionEvent: function(actor, event) { | ||||
|         let absX, absY; | ||||
|         [absX, absY] = event.get_coords(); | ||||
|         this._moveHandle(absX, absY); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     onKeyPressEvent: function (actor, event) { | ||||
|         let key = event.get_key_symbol(); | ||||
|         if (key == Clutter.KEY_Right || key == Clutter.KEY_Left) { | ||||
|             let delta = key == Clutter.KEY_Right ? 0.1 : -0.1; | ||||
|             this._value = Math.max(0, Math.min(this._value + delta, 1)); | ||||
|             this.actor.queue_repaint(); | ||||
|             this.emit('value-changed', this._value); | ||||
|             this.emit('drag-end'); | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _moveHandle: function(absX, absY) { | ||||
|         let relX, relY, sliderX, sliderY; | ||||
|         [sliderX, sliderY] = this.actor.get_transformed_position(); | ||||
|         relX = absX - sliderX; | ||||
|         relY = absY - sliderY; | ||||
|  | ||||
|         let width = this.actor.width; | ||||
|         let handleRadius = this.actor.get_theme_node().get_length('-slider-handle-radius'); | ||||
|  | ||||
|         let newvalue; | ||||
|         if (relX < handleRadius) | ||||
|             newvalue = 0; | ||||
|         else if (relX > width - handleRadius) | ||||
|             newvalue = 1; | ||||
|         else | ||||
|             newvalue = (relX - handleRadius) / (width - 2 * handleRadius); | ||||
|         this._value = newvalue; | ||||
|         this.actor.queue_repaint(); | ||||
|         this.emit('value-changed', this._value); | ||||
|     }, | ||||
|  | ||||
|     _getCurrentValue: function (actor) { | ||||
|         return this._value; | ||||
|     }, | ||||
|  | ||||
|     _getMinimumValue: function (actor) { | ||||
|         return 0; | ||||
|     }, | ||||
|  | ||||
|     _getMaximumValue: function (actor) { | ||||
|         return 1; | ||||
|     }, | ||||
|  | ||||
|     _getMinimumIncrement: function (actor) { | ||||
|         return 0.1; | ||||
|     }, | ||||
|  | ||||
|     _setCurrentValue: function (actor, value) { | ||||
|         this._value = value; | ||||
|     }, | ||||
|  | ||||
|     _valueChanged: function (slider, value, property) { | ||||
|         this._customAccessible.notify ("accessible-value"); | ||||
|     }, | ||||
|  | ||||
|     get value() { | ||||
|         return this._value; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| Signals.addSignalMethods(Slider.prototype); | ||||
| @@ -1,54 +1,39 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const A11Y_SCHEMA                   = 'org.gnome.desktop.a11y'; | ||||
| const KEY_ALWAYS_SHOW               = 'always-show-universal-access-status'; | ||||
| const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard'; | ||||
| const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable'; | ||||
| const KEY_BOUNCE_KEYS_ENABLED = 'bouncekeys-enable'; | ||||
| const KEY_SLOW_KEYS_ENABLED   = 'slowkeys-enable'; | ||||
| const KEY_MOUSE_KEYS_ENABLED  = 'mousekeys-enable'; | ||||
|  | ||||
| const A11Y_KEYBOARD_SCHEMA          = 'org.gnome.desktop.a11y.keyboard'; | ||||
| const KEY_STICKY_KEYS_ENABLED       = 'stickykeys-enable'; | ||||
| const KEY_BOUNCE_KEYS_ENABLED       = 'bouncekeys-enable'; | ||||
| const KEY_SLOW_KEYS_ENABLED         = 'slowkeys-enable'; | ||||
| const KEY_MOUSE_KEYS_ENABLED        = 'mousekeys-enable'; | ||||
| const APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications'; | ||||
|  | ||||
| const APPLICATIONS_SCHEMA           = 'org.gnome.desktop.a11y.applications'; | ||||
| const DPI_FACTOR_LARGE   = 1.25; | ||||
|  | ||||
| const DPI_FACTOR_LARGE              = 1.25; | ||||
| const WM_SCHEMA            = 'org.gnome.desktop.wm.preferences'; | ||||
| const KEY_VISUAL_BELL      = 'visual-bell'; | ||||
|  | ||||
| const WM_SCHEMA                     = 'org.gnome.desktop.wm.preferences'; | ||||
| const KEY_VISUAL_BELL               = 'visual-bell'; | ||||
| const DESKTOP_INTERFACE_SCHEMA = 'org.gnome.desktop.interface'; | ||||
| const KEY_GTK_THEME      = 'gtk-theme'; | ||||
| const KEY_ICON_THEME     = 'icon-theme'; | ||||
| const KEY_WM_THEME       = 'theme'; | ||||
| const KEY_TEXT_SCALING_FACTOR = 'text-scaling-factor'; | ||||
|  | ||||
| const DESKTOP_INTERFACE_SCHEMA      = 'org.gnome.desktop.interface'; | ||||
| const KEY_GTK_THEME                 = 'gtk-theme'; | ||||
| const KEY_ICON_THEME                = 'icon-theme'; | ||||
| const KEY_WM_THEME                  = 'theme'; | ||||
| const KEY_TEXT_SCALING_FACTOR       = 'text-scaling-factor'; | ||||
|  | ||||
| const HIGH_CONTRAST_THEME           = 'HighContrast'; | ||||
| const HIGH_CONTRAST_THEME = 'HighContrast'; | ||||
|  | ||||
| const ATIndicator = new Lang.Class({ | ||||
|     Name: 'ATIndicator', | ||||
|     Extends: PanelMenu.Button, | ||||
|     Extends: PanelMenu.SystemStatusButton, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(0.0, _("Accessibility")); | ||||
|  | ||||
|         this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' }); | ||||
|         this._hbox.add_child(new St.Icon({ style_class: 'system-status-icon', | ||||
|                                            icon_name: 'preferences-desktop-accessibility-symbolic' })); | ||||
|         this._hbox.add_child(PopupMenu.unicodeArrow(St.Side.BOTTOM)); | ||||
|  | ||||
|         this.actor.add_child(this._hbox); | ||||
|  | ||||
|         this._a11ySettings = new Gio.Settings({ schema: A11Y_SCHEMA }); | ||||
|         this._a11ySettings.connect('changed::' + KEY_ALWAYS_SHOW, Lang.bind(this, this._queueSyncMenuVisibility)); | ||||
|         this.parent('preferences-desktop-accessibility-symbolic', _("Accessibility")); | ||||
|  | ||||
|         let highContrast = this._buildHCItem(); | ||||
|         this.menu.addMenuItem(highContrast); | ||||
| @@ -71,28 +56,30 @@ const ATIndicator = new Lang.Class({ | ||||
|         let visualBell = this._buildItem(_("Visual Alerts"), WM_SCHEMA, KEY_VISUAL_BELL); | ||||
|         this.menu.addMenuItem(visualBell); | ||||
|  | ||||
|         let stickyKeys = this._buildItem(_("Sticky Keys"), A11Y_KEYBOARD_SCHEMA, KEY_STICKY_KEYS_ENABLED); | ||||
|         let stickyKeys = this._buildItem(_("Sticky Keys"), A11Y_SCHEMA, KEY_STICKY_KEYS_ENABLED); | ||||
|         this.menu.addMenuItem(stickyKeys); | ||||
|  | ||||
|         let slowKeys = this._buildItem(_("Slow Keys"), A11Y_KEYBOARD_SCHEMA, KEY_SLOW_KEYS_ENABLED); | ||||
|         let slowKeys = this._buildItem(_("Slow Keys"), A11Y_SCHEMA, KEY_SLOW_KEYS_ENABLED); | ||||
|         this.menu.addMenuItem(slowKeys); | ||||
|  | ||||
|         let bounceKeys = this._buildItem(_("Bounce Keys"), A11Y_KEYBOARD_SCHEMA, KEY_BOUNCE_KEYS_ENABLED); | ||||
|         let bounceKeys = this._buildItem(_("Bounce Keys"), A11Y_SCHEMA, KEY_BOUNCE_KEYS_ENABLED); | ||||
|         this.menu.addMenuItem(bounceKeys); | ||||
|  | ||||
|         let mouseKeys = this._buildItem(_("Mouse Keys"), A11Y_KEYBOARD_SCHEMA, KEY_MOUSE_KEYS_ENABLED); | ||||
|         let mouseKeys = this._buildItem(_("Mouse Keys"), A11Y_SCHEMA, KEY_MOUSE_KEYS_ENABLED); | ||||
|         this.menu.addMenuItem(mouseKeys); | ||||
|  | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|         this.menu.addSettingsAction(_("Universal Access Settings"), 'gnome-universal-access-panel.desktop'); | ||||
|  | ||||
|         this._syncMenuVisibility(); | ||||
|     }, | ||||
|  | ||||
|     _syncMenuVisibility: function() { | ||||
|         this._syncMenuVisibilityIdle = 0; | ||||
|  | ||||
|         let alwaysShow = this._a11ySettings.get_boolean(KEY_ALWAYS_SHOW); | ||||
|         let items = this.menu._getMenuItems(); | ||||
|  | ||||
|         this.actor.visible = alwaysShow || items.some(function(f) { return !!f.state; }); | ||||
|         this.actor.visible = items.some(function(f) { return !!f.state; }); | ||||
|  | ||||
|         return false; | ||||
|     }, | ||||
|   | ||||
| @@ -9,70 +9,292 @@ const St = imports.gi.St; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const MessageTray = imports.ui.messageTray; | ||||
| const NotificationDaemon = imports.ui.notificationDaemon; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const ConnectionState = { | ||||
|     DISCONNECTED: 0, | ||||
|     CONNECTED: 1, | ||||
|     DISCONNECTING: 2, | ||||
|     CONNECTING: 3 | ||||
| } | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'BTIndicator', | ||||
|     Extends: PanelMenu.SystemIndicator, | ||||
|     Extends: PanelMenu.SystemStatusButton, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|  | ||||
|         this._indicator = this._addIndicator(); | ||||
|         this._indicator.icon_name = 'bluetooth-active-symbolic'; | ||||
|  | ||||
|         // The Bluetooth menu only appears when Bluetooth is in use, | ||||
|         // so just statically build it with a "Turn Off" menu item. | ||||
|         this._item = new PopupMenu.PopupSubMenuMenuItem(_("Bluetooth"), true); | ||||
|         this._item.icon.icon_name = 'bluetooth-active-symbolic'; | ||||
|         this._item.menu.addAction(_("Turn Off"), Lang.bind(this, function() { | ||||
|             this._applet.killswitch_state = GnomeBluetooth.KillswitchState.SOFT_BLOCKED; | ||||
|         })); | ||||
|         this._item.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-bluetooth-panel.desktop'); | ||||
|         this.menu.addMenuItem(this._item); | ||||
|         this.parent('bluetooth-disabled-symbolic', _("Bluetooth")); | ||||
|  | ||||
|         this._applet = new GnomeBluetoothApplet.Applet(); | ||||
|         this._applet.connect('devices-changed', Lang.bind(this, this._sync)); | ||||
|         this._sync(); | ||||
|  | ||||
|         this._killswitch = new PopupMenu.PopupSwitchMenuItem(_("Bluetooth"), false); | ||||
|         this._applet.connect('notify::killswitch-state', Lang.bind(this, this._updateKillswitch)); | ||||
|         this._killswitch.connect('toggled', Lang.bind(this, function() { | ||||
|             let current_state = this._applet.killswitch_state; | ||||
|             if (current_state != GnomeBluetooth.KillswitchState.HARD_BLOCKED && | ||||
|                 current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER) { | ||||
|                 this._applet.killswitch_state = this._killswitch.state ? | ||||
|                     GnomeBluetooth.KillswitchState.UNBLOCKED: | ||||
|                     GnomeBluetooth.KillswitchState.SOFT_BLOCKED; | ||||
|             } else | ||||
|                 this._killswitch.setToggleState(false); | ||||
|         })); | ||||
|  | ||||
|         this._discoverable = new PopupMenu.PopupSwitchMenuItem(_("Visibility"), this._applet.discoverable); | ||||
|         this._applet.connect('notify::discoverable', Lang.bind(this, function() { | ||||
|             this._discoverable.setToggleState(this._applet.discoverable); | ||||
|         })); | ||||
|         this._discoverable.connect('toggled', Lang.bind(this, function() { | ||||
|             this._applet.discoverable = this._discoverable.state; | ||||
|         })); | ||||
|  | ||||
|         this._updateKillswitch(); | ||||
|         this.menu.addMenuItem(this._killswitch); | ||||
|         this.menu.addMenuItem(this._discoverable); | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|  | ||||
|         this._fullMenuItems = [new PopupMenu.PopupSeparatorMenuItem(), | ||||
|                                new PopupMenu.PopupMenuItem(_("Send Files to Device…")), | ||||
|                                new PopupMenu.PopupMenuItem(_("Set Up a New Device…")), | ||||
|                                new PopupMenu.PopupSeparatorMenuItem()]; | ||||
|         this._hasDevices = false; | ||||
|  | ||||
|         this._fullMenuItems[1].connect('activate', function() { | ||||
|             GLib.spawn_command_line_async('bluetooth-sendto'); | ||||
|         }); | ||||
|         this._fullMenuItems[2].connect('activate', function() { | ||||
|             GLib.spawn_command_line_async('bluetooth-wizard'); | ||||
|         }); | ||||
|  | ||||
|         for (let i = 0; i < this._fullMenuItems.length; i++) { | ||||
|             let item = this._fullMenuItems[i]; | ||||
|             this.menu.addMenuItem(item); | ||||
|         } | ||||
|  | ||||
|         this._deviceItemPosition = 3; | ||||
|         this._deviceItems = []; | ||||
|         this._applet.connect('devices-changed', Lang.bind(this, this._updateDevices)); | ||||
|         this._updateDevices(); | ||||
|  | ||||
|         this._applet.connect('notify::show-full-menu', Lang.bind(this, this._updateFullMenu)); | ||||
|         this._updateFullMenu(); | ||||
|  | ||||
|         this.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-bluetooth-panel.desktop'); | ||||
|  | ||||
|         this._applet.connect('pincode-request', Lang.bind(this, this._pinRequest)); | ||||
|         this._applet.connect('confirm-request', Lang.bind(this, this._confirmRequest)); | ||||
|         this._applet.connect('auth-request', Lang.bind(this, this._authRequest)); | ||||
|         this._applet.connect('auth-service-request', Lang.bind(this, this._authServiceRequest)); | ||||
|         this._applet.connect('cancel-request', Lang.bind(this, this._cancelRequest)); | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let connectedDevices = this._applet.get_devices().filter(function(device) { | ||||
|             return device.connected; | ||||
|         }); | ||||
|         let nDevices = connectedDevices.length; | ||||
|     _updateKillswitch: function() { | ||||
|         let current_state = this._applet.killswitch_state; | ||||
|         let on = current_state == GnomeBluetooth.KillswitchState.UNBLOCKED; | ||||
|         let has_adapter = current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER; | ||||
|         let can_toggle = current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER && | ||||
|                          current_state != GnomeBluetooth.KillswitchState.HARD_BLOCKED; | ||||
|  | ||||
|         let on = nDevices > 0; | ||||
|         this._indicator.visible = on; | ||||
|         this._item.actor.visible = on; | ||||
|         this._killswitch.setToggleState(on); | ||||
|         if (can_toggle) | ||||
|             this._killswitch.setStatus(null); | ||||
|         else | ||||
|             /* TRANSLATORS: this means that bluetooth was disabled by hardware rfkill */ | ||||
|             this._killswitch.setStatus(_("hardware disabled")); | ||||
|  | ||||
|         if (on) | ||||
|             this._item.status.text = ngettext("%d Connected Device", "%d Connected Devices", nDevices).format(nDevices); | ||||
|         this.actor.visible = has_adapter; | ||||
|  | ||||
|         if (on) { | ||||
|             this._discoverable.actor.show(); | ||||
|             this.setIcon('bluetooth-active-symbolic'); | ||||
|         } else { | ||||
|             this._discoverable.actor.hide(); | ||||
|             this.setIcon('bluetooth-disabled-symbolic'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateDevices: function() { | ||||
|         let devices = this._applet.get_devices(); | ||||
|  | ||||
|         let newlist = [ ]; | ||||
|         for (let i = 0; i < this._deviceItems.length; i++) { | ||||
|             let item = this._deviceItems[i]; | ||||
|             let destroy = true; | ||||
|             for (let j = 0; j < devices.length; j++) { | ||||
|                 if (item._device.device_path == devices[j].device_path) { | ||||
|                     this._updateDeviceItem(item, devices[j]); | ||||
|                     destroy = false; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if (destroy) | ||||
|                 item.destroy(); | ||||
|             else | ||||
|                 newlist.push(item); | ||||
|         } | ||||
|  | ||||
|         this._deviceItems = newlist; | ||||
|         this._hasDevices = newlist.length > 0; | ||||
|         for (let i = 0; i < devices.length; i++) { | ||||
|             let d = devices[i]; | ||||
|             if (d._item) | ||||
|                 continue; | ||||
|             let item = this._createDeviceItem(d); | ||||
|             if (item) { | ||||
|                 this.menu.addMenuItem(item, this._deviceItemPosition + this._deviceItems.length); | ||||
|                 this._deviceItems.push(item); | ||||
|                 this._hasDevices = true; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateDeviceItem: function(item, device) { | ||||
|         if (!device.can_connect && device.capabilities == GnomeBluetoothApplet.Capabilities.NONE) { | ||||
|             item.destroy(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let prevDevice = item._device; | ||||
|         let prevCapabilities = prevDevice.capabilities; | ||||
|         let prevCanConnect = prevDevice.can_connect; | ||||
|  | ||||
|         // adopt the new device object | ||||
|         item._device = device; | ||||
|         device._item = item; | ||||
|  | ||||
|         // update properties | ||||
|         item.label.text = device.alias; | ||||
|  | ||||
|         if (prevCapabilities != device.capabilities || | ||||
|             prevCanConnect != device.can_connect) { | ||||
|             // need to rebuild the submenu | ||||
|             item.menu.removeAll(); | ||||
|             this._buildDeviceSubMenu(item, device); | ||||
|         } | ||||
|  | ||||
|         // update connected property | ||||
|         if (device.can_connect) | ||||
|             item._connectedMenuItem.setToggleState(device.connected); | ||||
|     }, | ||||
|  | ||||
|     _createDeviceItem: function(device) { | ||||
|         if (!device.can_connect && device.capabilities == GnomeBluetoothApplet.Capabilities.NONE) | ||||
|             return null; | ||||
|         let item = new PopupMenu.PopupSubMenuMenuItem(device.alias); | ||||
|  | ||||
|         // adopt the device object, and add a back link | ||||
|         item._device = device; | ||||
|         device._item = item; | ||||
|  | ||||
|         this._buildDeviceSubMenu(item, device); | ||||
|  | ||||
|         return item; | ||||
|     }, | ||||
|  | ||||
|     _buildDeviceSubMenu: function(item, device) { | ||||
|         if (device.can_connect) { | ||||
|             let menuitem = new PopupMenu.PopupSwitchMenuItem(_("Connection"), device.connected); | ||||
|             item._connected = device.connected; | ||||
|             item._connectedMenuItem = menuitem; | ||||
|             menuitem.connect('toggled', Lang.bind(this, function() { | ||||
|                 if (item._connected > ConnectionState.CONNECTED) { | ||||
|                     // operation already in progress, revert | ||||
|                     // (should not happen anyway) | ||||
|                     menuitem.setToggleState(menuitem.state); | ||||
|                 } | ||||
|                 if (item._connected) { | ||||
|                     item._connected = ConnectionState.DISCONNECTING; | ||||
|                     menuitem.setStatus(_("disconnecting...")); | ||||
|                     this._applet.disconnect_device(item._device.device_path, function(applet, success) { | ||||
|                         if (success) { // apply | ||||
|                             item._connected = ConnectionState.DISCONNECTED; | ||||
|                             menuitem.setToggleState(false); | ||||
|                         } else { // revert | ||||
|                             item._connected = ConnectionState.CONNECTED; | ||||
|                             menuitem.setToggleState(true); | ||||
|                         } | ||||
|                         menuitem.setStatus(null); | ||||
|                     }); | ||||
|                 } else { | ||||
|                     item._connected = ConnectionState.CONNECTING; | ||||
|                     menuitem.setStatus(_("connecting...")); | ||||
|                     this._applet.connect_device(item._device.device_path, function(applet, success) { | ||||
|                         if (success) { // apply | ||||
|                             item._connected = ConnectionState.CONNECTED; | ||||
|                             menuitem.setToggleState(true); | ||||
|                         } else { // revert | ||||
|                             item._connected = ConnectionState.DISCONNECTED; | ||||
|                             menuitem.setToggleState(false); | ||||
|                         } | ||||
|                         menuitem.setStatus(null); | ||||
|                     }); | ||||
|                 } | ||||
|             })); | ||||
|  | ||||
|             item.menu.addMenuItem(menuitem); | ||||
|         } | ||||
|  | ||||
|         if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_PUSH) { | ||||
|             item.menu.addAction(_("Send Files…"), Lang.bind(this, function() { | ||||
|                 this._applet.send_to_address(device.bdaddr, device.alias); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         switch (device.type) { | ||||
|         case GnomeBluetoothApplet.Type.KEYBOARD: | ||||
|             item.menu.addSettingsAction(_("Keyboard Settings"), 'gnome-keyboard-panel.desktop'); | ||||
|             break; | ||||
|         case GnomeBluetoothApplet.Type.MOUSE: | ||||
|             item.menu.addSettingsAction(_("Mouse Settings"), 'gnome-mouse-panel.desktop'); | ||||
|             break; | ||||
|         case GnomeBluetoothApplet.Type.HEADSET: | ||||
|         case GnomeBluetoothApplet.Type.HEADPHONES: | ||||
|         case GnomeBluetoothApplet.Type.OTHER_AUDIO: | ||||
|             item.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop'); | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateFullMenu: function() { | ||||
|         if (this._applet.show_full_menu) { | ||||
|             this._showAll(this._fullMenuItems); | ||||
|             if (this._hasDevices) | ||||
|                 this._showAll(this._deviceItems); | ||||
|         } else { | ||||
|             this._hideAll(this._fullMenuItems); | ||||
|             this._hideAll(this._deviceItems); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _showAll: function(items) { | ||||
|         for (let i = 0; i < items.length; i++) | ||||
|             items[i].actor.show(); | ||||
|     }, | ||||
|  | ||||
|     _hideAll: function(items) { | ||||
|         for (let i = 0; i < items.length; i++) | ||||
|             items[i].actor.hide(); | ||||
|     }, | ||||
|  | ||||
|     _destroyAll: function(items) { | ||||
|         for (let i = 0; i < items.length; i++) | ||||
|             items[i].destroy(); | ||||
|     }, | ||||
|  | ||||
|     _ensureSource: function() { | ||||
|         if (!this._source) { | ||||
|             this._source = new MessageTray.Source(_("Bluetooth"), 'bluetooth-active'); | ||||
|             this._source.policy = new MessageTray.NotificationApplicationPolicy('gnome-bluetooth-panel'); | ||||
|             this._source.policy = new NotificationDaemon.NotificationApplicationPolicy('gnome-bluetooth-panel'); | ||||
|             Main.messageTray.add(this._source); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _authRequest: function(applet, device_path, name, long_name) { | ||||
|     _authRequest: function(applet, device_path, name, long_name, uuid) { | ||||
|         this._ensureSource(); | ||||
|         this._source.notify(new AuthNotification(this._source, this._applet, device_path, name, long_name)); | ||||
|     }, | ||||
|  | ||||
|     _authServiceRequest: function(applet, device_path, name, long_name, uuid) { | ||||
|         this._ensureSource(); | ||||
|         this._source.notify(new AuthServiceNotification(this._source, this._applet, device_path, name, long_name, uuid)); | ||||
|         this._source.notify(new AuthNotification(this._source, this._applet, device_path, name, long_name, uuid)); | ||||
|     }, | ||||
|  | ||||
|     _confirmRequest: function(applet, device_path, name, long_name, pin) { | ||||
| @@ -94,34 +316,6 @@ const AuthNotification = new Lang.Class({ | ||||
|     Name: 'AuthNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, applet, device_path, name, long_name) { | ||||
|         this.parent(source, | ||||
|                     _("Bluetooth"), | ||||
|                     _("Authorization request from %s").format(name), | ||||
|                     { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this._applet = applet; | ||||
|         this._devicePath = device_path; | ||||
|         this.addBody(_("Device %s wants to pair with this computer").format(long_name)); | ||||
|  | ||||
|         this.addButton('allow', _("Allow")); | ||||
|         this.addButton('deny', _("Deny")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             if (action == 'allow') | ||||
|                 this._applet.agent_reply_confirm(this._devicePath, true); | ||||
|             else | ||||
|                 this._applet.agent_reply_confirm(this._devicePath, false); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const AuthServiceNotification = new Lang.Class({ | ||||
|     Name: 'AuthServiceNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, applet, device_path, name, long_name, uuid) { | ||||
|         this.parent(source, | ||||
|                     _("Bluetooth"), | ||||
| @@ -140,14 +334,14 @@ const AuthServiceNotification = new Lang.Class({ | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'always-grant': | ||||
|                 this._applet.agent_reply_auth_service(this._devicePath, true, true); | ||||
|                 this._applet.agent_reply_auth(this._devicePath, true, true); | ||||
|                 break; | ||||
|             case 'grant': | ||||
|                 this._applet.agent_reply_auth_service(this._devicePath, true, false); | ||||
|                 this._applet.agent_reply_auth(this._devicePath, true, false); | ||||
|                 break; | ||||
|             case 'reject': | ||||
|             default: | ||||
|                 this._applet.agent_reply_auth_service(this._devicePath, false, false); | ||||
|                 this._applet.agent_reply_auth(this._devicePath, false, false); | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
| @@ -169,7 +363,7 @@ const ConfirmNotification = new Lang.Class({ | ||||
|         this._applet = applet; | ||||
|         this._devicePath = device_path; | ||||
|         this.addBody(_("Device %s wants to pair with this computer").format(long_name)); | ||||
|         this.addBody(_("Please confirm whether the Passkey '%06d' matches the one on the device.").format(pin)); | ||||
|         this.addBody(_("Please confirm whether the PIN '%06d' matches the one on the device.").format(pin)); | ||||
|  | ||||
|         /* Translators: this is the verb, not the noun */ | ||||
|         this.addButton('matches', _("Matches")); | ||||
|   | ||||
| @@ -1,68 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Gio = imports.gi.Gio; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
| const Slider = imports.ui.slider; | ||||
|  | ||||
| const BUS_NAME = 'org.gnome.SettingsDaemon.Power'; | ||||
| const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power'; | ||||
|  | ||||
| const BrightnessInterface = <interface name="org.gnome.SettingsDaemon.Power.Screen"> | ||||
| <property name='Brightness' type='i' access='readwrite'/> | ||||
| </interface>; | ||||
|  | ||||
| const BrightnessProxy = Gio.DBusProxy.makeProxyWrapper(BrightnessInterface); | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'BrightnessIndicator', | ||||
|     Extends: PanelMenu.SystemIndicator, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent('display-brightness-symbolic'); | ||||
|         this._proxy = new BrightnessProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH, | ||||
|                                           Lang.bind(this, function(proxy, error) { | ||||
|                                               if (error) { | ||||
|                                                   log(error.message); | ||||
|                                                   return; | ||||
|                                               } | ||||
|  | ||||
|                                               this._proxy.connect('g-properties-changed', Lang.bind(this, this._sync)); | ||||
|                                               this._sync(); | ||||
|                                           })); | ||||
|  | ||||
|         this._item = new PopupMenu.PopupBaseMenuItem({ activate: false }); | ||||
|         this.menu.addMenuItem(this._item); | ||||
|  | ||||
|         this._slider = new Slider.Slider(0); | ||||
|         this._slider.connect('value-changed', Lang.bind(this, this._sliderChanged)); | ||||
|         this._slider.actor.accessible_name = _("Brightness"); | ||||
|  | ||||
|         let icon = new St.Icon({ icon_name: 'display-brightness-symbolic', | ||||
|                                  style_class: 'popup-menu-icon' }); | ||||
|         this._item.actor.add(icon); | ||||
|         this._item.actor.add(this._slider.actor, { expand: true }); | ||||
|         this._item.actor.connect('button-press-event', Lang.bind(this, function(actor, event) { | ||||
|             this._slider.startDragging(event); | ||||
|         })); | ||||
|         this._item.actor.connect('key-press-event', Lang.bind(this, function(actor, event) { | ||||
|             return this._slider.onKeyPressEvent(actor, event); | ||||
|         })); | ||||
|  | ||||
|     }, | ||||
|  | ||||
|     _sliderChanged: function(slider, value) { | ||||
|         let percent = value * 100; | ||||
|         this._proxy.Brightness = percent; | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let visible = this._proxy.Brightness >= 0; | ||||
|         this._item.actor.visible = visible; | ||||
|         if (visible) | ||||
|             this._slider.setValue(this._proxy.Brightness / 100.0); | ||||
|     }, | ||||
| }); | ||||
| @@ -9,7 +9,6 @@ const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Gettext = imports.gettext; | ||||
|  | ||||
| try { | ||||
|     var IBus = imports.gi.IBus; | ||||
| @@ -34,32 +33,6 @@ const KEY_INPUT_SOURCES = 'sources'; | ||||
| const INPUT_SOURCE_TYPE_XKB = 'xkb'; | ||||
| const INPUT_SOURCE_TYPE_IBUS = 'ibus'; | ||||
|  | ||||
| // This is the longest we'll keep the keyboard frozen until an input | ||||
| // source is active. | ||||
| const MAX_INPUT_SOURCE_ACTIVATION_TIME = 4000; // ms | ||||
|  | ||||
| const BUS_NAME = 'org.gnome.SettingsDaemon.Keyboard'; | ||||
| const OBJECT_PATH = '/org/gnome/SettingsDaemon/Keyboard'; | ||||
|  | ||||
| const KeyboardManagerInterface = <interface name="org.gnome.SettingsDaemon.Keyboard"> | ||||
| <method name="SetInputSource"> | ||||
|     <arg type="u" direction="in" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const KeyboardManagerProxy = Gio.DBusProxy.makeProxyWrapper(KeyboardManagerInterface); | ||||
|  | ||||
| function releaseKeyboard() { | ||||
|     if (Main.modalCount > 0) | ||||
|         global.display.unfreeze_keyboard(global.get_current_time()); | ||||
|     else | ||||
|         global.display.ungrab_keyboard(global.get_current_time()); | ||||
| } | ||||
|  | ||||
| function holdKeyboard() { | ||||
|     global.freeze_keyboard(global.get_current_time()); | ||||
| } | ||||
|  | ||||
| const IBusManager = new Lang.Class({ | ||||
|     Name: 'IBusManager', | ||||
|  | ||||
| @@ -72,24 +45,26 @@ const IBusManager = new Lang.Class({ | ||||
|         this._readyCallback = readyCallback; | ||||
|         this._candidatePopup = new IBusCandidatePopup.CandidatePopup(); | ||||
|  | ||||
|         this._ibus = null; | ||||
|         this._panelService = null; | ||||
|         this._engines = {}; | ||||
|         this._ready = false; | ||||
|         this._registerPropertiesId = 0; | ||||
|         this._currentEngineName = null; | ||||
|  | ||||
|         this._ibus = IBus.Bus.new_async(); | ||||
|         this._ibus.connect('connected', Lang.bind(this, this._onConnected)); | ||||
|         this._ibus.connect('disconnected', Lang.bind(this, this._clear)); | ||||
|         // Need to set this to get 'global-engine-changed' emitions | ||||
|         this._ibus.set_watch_ibus_signal(true); | ||||
|         this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged)); | ||||
|         this._nameWatcherId = Gio.DBus.session.watch_name(IBus.SERVICE_IBUS, | ||||
|                                                           Gio.BusNameWatcherFlags.NONE, | ||||
|                                                           Lang.bind(this, this._onNameAppeared), | ||||
|                                                           Lang.bind(this, this._clear)); | ||||
|     }, | ||||
|  | ||||
|     _clear: function() { | ||||
|         if (this._panelService) | ||||
|             this._panelService.destroy(); | ||||
|         if (this._ibus) | ||||
|             this._ibus.destroy(); | ||||
|  | ||||
|         this._ibus = null; | ||||
|         this._panelService = null; | ||||
|         this._candidatePopup.setPanelService(null); | ||||
|         this._engines = {}; | ||||
| @@ -101,12 +76,18 @@ const IBusManager = new Lang.Class({ | ||||
|             this._readyCallback(false); | ||||
|     }, | ||||
|  | ||||
|     _onNameAppeared: function() { | ||||
|         this._ibus = IBus.Bus.new_async(); | ||||
|         this._ibus.connect('connected', Lang.bind(this, this._onConnected)); | ||||
|     }, | ||||
|  | ||||
|     _onConnected: function() { | ||||
|         this._ibus.list_engines_async(-1, null, Lang.bind(this, this._initEngines)); | ||||
|         this._ibus.request_name_async(IBus.SERVICE_PANEL, | ||||
|                                       IBus.BusNameFlag.REPLACE_EXISTING, | ||||
|                                       -1, null, | ||||
|                                       Lang.bind(this, this._initPanelService)); | ||||
|         this._ibus.connect('disconnected', Lang.bind(this, this._clear)); | ||||
|     }, | ||||
|  | ||||
|     _initEngines: function(ibus, result) { | ||||
| @@ -128,6 +109,9 @@ const IBusManager = new Lang.Class({ | ||||
|             this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(), | ||||
|                                                          object_path: IBus.PATH_PANEL }); | ||||
|             this._candidatePopup.setPanelService(this._panelService); | ||||
|             // Need to set this to get 'global-engine-changed' emitions | ||||
|             this._ibus.set_watch_ibus_signal(true); | ||||
|             this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged)); | ||||
|             this._panelService.connect('update-property', Lang.bind(this, this._updateProperty)); | ||||
|             // If an engine is already active we need to get its properties | ||||
|             this._ibus.get_global_engine_async(-1, null, Lang.bind(this, function(i, result) { | ||||
| @@ -156,9 +140,6 @@ const IBusManager = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _engineChanged: function(bus, engineName) { | ||||
|         if (!this._ready) | ||||
|             return; | ||||
|  | ||||
|         this._currentEngineName = engineName; | ||||
|  | ||||
|         if (this._registerPropertiesId != 0) | ||||
| @@ -202,8 +183,8 @@ const LayoutMenuItem = new Lang.Class({ | ||||
|  | ||||
|         this.label = new St.Label({ text: displayName }); | ||||
|         this.indicator = new St.Label({ text: shortName }); | ||||
|         this.actor.add(this.label, { expand: true }); | ||||
|         this.actor.add(this.indicator); | ||||
|         this.addActor(this.label); | ||||
|         this.addActor(this.indicator); | ||||
|         this.actor.label_actor = this.label; | ||||
|     } | ||||
| }); | ||||
| @@ -336,12 +317,7 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|         this._container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth)); | ||||
|         this._container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight)); | ||||
|         this._container.connect('allocate', Lang.bind(this, this._containerAllocate)); | ||||
|  | ||||
|         this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' }); | ||||
|         this._hbox.add_child(this._container); | ||||
|         this._hbox.add_child(PopupMenu.unicodeArrow(St.Side.BOTTOM)); | ||||
|  | ||||
|         this.actor.add_child(this._hbox); | ||||
|         this.actor.add_actor(this._container); | ||||
|         this.actor.add_style_class_name('panel-status-button'); | ||||
|  | ||||
|         // All valid input sources currently in the gsettings | ||||
| @@ -388,21 +364,14 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|         this._ibusManager.connect('property-updated', Lang.bind(this, this._ibusPropertyUpdated)); | ||||
|         this._inputSourcesChanged(); | ||||
|  | ||||
|         this._keyboardManager = new KeyboardManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH, | ||||
|                                                          function(proxy, error) { | ||||
|                                                              if (error) | ||||
|                                                                  log(error.message); | ||||
|                                                          }); | ||||
|         this._keyboardManager.g_default_timeout = MAX_INPUT_SOURCE_ACTIVATION_TIME; | ||||
|  | ||||
|         global.display.connect('modifiers-accelerator-activated', Lang.bind(this, this._modifiersSwitcher)); | ||||
|  | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|         this._showLayoutItem = this.menu.addAction(_("Show Keyboard Layout"), Lang.bind(this, this._showLayout)); | ||||
|  | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); | ||||
|         this._sessionUpdated(); | ||||
|  | ||||
|         this.menu.addSettingsAction(_("Region & Language Settings"), 'gnome-region-panel.desktop'); | ||||
|  | ||||
|         this._sourcesPerWindow = false; | ||||
|         this._focusWindowNotifyId = 0; | ||||
|         this._overviewShowingId = 0; | ||||
| @@ -428,43 +397,10 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|         this._inputSourcesChanged(); | ||||
|     }, | ||||
|  | ||||
|     _modifiersSwitcher: function() { | ||||
|         let sourceIndexes = Object.keys(this._inputSources); | ||||
|         if (sourceIndexes.length == 0) { | ||||
|             releaseKeyboard(); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         let is = this._currentSource; | ||||
|         if (!is) | ||||
|             is = this._inputSources[sourceIndexes[0]]; | ||||
|  | ||||
|         let nextIndex = is.index + 1; | ||||
|         if (nextIndex > sourceIndexes[sourceIndexes.length - 1]) | ||||
|             nextIndex = 0; | ||||
|  | ||||
|         while (!(is = this._inputSources[nextIndex])) | ||||
|             nextIndex += 1; | ||||
|  | ||||
|         is.activate(); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _switchInputSource: function(display, screen, window, binding) { | ||||
|         if (this._mruSources.length < 2) | ||||
|             return; | ||||
|  | ||||
|         // HACK: Fall back on simple input source switching since we | ||||
|         // can't show a popup switcher while a GrabHelper grab is in | ||||
|         // effect without considerable work to consolidate the usage | ||||
|         // of pushModal/popModal and grabHelper. See | ||||
|         // https://bugzilla.gnome.org/show_bug.cgi?id=695143 . | ||||
|         if (Main.keybindingMode == Shell.KeyBindingMode.MESSAGE_TRAY || | ||||
|             Main.keybindingMode == Shell.KeyBindingMode.TOPBAR_POPUP) { | ||||
|             this._modifiersSwitcher(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let popup = new InputSourcePopup(this._mruSources, this._keybindingAction, this._keybindingActionBackward); | ||||
|         let modifiers = binding.get_modifiers(); | ||||
|         let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK; | ||||
| @@ -480,11 +416,6 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|         let oldSource; | ||||
|         [oldSource, this._currentSource] = [this._currentSource, newSource]; | ||||
|  | ||||
|         if (oldSource) { | ||||
|             oldSource.menuItem.setOrnament(PopupMenu.Ornament.NONE); | ||||
|             oldSource.indicatorLabel.hide(); | ||||
|         } | ||||
|  | ||||
|         if (!newSource || (nVisibleSources < 2 && !newSource.properties)) { | ||||
|             // This source index might be invalid if we weren't able | ||||
|             // to build a menu item for it, so we hide ourselves since | ||||
| @@ -499,7 +430,12 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|  | ||||
|         this.actor.show(); | ||||
|  | ||||
|         newSource.menuItem.setOrnament(PopupMenu.Ornament.DOT); | ||||
|         if (oldSource) { | ||||
|             oldSource.menuItem.setShowDot(false); | ||||
|             oldSource.indicatorLabel.hide(); | ||||
|         } | ||||
|  | ||||
|         newSource.menuItem.setShowDot(true); | ||||
|         newSource.indicatorLabel.show(); | ||||
|  | ||||
|         this._buildPropSection(newSource.properties); | ||||
| @@ -523,7 +459,6 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|  | ||||
|         this._inputSources = {}; | ||||
|         this._ibusSources = {}; | ||||
|         this._currentSource = null; | ||||
|  | ||||
|         let inputSourcesByShortName = {}; | ||||
|  | ||||
| @@ -540,12 +475,8 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|                 let engineDesc = this._ibusManager.getEngineDesc(id); | ||||
|                 if (engineDesc) { | ||||
|                     let language = IBus.get_language_name(engineDesc.get_language()); | ||||
|                     let longName = engineDesc.get_longname(); | ||||
|                     let textdomain = engineDesc.get_textdomain(); | ||||
|                     if (textdomain != '') | ||||
|                         longName = Gettext.dgettext(textdomain, longName); | ||||
|                     exists = true; | ||||
|                     displayName = '%s (%s)'.format(language, longName); | ||||
|                     displayName = language + ' (' + engineDesc.get_longname() + ')'; | ||||
|                     shortName = this._makeEngineShortName(engineDesc); | ||||
|                 } | ||||
|             } | ||||
| @@ -556,8 +487,10 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|             let is = new InputSource(type, id, displayName, shortName, i); | ||||
|  | ||||
|             is.connect('activate', Lang.bind(this, function() { | ||||
|                 holdKeyboard(); | ||||
|                 this._keyboardManager.SetInputSourceRemote(is.index, releaseKeyboard); | ||||
|                 if (this._currentSource.index == is.index) | ||||
|                     return; | ||||
|                 this._settings.set_value(KEY_CURRENT_INPUT_SOURCE, | ||||
|                                          GLib.Variant.new_uint32(is.index)); | ||||
|             })); | ||||
|  | ||||
|             if (!(is.shortName in inputSourcesByShortName)) | ||||
| @@ -727,8 +660,7 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|                 item.prop = prop; | ||||
|                 radioGroup.push(item); | ||||
|                 item.radioGroup = radioGroup; | ||||
|                 item.setOrnament(prop.get_state() == IBus.PropState.CHECKED ? | ||||
|                                  PopupMenu.Ornament.DOT : PopupMenu.Ornament.NONE); | ||||
|                 item.setShowDot(prop.get_state() == IBus.PropState.CHECKED); | ||||
|                 item.connect('activate', Lang.bind(this, function() { | ||||
|                     if (item.prop.get_state() == IBus.PropState.CHECKED) | ||||
|                         return; | ||||
| @@ -736,12 +668,12 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|                     let group = item.radioGroup; | ||||
|                     for (let i = 0; i < group.length; ++i) { | ||||
|                         if (group[i] == item) { | ||||
|                             item.setOrnament(PopupMenu.Ornament.DOT); | ||||
|                             item.setShowDot(true); | ||||
|                             item.prop.set_state(IBus.PropState.CHECKED); | ||||
|                             this._ibusManager.activateProperty(item.prop.get_key(), | ||||
|                                                                IBus.PropState.CHECKED); | ||||
|                         } else { | ||||
|                             group[i].setOrnament(PopupMenu.Ornament.NONE); | ||||
|                             group[i].setShowDot(false); | ||||
|                             group[i].prop.set_state(IBus.PropState.UNCHECKED); | ||||
|                             this._ibusManager.activateProperty(group[i].prop.get_key(), | ||||
|                                                                IBus.PropState.UNCHECKED); | ||||
| @@ -771,7 +703,7 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|                 item.prop = prop; | ||||
|                 item.connect('activate', Lang.bind(this, function() { | ||||
|                     this._ibusManager.activateProperty(item.prop.get_key(), | ||||
|                                                        item.prop.get_state()); | ||||
|                                                        IBus.PropState.CHECKED); | ||||
|                 })); | ||||
|                 break; | ||||
|  | ||||
| @@ -905,7 +837,7 @@ const InputSourceIndicator = new Lang.Class({ | ||||
|  | ||||
|         for (let i in this._inputSources) { | ||||
|             let is = this._inputSources[i]; | ||||
|             is.indicatorLabel.allocate_align_fill(box, 0.5, 0.5, false, false, flags); | ||||
|             is.indicatorLabel.allocate_align_fill(box, 0.5, 0, false, false, flags); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|   | ||||
							
								
								
									
										62
									
								
								js/ui/status/lockScreenMenu.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,62 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GObject = imports.gi.GObject; | ||||
| const Lang = imports.lang; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
| const VolumeMenu = imports.ui.status.volume; | ||||
|  | ||||
| const FakeStatusIcon = new Lang.Class({ | ||||
|     Name: 'FakeStatusIcon', | ||||
|  | ||||
|     _init: function(button) { | ||||
|         this.actor = new St.BoxLayout({ style_class: 'panel-status-button-box' }); | ||||
|         this._button = button; | ||||
|         this._button.connect('icons-updated', Lang.bind(this, this._reconstructIcons)); | ||||
|         this._button.actor.bind_property('visible', this.actor, 'visible', | ||||
|                                          GObject.BindingFlags.SYNC_CREATE); | ||||
|         this._reconstructIcons(); | ||||
|     }, | ||||
|  | ||||
|     _reconstructIcons: function() { | ||||
|         this.actor.destroy_all_children(); | ||||
|         this._button.icons.forEach(Lang.bind(this, function(icon) { | ||||
|             let newIcon = new St.Icon({ style_class: 'system-status-icon' }); | ||||
|             icon.bind_property('gicon', newIcon, 'gicon', | ||||
|                                GObject.BindingFlags.SYNC_CREATE); | ||||
|             icon.bind_property('visible', newIcon, 'visible', | ||||
|                                GObject.BindingFlags.SYNC_CREATE); | ||||
|             this.actor.add_actor(newIcon); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'LockScreenMenuIndicator', | ||||
|     Extends: PanelMenu.SystemStatusButton, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(null, _("Volume, network, battery")); | ||||
|         this._box.style_class = 'lock-screen-status-button-box'; | ||||
|  | ||||
|         this._volumeControl = VolumeMenu.getMixerControl(); | ||||
|         this._volumeMenu = new VolumeMenu.VolumeMenu(this._volumeControl); | ||||
|         this.menu.addMenuItem(this._volumeMenu); | ||||
|  | ||||
|         this._volume = new FakeStatusIcon(Main.panel.statusArea.volume); | ||||
|         this._box.add_child(this._volume.actor); | ||||
|  | ||||
|         // Network may not exist if the user doesn't have NetworkManager | ||||
|         if (Main.panel.statusArea.network) { | ||||
|             this._network = new FakeStatusIcon(Main.panel.statusArea.network); | ||||
|             this._box.add_child(this._network.actor); | ||||
|         } | ||||
|  | ||||
|         this._battery = new FakeStatusIcon(Main.panel.statusArea.battery); | ||||
|         this._box.add_child(this._battery.actor); | ||||
|     } | ||||
| }); | ||||
| @@ -2,15 +2,39 @@ | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const UPower = imports.gi.UPowerGlib; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const BUS_NAME = 'org.gnome.SettingsDaemon.Power'; | ||||
| const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power'; | ||||
|  | ||||
| const UPDeviceType = { | ||||
|     UNKNOWN: 0, | ||||
|     AC_POWER: 1, | ||||
|     BATTERY: 2, | ||||
|     UPS: 3, | ||||
|     MONITOR: 4, | ||||
|     MOUSE: 5, | ||||
|     KEYBOARD: 6, | ||||
|     PDA: 7, | ||||
|     PHONE: 8, | ||||
|     MEDIA_PLAYER: 9, | ||||
|     TABLET: 10, | ||||
|     COMPUTER: 11 | ||||
| }; | ||||
|  | ||||
| const UPDeviceState = { | ||||
|     UNKNOWN: 0, | ||||
|     CHARGING: 1, | ||||
|     DISCHARGING: 2, | ||||
|     EMPTY: 3, | ||||
|     FULLY_CHARGED: 4, | ||||
|     PENDING_CHARGE: 5, | ||||
|     PENDING_DISCHARGE: 6 | ||||
| }; | ||||
|  | ||||
| const PowerManagerInterface = <interface name="org.gnome.SettingsDaemon.Power"> | ||||
| <method name="GetDevices"> | ||||
|     <arg type="a(susdut)" direction="out" /> | ||||
| @@ -25,91 +49,178 @@ const PowerManagerProxy = Gio.DBusProxy.makeProxyWrapper(PowerManagerInterface); | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'PowerIndicator', | ||||
|     Extends: PanelMenu.SystemIndicator, | ||||
|     Extends: PanelMenu.SystemStatusButton, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|  | ||||
|         this._indicator = this._addIndicator(); | ||||
|         this.parent('battery-missing-symbolic', _("Battery")); | ||||
|  | ||||
|         this._proxy = new PowerManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH, | ||||
|                                             Lang.bind(this, function(proxy, error) { | ||||
|                                                 if (error) { | ||||
|                                                     log(error.message); | ||||
|                                                     return; | ||||
|                                                 } | ||||
|                                                 this._proxy.connect('g-properties-changed', | ||||
|                                                                     Lang.bind(this, this._sync)); | ||||
|                                                 this._sync(); | ||||
|                                             })); | ||||
|                                            Lang.bind(this, function(proxy, error) { | ||||
|                                                if (error) { | ||||
|                                                    log(error.message); | ||||
|                                                    return; | ||||
|                                                } | ||||
|                                                this._proxy.connect('g-properties-changed', | ||||
|                                                                    Lang.bind(this, this._devicesChanged)); | ||||
|                                                this._devicesChanged(); | ||||
|                                            })); | ||||
|  | ||||
|         this._item = new PopupMenu.PopupSubMenuMenuItem(_("Battery"), true); | ||||
|         this._item.menu.addSettingsAction(_("Power Settings"), 'gnome-power-panel.desktop'); | ||||
|         this.menu.addMenuItem(this._item); | ||||
|         this._deviceItems = [ ]; | ||||
|         this._hasPrimary = false; | ||||
|         this._primaryDeviceId = null; | ||||
|  | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); | ||||
|         this._sessionUpdated(); | ||||
|         this._batteryItem = new PopupMenu.PopupMenuItem('', { reactive: false }); | ||||
|         this._primaryPercentage = new St.Label({ style_class: 'popup-battery-percentage' }); | ||||
|         this._batteryItem.addActor(this._primaryPercentage, { align: St.Align.END }); | ||||
|         this.menu.addMenuItem(this._batteryItem); | ||||
|  | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|         this._otherDevicePosition = 2; | ||||
|  | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|         this.menu.addSettingsAction(_("Power Settings"), 'gnome-power-panel.desktop'); | ||||
|     }, | ||||
|  | ||||
|     _sessionUpdated: function() { | ||||
|         let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; | ||||
|         this.menu.setSensitive(sensitive); | ||||
|     }, | ||||
|  | ||||
|     _statusForDevice: function(device) { | ||||
|         let [device_id, device_type, icon, percentage, state, seconds] = device; | ||||
|  | ||||
|         if (state == UPower.DeviceState.FULLY_CHARGED) | ||||
|             return _("Fully Charged"); | ||||
|  | ||||
|         let time = Math.round(seconds / 60); | ||||
|         if (time == 0) { | ||||
|             // 0 is reported when UPower does not have enough data | ||||
|             // to estimate battery life | ||||
|             return _("Estimating…"); | ||||
|         } | ||||
|  | ||||
|         let minutes = time % 60; | ||||
|         let hours = Math.floor(time / 60); | ||||
|  | ||||
|         if (state == UPower.DeviceState.DISCHARGING) { | ||||
|             // Translators: this is <hours>:<minutes> Remaining (<percentage>) | ||||
|             return _("%d\u2236%02d Remaining (%d%%)").format(hours, minutes, percentage); | ||||
|         } | ||||
|  | ||||
|         if (state == UPower.DeviceState.CHARGING) { | ||||
|             // Translators: this is <hours>:<minutes> Until Full (<percentage>) | ||||
|             return _("%d\u2236%02d Until Full (%d%%)").format(hours, minutes, percentage); | ||||
|         } | ||||
|  | ||||
|         // state is one of PENDING_CHARGING, PENDING_DISCHARGING | ||||
|         return _("Estimating…"); | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         function isBattery(result) { | ||||
|             if (!result) | ||||
|                 return false; | ||||
|  | ||||
|             let [device] = result; | ||||
|             let [, deviceType] = device; | ||||
|             return (deviceType == UPower.DeviceKind.BATTERY); | ||||
|         } | ||||
|  | ||||
|     _readPrimaryDevice: function() { | ||||
|         this._proxy.GetPrimaryDeviceRemote(Lang.bind(this, function(result, error) { | ||||
|             if (isBattery(result)) { | ||||
|                 let [device] = result; | ||||
|                 let [,, icon] = device; | ||||
|                 let gicon = Gio.icon_new_for_string(icon); | ||||
|                 this._indicator.gicon = gicon; | ||||
|                 this._item.icon.gicon = gicon; | ||||
|                 this._item.status.text = this._statusForDevice(device); | ||||
|                 this._item.actor.show(); | ||||
|             if (error) { | ||||
|                 this._hasPrimary = false; | ||||
|                 this._primaryDeviceId = null; | ||||
|                 this._batteryItem.actor.hide(); | ||||
|                 return; | ||||
|             } | ||||
|             let [[device_id, device_type, icon, percentage, state, seconds]] = result; | ||||
|             if (device_type == UPDeviceType.BATTERY) { | ||||
|                 this._hasPrimary = true; | ||||
|                 let time = Math.round(seconds / 60); | ||||
|                 if (time == 0) { | ||||
|                     // 0 is reported when UPower does not have enough data | ||||
|                     // to estimate battery life | ||||
|                     this._batteryItem.label.text = _("Estimating…"); | ||||
|                 } else { | ||||
|                     let minutes = time % 60; | ||||
|                     let hours = Math.floor(time / 60); | ||||
|                     let timestring; | ||||
|                     if (time >= 60) { | ||||
|                         if (minutes == 0) { | ||||
|                             timestring = ngettext("%d hour remaining", "%d hours remaining", hours).format(hours); | ||||
|                         } else { | ||||
|                             /* TRANSLATORS: this is a time string, as in "%d hours %d minutes remaining" */ | ||||
|                             let template = _("%d %s %d %s remaining"); | ||||
|  | ||||
|                             timestring = template.format (hours, ngettext("hour", "hours", hours), minutes, ngettext("minute", "minutes", minutes)); | ||||
|                         } | ||||
|                     } else | ||||
|                         timestring = ngettext("%d minute remaining", "%d minutes remaining", minutes).format(minutes); | ||||
|                     this._batteryItem.label.text = timestring; | ||||
|                 } | ||||
|                 this._primaryPercentage.text = C_("percent of battery remaining", "%d%%").format(Math.round(percentage)); | ||||
|                 this._batteryItem.actor.show(); | ||||
|             } else { | ||||
|                 // If there's no battery, then we use the power icon. | ||||
|                 this._indicator.icon_name = 'system-shutdown-symbolic'; | ||||
|                 this._item.actor.hide(); | ||||
|                 this._hasPrimary = false; | ||||
|                 this._batteryItem.actor.hide(); | ||||
|             } | ||||
|  | ||||
|             this._primaryDeviceId = device_id; | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _readOtherDevices: function() { | ||||
|         this._proxy.GetDevicesRemote(Lang.bind(this, function(result, error) { | ||||
|             this._deviceItems.forEach(function(i) { i.destroy(); }); | ||||
|             this._deviceItems = []; | ||||
|  | ||||
|             if (error) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let position = 0; | ||||
|             let [devices] = result; | ||||
|             for (let i = 0; i < devices.length; i++) { | ||||
|                 let [device_id, device_type] = devices[i]; | ||||
|                 if (device_type == UPDeviceType.AC_POWER || device_id == this._primaryDeviceId) | ||||
|                     continue; | ||||
|  | ||||
|                 let item = new DeviceItem (devices[i]); | ||||
|                 this._deviceItems.push(item); | ||||
|                 this.menu.addMenuItem(item, this._otherDevicePosition + position); | ||||
|                 position++; | ||||
|             } | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _syncIcon: function() { | ||||
|         let icon = this._proxy.Icon; | ||||
|         let hasIcon = false; | ||||
|  | ||||
|         if (icon) { | ||||
|             let gicon = Gio.icon_new_for_string(icon); | ||||
|             this.setGIcon(gicon); | ||||
|             hasIcon = true; | ||||
|         } | ||||
|         this.mainIcon.visible = hasIcon; | ||||
|         this.actor.visible = hasIcon; | ||||
|     }, | ||||
|  | ||||
|     _devicesChanged: function() { | ||||
|         this._syncIcon(); | ||||
|         this._readPrimaryDevice(); | ||||
|         this._readOtherDevices(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const DeviceItem = new Lang.Class({ | ||||
|     Name: 'DeviceItem', | ||||
|     Extends: PopupMenu.PopupBaseMenuItem, | ||||
|  | ||||
|     _init: function(device) { | ||||
|         this.parent({ reactive: false }); | ||||
|  | ||||
|         let [device_id, device_type, icon, percentage, state, time] = device; | ||||
|  | ||||
|         this._box = new St.BoxLayout({ style_class: 'popup-device-menu-item' }); | ||||
|         this._label = new St.Label({ text: this._deviceTypeToString(device_type) }); | ||||
|  | ||||
|         this._icon = new St.Icon({ gicon: Gio.icon_new_for_string(icon), | ||||
|                                    style_class: 'popup-menu-icon' }); | ||||
|  | ||||
|         this._box.add_actor(this._icon); | ||||
|         this._box.add_actor(this._label); | ||||
|         this.addActor(this._box); | ||||
|  | ||||
|         let percentLabel = new St.Label({ text: C_("percent of battery remaining", "%d%%").format(Math.round(percentage)), | ||||
|                                           style_class: 'popup-battery-percentage' }); | ||||
|         this.addActor(percentLabel, { align: St.Align.END }); | ||||
|         //FIXME: ideally we would like to expose this._label and percentLabel | ||||
|         this.actor.label_actor = percentLabel; | ||||
|     }, | ||||
|  | ||||
|     _deviceTypeToString: function(type) { | ||||
| 	switch (type) { | ||||
| 	case UPDeviceType.AC_POWER: | ||||
|             return _("AC Adapter"); | ||||
|         case UPDeviceType.BATTERY: | ||||
|             return _("Laptop Battery"); | ||||
|         case UPDeviceType.UPS: | ||||
|             return _("UPS"); | ||||
|         case UPDeviceType.MONITOR: | ||||
|             return _("Monitor"); | ||||
|         case UPDeviceType.MOUSE: | ||||
|             return _("Mouse"); | ||||
|         case UPDeviceType.KEYBOARD: | ||||
|             return _("Keyboard"); | ||||
|         case UPDeviceType.PDA: | ||||
|             return _("PDA"); | ||||
|         case UPDeviceType.PHONE: | ||||
|             return _("Cell Phone"); | ||||
|         case UPDeviceType.MEDIA_PLAYER: | ||||
|             return _("Media Player"); | ||||
|         case UPDeviceType.TABLET: | ||||
|             return _("Tablet"); | ||||
|         case UPDeviceType.COMPUTER: | ||||
|             return _("Computer"); | ||||
|         default: | ||||
|             return C_("device", "Unknown"); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -1,58 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
|  | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const BUS_NAME = 'org.gnome.SettingsDaemon.Rfkill'; | ||||
| const OBJECT_PATH = '/org/gnome/SettingsDaemon/Rfkill'; | ||||
|  | ||||
| const RfkillManagerInterface = <interface name="org.gnome.SettingsDaemon.Rfkill"> | ||||
| <property name="AirplaneMode" type="b" access="readwrite" /> | ||||
| </interface>; | ||||
|  | ||||
| const RfkillManagerProxy = Gio.DBusProxy.makeProxyWrapper(RfkillManagerInterface); | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'RfkillIndicator', | ||||
|     Extends: PanelMenu.SystemIndicator, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|  | ||||
|         this._proxy = new RfkillManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH, | ||||
|                                              Lang.bind(this, function(proxy, error) { | ||||
|                                                  if (error) { | ||||
|                                                      log(error.message); | ||||
|                                                      return; | ||||
|                                                  } | ||||
|                                                  this._proxy.connect('g-properties-changed', | ||||
|                                                                      Lang.bind(this, this._sync)); | ||||
|                                                  this._sync(); | ||||
|                                              })); | ||||
|  | ||||
|         this._indicator = this._addIndicator(); | ||||
|         this._indicator.icon_name = 'airplane-mode-symbolic'; | ||||
|         this._indicator.hide(); | ||||
|  | ||||
|         // The menu only appears when airplane mode is on, so just | ||||
|         // statically build it as if it was on, rather than dynamically | ||||
|         // changing the menu contents. | ||||
|         this._item = new PopupMenu.PopupSubMenuMenuItem(_("Airplane Mode"), true); | ||||
|         this._item.icon.icon_name = 'airplane-mode-symbolic'; | ||||
|         this._item.status.text = _("On"); | ||||
|         this._item.menu.addAction(_("Turn Off"), Lang.bind(this, function() { | ||||
|             this._proxy.AirplaneMode = false; | ||||
|         })); | ||||
|         this._item.menu.addSettingsAction(_("Network Settings"), 'gnome-network-panel.desktop'); | ||||
|         this.menu.addMenuItem(this._item); | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let airplaneMode = this._proxy.AirplaneMode; | ||||
|         this._indicator.visible = airplaneMode; | ||||
|         this._item.actor.visible = airplaneMode; | ||||
|     }, | ||||
| }); | ||||
| @@ -1,26 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Lang = imports.lang; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'ScreencastIndicator', | ||||
|     Extends: PanelMenu.SystemIndicator, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|  | ||||
|         this._indicator = this._addIndicator(); | ||||
|         this._indicator.icon_name = 'media-record-symbolic'; | ||||
|         this._indicator.add_style_class_name('screencast-indicator'); | ||||
|         this._sync(); | ||||
|  | ||||
|         Main.screencastService.connect('updated', Lang.bind(this, this._sync)); | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         this._indicator.visible = Main.screencastService.isRecording; | ||||
|     }, | ||||
| }); | ||||
| @@ -1,406 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const AccountsService = imports.gi.AccountsService; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gdm = imports.gi.Gdm; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const BoxPointer = imports.ui.boxpointer; | ||||
| const GnomeSession = imports.misc.gnomeSession; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| const Main = imports.ui.main; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown'; | ||||
| const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver'; | ||||
| const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; | ||||
| const PRIVACY_SCHEMA = 'org.gnome.desktop.privacy' | ||||
| const DISABLE_USER_SWITCH_KEY = 'disable-user-switching'; | ||||
| const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen'; | ||||
| const DISABLE_LOG_OUT_KEY = 'disable-log-out'; | ||||
| const DISABLE_RESTART_KEY = 'disable-restart-buttons'; | ||||
| const ALWAYS_SHOW_LOG_OUT_KEY = 'always-show-log-out'; | ||||
|  | ||||
| const AltSwitcher = new Lang.Class({ | ||||
|     Name: 'AltSwitcher', | ||||
|  | ||||
|     _init: function(standard, alternate) { | ||||
|         this._standard = standard; | ||||
|         this._standard.connect('notify::visible', Lang.bind(this, this._sync)); | ||||
|  | ||||
|         this._alternate = alternate; | ||||
|         this._alternate.connect('notify::visible', Lang.bind(this, this._sync)); | ||||
|  | ||||
|         this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent)); | ||||
|  | ||||
|         this.actor = new St.Bin(); | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let childToShow = null; | ||||
|  | ||||
|         if (this._standard.visible && this._alternate.visible) { | ||||
|             let [x, y, mods] = global.get_pointer(); | ||||
|             let altPressed = (mods & Clutter.ModifierType.MOD1_MASK) != 0; | ||||
|             childToShow = altPressed ? this._alternate : this._standard; | ||||
|         } else if (this._standard.visible) { | ||||
|             childToShow = this._standard; | ||||
|         } else if (this._alternate.visible) { | ||||
|             childToShow = this._alternate; | ||||
|         } | ||||
|  | ||||
|         if (this.actor.get_child() != childToShow) { | ||||
|             this.actor.set_child(childToShow); | ||||
|  | ||||
|             // The actors might respond to hover, so | ||||
|             // sync the pointer to make sure they update. | ||||
|             global.sync_pointer(); | ||||
|         } | ||||
|  | ||||
|         this.actor.visible = (childToShow != null); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy: function() { | ||||
|         if (this._capturedEventId > 0) { | ||||
|             global.stage.disconnect(this._capturedEventId); | ||||
|             this._capturedEventId = 0; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onCapturedEvent: function(actor, event) { | ||||
|         let type = event.type(); | ||||
|         if (type == Clutter.EventType.KEY_PRESS || type == Clutter.EventType.KEY_RELEASE) { | ||||
|             let key = event.get_key_symbol(); | ||||
|             if (key == Clutter.KEY_Alt_L || key == Clutter.KEY_Alt_R) | ||||
|                 this._sync(); | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'SystemIndicator', | ||||
|     Extends: PanelMenu.SystemIndicator, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|  | ||||
|         this._screenSaverSettings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA }); | ||||
|         this._loginScreenSettings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA }); | ||||
|         this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA }); | ||||
|         this._privacySettings = new Gio.Settings({ schema: PRIVACY_SCHEMA }); | ||||
|         this._orientationSettings = new Gio.Settings({ schema: 'org.gnome.settings-daemon.peripherals.touchscreen' }); | ||||
|  | ||||
|         this._session = new GnomeSession.SessionManager(); | ||||
|         this._loginManager = LoginManager.getLoginManager(); | ||||
|         this._haveShutdown = true; | ||||
|         this._haveSuspend = true; | ||||
|  | ||||
|         this._userManager = AccountsService.UserManager.get_default(); | ||||
|         this._user = this._userManager.get_user(GLib.get_user_name()); | ||||
|  | ||||
|         this._createSubMenu(); | ||||
|  | ||||
|         this._userManager.connect('notify::is-loaded', | ||||
|                                   Lang.bind(this, this._updateMultiUser)); | ||||
|         this._userManager.connect('notify::has-multiple-users', | ||||
|                                   Lang.bind(this, this._updateMultiUser)); | ||||
|         this._userManager.connect('user-added', | ||||
|                                   Lang.bind(this, this._updateMultiUser)); | ||||
|         this._userManager.connect('user-removed', | ||||
|                                   Lang.bind(this, this._updateMultiUser)); | ||||
|         this._lockdownSettings.connect('changed::' + DISABLE_USER_SWITCH_KEY, | ||||
|                                        Lang.bind(this, this._updateMultiUser)); | ||||
|         this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY, | ||||
|                                        Lang.bind(this, this._updateMultiUser)); | ||||
|         this._lockdownSettings.connect('changed::' + DISABLE_LOCK_SCREEN_KEY, | ||||
|                                        Lang.bind(this, this._updateLockScreen)); | ||||
|         global.settings.connect('changed::' + ALWAYS_SHOW_LOG_OUT_KEY, | ||||
|                                 Lang.bind(this, this._updateMultiUser)); | ||||
|         this._updateSwitchUser(); | ||||
|         this._updateMultiUser(); | ||||
|  | ||||
|         // Whether shutdown is available or not depends on both lockdown | ||||
|         // settings (disable-log-out) and Polkit policy - the latter doesn't | ||||
|         // notify, so we update the menu item each time the menu opens or | ||||
|         // the lockdown setting changes, which should be close enough. | ||||
|         this.menu.connect('open-state-changed', Lang.bind(this, | ||||
|             function(menu, open) { | ||||
|                 if (!open) | ||||
|                     return; | ||||
|  | ||||
|                 this._updateHaveShutdown(); | ||||
|                 this._updateHaveSuspend(); | ||||
|             })); | ||||
|         this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY, | ||||
|                                        Lang.bind(this, this._updateHaveShutdown)); | ||||
|  | ||||
|         this._orientationSettings.connect('changed::orientation-lock', | ||||
|                                           Lang.bind(this, this._updateOrientationLock)); | ||||
|         this._orientationExists = false; | ||||
|         Gio.DBus.session.watch_name('org.gnome.SettingsDaemon.Orientation', | ||||
|                                     Gio.BusNameWatcherFlags.NONE, | ||||
|                                     Lang.bind(this, function() { | ||||
|                                         this._orentationExists = true; | ||||
|                                         this._updateOrientationLock(); | ||||
|                                     }), | ||||
|                                     Lang.bind(this, function() { | ||||
|                                         this._orentationExists = false; | ||||
|                                         this._updateOrientationLock(); | ||||
|                                     })); | ||||
|         this._updateOrientationLock(); | ||||
|  | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); | ||||
|         this._sessionUpdated(); | ||||
|     }, | ||||
|  | ||||
|     _updateActionsVisibility: function() { | ||||
|         let visible = (this._settingsAction.visible || | ||||
|                        this._orientationLockAction.visible || | ||||
|                        this._lockScreenAction.visible || | ||||
|                        this._altSwitcher.actor.visible); | ||||
|  | ||||
|         this._actionsItem.actor.visible = visible; | ||||
|     }, | ||||
|  | ||||
|     _sessionUpdated: function() { | ||||
|         this._updateLockScreen(); | ||||
|         this._updatePowerOff(); | ||||
|         this._updateSuspend(); | ||||
|         this._updateMultiUser(); | ||||
|         this._settingsAction.visible = Main.sessionMode.allowSettings; | ||||
|         this._updateActionsVisibility(); | ||||
|     }, | ||||
|  | ||||
|     _updateMultiUser: function() { | ||||
|         let shouldShowInMode = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; | ||||
|         let hasSwitchUser = this._updateSwitchUser(); | ||||
|         let hasLogout = this._updateLogout(); | ||||
|  | ||||
|         this._switchUserSubMenu.actor.visible = shouldShowInMode && (hasSwitchUser || hasLogout); | ||||
|     }, | ||||
|  | ||||
|     _updateSwitchUser: function() { | ||||
|         let allowSwitch = !this._lockdownSettings.get_boolean(DISABLE_USER_SWITCH_KEY); | ||||
|         let multiUser = this._userManager.can_switch() && this._userManager.has_multiple_users; | ||||
|  | ||||
|         let visible = allowSwitch && multiUser; | ||||
|         this._loginScreenItem.actor.visible = visible; | ||||
|         return visible; | ||||
|     }, | ||||
|  | ||||
|     _updateLogout: function() { | ||||
|         let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY); | ||||
|         let alwaysShow = global.settings.get_boolean(ALWAYS_SHOW_LOG_OUT_KEY); | ||||
|         let systemAccount = this._user.system_account; | ||||
|         let localAccount = this._user.local_account; | ||||
|         let multiUser = this._userManager.has_multiple_users; | ||||
|         let multiSession = Gdm.get_session_ids().length > 1; | ||||
|  | ||||
|         let visible = allowLogout && (alwaysShow || multiUser || multiSession || systemAccount || !localAccount); | ||||
|         this._logoutItem.actor.visible = visible; | ||||
|         return visible; | ||||
|     }, | ||||
|  | ||||
|     _updateSwitchUserSubMenu: function() { | ||||
|         this._switchUserSubMenu.label.text = this._user.get_real_name(); | ||||
|         let clutterText = this._switchUserSubMenu.label.clutter_text; | ||||
|  | ||||
|         // XXX -- for some reason, the ClutterText's width changes | ||||
|         // rapidly unless we force a relayout of the actor. Probably | ||||
|         // a size cache issue or something. Moving this to be a layout | ||||
|         // manager would be a much better idea. | ||||
|         clutterText.get_allocation_box(); | ||||
|  | ||||
|         let layout = clutterText.get_layout(); | ||||
|         if (layout.is_ellipsized()) | ||||
|             this._switchUserSubMenu.label.text = this._user.get_user_name(); | ||||
|  | ||||
|         let iconFile = this._user.get_icon_file(); | ||||
|         if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS)) | ||||
|             iconFile = null; | ||||
|  | ||||
|         if (iconFile) { | ||||
|             let file = Gio.File.new_for_path(iconFile); | ||||
|             let gicon = new Gio.FileIcon({ file: file }); | ||||
|             this._switchUserSubMenu.icon.gicon = gicon; | ||||
|         } else { | ||||
|             this._switchUserSubMenu.icon.icon_name = 'avatar-default-symbolic'; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateOrientationLock: function() { | ||||
|         this._orientationLockAction.visible = this._orientationExists; | ||||
|  | ||||
|         let locked = this._orientationSettings.get_boolean('orientation-lock'); | ||||
|         let icon = this._orientationLockAction.child; | ||||
|         icon.icon_name = locked ? 'rotation-locked-symbolic' : 'rotation-allowed-symbolic'; | ||||
|  | ||||
|         this._updateActionsVisibility(); | ||||
|     }, | ||||
|  | ||||
|     _updateLockScreen: function() { | ||||
|         let showLock = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; | ||||
|         let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY); | ||||
|         this._lockScreenAction.visible = showLock && allowLockScreen && LoginManager.canLock(); | ||||
|         this._updateActionsVisibility(); | ||||
|     }, | ||||
|  | ||||
|     _updateHaveShutdown: function() { | ||||
|         this._session.CanShutdownRemote(Lang.bind(this, function(result, error) { | ||||
|             if (error) | ||||
|                 return; | ||||
|  | ||||
|             this._haveShutdown = result[0]; | ||||
|             this._updatePowerOff(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _updatePowerOff: function() { | ||||
|         let disabled = Main.sessionMode.isLocked || | ||||
|                        (Main.sessionMode.isGreeter && | ||||
|                         this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY)); | ||||
|         this._powerOffAction.visible = this._haveShutdown && !disabled; | ||||
|         this._updateActionsVisibility(); | ||||
|     }, | ||||
|  | ||||
|     _updateHaveSuspend: function() { | ||||
|         this._loginManager.canSuspend(Lang.bind(this, function(result) { | ||||
|             this._haveSuspend = result; | ||||
|             this._updateSuspend(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _updateSuspend: function() { | ||||
|         let disabled = Main.sessionMode.isLocked || | ||||
|                        (Main.sessionMode.isGreeter && | ||||
|                         this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY)); | ||||
|         this._suspendAction.visible = this._haveShutdown && !disabled; | ||||
|         this._updateActionsVisibility(); | ||||
|     }, | ||||
|  | ||||
|     _createActionButton: function(iconName, accessibleName) { | ||||
|         let icon = new St.Button({ reactive: true, | ||||
|                                    can_focus: true, | ||||
|                                    track_hover: true, | ||||
|                                    accessible_name: accessibleName, | ||||
|                                    style_class: 'system-menu-action' }); | ||||
|         icon.child = new St.Icon({ icon_name: iconName }); | ||||
|         return icon; | ||||
|     }, | ||||
|  | ||||
|     _createSubMenu: function() { | ||||
|         let item; | ||||
|  | ||||
|         this._switchUserSubMenu = new PopupMenu.PopupSubMenuMenuItem('', true); | ||||
|         this._switchUserSubMenu.icon.style_class = 'system-switch-user-submenu-icon'; | ||||
|  | ||||
|         // Since the label of the switch user submenu depends on the width of | ||||
|         // the popup menu, and we can't easily connect on allocation-changed | ||||
|         // or notify::width without creating layout cycles, simply update the | ||||
|         // label whenever the menu is opened. | ||||
|         this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) { | ||||
|             if (isOpen) | ||||
|                 this._updateSwitchUserSubMenu(); | ||||
|         })); | ||||
|  | ||||
|         item = new PopupMenu.PopupMenuItem(_("Switch User")); | ||||
|         item.connect('activate', Lang.bind(this, this._onLoginScreenActivate)); | ||||
|         this._switchUserSubMenu.menu.addMenuItem(item); | ||||
|         this._loginScreenItem = item; | ||||
|  | ||||
|         item = new PopupMenu.PopupMenuItem(_("Log Out")); | ||||
|         item.connect('activate', Lang.bind(this, this._onQuitSessionActivate)); | ||||
|         this._switchUserSubMenu.menu.addMenuItem(item); | ||||
|         this._logoutItem = item; | ||||
|  | ||||
|         this._user.connect('notify::is-loaded', Lang.bind(this, this._updateSwitchUserSubMenu)); | ||||
|         this._user.connect('changed', Lang.bind(this, this._updateSwitchUserSubMenu)); | ||||
|  | ||||
|         this.menu.addMenuItem(this._switchUserSubMenu); | ||||
|  | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|  | ||||
|         item = new PopupMenu.PopupBaseMenuItem({ reactive: false, | ||||
|                                                  can_focus: false }); | ||||
|  | ||||
|         this._settingsAction = this._createActionButton('preferences-system-symbolic', _("Settings")); | ||||
|         this._settingsAction.connect('clicked', Lang.bind(this, this._onSettingsClicked)); | ||||
|         item.actor.add(this._settingsAction, { expand: true, x_fill: false }); | ||||
|  | ||||
|         this._orientationLockAction = this._createActionButton('', _("Orientation Lock")); | ||||
|         this._orientationLockAction.connect('clicked', Lang.bind(this, this._onOrientationLockClicked)); | ||||
|         item.actor.add(this._orientationLockAction, { expand: true, x_fill: false }); | ||||
|  | ||||
|         this._lockScreenAction = this._createActionButton('changes-prevent-symbolic', _("Lock")); | ||||
|         this._lockScreenAction.connect('clicked', Lang.bind(this, this._onLockScreenClicked)); | ||||
|         item.actor.add(this._lockScreenAction, { expand: true, x_fill: false }); | ||||
|  | ||||
|         this._suspendAction = this._createActionButton('media-playback-pause-symbolic', _("Suspend")); | ||||
|         this._suspendAction.connect('clicked', Lang.bind(this, this._onSuspendClicked)); | ||||
|  | ||||
|         this._powerOffAction = this._createActionButton('system-shutdown-symbolic', _("Power Off")); | ||||
|         this._powerOffAction.connect('clicked', Lang.bind(this, this._onPowerOffClicked)); | ||||
|  | ||||
|         this._altSwitcher = new AltSwitcher(this._powerOffAction, this._suspendAction); | ||||
|         item.actor.add(this._altSwitcher.actor, { expand: true, x_fill: false }); | ||||
|  | ||||
|         this._actionsItem = item; | ||||
|         this.menu.addMenuItem(item); | ||||
|     }, | ||||
|  | ||||
|     _onSettingsClicked: function() { | ||||
|         this.menu.itemActivated(); | ||||
|         let app = Shell.AppSystem.get_default().lookup_app('gnome-control-center.desktop'); | ||||
|         Main.overview.hide(); | ||||
|         app.activate(); | ||||
|     }, | ||||
|  | ||||
|     _onOrientationLockClicked: function() { | ||||
|         this.menu.itemActivated(); | ||||
|         let locked = this._orientationSettings.get_boolean('orientation-lock'); | ||||
|         this._orientationSettings.set_boolean('orientation-lock', !locked); | ||||
|         this._updateOrientationLock(); | ||||
|     }, | ||||
|  | ||||
|     _onLockScreenClicked: function() { | ||||
|         this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); | ||||
|         Main.overview.hide(); | ||||
|         Main.screenShield.lock(true); | ||||
|     }, | ||||
|  | ||||
|     _onLoginScreenActivate: function() { | ||||
|         this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); | ||||
|         Main.overview.hide(); | ||||
|         if (Main.screenShield) | ||||
|             Main.screenShield.lock(false); | ||||
|  | ||||
|         Clutter.threads_add_repaint_func_full(Clutter.RepaintFlags.POST_PAINT, function() { | ||||
|             Gdm.goto_login_session_sync(null); | ||||
|             return false; | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     _onQuitSessionActivate: function() { | ||||
|         Main.overview.hide(); | ||||
|         this._session.LogoutRemote(0); | ||||
|     }, | ||||
|  | ||||
|     _onPowerOffClicked: function() { | ||||
|         this.menu.itemActivated(); | ||||
|         Main.overview.hide(); | ||||
|         this._session.ShutdownRemote(0); | ||||
|     }, | ||||
|  | ||||
|     _onSuspendClicked: function() { | ||||
|         this.menu.itemActivated(); | ||||
|         this._loginManager.suspend(); | ||||
|     }, | ||||
| }); | ||||
| @@ -9,7 +9,8 @@ const Signals = imports.signals; | ||||
|  | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
| const Slider = imports.ui.slider; | ||||
|  | ||||
| const VOLUME_ADJUSTMENT_STEP = 0.05; /* Volume adjustment step in % */ | ||||
|  | ||||
| const VOLUME_NOTIFY_ID = 1; | ||||
|  | ||||
| @@ -29,26 +30,21 @@ function getMixerControl() { | ||||
| const StreamSlider = new Lang.Class({ | ||||
|     Name: 'StreamSlider', | ||||
|  | ||||
|     _init: function(control) { | ||||
|     _init: function(control, title) { | ||||
|         this._control = control; | ||||
|  | ||||
|         this.item = new PopupMenu.PopupBaseMenuItem({ activate: false }); | ||||
|         this.item = new PopupMenu.PopupMenuSection(); | ||||
|  | ||||
|         this._slider = new Slider.Slider(0); | ||||
|         this._title = new PopupMenu.PopupMenuItem(title, { reactive: false }); | ||||
|         this._slider = new PopupMenu.PopupSliderMenuItem(0); | ||||
|         this._slider.connect('value-changed', Lang.bind(this, this._sliderChanged)); | ||||
|         this._slider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange)); | ||||
|  | ||||
|         this._icon = new St.Icon({ style_class: 'popup-menu-icon' }); | ||||
|         this.item.actor.add(this._icon); | ||||
|         this.item.actor.add(this._slider.actor, { expand: true }); | ||||
|         this.item.actor.connect('button-press-event', Lang.bind(this, function(actor, event) { | ||||
|             this._slider.startDragging(event); | ||||
|         })); | ||||
|         this.item.actor.connect('key-press-event', Lang.bind(this, function(actor, event) { | ||||
|             return this._slider.onKeyPressEvent(actor, event); | ||||
|         })); | ||||
|         this.item.addMenuItem(this._title); | ||||
|         this.item.addMenuItem(this._slider); | ||||
|  | ||||
|         this._stream = null; | ||||
|         this._shouldShow = true; | ||||
|     }, | ||||
|  | ||||
|     get stream() { | ||||
| @@ -90,7 +86,8 @@ const StreamSlider = new Lang.Class({ | ||||
|  | ||||
|     _updateVisibility: function() { | ||||
|         let visible = this._shouldBeVisible(); | ||||
|         this.item.actor.visible = visible; | ||||
|         this._title.actor.visible = visible; | ||||
|         this._slider.actor.visible = visible; | ||||
|     }, | ||||
|  | ||||
|     scroll: function(event) { | ||||
| @@ -157,11 +154,6 @@ const OutputStreamSlider = new Lang.Class({ | ||||
|     Name: 'OutputStreamSlider', | ||||
|     Extends: StreamSlider, | ||||
|  | ||||
|     _init: function(control) { | ||||
|         this.parent(control); | ||||
|         this._slider.actor.accessible_name = _("Volume"); | ||||
|     }, | ||||
|  | ||||
|     _connectStream: function(stream) { | ||||
|         this.parent(stream); | ||||
|         this._portChangedId = stream.connect('notify::port', Lang.bind(this, this._portChanged)); | ||||
| @@ -189,17 +181,11 @@ const OutputStreamSlider = new Lang.Class({ | ||||
|         this._portChangedId = 0; | ||||
|     }, | ||||
|  | ||||
|     _updateSliderIcon: function() { | ||||
|         this._icon.icon_name = (this._hasHeadphones ? | ||||
|                                 'audio-headphones-symbolic' : | ||||
|                                 'audio-speakers-symbolic'); | ||||
|     }, | ||||
|  | ||||
|     _portChanged: function() { | ||||
|         let hasHeadphones = this._findHeadphones(this._stream); | ||||
|         if (hasHeadphones != this._hasHeadphones) { | ||||
|             this._hasHeadphones = hasHeadphones; | ||||
|             this._updateSliderIcon(); | ||||
|             this.emit('headphones-changed', this._hasHeadphones); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -208,12 +194,10 @@ const InputStreamSlider = new Lang.Class({ | ||||
|     Name: 'InputStreamSlider', | ||||
|     Extends: StreamSlider, | ||||
|  | ||||
|     _init: function(control) { | ||||
|         this.parent(control); | ||||
|         this._slider.actor.accessible_name = _("Microphone"); | ||||
|     _init: function(control, title) { | ||||
|         this.parent(control, title); | ||||
|         this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput)); | ||||
|         this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput)); | ||||
|         this._icon.icon_name = 'audio-input-microphone-symbolic'; | ||||
|     }, | ||||
|  | ||||
|     _connectStream: function(stream) { | ||||
| @@ -261,13 +245,17 @@ const VolumeMenu = new Lang.Class({ | ||||
|         this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput)); | ||||
|         this._control.connect('default-source-changed', Lang.bind(this, this._readInput)); | ||||
|  | ||||
|         this._output = new OutputStreamSlider(this._control); | ||||
|         /* Translators: This is the label for audio volume */ | ||||
|         this._output = new OutputStreamSlider(this._control, _("Volume")); | ||||
|         this._output.connect('stream-updated', Lang.bind(this, function() { | ||||
|             this.emit('icon-changed'); | ||||
|         })); | ||||
|         this._output.connect('headphones-changed', Lang.bind(this, function(stream, value) { | ||||
|             this.emit('headphones-changed', value); | ||||
|         })); | ||||
|         this.addMenuItem(this._output.item); | ||||
|  | ||||
|         this._input = new InputStreamSlider(this._control); | ||||
|         this._input = new InputStreamSlider(this._control, _("Microphone")); | ||||
|         this.addMenuItem(this._input.item); | ||||
|  | ||||
|         this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
| @@ -303,29 +291,31 @@ const VolumeMenu = new Lang.Class({ | ||||
|  | ||||
| const Indicator = new Lang.Class({ | ||||
|     Name: 'VolumeIndicator', | ||||
|     Extends: PanelMenu.SystemIndicator, | ||||
|     Extends: PanelMenu.SystemStatusButton, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|  | ||||
|         this._primaryIndicator = this._addIndicator(); | ||||
|         this.parent('audio-volume-muted-symbolic', _("Volume")); | ||||
|  | ||||
|         this._control = getMixerControl(); | ||||
|         this._volumeMenu = new VolumeMenu(this._control); | ||||
|         this._volumeMenu.connect('icon-changed', Lang.bind(this, function(menu) { | ||||
|             let icon = this._volumeMenu.getIcon(); | ||||
|  | ||||
|             if (icon != null) { | ||||
|                 this.indicators.show(); | ||||
|                 this._primaryIndicator.icon_name = icon; | ||||
|             } else { | ||||
|                 this.indicators.hide(); | ||||
|             } | ||||
|             this.actor.visible = (icon != null); | ||||
|             this.setIcon(icon); | ||||
|         })); | ||||
|         this._volumeMenu.connect('headphones-changed', Lang.bind(this, function(menu, value) { | ||||
|             this._headphoneIcon.visible = value; | ||||
|         })); | ||||
|  | ||||
|         this._headphoneIcon = this.addIcon(new Gio.ThemedIcon({ name: 'headphones-symbolic' })); | ||||
|         this._headphoneIcon.visible = false; | ||||
|  | ||||
|         this.menu.addMenuItem(this._volumeMenu); | ||||
|  | ||||
|         this.indicators.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); | ||||
|         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|         this.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop'); | ||||
|  | ||||
|         this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); | ||||
|     }, | ||||
|  | ||||
|     _onScrollEvent: function(actor, event) { | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const AccountsService = imports.gi.AccountsService; | ||||
| const Atk = imports.gi.Atk; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gdm  = imports.gi.Gdm; | ||||
| const Gio = imports.gi.Gio; | ||||
| @@ -9,18 +8,18 @@ const GLib = imports.gi.GLib; | ||||
| const GnomeDesktop = imports.gi.GnomeDesktop; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
| const Shell = imports.gi.Shell; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Layout = imports.ui.layout; | ||||
| const Main = imports.ui.main; | ||||
| const ModalDialog = imports.ui.modalDialog; | ||||
| const Panel = imports.ui.panel; | ||||
| const ShellEntry = imports.ui.shellEntry; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const UserMenu = imports.ui.userMenu; | ||||
| const UserWidget = imports.ui.userWidget; | ||||
|  | ||||
| const AuthPrompt = imports.gdm.authPrompt; | ||||
| const Batch = imports.gdm.batch; | ||||
| const GdmUtil = imports.gdm.util; | ||||
| const LoginDialog = imports.gdm.loginDialog; | ||||
| @@ -28,39 +27,124 @@ const LoginDialog = imports.gdm.loginDialog; | ||||
| // The timeout before going back automatically to the lock screen (in seconds) | ||||
| const IDLE_TIMEOUT = 2 * 60; | ||||
|  | ||||
| function versionCompare(required, reference) { | ||||
|     required = required.split('.'); | ||||
|     reference = reference.split('.'); | ||||
|  | ||||
|     for (let i = 0; i < required.length; i++) { | ||||
|         if (required[i] != reference[i]) | ||||
|             return required[i] < reference[i]; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function isSupported() { | ||||
|     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; | ||||
|     } | ||||
| } | ||||
|  | ||||
| const UnlockDialog = new Lang.Class({ | ||||
|     Name: 'UnlockDialog', | ||||
|     Extends: ModalDialog.ModalDialog, | ||||
|  | ||||
|     _init: function(parentActor) { | ||||
|         this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, | ||||
|                                      style_class: 'login-dialog', | ||||
|                                      layout_manager: new Clutter.BoxLayout(), | ||||
|                                      visible: false }); | ||||
|  | ||||
|         this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); | ||||
|         parentActor.add_child(this.actor); | ||||
|         this.parent({ shellReactive: true, | ||||
|                       styleClass: 'login-dialog', | ||||
|                       keybindingMode: Shell.KeyBindingMode.UNLOCK_SCREEN, | ||||
|                       parentActor: parentActor | ||||
|                     }); | ||||
|  | ||||
|         this._userManager = AccountsService.UserManager.get_default(); | ||||
|         this._userName = GLib.get_user_name(); | ||||
|         this._user = this._userManager.get_user(this._userName); | ||||
|  | ||||
|         this._promptBox = new St.BoxLayout({ vertical: true, | ||||
|                                              x_align: Clutter.ActorAlign.CENTER, | ||||
|                                              y_align: Clutter.ActorAlign.CENTER, | ||||
|                                              x_expand: true, | ||||
|                                              y_expand: true }); | ||||
|         this.actor.add_child(this._promptBox); | ||||
|         this._failCounter = 0; | ||||
|         this._firstQuestion = true; | ||||
|  | ||||
|         this._authPrompt = new AuthPrompt.AuthPrompt(new Gdm.Client(), AuthPrompt.AuthPromptMode.UNLOCK_ONLY); | ||||
|         this._authPrompt.connect('failed', Lang.bind(this, this._fail)); | ||||
|         this._authPrompt.connect('cancelled', Lang.bind(this, this._fail)); | ||||
|         this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); | ||||
|         this._authPrompt.setPasswordChar('\u25cf'); | ||||
|         this._authPrompt.nextButton.label = _("Unlock"); | ||||
|         this._greeterClient = new Gdm.Client(); | ||||
|         this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true }); | ||||
|  | ||||
|         this._promptBox.add_child(this._authPrompt.actor); | ||||
|         this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion)); | ||||
|         this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage)); | ||||
|         this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); | ||||
|         this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed)); | ||||
|         this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); | ||||
|  | ||||
|         this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint)); | ||||
|         this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint)); | ||||
|  | ||||
|         this._userWidget = new UserWidget.UserWidget(this._user); | ||||
|         this.contentLayout.add_actor(this._userWidget.actor); | ||||
|  | ||||
|         this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', | ||||
|                                                 vertical: true }); | ||||
|  | ||||
|         this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); | ||||
|         this._promptLayout.add(this._promptLabel, | ||||
|                                { x_align: St.Align.START }); | ||||
|  | ||||
|         this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', | ||||
|                                            can_focus: true }); | ||||
|         this._promptEntry.clutter_text.set_password_char('\u25cf'); | ||||
|         ShellEntry.addContextMenu(this._promptEntry, { isPassword: true }); | ||||
|         this.setInitialKeyFocus(this._promptEntry); | ||||
|         this._promptEntry.clutter_text.connect('text-changed', Lang.bind(this, function() { | ||||
|             this._updateOkButtonSensitivity(this._promptEntry.text.length > 0); | ||||
|         })); | ||||
|  | ||||
|         this._promptLayout.add(this._promptEntry, | ||||
|                                { expand: true, | ||||
|                                  x_fill: true }); | ||||
|  | ||||
|         this.contentLayout.add_actor(this._promptLayout); | ||||
|  | ||||
|         this._promptMessage = new St.Label({ visible: false }); | ||||
|         this.contentLayout.add(this._promptMessage, { x_fill: true }); | ||||
|  | ||||
|         this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint' }); | ||||
|         this._promptLoginHint.hide(); | ||||
|         this.contentLayout.add_actor(this._promptLoginHint); | ||||
|  | ||||
|         this._workSpinner = new Panel.AnimatedIcon('process-working.svg', LoginDialog.WORK_SPINNER_ICON_SIZE); | ||||
|         this._workSpinner.actor.opacity = 0; | ||||
|  | ||||
|         this.allowCancel = false; | ||||
|         this.buttonLayout.visible = true; | ||||
|         this.addButton({ label: _("Cancel"), | ||||
|                          action: Lang.bind(this, this._escape), | ||||
|                          key: Clutter.KEY_Escape }, | ||||
|                        { expand: true, | ||||
|                          x_fill: false, | ||||
|                          y_fill: false, | ||||
|                          x_align: St.Align.START, | ||||
|                          y_align: St.Align.MIDDLE }); | ||||
|         this.buttonLayout.add(this._workSpinner.actor, | ||||
|                               { expand: false, | ||||
|                                 x_fill: false, | ||||
|                                 y_fill: false, | ||||
|                                 x_align: St.Align.END, | ||||
|                                 y_align: St.Align.MIDDLE }); | ||||
|         this._okButton = this.addButton({ label: _("Unlock"), | ||||
|                                           action: Lang.bind(this, this._doUnlock), | ||||
|                                           default: true }, | ||||
|                                         { expand: false, | ||||
|                                           x_fill: false, | ||||
|                                           y_fill: false, | ||||
|                                           x_align: St.Align.END, | ||||
|                                           y_align: St.Align.MIDDLE }); | ||||
|  | ||||
|         let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' }); | ||||
|         if (screenSaverSettings.get_boolean('user-switch-enabled')) { | ||||
| @@ -73,100 +157,180 @@ const UnlockDialog = new Lang.Class({ | ||||
|                                                     x_align: St.Align.START, | ||||
|                                                     x_fill: true }); | ||||
|             this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked)); | ||||
|             this._promptBox.add_child(this._otherUserButton); | ||||
|             this.dialogLayout.add(this._otherUserButton, | ||||
|                                   { x_align: St.Align.START, | ||||
|                                     x_fill: false }); | ||||
|         } else { | ||||
|             this._otherUserButton = null; | ||||
|         } | ||||
|  | ||||
|         this._authPrompt.reset(); | ||||
|         this._updateSensitivity(true); | ||||
|  | ||||
|         Main.ctrlAltTabManager.addGroup(this.actor, _("Unlock Window"), 'dialog-password-symbolic'); | ||||
|         let batch = new Batch.Hold(); | ||||
|         this._userVerifier.begin(this._userName, batch); | ||||
|  | ||||
|         this._idleMonitor = Meta.IdleMonitor.get_core(); | ||||
|         GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|             this.emit('loaded'); | ||||
|             return false; | ||||
|         })); | ||||
|  | ||||
|         Main.ctrlAltTabManager.addGroup(this.dialogLayout, _("Unlock Window"), 'dialog-password-symbolic'); | ||||
|  | ||||
|         this._idleMonitor = new GnomeDesktop.IdleMonitor(); | ||||
|         this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape)); | ||||
|     }, | ||||
|  | ||||
|     _updateSensitivity: function(sensitive) { | ||||
|         this._authPrompt.updateSensitivity(sensitive); | ||||
|  | ||||
|         this._promptEntry.reactive = sensitive; | ||||
|         this._promptEntry.clutter_text.editable = sensitive; | ||||
|         this._updateOkButtonSensitivity(sensitive && this._promptEntry.text.length > 0); | ||||
|         if (this._otherUserButton) { | ||||
|             this._otherUserButton.reactive = sensitive; | ||||
|             this._otherUserButton.can_focus = sensitive; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _fail: function() { | ||||
|     _updateOkButtonSensitivity: function(sensitive) { | ||||
|         this._okButton.reactive = sensitive; | ||||
|         this._okButton.can_focus = sensitive; | ||||
|     }, | ||||
|  | ||||
|     _setWorking: function(working) { | ||||
|         if (working) { | ||||
|             this._workSpinner.play(); | ||||
|             Tweener.addTween(this._workSpinner.actor, | ||||
|                              { opacity: 255, | ||||
|                                delay: LoginDialog.WORK_SPINNER_ANIMATION_DELAY, | ||||
|                                time: LoginDialog.WORK_SPINNER_ANIMATION_TIME, | ||||
|                                transition: 'linear' | ||||
|                              }); | ||||
|         } else { | ||||
|             Tweener.addTween(this._workSpinner.actor, | ||||
|                              { opacity: 0, | ||||
|                                time: LoginDialog.WORK_SPINNER_ANIMATION_TIME, | ||||
|                                transition: 'linear', | ||||
|                                onCompleteScope: this, | ||||
|                                onComplete: function() { | ||||
|                                    this._workSpinner.stop(); | ||||
|                                } | ||||
|                              }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _showMessage: function(userVerifier, message, styleClass) { | ||||
|         if (message) { | ||||
|             this._promptMessage.text = message; | ||||
|             this._promptMessage.styleClass = styleClass; | ||||
|             GdmUtil.fadeInActor(this._promptMessage); | ||||
|         } else { | ||||
|             GdmUtil.fadeOutActor(this._promptMessage); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onAskQuestion: function(verifier, serviceName, question, passwordChar) { | ||||
|         if (this._firstQuestion && this._firstQuestionAnswer) { | ||||
|             this._userVerifier.answerQuery(serviceName, this._firstQuestionAnswer); | ||||
|             this._firstQuestionAnswer = null; | ||||
|             this._firstQuestion = false; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._promptLabel.text = question; | ||||
|  | ||||
|         if (!this._firstQuestion) | ||||
|             this._promptEntry.text = ''; | ||||
|         else | ||||
|             this._firstQuestion = false; | ||||
|  | ||||
|         this._promptEntry.clutter_text.set_password_char(passwordChar); | ||||
|         this._promptEntry.menu.isPassword = passwordChar != ''; | ||||
|  | ||||
|         this._currentQuery = serviceName; | ||||
|         this._updateSensitivity(true); | ||||
|         this._setWorking(false); | ||||
|     }, | ||||
|  | ||||
|     _showLoginHint: function(verifier, message) { | ||||
|         this._promptLoginHint.set_text(message) | ||||
|         GdmUtil.fadeInActor(this._promptLoginHint); | ||||
|     }, | ||||
|  | ||||
|     _hideLoginHint: function() { | ||||
|         GdmUtil.fadeOutActor(this._promptLoginHint); | ||||
|     }, | ||||
|  | ||||
|     _doUnlock: function() { | ||||
|         if (this._firstQuestion) { | ||||
|             // we haven't received a query yet, so stash the answer | ||||
|             // and make ourself non-reactive | ||||
|             // the actual reply to GDM will be sent as soon as asked | ||||
|             this._firstQuestionAnswer = this._promptEntry.text; | ||||
|             this._updateSensitivity(false); | ||||
|             this._setWorking(true); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (!this._currentQuery) | ||||
|             return; | ||||
|  | ||||
|         let query = this._currentQuery; | ||||
|         this._currentQuery = null; | ||||
|  | ||||
|         this._updateSensitivity(false); | ||||
|         this._setWorking(true); | ||||
|  | ||||
|         this._userVerifier.answerQuery(query, this._promptEntry.text); | ||||
|     }, | ||||
|  | ||||
|     _onVerificationComplete: function() { | ||||
|         this._userVerifier.clear(); | ||||
|         this.emit('unlocked'); | ||||
|     }, | ||||
|  | ||||
|     _onReset: function() { | ||||
|         this.emit('failed'); | ||||
|     }, | ||||
|  | ||||
|     _onReset: function(authPrompt, beginRequest) { | ||||
|         let userName; | ||||
|         if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { | ||||
|             this._authPrompt.setUser(this._user); | ||||
|             userName = this._userName; | ||||
|         } else { | ||||
|             userName = null; | ||||
|         } | ||||
|     _onVerificationFailed: function() { | ||||
|         this._currentQuery = null; | ||||
|         this._firstQuestion = true; | ||||
|  | ||||
|         this._authPrompt.begin({ userName: userName }); | ||||
|         this._promptEntry.text = ''; | ||||
|         this._promptEntry.clutter_text.set_password_char('\u25cf'); | ||||
|         this._promptEntry.menu.isPassword = true; | ||||
|  | ||||
|         this._updateSensitivity(false); | ||||
|         this._setWorking(false); | ||||
|     }, | ||||
|  | ||||
|     _escape: function() { | ||||
|         if (this.allowCancel) | ||||
|             this._authPrompt.cancel(); | ||||
|         if (this.allowCancel) { | ||||
|             this._userVerifier.cancel(); | ||||
|             this.emit('failed'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _otherUserClicked: function(button, event) { | ||||
|         Gdm.goto_login_session_sync(null); | ||||
|  | ||||
|         this._authPrompt.cancel(); | ||||
|         this._userVerifier.cancel(); | ||||
|         this.emit('failed'); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this.popModal(); | ||||
|         this.actor.destroy(); | ||||
|         this._userVerifier.clear(); | ||||
|  | ||||
|         if (this._idleWatchId) { | ||||
|             this._idleMonitor.remove_watch(this._idleWatchId); | ||||
|             this._idleWatchId = 0; | ||||
|         } | ||||
|  | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
|     cancel: function() { | ||||
|         this._authPrompt.cancel(); | ||||
|         this._userVerifier.cancel(null); | ||||
|  | ||||
|         this.destroy(); | ||||
|     }, | ||||
|  | ||||
|     addCharacter: function(unichar) { | ||||
|         this._authPrompt.addCharacter(unichar); | ||||
|     }, | ||||
|  | ||||
|     finish: function(onComplete) { | ||||
|         this._authPrompt.finish(onComplete); | ||||
|     }, | ||||
|  | ||||
|     open: function(timestamp) { | ||||
|         this.actor.show(); | ||||
|  | ||||
|         if (this._isModal) | ||||
|             return true; | ||||
|  | ||||
|         if (!Main.pushModal(this.actor, { timestamp: timestamp, | ||||
|                                           keybindingMode: Shell.KeyBindingMode.UNLOCK_SCREEN })) | ||||
|             return false; | ||||
|  | ||||
|         this._isModal = true; | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     popModal: function(timestamp) { | ||||
|         if (this._isModal) { | ||||
|             Main.popModal(this.actor, timestamp); | ||||
|             this._isModal = false; | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(UnlockDialog.prototype); | ||||
|   | ||||