Compare commits

..

2 Commits

Author SHA1 Message Date
Giovanni Campagna
6d4ca1fcc8 WorkspaceThumbnails: show attached modal dialogs togheter with their parents
The window clones in the central part of the overview are showing modal
dialogs now, and this creates an inconsistency if the thumbnail doesn't too.
Code is intentionally similar in the two places.

https://bugzilla.gnome.org/show_bug.cgi?id=650843
2013-03-04 17:50:18 +01:00
Giovanni Campagna
93b1af401f Workspace: show attached modal dialog
Windows in the overview should be like they appear in the workspace,
including modal dialogs that are attached above them.
In addition, hiding the dialogs in the overview causes a flash as
dialog appears at the end of the transition.

Based on a patch by Maxim Ermilov <zaspire@rambler.ru>

https://bugzilla.gnome.org/show_bug.cgi?id=650843
2013-03-04 17:50:18 +01:00
246 changed files with 39949 additions and 51072 deletions

View File

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

345
NEWS
View File

@@ -1,348 +1,3 @@
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 3.7.90
====== ======
* Let GNOME Shell work on EGL and GLES2 [Neil; #693225, #693438, #693339] * Let GNOME Shell work on EGL and GLES2 [Neil; #693225, #693438, #693339]

View File

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

View File

@@ -1,5 +1,5 @@
AC_PREREQ(2.63) AC_PREREQ(2.63)
AC_INIT([gnome-shell],[3.9.90],[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_HEADERS([config.h])
AC_CONFIG_SRCDIR([src/shell-global.c]) AC_CONFIG_SRCDIR([src/shell-global.c])
@@ -53,7 +53,7 @@ if $PKG_CONFIG --exists gstreamer-1.0 '>=' $GSTREAMER_MIN_VERSION ; then
AC_MSG_RESULT(yes) AC_MSG_RESULT(yes)
build_recorder=true build_recorder=true
recorder_modules="gstreamer-1.0 gstreamer-base-1.0 x11 gtk+-3.0" recorder_modules="gstreamer-1.0 gstreamer-base-1.0 x11 gtk+-3.0"
PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0) PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0 xfixes)
else else
AC_MSG_RESULT(no) AC_MSG_RESULT(no)
fi fi
@@ -63,18 +63,18 @@ AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
CLUTTER_MIN_VERSION=1.13.4 CLUTTER_MIN_VERSION=1.13.4
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1 GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
GJS_MIN_VERSION=1.35.4 GJS_MIN_VERSION=1.35.4
MUTTER_MIN_VERSION=3.9.90 MUTTER_MIN_VERSION=3.7.90
GTK_MIN_VERSION=3.7.9 GTK_MIN_VERSION=3.7.9
GIO_MIN_VERSION=2.37.0 GIO_MIN_VERSION=2.35.0
LIBECAL_MIN_VERSION=3.5.3 LIBECAL_MIN_VERSION=3.5.3
LIBEDATASERVER_MIN_VERSION=3.5.3 LIBEDATASERVER_MIN_VERSION=3.5.3
TELEPATHY_GLIB_MIN_VERSION=0.17.5 TELEPATHY_GLIB_MIN_VERSION=0.17.5
POLKIT_MIN_VERSION=0.100 POLKIT_MIN_VERSION=0.100
STARTUP_NOTIFICATION_MIN_VERSION=0.11 STARTUP_NOTIFICATION_MIN_VERSION=0.11
GCR_MIN_VERSION=3.7.5 GCR_MIN_VERSION=3.3.90
GNOME_DESKTOP_REQUIRED_VERSION=3.7.90 GNOME_DESKTOP_REQUIRED_VERSION=3.7.90
GNOME_MENUS_REQUIRED_VERSION=3.5.3 GNOME_MENUS_REQUIRED_VERSION=3.5.3
NETWORKMANAGER_MIN_VERSION=0.9.8 NETWORKMANAGER_MIN_VERSION=0.9.6
PULSE_MIN_VERS=2.0 PULSE_MIN_VERS=2.0
# Collect more than 20 libraries for a prize! # Collect more than 20 libraries for a prize!
@@ -87,17 +87,16 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION
libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION
$recorder_modules $recorder_modules
gdk-x11-3.0 libsoup-2.4 gdk-x11-3.0 libsoup-2.4
xtst
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
clutter-glx-1.0 >= $CLUTTER_MIN_VERSION clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION
gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
libcanberra libcanberra-gtk3 libcanberra libcanberra-gtk3
telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION
polkit-agent-1 >= $POLKIT_MIN_VERSION polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes
libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION libnm-glib libnm-util >= $NETWORKMANAGER_MIN_VERSION
libnm-gtk >= $NETWORKMANAGER_MIN_VERSION libnm-gtk >= $NETWORKMANAGER_MIN_VERSION
libsecret-unstable gcr-base-3 >= $GCR_MIN_VERSION) 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(GNOME_SHELL_JS, gio-2.0 gjs-internals-1.0 >= $GJS_MIN_VERSION)
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.8 x11) PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.8 x11)
@@ -110,7 +109,7 @@ PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.7.4)
PKG_CHECK_MODULES(CARIBOU, caribou-1.0 >= 0.4.8) PKG_CHECK_MODULES(CARIBOU, caribou-1.0 >= 0.4.8)
AC_MSG_CHECKING([for bluetooth support]) AC_MSG_CHECKING([for bluetooth support])
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.9.0], PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0],
[BLUETOOTH_DIR=`$PKG_CONFIG --variable=applet_libdir gnome-bluetooth-1.0` [BLUETOOTH_DIR=`$PKG_CONFIG --variable=applet_libdir gnome-bluetooth-1.0`
BLUETOOTH_LIBS=`$PKG_CONFIG --variable=applet_libs gnome-bluetooth-1.0` BLUETOOTH_LIBS=`$PKG_CONFIG --variable=applet_libs gnome-bluetooth-1.0`
AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"]) AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"])

View File

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

View File

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

View File

@@ -1,6 +1,3 @@
wandadir = $(pkgdatadir)
dist_wanda_DATA = wanda.png
desktopdir=$(datadir)/applications desktopdir=$(datadir)/applications
desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
@@ -15,7 +12,6 @@ desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
introspectiondir = $(datadir)/dbus-1/interfaces introspectiondir = $(datadir)/dbus-1/interfaces
introspection_DATA = \ introspection_DATA = \
org.gnome.Shell.Screencast.xml \
org.gnome.Shell.Screenshot.xml \ org.gnome.Shell.Screenshot.xml \
org.gnome.ShellSearchProvider.xml \ org.gnome.ShellSearchProvider.xml \
org.gnome.ShellSearchProvider2.xml org.gnome.ShellSearchProvider2.xml
@@ -56,7 +52,10 @@ dist_theme_DATA = \
theme/ws-switch-arrow-down.png theme/ws-switch-arrow-down.png
keysdir = @GNOME_KEYBINDINGS_KEYSDIR@ keysdir = @GNOME_KEYBINDINGS_KEYSDIR@
keys_in_files = 50-gnome-shell-system.xml.in keys_in_files = \
50-gnome-shell-screenshot.xml.in \
50-gnome-shell-system.xml.in \
$(NULL)
keys_DATA = $(keys_in_files:.xml.in=.xml) keys_DATA = $(keys_in_files:.xml.in=.xml)
gsettings_SCHEMAS = org.gnome.shell.gschema.xml gsettings_SCHEMAS = org.gnome.shell.gschema.xml

View File

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

View File

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

View File

@@ -21,6 +21,16 @@
EnableExtension and DisableExtension DBus methods on org.gnome.Shell. EnableExtension and DisableExtension DBus methods on org.gnome.Shell.
</_description> </_description>
</key> </key>
<key name="enable-app-monitoring" type="b">
<default>true</default>
<_summary>Whether to collect stats about applications usage</_summary>
<_description>
The shell normally monitors active applications in order to present
the most used ones (e.g. in launchers). While this data will be
kept private, you may want to disable this for privacy reasons.
Please note that doing so won't remove already saved data.
</_description>
</key>
<key name="favorite-apps" type="as"> <key name="favorite-apps" type="as">
<default>[ 'epiphany.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'libreoffice-writer.desktop', 'nautilus.desktop', 'gnome-documents.desktop' ]</default> <default>[ 'epiphany.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'libreoffice-writer.desktop', 'nautilus.desktop', 'gnome-documents.desktop' ]</default>
<_summary>List of desktop file IDs for favorite applications</_summary> <_summary>List of desktop file IDs for favorite applications</_summary>
@@ -74,6 +84,7 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
</_description> </_description>
</key> </key>
<child name="calendar" schema="org.gnome.shell.calendar"/> <child name="calendar" schema="org.gnome.shell.calendar"/>
<child name="recorder" schema="org.gnome.shell.recorder"/>
<child name="keybindings" schema="org.gnome.shell.keybindings"/> <child name="keybindings" schema="org.gnome.shell.keybindings"/>
<child name="keyboard" schema="org.gnome.shell.keyboard"/> <child name="keyboard" schema="org.gnome.shell.keyboard"/>
</schema> </schema>
@@ -106,13 +117,6 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
Overview. Overview.
</_description> </_description>
</key> </key>
<key name="toggle-overview" type="as">
<default>["&lt;Super&gt;s"]</default>
<_summary>Keybinding to open the overview</_summary>
<_description>
Keybinding to open the Activities Overview.
</_description>
</key>
<key name="toggle-message-tray" type="as"> <key name="toggle-message-tray" type="as">
<default>["&lt;Super&gt;m"]</default> <default>["&lt;Super&gt;m"]</default>
<_summary>Keybinding to toggle the visibility of the message tray</_summary> <_summary>Keybinding to toggle the visibility of the message tray</_summary>
@@ -127,6 +131,13 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
Keybinding to focus the active notification. Keybinding to focus the active notification.
</_description> </_description>
</key> </key>
<key name="toggle-recording" type="as">
<default><![CDATA[['<Control><Shift><Alt>r']]]></default>
<_summary>Keybinding to toggle the screen recorder</_summary>
<_description>
Keybinding to start/stop the builtin screen recorder.
</_description>
</key>
</schema> </schema>
<schema id="org.gnome.shell.keyboard" path="/org/gnome/shell/keyboard/" <schema id="org.gnome.shell.keyboard" path="/org/gnome/shell/keyboard/"
@@ -140,16 +151,41 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
</key> </key>
</schema> </schema>
<schema id="org.gnome.shell.app-switcher" <schema id="org.gnome.shell.recorder" path="/org/gnome/shell/recorder/"
path="/org/gnome/shell/app-switcher/"
gettext-domain="@GETTEXT_PACKAGE@"> gettext-domain="@GETTEXT_PACKAGE@">
<key type="b" name="current-workspace-only"> <key name="framerate" type="i">
<default>false</default> <default>30</default>
<summary>Limit switcher to current workspace.</summary> <_summary>Framerate used for recording screencasts.</_summary>
<description> <_description>
If true, only applications that have windows on the current workspace are shown in the switcher. The framerate of the resulting screencast recordered
Otherwise, all applications are included. by GNOME Shell's screencast recorder in frames-per-second.
</description> </_description>
</key>
<key name="pipeline" type="s">
<default>''</default>
<_summary>The gstreamer pipeline used to encode the screencast</_summary>
<_description>
Sets the GStreamer pipeline used to encode recordings.
It follows the syntax used for gst-launch. The pipeline should have
an unconnected sink pad where the recorded video is recorded. It will
normally have a unconnected source pad; output from that pad
will be written into the output file. However the pipeline can also
take care of its own output - this might be used to send the output
to an icecast server via shout2send or similar. When unset or set
to an empty value, the default pipeline will be used. This is currently
'vp8enc min_quantizer=13 max_quantizer=13 cpu-used=5 deadline=1000000 threads=%T ! queue ! webmmux'
and records to WEBM using the VP8 codec. %T is used as a placeholder
for a guess at the optimal thread count on the system.
</_description>
</key>
<key name="file-extension" type="s">
<default>'webm'</default>
<_summary>File extension used for storing the screencast</_summary>
<_description>
The filename for recorded screencasts will be a unique filename
based on the current date, and use this extension. It should be
changed when recording to a different container format.
</_description>
</key> </key>
</schema> </schema>
@@ -171,7 +207,7 @@ value here is from the GsmPresenceStatus enumeration.</_summary>
</_description> </_description>
</key> </key>
<key type="b" name="current-workspace-only"> <key type="b" name="current-workspace-only">
<default>true</default> <default>false</default>
<summary>Limit switcher to current workspace.</summary> <summary>Limit switcher to current workspace.</summary>
<description> <description>
If true, only windows from the current workspace are shown in the switcher. If true, only windows from the current workspace are shown in the switcher.

View File

@@ -101,7 +101,7 @@ StScrollBar StButton#vhandle:active {
/* Check Boxes */ /* Check Boxes */
.check-box StBoxLayout { .check-box ShellGenericContainer {
spacing: .8em; spacing: .8em;
} }
@@ -123,28 +123,9 @@ StScrollBar StButton#vhandle:active {
background-image: url("checkbox-focused.svg"); background-image: url("checkbox-focused.svg");
} }
/* Slider */
.slider {
height: 1em;
-slider-height: 0.3em;
-slider-background-color: #333333;
-slider-border-color: #5f5f5f;
-slider-active-background-color: #76b0ec;
-slider-active-border-color: #1f6dbc;
-slider-border-width: 1px;
-slider-handle-radius: 6px;
}
/* PopupMenu */ /* PopupMenu */
.popup-menu-ornament { .popup-menu-boxpointer {
text-align: right;
width: 1em;
}
.popup-menu-boxpointer,
.candidate-popup-boxpointer {
-arrow-border-radius: 8px; -arrow-border-radius: 8px;
-arrow-background-color: rgba(0,0,0,0.9); -arrow-background-color: rgba(0,0,0,0.9);
-arrow-border-width: 2px; -arrow-border-width: 2px;
@@ -157,10 +138,6 @@ StScrollBar StButton#vhandle:active {
min-width: 200px; min-width: 200px;
} }
.popup-submenu-menu-item-triangle {
font-size: 120%;
}
.popup-submenu-menu-item:open { .popup-submenu-menu-item:open {
background-color: #4c4c4c; background-color: #4c4c4c;
} }
@@ -193,6 +170,13 @@ StScrollBar StButton#vhandle:active {
border-width: 0px; border-width: 0px;
} }
.popup-combo-menu {
background-color: rgba(0,0,0,0.9);
padding: 1em 0em;
border: 1px solid #5f5f5f;
border-radius: 9px;
}
/* The remaining popup-menu sizing is all done in ems, so that if you /* The remaining popup-menu sizing is all done in ems, so that if you
* override .popup-menu.font-size, everything else will scale with it. * override .popup-menu.font-size, everything else will scale with it.
*/ */
@@ -201,15 +185,8 @@ StScrollBar StButton#vhandle:active {
} }
.popup-menu-item { .popup-menu-item {
spacing: 12px; padding: .4em 1.75em;
} spacing: 1em;
.popup-menu-item:ltr {
padding: .4em 1.75em .4em 0em;
}
.popup-menu-item:rtl {
padding: .4em 0em .4em 1.75em;
} }
.popup-menu-item:active { .popup-menu-item:active {
@@ -223,6 +200,10 @@ StScrollBar StButton#vhandle:active {
.popup-image-menu-item { .popup-image-menu-item {
} }
.popup-combobox-item {
spacing: 1em;
}
.popup-separator-menu-item { .popup-separator-menu-item {
-gradient-height: 1px; -gradient-height: 1px;
-gradient-start: rgba(255,255,255,0.0); -gradient-start: rgba(255,255,255,0.0);
@@ -236,6 +217,22 @@ StScrollBar StButton#vhandle:active {
font-weight: bold; font-weight: bold;
} }
.popup-slider-menu-item {
height: 1em;
min-width: 15em;
-slider-height: 0.3em;
-slider-background-color: #333333;
-slider-border-color: #5f5f5f;
-slider-active-background-color: #76b0ec;
-slider-active-border-color: #1f6dbc;
-slider-border-width: 1px;
-slider-handle-radius: 0.5em;
}
.popup-device-menu-item {
spacing: .5em;
}
.popup-status-menu-item { .popup-status-menu-item {
font-weight: normal; font-weight: normal;
color: #999; color: #999;
@@ -245,10 +242,19 @@ StScrollBar StButton#vhandle:active {
color: white; color: white;
} }
.popup-subtitle-menu-item, .popup-subtitle-menu-item:insensitive {
font-weight: bold;
color: white;
}
.popup-menu-icon { .popup-menu-icon {
icon-size: 1.09em; icon-size: 1.09em;
} }
.popup-battery-percentage {
padding-left: 24px;
}
/* Switches */ /* Switches */
.toggle-switch { .toggle-switch {
width: 65px; width: 65px;
@@ -273,57 +279,10 @@ StScrollBar StButton#vhandle:active {
background-size: contain; background-size: contain;
} }
/* Network */ .nm-menu-item-icons {
.nm-dialog {
max-height: 500px;
min-height: 450px;
min-width: 470px;
}
.nm-dialog-content {
spacing: 20px;
}
.nm-dialog-header-hbox {
spacing: 10px;
}
.nm-dialog-header-icon {
icon-size: 32px;
}
.nm-dialog-scroll-view {
border: 2px solid #666;
border-radius: 6px;
}
.nm-dialog-header {
font-weight: bold;
}
.nm-dialog-item {
font-size: 12pt;
border-bottom: 1px solid #666;
padding: 12px;
}
.nm-dialog-item:checked {
background-color: #333;
}
.nm-dialog-item-box {
spacing: 20px;
}
.nm-dialog-icons {
spacing: .5em; spacing: .5em;
} }
.nm-dialog-icon {
icon-size: 16px;
}
/* Buttons */ /* Buttons */
.candidate-page-button, .candidate-page-button,
@@ -356,16 +315,10 @@ StScrollBar StButton#vhandle:active {
.notification-button:focus, .notification-button:focus,
.notification-icon-button:focus, .notification-icon-button:focus,
.hotplug-notification-item:focus, .hotplug-notification-item:focus,
.modal-dialog-button:focus, .modal-dialog-button:focus {
.app-view-control:focus {
border-width: 2px; border-width: 2px;
} }
.app-view-control:first-child:ltr:focus,
.app-view-control:last-child:rtl:focus {
border-right-width: 1px;
}
.candidate-page-button:active, .candidate-page-button:active,
.candidate-page-button:pressed, .candidate-page-button:pressed,
.notification-button:active, .notification-button:active,
@@ -395,8 +348,7 @@ StScrollBar StButton#vhandle:active {
.modal-dialog-button, .modal-dialog-button,
.notification-button, .notification-button,
.hotplug-notification-item, .hotplug-notification-item,
.app-view-controls, .app-view-controls {
#screenShieldNotifications {
border-radius: 18px; border-radius: 18px;
} }
@@ -414,7 +366,6 @@ StScrollBar StButton#vhandle:active {
/* Entries */ /* Entries */
#searchEntry, #searchEntry,
.login-dialog StEntry,
.notification StEntry, .notification StEntry,
.modal-dialog StEntry { .modal-dialog StEntry {
color: rgb(64, 64, 64); color: rgb(64, 64, 64);
@@ -426,7 +377,6 @@ StScrollBar StButton#vhandle:active {
} }
#searchEntry, #searchEntry,
.login-dialog StEntry,
.run-dialog-entry, .run-dialog-entry,
.notification StEntry { .notification StEntry {
border: 2px solid rgba(245,245,245,0.2); border: 2px solid rgba(245,245,245,0.2);
@@ -439,7 +389,6 @@ StScrollBar StButton#vhandle:active {
#searchEntry:focus, #searchEntry:focus,
#searchEntry:hover, #searchEntry:hover,
.login-dialog StEntry:focus,
.notification StEntry:focus, .notification StEntry:focus,
.modal-dialog StEntry { .modal-dialog StEntry {
border: 2px solid rgb(136,138,133); border: 2px solid rgb(136,138,133);
@@ -449,7 +398,6 @@ StScrollBar StButton#vhandle:active {
box-shadow: inset 0px 2px 4px rgba(0,0,0,0.6); box-shadow: inset 0px 2px 4px rgba(0,0,0,0.6);
} }
.login-dialog StEntry:focus,
.notification StEntry:focus, .notification StEntry:focus,
.modal-dialog StEntry:focus { .modal-dialog StEntry:focus {
border: 2px solid #3465a4; border: 2px solid #3465a4;
@@ -473,7 +421,6 @@ StScrollBar StButton#vhandle:active {
transition-duration: 0ms; transition-duration: 0ms;
} }
.login-dialog StEntry,
.notification StEntry, .notification StEntry,
.modal-dialog StEntry { .modal-dialog StEntry {
border-radius: 5px; border-radius: 5px;
@@ -487,7 +434,6 @@ StScrollBar StButton#vhandle:active {
padding: 0 4px; padding: 0 4px;
} }
.login-dialog StEntry:insensitive,
.modal-dialog StEntry:insensitive { .modal-dialog StEntry:insensitive {
border-color: #666666; border-color: #666666;
color: #9f9f9f; color: #9f9f9f;
@@ -505,6 +451,10 @@ StScrollBar StButton#vhandle:active {
height: 1.86em; height: 1.86em;
} }
#panel.lock-screen {
background-color: rgba(0,0,0,0.3);
}
#panel.unlock-screen, #panel.unlock-screen,
#panel.login-screen { #panel.login-screen {
background-color: transparent; background-color: transparent;
@@ -551,7 +501,6 @@ StScrollBar StButton#vhandle:active {
} }
#appMenu { #appMenu {
spinner-image: url("process-working.svg");
spacing: 4px; spacing: 4px;
} }
@@ -631,48 +580,66 @@ StScrollBar StButton#vhandle:active {
-boxpointer-gap: 4px; -boxpointer-gap: 4px;
} }
.panel-status-indicators-box, .panel-status-button-box {
.panel-status-menu-box { spacing: 4px;
}
.lock-screen-status-button-box {
spacing: 8px; spacing: 8px;
} }
/* User Menu */
#panelUserMenu {
spacing: 4px;
}
.status-chooser {
spacing: .4em;
}
.status-chooser .popup-menu-item,
.status-chooser-combo .popup-menu-item {
padding: .4em;
}
.status-chooser-user-icon {
border: 2px solid #8b8b8b;
border-radius: 5px;
width: 48pt;
height: 48pt;
background-size: contain;
}
.status-chooser-user-icon:hover {
border: 2px solid #bbbbbb;
}
.status-chooser-user-name {
font-weight: bold;
font-size: 1.3em;
min-width: 120pt;
}
.status-chooser-combo {
border: 1px solid transparent;
}
.status-chooser-combo.popup-combo-menu {
padding: .4em 0em;
border-radius: 4px;
border: 1px solid #5f5f5f;
}
.status-chooser-status-item,
.status-chooser-combo .popup-combobox-item {
spacing: .4em;
}
.system-status-icon { .system-status-icon {
icon-size: 1.09em; icon-size: 1.09em;
} }
.aggregate-menu {
width: 340px;
}
.aggregate-menu .popup-menu-icon {
padding: 0 4px;
}
.system-switch-user-submenu-icon {
icon-size: 24px;
border: 1px solid #8b8b8b;
}
.system-menu-action {
color: #e6e6e6;
border-radius: 4px;
padding: 6px;
}
.system-menu-action:hover,
.system-menu-action:focus {
color: white;
background-color: rgba(255,255,255,0.1);
}
.system-menu-action > StIcon {
icon-size: 32px;
}
.screencast-indicator {
color: #ff0000;
}
/* Overview */ /* Overview */
#overview { #overview {
@@ -821,11 +788,6 @@ StScrollBar StButton#vhandle:active {
height: 24px; height: 24px;
} }
.empty-dash-drop-target {
width: 24px;
height: 24px;
}
/* Search Box */ /* Search Box */
#searchEntry { #searchEntry {
@@ -913,23 +875,19 @@ StScrollBar StButton#vhandle:active {
} }
.app-view-controls { .app-view-controls {
width: 250px;
padding-bottom: 32px; padding-bottom: 32px;
} }
.app-view-control { .app-view-control {
padding: 4px 32px; padding: 4px 16px;
} }
.app-view-control:focus {
padding: 3px 31px;
}
.search-display > StBoxLayout, .search-display > StBoxLayout,
.all-apps > StBoxLayout, .all-apps > StBoxLayout,
.frequent-apps > StBoxLayout { .frequent-apps > StBoxLayout {
/* horizontal padding to make sure scrollbars or dash don't overlap content */ /* horizontal padding to make sure the scrollbar doesn't overlap content */
padding: 0px 88px; padding: 0px 18px;
} }
.app-folder-icon { .app-folder-icon {
@@ -973,10 +931,6 @@ StScrollBar StButton#vhandle:active {
text-align: center; text-align: center;
} }
.search-provider-icon {
padding: 15px;
}
.app-folder-popup { .app-folder-popup {
-arrow-border-radius: 8px; -arrow-border-radius: 8px;
-arrow-background-color: black; -arrow-background-color: black;
@@ -1009,10 +963,6 @@ StScrollBar StButton#vhandle:active {
color:white; color:white;
} }
.app-display .app-well-app > .overview-icon {
border-radius: 10px;
}
.list-search-result:hover .list-search-result-description { .list-search-result:hover .list-search-result-description {
text-shadow: rgba(0,0,0,0.8) 0px 1px 2px; text-shadow: rgba(0,0,0,0.8) 0px 1px 2px;
} }
@@ -1150,7 +1100,7 @@ StScrollBar StButton#vhandle:active {
padding: 4px; padding: 4px;
} }
.lg-extensions-list { .lg-extension-list {
padding: 4px; padding: 4px;
spacing: 6px; spacing: 6px;
} }
@@ -1178,6 +1128,11 @@ StScrollBar StButton#vhandle:active {
/* Calendar popup */ /* Calendar popup */
#calendarEventsArea {
/* this is the width of the second column of the popup */
min-width: 320px;
}
.calendar-vertical-separator { .calendar-vertical-separator {
-stipple-width: 1px; -stipple-width: 1px;
-stipple-color: #505050; -stipple-color: #505050;
@@ -1213,8 +1168,7 @@ StScrollBar StButton#vhandle:active {
background-image: url("calendar-arrow-right.svg"); background-image: url("calendar-arrow-right.svg");
} }
.calendar-change-month-back:hover, .calendar-change-month-back:hover {
.calendar-change-month-back:focus {
background-color: #999999; background-color: #999999;
} }
.calendar-change-month-back:active { .calendar-change-month-back:active {
@@ -1232,8 +1186,7 @@ StScrollBar StButton#vhandle:active {
background-image: url("calendar-arrow-left.svg"); background-image: url("calendar-arrow-left.svg");
} }
.calendar-change-month-forward:hover, .calendar-change-month-forward:hover {
.calendar-change-month-forward:focus {
background-color: #999999; background-color: #999999;
} }
.calendar-change-month-forward:active { .calendar-change-month-forward:active {
@@ -1254,8 +1207,7 @@ StScrollBar StButton#vhandle:active {
height: 2.4em; height: 2.4em;
} }
.calendar-day-base:hover, .calendar-day-base:hover {
.calendar-day-base:focus {
background-color: #777777; background-color: #777777;
} }
@@ -1305,49 +1257,41 @@ StScrollBar StButton#vhandle:active {
font-weight: bold; font-weight: bold;
} }
.calendar-other-month-day {
color: #333333;
}
.calendar-day-with-events { .calendar-day-with-events {
font-weight: bold; font-weight: bold;
color: white; color: white;
} }
.calendar-other-month-day { .events-header-vbox {
color: #333333; spacing: 6pt;
padding-right: .5em;
} }
.events-table { .events-header-vbox:rtl {
width: 320px; padding-left: .5em;
spacing-columns: 6pt;
padding: 0 1.4em;
} }
.events-table:ltr { .events-header-hbox {
padding-right: 1.9em; padding: 0.3em 1.4em;
}
.events-table:rtl {
padding-left: 1.9em;
} }
.events-day-header { .events-day-header {
font-weight: bold; font-weight: bold;
color: #999999; color: #999999;
padding-left: 0.4em; padding: 0.4em 1.4em 0em 1.4em;
padding-top: 1.2em;
}
.events-day-header:first-child {
padding-top: 0;
} }
.events-day-header:rtl { .events-day-header:rtl {
padding-left: 0; padding: 0em 1.4em 0.4em 1.4em;
padding-right: 0.4em;
} }
.events-day-dayname { .events-day-dayname {
color: rgba(153, 153, 153, 1.0); color: rgba(153, 153, 153, 1.0);
text-align: left; text-align: left;
min-width: 20px;
} }
.events-day-dayname:rtl { .events-day-dayname:rtl {
@@ -1365,12 +1309,23 @@ StScrollBar StButton#vhandle:active {
.events-day-task { .events-day-task {
color: rgba(153, 153, 153, 1.0); color: rgba(153, 153, 153, 1.0);
padding-left: 8pt;
} }
.events-day-task:rtl { .events-day-name-box {
padding-left: 0px; min-width: 15pt;
padding-right: 8pt; }
.events-time-box {
min-width: 48pt;
padding-right: 12pt;
}
.events-time-box:rtl {
padding-right: 0px;
padding-left: 12pt;
}
.events-event-box {
} }
.url-highlighter { .url-highlighter {
@@ -1388,20 +1343,9 @@ StScrollBar StButton#vhandle:active {
height: 72px; height: 72px;
} }
.message-tray-menu-button StIcon { .no-messages-label {
padding: 0 20px; font-family: cantarell, sans-serif;
color: #aaaaaa; font-size: 11pt;
icon-size: 24px;
}
.message-tray-menu-button:hover StIcon,
.message-tray-menu-button:active StIcon,
.message-tray-menu-button:focus StIcon {
color: #eeeeee;
}
.no-messages-label,
.no-networks-label {
color: #999999; color: #999999;
} }
@@ -1494,13 +1438,11 @@ StScrollBar StButton#vhandle:active {
} }
.notification-button { .notification-button {
-st-natural-width: 140px; padding: 4px 42px 5px;
padding: 4px 4px 5px;
} }
.notification-button:focus { .notification-button:focus {
-st-natural-width: 138px; padding: 3px 41px 4px;
padding: 3px 4px 4px;
} }
.notification-icon-button { .notification-icon-button {
@@ -1837,6 +1779,8 @@ StScrollBar StButton#vhandle:active {
} }
.modal-dialog-button { .modal-dialog-button {
margin-left: 10px;
margin-right: 10px;
padding: 4px 32px 5px; padding: 4px 32px 5px;
} }
@@ -2185,12 +2129,23 @@ StScrollBar StButton#vhandle:active {
} }
/* IBus Candidate Popup */ /* IBus Candidate Popup */
.candidate-popup-boxpointer {
-arrow-border-radius: 8px;
-arrow-background-color: #707070;
-arrow-border-width: 0px;
-arrow-base: 24px;
-arrow-rise: 11px;
}
.candidate-popup-content { .candidate-popup-content {
padding: 0.5em; padding: 0.5em;
spacing: 0.3em; spacing: 0.3em;
} }
.candidate-popup-text {
font-size: 9pt;
}
.candidate-index { .candidate-index {
padding: 0 0.5em 0 0; padding: 0 0.5em 0 0;
color: #cccccc; color: #cccccc;
@@ -2236,18 +2191,6 @@ StScrollBar StButton#vhandle:active {
/* Login Dialog */ /* Login Dialog */
.framed-user-icon {
border: 2px solid #8b8b8b;
border-radius: 5px;
width: 48pt;
height: 48pt;
background-size: contain;
}
.framed-user-icon:hover {
border: 2px solid #bbbbbb;
}
.login-dialog-banner { .login-dialog-banner {
font-size: 10pt; font-size: 10pt;
font-weight: bold; font-weight: bold;
@@ -2268,17 +2211,14 @@ StScrollBar StButton#vhandle:active {
border: none; border: none;
background-color: transparent; background-color: transparent;
padding-bottom: 80px;
padding-top: 80px;
border-radius: 16px; border-radius: 16px;
min-height: 150px; min-height: 150px;
max-height: 700px; max-height: 700px;
min-width: 350px; min-width: 350px;
} }
.login-dialog-button-box { .login-dialog-prompt-login-hint-message {
spacing: 5px; font-size: 10.5pt;
} }
.login-dialog-user-list-view { .login-dialog-user-list-view {
@@ -2305,7 +2245,7 @@ StScrollBar StButton#vhandle:active {
.login-dialog-user-list-item .login-dialog-user-list-item-name { .login-dialog-user-list-item .login-dialog-user-list-item-name {
font-size: 20pt; font-size: 20pt;
padding-left: 9px; padding-left: 1em;
} }
.login-dialog-user-list:expanded .login-dialog-user-list-item { .login-dialog-user-list:expanded .login-dialog-user-list-item {
@@ -2360,10 +2300,6 @@ StScrollBar StButton#vhandle:active {
padding-top: 1em; padding-top: 1em;
} }
.login-dialog-user-selection-box .login-dialog-not-listed-label {
padding-left: 2px;
}
.login-dialog-not-listed-button:focus .login-dialog-not-listed-label, .login-dialog-not-listed-button:focus .login-dialog-not-listed-label,
.login-dialog-not-listed-button:hover .login-dialog-not-listed-label { .login-dialog-not-listed-button:hover .login-dialog-not-listed-label {
color: #E8E8E8; color: #E8E8E8;
@@ -2381,30 +2317,63 @@ StScrollBar StButton#vhandle:active {
padding-top: 24px; padding-top: 24px;
padding-bottom: 12px; padding-bottom: 12px;
spacing: 8px; spacing: 8px;
width: 23em;
} }
.login-dialog-prompt-label { .login-dialog-prompt-label {
color: #eeeeee; color: #eeeeee;
font-size: 14px; font-size: 14px;
padding-top: 11px;
} }
.login-dialog-session-list-button StIcon { .login-dialog-prompt-entry {
icon-size: 1.25em; width: 15em;
}
.login-dialog-session-list {
color: #ffffff;
font-size: 10.5pt;
} }
.login-dialog-session-list-button { .login-dialog-session-list-button {
color: #8b8b8b; padding: 4px;
}
.login-dialog-session-list-button:focus {
background-color: #4c4c4c;
} }
.login-dialog-session-list-button:hover,
.login-dialog-session-list-button:active { .login-dialog-session-list-button:active {
color: white; background-color: #4c4c4c;
} }
.login-dialog-logo-bin { .login-dialog-session-list-button:hover {
padding: 24px 0px; font-weight: bold;
}
.login-dialog-session-list-scroll-view {
background-gradient-start: rgba(80,80,80,0.3);
background-gradient-end: rgba(80,80,80,0.7);
background-gradient-direction: vertical;
box-shadow: inset 0px 2px 4px rgba(0,0,0,0.9);
border-radius: 8px;
border: 1px solid rgba(80,80,80,1.0);
padding: .5em;
}
.login-dialog-session-list-item:focus {
background-color: #666666;
}
.login-dialog-session-list-triangle {
padding-right: .5em;
}
.login-dialog-session-list-item-box {
spacing: .25em;
}
.login-dialog-session-list-item-dot {
width: .75em;
height: .75em;
} }
.login-dialog .modal-dialog-button-box { .login-dialog .modal-dialog-button-box {
@@ -2449,20 +2418,12 @@ StScrollBar StButton#vhandle:active {
background-color: rgba(102, 102, 102, 0.15); background-color: rgba(102, 102, 102, 0.15);
} }
.login-dialog-message-warning,
.login-dialog-message-info {
padding-top: 4px;
padding-bottom: 16px;
min-height: 2em;
}
.login-dialog-message-warning { .login-dialog-message-warning {
color: orange; color: orange;
} }
.login-dialog-message-hint { .user-widget {
padding-bottom: 16px; spacing: .4em;
min-height: 2em;
} }
.user-widget-label { .user-widget-label {
@@ -2475,11 +2436,6 @@ StScrollBar StButton#vhandle:active {
/* Screen shield */ /* Screen shield */
#panel.lock-screen,
#screenShieldNotifications {
background-color: rgba(0,0,0,0.3);
}
.screen-shield-background { .screen-shield-background {
background: black; background: black;
box-shadow: 0px 4px 8px rgba(0,0,0,0.9); box-shadow: 0px 4px 8px rgba(0,0,0,0.9);
@@ -2524,27 +2480,33 @@ StScrollBar StButton#vhandle:active {
} }
#screenShieldNotifications { #screenShieldNotifications {
border-radius: 8px;
background-color: rgba(0.0, 0.0, 0.0, 0.9);
border: 2px solid #868686;
max-height: 500px; max-height: 500px;
padding: 12px; padding: 18px 0;
box-shadow: .5em .5em 20px rgba(0, 0, 0, 0.5);
} }
.screen-shield-notifications-box { .screen-shield-notifications-box {
spacing: 12px; spacing: 18px;
width: 30em; max-width: 34em;
} }
.screen-shield-notification-source { .screen-shield-notification-source {
padding: 3px 6px; padding: 13px 24px;
spacing: 5px; spacing: 5px;
} }
.screen-shield-notification-label { .screen-shield-notification-label {
font-size: 1.2em;
font-weight: bold; font-weight: bold;
padding: 0px 0px 0px 12px; padding: 0px 18px;
color: #babdb6;
} }
.screen-shield-notification-count-text { .screen-shield-notification-count-text {
padding: 0px 0px 0px 12px; padding: 0px 18px;
} }
/* Remove background from notifications, otherwise /* Remove background from notifications, otherwise
@@ -2562,31 +2524,6 @@ StScrollBar StButton#vhandle:active {
padding-bottom: 0px; padding-bottom: 0px;
} }
#screenShieldNotifications .notification-button,
#screenShieldNotifications .notification-icon-button {
border: 1px rgba(255,255,255,0.5);
}
#screenShieldNotifications StScrollBar StBin#trough {
background-color: rgba(0,0,0,0.2);
}
#screenShieldNotifications StScrollBar StButton#vhandle,
#screenShieldNotifications StScrollBar StButton#hhandle {
background-color: rgba(0,0,0,0.3);
border: none;
}
#screenShieldNotifications StScrollBar StButton#vhandle:hover,
#screenShieldNotifications StScrollBar StButton#hhandle {
background-color: rgba(0,0,0,0.6);
}
#screenShieldNotifications StScrollBar StButton#vhandle:active,
#screenShieldNotifications StScrollBar StButton#hhandle {
background-color: rgba(0,0,0,0.8);
}
.input-source-switcher-symbol { .input-source-switcher-symbol {
font-size: 34pt; font-size: 34pt;
width: 96px; width: 96px;

View File

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

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

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

View File

@@ -66,4 +66,11 @@ its dependencies to build from tarballs.</description>
<gnome:userid>fmuellner</gnome:userid> <gnome:userid>fmuellner</gnome:userid>
</foaf:Person> </foaf:Person>
</maintainer> </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> </Project>

View File

@@ -17,10 +17,10 @@ misc/config.js: misc/config.js.in Makefile
jsdir = $(pkgdatadir)/js jsdir = $(pkgdatadir)/js
nobase_dist_js_DATA = \ nobase_dist_js_DATA = \
gdm/authPrompt.js \
gdm/batch.js \ gdm/batch.js \
gdm/fingerprint.js \ gdm/fingerprint.js \
gdm/loginDialog.js \ gdm/loginDialog.js \
gdm/powerMenu.js \
gdm/realmd.js \ gdm/realmd.js \
gdm/util.js \ gdm/util.js \
extensionPrefs/main.js \ extensionPrefs/main.js \
@@ -33,13 +33,10 @@ nobase_dist_js_DATA = \
misc/jsParse.js \ misc/jsParse.js \
misc/loginManager.js \ misc/loginManager.js \
misc/modemManager.js \ misc/modemManager.js \
misc/objectManager.js \
misc/params.js \ misc/params.js \
misc/smartcardManager.js \
misc/util.js \ misc/util.js \
perf/core.js \ perf/core.js \
ui/altTab.js \ ui/altTab.js \
ui/animation.js \
ui/appDisplay.js \ ui/appDisplay.js \
ui/appFavorites.js \ ui/appFavorites.js \
ui/backgroundMenu.js \ ui/backgroundMenu.js \
@@ -71,7 +68,6 @@ nobase_dist_js_DATA = \
ui/sessionMode.js \ ui/sessionMode.js \
ui/shellEntry.js \ ui/shellEntry.js \
ui/shellMountOperation.js \ ui/shellMountOperation.js \
ui/slider.js \
ui/notificationDaemon.js \ ui/notificationDaemon.js \
ui/osdWindow.js \ ui/osdWindow.js \
ui/overview.js \ ui/overview.js \
@@ -81,9 +77,7 @@ nobase_dist_js_DATA = \
ui/pointerWatcher.js \ ui/pointerWatcher.js \
ui/popupMenu.js \ ui/popupMenu.js \
ui/remoteSearch.js \ ui/remoteSearch.js \
ui/remoteMenu.js \
ui/runDialog.js \ ui/runDialog.js \
ui/screencast.js \
ui/screenshot.js \ ui/screenshot.js \
ui/screenShield.js \ ui/screenShield.js \
ui/scripting.js \ ui/scripting.js \
@@ -91,18 +85,16 @@ nobase_dist_js_DATA = \
ui/searchDisplay.js \ ui/searchDisplay.js \
ui/shellDBus.js \ ui/shellDBus.js \
ui/status/accessibility.js \ ui/status/accessibility.js \
ui/status/brightness.js \
ui/status/keyboard.js \ ui/status/keyboard.js \
ui/status/lockScreenMenu.js \
ui/status/network.js \ ui/status/network.js \
ui/status/power.js \ ui/status/power.js \
ui/status/rfkill.js \
ui/status/volume.js \ ui/status/volume.js \
ui/status/bluetooth.js \ ui/status/bluetooth.js \
ui/status/screencast.js \
ui/status/system.js \
ui/switcherPopup.js \ ui/switcherPopup.js \
ui/tweener.js \ ui/tweener.js \
ui/unlockDialog.js \ ui/unlockDialog.js \
ui/userMenu.js \
ui/userWidget.js \ ui/userWidget.js \
ui/viewSelector.js \ ui/viewSelector.js \
ui/wanda.js \ ui/wanda.js \
@@ -118,6 +110,7 @@ nobase_dist_js_DATA = \
ui/components/automountManager.js \ ui/components/automountManager.js \
ui/components/networkAgent.js \ ui/components/networkAgent.js \
ui/components/polkitAgent.js \ ui/components/polkitAgent.js \
ui/components/recorder.js \
ui/components/telepathyClient.js \ ui/components/telepathyClient.js \
ui/components/keyring.js \ ui/components/keyring.js \
$(NULL) $(NULL)

View File

@@ -1,509 +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
};
let _messageStyleMap;
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.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 });
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();
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');
},
_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.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);
actor.add_constraint(new Clutter.AlignConstraint({ source: this._spinner.actor,
align_axis: Clutter.AlignAxis.BOTH,
factor: 0.5 }));
},
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'
});
},
_initMessageStyleMap: function() {
if (_messageStyleMap)
return;
_messageStyleMap = {};
_messageStyleMap[GdmUtil.MessageType.NONE] = '';
_messageStyleMap[GdmUtil.MessageType.ERROR] = 'login-dialog-message-warning';
_messageStyleMap[GdmUtil.MessageType.INFO] = 'login-dialog-message-info';
_messageStyleMap[GdmUtil.MessageType.HINT] = 'login-dialog-message-hint';
},
setMessage: function(message, type) {
this._initMessageStyleMap();
if (message) {
Tweener.removeTweens(this._message);
this._message.text = message;
this._message.styleClass = _messageStyleMap[type];
this._message.opacity = 255;
} else {
this._message.styleClass = null;
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.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.
beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME;
} else {
// In all other cases, we should get the username up front.
beginRequestType = BeginRequestType.PROVIDE_USERNAME;
}
this.emit('reset', beginRequestType);
},
addCharacter: function(unichar) {
if (!this._entry.visible)
return;
this._entry.grab_key_focus();
this._entry.clutter_text.insert_unichar(unichar);
},
begin: function(params) {
params = Params.parse(params, { userName: null,
hold: null });
this.updateSensitivity(false);
let hold = params.hold;
if (!hold)
hold = new Batch.Hold();
this._userVerifier.begin(params.userName, hold);
this.verificationStatus = AuthPromptStatus.VERIFYING;
},
finish: function(onComplete) {
if (!this._userVerifier.hasPendingMessages) {
onComplete();
return;
}
let signalId = this._userVerifier.connect('no-more-messages',
Lang.bind(this, function() {
this._userVerifier.disconnect(signalId);
onComplete();
}));
},
cancel: function() {
this.reset();
this.emit('cancelled');
}
});
Signals.addSignalMethods(AuthPrompt.prototype);

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,129 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/*
* Copyright 2011 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const LoginManager = imports.misc.loginManager;
const GdmUtil = imports.gdm.util;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const PowerMenuButton = new Lang.Class({
Name: 'PowerMenuButton',
Extends: PanelMenu.SystemStatusButton,
_init: function() {
/* Translators: accessible name of the power menu in the login screen */
this.parent('system-shutdown-symbolic', _("Power"));
this._loginManager = LoginManager.getLoginManager();
this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA });
this._settings.connect('changed::disable-restart-buttons',
Lang.bind(this, this._updateVisibility));
this._createSubMenu();
// ConsoleKit doesn't send notifications when shutdown/reboot
// are disabled, so we update the menu item each time the menu opens
this.menu.connect('open-state-changed', Lang.bind(this,
function(menu, open) {
if (open) {
this._updateHaveShutdown();
this._updateHaveRestart();
this._updateHaveSuspend();
}
}));
this._updateHaveShutdown();
this._updateHaveRestart();
this._updateHaveSuspend();
},
_updateVisibility: function() {
let shouldBeVisible = (this._haveSuspend || this._haveShutdown || this._haveRestart);
this.actor.visible = shouldBeVisible && !this._settings.get_boolean('disable-restart-buttons');
},
_updateHaveShutdown: function() {
this._loginManager.canPowerOff(Lang.bind(this, function(result) {
this._haveShutdown = result;
this._powerOffItem.actor.visible = this._haveShutdown;
this._updateVisibility();
}));
},
_updateHaveRestart: function() {
this._loginManager.canReboot(Lang.bind(this, function(result) {
this._haveRestart = result;
this._restartItem.actor.visible = this._haveRestart;
this._updateVisibility();
}));
},
_updateHaveSuspend: function() {
this._loginManager.canSuspend(Lang.bind(this, function(result) {
this._haveSuspend = result;
this._suspendItem.actor.visible = this._haveSuspend;
this._updateVisibility();
}));
},
_createSubMenu: function() {
let item;
item = new PopupMenu.PopupMenuItem(_("Suspend"));
item.connect('activate', Lang.bind(this, this._onActivateSuspend));
this.menu.addMenuItem(item);
this._suspendItem = item;
item = new PopupMenu.PopupMenuItem(_("Restart"));
item.connect('activate', Lang.bind(this, this._onActivateRestart));
this.menu.addMenuItem(item);
this._restartItem = item;
item = new PopupMenu.PopupMenuItem(_("Power Off"));
item.connect('activate', Lang.bind(this, this._onActivatePowerOff));
this.menu.addMenuItem(item);
this._powerOffItem = item;
},
_onActivateSuspend: function() {
if (!this._haveSuspend)
return;
this._loginManager.suspend();
},
_onActivateRestart: function() {
if (!this._haveRestart)
return;
this._loginManager.reboot();
},
_onActivatePowerOff: function() {
if (!this._haveShutdown)
return;
this._loginManager.powerOff();
}
});

View File

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

View File

@@ -2,30 +2,24 @@
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Signals = imports.signals; const Signals = imports.signals;
const St = imports.gi.St;
const Batch = imports.gdm.batch; const Batch = imports.gdm.batch;
const Fprint = imports.gdm.fingerprint; const Fprint = imports.gdm.fingerprint;
const Realmd = imports.gdm.realmd;
const Main = imports.ui.main; const Main = imports.ui.main;
const Params = imports.misc.params; const Params = imports.misc.params;
const ShellEntry = imports.ui.shellEntry;
const SmartcardManager = imports.misc.smartcardManager;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const PASSWORD_SERVICE_NAME = 'gdm-password'; const PASSWORD_SERVICE_NAME = 'gdm-password';
const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
const SMARTCARD_SERVICE_NAME = 'gdm-smartcard';
const FADE_ANIMATION_TIME = 0.16; const FADE_ANIMATION_TIME = 0.16;
const CLONE_FADE_ANIMATION_TIME = 0.25; const CLONE_FADE_ANIMATION_TIME = 0.25;
const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
const PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication';
const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
const SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication';
const BANNER_MESSAGE_KEY = 'banner-message-enable'; const BANNER_MESSAGE_KEY = 'banner-message-enable';
const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text';
const ALLOWED_FAILURES_KEY = 'allowed-failures'; const ALLOWED_FAILURES_KEY = 'allowed-failures';
@@ -33,16 +27,6 @@ const ALLOWED_FAILURES_KEY = 'allowed-failures';
const LOGO_KEY = 'logo'; const LOGO_KEY = 'logo';
const DISABLE_USER_LIST_KEY = 'disable-user-list'; 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) { function fadeInActor(actor) {
if (actor.opacity == 255 && actor.visible) if (actor.opacity == 255 && actor.visible)
return null; return null;
@@ -127,28 +111,9 @@ const ShellUserVerifier = new Lang.Class({
this._client = client; this._client = client;
this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA }); this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA });
this._settings.connect('changed',
Lang.bind(this, this._updateDefaultService));
this._updateDefaultService();
this._fprintManager = new Fprint.FprintManager(); this._fprintManager = new Fprint.FprintManager();
this._smartcardManager = SmartcardManager.getSmartcardManager(); this._realmManager = new Realmd.Manager();
// We check for smartcards right away, since an inserted smartcard
// at startup should result in immediately initiating authentication.
// This is different than fingeprint readers, where we only check them
// after a user has been picked.
this._checkForSmartcard();
this._smartcardManager.connect('smartcard-inserted',
Lang.bind(this, this._checkForSmartcard));
this._smartcardManager.connect('smartcard-removed',
Lang.bind(this, this._checkForSmartcard));
this._messageQueue = [];
this._messageQueueTimeoutId = 0;
this.hasPendingMessages = false;
this.reauthenticating = false;
this._failCounter = 0; this._failCounter = 0;
}, },
@@ -157,7 +122,6 @@ const ShellUserVerifier = new Lang.Class({
this._cancellable = new Gio.Cancellable(); this._cancellable = new Gio.Cancellable();
this._hold = hold; this._hold = hold;
this._userName = userName; this._userName = userName;
this.reauthenticating = false;
this._checkForFingerprintReader(); this._checkForFingerprintReader();
@@ -175,10 +139,8 @@ const ShellUserVerifier = new Lang.Class({
if (this._cancellable) if (this._cancellable)
this._cancellable.cancel(); this._cancellable.cancel();
if (this._userVerifier) { if (this._userVerifier)
this._userVerifier.call_cancel_sync(null); this._userVerifier.call_cancel_sync(null);
this.clear();
}
}, },
clear: function() { clear: function() {
@@ -191,119 +153,33 @@ const ShellUserVerifier = new Lang.Class({
this._userVerifier.run_dispose(); this._userVerifier.run_dispose();
this._userVerifier = null; this._userVerifier = null;
} }
this._clearMessageQueue();
}, },
answerQuery: function(serviceName, answer) { answerQuery: function(serviceName, answer) {
if (!this.hasPendingMessages) { // Clear any previous message
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); this.emit('show-message', null, 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);
}));
}
},
_getIntervalForMessage: function(message) { this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
// 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);
}, },
_checkForFingerprintReader: function() { _checkForFingerprintReader: function() {
this._haveFingerprintReader = false; this._haveFingerprintReader = false;
if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) { if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY))
this._updateDefaultService();
return; return;
}
this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this, this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this,
function(device, error) { function(device, error) {
if (!error && device) if (!error && device)
this._haveFingerprintReader = true; this._haveFingerprintReader = true;
this._updateDefaultService();
})); }));
}, },
_checkForSmartcard: function() {
let smartcardDetected;
if (!this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY))
smartcardDetected = false;
else if (this.reauthenticating)
smartcardDetected = this._smartcardManager.hasInsertedLoginToken();
else
smartcardDetected = this._smartcardManager.hasInsertedTokens();
if (smartcardDetected != this.smartcardDetected) {
this.smartcardDetected = smartcardDetected;
if (this.smartcardDetected)
this._preemptingService = SMARTCARD_SERVICE_NAME;
else if (this._preemptingService == SMARTCARD_SERVICE_NAME)
this._preemptingService = null;
this.emit('smartcard-status-changed');
}
},
_reportInitError: function(where, error) { _reportInitError: function(where, error) {
logError(error, where); logError(error, where);
this._hold.release(); this._hold.release();
this._queueMessage(_("Authentication error"), MessageType.ERROR); this.emit('show-message', _("Authentication error"), 'login-dialog-message-warning');
this._verificationFailed(false); this._verificationFailed(false);
}, },
@@ -324,7 +200,6 @@ const ShellUserVerifier = new Lang.Class({
return; return;
} }
this.reauthenticating = true;
this._connectSignals(); this._connectSignals();
this._beginVerification(); this._beginVerification();
this._hold.release(); this._hold.release();
@@ -355,96 +230,126 @@ const ShellUserVerifier = new Lang.Class({
this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
}, },
_getForegroundService: function() {
if (this._preemptingService)
return this._preemptingService;
return this._defaultService;
},
serviceIsForeground: function(serviceName) {
return serviceName == this._getForegroundService();
},
serviceIsDefault: function(serviceName) {
return serviceName == this._defaultService;
},
_updateDefaultService: function() {
if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY))
this._defaultService = PASSWORD_SERVICE_NAME;
else if (this.smartcardDetected)
this._defaultService = SMARTCARD_SERVICE_NAME;
else if (this._haveFingerprintReader)
this._defaultService = FINGERPRINT_SERVICE_NAME;
},
_startService: function(serviceName) {
this._hold.acquire();
this._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._hold.release();
}));
},
_beginVerification: function() { _beginVerification: function() {
this._startService(this._getForegroundService()); this._hold.acquire();
if (this._userName && this._haveFingerprintReader && !this.serviceIsForeground(FINGERPRINT_SERVICE_NAME)) if (this._userName) {
this._startService(FINGERPRINT_SERVICE_NAME); this._userVerifier.call_begin_verification_for_user(PASSWORD_SERVICE_NAME,
this._userName,
this._cancellable,
Lang.bind(this, function(obj, result) {
try {
obj.call_begin_verification_for_user_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
} catch(e) {
this._reportInitError('Failed to start verification for user', e);
return;
}
this._hold.release();
}));
if (this._haveFingerprintReader) {
this._hold.acquire();
this._userVerifier.call_begin_verification_for_user(FINGERPRINT_SERVICE_NAME,
this._userName,
this._cancellable,
Lang.bind(this, function(obj, result) {
try {
obj.call_begin_verification_for_user_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
} catch(e) {
this._reportInitError('Failed to start fingerprint verification for user', e);
return;
}
this._hold.release();
}));
}
} else {
this._userVerifier.call_begin_verification(PASSWORD_SERVICE_NAME,
this._cancellable,
Lang.bind(this, function(obj, result) {
try {
obj.call_begin_verification_finish(result);
} catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
return;
} catch(e) {
this._reportInitError('Failed to start verification', e);
return;
}
this._hold.release();
}));
}
}, },
_onInfo: function(client, serviceName, info) { _onInfo: function(client, serviceName, info) {
if (this.serviceIsForeground(serviceName)) { // We don't display fingerprint messages, because they
this._queueMessage(info, MessageType.INFO); // have words like UPEK in them. Instead we use the messages
} else if (serviceName == FINGERPRINT_SERVICE_NAME && // as a cue to display our own message.
if (serviceName == FINGERPRINT_SERVICE_NAME &&
this._haveFingerprintReader) { this._haveFingerprintReader) {
// We don't show fingerprint messages directly since it's
// not the main auth service. Instead we use the messages
// as a cue to display our own message.
// Translators: this message is shown below the password entry field // Translators: this message is shown below the password entry field
// to indicate the user can swipe their finger instead // to indicate the user can swipe their finger instead
this._queueMessage(_("(or swipe finger)"), MessageType.HINT); this.emit('show-login-hint', _("(or swipe finger)"));
} else if (serviceName == PASSWORD_SERVICE_NAME) {
this.emit('show-message', info, 'login-dialog-message-info');
} }
}, },
_onProblem: function(client, serviceName, problem) { _onProblem: function(client, serviceName, problem) {
if (!this.serviceIsForeground(serviceName)) // we don't want to show auth failed messages to
// users who haven't enrolled their fingerprint.
if (serviceName != PASSWORD_SERVICE_NAME)
return; return;
this.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) { _onInfoQuery: function(client, serviceName, question) {
if (!this.serviceIsForeground(serviceName)) // We only expect questions to come from the main auth service
if (serviceName != PASSWORD_SERVICE_NAME)
return; return;
this._showRealmLoginHint();
this._realmLoginHintSignalId = this._realmManager.connect('login-format-changed',
Lang.bind(this, this._showRealmLoginHint));
this.emit('ask-question', serviceName, question, ''); this.emit('ask-question', serviceName, question, '');
}, },
_onSecretInfoQuery: function(client, serviceName, secretQuestion) { _onSecretInfoQuery: function(client, serviceName, secretQuestion) {
if (!this.serviceIsForeground(serviceName)) // We only expect secret requests to come from the main auth service
if (serviceName != PASSWORD_SERVICE_NAME)
return; return;
this.emit('ask-question', serviceName, secretQuestion, '\u25cf'); this.emit('ask-question', serviceName, secretQuestion, '\u25cf');
}, },
_onReset: function() { _onReset: function() {
this.clear();
// Clear previous attempts to authenticate // Clear previous attempts to authenticate
this._failCounter = 0; this._failCounter = 0;
this._updateDefaultService();
this.emit('reset'); this.emit('reset');
}, },
@@ -453,15 +358,6 @@ const ShellUserVerifier = new Lang.Class({
this.emit('verification-complete'); this.emit('verification-complete');
}, },
_cancelAndReset: function() {
this.cancel();
this._onReset();
},
_retry: function() {
this.begin(this._userName, new Batch.Hold());
},
_verificationFailed: function(retry) { _verificationFailed: function(retry) {
// For Not Listed / enterprise logins, immediately reset // For Not Listed / enterprise logins, immediately reset
// the dialog // the dialog
@@ -473,25 +369,15 @@ const ShellUserVerifier = new Lang.Class({
this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY); this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY);
if (canRetry) { if (canRetry) {
if (!this.hasPendingMessages) { this.clear();
this._retry(); this.begin(this._userName, new Batch.Hold());
} else {
let signalId = this.connect('no-more-messages',
Lang.bind(this, function() {
this.disconnect(signalId);
this._retry();
}));
}
} else { } else {
if (!this.hasPendingMessages) { // Allow some time to see the message, then reset everything
this._cancelAndReset(); Mainloop.timeout_add(3000, Lang.bind(this, function() {
} else { this.cancel();
let signalId = this.connect('no-more-messages',
Lang.bind(this, function() { this._onReset();
this.disconnect(signalId); }));
this._cancelAndReset();
}));
}
} }
this.emit('verification-failed'); this.emit('verification-failed');
@@ -501,9 +387,16 @@ const ShellUserVerifier = new Lang.Class({
// if the password service fails, then cancel everything. // if the password service fails, then cancel everything.
// But if, e.g., fingerprint fails, still give // But if, e.g., fingerprint fails, still give
// password authentication a chance to succeed // password authentication a chance to succeed
if (this.serviceIsForeground(serviceName)) { if (serviceName == PASSWORD_SERVICE_NAME) {
this._verificationFailed(true); this._verificationFailed(true);
} }
this.emit('hide-login-hint');
if (this._realmLoginHintSignalId) {
this._realmManager.disconnect(this._realmLoginHintSignalId);
this._realmLoginHintSignalId = 0;
}
}, },
}); });
Signals.addSignalMethods(ShellUserVerifier.prototype); Signals.addSignalMethods(ShellUserVerifier.prototype);

View File

@@ -117,6 +117,7 @@ function recursivelyMoveDir(srcDir, destDir) {
let type = info.get_file_type(); let type = info.get_file_type();
let srcChild = srcDir.get_child(info.get_name()); let srcChild = srcDir.get_child(info.get_name());
let destChild = destDir.get_child(info.get_name()); let destChild = destDir.get_child(info.get_name());
log([srcChild.get_path(), destChild.get_path()]);
if (type == Gio.FileType.REGULAR) if (type == Gio.FileType.REGULAR)
srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null); srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null);
else if (type == Gio.FileType.DIRECTORY) else if (type == Gio.FileType.DIRECTORY)

View File

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

View File

@@ -33,10 +33,6 @@ const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager
<arg type='s' direction='in'/> <arg type='s' direction='in'/>
<arg type='h' direction='out'/> <arg type='h' direction='out'/>
</method> </method>
<method name='GetSession'>
<arg type='s' direction='in'/>
<arg type='o' direction='out'/>
</method>
<method name='ListSessions'> <method name='ListSessions'>
<arg name='sessions' type='a(susso)' direction='out'/> <arg name='sessions' type='a(susso)' direction='out'/>
</method> </method>
@@ -76,36 +72,7 @@ const ConsoleKitSession = Gio.DBusProxy.makeProxyWrapper(ConsoleKitSessionIface)
const ConsoleKitManager = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface); const ConsoleKitManager = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface);
function haveSystemd() { function haveSystemd() {
return GLib.access("/run/systemd/seats", 0) >= 0; return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0;
}
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 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;
}
} }
let _loginManager = null; let _loginManager = null;
@@ -140,23 +107,15 @@ const LoginManagerSystemd = new Lang.Class({
// Having this function is a bit of a hack since the Systemd and ConsoleKit // 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 // session objects have different interfaces - but in both cases there are
// Lock/Unlock signals, and that's all we count upon at the moment. // Lock/Unlock signals, and that's all we count upon at the moment.
getCurrentSessionProxy: function(callback) { getCurrentSessionProxy: function() {
if (this._currentSession) { if (!this._currentSession) {
callback (this._currentSession); this._currentSession = new SystemdLoginSession(Gio.DBus.system,
return; 'org.freedesktop.login1',
'/org/freedesktop/login1/session/' +
GLib.getenv('XDG_SESSION_ID'));
} }
this._proxy.GetSessionRemote(GLib.getenv('XDG_SESSION_ID'), Lang.bind(this, return this._currentSession;
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);
}
}));
}, },
canPowerOff: function(asyncCallback) { canPowerOff: function(asyncCallback) {
@@ -245,23 +204,15 @@ const LoginManagerConsoleKit = new Lang.Class({
// Having this function is a bit of a hack since the Systemd and ConsoleKit // 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 // session objects have different interfaces - but in both cases there are
// Lock/Unlock signals, and that's all we count upon at the moment. // Lock/Unlock signals, and that's all we count upon at the moment.
getCurrentSessionProxy: function(callback) { getCurrentSessionProxy: function() {
if (this._currentSession) { if (!this._currentSession) {
callback (this._currentSession); let [currentSessionId] = this._proxy.GetCurrentSessionSync();
return; this._currentSession = new ConsoleKitSession(Gio.DBus.system,
'org.freedesktop.ConsoleKit',
currentSessionId);
} }
this._proxy.GetCurrentSessionRemote(Lang.bind(this, return this._currentSession;
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);
}
}));
}, },
canPowerOff: function(asyncCallback) { canPowerOff: function(asyncCallback) {

View File

@@ -1,254 +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)
return;
let proxy = new Gio.DBusProxy({ g_connection: this._connection,
g_name: this._serviceName,
g_object_path: objectPath,
g_interface_name: interfaceName,
g_interface_info: info,
g_flags: Gio.DBusProxyFlags.NONE });
proxy.init_async(GLib.PRIORITY_DEFAULT,
this._cancellable,
Lang.bind(this, function(initable, result) {
let error = null;
try {
initable.init_finish(result);
} catch(e) {
logError(e, 'could not initialize proxy for interface ' + interfaceName);
if (onFinished)
onFinished();
return;
}
let isNewObject;
if (!this._objects[objectPath]) {
this._objects[objectPath] = {};
isNewObject = true;
} else {
isNewObject = false;
}
this._objects[objectPath][interfaceName] = proxy;
if (!this._interfaces[interfaceName])
this._interfaces[interfaceName] = [];
this._interfaces[interfaceName].push(proxy);
if (isNewObject)
this.emit('object-added', objectPath);
this.emit('interface-added', interfaceName, proxy);
if (onFinished)
onFinished();
}));
},
_removeInterface: function(objectPath, interfaceName) {
if (!this._objects[objectPath])
return;
let proxy = this._objects[objectPath][interfaceName];
if (this._interfaces[interfaceName]) {
let index = this._interfaces[interfaceName].indexOf(proxy);
if (index >= 0)
this._interfaces[interfaceName].splice(index, 1);
if (this._interfaces[interfaceName].length == 0)
delete this._interfaces[interfaceName];
}
this.emit('interface-removed', interfaceName, proxy);
this._objects[objectPath][interfaceName] = null;
if (Object.keys(this._objects[objectPath]).length == 0) {
delete this._objects[objectPath];
this.emit('object-removed', objectPath);
}
},
_onManagerProxyLoaded: function(initable, result) {
let error = null;
try {
initable.init_finish(result);
} catch(e) {
logError(e, 'could not initialize object manager for object ' + params.name);
this._tryToCompleteLoad();
return;
}
this._managerProxy.connectSignal('InterfacesAdded',
Lang.bind(this, function(objectManager, sender, [objectPath, interfaces]) {
let interfaceNames = Object.keys(interfaces);
for (let i = 0; i < interfaceNames.length; i++)
this._addInterface(objectPath, interfaceNames[i]);
}));
this._managerProxy.connectSignal('InterfacesRemoved',
Lang.bind(this, function(objectManager, sender, [objectPath, interfaceNames]) {
for (let i = 0; i < interfaceNames.length; i++)
this._removeInterface(objectPath, interfaceNames[i]);
}));
if (Object.keys(this._interfaceInfos).length == 0) {
this._tryToCompleteLoad();
return;
}
this._managerProxy.GetManagedObjectsRemote(Lang.bind(this, function(result, error) {
if (!result) {
if (error) {
logError(error, 'could not get remote objects for service ' + this._serviceName + ' path ' + this._managerPath);
}
this._tryToCompleteLoad();
return;
}
let [objects] = result;
let objectPaths = Object.keys(objects);
for (let i = 0; i < objectPaths.length; i++) {
let objectPath = objectPaths[i];
let object = objects[objectPath];
let interfaceNames = Object.getOwnPropertyNames(object);
for (let j = 0; j < interfaceNames.length; j++) {
let interfaceName = interfaceNames[j];
// Prevent load from completing until the interface is loaded
this._numLoadInhibitors++;
this._addInterface(objectPath,
interfaceName,
Lang.bind(this, this._tryToCompleteLoad));
}
}
this._tryToCompleteLoad();
}));
},
_registerInterfaces: function(interfaces) {
for (let i = 0; i < interfaces.length; i++) {
let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]);
this._interfaceInfos[info.name] = info;
}
},
getProxy: function(objectPath, interfaceName) {
let object = this._objects[objectPath];
if (!object)
return null;
return object[interfaceName];
},
getProxiesForInterface: function(interfaceName) {
let proxyList = this._interfaces[interfaceName];
if (!proxyList)
return [];
return proxyList;
},
getAllProxies: function() {
let proxies = [];
let objectPaths = Object.keys(this._objects);
for (let i = 0; i < objectPaths.length; i++) {
let object = this._objects[objectPaths];
let interfaceNames = Object.keys(object);
for (let j = 0; i < interfaceNames.length; i++) {
let interfaceName = interfaceNames[i];
if (object[interfaceName])
proxies.push(object(interfaceName));
}
}
return proxies;
}
});
Signals.addSignalMethods(ObjectManager.prototype);

View File

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

View File

@@ -5,9 +5,6 @@ const GLib = imports.gi.GLib;
const St = imports.gi.St; const St = imports.gi.St;
const Main = imports.ui.main; 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 // http://daringfireball.net/2010/07/improved_regex_for_matching_urls
const _balancedParens = '\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))*\\)'; const _balancedParens = '\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))*\\)';
@@ -18,7 +15,7 @@ const _urlRegexp = new RegExp(
'(^|' + _leadingJunk + ')' + '(^|' + _leadingJunk + ')' +
'(' + '(' +
'(?:' + '(?:' +
'(?:http|https|ftp)://' + // scheme:// '[a-z][\\w-]+://' + // scheme://
'|' + '|' +
'www\\d{0,3}[.]' + // www. 'www\\d{0,3}[.]' + // www.
'|' + '|' +
@@ -139,6 +136,31 @@ function _handleSpawnError(command, err) {
Main.notifyError(title, err.message); Main.notifyError(title, err.message);
} }
// killall:
// @processName: a process name
//
// Kills @processName. If no process with the given name is found,
// this will fail silently.
function killall(processName) {
try {
// pkill is more portable than killall, but on Linux at least
// it won't match if you pass more than 15 characters of the
// process name... However, if you use the '-f' flag to match
// the entire command line, it will work, but we have to be
// careful in that case that we can match
// '/usr/bin/processName' but not 'gedit processName.c' or
// whatever...
let argv = ['pkill', '-f', '^([^ ]*/)?' + processName + '($| )'];
GLib.spawn_sync(null, argv, null, GLib.SpawnFlags.SEARCH_PATH, null);
// It might be useful to return success/failure, but we'd need
// a wrapper around WIFEXITED and WEXITSTATUS. Since none of
// the current callers care, we don't bother.
} catch (e) {
logError(e, 'Failed to kill ' + processName);
}
}
// lowerBound: // lowerBound:
// @array: an array or array-like object, already sorted // @array: an array or array-like object, already sorted
// according to @cmp // according to @cmp
@@ -212,39 +234,3 @@ function makeCloseButton() {
return closeButton; return closeButton;
} }
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' });
}

View File

@@ -232,13 +232,11 @@ const AppSwitcherPopup = new Lang.Class({
}, },
_finish : function(timestamp) { _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(); 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() { _onDestroy : function() {
@@ -397,9 +395,9 @@ const WindowSwitcherPopup = new Lang.Class({
}, },
_finish: function() { _finish: function() {
Main.activateWindow(this._items[this._selectedIndex].window);
this.parent(); this.parent();
Main.activateWindow(this._items[this._selectedIndex].window);
} }
}); });
@@ -436,11 +434,8 @@ const AppSwitcher = new Lang.Class({
this._arrows = []; this._arrows = [];
let windowTracker = Shell.WindowTracker.get_default(); let windowTracker = Shell.WindowTracker.get_default();
let settings = new Gio.Settings({ schema: 'org.gnome.shell.app-switcher' });
let workspace = settings.get_boolean('current-workspace-only') ? global.screen.get_active_workspace()
: null;
let allWindows = global.display.get_tab_list(Meta.TabList.NORMAL, let allWindows = global.display.get_tab_list(Meta.TabList.NORMAL,
global.screen, workspace); global.screen, null);
// Construct the AppIcons, add to the popup // Construct the AppIcons, add to the popup
for (let i = 0; i < apps.length; i++) { for (let i = 0; i < apps.length; i++) {
@@ -450,9 +445,7 @@ const AppSwitcher = new Lang.Class({
appIcon.cachedWindows = allWindows.filter(function(w) { appIcon.cachedWindows = allWindows.filter(function(w) {
return windowTracker.get_window_app (w) == appIcon.app; return windowTracker.get_window_app (w) == appIcon.app;
}); });
if (workspace == null || appIcon.cachedWindows.length > 0) { this._addIcon(appIcon);
this._addIcon(appIcon);
}
} }
this._curApp = -1; this._curApp = -1;

View File

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

View File

@@ -29,6 +29,7 @@ const Util = imports.misc.util;
const MAX_APPLICATION_WORK_MILLIS = 75; const MAX_APPLICATION_WORK_MILLIS = 75;
const MENU_POPUP_TIMEOUT = 600; const MENU_POPUP_TIMEOUT = 600;
const SCROLL_TIME = 0.1;
const MAX_COLUMNS = 6; const MAX_COLUMNS = 6;
const INACTIVE_GRID_OPACITY = 77; const INACTIVE_GRID_OPACITY = 77;
@@ -156,30 +157,6 @@ const FolderView = new Lang.Class({
} }
}); });
const AllViewLayout = new Lang.Class({
Name: 'AllViewLayout',
Extends: Clutter.BinLayout,
vfunc_get_preferred_height: function(container, forWidth) {
let minBottom = 0;
let naturalBottom = 0;
for (let child = container.get_first_child();
child;
child = child.get_next_sibling()) {
let childY = child.y;
let [childMin, childNatural] = child.get_preferred_height(forWidth);
if (childMin + childY > minBottom)
minBottom = childMin + childY;
if (childNatural + childY > naturalBottom)
naturalBottom = childNatural + childY;
}
return [minBottom, naturalBottom];
}
});
const AllView = new Lang.Class({ const AllView = new Lang.Class({
Name: 'AllView', Name: 'AllView',
Extends: AlphabeticalView, Extends: AlphabeticalView,
@@ -187,11 +164,8 @@ const AllView = new Lang.Class({
_init: function() { _init: function() {
this.parent(); this.parent();
this._grid.actor.y_align = Clutter.ActorAlign.START;
this._grid.actor.y_expand = true;
let box = new St.BoxLayout({ vertical: true }); let box = new St.BoxLayout({ vertical: true });
this._stack = new St.Widget({ layout_manager: new AllViewLayout() }); this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
this._stack.add_actor(this._grid.actor); this._stack.add_actor(this._grid.actor);
this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true }); this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
this._stack.add_actor(this._eventBlocker); this._stack.add_actor(this._eventBlocker);
@@ -279,17 +253,35 @@ const AllView = new Lang.Class({
this._eventBlocker.reactive = isOpen; this._eventBlocker.reactive = isOpen;
this._currentPopup = isOpen ? popup : null; this._currentPopup = isOpen ? popup : null;
this._updateIconOpacities(isOpen); this._updateIconOpacities(isOpen);
if (isOpen) { if (isOpen)
this._ensureIconVisible(popup.actor); this._ensureIconVisible(popup.actor);
this._grid.actor.y = popup.parentOffset;
} else {
this._grid.actor.y = 0;
}
})); }));
}, },
_ensureIconVisible: function(icon) { _ensureIconVisible: function(icon) {
Util.ensureActorVisibleInScrollView(this.actor, icon); let adjustment = this.actor.vscroll.adjustment;
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
let offset = 0;
let vfade = this.actor.get_effect("fade");
if (vfade)
offset = vfade.vfade_offset;
// If this gets called as part of a right-click, the actor
// will be needs_allocation, and so "icon.y" would return 0
let box = icon.get_allocation_box();
if (box.y1 < value + offset)
value = Math.max(0, box.y1 - offset);
else if (box.y2 > value + pageSize - offset)
value = Math.min(upper, box.y2 + offset - pageSize);
else
return;
Tweener.addTween(adjustment,
{ value: value,
time: SCROLL_TIME,
transition: 'easeOutQuad' });
}, },
_updateIconOpacities: function(folderOpen) { _updateIconOpacities: function(folderOpen) {
@@ -323,8 +315,6 @@ const FrequentView = new Lang.Class({
loadApps: function() { loadApps: function() {
let mostUsed = this._usage.get_most_used (""); let mostUsed = this._usage.get_most_used ("");
for (let i = 0; i < mostUsed.length; i++) { for (let i = 0; i < mostUsed.length; i++) {
if (!mostUsed[i].get_app_info().should_show())
continue;
let appIcon = new AppIcon(mostUsed[i]); let appIcon = new AppIcon(mostUsed[i]);
this._grid.addItem(appIcon.actor, -1); this._grid.addItem(appIcon.actor, -1);
} }
@@ -336,31 +326,6 @@ const Views = {
ALL: 1 ALL: 1
}; };
const ControlsBoxLayout = Lang.Class({
Name: 'ControlsBoxLayout',
Extends: Clutter.BoxLayout,
/**
* Override the BoxLayout behavior to use the maximum preferred width of all
* buttons for each child
*/
vfunc_get_preferred_width: function(container, forHeight) {
let maxMinWidth = 0;
let maxNaturalWidth = 0;
for (let child = container.get_first_child();
child;
child = child.get_next_sibling()) {
let [minWidth, natWidth] = child.get_preferred_width(forHeight);
maxMinWidth = Math.max(maxMinWidth, minWidth);
maxNaturalWidth = Math.max(maxNaturalWidth, natWidth);
}
let childrenCount = container.get_n_children();
let totalSpacing = this.spacing * (childrenCount - 1);
return [maxMinWidth * childrenCount + totalSpacing,
maxNaturalWidth * childrenCount + totalSpacing];
}
});
const AppDisplay = new Lang.Class({ const AppDisplay = new Lang.Class({
Name: 'AppDisplay', Name: 'AppDisplay',
@@ -375,9 +340,6 @@ const AppDisplay = new Lang.Class({
global.settings.connect('changed::app-folder-categories', Lang.bind(this, function() { global.settings.connect('changed::app-folder-categories', Lang.bind(this, function() {
Main.queueDeferredWork(this._allAppsWorkId); Main.queueDeferredWork(this._allAppsWorkId);
})); }));
this._privacySettings = new Gio.Settings({ schema: 'org.gnome.desktop.privacy' });
this._privacySettings.connect('changed::remember-app-usage',
Lang.bind(this, this._updateFrequentVisibility));
this._views = []; this._views = [];
@@ -404,10 +366,9 @@ const AppDisplay = new Lang.Class({
x_expand: true, y_expand: true }); x_expand: true, y_expand: true });
this.actor.add(this._viewStack, { expand: true }); this.actor.add(this._viewStack, { expand: true });
let layout = new ControlsBoxLayout({ homogeneous: true }); let layout = new Clutter.BoxLayout({ homogeneous: true });
this._controls = new St.Widget({ style_class: 'app-view-controls', this._controls = new St.Widget({ style_class: 'app-view-controls',
layout_manager: layout }); layout_manager: layout });
layout.hookup_style(this._controls);
this.actor.add(new St.Bin({ child: this._controls })); this.actor.add(new St.Bin({ child: this._controls }));
@@ -422,7 +383,6 @@ const AppDisplay = new Lang.Class({
})); }));
} }
this._showView(Views.FREQUENT); this._showView(Views.FREQUENT);
this._updateFrequentVisibility();
// We need a dummy actor to catch the keyboard focus if the // We need a dummy actor to catch the keyboard focus if the
// user Ctrl-Alt-Tabs here before the deferred work creates // user Ctrl-Alt-Tabs here before the deferred work creates
@@ -452,19 +412,6 @@ const AppDisplay = new Lang.Class({
} }
}, },
_updateFrequentVisibility: function() {
let enabled = this._privacySettings.get_boolean('remember-app-usage');
this._views[Views.FREQUENT].control.visible = enabled;
let visibleViews = this._views.filter(function(v) {
return v.control.visible;
});
this._controls.visible = visibleViews.length > 1;
if (!enabled && this._views[Views.FREQUENT].view.actor.visible)
this._showView(Views.ALL);
},
_redisplay: function() { _redisplay: function() {
this._redisplayFrequentApps(); this._redisplayFrequentApps();
this._redisplayAllApps(); this._redisplayAllApps();
@@ -535,11 +482,11 @@ const AppSearchProvider = new Lang.Class({
}, },
getInitialResultSet: function(terms) { getInitialResultSet: function(terms) {
this.searchSystem.setResults(this, this._appSys.initial_search(terms)); this.searchSystem.pushResults(this, this._appSys.initial_search(terms));
}, },
getSubsearchResultSet: function(previousResults, terms) { getSubsearchResultSet: function(previousResults, terms) {
this.searchSystem.setResults(this, this._appSys.subsearch(previousResults, terms)); this.searchSystem.pushResults(this, this._appSys.subsearch(previousResults, terms));
}, },
activateResult: function(app) { activateResult: function(app) {
@@ -624,11 +571,7 @@ const FolderIcon = new Lang.Class({
// Position the popup above or below the source icon // Position the popup above or below the source icon
if (side == St.Side.BOTTOM) { if (side == St.Side.BOTTOM) {
this._popup.actor.show(); this._popup.actor.show();
let closeButtonOffset = -this._popup.closeButton.translation_y; this._popup.actor.y = this.actor.y - this._popup.actor.height;
let y = this.actor.y - this._popup.actor.height;
let yWithButton = y - closeButtonOffset;
this._popup.parentOffset = yWithButton < 0 ? -yWithButton : 0;
this._popup.actor.y = Math.max(y, closeButtonOffset);
this._popup.actor.hide(); this._popup.actor.hide();
} else { } else {
this._popup.actor.y = this.actor.y + this.actor.height; this._popup.actor.y = this.actor.y + this.actor.height;
@@ -651,7 +594,6 @@ const AppFolderPopup = new Lang.Class({
this._arrowSide = side; this._arrowSide = side;
this._isOpen = false; this._isOpen = false;
this.parentOffset = 0;
this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(), this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
visible: false, visible: false,
@@ -675,31 +617,17 @@ const AppFolderPopup = new Lang.Class({
this.actor.add_actor(this._boxPointer.actor); this.actor.add_actor(this._boxPointer.actor);
this._boxPointer.bin.set_child(this._view.actor); this._boxPointer.bin.set_child(this._view.actor);
this.closeButton = Util.makeCloseButton(); let closeButton = Util.makeCloseButton();
this.closeButton.connect('clicked', Lang.bind(this, this.popdown)); closeButton.connect('clicked', Lang.bind(this, this.popdown));
this.actor.add_actor(this.closeButton); this.actor.add_actor(closeButton);
this._boxPointer.actor.bind_property('opacity', this.closeButton, 'opacity', this._boxPointer.actor.bind_property('opacity', closeButton, 'opacity',
GObject.BindingFlags.SYNC_CREATE); GObject.BindingFlags.SYNC_CREATE);
global.focus_manager.add_group(this.actor);
source.actor.connect('destroy', Lang.bind(this, source.actor.connect('destroy', Lang.bind(this,
function() { function() {
this.actor.destroy(); this.actor.destroy();
})); }));
this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPress));
},
_onKeyPress: function(actor, event) {
if (!this._isOpen)
return false;
if (event.get_key_symbol() != Clutter.KEY_Escape)
return false;
this.popdown();
return true;
}, },
toggle: function() { toggle: function() {
@@ -714,7 +642,6 @@ const AppFolderPopup = new Lang.Class({
return; return;
this.actor.show(); this.actor.show();
this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
this._boxPointer.setArrowActor(this._source.actor); this._boxPointer.setArrowActor(this._source.actor);
this._boxPointer.show(BoxPointer.PopupAnimation.FADE | this._boxPointer.show(BoxPointer.PopupAnimation.FADE |

View File

@@ -38,7 +38,6 @@ const BackgroundCache = new Lang.Class({
_init: function() { _init: function() {
this._patterns = []; this._patterns = [];
this._images = []; this._images = [];
this._pendingFileLoads = [];
this._fileMonitors = {}; this._fileMonitors = {};
}, },
@@ -85,9 +84,9 @@ const BackgroundCache = new Lang.Class({
} else { } else {
content.load_gradient(params.shadingType, params.color, params.secondColor); content.load_gradient(params.shadingType, params.color, params.secondColor);
} }
}
this._patterns.push(content); this._patterns.push(content);
}
return content; return content;
}, },
@@ -126,81 +125,9 @@ const BackgroundCache = new Lang.Class({
}, },
removeImageContent: function(content) { removeImageContent: function(content) {
let filename = content.get_filename();
if (filename && this._fileMonitors[filename])
delete this._fileMonitors[filename];
this._removeContent(this._images, content); this._removeContent(this._images, content);
}, },
_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) { getImageContent: function(params) {
params = Params.parse(params, { monitorIndex: 0, params = Params.parse(params, { monitorIndex: 0,
style: null, style: null,
@@ -238,19 +165,31 @@ const BackgroundCache = new Lang.Class({
if (params.cancellable && params.cancellable.is_cancelled()) if (params.cancellable && params.cancellable.is_cancelled())
content = null; content = null;
else
this._images.push(content);
if (params.onFinished) if (params.onFinished)
params.onFinished(content); params.onFinished(content);
} else { } else {
this._loadImageContent({ filename: params.filename, content = new Meta.Background({ meta_screen: global.screen,
style: params.style, monitor: params.monitorIndex,
effects: params.effects, effects: params.effects });
monitorIndex: params.monitorIndex,
cancellable: params.cancellable,
onFinished: params.onFinished });
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) { _init: function(params) {
params = Params.parse(params, { monitorIndex: 0, params = Params.parse(params, { monitorIndex: 0,
layoutManager: Main.layoutManager, layoutManager: Main.layoutManager,
effects: Meta.BackgroundEffects.NONE, effects: Meta.BackgroundEffects.NONE });
settings: null });
this.actor = new Meta.BackgroundGroup(); this.actor = new Meta.BackgroundGroup();
this.actor._delegate = this; this.actor._delegate = this;
this._destroySignalId = this.actor.connect('destroy', this._destroySignalId = this.actor.connect('destroy',
Lang.bind(this, this._destroy)); Lang.bind(this, this._destroy));
this._settings = params.settings; this._settings = new Gio.Settings({ schema: BACKGROUND_SCHEMA });
this._monitorIndex = params.monitorIndex; this._monitorIndex = params.monitorIndex;
this._layoutManager = params.layoutManager; this._layoutManager = params.layoutManager;
this._effects = params.effects; this._effects = params.effects;
@@ -329,9 +267,9 @@ const Background = new Lang.Class({
_destroy: function() { _destroy: function() {
this._cancellable.cancel(); this._cancellable.cancel();
if (this._updateAnimationTimeoutId) { if (this._animationUpdateTimeoutId) {
GLib.source_remove (this._updateAnimationTimeoutId); GLib.source_remove (this._animationUpdateTimeoutId);
this._updateAnimationTimeoutId = 0; this._animationUpdateTimeoutId = 0
} }
let i; let i;
@@ -418,10 +356,7 @@ const Background = new Lang.Class({
let actor = new Meta.BackgroundActor(); let actor = new Meta.BackgroundActor();
actor.content = content; actor.content = content;
this.actor.add_child(actor);
// The background pattern is the first actor in
// the group, and all images should be above that.
this.actor.insert_child_at_index(actor, index + 1);
this._images[index] = actor; this._images[index] = actor;
this._watchCacheFile(filename); this._watchCacheFile(filename);
@@ -432,27 +367,27 @@ const Background = new Lang.Class({
content.brightness = this._brightness; content.brightness = this._brightness;
content.vignette_sharpness = this._vignetteSharpness; content.vignette_sharpness = this._vignetteSharpness;
this._cache.removeImageContent(this._images[index].content);
this._images[index].content = content; this._images[index].content = content;
this._watchCacheFile(filename); this._watchCacheFile(filename);
}, },
_updateAnimationProgress: function() { _updateAnimationProgress: function() {
if (this._images[1]) if (this._images[1]) {
this._images[1].raise_top();
this._images[1].opacity = this._animation.transitionProgress * 255; this._images[1].opacity = this._animation.transitionProgress * 255;
}
this._queueUpdateAnimation(); this._queueAnimationUpdate();
}, },
_updateAnimation: function() { _updateAnimation: function() {
this._updateAnimationTimeoutId = 0; this._animationUpdateTimeoutId = 0;
this._animation.update(this._layoutManager.monitors[this._monitorIndex]); let files = this._animation.getKeyFrameFiles(this._layoutManager.monitors[this._monitorIndex]);
let files = this._animation.keyFrameFiles;
if (files.length == 0) { if (!files) {
this._setLoaded(); this._setLoaded();
this._queueUpdateAnimation(); this._queueAnimationUpdate();
return; return;
} }
@@ -471,7 +406,7 @@ const Background = new Lang.Class({
style: this._style, style: this._style,
filename: files[i], filename: files[i],
cancellable: this._cancellable, cancellable: this._cancellable,
onFinished: Lang.bind(this, function(content, i) { onFinished: Lang.bind(this, function(content) {
numPendingImages--; numPendingImages--;
if (!content) { if (!content) {
@@ -491,34 +426,27 @@ const Background = new Lang.Class({
this._setLoaded(); this._setLoaded();
this._updateAnimationProgress(); this._updateAnimationProgress();
} }
}, i) })
}); });
} }
}, },
_queueUpdateAnimation: function() { _queueAnimationUpdate: function() {
if (this._updateAnimationTimeoutId != 0) if (this._animationUpdateTimeoutId != 0)
return; return;
if (!this._cancellable || this._cancellable.is_cancelled()) if (!this._cancellable || this._cancellable.is_cancelled())
return; return;
if (!this._animation.transitionDuration) if (!this._animation.duration)
return; return;
let nSteps = 255 / ANIMATION_OPACITY_STEP_INCREMENT;
let timePerStep = (this._animation.transitionDuration * 1000) / nSteps;
let interval = Math.max(ANIMATION_MIN_WAKEUP_INTERVAL * 1000, let interval = Math.max(ANIMATION_MIN_WAKEUP_INTERVAL * 1000,
timePerStep); ANIMATION_OPACITY_STEP_INCREMENT / this._animation.duration);
this._animationUpdateTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
if (interval > GLib.MAXUINT32)
return;
this._updateAnimationTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
interval, interval,
Lang.bind(this, function() { Lang.bind(this, function() {
this._updateAnimationTimeoutId = 0; this._animationUpdateTimeoutId = 0;
this._updateAnimation(); this._updateAnimation();
return false; return false;
})); }));
@@ -572,16 +500,7 @@ const Background = new Lang.Class({
} }
let uri = this._settings.get_string(PICTURE_URI_KEY); let uri = this._settings.get_string(PICTURE_URI_KEY);
let filename; let filename = Gio.File.new_for_uri(uri).get_path();
if (GLib.uri_parse_scheme(uri) != null)
filename = Gio.File.new_for_uri(uri).get_path();
else
filename = uri;
if (!filename) {
this._setLoaded();
return;
}
this._loadFile(filename); this._loadFile(filename);
}, },
@@ -666,9 +585,9 @@ const Animation = new Lang.Class({
params = Params.parse(params, { filename: null }); params = Params.parse(params, { filename: null });
this.filename = params.filename; this.filename = params.filename;
this.keyFrameFiles = []; this._keyFrames = [];
this.duration = 0.0;
this.transitionProgress = 0.0; this.transitionProgress = 0.0;
this.transitionDuration = 0.0;
this.loaded = false; this.loaded = false;
}, },
@@ -680,31 +599,33 @@ const Animation = new Lang.Class({
this._show.load_async(null, this._show.load_async(null,
Lang.bind(this, Lang.bind(this,
function(object, result) { function(object, result) {
this.duration = this._show.get_total_duration();
this.loaded = true; this.loaded = true;
if (callback) if (callback)
callback(); callback();
})); }));
}, },
update: function(monitor) { getKeyFrameFiles: function(monitor) {
this.keyFrameFiles = [];
if (!this._show) if (!this._show)
return; return null;
if (this._show.get_num_slides() < 1) 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); let [progress, duration, isFixed, file1, file2] = this._show.get_current_slide(monitor.width, monitor.height);
this.transitionDuration = duration;
this.transitionProgress = progress; this.transitionProgress = progress;
let files = [];
if (file1) if (file1)
this.keyFrameFiles.push(file1); files.push(file1);
if (file2) if (file2)
this.keyFrameFiles.push(file2); files.push(file2);
return files;
}, },
}); });
Signals.addSignalMethods(Animation.prototype); Signals.addSignalMethods(Animation.prototype);
@@ -717,10 +638,8 @@ const BackgroundManager = new Lang.Class({
layoutManager: Main.layoutManager, layoutManager: Main.layoutManager,
monitorIndex: null, monitorIndex: null,
effects: Meta.BackgroundEffects.NONE, effects: Meta.BackgroundEffects.NONE,
controlPosition: true, controlPosition: true });
settingsSchema: BACKGROUND_SCHEMA });
this._settings = new Gio.Settings({ schema: params.settingsSchema });
this._container = params.container; this._container = params.container;
this._layoutManager = params.layoutManager; this._layoutManager = params.layoutManager;
this._effects = params.effects; this._effects = params.effects;
@@ -729,9 +648,17 @@ const BackgroundManager = new Lang.Class({
this.background = this._createBackground(); this.background = this._createBackground();
this._newBackground = null; this._newBackground = null;
this._loadedSignalId = 0;
this._changedSignalId = 0;
}, },
destroy: function() { destroy: function() {
if (this._loadedSignalId)
this._newBackground.disconnect(this._loadedSignalId);
if (this._changedSignalId)
this.background.disconnect(this._changedSignalId);
if (this._newBackground) { if (this._newBackground) {
this._newBackground.actor.destroy(); this._newBackground.actor.destroy();
this._newBackground = null; this._newBackground = null;
@@ -750,28 +677,22 @@ const BackgroundManager = new Lang.Class({
newBackground.saturation = background.saturation; newBackground.saturation = background.saturation;
newBackground.visible = background.visible; newBackground.visible = background.visible;
newBackground.loadedSignalId = newBackground.connect('loaded', let signalId = newBackground.connect('loaded',
Lang.bind(this, function() { Lang.bind(this, function() {
newBackground.disconnect(newBackground.loadedSignalId); newBackground.disconnect(signalId);
newBackground.loadedSignalId = 0;
Tweener.addTween(background.actor, Tweener.addTween(background.actor,
{ opacity: 0, { opacity: 0,
time: FADE_ANIMATION_TIME, time: FADE_ANIMATION_TIME,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, function() {
if (this._newBackground == newBackground) { this.background = newBackground;
this.background = newBackground; this._newBackground = null;
this._newBackground = null;
} else {
newBackground.actor.destroy();
}
background.actor.destroy(); background.actor.destroy();
this.emit('changed'); this.emit('changed');
}) })
}); });
})); }));
this._loadedSignalId = signalId;
this._newBackground = newBackground; this._newBackground = newBackground;
}, },
@@ -779,8 +700,7 @@ const BackgroundManager = new Lang.Class({
_createBackground: function() { _createBackground: function() {
let background = new Background({ monitorIndex: this._monitorIndex, let background = new Background({ monitorIndex: this._monitorIndex,
layoutManager: this._layoutManager, layoutManager: this._layoutManager,
effects: this._effects, effects: this._effects });
settings: this._settings });
this._container.add_child(background.actor); this._container.add_child(background.actor);
let monitor = this._layoutManager.monitors[this._monitorIndex]; let monitor = this._layoutManager.monitors[this._monitorIndex];
@@ -791,19 +711,12 @@ const BackgroundManager = new Lang.Class({
background.actor.lower_bottom(); background.actor.lower_bottom();
} }
background.changeSignalId = background.connect('changed', Lang.bind(this, function() { let signalId = background.connect('changed', Lang.bind(this, function() {
background.disconnect(background.changeSignalId); background.disconnect(signalId);
background.changeSignalId = 0;
this._updateBackground(background, this._monitorIndex); this._updateBackground(background, this._monitorIndex);
})); }));
background.actor.connect('destroy', Lang.bind(this, function() { this._changedSignalId = signalId;
if (background.changeSignalId)
background.disconnect(background.changeSignalId);
if (background.loadedSignalId)
background.disconnect(background.loadedSignalId);
}));
return background; return background;
}, },

View File

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

View File

@@ -589,12 +589,12 @@ const BoxPointer = new Lang.Class({
return St.Side.TOP; return St.Side.TOP;
break; break;
case St.Side.LEFT: case St.Side.LEFT:
if (sourceAllocation.x2 + boxWidth > monitor.x + monitor.width && if (sourceAllocation.y2 + boxWidth > monitor.x + monitor.width &&
boxWidth < sourceAllocation.x1 - monitor.x) boxWidth < sourceAllocation.x1 - monitor.x)
return St.Side.RIGHT; return St.Side.RIGHT;
break; break;
case St.Side.RIGHT: case St.Side.RIGHT:
if (sourceAllocation.x1 - boxWidth < monitor.x && if (sourceAllocation.y1 - boxWidth < monitor.x &&
boxWidth < monitor.x + monitor.width - sourceAllocation.x2) boxWidth < monitor.x + monitor.width - sourceAllocation.x2)
return St.Side.LEFT; return St.Side.LEFT;
break; break;

View File

@@ -71,7 +71,7 @@ function _formatEventTime(event, clockFormat) {
default: default:
/* explicit fall-through */ /* explicit fall-through */
case '12h': 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 \u2236 is a ratio character, similar to : and \u2009 is
a thin space */ a thin space */
ret = event.date.toLocaleFormat(C_("event list time", "%l\u2236%M\u2009%p")); ret = event.date.toLocaleFormat(C_("event list time", "%l\u2236%M\u2009%p"));
@@ -168,12 +168,6 @@ const EmptyEventSource = new Lang.Class({
Name: 'EmptyEventSource', Name: 'EmptyEventSource',
_init: function() { _init: function() {
this.isLoading = false;
this.isDummy = true;
this.hasCalendars = false;
},
destroy: function() {
}, },
requestRange: function(begin, end) { requestRange: function(begin, end) {
@@ -197,7 +191,6 @@ const CalendarServerIface = <interface name="org.gnome.Shell.CalendarServer">
<arg type="b" direction="in" /> <arg type="b" direction="in" />
<arg type="a(sssbxxa{sv})" direction="out" /> <arg type="a(sssbxxa{sv})" direction="out" />
</method> </method>
<property name="HasCalendars" type="b" access="read" />
<signal name="Changed" /> <signal name="Changed" />
</interface>; </interface>;
@@ -208,7 +201,8 @@ function CalendarServer() {
g_interface_name: CalendarServerInfo.name, g_interface_name: CalendarServerInfo.name,
g_interface_info: CalendarServerInfo, g_interface_info: CalendarServerInfo,
g_name: 'org.gnome.Shell.CalendarServer', g_name: 'org.gnome.Shell.CalendarServer',
g_object_path: '/org/gnome/Shell/CalendarServer' }); g_object_path: '/org/gnome/Shell/CalendarServer',
g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES });
} }
function _datesEqual(a, b) { function _datesEqual(a, b) {
@@ -235,8 +229,6 @@ const DBusEventSource = new Lang.Class({
_init: function() { _init: function() {
this._resetCache(); this._resetCache();
this.isLoading = false;
this.isDummy = false;
this._initialized = false; this._initialized = false;
this._dbusProxy = new CalendarServer(); this._dbusProxy = new CalendarServer();
@@ -257,27 +249,11 @@ const DBusEventSource = new Lang.Class({
this._onNameVanished(); this._onNameVanished();
})); }));
this._dbusProxy.connect('g-properties-changed', Lang.bind(this, function() {
this.emit('notify::has-calendars');
}));
this._initialized = true; this._initialized = true;
this.emit('notify::has-calendars');
this._onNameAppeared(); this._onNameAppeared();
})); }));
}, },
destroy: function() {
this._dbusProxy.run_dispose();
},
get hasCalendars() {
if (this._initialized)
return this._dbusProxy.HasCalendars;
else
return false;
},
_resetCache: function() { _resetCache: function() {
this._events = []; this._events = [];
this._lastRequestBegin = null; this._lastRequestBegin = null;
@@ -317,7 +293,6 @@ const DBusEventSource = new Lang.Class({
} }
this._events = newEvents; this._events = newEvents;
this.isLoading = false;
this.emit('changed'); this.emit('changed');
}, },
@@ -340,7 +315,6 @@ const DBusEventSource = new Lang.Class({
requestRange: function(begin, end, forceReload) { requestRange: function(begin, end, forceReload) {
if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) { if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) {
this.isLoading = true;
this._lastRequestBegin = begin; this._lastRequestBegin = begin;
this._lastRequestEnd = end; this._lastRequestEnd = end;
this._curRequestBegin = begin; this._curRequestBegin = begin;
@@ -415,11 +389,19 @@ const Calendar = new Lang.Class({
// @eventSource: is an object implementing the EventSource API, e.g. the // @eventSource: is an object implementing the EventSource API, e.g. the
// requestRange(), getEvents(), hasEvents() methods and the ::changed signal. // requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
setEventSource: function(eventSource) { setEventSource: function(eventSource) {
if (this._eventSource) {
this._eventSource.disconnect(this._eventSourceChangedId);
this._eventSource = null;
}
this._eventSource = eventSource; this._eventSource = eventSource;
this._eventSource.connect('changed', Lang.bind(this, function() {
this._update(false); if (this._eventSource) {
})); this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, function() {
this._update(true); this._update(false);
}));
this._update(true);
}
}, },
// Sets the calendar to show a specific date // Sets the calendar to show a specific date
@@ -443,18 +425,16 @@ const Calendar = new Lang.Class({
this.actor.add(this._topBox, this.actor.add(this._topBox,
{ row: 0, col: 0, col_span: offsetCols + 7 }); { row: 0, col: 0, col_span: offsetCols + 7 });
this._backButton = new St.Button({ style_class: 'calendar-change-month-back', let back = new St.Button({ style_class: 'calendar-change-month-back' });
can_focus: true }); this._topBox.add(back);
this._topBox.add(this._backButton); back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
this._backButton.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
this._monthLabel = new St.Label({style_class: 'calendar-month-label'}); this._monthLabel = new St.Label({style_class: 'calendar-month-label'});
this._topBox.add(this._monthLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE }); this._topBox.add(this._monthLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
this._forwardButton = new St.Button({ style_class: 'calendar-change-month-forward', let forward = new St.Button({ style_class: 'calendar-change-month-forward' });
can_focus: true }); this._topBox.add(forward);
this._topBox.add(this._forwardButton); forward.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked));
this._forwardButton.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked));
// Add weekday labels... // Add weekday labels...
// //
@@ -513,12 +493,10 @@ const Calendar = new Lang.Class({
} }
} }
this._backButton.grab_key_focus();
this.setDate(newDate, false); this.setDate(newDate, false);
}, },
_onNextMonthButtonClicked: function() { _onNextMonthButtonClicked: function() {
let newDate = new Date(this._selectedDate); let newDate = new Date(this._selectedDate);
let oldMonth = newDate.getMonth(); let oldMonth = newDate.getMonth();
if (oldMonth == 11) { if (oldMonth == 11) {
@@ -537,9 +515,7 @@ const Calendar = new Lang.Class({
} }
} }
this._forwardButton.grab_key_focus(); this.setDate(newDate, false);
this.setDate(newDate, false);
}, },
_onSettingsChange: function() { _onSettingsChange: function() {
@@ -562,60 +538,30 @@ const Calendar = new Lang.Class({
children[i].destroy(); children[i].destroy();
// Start at the beginning of the week before the start of the month // 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); let beginDate = new Date(this._selectedDate);
beginDate.setDate(1); beginDate.setDate(1);
beginDate.setSeconds(0); beginDate.setSeconds(0);
beginDate.setHours(12); beginDate.setHours(12);
let year = beginDate.getYear();
let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7; let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7;
let startsOnWeekStart = daysToWeekStart == 0; beginDate.setTime(beginDate.getTime() - daysToWeekStart * MSECS_IN_DAY);
let weekPadding = startsOnWeekStart ? 7 : 0;
beginDate.setTime(beginDate.getTime() - (weekPadding + daysToWeekStart) * MSECS_IN_DAY);
let iter = new Date(beginDate); let iter = new Date(beginDate);
let row = 2; let row = 2;
// nRows here means 6 weeks + one header + one navbar while (true) {
let nRows = 8; let button = new St.Button({ label: iter.getDate().toString() });
while (row < 8) {
let button = new St.Button({ label: iter.getDate().toString(),
can_focus: true });
let rtl = button.get_text_direction() == Clutter.TextDirection.RTL; let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;
if (this._eventSource.isDummy) if (!this._eventSource)
button.reactive = false; button.reactive = false;
let iterStr = iter.toUTCString(); let iterStr = iter.toUTCString();
button.connect('clicked', Lang.bind(this, function() { button.connect('clicked', Lang.bind(this, function() {
this._shouldDateGrabFocus = true;
let newlySelectedDate = new Date(iterStr); let newlySelectedDate = new Date(iterStr);
this.setDate(newlySelectedDate, false); this.setDate(newlySelectedDate, false);
this._shouldDateGrabFocus = false;
})); }));
let hasEvents = this._eventSource.hasEvents(iter); let hasEvents = this._eventSource && this._eventSource.hasEvents(iter);
let styleClass = 'calendar-day-base calendar-day'; let styleClass = 'calendar-day-base calendar-day';
if (_isWorkDay(iter)) if (_isWorkDay(iter))
styleClass += ' calendar-work-day' styleClass += ' calendar-work-day'
else else
@@ -635,6 +581,9 @@ const Calendar = new Lang.Class({
else if (iter.getMonth() != this._selectedDate.getMonth()) else if (iter.getMonth() != this._selectedDate.getMonth())
styleClass += ' calendar-other-month-day'; styleClass += ' calendar-other-month-day';
if (_sameDay(this._selectedDate, iter))
button.add_style_pseudo_class('active');
if (hasEvents) if (hasEvents)
styleClass += ' calendar-day-with-events' styleClass += ' calendar-day-with-events'
@@ -644,13 +593,6 @@ const Calendar = new Lang.Class({
this.actor.add(button, this.actor.add(button,
{ row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 }); { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 });
if (_sameDay(this._selectedDate, iter)) {
button.add_style_pseudo_class('active');
if (this._shouldDateGrabFocus)
button.grab_key_focus();
}
if (this._useWeekdate && iter.getDay() == 4) { if (this._useWeekdate && iter.getDay() == 4) {
let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(), let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(),
style_class: 'calendar-day-base calendar-week-number'}); style_class: 'calendar-day-base calendar-week-number'});
@@ -659,13 +601,17 @@ const Calendar = new Lang.Class({
} }
iter.setTime(iter.getTime() + MSECS_IN_DAY); 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++; row++;
}
} }
// Signal to the event source that we are interested in events // Signal to the event source that we are interested in events
// only from this date range // only from this date range
this._eventSource.requestRange(beginDate, iter, forceReload); if (this._eventSource)
this._eventSource.requestRange(beginDate, iter, forceReload);
} }
}); });
@@ -675,7 +621,7 @@ const EventsList = new Lang.Class({
Name: 'EventsList', Name: 'EventsList',
_init: function() { _init: function() {
this.actor = new St.Table({ style_class: 'events-table' }); this.actor = new St.BoxLayout({ vertical: true, style_class: 'events-header-vbox'});
this._date = new Date(); this._date = new Date();
this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' }); this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
this._desktopSettings.connect('changed', Lang.bind(this, this._update)); this._desktopSettings.connect('changed', Lang.bind(this, this._update));
@@ -683,76 +629,70 @@ const EventsList = new Lang.Class({
}, },
setEventSource: function(eventSource) { setEventSource: function(eventSource) {
if (this._eventSource) {
this._eventSource.disconnect(this._eventSourceChangedId);
this._eventSource = null;
}
this._eventSource = eventSource; 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) { _addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) {
let dayString; if (includeDayName) {
if (includeDayName) dayNameBox.add(new St.Label( { style_class: 'events-day-dayname',
dayString = _getEventDayAbbreviation(event.date.getDay()); text: day } ),
else { x_fill: true } );
dayString = ''; }
timeBox.add(new St.Label( { style_class: 'events-day-time',
let dayLabel = new St.Label({ style_class: 'events-day-dayname', text: time} ),
text: dayString }); { x_fill: true } );
dayLabel.clutter_text.line_wrap = false; eventTitleBox.add(new St.Label( { style_class: 'events-day-task',
dayLabel.clutter_text.ellipsize = false; text: desc} ));
this.actor.add(dayLabel, { row: index, col: 0,
x_expand: false, x_align: St.Align.END,
y_fill: false, y_align: St.Align.START });
let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
let timeString = _formatEventTime(event, clockFormat);
let timeLabel = new St.Label({ style_class: 'events-day-time',
text: timeString });
timeLabel.clutter_text.line_wrap = false;
timeLabel.clutter_text.ellipsize = false;
this.actor.add(timeLabel, { row: index, col: 1,
x_expand: false, x_align: St.Align.MIDDLE,
y_fill: false, y_align: St.Align.START });
let titleLabel = new St.Label({ style_class: 'events-day-task',
text: event.summary });
titleLabel.clutter_text.line_wrap = true;
titleLabel.clutter_text.ellipsize = false;
this.actor.add(titleLabel, { row: index, col: 2,
x_expand: true, x_align: St.Align.START,
y_fill: false, y_align: St.Align.START });
}, },
_addPeriod: function(header, index, begin, end, includeDayName, showNothingScheduled) { _addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
if (!this._eventSource)
return;
let events = this._eventSource.getEvents(begin, end); let events = this._eventSource.getEvents(begin, end);
if (events.length == 0 && !showNothingScheduled) let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);;
return index;
this.actor.add(new St.Label({ style_class: 'events-day-header', text: header }), if (events.length == 0 && !showNothingScheduled)
{ row: index, col: 0, col_span: 3, return;
// In theory, x_expand should be true here, but x_expand
// is a property of the column for StTable, ie all day cells let vbox = new St.BoxLayout( {vertical: true} );
// get it too this.actor.add(vbox);
x_expand: false, x_align: St.Align.START,
y_fill: false, y_align: St.Align.START }); vbox.add(new St.Label({ style_class: 'events-day-header', text: header }));
index++; let box = new St.BoxLayout({style_class: 'events-header-hbox'});
let dayNameBox = new St.BoxLayout({ vertical: true, style_class: 'events-day-name-box' });
let timeBox = new St.BoxLayout({ vertical: true, style_class: 'events-time-box' });
let eventTitleBox = new St.BoxLayout({ vertical: true, style_class: 'events-event-box' });
box.add(dayNameBox, {x_fill: false});
box.add(timeBox, {x_fill: false});
box.add(eventTitleBox, {expand: true});
vbox.add(box);
for (let n = 0; n < events.length; n++) { for (let n = 0; n < events.length; n++) {
this._addEvent(events[n], index, includeDayName); let event = events[n];
index++; let dayString = _getEventDayAbbreviation(event.date.getDay());
let timeString = _formatEventTime(event, clockFormat);
let summaryString = event.summary;
this._addEvent(dayNameBox, timeBox, eventTitleBox, includeDayName, dayString, timeString, summaryString);
} }
if (events.length == 0 && showNothingScheduled) { if (events.length == 0 && showNothingScheduled) {
let now = new Date(); let now = new Date();
/* Translators: Text to show if there are no events */ /* Translators: Text to show if there are no events */
let nothingEvent = new CalendarEvent(now, now, _("Nothing Scheduled"), true); let nothingEvent = new CalendarEvent(now, now, _("Nothing Scheduled"), true);
this._addEvent(nothingEvent, index, false); let timeString = _formatEventTime(nothingEvent, clockFormat);
index++; this._addEvent(dayNameBox, timeBox, eventTitleBox, false, "", timeString, nothingEvent.summary);
} }
return index;
}, },
_showOtherDay: function(day) { _showOtherDay: function(day) {
@@ -769,21 +709,20 @@ const EventsList = new Lang.Class({
else else
/* Translators: Shown on calendar heading when selected day occurs on different year */ /* Translators: Shown on calendar heading when selected day occurs on different year */
dayString = day.toLocaleFormat(C_("calendar heading", "%A, %B %d, %Y")); dayString = day.toLocaleFormat(C_("calendar heading", "%A, %B %d, %Y"));
this._addPeriod(dayString, 0, dayBegin, dayEnd, false, true); this._addPeriod(dayString, dayBegin, dayEnd, false, true);
}, },
_showToday: function() { _showToday: function() {
this.actor.destroy_all_children(); this.actor.destroy_all_children();
let index = 0;
let now = new Date(); let now = new Date();
let dayBegin = _getBeginningOfDay(now); let dayBegin = _getBeginningOfDay(now);
let dayEnd = _getEndOfDay(now); let dayEnd = _getEndOfDay(now);
index = this._addPeriod(_("Today"), index, dayBegin, dayEnd, false, true); this._addPeriod(_("Today"), dayBegin, dayEnd, false, true);
let tomorrowBegin = new Date(dayBegin.getTime() + 86400 * 1000); let tomorrowBegin = new Date(dayBegin.getTime() + 86400 * 1000);
let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000); let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000);
index = this._addPeriod(_("Tomorrow"), index, tomorrowBegin, tomorrowEnd, false, true); this._addPeriod(_("Tomorrow"), tomorrowBegin, tomorrowEnd, false, true);
let dayInWeek = (dayEnd.getDay() - this._weekStart + 7) % 7; let dayInWeek = (dayEnd.getDay() - this._weekStart + 7) % 7;
@@ -794,7 +733,7 @@ const EventsList = new Lang.Class({
*/ */
let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000); let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
let thisWeekEnd = new Date(dayEnd.getTime() + (6 - dayInWeek) * 86400 * 1000); let thisWeekEnd = new Date(dayEnd.getTime() + (6 - dayInWeek) * 86400 * 1000);
index = this._addPeriod(_("This week"), index, thisWeekBegin, thisWeekEnd, true, false); this._addPeriod(_("This week"), thisWeekBegin, thisWeekEnd, true, false);
} else { } else {
/* otherwise it's one of the two last days of the week ... show /* otherwise it's one of the two last days of the week ... show
* "Next week" and include events up until and including *next* * "Next week" and include events up until and including *next*
@@ -802,7 +741,7 @@ const EventsList = new Lang.Class({
*/ */
let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000); let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
let nextWeekEnd = new Date(dayEnd.getTime() + (13 - dayInWeek) * 86400 * 1000); let nextWeekEnd = new Date(dayEnd.getTime() + (13 - dayInWeek) * 86400 * 1000);
index = this._addPeriod(_("Next week"), index, nextWeekBegin, nextWeekEnd, true, false); this._addPeriod(_("Next week"), nextWeekBegin, nextWeekEnd, true, false);
} }
}, },
@@ -815,9 +754,6 @@ const EventsList = new Lang.Class({
}, },
_update: function() { _update: function() {
if (this._eventSource.isLoading)
return;
let today = new Date(); let today = new Date();
if (_sameDay (this._date, today)) { if (_sameDay (this._date, today)) {
this._showToday(); this._showToday();

View File

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

View File

@@ -31,7 +31,7 @@ function shouldAutorunMount(mount, forTransient) {
if (!volume || (!volume.allowAutorun && forTransient)) if (!volume || (!volume.allowAutorun && forTransient))
return false; return false;
if (root.is_native() && isMountRootHidden(root)) if (!root.is_native() || isMountRootHidden(root))
return false; return false;
return true; return true;
@@ -292,7 +292,6 @@ const AutorunResidentSource = new Lang.Class({
_init: function(manager) { _init: function(manager) {
this.parent(_("Removable Devices"), 'media-removable'); this.parent(_("Removable Devices"), 'media-removable');
this.resident = true;
this._mounts = []; this._mounts = [];

View File

@@ -25,7 +25,7 @@ const KeyringDialog = new Lang.Class({
this.prompt = new Shell.KeyringPrompt(); this.prompt = new Shell.KeyringPrompt();
this.prompt.connect('show-password', Lang.bind(this, this._onShowPassword)); this.prompt.connect('show-password', Lang.bind(this, this._onShowPassword));
this.prompt.connect('show-confirm', Lang.bind(this, this._onShowConfirm)); this.prompt.connect('show-confirm', Lang.bind(this, this._onShowConfirm));
this.prompt.connect('prompt-close', Lang.bind(this, this._onHidePrompt)); this.prompt.connect('hide-prompt', Lang.bind(this, this._onHidePrompt));
let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout', let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout',
vertical: false }); vertical: false });
@@ -63,17 +63,11 @@ const KeyringDialog = new Lang.Class({
this._cancelButton = this.addButton({ label: '', this._cancelButton = this.addButton({ label: '',
action: Lang.bind(this, this._onCancelButton), action: Lang.bind(this, this._onCancelButton),
key: Clutter.Escape }, key: Clutter.Escape });
{ expand: true, x_fill: false, x_align: St.Align.START });
this.placeSpinner({ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
this._continueButton = this.addButton({ label: '', this._continueButton = this.addButton({ label: '',
action: Lang.bind(this, this._onContinueButton), action: Lang.bind(this, this._onContinueButton),
default: true }, default: true },
{ expand: false, x_fill: false, x_align: St.Align.END }); { expand: true, x_fill: false, x_align: St.Align.END });
this.prompt.bind_property('cancel-label', this._cancelButton, 'label', GObject.BindingFlags.SYNC_CREATE); this.prompt.bind_property('cancel-label', this._cancelButton, 'label', GObject.BindingFlags.SYNC_CREATE);
this.prompt.bind_property('continue-label', this._continueButton, 'label', GObject.BindingFlags.SYNC_CREATE); this.prompt.bind_property('continue-label', this._continueButton, 'label', GObject.BindingFlags.SYNC_CREATE);
@@ -149,19 +143,11 @@ const KeyringDialog = new Lang.Class({
}, },
_updateSensitivity: function(sensitive) { _updateSensitivity: function(sensitive) {
if (this._passwordEntry) { this._passwordEntry.reactive = sensitive;
this._passwordEntry.reactive = sensitive; this._passwordEntry.clutter_text.editable = sensitive;
this._passwordEntry.clutter_text.editable = sensitive;
}
if (this._confirmEntry) {
this._confirmEntry.reactive = sensitive;
this._confirmEntry.clutter_text.editable = sensitive;
}
this._continueButton.can_focus = sensitive; this._continueButton.can_focus = sensitive;
this._continueButton.reactive = sensitive; this._continueButton.reactive = sensitive;
this.setWorking(!sensitive);
}, },
_ensureOpen: function() { _ensureOpen: function() {
@@ -236,7 +222,7 @@ const KeyringPrompter = new Lang.Class({
enable: function() { enable: function() {
this._prompter.register(Gio.DBus.session); this._prompter.register(Gio.DBus.session);
this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter', this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter',
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null); Gio.BusNameOwnerFlags.REPLACE, null, null);
}, },
disable: function() { disable: function() {

View File

@@ -62,9 +62,14 @@ const NetworkSecretDialog = new Lang.Class({
if (this._content.message != null) { if (this._content.message != null) {
let descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description', let descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description',
text: this._content.message }); text: this._content.message,
// HACK: for reasons unknown to me, the label
// is not asked the correct height for width,
// and thus is underallocated
// place a fixed height to avoid overflowing
style: 'height: 3em'
});
descriptionLabel.clutter_text.line_wrap = true; descriptionLabel.clutter_text.line_wrap = true;
descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
messageBox.add(descriptionLabel, messageBox.add(descriptionLabel,
{ y_fill: true, { y_fill: true,

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ const Params = imports.misc.params;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
// See Notification.appendMessage // See Notification.appendMessage
const SCROLLBACK_IMMEDIATE_TIME = 3 * 60; // 3 minutes const SCROLLBACK_IMMEDIATE_TIME = 60; // 1 minute
const SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes const SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes
const SCROLLBACK_RECENT_LENGTH = 20; const SCROLLBACK_RECENT_LENGTH = 20;
const SCROLLBACK_IDLE_LENGTH = 5; const SCROLLBACK_IDLE_LENGTH = 5;
@@ -967,8 +967,7 @@ const ChatNotification = new Lang.Class({
let timeLabel = this._append({ body: this._formatTimestamp(lastMessageDate), let timeLabel = this._append({ body: this._formatTimestamp(lastMessageDate),
group: 'meta', group: 'meta',
styles: ['chat-meta-message'], styles: ['chat-meta-message'],
childProps: { expand: true, x_fill: false, childProps: { expand: true, x_fill: false },
x_align: St.Align.END },
noTimestamp: true, noTimestamp: true,
timestamp: lastMessageTime }); timestamp: lastMessageTime });
@@ -1135,8 +1134,6 @@ const AudioVideoNotification = new Lang.Class({
this.parent(source, title, null, { customContent: true }); this.parent(source, title, null, { customContent: true });
this.setResident(true); this.setResident(true);
this.setUrgency(MessageTray.Urgency.CRITICAL);
this.addButton('reject', _("Decline")); this.addButton('reject', _("Decline"));
/* translators: this is a button label (verb), not a noun */ /* translators: this is a button label (verb), not a noun */
this.addButton('answer', _("Answer")); this.addButton('answer', _("Answer"));

View File

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

View File

@@ -287,7 +287,13 @@ const ShowAppsIcon = new Lang.Class({
}, },
handleDragOver: function(source, actor, x, y, time) { handleDragOver: function(source, actor, x, y, time) {
if (!this._canRemoveApp(getAppFromSource(source))) let app = getAppFromSource(source);
if (app == null)
return DND.DragMotionResult.NO_DROP;
let id = app.get_id();
let isFavorite = AppFavorites.getAppFavorites().isFavorite(id);
if (!isFavorite)
return DND.DragMotionResult.NO_DROP; return DND.DragMotionResult.NO_DROP;
return DND.DragMotionResult.MOVE_DROP; return DND.DragMotionResult.MOVE_DROP;
@@ -295,7 +301,7 @@ const ShowAppsIcon = new Lang.Class({
acceptDrop: function(source, actor, x, y, time) { acceptDrop: function(source, actor, x, y, time) {
let app = getAppFromSource(source); let app = getAppFromSource(source);
if (!this._canRemoveApp(app)) if (app == null)
return false; return false;
let id = app.get_id(); let id = app.get_id();
@@ -320,16 +326,6 @@ const DragPlaceholderItem = new Lang.Class({
} }
}); });
const EmptyDropTargetItem = new Lang.Class({
Name: 'EmptyDropTargetItem',
Extends: DashItemContainer,
_init: function() {
this.parent();
this.setChild(new St.Bin({ style_class: 'empty-dash-drop-target' }));
}
});
const DashActor = new Lang.Class({ const DashActor = new Lang.Class({
Name: 'DashActor', Name: 'DashActor',
Extends: St.Widget, Extends: St.Widget,
@@ -445,12 +441,6 @@ const Dash = new Lang.Class({
dragMotion: Lang.bind(this, this._onDragMotion) dragMotion: Lang.bind(this, this._onDragMotion)
}; };
DND.addDragMonitor(this._dragMonitor); DND.addDragMonitor(this._dragMonitor);
if (this._box.get_n_children() == 0) {
this._emptyDropTarget = new EmptyDropTargetItem();
this._box.insert_child_at_index(this._emptyDropTarget, 0);
this._emptyDropTarget.show(true);
}
}, },
_onDragCancelled: function() { _onDragCancelled: function() {
@@ -467,7 +457,6 @@ const Dash = new Lang.Class({
_endDrag: function() { _endDrag: function() {
this._clearDragPlaceholder(); this._clearDragPlaceholder();
this._clearEmptyDropTarget();
this._showAppsIcon.setDragApp(null); this._showAppsIcon.setDragApp(null);
DND.removeDragMonitor(this._dragMonitor); DND.removeDragMonitor(this._dragMonitor);
}, },
@@ -808,21 +797,9 @@ const Dash = new Lang.Class({
_clearDragPlaceholder: function() { _clearDragPlaceholder: function() {
if (this._dragPlaceholder) { if (this._dragPlaceholder) {
this._animatingPlaceholdersCount++;
this._dragPlaceholder.animateOutAndDestroy(); this._dragPlaceholder.animateOutAndDestroy();
this._dragPlaceholder.connect('destroy',
Lang.bind(this, function() {
this._animatingPlaceholdersCount--;
}));
this._dragPlaceholder = null; this._dragPlaceholder = null;
} this._dragPlaceholderPos = -1;
this._dragPlaceholderPos = -1;
},
_clearEmptyDropTarget: function() {
if (this._emptyDropTarget) {
this._emptyDropTarget.animateOutAndDestroy();
this._emptyDropTarget = null;
} }
}, },
@@ -850,18 +827,23 @@ const Dash = new Lang.Class({
numChildren--; numChildren--;
} }
let pos; let pos = Math.floor(y * numChildren / boxHeight);
if (!this._emptyDropTarget)
pos = Math.floor(y * numChildren / boxHeight);
else
pos = 0; // always insert at the top when dash is empty
if (pos != this._dragPlaceholderPos && pos <= numFavorites && this._animatingPlaceholdersCount == 0) { if (pos != this._dragPlaceholderPos && pos <= numFavorites && this._animatingPlaceholdersCount == 0) {
this._dragPlaceholderPos = pos; this._dragPlaceholderPos = pos;
// Don't allow positioning before or after self // Don't allow positioning before or after self
if (favPos != -1 && (pos == favPos || pos == favPos + 1)) { if (favPos != -1 && (pos == favPos || pos == favPos + 1)) {
this._clearDragPlaceholder(); if (this._dragPlaceholder) {
this._dragPlaceholder.animateOutAndDestroy();
this._animatingPlaceholdersCount++;
this._dragPlaceholder.connect('destroy',
Lang.bind(this, function() {
this._animatingPlaceholdersCount--;
}));
}
this._dragPlaceholder = null;
return DND.DragMotionResult.CONTINUE; return DND.DragMotionResult.CONTINUE;
} }
@@ -886,9 +868,9 @@ const Dash = new Lang.Class({
// Remove the drag placeholder if we are not in the // Remove the drag placeholder if we are not in the
// "favorites zone" // "favorites zone"
if (pos > numFavorites) if (pos > numFavorites && this._dragPlaceholder) {
this._clearDragPlaceholder(); this._clearDragPlaceholder();
}
if (!this._dragPlaceholder) if (!this._dragPlaceholder)
return DND.DragMotionResult.NO_DROP; return DND.DragMotionResult.NO_DROP;

View File

@@ -49,12 +49,16 @@ const DateMenuButton = new Lang.Class({
menuAlignment = 1.0 - menuAlignment; menuAlignment = 1.0 - menuAlignment;
this.parent(menuAlignment); this.parent(menuAlignment);
this._clockDisplay = new St.Label({ y_align: Clutter.ActorAlign.CENTER }); // At this moment calendar menu is not keyboard navigable at
this.actor.add_actor(this._clockDisplay); // all (so not accessible), so it doesn't make sense to set as
this.actor.add_style_class_name ('clock-display'); // role ATK_ROLE_MENU like other elements of the panel.
this.actor.accessible_role = Atk.Role.LABEL;
hbox = new St.BoxLayout({ name: 'calendarArea' }); this._clockDisplay = new St.Label();
this.menu.box.add_child(hbox); this.actor.add_actor(this._clockDisplay);
hbox = new St.BoxLayout({name: 'calendarArea' });
this.menu.addActor(hbox);
// Fill up the first column // Fill up the first column
@@ -81,22 +85,27 @@ const DateMenuButton = new Lang.Class({
vbox.add(this._calendar.actor); vbox.add(this._calendar.actor);
let separator = new PopupMenu.PopupSeparatorMenuItem(); let separator = new PopupMenu.PopupSeparatorMenuItem();
vbox.add(separator.actor, { y_align: St.Align.END, expand: true, y_fill: false }); separator.setColumnWidths(1);
vbox.add(separator.actor, {y_align: St.Align.END, expand: true, y_fill: false});
this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar")); this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar"));
this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate)); this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate));
this._openCalendarItem.actor.can_focus = false;
vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: true, y_fill: false}); vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: true, y_fill: false});
this._openClocksItem = new PopupMenu.PopupMenuItem(_("Open Clocks")); this._openClocksItem = new PopupMenu.PopupMenuItem(_("Open Clocks"));
this._openClocksItem.connect('activate', Lang.bind(this, this._onOpenClocksActivate)); this._openClocksItem.connect('activate', Lang.bind(this, this._onOpenClocksActivate));
this._openClocksItem.actor.can_focus = false;
vbox.add(this._openClocksItem.actor, {y_align: St.Align.END, expand: true, y_fill: false}); vbox.add(this._openClocksItem.actor, {y_align: St.Align.END, expand: true, y_fill: false});
Shell.AppSystem.get_default().connect('installed-changed', Shell.AppSystem.get_default().connect('installed-changed',
Lang.bind(this, this._appInstalledChanged)); Lang.bind(this, this._appInstalledChanged));
this._appInstalledChanged();
item = this.menu.addSettingsAction(_("Date & Time Settings"), 'gnome-datetime-panel.desktop'); item = this.menu.addSettingsAction(_("Date & Time Settings"), 'gnome-datetime-panel.desktop');
if (item) { if (item) {
item.actor.show_on_set_parent = false; item.actor.show_on_set_parent = false;
item.actor.can_focus = false;
item.actor.reparent(vbox); item.actor.reparent(vbox);
this._dateAndTimeSeparator = separator; this._dateAndTimeSeparator = separator;
} }
@@ -107,7 +116,12 @@ const DateMenuButton = new Lang.Class({
hbox.add(this._separator); hbox.add(this._separator);
// Fill up the second column // Fill up the second column
hbox.add(this._eventList.actor, { expand: true, y_fill: false, y_align: St.Align.START }); vbox = new St.BoxLayout({ name: 'calendarEventsArea',
vertical: true });
hbox.add(vbox, { expand: true });
// Event list
vbox.add(this._eventList.actor, { expand: true });
// Whenever the menu is opened, select today // Whenever the menu is opened, select today
this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) { this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
@@ -143,39 +157,28 @@ const DateMenuButton = new Lang.Class({
}, },
_appInstalledChanged: function() { _appInstalledChanged: function() {
this._calendarApp = undefined; let app = Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
this._updateEventsVisibility(); this._openClocksItem.actor.visible = app !== null;
}, },
_updateEventsVisibility: function() { _setEventsVisibility: function(visible) {
let visible = this._eventSource.hasCalendars; this._openCalendarItem.actor.visible = visible;
this._openCalendarItem.actor.visible = visible &&
(this._getCalendarApp() != null);
this._openClocksItem.actor.visible = visible &&
(this._getClockApp() != null);
this._separator.visible = visible; this._separator.visible = visible;
this._eventList.actor.visible = visible;
if (visible) { if (visible) {
let alignment = 0.25; let alignment = 0.25;
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
alignment = 1.0 - alignment; alignment = 1.0 - alignment;
this.menu._arrowAlignment = alignment; this.menu._arrowAlignment = alignment;
this._eventList.actor.get_parent().show();
} else { } else {
this.menu._arrowAlignment = 0.5; this.menu._arrowAlignment = 0.5;
this._eventList.actor.get_parent().hide();
} }
}, },
_setEventSource: function(eventSource) { _setEventSource: function(eventSource) {
if (this._eventSource)
this._eventSource.destroy();
this._calendar.setEventSource(eventSource); this._calendar.setEventSource(eventSource);
this._eventList.setEventSource(eventSource); this._eventList.setEventSource(eventSource);
this._eventSource = eventSource;
this._eventSource.connect('notify::has-calendars', Lang.bind(this, function() {
this._updateEventsVisibility();
}));
}, },
_sessionUpdated: function() { _sessionUpdated: function() {
@@ -184,10 +187,10 @@ const DateMenuButton = new Lang.Class({
if (showEvents) { if (showEvents) {
eventSource = new Calendar.DBusEventSource(); eventSource = new Calendar.DBusEventSource();
} else { } else {
eventSource = new Calendar.EmptyEventSource(); eventSource = null;
} }
this._setEventSource(eventSource); this._setEventSource(eventSource);
this._updateEventsVisibility(); this._setEventsVisibility(showEvents);
// This needs to be handled manually, as the code to // This needs to be handled manually, as the code to
// autohide separators doesn't work across the vbox // autohide separators doesn't work across the vbox
@@ -204,34 +207,16 @@ const DateMenuButton = new Lang.Class({
this._date.set_text(displayDate.toLocaleFormat(dateFormat)); this._date.set_text(displayDate.toLocaleFormat(dateFormat));
}, },
_getCalendarApp: function() {
if (this._calendarApp !== undefined)
return this._calendarApp;
let apps = Gio.AppInfo.get_recommended_for_type('text/calendar');
if (apps && (apps.length > 0))
this._calendarApp = apps[0];
else
this._calendarApp = null;
return this._calendarApp;
},
_getClockApp: function() {
return Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
},
_onOpenCalendarActivate: function() { _onOpenCalendarActivate: function() {
this.menu.close(); this.menu.close();
let app = this._getCalendarApp(); let app = Gio.AppInfo.get_default_for_type('text/calendar', false);
if (app.get_id() == 'evolution.desktop')
app = Gio.DesktopAppInfo.new('evolution-calendar.desktop');
app.launch([], global.create_app_launch_context()); app.launch([], global.create_app_launch_context());
}, },
_onOpenClocksActivate: function() { _onOpenClocksActivate: function() {
this.menu.close(); this.menu.close();
let app = this._getClockApp(); let app = Shell.AppSystem.get_default().lookup_app('gnome-clocks.desktop');
app.activate(); app.activate();
} }
}); });

View File

@@ -1,7 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const St = imports.gi.St; const St = imports.gi.St;
const Lang = imports.lang; const Lang = imports.lang;
@@ -44,7 +43,9 @@ let dragMonitors = [];
function _getEventHandlerActor() { function _getEventHandlerActor() {
if (!eventHandlerActor) { if (!eventHandlerActor) {
eventHandlerActor = new Clutter.Actor({ width: 0, height: 0 }); eventHandlerActor = new Clutter.Rectangle();
eventHandlerActor.width = 0;
eventHandlerActor.height = 0;
Main.uiGroup.add_actor(eventHandlerActor); Main.uiGroup.add_actor(eventHandlerActor);
// We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen // We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen
// when you've grabbed the pointer. // when you've grabbed the pointer.
@@ -84,13 +85,11 @@ const _Draggable = new Lang.Class({
this.actor.connect('destroy', Lang.bind(this, function() { this.actor.connect('destroy', Lang.bind(this, function() {
this._actorDestroyed = true; this._actorDestroyed = true;
// If the drag actor is destroyed and we were going to fix // If the drag actor is destroyed and we were going to fix
// up its hover state, fix up the parent hover state instead // up its hover state, fix up the parent hover state instead
if (this.actor == this._firstLeaveActor) if (this.actor == this._firstLeaveActor)
this._firstLeaveActor = this._dragOrigParent; this._firstLeaveActor = this._dragOrigParent;
if (this._dragInProgress)
if (this._dragInProgress && this._dragCancellable)
this._cancelDrag(global.get_current_time()); this._cancelDrag(global.get_current_time());
this.disconnectAll(); this.disconnectAll();
})); }));
@@ -103,7 +102,6 @@ const _Draggable = new Lang.Class({
this._buttonDown = false; // The mouse button has been pressed and has not yet been released. 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._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._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. // 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 // But we remember the actors that we first left/last entered so we can
@@ -148,16 +146,16 @@ const _Draggable = new Lang.Class({
_grabEvents: function() { _grabEvents: function() {
if (!this._eventsGrabbed) { if (!this._eventsGrabbed) {
this._eventsGrabbed = Main.pushModal(_getEventHandlerActor()); Clutter.grab_pointer(_getEventHandlerActor());
if (this._eventsGrabbed) Clutter.grab_keyboard(_getEventHandlerActor());
Clutter.grab_pointer(_getEventHandlerActor()); this._eventsGrabbed = true;
} }
}, },
_ungrabEvents: function() { _ungrabEvents: function() {
if (this._eventsGrabbed) { if (this._eventsGrabbed) {
Clutter.ungrab_pointer(); Clutter.ungrab_pointer();
Main.popModal(_getEventHandlerActor()); Clutter.ungrab_keyboard();
this._eventsGrabbed = false; this._eventsGrabbed = false;
} }
}, },
@@ -290,19 +288,19 @@ const _Draggable = new Lang.Class({
this._dragOrigY = this._dragActor.y; this._dragOrigY = this._dragActor.y;
this._dragOrigScale = this._dragActor.scale_x; this._dragOrigScale = this._dragActor.scale_x;
// Set the actor's scale such that it will keep the same this._dragActor.reparent(Main.uiGroup);
// transformed size when it's reparented to the uiGroup this._dragActor.raise_top();
let [scaledWidth, scaledHeight] = this.actor.get_transformed_size(); Shell.util_set_hidden_from_pick(this._dragActor, true);
this._dragActor.set_scale(scaledWidth / this.actor.width,
scaledHeight / this.actor.height);
let [actorStageX, actorStageY] = this.actor.get_transformed_position(); let [actorStageX, actorStageY] = this.actor.get_transformed_position();
this._dragOffsetX = actorStageX - this._dragStartX; this._dragOffsetX = actorStageX - this._dragStartX;
this._dragOffsetY = actorStageY - this._dragStartY; this._dragOffsetY = actorStageY - this._dragStartY;
this._dragActor.reparent(Main.uiGroup); // Set the actor's scale such that it will keep the same
this._dragActor.raise_top(); // transformed size when it's reparented to the uiGroup
Shell.util_set_hidden_from_pick(this._dragActor, true); let [scaledWidth, scaledHeight] = this.actor.get_transformed_size();
this.actor.set_scale(scaledWidth / this.actor.width,
scaledHeight / this.actor.height);
} }
this._dragOrigOpacity = this._dragActor.opacity; this._dragOrigOpacity = this._dragActor.opacity;
@@ -359,65 +357,60 @@ const _Draggable = new Lang.Class({
return true; return true;
}, },
_updateDragHover : function () {
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
this._dragX, this._dragY);
let dragEvent = {
x: this._dragX,
y: this._dragY,
dragActor: this._dragActor,
source: this.actor._delegate,
targetActor: target
};
for (let i = 0; i < dragMonitors.length; i++) {
let motionFunc = dragMonitors[i].dragMotion;
if (motionFunc) {
let result = motionFunc(dragEvent);
if (result != DragMotionResult.CONTINUE) {
global.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.set_cursor(DRAG_CURSOR_MAP[result]);
return false;
}
}
target = target.get_parent();
}
global.set_cursor(Shell.Cursor.DND_IN_DRAG);
return false;
},
_queueUpdateDragHover: function() {
if (this._updateHoverId)
GLib.source_remove(this._updateHoverId);
this._updateHoverId = GLib.idle_add(GLib.PRIORITY_DEFAULT,
Lang.bind(this, this._updateDragHover));
},
_updateDragPosition : function (event) { _updateDragPosition : function (event) {
let [stageX, stageY] = event.get_coords(); let [stageX, stageY] = event.get_coords();
this._dragX = stageX; this._dragX = stageX;
this._dragY = stageY; this._dragY = stageY;
this._dragActor.set_position(stageX + this._dragOffsetX,
stageY + this._dragOffsetY);
this._queueUpdateDragHover(); // If we are dragging, update the position
if (this._dragActor) {
this._dragActor.set_position(stageX + this._dragOffsetX,
stageY + this._dragOffsetY);
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
stageX, stageY);
// We call observers only once per motion with the innermost
// target actor. If necessary, the observer can walk the
// parent itself.
let dragEvent = {
x: stageX,
y: stageY,
dragActor: this._dragActor,
source: this.actor._delegate,
targetActor: target
};
for (let i = 0; i < dragMonitors.length; i++) {
let motionFunc = dragMonitors[i].dragMotion;
if (motionFunc) {
let result = motionFunc(dragEvent);
if (result != DragMotionResult.CONTINUE) {
global.set_cursor(DRAG_CURSOR_MAP[result]);
return true;
}
}
}
while (target) {
if (target._delegate && target._delegate.handleDragOver) {
let [r, targX, targY] = target.transform_stage_point(stageX, stageY);
// We currently loop through all parents on drag-over even if one of the children has handled it.
// We can check the return value of the function and break the loop if it's true if we don't want
// to continue checking the parents.
let result = target._delegate.handleDragOver(this.actor._delegate,
this._dragActor,
targX,
targY,
event.get_time());
if (result != DragMotionResult.CONTINUE) {
global.set_cursor(DRAG_CURSOR_MAP[result]);
return true;
}
}
target = target.get_parent();
}
global.set_cursor(Shell.Cursor.DND_IN_DRAG);
}
return true; return true;
}, },
@@ -446,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) { while (target) {
if (target._delegate && target._delegate.acceptDrop) { if (target._delegate && target._delegate.acceptDrop) {
let [r, targX, targY] = target.transform_stage_point(dropX, dropY); let [r, targX, targY] = target.transform_stage_point(dropX, dropY);
@@ -459,6 +447,8 @@ const _Draggable = new Lang.Class({
targX, targX,
targY, targY,
event.get_time())) { event.get_time())) {
if (this._actorDestroyed)
return true;
// If it accepted the drop without taking the actor, // If it accepted the drop without taking the actor,
// handle it ourselves. // handle it ourselves.
if (this._dragActor.get_parent() == Main.uiGroup) { if (this._dragActor.get_parent() == Main.uiGroup) {
@@ -517,11 +507,6 @@ const _Draggable = new Lang.Class({
}, },
_cancelDrag: function(eventTime) { _cancelDrag: function(eventTime) {
if (this._updateHoverId) {
GLib.source_remove(this._updateHoverId);
this._updateHoverId = 0;
}
this.emit('drag-cancelled', eventTime); this.emit('drag-cancelled', eventTime);
this._dragInProgress = false; this._dragInProgress = false;
let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation(); let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();

View File

@@ -35,7 +35,7 @@ const GnomeSession = imports.misc.gnomeSession;
const Main = imports.ui.main; const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog; const ModalDialog = imports.ui.modalDialog;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const UserWidget = imports.ui.userWidget; const UserMenu = imports.ui.userMenu;
let _endSessionDialog = null; let _endSessionDialog = null;
@@ -225,8 +225,7 @@ const EndSessionDialog = new Lang.Class({
Extends: ModalDialog.ModalDialog, Extends: ModalDialog.ModalDialog,
_init: function() { _init: function() {
this.parent({ styleClass: 'end-session-dialog', this.parent({ styleClass: 'end-session-dialog' });
destroyOnClose: false });
this._user = AccountsService.UserManager.get_default().get_user(GLib.get_user_name()); this._user = AccountsService.UserManager.get_default().get_user(GLib.get_user_name());
@@ -360,9 +359,9 @@ const EndSessionDialog = new Lang.Class({
icon_size: _DIALOG_ICON_SIZE, icon_size: _DIALOG_ICON_SIZE,
style_class: dialogContent.iconStyleClass }); style_class: dialogContent.iconStyleClass });
} else { } else {
let avatarWidget = new UserWidget.Avatar(this._user, let avatarWidget = new UserMenu.UserAvatarWidget(this._user,
{ iconSize: _DIALOG_ICON_SIZE, { iconSize: _DIALOG_ICON_SIZE,
styleClass: dialogContent.iconStyleClass }); styleClass: dialogContent.iconStyleClass });
this._iconBin.child = avatarWidget.actor; this._iconBin.child = avatarWidget.actor;
avatarWidget.update(); avatarWidget.update();
} }
@@ -420,7 +419,6 @@ const EndSessionDialog = new Lang.Class({
_startTimer: function() { _startTimer: function() {
let startTime = GLib.get_monotonic_time(); let startTime = GLib.get_monotonic_time();
this._secondsLeft = this._totalSecondsToStayOpen; this._secondsLeft = this._totalSecondsToStayOpen;
this._updateDescription();
this._timerId = Mainloop.timeout_add_seconds(1, Lang.bind(this, this._timerId = Mainloop.timeout_add_seconds(1, Lang.bind(this,
function() { function() {

View File

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

View File

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

View File

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

View File

@@ -204,10 +204,10 @@ const IconGrid = new Lang.Class({
// later we'll allocate as many children as fit the parent // later we'll allocate as many children as fit the parent
return; return;
let nChildren = this._grid.get_n_children(); let children = this._grid.get_children();
let nColumns = this._colLimit ? Math.min(this._colLimit, let nColumns = this._colLimit ? Math.min(this._colLimit,
nChildren) children.length)
: nChildren; : children.length;
let totalSpacing = Math.max(0, nColumns - 1) * this._spacing; let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
// Kind of a lie, but not really an issue right now. If // Kind of a lie, but not really an issue right now. If
// we wanted to support some sort of hidden/overflow that would // we wanted to support some sort of hidden/overflow that would

File diff suppressed because it is too large Load Diff

View File

@@ -42,11 +42,15 @@ const Lightbox = new Lang.Class({
params = Params.parse(params, { inhibitEvents: false, params = Params.parse(params, { inhibitEvents: false,
width: null, width: null,
height: null, height: null,
fadeFactor: DEFAULT_FADE_FACTOR, fadeInTime: null,
fadeOutTime: null,
fadeFactor: DEFAULT_FADE_FACTOR
}); });
this._container = container; this._container = container;
this._children = container.get_children(); this._children = container.get_children();
this._fadeInTime = params.fadeInTime;
this._fadeOutTime = params.fadeOutTime;
this._fadeFactor = params.fadeFactor; this._fadeFactor = params.fadeFactor;
this.actor = new St.Bin({ x: 0, this.actor = new St.Bin({ x: 0,
y: 0, y: 0,
@@ -97,16 +101,14 @@ const Lightbox = new Lang.Class({
} }
}, },
show: function(fadeInTime) { show: function() {
fadeInTime = fadeInTime || 0;
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);
if (fadeInTime != 0) { if (this._fadeInTime) {
this.shown = false; this.shown = false;
this.actor.opacity = 0; this.actor.opacity = 0;
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 255 * this._fadeFactor, { opacity: 255 * this._fadeFactor,
time: fadeInTime, time: this._fadeInTime,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, function() {
this.shown = true; this.shown = true;
@@ -121,15 +123,13 @@ const Lightbox = new Lang.Class({
this.actor.show(); this.actor.show();
}, },
hide: function(fadeOutTime) { hide: function() {
fadeOutTime = fadeOutTime || 0;
this.shown = false; this.shown = false;
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);
if (fadeOutTime != 0) { if (this._fadeOutTime) {
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 0, { opacity: 0,
time: fadeOutTime, time: this._fadeOutTime,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() { onComplete: Lang.bind(this, function() {
this.actor.hide(); this.actor.hide();

View File

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

View File

@@ -7,7 +7,6 @@ const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Signals = imports.signals; const Signals = imports.signals;
const Main = imports.ui.main; const Main = imports.ui.main;
@@ -54,9 +53,9 @@ const Magnifier = new Lang.Class({
this._zoomRegions = []; this._zoomRegions = [];
// Create small clutter tree for the magnified mouse. // Create small clutter tree for the magnified mouse.
let cursorTracker = Meta.CursorTracker.get_for_screen(global.screen); let xfixesCursor = Shell.XFixesCursor.get_for_stage(global.stage);
this._mouseSprite = new Clutter.Texture(); this._mouseSprite = new Clutter.Texture();
Shell.util_cursor_tracker_to_clutter(cursorTracker, this._mouseSprite); xfixesCursor.update_texture_image(this._mouseSprite);
this._cursorRoot = new Clutter.Actor(); this._cursorRoot = new Clutter.Actor();
this._cursorRoot.add_actor(this._mouseSprite); this._cursorRoot.add_actor(this._mouseSprite);
@@ -71,8 +70,8 @@ const Magnifier = new Lang.Class({
let showAtLaunch = this._settingsInit(aZoomRegion); let showAtLaunch = this._settingsInit(aZoomRegion);
aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse); aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse);
cursorTracker.connect('cursor-changed', Lang.bind(this, this._updateMouseSprite)); xfixesCursor.connect('cursor-change', Lang.bind(this, this._updateMouseSprite));
this._cursorTracker = cursorTracker; this._xfixesCursor = xfixesCursor;
// Export to dbus. // Export to dbus.
magDBusService = new MagnifierDBus.ShellMagnifier(); magDBusService = new MagnifierDBus.ShellMagnifier();
@@ -84,7 +83,7 @@ const Magnifier = new Lang.Class({
* Show the system mouse pointer. * Show the system mouse pointer.
*/ */
showSystemCursor: function() { showSystemCursor: function() {
global.stage.show_cursor(); this._xfixesCursor.show();
}, },
/** /**
@@ -92,7 +91,7 @@ const Magnifier = new Lang.Class({
* Hide the system mouse pointer. * Hide the system mouse pointer.
*/ */
hideSystemCursor: function() { hideSystemCursor: function() {
global.stage.hide_cursor(); this._xfixesCursor.hide();
}, },
/** /**
@@ -113,7 +112,7 @@ const Magnifier = new Lang.Class({
// Make sure system mouse pointer is shown when all zoom regions are // Make sure system mouse pointer is shown when all zoom regions are
// invisible. // invisible.
if (!activate) if (!activate)
global.stage.show_cursor(); this._xfixesCursor.show();
// Notify interested parties of this change // Notify interested parties of this change
this.emit('active-changed', activate); this.emit('active-changed', activate);
@@ -423,8 +422,9 @@ const Magnifier = new Lang.Class({
//// Private methods //// //// Private methods ////
_updateMouseSprite: function() { _updateMouseSprite: function() {
Shell.util_cursor_tracker_to_clutter(this._cursorTracker, this._mouseSprite); this._xfixesCursor.update_texture_image(this._mouseSprite);
let [xHot, yHot] = this._cursorTracker.get_hot(); let xHot = this._xfixesCursor.get_hot_x();
let yHot = this._xfixesCursor.get_hot_y();
this._mouseSprite.set_anchor_point(xHot, yHot); this._mouseSprite.set_anchor_point(xHot, yHot);
}, },

View File

@@ -24,26 +24,23 @@ const Panel = imports.ui.panel;
const Params = imports.misc.params; const Params = imports.misc.params;
const RunDialog = imports.ui.runDialog; const RunDialog = imports.ui.runDialog;
const Layout = imports.ui.layout; const Layout = imports.ui.layout;
const LoginManager = imports.misc.loginManager;
const LookingGlass = imports.ui.lookingGlass; const LookingGlass = imports.ui.lookingGlass;
const NotificationDaemon = imports.ui.notificationDaemon; const NotificationDaemon = imports.ui.notificationDaemon;
const WindowAttentionHandler = imports.ui.windowAttentionHandler; const WindowAttentionHandler = imports.ui.windowAttentionHandler;
const Screencast = imports.ui.screencast;
const ScreenShield = imports.ui.screenShield; const ScreenShield = imports.ui.screenShield;
const Scripting = imports.ui.scripting; const Scripting = imports.ui.scripting;
const SessionMode = imports.ui.sessionMode; const SessionMode = imports.ui.sessionMode;
const ShellDBus = imports.ui.shellDBus; const ShellDBus = imports.ui.shellDBus;
const ShellMountOperation = imports.ui.shellMountOperation; const ShellMountOperation = imports.ui.shellMountOperation;
const UnlockDialog = imports.ui.unlockDialog;
const WindowManager = imports.ui.windowManager; const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier; const Magnifier = imports.ui.magnifier;
const XdndHandler = imports.ui.xdndHandler; const XdndHandler = imports.ui.xdndHandler;
const Util = imports.misc.util; const Util = imports.misc.util;
const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides';
const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff); const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
const STICKY_KEYS_ENABLE = 'stickykeys-enable';
let componentManager = null; let componentManager = null;
let panel = null; let panel = null;
let overview = null; let overview = null;
@@ -60,9 +57,8 @@ let sessionMode = null;
let shellDBusService = null; let shellDBusService = null;
let shellMountOpDBusService = null; let shellMountOpDBusService = null;
let screenSaverDBus = null; let screenSaverDBus = null;
let screencastService = null;
let modalCount = 0; let modalCount = 0;
let keybindingMode = Shell.KeyBindingMode.NONE; let keybindingMode = Shell.KeyBindingMode.NORMAL;
let modalActorFocusStack = []; let modalActorFocusStack = [];
let uiGroup = null; let uiGroup = null;
let magnifier = null; let magnifier = null;
@@ -72,12 +68,9 @@ let layoutManager = null;
let _startDate; let _startDate;
let _defaultCssStylesheet = null; let _defaultCssStylesheet = null;
let _cssStylesheet = null; let _cssStylesheet = null;
let _a11ySettings = null; let _overridesSettings = null;
let dynamicWorkspacesSchema = null;
function _sessionUpdated() { function _sessionUpdated() {
_loadDefaultStylesheet();
wm.setCustomKeybindingHandler('panel-main-menu', wm.setCustomKeybindingHandler('panel-main-menu',
Shell.KeyBindingMode.NORMAL | Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW, Shell.KeyBindingMode.OVERVIEW,
@@ -89,9 +82,8 @@ function _sessionUpdated() {
Shell.KeyBindingMode.NORMAL | Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW, Shell.KeyBindingMode.OVERVIEW,
sessionMode.hasRunDialog ? openRunDialog : null); sessionMode.hasRunDialog ? openRunDialog : null);
if (sessionMode.isGreeter)
if (!sessionMode.hasRunDialog && lookingGlass) screenShield.showDialog();
lookingGlass.close();
} }
function start() { function start() {
@@ -99,39 +91,29 @@ function start() {
global.logError = window.log; global.logError = window.log;
global.log = window.log; global.log = window.log;
// Hide the stage until we're ready for it
global.stage.hide();
// Chain up async errors reported from C // Chain up async errors reported from C
global.connect('notify-error', function (global, msg, detail) { notifyError(msg, detail); }); global.connect('notify-error', function (global, msg, detail) { notifyError(msg, detail); });
Gio.DesktopAppInfo.set_desktop_env('GNOME'); Gio.DesktopAppInfo.set_desktop_env('GNOME');
sessionMode = new SessionMode.SessionMode(); 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() { function startSession() {
sessionMode.connect('updated', _sessionUpdated); sessionMode.connect('updated', _loadDefaultStylesheet);
_initializePrefs();
_initializeUI();
shellDBusService = new ShellDBus.GnomeShell(); shellDBusService = new ShellDBus.GnomeShell();
shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler(); 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 // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
// also initialize ShellAppSystem first. ShellAppSystem // also initialize ShellAppSystem first. ShellAppSystem
// needs to load all the .desktop files, and ShellWindowTracker // needs to load all the .desktop files, and ShellWindowTracker
@@ -140,9 +122,11 @@ function _initializeUI() {
// and recalculate application associations, so to avoid // and recalculate application associations, so to avoid
// races for now we initialize it here. It's better to // races for now we initialize it here. It's better to
// be predictable anyways. // be predictable anyways.
Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
Shell.AppUsage.get_default(); Shell.AppUsage.get_default();
tracker.connect('startup-sequence-changed', _queueCheckWorkspaces);
_loadDefaultStylesheet(); _loadDefaultStylesheet();
// Setup the stage hierarchy early // Setup the stage hierarchy early
@@ -153,16 +137,19 @@ function _initializeUI() {
// working until it's updated. // working until it's updated.
uiGroup = layoutManager.uiGroup; uiGroup = layoutManager.uiGroup;
screencastService = new Screencast.ScreencastService();
xdndHandler = new XdndHandler.XdndHandler(); xdndHandler = new XdndHandler.XdndHandler();
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
osdWindow = new OsdWindow.OsdWindow(); osdWindow = new OsdWindow.OsdWindow();
overview = new Overview.Overview(); overview = new Overview.Overview();
wm = new WindowManager.WindowManager(); wm = new WindowManager.WindowManager();
magnifier = new Magnifier.Magnifier(); magnifier = new Magnifier.Magnifier();
if (LoginManager.canLock()) if (UnlockDialog.isSupported())
screenShield = new ScreenShield.ScreenShield(); screenShield = new ScreenShield.ScreenShield();
else
screenShield = new ScreenShield.ScreenShieldFallback();
// The message tray relies on being constructed
// after the panel.
panel = new Panel.Panel(); panel = new Panel.Panel();
messageTray = new MessageTray.MessageTray(); messageTray = new MessageTray.MessageTray();
keyboard = new Keyboard.Keyboard(); keyboard = new Keyboard.Keyboard();
@@ -171,14 +158,14 @@ function _initializeUI() {
componentManager = new Components.ComponentManager(); componentManager = new Components.ComponentManager();
layoutManager.init(); layoutManager.init();
layoutManager.prepareStartupAnimation();
overview.init(); overview.init();
_a11ySettings = new Gio.Settings({ schema: A11Y_SCHEMA }); global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT,
false, -1, 1);
global.display.connect('overlay-key', Lang.bind(overview, function () { global.display.connect('overlay-key', Lang.bind(overview, overview.toggle));
if (!_a11ySettings.get_boolean (STICKY_KEYS_ENABLE)) sessionMode.connect('updated', _sessionUpdated);
overview.toggle(); _sessionUpdated();
}));
// Provide the bus object for gnome-session to // Provide the bus object for gnome-session to
// initiate logouts. // initiate logouts.
@@ -198,23 +185,204 @@ function _initializeUI() {
Scripting.runPerfScript(module, perfOutput); Scripting.runPerfScript(module, perfOutput);
} }
_overridesSettings = new Gio.Settings({ schema: OVERRIDES_SCHEMA });
_overridesSettings.connect('changed::dynamic-workspaces', _queueCheckWorkspaces);
global.screen.connect('notify::n-workspaces', _nWorkspacesChanged);
global.screen.connect('window-entered-monitor', _windowEnteredMonitor);
global.screen.connect('window-left-monitor', _windowLeftMonitor);
global.screen.connect('restacked', _windowsRestacked);
_nWorkspacesChanged();
ExtensionDownloader.init(); ExtensionDownloader.init();
ExtensionSystem.init(); ExtensionSystem.init();
if (sessionMode.isGreeter && screenShield) { layoutManager.connect('startup-prepared', function() {
layoutManager.connect('startup-prepared', function() { layoutManager.startupAnimation();
screenShield.showDialog(); });
}); }
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() { for (i = 0; i < _workspaces.length; i++) {
if (keybindingMode == Shell.KeyBindingMode.NONE) { let lastRemoved = _workspaces[i]._lastRemovedWindow;
keybindingMode = Shell.KeyBindingMode.NORMAL; if ((lastRemoved &&
} (lastRemoved.get_window_type() == Meta.WindowType.SPLASHSCREEN ||
if (screenShield) { lastRemoved.get_window_type() == Meta.WindowType.DIALOG ||
screenShield.lockIfWasLocked(); lastRemoved.get_window_type() == Meta.WindowType.MODAL_DIALOG)) ||
} _workspaces[i]._keepAliveId)
}); emptyWorkspaces[i] = false;
else
emptyWorkspaces[i] = true;
}
let sequences = Shell.WindowTracker.get_default().get_startup_sequences();
for (i = 0; i < sequences.length; i++) {
let index = sequences[i].get_workspace();
if (index >= 0 && index <= global.screen.n_workspaces)
emptyWorkspaces[index] = false;
}
let windows = global.get_window_actors();
for (i = 0; i < windows.length; i++) {
let win = windows[i];
if (win.get_meta_window().is_on_all_workspaces())
continue;
let workspaceIndex = win.get_workspace();
emptyWorkspaces[workspaceIndex] = false;
}
// If we don't have an empty workspace at the end, add one
if (!emptyWorkspaces[emptyWorkspaces.length -1]) {
global.screen.append_new_workspace(false, global.get_current_time());
emptyWorkspaces.push(false);
}
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let removingCurrentWorkspace = (emptyWorkspaces[activeWorkspaceIndex] &&
activeWorkspaceIndex < emptyWorkspaces.length - 1);
// Don't enter the overview when removing multiple empty workspaces at startup
let showOverview = (removingCurrentWorkspace &&
!emptyWorkspaces.every(function(x) { return x; }));
if (removingCurrentWorkspace) {
// "Merge" the empty workspace we are removing with the one at the end
wm.blockAnimations();
}
// Delete other empty workspaces; do it from the end to avoid index changes
for (i = emptyWorkspaces.length - 2; i >= 0; i--) {
if (emptyWorkspaces[i])
global.screen.remove_workspace(_workspaces[i], global.get_current_time());
}
if (removingCurrentWorkspace) {
global.screen.get_workspace_by_index(global.screen.n_workspaces - 1).activate(global.get_current_time());
wm.unblockAnimations();
if (!overview.visible && showOverview)
overview.show();
}
_checkWorkspacesId = 0;
return false;
}
function keepWorkspaceAlive(workspace, duration) {
if (workspace._keepAliveId)
Mainloop.source_remove(workspace._keepAliveId);
workspace._keepAliveId = Mainloop.timeout_add(duration, function() {
workspace._keepAliveId = 0;
_queueCheckWorkspaces();
return false;
});
}
function _windowRemoved(workspace, window) {
workspace._lastRemovedWindow = window;
_queueCheckWorkspaces();
Mainloop.timeout_add(LAST_WINDOW_GRACE_TIME, function() {
if (workspace._lastRemovedWindow == window) {
workspace._lastRemovedWindow = null;
_queueCheckWorkspaces();
}
return false;
});
}
function _windowLeftMonitor(metaScreen, monitorIndex, metaWin) {
// If the window left the primary monitor, that
// might make that workspace empty
if (monitorIndex == layoutManager.primaryIndex)
_queueCheckWorkspaces();
}
function _windowEnteredMonitor(metaScreen, monitorIndex, metaWin) {
// If the window entered the primary monitor, that
// might make that workspace non-empty
if (monitorIndex == layoutManager.primaryIndex)
_queueCheckWorkspaces();
}
function _windowsRestacked() {
// Figure out where the pointer is in case we lost track of
// it during a grab. (In particular, if a trayicon popup menu
// is dismissed, see if we need to close the message tray.)
global.sync_pointer();
}
function _queueCheckWorkspaces() {
if (_checkWorkspacesId == 0)
_checkWorkspacesId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, _checkWorkspaces);
}
function _nWorkspacesChanged() {
let oldNumWorkspaces = _workspaces.length;
let newNumWorkspaces = global.screen.n_workspaces;
if (oldNumWorkspaces == newNumWorkspaces)
return false;
let lostWorkspaces = [];
if (newNumWorkspaces > oldNumWorkspaces) {
let w;
// Assume workspaces are only added at the end
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++)
_workspaces[w] = global.screen.get_workspace_by_index(w);
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
let workspace = _workspaces[w];
workspace._windowAddedId = workspace.connect('window-added', _queueCheckWorkspaces);
workspace._windowRemovedId = workspace.connect('window-removed', _windowRemoved);
}
} else {
// Assume workspaces are only removed sequentially
// (e.g. 2,3,4 - not 2,4,7)
let removedIndex;
let removedNum = oldNumWorkspaces - newNumWorkspaces;
for (let w = 0; w < oldNumWorkspaces; w++) {
let workspace = global.screen.get_workspace_by_index(w);
if (_workspaces[w] != workspace) {
removedIndex = w;
break;
}
}
let lostWorkspaces = _workspaces.splice(removedIndex, removedNum);
lostWorkspaces.forEach(function(workspace) {
workspace.disconnect(workspace._windowAddedId);
workspace.disconnect(workspace._windowRemovedId);
});
}
_queueCheckWorkspaces();
return false;
} }
function _loadDefaultStylesheet() { function _loadDefaultStylesheet() {
@@ -263,8 +431,11 @@ function loadTheme() {
let themeContext = St.ThemeContext.get_for_stage (global.stage); let themeContext = St.ThemeContext.get_for_stage (global.stage);
let previousTheme = themeContext.get_theme(); let previousTheme = themeContext.get_theme();
let theme = new St.Theme ({ application_stylesheet: _cssStylesheet, let cssStylesheet = _defaultCssStylesheet;
default_stylesheet: _defaultCssStylesheet }); if (_cssStylesheet != null)
cssStylesheet = _cssStylesheet;
let theme = new St.Theme ({ application_stylesheet: cssStylesheet });
if (previousTheme) { if (previousTheme) {
let customStylesheets = previousTheme.get_custom_stylesheets(); let customStylesheets = previousTheme.get_custom_stylesheets();
@@ -355,6 +526,8 @@ function pushModal(actor, params) {
Meta.disable_unredirect_for_screen(global.screen); Meta.disable_unredirect_for_screen(global.screen);
} }
global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN);
modalCount += 1; modalCount += 1;
let actorDestroyId = actor.connect('destroy', function() { let actorDestroyId = actor.connect('destroy', function() {
let index = _findModal(actor); let index = _findModal(actor);
@@ -403,6 +576,7 @@ function popModal(actor, timestamp) {
if (focusIndex < 0) { if (focusIndex < 0) {
global.stage.set_key_focus(null); global.stage.set_key_focus(null);
global.end_modal(timestamp); global.end_modal(timestamp);
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
keybindingMode = Shell.KeyBindingMode.NORMAL; keybindingMode = Shell.KeyBindingMode.NORMAL;
throw new Error('incorrect pop'); throw new Error('incorrect pop');
@@ -450,6 +624,7 @@ function popModal(actor, timestamp) {
return; return;
global.end_modal(timestamp); global.end_modal(timestamp);
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
Meta.enable_unredirect_for_screen(global.screen); Meta.enable_unredirect_for_screen(global.screen);
keybindingMode = Shell.KeyBindingMode.NORMAL; keybindingMode = Shell.KeyBindingMode.NORMAL;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,6 @@ const Atk = imports.gi.Atk;
const Params = imports.misc.params; const Params = imports.misc.params;
const Animation = imports.ui.animation;
const Layout = imports.ui.layout; const Layout = imports.ui.layout;
const Lightbox = imports.ui.lightbox; const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main; const Main = imports.ui.main;
@@ -23,10 +22,6 @@ const Tweener = imports.ui.tweener;
const OPEN_AND_CLOSE_TIME = 0.1; const OPEN_AND_CLOSE_TIME = 0.1;
const FADE_OUT_DIALOG_TIME = 1.0; const FADE_OUT_DIALOG_TIME = 1.0;
const WORK_SPINNER_ICON_SIZE = 24;
const WORK_SPINNER_ANIMATION_DELAY = 1.0;
const WORK_SPINNER_ANIMATION_TIME = 0.3;
const State = { const State = {
OPENED: 0, OPENED: 0,
CLOSED: 1, CLOSED: 1,
@@ -43,15 +38,13 @@ const ModalDialog = new Lang.Class({
styleClass: null, styleClass: null,
parentActor: Main.uiGroup, parentActor: Main.uiGroup,
keybindingMode: Shell.KeyBindingMode.SYSTEM_MODAL, keybindingMode: Shell.KeyBindingMode.SYSTEM_MODAL,
shouldFadeIn: true, shouldFadeIn: true });
destroyOnClose: true });
this.state = State.CLOSED; this.state = State.CLOSED;
this._hasModal = false; this._hasModal = false;
this._keybindingMode = params.keybindingMode; this._keybindingMode = params.keybindingMode;
this._shellReactive = params.shellReactive; this._shellReactive = params.shellReactive;
this._shouldFadeIn = params.shouldFadeIn; this._shouldFadeIn = params.shouldFadeIn;
this._destroyOnClose = params.destroyOnClose;
this._group = new St.Widget({ visible: false, this._group = new St.Widget({ visible: false,
x: 0, 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-press-event', Lang.bind(this, this._onKeyPressEvent));
this._group.connect('key-release-event', Lang.bind(this, this._onKeyReleaseEvent)); this._group.connect('key-release-event', Lang.bind(this, this._onKeyReleaseEvent));
this.backgroundStack = new St.Widget({ layout_manager: new Clutter.BinLayout() }); this._backgroundBin = new St.Bin();
this._backgroundBin = new St.Bin({ child: this.backgroundStack,
x_fill: true, y_fill: true });
this._monitorConstraint = new Layout.MonitorConstraint(); this._monitorConstraint = new Layout.MonitorConstraint();
this._backgroundBin.add_constraint(this._monitorConstraint); this._backgroundBin.add_constraint(this._monitorConstraint);
this._group.add_actor(this._backgroundBin); this._group.add_actor(this._backgroundBin);
this.dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog', this.dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog',
vertical: true }); vertical: true });
// modal dialogs are fixed width and grow vertically; set the request if (params.styleClass != null) {
// mode accordingly so wrapped labels are handled correctly during
// size requests.
this.dialogLayout.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
if (params.styleClass != null)
this.dialogLayout.add_style_class_name(params.styleClass); this.dialogLayout.add_style_class_name(params.styleClass);
}
if (!this._shellReactive) { if (!this._shellReactive) {
this._lightbox = new Lightbox.Lightbox(this._group, this._lightbox = new Lightbox.Lightbox(this._group,
{ inhibitEvents: true }); { inhibitEvents: true });
this._lightbox.highlight(this._backgroundBin); this._lightbox.highlight(this._backgroundBin);
let stack = new Shell.Stack();
this._backgroundBin.child = stack;
this._eventBlocker = new Clutter.Actor({ reactive: true }); this._eventBlocker = new Clutter.Actor({ reactive: true });
this.backgroundStack.add_actor(this._eventBlocker); stack.add_actor(this._eventBlocker);
stack.add_actor(this.dialogLayout);
} else {
this._backgroundBin.child = this.dialogLayout;
} }
this.backgroundStack.add_actor(this.dialogLayout);
this.contentLayout = new St.BoxLayout({ vertical: true }); this.contentLayout = new St.BoxLayout({ vertical: true });
this.dialogLayout.add(this.contentLayout, this.dialogLayout.add(this.contentLayout,
{ expand: true, { x_fill: true,
x_fill: true,
y_fill: true, y_fill: true,
x_align: St.Align.MIDDLE, x_align: St.Align.MIDDLE,
y_align: St.Align.START }); y_align: St.Align.START });
@@ -109,15 +100,14 @@ const ModalDialog = new Lang.Class({
this.buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box', this.buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box',
vertical: false }); vertical: false });
this.dialogLayout.add(this.buttonLayout, this.dialogLayout.add(this.buttonLayout,
{ x_align: St.Align.MIDDLE, { expand: true,
x_align: St.Align.MIDDLE,
y_align: St.Align.END }); y_align: St.Align.END });
global.focus_manager.add_group(this.dialogLayout); global.focus_manager.add_group(this.dialogLayout);
this._initialKeyFocus = this.dialogLayout; this._initialKeyFocus = this.dialogLayout;
this._initialKeyFocusDestroyId = 0; this._initialKeyFocusDestroyId = 0;
this._savedKeyFocus = null; this._savedKeyFocus = null;
this._workSpinner = null;
}, },
destroy: function() { destroy: function() {
@@ -191,42 +181,6 @@ const ModalDialog = new Lang.Class({
return button; return button;
}, },
placeSpinner: function(layoutInfo) {
let spinnerIcon = global.datadir + '/theme/process-working.svg';
this._workSpinner = new Animation.AnimatedIcon(spinnerIcon, WORK_SPINNER_ICON_SIZE);
this._workSpinner.actor.opacity = 0;
this._workSpinner.actor.show();
this.buttonLayout.add(this._workSpinner.actor, layoutInfo);
},
setWorking: function(working) {
if (!this._workSpinner)
return;
Tweener.removeTweens(this._workSpinner.actor);
if (working) {
this._workSpinner.play();
Tweener.addTween(this._workSpinner.actor,
{ opacity: 255,
delay: WORK_SPINNER_ANIMATION_DELAY,
time: WORK_SPINNER_ANIMATION_TIME,
transition: 'linear'
});
} else {
Tweener.addTween(this._workSpinner.actor,
{ opacity: 0,
time: WORK_SPINNER_ANIMATION_TIME,
transition: 'linear',
onCompleteScope: this,
onComplete: function() {
if (this._workSpinner)
this._workSpinner.stop();
}
});
}
},
_onKeyPressEvent: function(object, event) { _onKeyPressEvent: function(object, event) {
this._pressedKey = event.get_key_symbol(); this._pressedKey = event.get_key_symbol();
}, },
@@ -323,9 +277,6 @@ const ModalDialog = new Lang.Class({
this.state = State.CLOSED; this.state = State.CLOSED;
this._group.hide(); this._group.hide();
this.emit('closed'); this.emit('closed');
if (this._destroyOnClose)
this.destroy();
}) })
}); });
}, },
@@ -361,9 +312,8 @@ const ModalDialog = new Lang.Class({
if (this._savedKeyFocus) { if (this._savedKeyFocus) {
this._savedKeyFocus.grab_key_focus(); this._savedKeyFocus.grab_key_focus();
this._savedKeyFocus = null; this._savedKeyFocus = null;
} else { } else
this._initialKeyFocus.grab_key_focus(); this._initialKeyFocus.grab_key_focus();
}
if (!this._shellReactive) if (!this._shellReactive)
this._eventBlocker.lower_bottom(); this._eventBlocker.lower_bottom();

View File

@@ -244,7 +244,7 @@ const NotificationDaemon = new Lang.Class({
Main.overview.connect('hidden', Main.overview.connect('hidden',
Lang.bind(this, this._onFocusAppChanged)); 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) { _imageForNotificationData: function(hints) {
@@ -717,8 +717,8 @@ const Source = new Lang.Class({
this.notifications.length > 0) this.notifications.length > 0)
return false; return false;
let id = global.stage.connect('deactivate', Lang.bind(this, function () { let id = global.connect('notify::stage-input-mode', Lang.bind(this, function () {
global.stage.disconnect(id); global.disconnect(id);
this.trayIcon.click(event); this.trayIcon.click(event);
})); }));
@@ -734,11 +734,7 @@ const Source = new Lang.Class({
return app; return app;
if (this.trayIcon) { if (this.trayIcon) {
app = Shell.AppSystem.get_default().lookup_startup_wmclass(this.trayIcon.wm_class); app = Shell.AppSystem.get_default().lookup_wmclass(this.trayIcon.wm_class);
if (app != null)
return app;
app = Shell.AppSystem.get_default().lookup_desktop_wmclass(this.trayIcon.wm_class);
if (app != null) if (app != null)
return app; return app;
} }

View File

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

View File

@@ -11,6 +11,7 @@ const Shell = imports.gi.Shell;
const Gdk = imports.gi.Gdk; const Gdk = imports.gi.Gdk;
const Background = imports.ui.background; const Background = imports.ui.background;
const Dash = imports.ui.dash;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const LayoutManager = imports.ui.layout; const LayoutManager = imports.ui.layout;
const Main = imports.ui.main; const Main = imports.ui.main;
@@ -19,6 +20,7 @@ const OverviewControls = imports.ui.overviewControls;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const Params = imports.misc.params; const Params = imports.misc.params;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const ViewSelector = imports.ui.viewSelector;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail; const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
// Time for initial animation going into Overview mode // Time for initial animation going into Overview mode
@@ -29,9 +31,7 @@ const ANIMATION_TIME = 0.25;
// and don't want the shading animation to get cut off // and don't want the shading animation to get cut off
const SHADE_ANIMATION_TIME = .20; const SHADE_ANIMATION_TIME = .20;
const DND_WINDOW_SWITCH_TIMEOUT = 750; const DND_WINDOW_SWITCH_TIMEOUT = 1250;
const OVERVIEW_ACTIVATION_TIMEOUT = 0.5;
const ShellInfo = new Lang.Class({ const ShellInfo = new Lang.Class({
Name: 'ShellInfo', Name: 'ShellInfo',
@@ -93,7 +93,9 @@ const Overview = new Lang.Class({
_init: function() { _init: function() {
this._overviewCreated = false; this._overviewCreated = false;
this._initCalled = 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)); Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
this._sessionUpdated(); this._sessionUpdated();
}, },
@@ -115,7 +117,7 @@ const Overview = new Lang.Class({
let monitor = Main.layoutManager.primaryMonitor; let monitor = Main.layoutManager.primaryMonitor;
this._desktopFade = new St.Bin(); this._desktopFade = new St.Bin();
Main.layoutManager.overviewGroup.add_child(this._desktopFade); global.overlay_group.add_actor(this._desktopFade);
let layout = new Clutter.BinLayout(); let layout = new Clutter.BinLayout();
this._stack = new Clutter.Actor({ layout_manager: layout }); this._stack = new Clutter.Actor({ layout_manager: layout });
@@ -131,14 +133,22 @@ const Overview = new Lang.Class({
y_expand: true }); y_expand: true });
this._overview._delegate = this; this._overview._delegate = this;
this._backgroundGroup = new Meta.BackgroundGroup(); this._groupStack = new St.Widget({ layout_manager: new Clutter.BinLayout(),
Main.layoutManager.overviewGroup.add_child(this._backgroundGroup); x_expand: true, y_expand: true,
this._bgManagers = []; 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.visible = false; // animating to overview, in overview, animating out
this._shown = false; // show() and not hide() this._shown = false; // show() and not hide()
this._shownTemporarily = false; // showTemporarily() and not hideTemporarily()
this._modal = false; // have a modal grab this._modal = false; // have a modal grab
this.animationInProgress = false; this.animationInProgress = false;
this.visibleTarget = 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 // During transitions, we raise this to the top to avoid having the overview
// area be reactive; it causes too many issues such as double clicks on // area be reactive; it causes too many issues such as double clicks on
// Dash elements, or mouseover handlers in the workspaces. // Dash elements, or mouseover handlers in the workspaces.
this._coverPane = new Clutter.Actor({ opacity: 0, this._coverPane = new Clutter.Rectangle({ opacity: 0,
reactive: true }); reactive: true });
this._stack.add_actor(this._coverPane); this._overview.add_actor(this._coverPane);
this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; })); this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; }));
this._stack.hide();
this._stack.add_actor(this._overview); this._stack.add_actor(this._overview);
Main.layoutManager.overviewGroup.add_child(this._stack); global.overlay_group.add_actor(this._stack);
this._coverPane.hide(); this._coverPane.hide();
@@ -165,6 +176,7 @@ const Overview = new Lang.Class({
Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd)); Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd));
global.screen.connect('restacked', Lang.bind(this, this._onRestacked)); global.screen.connect('restacked', Lang.bind(this, this._onRestacked));
this._group.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
this._windowSwitchTimeoutId = 0; this._windowSwitchTimeoutId = 0;
this._windowSwitchTimestamp = 0; this._windowSwitchTimestamp = 0;
@@ -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() { _sessionUpdated: function() {
this.isDummy = !Main.sessionMode.hasOverview; this.isDummy = !Main.sessionMode.hasOverview;
this._createOverview(); this._createOverview();
@@ -263,13 +289,28 @@ const Overview = new Lang.Class({
this._overview.add_actor(this._searchEntryBin); this._overview.add_actor(this._searchEntryBin);
// Create controls // Create controls
this._controls = new OverviewControls.ControlsManager(this._searchEntry); this._dash = new Dash.Dash();
this._dash = this._controls.dash; this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry,
this._viewSelector = this._controls.viewSelector; this._dash.showAppsButton);
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox();
this._controls = new OverviewControls.ControlsManager(this._dash,
this._thumbnailsBox,
this._viewSelector);
this._controls.dashActor.x_align = Clutter.ActorAlign.START;
this._controls.dashActor.y_expand = true;
// Put the dash in a separate layer to allow content to be centered
this._groupStack.add_actor(this._controls.dashActor);
// Pack all the actors into the group
this._group.add_actor(this._controls.dashSpacer);
this._group.add(this._viewSelector.actor, { x_fill: true,
expand: true });
this._group.add_actor(this._controls.thumbnailsActor);
// Add our same-line elements after the search entry // Add our same-line elements after the search entry
this._overview.add(this._controls.actor, { y_fill: true, expand: true }); this._overview.add(this._groupStack, { y_fill: true, expand: true });
this._controls.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
this._stack.add_actor(this._controls.indicatorActor); this._stack.add_actor(this._controls.indicatorActor);
@@ -305,22 +346,18 @@ const Overview = new Lang.Class({
}, },
_onDragBegin: function() { _onDragBegin: function() {
this._inXdndDrag = true;
DND.addDragMonitor(this._dragMonitor); DND.addDragMonitor(this._dragMonitor);
// Remember the workspace we started from // Remember the workspace we started from
this._lastActiveWorkspaceIndex = global.screen.get_active_workspace_index(); this._lastActiveWorkspaceIndex = global.screen.get_active_workspace_index();
}, },
_onDragEnd: function(time) { _onDragEnd: function(time) {
this._inXdndDrag = false;
// In case the drag was canceled while in the overview // In case the drag was canceled while in the overview
// we have to go back to where we started and hide // we have to go back to where we started and hide
// the overview // the overview
if (this._shown) { if (this._shownTemporarily) {
global.screen.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time); global.screen.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time);
this.hide(); this.hideTemporarily();
} }
this._resetWindowSwitchTimeout(); this._resetWindowSwitchTimeout();
this._lastHoveredWindow = null; this._lastHoveredWindow = null;
@@ -368,7 +405,7 @@ const Overview = new Lang.Class({
this._needsFakePointerEvent = true; this._needsFakePointerEvent = true;
Main.activateWindow(dragEvent.targetActor._delegate.metaWindow, Main.activateWindow(dragEvent.targetActor._delegate.metaWindow,
this._windowSwitchTimestamp); this._windowSwitchTimestamp);
this.hide(); this.hideTemporarily();
this._lastHoveredWindow = null; this._lastHoveredWindow = null;
})); }));
} }
@@ -433,7 +470,6 @@ const Overview = new Lang.Class({
beginItemDrag: function(source) { beginItemDrag: function(source) {
this.emit('item-drag-begin'); this.emit('item-drag-begin');
this._inDrag = true;
}, },
cancelledItemDrag: function(source) { cancelledItemDrag: function(source) {
@@ -442,12 +478,10 @@ const Overview = new Lang.Class({
endItemDrag: function(source) { endItemDrag: function(source) {
this.emit('item-drag-end'); this.emit('item-drag-end');
this._inDrag = false;
}, },
beginWindowDrag: function(source) { beginWindowDrag: function(source) {
this.emit('window-drag-begin'); this.emit('window-drag-begin');
this._inDrag = true;
}, },
cancelledWindowDrag: function(source) { cancelledWindowDrag: function(source) {
@@ -456,31 +490,23 @@ const Overview = new Lang.Class({
endWindowDrag: function(source) { endWindowDrag: function(source) {
this.emit('window-drag-end'); this.emit('window-drag-end');
this._inDrag = false;
}, },
// show: // show:
// //
// Animates the overview visible and grabs mouse and keyboard input // Animates the overview visible and grabs mouse and keyboard input
show: function() { show : function() {
if (this.isDummy) if (this.isDummy)
return; return;
if (this._shown) if (this._shown)
return; return;
this._shown = true; this._shown = true;
this._syncInputMode();
if (!this._syncGrab()) if (!this._modal)
return; return;
Main.layoutManager.showOverview();
this._animateVisible(); this._animateVisible();
}, },
focusSearch: function() {
this.show();
this._searchEntry.grab_key_focus();
},
fadeInDesktop: function() { fadeInDesktop: function() {
this._desktopFade.opacity = 0; this._desktopFade.opacity = 0;
this._desktopFade.show(); this._desktopFade.show();
@@ -510,9 +536,21 @@ const Overview = new Lang.Class({
this.visible = true; this.visible = true;
this.animationInProgress = true; this.animationInProgress = true;
this.visibleTarget = 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); Meta.disable_unredirect_for_screen(global.screen);
global.window_group.hide();
global.top_window_group.hide();
this._stack.show();
this._backgroundGroup.show();
this._viewSelector.show(); this._viewSelector.show();
this._stack.opacity = 0; this._stack.opacity = 0;
@@ -530,6 +568,24 @@ const Overview = new Lang.Class({
this.emit('showing'); 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: // hide:
// //
// Reverses the effect of show() // Reverses the effect of show()
@@ -540,20 +596,31 @@ const Overview = new Lang.Class({
if (!this._shown) if (!this._shown)
return; return;
let event = Clutter.get_current_event(); if (this._controlPressed)
if (event) { return;
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;
}
this._animateNotVisible(); if (!this._shownTemporarily)
this._animateNotVisible();
this._shown = false; 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() { toggle: function() {
@@ -566,51 +633,37 @@ const Overview = new Lang.Class({
this.show(); 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 //// //// Private methods ////
_syncGrab: function() { _syncInputMode: function() {
// We delay grab changes during animation so that when removing the // We delay input mode changes during animation so that when removing the
// overview we don't have a problem with the release of a press/release // overview we don't have a problem with the release of a press/release
// going to an application. // going to an application.
if (this.animationInProgress) if (this.animationInProgress)
return true; return;
if (this._shown) { if (this._shown) {
let shouldBeModal = !this._inXdndDrag; if (!this._modal) {
if (shouldBeModal) { if (Main.pushModal(this._overview,
if (!this._modal) { { keybindingMode: Shell.KeyBindingMode.OVERVIEW }))
if (Main.pushModal(this._overview, this._modal = true;
{ keybindingMode: Shell.KeyBindingMode.OVERVIEW })) { else
this._modal = true; this.hide();
} else {
this.hide();
return false;
}
}
} }
} else if (this._shownTemporarily) {
if (this._modal) {
Main.popModal(this._overview);
this._modal = false;
}
global.stage_input_mode = Shell.StageInputMode.FULLSCREEN;
} else { } else {
if (this._modal) { if (this._modal) {
Main.popModal(this._overview); Main.popModal(this._overview);
this._modal = false; this._modal = false;
} }
else if (global.stage_input_mode == Shell.StageInputMode.FULLSCREEN)
global.stage_input_mode = Shell.StageInputMode.NORMAL;
} }
return true;
}, },
_animateNotVisible: function() { _animateNotVisible: function() {
@@ -644,10 +697,10 @@ const Overview = new Lang.Class({
this.emit('shown'); this.emit('shown');
// Handle any calls to hide* while we were showing // Handle any calls to hide* while we were showing
if (!this._shown) if (!this._shown && !this._shownTemporarily)
this._animateNotVisible(); this._animateNotVisible();
this._syncGrab(); this._syncInputMode();
global.sync_pointer(); global.sync_pointer();
}, },
@@ -655,21 +708,25 @@ const Overview = new Lang.Class({
// Re-enable unredirection // Re-enable unredirection
Meta.enable_unredirect_for_screen(global.screen); Meta.enable_unredirect_for_screen(global.screen);
global.window_group.show();
global.top_window_group.show();
this._viewSelector.hide(); this._viewSelector.hide();
this._desktopFade.hide(); this._desktopFade.hide();
this._coverPane.hide(); this._backgroundGroup.hide();
this._stack.hide();
this.visible = false; this.visible = false;
this.animationInProgress = false; this.animationInProgress = false;
this._coverPane.hide();
this.emit('hidden'); this.emit('hidden');
// Handle any calls to show* while we were hiding // Handle any calls to show* while we were hiding
if (this._shown) if (this._shown || this._shownTemporarily)
this._animateVisible(); this._animateVisible();
else
Main.layoutManager.hideOverview();
this._syncGrab(); this._syncInputMode();
// Fake a pointer event if requested // Fake a pointer event if requested
if (this._needsFakePointerEvent) { if (this._needsFakePointerEvent) {

View File

@@ -1,18 +1,15 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const GObject = imports.gi.GObject;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Lang = imports.lang; const Lang = imports.lang;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
const St = imports.gi.St; const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Dash = imports.ui.dash;
const Main = imports.ui.main; const Main = imports.ui.main;
const Params = imports.misc.params; const Params = imports.misc.params;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const ViewSelector = imports.ui.viewSelector; const ViewSelector = imports.ui.viewSelector;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
const SIDE_CONTROLS_ANIMATION_TIME = 0.16; const SIDE_CONTROLS_ANIMATION_TIME = 0.16;
@@ -207,7 +204,6 @@ const SlidingControl = new Lang.Class({
slideIn: function() { slideIn: function() {
this.visible = true; this.visible = true;
this._updateTranslation();
// we will update slideX and the translation from pageEmpty // we will update slideX and the translation from pageEmpty
}, },
@@ -247,9 +243,7 @@ const ThumbnailsSlider = new Lang.Class({
this.actor.add_actor(this._thumbnailsBox.actor); this.actor.add_actor(this._thumbnailsBox.actor);
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this.updateSlide)); Main.layoutManager.connect('monitors-changed', Lang.bind(this, this.updateSlide));
Main.overview.connect('hiding', Lang.bind(this, this.slideOut));
this.actor.connect('notify::hover', Lang.bind(this, this.updateSlide)); this.actor.connect('notify::hover', Lang.bind(this, this.updateSlide));
this._thumbnailsBox.actor.bind_property('visible', this.actor, 'visible', GObject.BindingFlags.SYNC_CREATE);
}, },
_getAlwaysZoomOut: function() { _getAlwaysZoomOut: function() {
@@ -275,18 +269,6 @@ const ThumbnailsSlider = new Lang.Class({
return alwaysZoomOut; return alwaysZoomOut;
}, },
_onOverviewShowing: function() {
this.visible = true;
this.layout.slideX = this.getSlide();
this.actor.translation_x = this._getTranslation();
this.slideIn();
},
getNonExpandedWidth: function() {
let child = this.actor.get_first_child();
return child.get_theme_node().get_length('visible-width');
},
getSlide: function() { getSlide: function() {
if (!this.visible) if (!this.visible)
return 0; return 0;
@@ -298,16 +280,18 @@ const ThumbnailsSlider = new Lang.Class({
let child = this.actor.get_first_child(); let child = this.actor.get_first_child();
let preferredHeight = child.get_preferred_height(-1)[1]; let preferredHeight = child.get_preferred_height(-1)[1];
let expandedWidth = child.get_preferred_width(preferredHeight)[1]; let expandedWidth = child.get_preferred_width(preferredHeight)[1];
let visibleWidth = child.get_theme_node().get_length('visible-width');
return this.getNonExpandedWidth() / expandedWidth; return visibleWidth / expandedWidth;
}, },
getVisibleWidth: function() { getVisibleWidth: function() {
let alwaysZoomOut = this._getAlwaysZoomOut(); let alwaysZoomOut = this._getAlwaysZoomOut();
if (alwaysZoomOut) if (alwaysZoomOut)
return this.parent(); return this.parent();
else
return this.getNonExpandedWidth(); let child = this.actor.get_first_child();
return child.get_theme_node().get_length('visible-width');
} }
}); });
@@ -325,14 +309,9 @@ const DashSlider = new Lang.Class({
// available allocation // available allocation
this._dash.actor.x_expand = true; this._dash.actor.x_expand = true;
this._dash.actor.y_expand = true; this._dash.actor.y_expand = true;
this.actor.x_align = Clutter.ActorAlign.START;
this.actor.y_expand = true;
this.actor.add_actor(this._dash.actor); this.actor.add_actor(this._dash.actor);
this._dash.connect('icon-size-changed', Lang.bind(this, this.updateSlide)); this._dash.connect('icon-size-changed', Lang.bind(this, this.updateSlide));
Main.overview.connect('hiding', Lang.bind(this, this.slideOut));
}, },
getSlide: function() { getSlide: function() {
@@ -342,13 +321,6 @@ const DashSlider = new Lang.Class({
return 0; return 0;
}, },
_onOverviewShowing: function() {
this.visible = true;
this.layout.slideX = this.getSlide();
this.actor.translation_x = this._getTranslation();
this.slideIn();
},
_onWindowDragBegin: function() { _onWindowDragBegin: function() {
this.fadeHalf(); this.fadeHalf();
}, },
@@ -480,11 +452,9 @@ const MessagesIndicator = new Lang.Class({
_updateCount: function() { _updateCount: function() {
let count = 0; let count = 0;
let hasChats = false;
this._sources.forEach(Lang.bind(this, this._sources.forEach(Lang.bind(this,
function(source) { function(source) {
count += source.indicatorCount; count += source.indicatorCount;
hasChats |= source.isChat;
})); }));
this._count = count; this._count = count;
@@ -492,7 +462,6 @@ const MessagesIndicator = new Lang.Class({
"%d new messages", "%d new messages",
count).format(count); count).format(count);
this._icon.visible = hasChats;
this._updateVisibility(); this._updateVisibility();
}, },
@@ -507,77 +476,39 @@ const MessagesIndicator = new Lang.Class({
const ControlsManager = new Lang.Class({ const ControlsManager = new Lang.Class({
Name: 'ControlsManager', Name: 'ControlsManager',
_init: function(searchEntry) { _init: function(dash, thumbnails, viewSelector) {
this.dash = new Dash.Dash(); this._dashSlider = new DashSlider(dash);
this._dashSlider = new DashSlider(this.dash); this.dashActor = this._dashSlider.actor;
this._dashSpacer = new DashSpacer(); this.dashSpacer = new DashSpacer();
this._dashSpacer.setDashActor(this._dashSlider.actor); this.dashSpacer.setDashActor(this.dashActor);
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox(); this._thumbnailsSlider = new ThumbnailsSlider(thumbnails);
this._thumbnailsSlider = new ThumbnailsSlider(this._thumbnailsBox); this.thumbnailsActor = this._thumbnailsSlider.actor;
this.viewSelector = new ViewSelector.ViewSelector(searchEntry, this._indicator = new MessagesIndicator(viewSelector);
this.dash.showAppsButton);
this.viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility));
this.viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty));
this._indicator = new MessagesIndicator(this.viewSelector);
this.indicatorActor = this._indicator.actor; this.indicatorActor = this._indicator.actor;
this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(), this._viewSelector = viewSelector;
reactive: true, this._viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility));
x_expand: true, y_expand: true, this._viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty));
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);
this._group.connect('notify::allocation', Lang.bind(this, this._updateWorkspacesGeometry));
Main.overview.connect('showing', Lang.bind(this, this._updateSpacerVisibility)); Main.overview.connect('showing', Lang.bind(this, this._updateSpacerVisibility));
Main.overview.connect('item-drag-begin', Lang.bind(this, Main.overview.connect('item-drag-begin', Lang.bind(this,
function() { function() {
let activePage = this.viewSelector.getActivePage(); let activePage = this._viewSelector.getActivePage();
if (activePage != ViewSelector.ViewPage.WINDOWS) if (activePage != ViewSelector.ViewPage.WINDOWS)
this.viewSelector.fadeHalf(); this._viewSelector.fadeHalf();
})); }));
Main.overview.connect('item-drag-end', Lang.bind(this, Main.overview.connect('item-drag-end', Lang.bind(this,
function() { function() {
this.viewSelector.fadeIn(); this._viewSelector.fadeIn();
})); }));
Main.overview.connect('item-drag-cancelled', Lang.bind(this, Main.overview.connect('item-drag-cancelled', Lang.bind(this,
function() { function() {
this.viewSelector.fadeIn(); this._viewSelector.fadeIn();
})); }));
}, },
_updateWorkspacesGeometry: function() {
let [x, y] = this.actor.get_transformed_position();
let [width, height] = this.actor.get_transformed_size();
let geometry = { x: x, y: y, width: width, height: height };
let spacing = this.actor.get_theme_node().get_length('spacing');
let dashWidth = this._dashSlider.getVisibleWidth() + spacing;
let thumbnailsWidth = this._thumbnailsSlider.getNonExpandedWidth() + spacing;
geometry.width -= dashWidth;
geometry.width -= thumbnailsWidth;
if (this.actor.get_text_direction() == Clutter.TextDirection.LTR)
geometry.x += dashWidth;
else
geometry.x += thumbnailsWidth;
this.viewSelector.setWorkspacesFullGeometry(geometry);
},
_setVisibility: function() { _setVisibility: function() {
// Ignore the case when we're leaving the overview, since // Ignore the case when we're leaving the overview, since
// actors will be made visible again when entering the overview // actors will be made visible again when entering the overview
@@ -587,7 +518,7 @@ const ControlsManager = new Lang.Class({
(Main.overview.animationInProgress && !Main.overview.visibleTarget)) (Main.overview.animationInProgress && !Main.overview.visibleTarget))
return; return;
let activePage = this.viewSelector.getActivePage(); let activePage = this._viewSelector.getActivePage();
let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS || let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS ||
activePage == ViewSelector.ViewPage.APPS); activePage == ViewSelector.ViewPage.APPS);
let thumbnailsVisible = (activePage == ViewSelector.ViewPage.WINDOWS); let thumbnailsVisible = (activePage == ViewSelector.ViewPage.WINDOWS);
@@ -607,8 +538,8 @@ const ControlsManager = new Lang.Class({
if (Main.overview.animationInProgress && !Main.overview.visibleTarget) if (Main.overview.animationInProgress && !Main.overview.visibleTarget)
return; return;
let activePage = this.viewSelector.getActivePage(); let activePage = this._viewSelector.getActivePage();
this._dashSpacer.visible = (activePage == ViewSelector.ViewPage.WINDOWS); this.dashSpacer.visible = (activePage == ViewSelector.ViewPage.WINDOWS);
}, },
_onPageEmpty: function() { _onPageEmpty: function() {

View File

@@ -15,14 +15,13 @@ const Signals = imports.signals;
const Atk = imports.gi.Atk; const Atk = imports.gi.Atk;
const Animation = imports.ui.animation;
const Config = imports.misc.config; const Config = imports.misc.config;
const CtrlAltTab = imports.ui.ctrlAltTab; const CtrlAltTab = imports.ui.ctrlAltTab;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Layout = imports.ui.layout;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const RemoteMenu = imports.ui.remoteMenu;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
@@ -30,6 +29,7 @@ const PANEL_ICON_SIZE = 24;
const BUTTON_DND_ACTIVATION_TIMEOUT = 250; const BUTTON_DND_ACTIVATION_TIMEOUT = 250;
const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
const SPINNER_ANIMATION_TIME = 0.2; const SPINNER_ANIMATION_TIME = 0.2;
// To make sure the panel corners blend nicely with the panel, // To make sure the panel corners blend nicely with the panel,
@@ -75,6 +75,81 @@ function _unpremultiply(color) {
blue: blue, alpha: color.alpha }); blue: blue, alpha: color.alpha });
}; };
const Animation = new Lang.Class({
Name: 'Animation',
_init: function(filename, width, height, speed) {
this.actor = new St.Bin();
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this._speed = speed;
this._isLoaded = false;
this._isPlaying = false;
this._timeoutId = 0;
this._frame = 0;
this._animations = St.TextureCache.get_default().load_sliced_image (filename, width, height,
Lang.bind(this, this._animationsLoaded));
this.actor.set_child(this._animations);
},
play: function() {
if (this._isLoaded && this._timeoutId == 0) {
if (this._frame == 0)
this._showFrame(0);
this._timeoutId = Mainloop.timeout_add(this._speed, Lang.bind(this, this._update));
}
this._isPlaying = true;
},
stop: function() {
if (this._timeoutId > 0) {
Mainloop.source_remove(this._timeoutId);
this._timeoutId = 0;
}
this._isPlaying = false;
},
_showFrame: function(frame) {
let oldFrameActor = this._animations.get_child_at_index(this._frame);
if (oldFrameActor)
oldFrameActor.hide();
this._frame = (frame % this._animations.get_n_children());
let newFrameActor = this._animations.get_child_at_index(this._frame);
if (newFrameActor)
newFrameActor.show();
},
_update: function() {
this._showFrame(this._frame + 1);
return true;
},
_animationsLoaded: function() {
this._isLoaded = true;
if (this._isPlaying)
this.play();
},
_onDestroy: function() {
this.stop();
}
});
const AnimatedIcon = new Lang.Class({
Name: 'AnimatedIcon',
Extends: Animation,
_init: function(name, size) {
this.parent(global.datadir + '/theme/' + name, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
}
});
const TextShadower = new Lang.Class({ const TextShadower = new Lang.Class({
Name: 'TextShadower', Name: 'TextShadower',
@@ -184,11 +259,11 @@ const AppMenuButton = new Lang.Class({
this._actionGroupNotifyId = 0; this._actionGroupNotifyId = 0;
let bin = new St.Bin({ name: 'appMenu' }); let bin = new St.Bin({ name: 'appMenu' });
bin.connect('style-changed', Lang.bind(this, this._onStyleChanged));
this.actor.add_actor(bin); this.actor.add_actor(bin);
this.actor.bind_property("reactive", this.actor, "can-focus", 0); this.actor.bind_property("reactive", this.actor, "can-focus", 0);
this.actor.reactive = false; this.actor.reactive = false;
this._targetIsCurrent = false;
this._container = new Shell.GenericContainer(); this._container = new Shell.GenericContainer();
bin.set_child(this._container); bin.set_child(this._container);
@@ -206,38 +281,35 @@ const AppMenuButton = new Lang.Class({
this._iconBox.connect('notify::allocation', this._iconBox.connect('notify::allocation',
Lang.bind(this, this._updateIconBoxClip)); Lang.bind(this, this._updateIconBoxClip));
this._container.add_actor(this._iconBox); this._container.add_actor(this._iconBox);
this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
this._container.add_actor(this._hbox);
this._label = new TextShadower(); this._label = new TextShadower();
this._label.actor.y_align = Clutter.ActorAlign.CENTER; this._container.add_actor(this._label.actor);
this._hbox.add_actor(this._label.actor);
this._arrow = new St.Label({ text: '\u25BE',
y_expand: true,
y_align: Clutter.ActorAlign.CENTER });
this._hbox.add_actor(this._arrow);
this._iconBottomClip = 0; this._iconBottomClip = 0;
this._visible = !Main.overview.visible; this._visible = !Main.overview.visible;
if (!this._visible) if (!this._visible)
this.actor.hide(); this.actor.hide();
this._overviewHidingId = Main.overview.connect('hiding', Lang.bind(this, this._sync)); Main.overview.connect('hiding', Lang.bind(this, function () {
this._overviewShowingId = Main.overview.connect('showing', Lang.bind(this, this._sync)); this.show();
}));
Main.overview.connect('showing', Lang.bind(this, function () {
this.hide();
}));
this._stop = true; this._stop = true;
this._spinner = null; this._spinner = new AnimatedIcon('process-working.svg',
PANEL_ICON_SIZE);
this._container.add_actor(this._spinner.actor);
this._spinner.actor.hide();
this._spinner.actor.lower_bottom();
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
let appSys = Shell.AppSystem.get_default(); let appSys = Shell.AppSystem.get_default();
this._focusAppNotifyId = tracker.connect('notify::focus-app', Lang.bind(this, this._focusAppChanged));
tracker.connect('notify::focus-app', Lang.bind(this, this._focusAppChanged)); appSys.connect('app-state-changed', Lang.bind(this, this._onAppStateChanged));
this._appStateChangedSignalId =
appSys.connect('app-state-changed', Lang.bind(this, this._onAppStateChanged)); global.window_manager.connect('switch-workspace', Lang.bind(this, this._sync));
this._switchWorkspaceNotifyId =
global.window_manager.connect('switch-workspace', Lang.bind(this, this._sync));
this._sync(); this._sync();
}, },
@@ -247,8 +319,14 @@ const AppMenuButton = new Lang.Class({
return; return;
this._visible = true; this._visible = true;
this.actor.reactive = true;
this.actor.show(); this.actor.show();
if (!this._targetIsCurrent)
return;
this.actor.reactive = true;
Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 255, { opacity: 255,
time: Overview.ANIMATION_TIME, time: Overview.ANIMATION_TIME,
@@ -261,6 +339,12 @@ const AppMenuButton = new Lang.Class({
this._visible = false; this._visible = false;
this.actor.reactive = false; this.actor.reactive = false;
if (!this._targetIsCurrent) {
this.actor.hide();
return;
}
Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor, Tweener.addTween(this.actor,
{ opacity: 0, { opacity: 0,
time: Overview.ANIMATION_TIME, time: Overview.ANIMATION_TIME,
@@ -271,36 +355,19 @@ const AppMenuButton = new Lang.Class({
onCompleteScope: this }); onCompleteScope: this });
}, },
_onStyleChanged: function(actor) {
let node = actor.get_theme_node();
let [success, icon] = node.lookup_url('spinner-image', false);
if (!success || this._spinnerIcon == icon)
return;
this._spinnerIcon = icon;
this._spinner = new Animation.AnimatedIcon(this._spinnerIcon, PANEL_ICON_SIZE);
this._hbox.add_actor(this._spinner.actor);
this._spinner.actor.hide();
},
_onIconBoxStyleChanged: function() { _onIconBoxStyleChanged: function() {
let node = this._iconBox.get_theme_node(); let node = this._iconBox.get_theme_node();
this._iconBottomClip = node.get_length('app-icon-bottom-clip'); this._iconBottomClip = node.get_length('app-icon-bottom-clip');
this._updateIconBoxClip(); this._updateIconBoxClip();
}, },
_syncIcon: function() {
if (!this._targetApp)
return;
let icon = this._targetApp.get_faded_icon(2 * PANEL_ICON_SIZE, this._iconBox.text_direction);
this._iconBox.set_child(icon);
},
_onIconThemeChanged: function() { _onIconThemeChanged: function() {
if (this._iconBox.child == null) if (this._iconBox.child == null)
return; return;
this._syncIcon(); this._iconBox.child.destroy();
let icon = this._targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
this._iconBox.set_child(icon);
}, },
_updateIconBoxClip: function() { _updateIconBoxClip: function() {
@@ -318,10 +385,7 @@ const AppMenuButton = new Lang.Class({
return; return;
this._stop = true; this._stop = true;
this.actor.reactive = true;
if (this._spinner == null)
return;
Tweener.addTween(this._spinner.actor, Tweener.addTween(this._spinner.actor,
{ opacity: 0, { opacity: 0,
time: SPINNER_ANIMATION_TIME, time: SPINNER_ANIMATION_TIME,
@@ -337,10 +401,7 @@ const AppMenuButton = new Lang.Class({
startAnimation: function() { startAnimation: function() {
this._stop = false; this._stop = false;
this.actor.reactive = false;
if (this._spinner == null)
return;
this._spinner.play(); this._spinner.play();
this._spinner.actor.show(); this._spinner.actor.show();
}, },
@@ -349,7 +410,7 @@ const AppMenuButton = new Lang.Class({
let [minSize, naturalSize] = this._iconBox.get_preferred_width(forHeight); let [minSize, naturalSize] = this._iconBox.get_preferred_width(forHeight);
alloc.min_size = minSize; alloc.min_size = minSize;
alloc.natural_size = naturalSize; alloc.natural_size = naturalSize;
[minSize, naturalSize] = this._hbox.get_preferred_width(forHeight); [minSize, naturalSize] = this._label.actor.get_preferred_width(forHeight);
alloc.min_size = alloc.min_size + Math.max(0, minSize - Math.floor(alloc.min_size / 2)); alloc.min_size = alloc.min_size + Math.max(0, minSize - Math.floor(alloc.min_size / 2));
alloc.natural_size = alloc.natural_size + Math.max(0, naturalSize - Math.floor(alloc.natural_size / 2)); alloc.natural_size = alloc.natural_size + Math.max(0, naturalSize - Math.floor(alloc.natural_size / 2));
}, },
@@ -358,7 +419,7 @@ const AppMenuButton = new Lang.Class({
let [minSize, naturalSize] = this._iconBox.get_preferred_height(forWidth); let [minSize, naturalSize] = this._iconBox.get_preferred_height(forWidth);
alloc.min_size = minSize; alloc.min_size = minSize;
alloc.natural_size = naturalSize; alloc.natural_size = naturalSize;
[minSize, naturalSize] = this._hbox.get_preferred_height(forWidth); [minSize, naturalSize] = this._label.actor.get_preferred_height(forWidth);
if (minSize > alloc.min_size) if (minSize > alloc.min_size)
alloc.min_size = minSize; alloc.min_size = minSize;
if (naturalSize > alloc.natural_size) if (naturalSize > alloc.natural_size)
@@ -388,10 +449,11 @@ const AppMenuButton = new Lang.Class({
let iconWidth = childBox.x2 - childBox.x1; let iconWidth = childBox.x2 - childBox.x1;
[minWidth, naturalWidth] = this._hbox.get_preferred_width(-1); [minWidth, minHeight, naturalWidth, naturalHeight] = this._label.actor.get_preferred_size();
childBox.y1 = 0; yPadding = Math.floor(Math.max(0, allocHeight - naturalHeight) / 2);
childBox.y2 = allocHeight; childBox.y1 = yPadding;
childBox.y2 = childBox.y1 + Math.min(naturalHeight, allocHeight);
if (direction == Clutter.TextDirection.LTR) { if (direction == Clutter.TextDirection.LTR) {
childBox.x1 = Math.floor(iconWidth / 2); childBox.x1 = Math.floor(iconWidth / 2);
@@ -400,7 +462,21 @@ const AppMenuButton = new Lang.Class({
childBox.x2 = allocWidth - Math.floor(iconWidth / 2); childBox.x2 = allocWidth - Math.floor(iconWidth / 2);
childBox.x1 = Math.max(0, childBox.x2 - naturalWidth); childBox.x1 = Math.max(0, childBox.x2 - naturalWidth);
} }
this._hbox.allocate(childBox, flags); this._label.actor.allocate(childBox, flags);
if (direction == Clutter.TextDirection.LTR) {
childBox.x1 = Math.floor(iconWidth / 2) + this._label.actor.width;
childBox.x2 = childBox.x1 + this._spinner.actor.width;
childBox.y1 = box.y1;
childBox.y2 = box.y2 - 1;
this._spinner.actor.allocate(childBox, flags);
} else {
childBox.x1 = -this._spinner.actor.width;
childBox.x2 = childBox.x1 + this._spinner.actor.width;
childBox.y1 = box.y1;
childBox.y2 = box.y2 - 1;
this._spinner.actor.allocate(childBox, flags);
}
}, },
_onAppStateChanged: function(appSys, app) { _onAppStateChanged: function(appSys, app) {
@@ -426,88 +502,109 @@ const AppMenuButton = new Lang.Class({
// If the app has just lost focus to the panel, pretend // If the app has just lost focus to the panel, pretend
// nothing happened; otherwise you can't keynav to the // nothing happened; otherwise you can't keynav to the
// app menu. // app menu.
if (global.stage.key_focus != null) if (global.stage_input_mode == Shell.StageInputMode.FOCUSED)
return; return;
} }
this._sync(); this._sync();
}, },
_findTargetApp: function() { _sync: function() {
let workspace = global.screen.get_active_workspace();
let tracker = Shell.WindowTracker.get_default(); let tracker = Shell.WindowTracker.get_default();
let focusedApp = tracker.focus_app; let focusedApp = tracker.focus_app;
if (focusedApp && focusedApp.is_on_workspace(workspace)) let lastStartedApp = null;
return focusedApp; let workspace = global.screen.get_active_workspace();
for (let i = 0; i < this._startingApps.length; i++) for (let i = 0; i < this._startingApps.length; i++)
if (this._startingApps[i].is_on_workspace(workspace)) if (this._startingApps[i].is_on_workspace(workspace))
return this._startingApps[i]; lastStartedApp = this._startingApps[i];
return null; let targetApp = focusedApp != null ? focusedApp : lastStartedApp;
},
_sync: function() { if (targetApp == null) {
let targetApp = this._findTargetApp(); if (!this._targetIsCurrent)
return;
if (this._targetApp != targetApp) { this.actor.reactive = false;
if (this._appMenuNotifyId) { this._targetIsCurrent = false;
this._targetApp.disconnect(this._appMenuNotifyId);
this._appMenuNotifyId = 0;
}
if (this._actionGroupNotifyId) {
this._targetApp.disconnect(this._actionGroupNotifyId);
this._actionGroupNotifyId = 0;
}
this._targetApp = targetApp; Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor, { opacity: 0,
if (this._targetApp) { time: Overview.ANIMATION_TIME,
this._appMenuNotifyId = this._targetApp.connect('notify::menu', Lang.bind(this, this._sync)); transition: 'easeOutQuad' });
this._actionGroupNotifyId = this._targetApp.connect('notify::action-group', Lang.bind(this, this._sync)); return;
this._label.setText(this._targetApp.get_name());
this.actor.set_accessible_name(this._targetApp.get_name());
}
} }
let visible = (this._targetApp != null && !Main.overview.visibleTarget); if (!targetApp.is_on_workspace(workspace))
if (visible) return;
this.show();
else
this.hide();
let isBusy = (this._targetApp != null && if (!this._targetIsCurrent) {
(this._targetApp.get_state() == Shell.AppState.STARTING || this.actor.reactive = true;
this._targetApp.get_state() == Shell.AppState.BUSY)); this._targetIsCurrent = true;
if (isBusy)
Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor, { opacity: 255,
time: Overview.ANIMATION_TIME,
transition: 'easeOutQuad' });
}
if (targetApp == this._targetApp) {
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING) {
this.stopAnimation();
this._maybeSetMenu();
}
return;
}
this._spinner.actor.hide();
if (this._iconBox.child != null)
this._iconBox.child.destroy();
this._iconBox.hide();
this._label.setText('');
if (this._appMenuNotifyId)
this._targetApp.disconnect(this._appMenuNotifyId);
if (this._actionGroupNotifyId)
this._targetApp.disconnect(this._actionGroupNotifyId);
if (targetApp) {
this._appMenuNotifyId = targetApp.connect('notify::menu', Lang.bind(this, this._sync));
this._actionGroupNotifyId = targetApp.connect('notify::action-group', Lang.bind(this, this._sync));
} else {
this._appMenuNotifyId = 0;
this._actionGroupNotifyId = 0;
}
this._targetApp = targetApp;
let icon = targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
this._label.setText(targetApp.get_name());
this.setName(targetApp.get_name());
this._iconBox.set_child(icon);
this._iconBox.show();
if (targetApp.get_state() == Shell.AppState.STARTING)
this.startAnimation(); this.startAnimation();
else else
this.stopAnimation(); this._maybeSetMenu();
this.actor.reactive = (visible && !isBusy);
this._syncIcon();
this._maybeSetMenu();
this.emit('changed'); this.emit('changed');
}, },
_maybeSetMenu: function() { _maybeSetMenu: function() {
let menu; let menu;
if (this._targetApp == null) { if (this._targetApp.action_group && this._targetApp.menu) {
menu = null; if (this.menu instanceof PopupMenu.RemoteMenu &&
} else if (this._targetApp.action_group && this._targetApp.menu) {
if (this.menu instanceof RemoteMenu.RemoteMenu &&
this.menu.actionGroup == this._targetApp.action_group) this.menu.actionGroup == this._targetApp.action_group)
return; return;
menu = new RemoteMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group); menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
menu.connect('activate', Lang.bind(this, function() { menu.connect('activate', Lang.bind(this, function() {
let win = this._targetApp.get_windows()[0]; let win = this._targetApp.get_windows()[0];
win.check_alive(global.get_current_time()); win.check_alive(global.get_current_time());
})); }));
} else { } else {
if (this.menu && this.menu.isDummyQuitMenu) if (this.menu.isDummyQuitMenu)
return; return;
// fallback to older menu // fallback to older menu
@@ -519,35 +616,7 @@ const AppMenuButton = new Lang.Class({
} }
this.setMenu(menu); this.setMenu(menu);
if (menu) this._menuManager.addMenu(menu);
this._menuManager.addMenu(menu);
},
destroy: function() {
if (this._appStateChangedSignalId > 0) {
let appSys = Shell.AppSystem.get_default();
appSys.disconnect(this._appStateChangedSignalId);
this._appStateChangedSignalId = 0;
}
if (this._focusAppNotifyId > 0) {
let tracker = Shell.WindowTracker.get_default();
tracker.disconnect(this._focusAppNotifyId);
this._focusAppNotifyId = 0;
}
if (this._overviewHidingId > 0) {
Main.overview.disconnect(this._overviewHidingId);
this._overviewHidingId = 0;
}
if (this._overviewShowingId > 0) {
Main.overview.disconnect(this._overviewShowingId);
this._overviewShowingId = 0;
}
if (this._switchWorkspaceNotifyId > 0) {
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
this._switchWorkspaceNotifyId = 0;
}
this.parent();
} }
}); });
@@ -561,16 +630,23 @@ const ActivitiesButton = new Lang.Class({
this.parent(0.0, null, true); this.parent(0.0, null, true);
this.actor.accessible_role = Atk.Role.TOGGLE_BUTTON; 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'; this.actor.name = 'panelActivities';
/* Translators: If there is no suitable word for "Activities" /* Translators: If there is no suitable word for "Activities"
in your language, you can use the word for "Overview". */ in your language, you can use the word for "Overview". */
this._label = new St.Label({ text: _("Activities"), this._label = new St.Label({ text: _("Activities") });
y_align: Clutter.ActorAlign.CENTER }); container.add_actor(this._label);
this.actor.add_actor(this._label);
this.actor.label_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('captured-event', Lang.bind(this, this._onCapturedEvent));
this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRelease)); this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRelease));
this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease)); this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease));
@@ -585,6 +661,44 @@ const ActivitiesButton = new Lang.Class({
})); }));
this._xdndTimeOut = 0; 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) { handleDragOver: function(source, actor, x, y, time) {
@@ -594,14 +708,14 @@ const ActivitiesButton = new Lang.Class({
if (this._xdndTimeOut != 0) if (this._xdndTimeOut != 0)
Mainloop.source_remove(this._xdndTimeOut); Mainloop.source_remove(this._xdndTimeOut);
this._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT, 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; return DND.DragMotionResult.CONTINUE;
}, },
_onCapturedEvent: function(actor, event) { _onCapturedEvent: function(actor, event) {
if (event.type() == Clutter.EventType.BUTTON_PRESS) { if (event.type() == Clutter.EventType.BUTTON_PRESS) {
if (!Main.overview.shouldToggleByCornerOrButton()) if (!this.hotCorner.shouldToggleOverviewOnClick())
return true; return true;
} }
return false; return false;
@@ -618,12 +732,15 @@ const ActivitiesButton = new Lang.Class({
} }
}, },
_xdndToggleOverview: function(actor) { _xdndShowOverview: function(actor) {
let [x, y, mask] = global.get_pointer(); let [x, y, mask] = global.get_pointer();
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y); let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
if (pickedActor == this.actor && Main.overview.shouldToggleByCornerOrButton()) if (pickedActor == this.actor) {
Main.overview.toggle(); if (!Main.overview.visible && !Main.overview.animationInProgress) {
Main.overview.showTemporarily();
}
}
Mainloop.source_remove(this._xdndTimeOut); Mainloop.source_remove(this._xdndTimeOut);
this._xdndTimeOut = 0; this._xdndTimeOut = 0;
@@ -797,58 +914,32 @@ const PanelCorner = new Lang.Class({
} }
}); });
const AggregateMenu = new Lang.Class({
Name: 'AggregateMenu',
Extends: PanelMenu.Button,
_init: function() {
this.parent(0.0, _("Settings Menu"), 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();
this._bluetooth = new imports.ui.status.bluetooth.Indicator();
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);
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(new St.Label({ text: '\u25BE',
y_expand: true,
y_align: Clutter.ActorAlign.CENTER }));
this.menu.addMenuItem(this._volume.menu);
this.menu.addMenuItem(this._brightness.menu);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addMenuItem(this._network.menu);
this.menu.addMenuItem(this._bluetooth.menu);
this.menu.addMenuItem(this._rfkill.menu);
this.menu.addMenuItem(this._power.menu);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addMenuItem(this._system.menu);
},
});
const PANEL_ITEM_IMPLEMENTATIONS = { const PANEL_ITEM_IMPLEMENTATIONS = {
'activities': ActivitiesButton, 'activities': ActivitiesButton,
'aggregateMenu': AggregateMenu,
'appMenu': AppMenuButton, 'appMenu': AppMenuButton,
'dateMenu': imports.ui.dateMenu.DateMenuButton, 'dateMenu': imports.ui.dateMenu.DateMenuButton,
'a11y': imports.ui.status.accessibility.ATIndicator, 'a11y': imports.ui.status.accessibility.ATIndicator,
'a11yGreeter': imports.ui.status.accessibility.ATGreeterIndicator, 'a11yGreeter': imports.ui.status.accessibility.ATGreeterIndicator,
'volume': imports.ui.status.volume.Indicator,
'battery': imports.ui.status.power.Indicator,
'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
'logo': imports.gdm.loginDialog.LogoMenuButton,
'keyboard': imports.ui.status.keyboard.InputSourceIndicator, 'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
'powerMenu': imports.gdm.powerMenu.PowerMenuButton,
'userMenu': imports.ui.userMenu.UserMenuButton
}; };
if (Config.HAVE_BLUETOOTH)
PANEL_ITEM_IMPLEMENTATIONS['bluetooth'] =
imports.ui.status.bluetooth.Indicator;
try {
PANEL_ITEM_IMPLEMENTATIONS['network'] =
imports.ui.status.network.NMApplet;
} catch(e) {
log('NMApplet is not supported. It is possible that your NetworkManager version is too old');
}
const Panel = new Lang.Class({ const Panel = new Lang.Class({
Name: 'Panel', Name: 'Panel',
@@ -861,7 +952,7 @@ const Panel = new Lang.Class({
this.statusArea = {}; this.statusArea = {};
this.menuManager = new PopupMenu.PopupMenuManager(this, { keybindingMode: Shell.KeyBindingMode.TOPBAR_POPUP }); this.menuManager = new PopupMenu.PopupMenuManager(this);
this._leftBox = new St.BoxLayout({ name: 'panelLeft' }); this._leftBox = new St.BoxLayout({ name: 'panelLeft' });
this.actor.add_actor(this._leftBox); this.actor.add_actor(this._leftBox);
@@ -1015,18 +1106,17 @@ const Panel = new Lang.Class({
return true; return true;
}, },
toggleAppMenu: function() { openAppMenu: function() {
let indicator = this.statusArea.appMenu; let indicator = this.statusArea.appMenu;
if (!indicator) // appMenu not supported by current session mode if (!indicator) // appMenu not supported by current session mode
return; return;
let menu = indicator.menu; let menu = indicator.menu;
if (!indicator.actor.reactive) if (!indicator.actor.reactive || menu.isOpen)
return; return;
menu.toggle(); menu.open();
if (menu.isOpen) menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
}, },
set boxOpacity(value) { set boxOpacity(value) {

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -30,13 +30,144 @@ const EXEC_ARG_KEY = 'exec-arg';
const DIALOG_GROW_TIME = 0.1; 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({ const RunDialog = new Lang.Class({
Name: 'RunDialog', Name: 'RunDialog',
Extends: ModalDialog.ModalDialog, Extends: ModalDialog.ModalDialog,
_init : function() { _init : function() {
this.parent({ styleClass: 'run-dialog', this.parent({ styleClass: 'run-dialog' });
destroyOnClose: false });
this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA }); this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA });
this._terminalSettings = new Gio.Settings({ schema: TERMINAL_SCHEMA }); this._terminalSettings = new Gio.Settings({ schema: TERMINAL_SCHEMA });
@@ -111,6 +242,8 @@ const RunDialog = new Lang.Class({
key: Clutter.Escape }]); key: Clutter.Escape }]);
this._pathCompleter = new Gio.FilenameCompleter(); 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, this._history = new History.HistoryManager({ gsettingsKey: HISTORY_KEY,
entry: this._entryText }); entry: this._entryText });
@@ -126,6 +259,18 @@ const RunDialog = new Lang.Class({
return true; 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) { if (symbol == Clutter.Tab) {
let text = o.get_text(); let text = o.get_text();
let prefix; let prefix;
@@ -137,6 +282,8 @@ const RunDialog = new Lang.Class({
if (postfix != null && postfix.length > 0) { if (postfix != null && postfix.length > 0) {
o.insert_text(postfix, -1); o.insert_text(postfix, -1);
o.set_cursor_position(text.length + postfix.length); o.set_cursor_position(text.length + postfix.length);
if (postfix[postfix.length - 1] == '/')
this._getCompletion(text + postfix + 'a');
} }
return true; 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) { _getCompletion : function(text) {
if (text.indexOf('/') != -1) { if (text.indexOf('/') != -1) {
return this._pathCompleter.get_completion_suffix(text); return this._pathCompleter.get_completion_suffix(text);
} else { } else {
return this._getCommandCompletion(text); return this._commandCompleter.getCompletion(text);
} }
}, },

View File

@@ -1,12 +1,10 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const AccountsService = imports.gi.AccountsService;
const Cairo = imports.cairo; const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const GnomeDesktop = imports.gi.GnomeDesktop; const GnomeDesktop = imports.gi.GnomeDesktop;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
@@ -25,7 +23,6 @@ const Main = imports.ui.main;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const ShellDBus = imports.ui.shellDBus; const ShellDBus = imports.ui.shellDBus;
const SmartcardManager = imports.misc.smartcardManager;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const Util = imports.misc.util; const Util = imports.misc.util;
@@ -33,7 +30,6 @@ const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const LOCK_ENABLED_KEY = 'lock-enabled'; const LOCK_ENABLED_KEY = 'lock-enabled';
const LOCK_DELAY_KEY = 'lock-delay'; const LOCK_DELAY_KEY = 'lock-delay';
const LOCKED_STATE_STR = 'screenShield.locked';
// fraction of screen height the arrow must reach before completing // fraction of screen height the arrow must reach before completing
// the slide up automatically // the slide up automatically
const ARROW_DRAG_THRESHOLD = 0.1; const ARROW_DRAG_THRESHOLD = 0.1;
@@ -42,7 +38,11 @@ const ARROW_DRAG_THRESHOLD = 0.1;
const N_ARROWS = 3; const N_ARROWS = 3;
const ARROW_ANIMATION_TIME = 0.6; const ARROW_ANIMATION_TIME = 0.6;
const ARROW_ANIMATION_PEAK_OPACITY = 0.4; 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; const SUMMARY_ICON_SIZE = 48;
@@ -54,7 +54,7 @@ const SUMMARY_ICON_SIZE = 48;
// - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking // - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking
// - INITIAL_FADE_IN_TIME is used for the initial fade in at startup // - INITIAL_FADE_IN_TIME is used for the initial fade in at startup
const STANDARD_FADE_TIME = 10; const STANDARD_FADE_TIME = 10;
const MANUAL_FADE_TIME = 0.3; const MANUAL_FADE_TIME = 0.8;
const BACKGROUND_FADE_TIME = 1.0; const BACKGROUND_FADE_TIME = 1.0;
const CURTAIN_SLIDE_TIME = 0.3; const CURTAIN_SLIDE_TIME = 0.3;
const INITIAL_FADE_IN_TIME = 0.25; const INITIAL_FADE_IN_TIME = 0.25;
@@ -107,14 +107,13 @@ const NotificationsBox = new Lang.Class({
this._musicBin = new St.Bin({ style_class: 'screen-shield-notifications-box', this._musicBin = new St.Bin({ style_class: 'screen-shield-notifications-box',
visible: false }); visible: false });
this._scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START, let scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START });
hscrollbar_policy: Gtk.PolicyType.NEVER });
this._notificationBox = new St.BoxLayout({ vertical: true, this._notificationBox = new St.BoxLayout({ vertical: true,
style_class: 'screen-shield-notifications-box' }); style_class: 'screen-shield-notifications-box' });
this._scrollView.add_actor(this._notificationBox); scrollView.add_actor(this._notificationBox);
this.actor.add(this._musicBin); this.actor.add(this._musicBin);
this.actor.add(this._scrollView, { x_fill: true, x_align: St.Align.START }); this.actor.add(scrollView, { x_fill: true, x_align: St.Align.START });
this._sources = new Hash.Map(); this._sources = new Hash.Map();
Main.messageTray.getSources().forEach(Lang.bind(this, function(source) { Main.messageTray.getSources().forEach(Lang.bind(this, function(source) {
@@ -219,7 +218,6 @@ const NotificationsBox = new Lang.Class({
if (musicNotification != null && if (musicNotification != null &&
this._musicBin.child == null) { this._musicBin.child == null) {
musicNotification.acknowledged = true;
if (musicNotification.actor.get_parent() != null) if (musicNotification.actor.get_parent() != null)
musicNotification.actor.get_parent().remove_actor(musicNotification.actor); musicNotification.actor.get_parent().remove_actor(musicNotification.actor);
this._musicBin.child = musicNotification.actor; this._musicBin.child = musicNotification.actor;
@@ -240,7 +238,7 @@ const NotificationsBox = new Lang.Class({
(source.unseenCount > (musicNotification ? 1 : 0)); (source.unseenCount > (musicNotification ? 1 : 0));
}, },
_sourceAdded: function(tray, source, initial) { _sourceAdded: function(tray, source, dontUpdateVisibility) {
// Ignore transient sources // Ignore transient sources
if (source.isTransient) if (source.isTransient)
return; return;
@@ -252,7 +250,6 @@ const NotificationsBox = new Lang.Class({
sourceCountChangedId: 0, sourceCountChangedId: 0,
sourceTitleChangedId: 0, sourceTitleChangedId: 0,
sourceUpdatedId: 0, sourceUpdatedId: 0,
sourceNotifyId: 0,
musicNotification: null, musicNotification: null,
sourceBox: null, sourceBox: null,
titleLabel: null, titleLabel: null,
@@ -263,12 +260,6 @@ const NotificationsBox = new Lang.Class({
this._showSource(source, obj, obj.sourceBox); this._showSource(source, obj, obj.sourceBox);
this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START }); this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
if (obj.musicNotification) {
obj.sourceNotifyId = source.connect('notify', Lang.bind(this, function(source, notification) {
notification.acknowledged = true;
}));
}
obj.sourceCountChangedId = source.connect('count-updated', Lang.bind(this, function(source) { obj.sourceCountChangedId = source.connect('count-updated', Lang.bind(this, function(source) {
this._countChanged(source, obj); this._countChanged(source, obj);
})); }));
@@ -287,29 +278,8 @@ const NotificationsBox = new Lang.Class({
this._sources.set(source, obj); this._sources.set(source, obj);
if (!initial) { if (!dontUpdateVisibility)
// block scrollbars while animating, if they're not needed now
let boxHeight = this._notificationBox.height;
if (this._scrollView.height >= boxHeight)
this._scrollView.vscrollbar_policy = Gtk.PolicyType.NEVER;
let widget = obj.sourceBox;
let [, natHeight] = widget.get_preferred_height(-1);
widget.height = 0;
Tweener.addTween(widget,
{ height: natHeight,
transition: 'easeOutQuad',
time: 0.25,
onComplete: function() {
this._scrollView.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC;
widget.set_height(-1);
},
onCompleteScope: this
});
this._updateVisibility(); this._updateVisibility();
Shell.util_wake_up_screen();
}
}, },
_titleChanged: function(source, obj) { _titleChanged: function(source, obj) {
@@ -331,10 +301,7 @@ const NotificationsBox = new Lang.Class({
obj.sourceBox.visible = obj.visible && obj.sourceBox.visible = obj.visible &&
(source.unseenCount > (obj.musicNotification ? 1 : 0)); (source.unseenCount > (obj.musicNotification ? 1 : 0));
this._updateVisibility(); this._updateVisibility();
if (obj.sourceBox.visible)
Shell.util_wake_up_screen();
}, },
_visibleChanged: function(source, obj) { _visibleChanged: function(source, obj) {
@@ -348,8 +315,6 @@ const NotificationsBox = new Lang.Class({
source.unseenCount > (obj.musicNotification ? 1 : 0); source.unseenCount > (obj.musicNotification ? 1 : 0);
this._updateVisibility(); this._updateVisibility();
if (obj.sourceBox.visible)
Shell.util_wake_up_screen();
}, },
_detailedChanged: function(source, obj) { _detailedChanged: function(source, obj) {
@@ -375,8 +340,6 @@ const NotificationsBox = new Lang.Class({
if (obj.musicNotification) { if (obj.musicNotification) {
this._musicBin.child = null; this._musicBin.child = null;
obj.musicNotification = null; obj.musicNotification = null;
source.disconnect(obj.sourceNotifyId);
} }
source.disconnect(obj.sourceDestroyId); source.disconnect(obj.sourceDestroyId);
@@ -472,8 +435,8 @@ const ScreenShield = new Lang.Class({
name: 'lockScreenGroup', name: 'lockScreenGroup',
visible: false, visible: false,
}); });
this._lockScreenGroup.connect('key-press-event', this._lockScreenGroup.connect('key-release-event',
Lang.bind(this, this._onLockScreenKeyPress)); Lang.bind(this, this._onLockScreenKeyRelease));
this._lockScreenGroup.connect('scroll-event', this._lockScreenGroup.connect('scroll-event',
Lang.bind(this, this._onLockScreenScroll)); Lang.bind(this, this._onLockScreenScroll));
Main.ctrlAltTabManager.addGroup(this._lockScreenGroup, _("Lock"), 'changes-prevent-symbolic'); Main.ctrlAltTabManager.addGroup(this._lockScreenGroup, _("Lock"), 'changes-prevent-symbolic');
@@ -493,9 +456,6 @@ const ScreenShield = new Lang.Class({
this._updateBackgrounds(); this._updateBackgrounds();
Main.layoutManager.connect('monitors-changed', Lang.bind(this, 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', this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows',
vertical: true, vertical: true,
x_align: Clutter.ActorAlign.CENTER, x_align: Clutter.ActorAlign.CENTER,
@@ -519,7 +479,6 @@ const ScreenShield = new Lang.Class({
this._lockDialogGroup = new St.Widget({ x_expand: true, this._lockDialogGroup = new St.Widget({ x_expand: true,
y_expand: true, y_expand: true,
reactive: true,
opacity: 0, opacity: 0,
pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }), pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }),
name: 'lockDialogGroup' }); name: 'lockDialogGroup' });
@@ -547,26 +506,15 @@ const ScreenShield = new Lang.Class({
this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this); this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this);
this._smartcardManager = SmartcardManager.getSmartcardManager();
this._smartcardManager.connect('smartcard-inserted',
Lang.bind(this, function(token) {
if (this._isLocked && token.UsedToLogin)
this._liftShield(true, 0);
}));
this._inhibitor = null; this._inhibitor = null;
this._aboutToSuspend = false;
this._loginManager = LoginManager.getLoginManager(); this._loginManager = LoginManager.getLoginManager();
this._loginManager.connect('prepare-for-sleep', this._loginManager.connect('prepare-for-sleep',
Lang.bind(this, this._prepareForSleep)); Lang.bind(this, this._prepareForSleep));
this._inhibitSuspend(); this._inhibitSuspend();
this._loginManager.getCurrentSessionProxy(Lang.bind(this, this._loginSession = this._loginManager.getCurrentSessionProxy();
function(sessionProxy) { this._loginSession.connectSignal('Lock', Lang.bind(this, function() { this.lock(false); }));
this._loginSession = sessionProxy; this._loginSession.connectSignal('Unlock', Lang.bind(this, function() { this.deactivate(false); }));
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 }); this._settings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
@@ -580,17 +528,11 @@ const ScreenShield = new Lang.Class({
this._becameActiveId = 0; this._becameActiveId = 0;
this._lockTimeoutId = 0; this._lockTimeoutId = 0;
// The "long" lightbox is used for the longer (20 seconds) fade from session this._lightbox = new Lightbox.Lightbox(Main.uiGroup,
// to idle status, the "short" is used for quickly fading to black when locking { inhibitEvents: true,
// manually fadeInTime: STANDARD_FADE_TIME,
this._longLightbox = new Lightbox.Lightbox(Main.uiGroup, fadeFactor: 1 });
{ inhibitEvents: true, this._lightbox.connect('shown', Lang.bind(this, this._onLightboxShown));
fadeFactor: 1 });
this._longLightbox.connect('shown', Lang.bind(this, this._onLongLightboxShown));
this._shortLightbox = new Lightbox.Lightbox(Main.uiGroup,
{ inhibitEvents: true,
fadeFactor: 1 });
this._shortLightbox.connect('shown', Lang.bind(this, this._onShortLightboxShown));
this.idleMonitor = new GnomeDesktop.IdleMonitor(); this.idleMonitor = new GnomeDesktop.IdleMonitor();
}, },
@@ -605,8 +547,9 @@ const ScreenShield = new Lang.Class({
let bgManager = new Background.BackgroundManager({ container: widget, let bgManager = new Background.BackgroundManager({ container: widget,
monitorIndex: monitorIndex, monitorIndex: monitorIndex,
controlPosition: false, effects: Meta.BackgroundEffects.BLUR | Meta.BackgroundEffects.DESATURATE,
settingsSchema: SCREENSAVER_SCHEMA }); controlPosition: false });
bgManager.background.saturation = 0.6;
this._bgManagers.push(bgManager); this._bgManagers.push(bgManager);
@@ -618,7 +561,6 @@ const ScreenShield = new Lang.Class({
this._bgManagers[i].destroy(); this._bgManagers[i].destroy();
this._bgManagers = []; this._bgManagers = [];
this._backgroundGroup.destroy_all_children();
for (let i = 0; i < Main.layoutManager.monitors.length; i++) for (let i = 0; i < Main.layoutManager.monitors.length; i++)
this._createBackground(i); this._createBackground(i);
@@ -626,28 +568,13 @@ const ScreenShield = new Lang.Class({
_liftShield: function(onPrimary, velocity) { _liftShield: function(onPrimary, velocity) {
if (this._isLocked) { if (this._isLocked) {
if (this._ensureUnlockDialog(onPrimary, true /* allowCancel */)) this._ensureUnlockDialog(onPrimary, true /* allowCancel */);
this._hideLockScreen(true /* animate */, velocity); this._hideLockScreen(true /* animate */, velocity);
} else { } else {
this.deactivate(true /* animate */); this.deactivate(true /* animate */);
} }
}, },
_maybeCancelDialog: function() {
if (!this._dialog)
return;
this._dialog.cancel();
if (this._isGreeter) {
// LoginDialog.cancel() will grab the key focus
// on its own, so ensure it stays on lock screen
// instead
this._lockScreenGroup.grab_key_focus();
} else {
this._dialog = null;
}
},
_becomeModal: function() { _becomeModal: function() {
if (this._isModal) if (this._isModal)
return true; return true;
@@ -663,28 +590,28 @@ const ScreenShield = new Lang.Class({
return this._isModal; return this._isModal;
}, },
_onLockScreenKeyPress: function(actor, event) { _onLockScreenKeyRelease: function(actor, event) {
let symbol = event.get_key_symbol(); let symbol = event.get_key_symbol();
let unichar = event.get_key_unicode();
// Do nothing if the lock screen is not fully shown. // Do nothing if the lock screen is not fully shown.
// This avoids reusing the previous (and stale) unlock // This avoids reusing the previous (and stale) unlock
// dialog if esc is pressed while the curtain is going // dialog if esc is pressed while the curtain is going
// down after cancel. // 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) if (this._lockScreenState != MessageTray.State.SHOWN)
return false; return false;
let isEnter = (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_KP_Enter); if (symbol == Clutter.KEY_Escape ||
if (!isEnter && !(GLib.unichar_isprint(unichar) || symbol == Clutter.KEY_Escape)) symbol == Clutter.KEY_Return ||
return false; symbol == Clutter.KEY_KP_Enter) {
this._liftShield(false, 0);
return true;
}
if (this._isLocked && this._bumpLockScreen();
this._ensureUnlockDialog(true, true) &&
GLib.unichar_isgraph(unichar))
this._dialog.addCharacter(unichar);
this._liftShield(true, 0);
return true; return true;
}, },
@@ -722,8 +649,6 @@ const ScreenShield = new Lang.Class({
}, },
_prepareForSleep: function(loginManager, aboutToSuspend) { _prepareForSleep: function(loginManager, aboutToSuspend) {
this._aboutToSuspend = aboutToSuspend;
if (aboutToSuspend) { if (aboutToSuspend) {
if (!this._settings.get_boolean(LOCK_ENABLED_KEY)) { if (!this._settings.get_boolean(LOCK_ENABLED_KEY)) {
this._uninhibitSuspend(); this._uninhibitSuspend();
@@ -780,8 +705,6 @@ const ScreenShield = new Lang.Class({
}, },
_onDragEnd: function(action, actor, eventX, eventY, modifiers) { _onDragEnd: function(action, actor, eventX, eventY, modifiers) {
if (this._lockScreenState != MessageTray.State.HIDING)
return;
if (this._lockScreenGroup.y < -(ARROW_DRAG_THRESHOLD * global.stage.height)) { if (this._lockScreenGroup.y < -(ARROW_DRAG_THRESHOLD * global.stage.height)) {
// Complete motion automatically // Complete motion automatically
let [velocity, velocityX, velocityY] = this._dragAction.get_velocity(0); let [velocity, velocityX, velocityY] = this._dragAction.get_velocity(0);
@@ -803,7 +726,13 @@ const ScreenShield = new Lang.Class({
onCompleteScope: this, onCompleteScope: this,
}); });
this._maybeCancelDialog(); // If we have a unlock dialog, cancel it
if (this._dialog) {
this._dialog.cancel();
if (!this._isGreeter) {
this._dialog = null;
}
}
} }
}, },
@@ -811,9 +740,27 @@ const ScreenShield = new Lang.Class({
if (status != GnomeSession.PresenceStatus.IDLE) if (status != GnomeSession.PresenceStatus.IDLE)
return; return;
this._maybeCancelDialog(); if (this._dialog) {
this._dialog.cancel();
if (!this._isGreeter) {
this._dialog = null;
}
}
if (this._longLightbox.actor.visible || if (!this._becomeModal()) {
// We could not become modal, so we can't activate the
// screenshield. The user is probably very upset at this
// point, but any application using global grabs is broken
// Just tell him to stop using this app
//
// XXX: another option is to kick the user into the gdm login
// screen, where we're not affected by grabs
Main.notifyError(_("Unable to lock"),
_("Lock was blocked by an application"));
return;
}
if (this._lightbox.actor.visible ||
this._isActive) { this._isActive) {
// We're either shown and active, or in the process of // We're either shown and active, or in the process of
// showing. // showing.
@@ -826,22 +773,13 @@ const ScreenShield = new Lang.Class({
return; return;
} }
if (!this._becomeModal()) { this._lightbox.show();
// We could not become modal, so we can't activate the
// screenshield. The user is probably very upset at this
// point, but any application using global grabs is broken
// Just tell him to stop using this app
//
// XXX: another option is to kick the user into the gdm login
// screen, where we're not affected by grabs
Main.notifyError(_("Unable to lock"),
_("Lock was blocked by an application"));
return;
}
if (this._activationTime == 0) if (this._activationTime == 0)
this._activationTime = GLib.get_monotonic_time(); this._activationTime = GLib.get_monotonic_time();
this._becameActiveId = this.idleMonitor.add_user_active_watch(Lang.bind(this, this._onUserBecameActive));
let shouldLock = this._settings.get_boolean(LOCK_ENABLED_KEY) && !this._isLocked; let shouldLock = this._settings.get_boolean(LOCK_ENABLED_KEY) && !this._isLocked;
if (shouldLock) { if (shouldLock) {
@@ -849,59 +787,48 @@ const ScreenShield = new Lang.Class({
this._lockTimeoutId = Mainloop.timeout_add(lockTimeout * 1000, this._lockTimeoutId = Mainloop.timeout_add(lockTimeout * 1000,
Lang.bind(this, function() { Lang.bind(this, function() {
this._lockTimeoutId = 0; this._lockTimeoutId = 0;
this.lock(false); this.lock(true);
return false; return false;
})); }));
} }
this._activateFade(this._longLightbox, STANDARD_FADE_TIME);
},
_activateFade: function(lightbox, time) {
lightbox.show(time);
if (this._becameActiveId == 0)
this._becameActiveId = this.idleMonitor.add_user_active_watch(Lang.bind(this, this._onUserBecameActive));
}, },
_onUserBecameActive: function() { _onUserBecameActive: function() {
// This function gets called here when the user becomes active // This function gets called here when the user becomes active
// after we activated a lightbox // after gnome-session changed the status to IDLE
// There are two possibilities here: // There are four possibilities here:
// - we're called when already locked/active; isLocked or isActive is true, // - we're called when already locked; isActive and isLocked are true,
// we just go back to the lock screen curtain // we just go back to the lock screen curtain
// (isActive == isLocked == true: normal case // - we're called before the lightbox is fully shown; at this point
// isActive == false, isLocked == true: during the fade for manual locking // isActive is false, so we just hide the ligthbox, reset the activationTime
// isActive == true, isLocked == false: after session idle, before lock-delay) // and go back to the unlocked desktop
// - we're called because the session is IDLE but before the lightbox // - we're called after showing the lightbox, but before the lock
// is fully shown; at this point isActive is false, so we just hide // delay; this is mostly like the case above, but isActive is true now
// the lightbox, reset the activationTime and go back to the unlocked // so we need to notify gnome-settings-daemon to go back to the normal
// desktop // policies for blanking
// using deactivate() is a little of overkill, but it ensures we // (they're handled by the same code, and we emit one extra ActiveChanged
// don't forget of some bit like modal, DBus properties or idle watches // signal in the case above)
// // - we're called after showing the lightbox and after lock-delay; the
// Note: if the (long) lightbox is shown then we're necessarily // session is effectivelly locked now, it's time to build and show
// active, because we call activate() without animation. // the lock screen
this.idleMonitor.remove_watch(this._becameActiveId); this.idleMonitor.remove_watch(this._becameActiveId);
this._becameActiveId = 0; this._becameActiveId = 0;
if (this._isActive || this._isLocked) { let lightboxWasShown = this._lightbox.shown;
this._longLightbox.hide(); this._lightbox.hide();
this._shortLightbox.hide();
} else { // Shortcircuit in case the mouse was moved before the fade completed
if (!lightboxWasShown) {
this.deactivate(false); this.deactivate(false);
return;
} }
}, },
_onLongLightboxShown: function() { _onLightboxShown: function() {
this.activate(false); this.activate(false);
}, },
_onShortLightboxShown: function() {
this._completeLockScreenShown();
},
showDialog: function() { showDialog: function() {
// Ensure that the stage window is mapped, before taking a grab // Ensure that the stage window is mapped, before taking a grab
// otherwise X errors out // otherwise X errors out
@@ -918,8 +845,23 @@ const ScreenShield = new Lang.Class({
this.actor.show(); this.actor.show();
this._isGreeter = Main.sessionMode.isGreeter; this._isGreeter = Main.sessionMode.isGreeter;
this._isLocked = true; this._isLocked = true;
if (this._ensureUnlockDialog(true, true)) this._ensureUnlockDialog(true, true);
this._hideLockScreen(false, 0); this._hideLockScreen(false, 0);
},
_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() { _hideLockScreenComplete: function() {
@@ -936,8 +878,6 @@ const ScreenShield = new Lang.Class({
this._lockScreenState = MessageTray.State.HIDING; this._lockScreenState = MessageTray.State.HIDING;
Tweener.removeTweens(this._lockScreenGroup);
if (animate) { if (animate) {
// Tween the lock screen out of screen // Tween the lock screen out of screen
// if velocity is not specified (i.e. we come here from pressing ESC), // if velocity is not specified (i.e. we come here from pressing ESC),
@@ -950,6 +890,7 @@ const ScreenShield = new Lang.Class({
velocity = Math.max(min_velocity, velocity); velocity = Math.max(min_velocity, velocity);
let time = (delta / velocity) / 1000; let time = (delta / velocity) / 1000;
Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup, Tweener.addTween(this._lockScreenGroup,
{ y: -h, { y: -h,
time: time, time: time,
@@ -969,35 +910,38 @@ const ScreenShield = new Lang.Class({
if (!constructor) { if (!constructor) {
// This session mode has no locking capabilities // This session mode has no locking capabilities
this.deactivate(true); this.deactivate(true);
return false; return;
} }
this._dialog = new constructor(this._lockDialogGroup); this._dialog = new constructor(this._lockDialogGroup);
let time = global.get_current_time(); let time = global.get_current_time();
if (!this._dialog.open(time, onPrimary)) { this._dialog.connect('loaded', Lang.bind(this, function() {
// This is kind of an impossible error: we're already modal if (!this._dialog.open(time, onPrimary)) {
// by the time we reach this... // This is kind of an impossible error: we're already modal
log('Could not open login dialog: failed to acquire grab'); // by the time we reach this...
this.deactivate(true); log('Could not open login dialog: failed to acquire grab');
return false; this.deactivate(true);
} }
}));
this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed)); this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed));
this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded));
} }
this._dialog.allowCancel = allowCancel; this._dialog.allowCancel = allowCancel;
return true;
}, },
_onUnlockFailed: function() { _onUnlockFailed: function() {
this._resetLockScreen({ animateLockScreen: true, this._resetLockScreen(true, false);
animateLockDialog: false,
fadeToBlack: false });
}, },
_resetLockScreen: function(params) { _onUnlockSucceded: function() {
this.deactivate(true);
},
_resetLockScreen: function(animateLockScreen, animateLockDialog) {
// Don't reset the lock screen unless it is completely hidden // Don't reset the lock screen unless it is completely hidden
// This prevents the shield going down if the lock-delay timeout // This prevents the shield going down if the lock-delay timeout
// fires while the user is dragging (which has the potential // fires while the user is dragging (which has the potential
@@ -1012,9 +956,7 @@ const ScreenShield = new Lang.Class({
this._lockScreenGroup.show(); this._lockScreenGroup.show();
this._lockScreenState = MessageTray.State.SHOWING; this._lockScreenState = MessageTray.State.SHOWING;
let fadeToBlack = params.fadeToBlack; if (animateLockScreen) {
if (params.animateLockScreen) {
this._lockScreenGroup.y = -global.screen_height; this._lockScreenGroup.y = -global.screen_height;
Tweener.removeTweens(this._lockScreenGroup); Tweener.removeTweens(this._lockScreenGroup);
Tweener.addTween(this._lockScreenGroup, Tweener.addTween(this._lockScreenGroup,
@@ -1022,18 +964,16 @@ const ScreenShield = new Lang.Class({
time: MANUAL_FADE_TIME, time: MANUAL_FADE_TIME,
transition: 'easeOutQuad', transition: 'easeOutQuad',
onComplete: function() { onComplete: function() {
this._lockScreenShown({ fadeToBlack: fadeToBlack, this._lockScreenShown();
animateFade: true });
}, },
onCompleteScope: this onCompleteScope: this
}); });
} else { } else {
this._lockScreenGroup.fixed_position_set = false; this._lockScreenGroup.fixed_position_set = false;
this._lockScreenShown({ fadeToBlack: fadeToBlack, this._lockScreenShown();
animateFade: false });
} }
if (params.animateLockDialog) { if (animateLockDialog) {
this._lockDialogGroup.opacity = 0; this._lockDialogGroup.opacity = 0;
Tweener.removeTweens(this._lockDialogGroup); Tweener.removeTweens(this._lockDialogGroup);
Tweener.addTween(this._lockDialogGroup, Tweener.addTween(this._lockDialogGroup,
@@ -1050,60 +990,16 @@ const ScreenShield = new Lang.Class({
Main.sessionMode.pushMode('lock-screen'); Main.sessionMode.pushMode('lock-screen');
}, },
_startArrowAnimation: function() { _lockScreenShown: 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) {
if (this._dialog && !this._isGreeter) { if (this._dialog && !this._isGreeter) {
this._dialog.destroy(); this._dialog.destroy();
this._dialog = null; 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', function(stage, event) { let motionId = global.stage.connect('captured-event', function(stage, event) {
if (event.type() == Clutter.EventType.MOTION) { if (event.type() == Clutter.EventType.MOTION) {
@@ -1119,29 +1015,13 @@ const ScreenShield = new Lang.Class({
this._lockScreenGroup.fixed_position_set = false; this._lockScreenGroup.fixed_position_set = false;
this._lockScreenScrollCounter = 0; this._lockScreenScrollCounter = 0;
if (params.fadeToBlack && params.animateFade) {
// Take a beat
Mainloop.timeout_add(1000 * MANUAL_FADE_TIME, Lang.bind(this, function() {
this._activateFade(this._shortLightbox, MANUAL_FADE_TIME);
}));
} else {
if (params.fadeToBlack)
this._activateFade(this._shortLightbox, 0);
this._completeLockScreenShown();
}
},
_completeLockScreenShown: function() {
let prevIsActive = this._isActive; let prevIsActive = this._isActive;
this._isActive = true; this._isActive = true;
if (prevIsActive != this._isActive) if (prevIsActive != this._isActive)
this.emit('active-changed'); this.emit('active-changed');
if (this._aboutToSuspend) this._uninhibitSuspend();
this._uninhibitSuspend();
this.emit('lock-screen-shown'); this.emit('lock-screen-shown');
}, },
@@ -1181,10 +1061,13 @@ const ScreenShield = new Lang.Class({
this._notificationsBox = null; this._notificationsBox = null;
} }
this._stopArrowAnimation();
this._lockScreenContentsBox.destroy(); this._lockScreenContentsBox.destroy();
if (this._arrowAnimationId) {
Mainloop.source_remove(this._arrowAnimationId);
this._arrowAnimationId = 0;
}
this._hasLockScreen = false; this._hasLockScreen = false;
}, },
@@ -1201,47 +1084,13 @@ const ScreenShield = new Lang.Class({
}, },
deactivate: function(animate) { deactivate: function(animate) {
if (this._dialog)
this._dialog.finish(Lang.bind(this, function() {
this._continueDeactivate(animate);
}));
else
this._continueDeactivate(animate);
},
_continueDeactivate: function(animate) {
this._hideLockScreen(animate, 0); this._hideLockScreen(animate, 0);
if (this._hasLockScreen)
this._clearLockScreen();
if (Main.sessionMode.currentMode == 'lock-screen') if (Main.sessionMode.currentMode == 'lock-screen')
Main.sessionMode.popMode('lock-screen'); Main.sessionMode.popMode('lock-screen');
if (Main.sessionMode.currentMode == 'unlock-dialog') if (Main.sessionMode.currentMode == 'unlock-dialog')
Main.sessionMode.popMode('unlock-dialog'); Main.sessionMode.popMode('unlock-dialog');
if (this._isGreeter) {
// We don't want to "deactivate" any more than
// this. In particular, we don't want to drop
// the modal, hide ourselves or destroy the dialog
// But we do want to set isActive to false, so that
// gnome-session will reset the idle counter, and
// gnome-settings-daemon will stop blanking the screen
this._activationTime = 0;
this._isActive = false;
this.emit('active-changed');
return;
}
if (this._dialog && !this._isGreeter)
this._dialog.popModal();
if (this._isModal) {
Main.popModal(this.actor);
this._isModal = false;
}
Tweener.addTween(this._lockDialogGroup, { Tweener.addTween(this._lockDialogGroup, {
scale_x: 0, scale_x: 0,
scale_y: 0, scale_y: 0,
@@ -1253,13 +1102,21 @@ const ScreenShield = new Lang.Class({
}, },
_completeDeactivate: function() { _completeDeactivate: function() {
if (this._dialog) { if (this._hasLockScreen)
this._clearLockScreen();
if (this._dialog && !this._isGreeter) {
this._dialog.destroy(); this._dialog.destroy();
this._dialog = null; this._dialog = null;
} }
this._longLightbox.hide(); this._lightbox.hide();
this._shortLightbox.hide();
if (this._isModal) {
Main.popModal(this.actor);
this._isModal = false;
}
this.actor.hide(); this.actor.hide();
if (this._becameActiveId != 0) { if (this._becameActiveId != 0) {
@@ -1277,7 +1134,6 @@ const ScreenShield = new Lang.Class({
this._isLocked = false; this._isLocked = false;
this.emit('active-changed'); this.emit('active-changed');
this.emit('locked-changed'); this.emit('locked-changed');
global.set_runtime_state(LOCKED_STATE_STR, null);
}, },
activate: function(animate) { activate: function(animate) {
@@ -1293,10 +1149,7 @@ const ScreenShield = new Lang.Class({
Main.sessionMode.pushMode('unlock-dialog'); Main.sessionMode.pushMode('unlock-dialog');
} }
this._resetLockScreen({ animateLockScreen: animate, this._resetLockScreen(animate, animate);
animateLockDialog: animate,
fadeToBlack: true });
global.set_runtime_state(LOCKED_STATE_STR, GLib.Variant.new('b', true));
// We used to set isActive and emit active-changed here, // We used to set isActive and emit active-changed here,
// but now we do that from lockScreenShown, which means // but now we do that from lockScreenShown, which means
@@ -1318,33 +1171,69 @@ const ScreenShield = new Lang.Class({
return; return;
} }
// Clear the clipboard - otherwise, its contents may be leaked this._isLocked = true;
// to unauthorized parties by pasting into the unlock dialog's
// password entry and unmasking the entry
St.Clipboard.get_default().set_text(St.ClipboardType.CLIPBOARD, '');
St.Clipboard.get_default().set_text(St.ClipboardType.PRIMARY, '');
let userManager = AccountsService.UserManager.get_default();
let user = userManager.get_user(GLib.get_user_name());
if (this._isGreeter)
this._isLocked = true;
else
this._isLocked = user.password_mode != AccountsService.UserPasswordMode.NONE;
this.activate(animate); this.activate(animate);
this.emit('locked-changed'); this.emit('locked-changed');
}, },
// If the previous shell crashed, and gnome-session restarted us, then re-lock
lockIfWasLocked: function() {
let wasLocked = global.get_runtime_state('b', LOCKED_STATE_STR);
if (wasLocked === null)
return;
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
this.lock(false);
}));
}
}); });
Signals.addSignalMethods(ScreenShield.prototype); Signals.addSignalMethods(ScreenShield.prototype);
/* 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);

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter;
const Lang = imports.lang; const Lang = imports.lang;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
const Signals = imports.signals;
const St = imports.gi.St; const St = imports.gi.St;
const Atk = imports.gi.Atk; const Atk = imports.gi.Atk;
@@ -14,7 +13,6 @@ const Main = imports.ui.main;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
const Separator = imports.ui.separator; const Separator = imports.ui.separator;
const Search = imports.ui.search; const Search = imports.ui.search;
const Util = imports.misc.util;
const MAX_LIST_SEARCH_RESULTS_ROWS = 3; const MAX_LIST_SEARCH_RESULTS_ROWS = 3;
const MAX_GRID_SEARCH_RESULTS_ROWS = 1; const MAX_GRID_SEARCH_RESULTS_ROWS = 1;
@@ -104,7 +102,6 @@ const ListSearchResult = new Lang.Class({
y_fill: false, y_fill: false,
x_align: St.Align.START, x_align: St.Align.START,
y_align: St.Align.START }); y_align: St.Align.START });
this.actor.label_actor = title;
if (this.metaInfo['description']) { if (this.metaInfo['description']) {
let description = new St.Label({ style_class: 'list-search-result-description' }); let description = new St.Label({ style_class: 'list-search-result-description' });
@@ -142,7 +139,6 @@ const GridSearchResult = new Lang.Class({
} }
this.actor.set_child(content); this.actor.set_child(content);
this.actor.label_actor = content.label_actor;
let draggable = DND.makeDraggable(this.actor); let draggable = DND.makeDraggable(this.actor);
draggable.connect('drag-begin', draggable.connect('drag-begin',
@@ -180,82 +176,13 @@ const GridSearchResult = new Lang.Class({
} }
}); });
const SearchResultsBase = new Lang.Class({ const ListSearchResults = new Lang.Class({
Name: 'SearchResultsBase', Name: 'ListSearchResults',
_init: function(provider) { _init: function(provider) {
this.provider = provider; this.provider = provider;
this._terms = []; this.actor = new St.BoxLayout({ style_class: 'search-section-content' });
this.actor = new St.BoxLayout({ style_class: 'search-section',
vertical: true });
this._resultDisplayBin = new St.Bin({ x_fill: true,
y_fill: true });
this.actor.add(this._resultDisplayBin, { expand: true });
let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' });
this.actor.add(separator.actor);
},
destroy: function() {
this.actor.destroy();
this._terms = [];
},
_clearResultDisplay: function() {
},
clear: function() {
this._clearResultDisplay();
this.actor.hide();
},
_keyFocusIn: function(icon) {
this.emit('key-focus-in', icon);
},
_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 = providerResults.slice(0, maxResults);
let hasMoreResults = results.length < providerResults.length;
this.provider.getResultMetas(results, Lang.bind(this, function(metas) {
this.clear();
// To avoid CSS transitions causing flickering when
// the first search result stays the same, we hide the
// content while filling in the results.
this.actor.hide();
this._clearResultDisplay();
this._renderResults(metas);
this._setMoreIconVisible(hasMoreResults && this.provider.canLaunchSearch);
this.actor.show();
callback();
}));
}
}
});
const ListSearchResults = new Lang.Class({
Name: 'ListSearchResults',
Extends: SearchResultsBase,
_init: function(provider) {
this.parent(provider);
this._container = new St.BoxLayout({ style_class: 'search-section-content' });
this.providerIcon = new ProviderIcon(provider); this.providerIcon = new ProviderIcon(provider);
this.providerIcon.connect('clicked', Lang.bind(this, this.providerIcon.connect('clicked', Lang.bind(this,
function() { function() {
@@ -263,86 +190,123 @@ const ListSearchResults = new Lang.Class({
Main.overview.toggle(); Main.overview.toggle();
})); }));
this._container.add(this.providerIcon, { x_fill: false, this.actor.add(this.providerIcon, { x_fill: false,
y_fill: false, y_fill: false,
x_align: St.Align.START, x_align: St.Align.START,
y_align: St.Align.START }); y_align: St.Align.START });
this._content = new St.BoxLayout({ style_class: 'list-search-results', this._content = new St.BoxLayout({ style_class: 'list-search-results',
vertical: true }); vertical: true });
this._container.add(this._content, { expand: true }); this.actor.add(this._content, { expand: true });
this._resultDisplayBin.set_child(this._container); this._notDisplayedResult = [];
this._terms = [];
this._pendingClear = false;
}, },
_setMoreIconVisible: function(visible) { getResultsForDisplay: function() {
this.providerIcon.moreIcon.visible = true; let alreadyVisible = this._pendingClear ? 0 : this.getVisibleResultCount();
let canDisplay = MAX_LIST_SEARCH_RESULTS_ROWS - alreadyVisible;
let newResults = this._notDisplayedResult.splice(0, canDisplay);
return newResults;
}, },
_getMaxDisplayedResults: function() { getVisibleResultCount: function() {
return MAX_LIST_SEARCH_RESULTS_ROWS; return this._content.get_n_children();
}, },
_renderResults: function(metas) { hasMoreResults: function() {
return this._notDisplayedResult.length > 0;
},
setResults: function(results, terms) {
// copy the lists
this._notDisplayedResult = results.slice(0);
this._terms = terms.slice(0);
this._pendingClear = true;
},
renderResults: function(metas) {
for (let i = 0; i < metas.length; i++) { for (let i = 0; i < metas.length; i++) {
let display = new ListSearchResult(this.provider, metas[i], this._terms); let display = new ListSearchResult(this.provider, metas[i], this._terms);
display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
this._content.add_actor(display.actor); this._content.add_actor(display.actor);
} }
}, },
_clearResultDisplay: function () { clear: function () {
this._content.destroy_all_children(); this._content.destroy_all_children();
this._pendingClear = false;
}, },
getFirstResult: function() { getFirstResult: function() {
if (this._content.get_n_children() > 0) if (this.getVisibleResultCount() > 0)
return this._content.get_child_at_index(0)._delegate; return this._content.get_child_at_index(0)._delegate;
else else
return null; return null;
} }
}); });
Signals.addSignalMethods(ListSearchResults.prototype);
const GridSearchResults = new Lang.Class({ const GridSearchResults = new Lang.Class({
Name: 'GridSearchResults', Name: 'GridSearchResults',
Extends: SearchResultsBase,
_init: function(provider) { _init: function(provider) {
this.parent(provider); this.provider = provider;
this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS, this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
xAlign: St.Align.START }); xAlign: St.Align.START });
this._bin = new St.Bin({ x_align: St.Align.MIDDLE }); this.actor = new St.Bin({ x_align: St.Align.MIDDLE });
this._bin.set_child(this._grid.actor);
this._resultDisplayBin.set_child(this._bin); this.actor.set_child(this._grid.actor);
this._notDisplayedResult = [];
this._terms = [];
this._pendingClear = false;
}, },
_getMaxDisplayedResults: function() { getResultsForDisplay: function() {
return this._grid.childrenInRow(this._bin.width) * this._grid.getRowLimit(); let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
let canDisplay = this._grid.childrenInRow(this.actor.width) * this._grid.getRowLimit()
- alreadyVisible;
let newResults = this._notDisplayedResult.splice(0, canDisplay);
return newResults;
}, },
_renderResults: function(metas) { getVisibleResultCount: function() {
return this._grid.visibleItemsCount();
},
hasMoreResults: function() {
return this._notDisplayedResult.length > 0;
},
setResults: function(results, terms) {
// copy the lists
this._notDisplayedResult = results.slice(0);
this._terms = terms.slice(0);
this._pendingClear = true;
},
renderResults: function(metas) {
for (let i = 0; i < metas.length; i++) { for (let i = 0; i < metas.length; i++) {
let display = new GridSearchResult(this.provider, metas[i], this._terms); let display = new GridSearchResult(this.provider, metas[i], this._terms);
display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
this._grid.addItem(display.actor); this._grid.addItem(display.actor);
} }
}, },
_clearResultDisplay: function () { clear: function () {
this._grid.removeAll(); this._grid.removeAll();
this._pendingClear = false;
}, },
getFirstResult: function() { getFirstResult: function() {
if (this._grid.visibleItemsCount() > 0) if (this.getVisibleResultCount() > 0)
return this._grid.getItemAtIndex(0)._delegate; return this._grid.getItemAtIndex(0)._delegate;
else else
return null; return null;
} }
}); });
Signals.addSignalMethods(GridSearchResults.prototype);
const SearchResults = new Lang.Class({ const SearchResults = new Lang.Class({
Name: 'SearchResults', Name: 'SearchResults',
@@ -386,9 +350,9 @@ const SearchResults = new Lang.Class({
this._content.add(this._statusBin, { expand: true }); this._content.add(this._statusBin, { expand: true });
this._statusBin.add_actor(this._statusText); this._statusBin.add_actor(this._statusText);
this._providers = this._searchSystem.getProviders(); this._providers = this._searchSystem.getProviders();
this._providerDisplays = {}; this._providerMeta = [];
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providers.length; i++) {
this.createProviderDisplay(this._providers[i]); this.createProviderMeta(this._providers[i]);
} }
this._highlightDefault = false; this._highlightDefault = false;
@@ -402,37 +366,59 @@ const SearchResults = new Lang.Class({
return false; return false;
}, },
_keyFocusIn: function(provider, icon) { createProviderMeta: function(provider) {
Util.ensureActorVisibleInScrollView(this._scrollView, icon); let providerBox = new St.BoxLayout({ style_class: 'search-section',
}, vertical: true });
let providerIcon = null;
createProviderDisplay: function(provider) { let resultDisplay = null;
let providerDisplay = null;
if (provider.appInfo) { if (provider.appInfo) {
providerDisplay = new ListSearchResults(provider); resultDisplay = new ListSearchResults(provider);
providerIcon = resultDisplay.providerIcon;
} else { } else {
providerDisplay = new GridSearchResults(provider); resultDisplay = new GridSearchResults(provider);
} }
providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); let resultDisplayBin = new St.Bin({ child: resultDisplay.actor,
this._providerDisplays[provider.id] = providerDisplay; x_fill: true,
this._content.add(providerDisplay.actor); y_fill: true });
providerBox.add(resultDisplayBin, { expand: true });
let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' });
providerBox.add(separator.actor);
this._providerMeta.push({ provider: provider,
actor: providerBox,
icon: providerIcon,
resultDisplay: resultDisplay });
this._content.add(providerBox);
}, },
destroyProviderDisplay: function(provider) { destroyProviderMeta: function(provider) {
this._providerDisplays[provider.id].destroy(); for (let i=0; i < this._providerMeta.length; i++) {
delete this._providerDisplays[provider.id]; let meta = this._providerMeta[i];
if (meta.provider == provider) {
meta.actor.destroy();
this._providerMeta.splice(i, 1);
break;
}
}
}, },
_clearDisplay: function() { _clearDisplay: function() {
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providerMeta.length; i++) {
let provider = this._providers[i]; let meta = this._providerMeta[i];
let providerDisplay = this._providerDisplays[provider.id]; meta.resultDisplay.clear();
providerDisplay.clear(); meta.actor.hide();
} }
}, },
_clearDisplayForProvider: function(provider) {
let meta = this._metaForProvider(provider);
meta.resultDisplay.clear();
meta.actor.hide();
},
reset: function() { reset: function() {
this._searchSystem.reset(); this._searchSystem.reset();
this._statusBin.hide(); this._statusBin.hide();
@@ -446,17 +432,20 @@ const SearchResults = new Lang.Class({
this._statusBin.show(); this._statusBin.show();
}, },
_metaForProvider: function(provider) {
return this._providerMeta[this._providers.indexOf(provider)];
},
_maybeSetInitialSelection: function() { _maybeSetInitialSelection: function() {
let newDefaultResult = null; let newDefaultResult = null;
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providerMeta.length; i++) {
let provider = this._providers[i]; let meta = this._providerMeta[i];
let display = this._providerDisplays[provider.id];
if (!display.actor.visible) if (!meta.actor.visible)
continue; continue;
let firstResult = display.getFirstResult(); let firstResult = meta.resultDisplay.getFirstResult();
if (firstResult) { if (firstResult) {
newDefaultResult = firstResult; newDefaultResult = firstResult;
break; // select this one! break; // select this one!
@@ -476,14 +465,11 @@ const SearchResults = new Lang.Class({
_updateStatusText: function () { _updateStatusText: function () {
let haveResults = false; let haveResults = false;
for (let i = 0; i < this._providers.length; i++) { for (let i = 0; i < this._providerMeta.length; ++i)
let provider = this._providers[i]; if (this._providerMeta[i].resultDisplay.getFirstResult()) {
let display = this._providerDisplays[provider.id];
if (display.getFirstResult()) {
haveResults = true; haveResults = true;
break; break;
} }
}
if (!haveResults) { if (!haveResults) {
this._statusText.set_text(_("No results.")); this._statusText.set_text(_("No results."));
@@ -496,12 +482,42 @@ const SearchResults = new Lang.Class({
_updateResults: function(searchSystem, results) { _updateResults: function(searchSystem, results) {
let terms = searchSystem.getTerms(); let terms = searchSystem.getTerms();
let [provider, providerResults] = results; let [provider, providerResults] = results;
let display = this._providerDisplays[provider.id]; let meta = this._metaForProvider(provider);
display.updateSearch(providerResults, terms, Lang.bind(this, function() { if (providerResults.length == 0) {
this._clearDisplayForProvider(provider);
meta.resultDisplay.setResults([], []);
this._maybeSetInitialSelection(); this._maybeSetInitialSelection();
this._updateStatusText(); this._updateStatusText();
})); } else {
meta.resultDisplay.setResults(providerResults, terms);
let results = meta.resultDisplay.getResultsForDisplay();
if (meta.icon)
meta.icon.moreIcon.visible =
meta.resultDisplay.hasMoreResults() &&
provider.canLaunchSearch;
provider.getResultMetas(results, Lang.bind(this, function(metas) {
this._clearDisplayForProvider(provider);
meta.actor.show();
// Hiding drops the key focus if we have it
let focus = global.stage.get_key_focus();
// To avoid CSS transitions causing flickering when
// the first search result stays the same, we hide the
// content while filling in the results.
this._content.hide();
meta.resultDisplay.renderResults(metas);
this._maybeSetInitialSelection();
this._updateStatusText();
this._content.show();
if (this._content.contains(focus))
global.stage.set_key_focus(focus);
}));
}
}, },
activateDefault: function() { activateDefault: function() {
@@ -541,7 +557,6 @@ const ProviderIcon = new Lang.Class({
this.parent({ style_class: 'search-provider-icon', this.parent({ style_class: 'search-provider-icon',
reactive: true, reactive: true,
can_focus: true, can_focus: true,
accessible_name: provider.appInfo.get_name(),
track_hover: true }); track_hover: true });
this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() }); this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() });

View File

@@ -16,12 +16,10 @@ const _modes = {
'restrictive': { 'restrictive': {
parentMode: null, parentMode: null,
stylesheetName: 'gnome-shell.css', stylesheetName: 'gnome-shell.css',
overridesSchema: 'org.gnome.shell.overrides',
hasOverview: false, hasOverview: false,
showCalendarEvents: false, showCalendarEvents: false,
allowSettings: false, allowSettings: false,
allowExtensions: false, allowExtensions: false,
allowScreencast: false,
enabledExtensions: [], enabledExtensions: [],
hasRunDialog: false, hasRunDialog: false,
hasWorkspaces: false, hasWorkspaces: false,
@@ -47,9 +45,10 @@ const _modes = {
unlockDialog: imports.gdm.loginDialog.LoginDialog, unlockDialog: imports.gdm.loginDialog.LoginDialog,
components: ['polkitAgent'], components: ['polkitAgent'],
panel: { panel: {
left: [], left: ['logo'],
center: ['dateMenu'], center: ['dateMenu'],
right: ['a11yGreeter', 'keyboard', 'aggregateMenu'], right: ['a11yGreeter', 'display', 'keyboard',
'volume', 'battery', 'powerMenu']
}, },
panelStyle: 'login-screen' panelStyle: 'login-screen'
}, },
@@ -60,9 +59,9 @@ const _modes = {
unlockDialog: undefined, unlockDialog: undefined,
components: ['polkitAgent', 'telepathyClient'], components: ['polkitAgent', 'telepathyClient'],
panel: { panel: {
left: [], left: ['userMenu'],
center: [], center: [],
right: ['aggregateMenu'] right: ['lockScreen']
}, },
panelStyle: 'lock-screen' panelStyle: 'lock-screen'
}, },
@@ -72,19 +71,28 @@ const _modes = {
unlockDialog: undefined, unlockDialog: undefined,
components: ['polkitAgent', 'telepathyClient'], components: ['polkitAgent', 'telepathyClient'],
panel: { panel: {
left: [], left: ['userMenu'],
center: [], center: [],
right: ['a11y', 'keyboard', 'aggregateMenu'] right: ['a11y', 'keyboard', 'lockScreen']
}, },
panelStyle: 'unlock-screen' panelStyle: 'unlock-screen'
}, },
'initial-setup': {
isPrimary: true,
components: ['keyring'],
panel: {
left: [],
center: ['dateMenu'],
right: ['a11yGreeter', 'keyboard', 'volume']
}
},
'user': { 'user': {
hasOverview: true, hasOverview: true,
showCalendarEvents: true, showCalendarEvents: true,
allowSettings: true, allowSettings: true,
allowExtensions: true, allowExtensions: true,
allowScreencast: true,
hasRunDialog: true, hasRunDialog: true,
hasWorkspaces: true, hasWorkspaces: true,
hasWindows: true, hasWindows: true,
@@ -93,11 +101,12 @@ const _modes = {
isPrimary: true, isPrimary: true,
unlockDialog: imports.ui.unlockDialog.UnlockDialog, unlockDialog: imports.ui.unlockDialog.UnlockDialog,
components: ['networkAgent', 'polkitAgent', 'telepathyClient', components: ['networkAgent', 'polkitAgent', 'telepathyClient',
'keyring', 'autorunManager', 'automountManager'], 'keyring', 'recorder', 'autorunManager', 'automountManager'],
panel: { panel: {
left: ['activities', 'appMenu'], left: ['activities', 'appMenu'],
center: ['dateMenu'], center: ['dateMenu'],
right: ['a11y', 'keyboard', 'aggregateMenu'] right: ['a11y', 'keyboard', 'volume', 'bluetooth',
'network', 'battery', 'userMenu']
} }
} }
}; };
@@ -149,7 +158,12 @@ function listModes() {
const SessionMode = new Lang.Class({ const SessionMode = new Lang.Class({
Name: 'SessionMode', 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) { _getModes(Lang.bind(this, function(modes) {
this._modes = modes; this._modes = modes;
let primary = modes[global.session_mode] && let primary = modes[global.session_mode] &&
@@ -157,8 +171,6 @@ const SessionMode = new Lang.Class({
let mode = primary ? global.session_mode : 'user'; let mode = primary ? global.session_mode : 'user';
this._modeStack = [mode]; this._modeStack = [mode];
this._sync(); this._sync();
this.emit('sessions-loaded');
})); }));
}, },

View File

@@ -20,7 +20,6 @@ const GnomeShellIface = <interface name="org.gnome.Shell">
<arg type="b" direction="out" name="success" /> <arg type="b" direction="out" name="success" />
<arg type="s" direction="out" name="result" /> <arg type="s" direction="out" name="result" />
</method> </method>
<method name="FocusSearch"/>
<method name="ShowOSD"> <method name="ShowOSD">
<arg type="a{sv}" direction="in" name="params"/> <arg type="a{sv}" direction="in" name="params"/>
</method> </method>
@@ -40,7 +39,6 @@ const GnomeShellIface = <interface name="org.gnome.Shell">
<signal name="AcceleratorActivated"> <signal name="AcceleratorActivated">
<arg name="action" type="u" /> <arg name="action" type="u" />
<arg name="deviceid" type="u" /> <arg name="deviceid" type="u" />
<arg name="timestamp" type="u" />
</signal> </signal>
<property name="Mode" type="s" access="read" /> <property name="Mode" type="s" access="read" />
<property name="OverviewActive" type="b" access="readwrite" /> <property name="OverviewActive" type="b" access="readwrite" />
@@ -78,8 +76,8 @@ const GnomeShell = new Lang.Class({
this._grabbers = new Hash.Map(); this._grabbers = new Hash.Map();
global.display.connect('accelerator-activated', Lang.bind(this, global.display.connect('accelerator-activated', Lang.bind(this,
function(display, action, deviceid, timestamp) { function(display, action, deviceid) {
this._emitAcceleratorActivated(action, deviceid, timestamp); this._emitAcceleratorActivated(action, deviceid);
})); }));
}, },
@@ -99,7 +97,7 @@ const GnomeShell = new Lang.Class({
*/ */
Eval: function(code) { Eval: function(code) {
if (!global.settings.get_boolean('development-tools')) if (!global.settings.get_boolean('development-tools'))
return [false, '']; return [false, null];
let returnValue; let returnValue;
let success; let success;
@@ -116,10 +114,6 @@ const GnomeShell = new Lang.Class({
return [success, returnValue]; return [success, returnValue];
}, },
FocusSearch: function() {
Main.overview.focusSearch();
},
ShowOSD: function(params) { ShowOSD: function(params) {
for (let param in params) for (let param in params)
params[param] = params[param].deep_unpack(); params[param] = params[param].deep_unpack();
@@ -165,7 +159,7 @@ const GnomeShell = new Lang.Class({
return invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded])); return invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded]));
}, },
_emitAcceleratorActivated: function(action, deviceid, timestamp) { _emitAcceleratorActivated: function(action, deviceid) {
let destination = this._grabbedAccelerators.get(action); let destination = this._grabbedAccelerators.get(action);
if (!destination) if (!destination)
return; return;
@@ -176,7 +170,7 @@ const GnomeShell = new Lang.Class({
this._dbusImpl.get_object_path(), this._dbusImpl.get_object_path(),
info ? info.name : null, info ? info.name : null,
'AcceleratorActivated', 'AcceleratorActivated',
GLib.Variant.new('(uuu)', [action, deviceid, timestamp])); GLib.Variant.new('(uu)', [action, deviceid]));
}, },
_grabAcceleratorForSender: function(accelerator, flags, sender) { _grabAcceleratorForSender: function(accelerator, flags, sender) {
@@ -403,7 +397,7 @@ const ScreenSaverDBus = new Lang.Class({
if (active) if (active)
this._screenShield.activate(true); this._screenShield.activate(true);
else else
this._screenShield.deactivate(false); this._screenShield.unlock(false);
}, },
GetActive: function() { GetActive: function() {

View File

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

View File

@@ -1,210 +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 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 });
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;
},
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._slider.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);
},
get value() {
return this._value;
}
});
Signals.addSignalMethods(Slider.prototype);

View File

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

View File

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

View File

@@ -1,63 +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));
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);
}));
},
_sliderChanged: function(slider, value) {
let percent = value * 100;
this._proxy.Brightness = percent;
},
_sync: function() {
let visible = this._proxy.Brightness >= 0;
this._item.actor.visible = visible;
if (visible)
this._slider.setValue(this._proxy.Brightness / 100.0);
},
});

View File

@@ -9,7 +9,6 @@ const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const St = imports.gi.St; const St = imports.gi.St;
const Gettext = imports.gettext;
try { try {
var IBus = imports.gi.IBus; var IBus = imports.gi.IBus;
@@ -34,33 +33,6 @@ const KEY_INPUT_SOURCES = 'sources';
const INPUT_SOURCE_TYPE_XKB = 'xkb'; const INPUT_SOURCE_TYPE_XKB = 'xkb';
const INPUT_SOURCE_TYPE_IBUS = 'ibus'; const INPUT_SOURCE_TYPE_IBUS = 'ibus';
// This is the longest we'll keep the keyboard frozen until an input
// 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({ const IBusManager = new Lang.Class({
Name: 'IBusManager', Name: 'IBusManager',
@@ -73,24 +45,26 @@ const IBusManager = new Lang.Class({
this._readyCallback = readyCallback; this._readyCallback = readyCallback;
this._candidatePopup = new IBusCandidatePopup.CandidatePopup(); this._candidatePopup = new IBusCandidatePopup.CandidatePopup();
this._ibus = null;
this._panelService = null; this._panelService = null;
this._engines = {}; this._engines = {};
this._ready = false; this._ready = false;
this._registerPropertiesId = 0; this._registerPropertiesId = 0;
this._currentEngineName = null; this._currentEngineName = null;
this._ibus = IBus.Bus.new_async(); this._nameWatcherId = Gio.DBus.session.watch_name(IBus.SERVICE_IBUS,
this._ibus.connect('connected', Lang.bind(this, this._onConnected)); Gio.BusNameWatcherFlags.NONE,
this._ibus.connect('disconnected', Lang.bind(this, this._clear)); Lang.bind(this, this._onNameAppeared),
// Need to set this to get 'global-engine-changed' emitions Lang.bind(this, this._clear));
this._ibus.set_watch_ibus_signal(true);
this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged));
}, },
_clear: function() { _clear: function() {
if (this._panelService) if (this._panelService)
this._panelService.destroy(); this._panelService.destroy();
if (this._ibus)
this._ibus.destroy();
this._ibus = null;
this._panelService = null; this._panelService = null;
this._candidatePopup.setPanelService(null); this._candidatePopup.setPanelService(null);
this._engines = {}; this._engines = {};
@@ -102,12 +76,18 @@ const IBusManager = new Lang.Class({
this._readyCallback(false); this._readyCallback(false);
}, },
_onNameAppeared: function() {
this._ibus = IBus.Bus.new_async();
this._ibus.connect('connected', Lang.bind(this, this._onConnected));
},
_onConnected: function() { _onConnected: function() {
this._ibus.list_engines_async(-1, null, Lang.bind(this, this._initEngines)); this._ibus.list_engines_async(-1, null, Lang.bind(this, this._initEngines));
this._ibus.request_name_async(IBus.SERVICE_PANEL, this._ibus.request_name_async(IBus.SERVICE_PANEL,
IBus.BusNameFlag.REPLACE_EXISTING, IBus.BusNameFlag.REPLACE_EXISTING,
-1, null, -1, null,
Lang.bind(this, this._initPanelService)); Lang.bind(this, this._initPanelService));
this._ibus.connect('disconnected', Lang.bind(this, this._clear));
}, },
_initEngines: function(ibus, result) { _initEngines: function(ibus, result) {
@@ -129,6 +109,9 @@ const IBusManager = new Lang.Class({
this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(), this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(),
object_path: IBus.PATH_PANEL }); object_path: IBus.PATH_PANEL });
this._candidatePopup.setPanelService(this._panelService); this._candidatePopup.setPanelService(this._panelService);
// Need to set this to get 'global-engine-changed' emitions
this._ibus.set_watch_ibus_signal(true);
this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged));
this._panelService.connect('update-property', Lang.bind(this, this._updateProperty)); this._panelService.connect('update-property', Lang.bind(this, this._updateProperty));
// If an engine is already active we need to get its properties // If an engine is already active we need to get its properties
this._ibus.get_global_engine_async(-1, null, Lang.bind(this, function(i, result) { this._ibus.get_global_engine_async(-1, null, Lang.bind(this, function(i, result) {
@@ -157,9 +140,6 @@ const IBusManager = new Lang.Class({
}, },
_engineChanged: function(bus, engineName) { _engineChanged: function(bus, engineName) {
if (!this._ready)
return;
this._currentEngineName = engineName; this._currentEngineName = engineName;
if (this._registerPropertiesId != 0) if (this._registerPropertiesId != 0)
@@ -203,8 +183,8 @@ const LayoutMenuItem = new Lang.Class({
this.label = new St.Label({ text: displayName }); this.label = new St.Label({ text: displayName });
this.indicator = new St.Label({ text: shortName }); this.indicator = new St.Label({ text: shortName });
this.actor.add(this.label, { expand: true }); this.addActor(this.label);
this.actor.add(this.indicator); this.addActor(this.indicator);
this.actor.label_actor = this.label; this.actor.label_actor = this.label;
} }
}); });
@@ -337,14 +317,7 @@ const InputSourceIndicator = new Lang.Class({
this._container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth)); this._container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth));
this._container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight)); this._container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight));
this._container.connect('allocate', Lang.bind(this, this._containerAllocate)); this._container.connect('allocate', Lang.bind(this, this._containerAllocate));
this.actor.add_actor(this._container);
this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
this._hbox.add_child(this._container);
this._hbox.add_child(new St.Label({ text: '\u25BE',
y_expand: true,
y_align: Clutter.ActorAlign.CENTER }));
this.actor.add_child(this._hbox);
this.actor.add_style_class_name('panel-status-button'); this.actor.add_style_class_name('panel-status-button');
// All valid input sources currently in the gsettings // All valid input sources currently in the gsettings
@@ -391,21 +364,14 @@ const InputSourceIndicator = new Lang.Class({
this._ibusManager.connect('property-updated', Lang.bind(this, this._ibusPropertyUpdated)); this._ibusManager.connect('property-updated', Lang.bind(this, this._ibusPropertyUpdated));
this._inputSourcesChanged(); 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.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._showLayoutItem = this.menu.addAction(_("Show Keyboard Layout"), Lang.bind(this, this._showLayout)); this._showLayoutItem = this.menu.addAction(_("Show Keyboard Layout"), Lang.bind(this, this._showLayout));
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
this._sessionUpdated(); this._sessionUpdated();
this.menu.addSettingsAction(_("Region & Language Settings"), 'gnome-region-panel.desktop');
this._sourcesPerWindow = false; this._sourcesPerWindow = false;
this._focusWindowNotifyId = 0; this._focusWindowNotifyId = 0;
this._overviewShowingId = 0; this._overviewShowingId = 0;
@@ -431,43 +397,10 @@ const InputSourceIndicator = new Lang.Class({
this._inputSourcesChanged(); 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) { _switchInputSource: function(display, screen, window, binding) {
if (this._mruSources.length < 2) if (this._mruSources.length < 2)
return; 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 popup = new InputSourcePopup(this._mruSources, this._keybindingAction, this._keybindingActionBackward);
let modifiers = binding.get_modifiers(); let modifiers = binding.get_modifiers();
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK; let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
@@ -483,11 +416,6 @@ const InputSourceIndicator = new Lang.Class({
let oldSource; let oldSource;
[oldSource, this._currentSource] = [this._currentSource, newSource]; [oldSource, this._currentSource] = [this._currentSource, newSource];
if (oldSource) {
oldSource.menuItem.setOrnament(PopupMenu.Ornament.NONE);
oldSource.indicatorLabel.hide();
}
if (!newSource || (nVisibleSources < 2 && !newSource.properties)) { if (!newSource || (nVisibleSources < 2 && !newSource.properties)) {
// This source index might be invalid if we weren't able // This source index might be invalid if we weren't able
// to build a menu item for it, so we hide ourselves since // to build a menu item for it, so we hide ourselves since
@@ -502,7 +430,12 @@ const InputSourceIndicator = new Lang.Class({
this.actor.show(); 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(); newSource.indicatorLabel.show();
this._buildPropSection(newSource.properties); this._buildPropSection(newSource.properties);
@@ -526,7 +459,6 @@ const InputSourceIndicator = new Lang.Class({
this._inputSources = {}; this._inputSources = {};
this._ibusSources = {}; this._ibusSources = {};
this._currentSource = null;
let inputSourcesByShortName = {}; let inputSourcesByShortName = {};
@@ -543,12 +475,8 @@ const InputSourceIndicator = new Lang.Class({
let engineDesc = this._ibusManager.getEngineDesc(id); let engineDesc = this._ibusManager.getEngineDesc(id);
if (engineDesc) { if (engineDesc) {
let language = IBus.get_language_name(engineDesc.get_language()); let language = IBus.get_language_name(engineDesc.get_language());
let longName = engineDesc.get_longname();
let textdomain = engineDesc.get_textdomain();
if (textdomain != '')
longName = Gettext.dgettext(textdomain, longName);
exists = true; exists = true;
displayName = '%s (%s)'.format(language, longName); displayName = language + ' (' + engineDesc.get_longname() + ')';
shortName = this._makeEngineShortName(engineDesc); shortName = this._makeEngineShortName(engineDesc);
} }
} }
@@ -559,8 +487,10 @@ const InputSourceIndicator = new Lang.Class({
let is = new InputSource(type, id, displayName, shortName, i); let is = new InputSource(type, id, displayName, shortName, i);
is.connect('activate', Lang.bind(this, function() { is.connect('activate', Lang.bind(this, function() {
holdKeyboard(); if (this._currentSource.index == is.index)
this._keyboardManager.SetInputSourceRemote(is.index, releaseKeyboard); return;
this._settings.set_value(KEY_CURRENT_INPUT_SOURCE,
GLib.Variant.new_uint32(is.index));
})); }));
if (!(is.shortName in inputSourcesByShortName)) if (!(is.shortName in inputSourcesByShortName))
@@ -730,8 +660,7 @@ const InputSourceIndicator = new Lang.Class({
item.prop = prop; item.prop = prop;
radioGroup.push(item); radioGroup.push(item);
item.radioGroup = radioGroup; item.radioGroup = radioGroup;
item.setOrnament(prop.get_state() == IBus.PropState.CHECKED ? item.setShowDot(prop.get_state() == IBus.PropState.CHECKED);
PopupMenu.Ornament.DOT : PopupMenu.Ornament.NONE);
item.connect('activate', Lang.bind(this, function() { item.connect('activate', Lang.bind(this, function() {
if (item.prop.get_state() == IBus.PropState.CHECKED) if (item.prop.get_state() == IBus.PropState.CHECKED)
return; return;
@@ -739,12 +668,12 @@ const InputSourceIndicator = new Lang.Class({
let group = item.radioGroup; let group = item.radioGroup;
for (let i = 0; i < group.length; ++i) { for (let i = 0; i < group.length; ++i) {
if (group[i] == item) { if (group[i] == item) {
item.setOrnament(PopupMenu.Ornament.DOT); item.setShowDot(true);
item.prop.set_state(IBus.PropState.CHECKED); item.prop.set_state(IBus.PropState.CHECKED);
this._ibusManager.activateProperty(item.prop.get_key(), this._ibusManager.activateProperty(item.prop.get_key(),
IBus.PropState.CHECKED); IBus.PropState.CHECKED);
} else { } else {
group[i].setOrnament(PopupMenu.Ornament.NONE); group[i].setShowDot(false);
group[i].prop.set_state(IBus.PropState.UNCHECKED); group[i].prop.set_state(IBus.PropState.UNCHECKED);
this._ibusManager.activateProperty(group[i].prop.get_key(), this._ibusManager.activateProperty(group[i].prop.get_key(),
IBus.PropState.UNCHECKED); IBus.PropState.UNCHECKED);
@@ -774,7 +703,7 @@ const InputSourceIndicator = new Lang.Class({
item.prop = prop; item.prop = prop;
item.connect('activate', Lang.bind(this, function() { item.connect('activate', Lang.bind(this, function() {
this._ibusManager.activateProperty(item.prop.get_key(), this._ibusManager.activateProperty(item.prop.get_key(),
item.prop.get_state()); IBus.PropState.CHECKED);
})); }));
break; break;
@@ -908,7 +837,7 @@ const InputSourceIndicator = new Lang.Class({
for (let i in this._inputSources) { for (let i in this._inputSources) {
let is = this._inputSources[i]; let is = this._inputSources[i];
is.indicatorLabel.allocate_align_fill(box, 0.5, 0.5, false, false, flags); is.indicatorLabel.allocate_align_fill(box, 0.5, 0, false, false, flags);
} }
} }
}); });

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -1,426 +0,0 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const AccountsService = imports.gi.AccountsService;
const Gdm = imports.gi.Gdm;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Clutter = imports.gi.Clutter;
const BoxPointer = imports.ui.boxpointer;
const GnomeSession = imports.misc.gnomeSession;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const UserWidget = imports.ui.userWidget;
const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const PRIVACY_SCHEMA = 'org.gnome.desktop.privacy'
const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen';
const DISABLE_LOG_OUT_KEY = 'disable-log-out';
const ALWAYS_SHOW_LOG_OUT_KEY = 'always-show-log-out';
const MAX_USERS_IN_SESSION_DIALOG = 5;
const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'>
<property name="Id" type="s" access="read"/>
<property name="Remote" type="b" access="read"/>
<property name="Class" type="s" access="read"/>
<property name="Type" type="s" access="read"/>
<property name="State" type="s" access="read"/>
</interface>;
const SystemdLoginSession = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface);
const Indicator = new Lang.Class({
Name: 'SystemIndicator',
Extends: PanelMenu.SystemIndicator,
_init: function() {
this.parent();
this._screenSaverSettings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
this._lockdownSettings = new Gio.Settings({ schema: LOCKDOWN_SCHEMA });
this._privacySettings = new Gio.Settings({ schema: PRIVACY_SCHEMA });
this._orientationSettings = new Gio.Settings({ schema: 'org.gnome.settings-daemon.peripherals.touchscreen' });
this._session = new GnomeSession.SessionManager();
this._haveShutdown = true;
this._loginManager = LoginManager.getLoginManager();
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._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._powerOffAction.visible);
this._actionsItem.actor.visible = visible;
},
_sessionUpdated: function() {
this._updateLockScreen();
this._updatePowerOff();
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) {
this._haveShutdown = result[0];
this._updatePowerOff();
}
}));
},
_updatePowerOff: function() {
this._powerOffAction.visible = this._haveShutdown && !Main.sessionMode.isLocked;
this._updateActionsVisibility();
},
_createActionButton: function(iconName, accessibleName) {
let icon = new St.Button({ reactive: true,
can_focus: true,
track_hover: true,
accessible_name: accessibleName,
style_class: 'system-menu-action' });
icon.child = new St.Icon({ icon_name: iconName });
return icon;
},
_createSubMenu: function() {
let item;
this._switchUserSubMenu = new PopupMenu.PopupSubMenuMenuItem('', true);
this._switchUserSubMenu.icon.style_class = 'system-switch-user-submenu-icon';
// Since the label of the switch user submenu depends on the width of
// the popup menu, and we can't easily connect on allocation-changed
// or notify::width without creating layout cycles, simply update the
// label whenever the menu is opened.
this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
if (isOpen)
this._updateSwitchUserSubMenu();
}));
item = new PopupMenu.PopupMenuItem(_("Switch User"));
item.connect('activate', Lang.bind(this, this._onLoginScreenActivate));
this._switchUserSubMenu.menu.addMenuItem(item);
this._loginScreenItem = item;
item = new PopupMenu.PopupMenuItem(_("Log Out"));
item.connect('activate', Lang.bind(this, this._onQuitSessionActivate));
this._switchUserSubMenu.menu.addMenuItem(item);
this._logoutItem = item;
this._user.connect('notify::is-loaded', Lang.bind(this, this._updateSwitchUserSubMenu));
this._user.connect('changed', Lang.bind(this, this._updateSwitchUserSubMenu));
this.menu.addMenuItem(this._switchUserSubMenu);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
item = new PopupMenu.PopupBaseMenuItem({ reactive: false,
can_focus: false });
this._settingsAction = this._createActionButton('preferences-system-symbolic', _("Settings"));
this._settingsAction.connect('clicked', Lang.bind(this, this._onSettingsClicked));
item.actor.add(this._settingsAction, { expand: true, x_fill: false });
this._orientationLockAction = this._createActionButton('', _("Orientation Lock"));
this._orientationLockAction.connect('clicked', Lang.bind(this, this._onOrientationLockClicked));
item.actor.add(this._orientationLockAction, { expand: true, x_fill: false });
this._lockScreenAction = this._createActionButton('changes-prevent-symbolic', _("Lock"));
this._lockScreenAction.connect('clicked', Lang.bind(this, this._onLockScreenClicked));
item.actor.add(this._lockScreenAction, { expand: true, x_fill: false });
this._powerOffAction = this._createActionButton('system-shutdown-symbolic', _("Power Off"));
this._powerOffAction.connect('clicked', Lang.bind(this, this._onPowerOffClicked));
item.actor.add(this._powerOffAction, { 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);
Gdm.goto_login_session_sync(null);
},
_onQuitSessionActivate: function() {
Main.overview.hide();
this._session.LogoutRemote(0);
},
_openSessionWarnDialog: function(sessions) {
let dialog = new ModalDialog.ModalDialog();
let subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject',
text: _("Other users are logged in.") });
dialog.contentLayout.add(subjectLabel, { y_fill: true,
y_align: St.Align.START });
let descriptionLabel = new St.Label({ style_class: 'end-session-dialog-description'});
descriptionLabel.set_text(_("Shutting down might cause them to lose unsaved work."));
descriptionLabel.clutter_text.line_wrap = true;
dialog.contentLayout.add(descriptionLabel, { x_fill: true,
y_fill: true,
y_align: St.Align.START });
let scrollView = new St.ScrollView({ style_class: 'end-session-dialog-app-list' });
scrollView.add_style_class_name('vfade');
scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
dialog.contentLayout.add(scrollView, { x_fill: true, y_fill: true });
let userList = new St.BoxLayout({ vertical: true });
scrollView.add_actor(userList);
for (let i = 0; i < sessions.length; i++) {
let session = sessions[i];
let userEntry = new St.BoxLayout({ style_class: 'login-dialog-user-list-item',
vertical: false });
let avatar = new UserWidget.Avatar(session.user);
avatar.update();
userEntry.add(avatar.actor);
let userLabelText = "";;
let userName = session.user.get_real_name() ?
session.user.get_real_name() : session.username;
if (session.info.remote)
/* Translators: Remote here refers to a remote session, like a ssh login */
userLabelText = _("%s (remote)").format(userName);
else if (session.info.type == "tty")
/* Translators: Console here refers to a tty like a VT console */
userLabelText = _("%s (console)").format(userName);
else
userLabelText = userName;
let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box',
vertical: true });
textLayout.add(new St.Label({ text: userLabelText }),
{ y_fill: false,
y_align: St.Align.MIDDLE,
expand: true });
userEntry.add(textLayout, { expand: true });
userList.add(userEntry, { x_fill: true });
}
let cancelButton = { label: _("Cancel"),
action: function() { dialog.close(); },
key: Clutter.Escape };
let powerOffButton = { label: _("Power Off"), action: Lang.bind(this, function() {
dialog.close();
this._session.ShutdownRemote();
}), default: true };
dialog.setButtons([cancelButton, powerOffButton]);
dialog.open();
},
_onPowerOffClicked: function() {
this.menu.itemActivated();
Main.overview.hide();
this._loginManager.listSessions(Lang.bind(this, function(result) {
let sessions = [];
let n = 0;
for (let i = 0; i < result.length; i++) {
let[id, uid, userName, seat, sessionPath] = result[i];
let proxy = new SystemdLoginSession(Gio.DBus.system,
'org.freedesktop.login1',
sessionPath);
if (proxy.Class != 'user')
continue;
if (proxy.State == 'closing')
continue;
if (proxy.Id == GLib.getenv('XDG_SESSION_ID'))
continue;
sessions.push({ user: this._userManager.get_user(userName),
username: userName,
info: { type: proxy.Type,
remote: proxy.Remote }
});
// limit the number of entries
n++;
if (n == MAX_USERS_IN_SESSION_DIALOG)
break;
}
if (n != 0)
this._openSessionWarnDialog(sessions);
else
this._session.ShutdownRemote();
}));
}
});

View File

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

View File

@@ -1,7 +1,6 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const AccountsService = imports.gi.AccountsService; const AccountsService = imports.gi.AccountsService;
const Atk = imports.gi.Atk;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gdm = imports.gi.Gdm; const Gdm = imports.gi.Gdm;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
@@ -13,13 +12,14 @@ const Signals = imports.signals;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const Layout = imports.ui.layout;
const Main = imports.ui.main; const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const UserMenu = imports.ui.userMenu;
const UserWidget = imports.ui.userWidget; const UserWidget = imports.ui.userWidget;
const AuthPrompt = imports.gdm.authPrompt;
const Batch = imports.gdm.batch; const Batch = imports.gdm.batch;
const GdmUtil = imports.gdm.util; const GdmUtil = imports.gdm.util;
const LoginDialog = imports.gdm.loginDialog; const LoginDialog = imports.gdm.loginDialog;
@@ -27,37 +27,124 @@ const LoginDialog = imports.gdm.loginDialog;
// The timeout before going back automatically to the lock screen (in seconds) // The timeout before going back automatically to the lock screen (in seconds)
const IDLE_TIMEOUT = 2 * 60; 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({ const UnlockDialog = new Lang.Class({
Name: 'UnlockDialog', Name: 'UnlockDialog',
Extends: ModalDialog.ModalDialog,
_init: function(parentActor) { _init: function(parentActor) {
this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, this.parent({ shellReactive: true,
style_class: 'login-dialog', styleClass: 'login-dialog',
visible: false }); keybindingMode: Shell.KeyBindingMode.UNLOCK_SCREEN,
parentActor: parentActor
this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); });
parentActor.add_child(this.actor);
this._userManager = AccountsService.UserManager.get_default(); this._userManager = AccountsService.UserManager.get_default();
this._userName = GLib.get_user_name(); this._userName = GLib.get_user_name();
this._user = this._userManager.get_user(this._userName); this._user = this._userManager.get_user(this._userName);
this._promptBox = new St.BoxLayout({ vertical: true }); this._failCounter = 0;
this.actor.add_child(this._promptBox); this._firstQuestion = true;
this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor,
align_axis: Clutter.AlignAxis.BOTH,
factor: 0.5 }));
this._authPrompt = new AuthPrompt.AuthPrompt(new Gdm.Client(), AuthPrompt.AuthPromptMode.UNLOCK_ONLY); this._greeterClient = new Gdm.Client();
this._authPrompt.connect('failed', Lang.bind(this, this._fail)); this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true });
this._authPrompt.connect('cancelled', Lang.bind(this, this._fail));
this._authPrompt.connect('reset', Lang.bind(this, this._onReset));
this._authPrompt.setPasswordChar('\u25cf');
this._authPrompt.nextButton.label = _("Unlock");
this._promptBox.add_child(this._authPrompt.actor); this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion));
this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage));
this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed));
this._userVerifier.connect('reset', Lang.bind(this, this._onReset));
this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint));
this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint));
this._userWidget = new UserWidget.UserWidget(this._user);
this.contentLayout.add_actor(this._userWidget.actor);
this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
vertical: true });
this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' });
this._promptLayout.add(this._promptLabel,
{ x_align: St.Align.START });
this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
can_focus: true });
this._promptEntry.clutter_text.set_password_char('\u25cf');
ShellEntry.addContextMenu(this._promptEntry, { isPassword: true });
this.setInitialKeyFocus(this._promptEntry);
this._promptEntry.clutter_text.connect('text-changed', Lang.bind(this, function() {
this._updateOkButtonSensitivity(this._promptEntry.text.length > 0);
}));
this._promptLayout.add(this._promptEntry,
{ expand: true,
x_fill: true });
this.contentLayout.add_actor(this._promptLayout);
this._promptMessage = new St.Label({ visible: false });
this.contentLayout.add(this._promptMessage, { x_fill: true });
this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint' });
this._promptLoginHint.hide();
this.contentLayout.add_actor(this._promptLoginHint);
this._workSpinner = new Panel.AnimatedIcon('process-working.svg', LoginDialog.WORK_SPINNER_ICON_SIZE);
this._workSpinner.actor.opacity = 0;
this.allowCancel = false; this.allowCancel = false;
this.buttonLayout.visible = true;
this.addButton({ label: _("Cancel"),
action: Lang.bind(this, this._escape),
key: Clutter.KEY_Escape },
{ expand: true,
x_fill: false,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
this.buttonLayout.add(this._workSpinner.actor,
{ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
this._okButton = this.addButton({ label: _("Unlock"),
action: Lang.bind(this, this._doUnlock),
default: true },
{ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' }); let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' });
if (screenSaverSettings.get_boolean('user-switch-enabled')) { if (screenSaverSettings.get_boolean('user-switch-enabled')) {
@@ -70,100 +157,180 @@ const UnlockDialog = new Lang.Class({
x_align: St.Align.START, x_align: St.Align.START,
x_fill: true }); x_fill: true });
this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked)); this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked));
this._promptBox.add_child(this._otherUserButton); this.dialogLayout.add(this._otherUserButton,
{ x_align: St.Align.START,
x_fill: false });
} else { } else {
this._otherUserButton = null; this._otherUserButton = null;
} }
this._authPrompt.reset();
this._updateSensitivity(true); this._updateSensitivity(true);
Main.ctrlAltTabManager.addGroup(this.actor, _("Unlock Window"), 'dialog-password-symbolic'); let batch = new Batch.Hold();
this._userVerifier.begin(this._userName, batch);
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._idleMonitor = new GnomeDesktop.IdleMonitor();
this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape)); this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape));
}, },
_updateSensitivity: function(sensitive) { _updateSensitivity: function(sensitive) {
this._authPrompt.updateSensitivity(sensitive); this._promptEntry.reactive = sensitive;
this._promptEntry.clutter_text.editable = sensitive;
this._updateOkButtonSensitivity(sensitive && this._promptEntry.text.length > 0);
if (this._otherUserButton) { if (this._otherUserButton) {
this._otherUserButton.reactive = sensitive; this._otherUserButton.reactive = sensitive;
this._otherUserButton.can_focus = sensitive; this._otherUserButton.can_focus = sensitive;
} }
}, },
_fail: function() { _updateOkButtonSensitivity: function(sensitive) {
this._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'); this.emit('failed');
}, },
_onReset: function(authPrompt, beginRequest) { _onVerificationFailed: function() {
let userName; this._currentQuery = null;
if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { this._firstQuestion = true;
this._authPrompt.setUser(this._user);
userName = this._userName;
} else {
userName = null;
}
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() { _escape: function() {
if (this.allowCancel) if (this.allowCancel) {
this._authPrompt.cancel(); this._userVerifier.cancel();
this.emit('failed');
}
}, },
_otherUserClicked: function(button, event) { _otherUserClicked: function(button, event) {
Gdm.goto_login_session_sync(null); Gdm.goto_login_session_sync(null);
this._authPrompt.cancel(); this._userVerifier.cancel();
this.emit('failed');
}, },
destroy: function() { destroy: function() {
this.popModal(); this._userVerifier.clear();
this.actor.destroy();
if (this._idleWatchId) { if (this._idleWatchId) {
this._idleMonitor.remove_watch(this._idleWatchId); this._idleMonitor.remove_watch(this._idleWatchId);
this._idleWatchId = 0; this._idleWatchId = 0;
} }
this.parent();
}, },
cancel: function() { cancel: function() {
this._authPrompt.cancel(); this._userVerifier.cancel(null);
this.destroy(); this.destroy();
}, },
addCharacter: function(unichar) {
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);

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

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

View File

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

View File

@@ -150,14 +150,6 @@ const ViewSelector = new Lang.Class({
Shell.KeyBindingMode.NORMAL | Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW, Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._toggleAppsPage)); Lang.bind(this, this._toggleAppsPage));
Main.wm.addKeybinding('toggle-overview',
new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.NONE,
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(Main.overview, Main.overview.toggle));
}, },
_toggleAppsPage: function() { _toggleAppsPage: function() {
@@ -186,10 +178,6 @@ const ViewSelector = new Lang.Class({
Main.overview.fadeInDesktop(); Main.overview.fadeInDesktop();
}, },
setWorkspacesFullGeometry: function(geom) {
this._workspacesDisplay.setWorkspacesFullGeometry(geom);
},
hide: function() { hide: function() {
this._workspacesDisplay.hide(); this._workspacesDisplay.hide();
}, },
@@ -508,12 +496,12 @@ const ViewSelector = new Lang.Class({
return; return;
this._searchSystem.registerProvider(provider); this._searchSystem.registerProvider(provider);
this._searchResults.createProviderDisplay(provider); this._searchResults.createProviderMeta(provider);
}, },
removeSearchProvider: function(provider) { removeSearchProvider: function(provider) {
this._searchSystem.unregisterProvider(provider); this._searchSystem.unregisterProvider(provider);
this._searchResults.destroyProviderDisplay(provider); this._searchResults.destroyProviderMeta(provider);
}, },
getActivePage: function() { getActivePage: function() {

View File

@@ -11,14 +11,12 @@ const Layout = imports.ui.layout;
const Main = imports.ui.main; const Main = imports.ui.main;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
// we could make these gsettings
const FISH_NAME = 'wanda'; const FISH_NAME = 'wanda';
const FISH_FILENAME = 'wanda.png';
const FISH_SPEED = 300; const FISH_SPEED = 300;
const FISH_COMMAND = 'fortune'; const FISH_COMMAND = 'fortune';
// The size of an individual frame in the animation
const FISH_HEIGHT = 22;
const FISH_WIDTH = 36;
const GNOME_PANEL_PIXMAPDIR = '../gnome-panel/fish';
const FISH_GROUP = 'Fish Animation'; const FISH_GROUP = 'Fish Animation';
const MAGIC_FISH_KEY = 'free the fish'; const MAGIC_FISH_KEY = 'free the fish';
@@ -28,16 +26,33 @@ const WandaIcon = new Lang.Class({
Extends: IconGrid.BaseIcon, Extends: IconGrid.BaseIcon,
_init : function(fish, label, params) { _init : function(fish, label, params) {
this.parent(label, params);
this._fish = fish; this._fish = fish;
this._imageFile = GLib.build_filenamev([global.datadir, fish + '.png']); let file = GLib.build_filenamev([global.datadir, GNOME_PANEL_PIXMAPDIR, fish + '.fish']);
this._imgHeight = FISH_HEIGHT; if (GLib.file_test(file, GLib.FileTest.EXISTS)) {
this._imgWidth = FISH_WIDTH; this._keyfile = new GLib.KeyFile();
this._keyfile.load_from_file(file, GLib.KeyFileFlags.NONE);
this._imageFile = GLib.build_filenamev([global.datadir, GNOME_PANEL_PIXMAPDIR,
this._keyfile.get_string(FISH_GROUP, 'image')]);
let tmpPixbuf = GdkPixbuf.Pixbuf.new_from_file(this._imageFile);
this._imgHeight = tmpPixbuf.height;
this._imgWidth = tmpPixbuf.width / this._keyfile.get_integer(FISH_GROUP, 'frames');
} else {
this._imageFile = null;
}
this.parent(label, params);
}, },
createIcon: function(iconSize) { createIcon: function(iconSize) {
if (!this._imageFile) {
return new St.Icon({ icon_name: 'face-smile',
icon_size: iconSize });
}
this._animations = new Panel.Animation(this._imageFile, this._imgWidth, this._imgHeight, FISH_SPEED); this._animations = new Panel.Animation(this._imageFile, this._imgWidth, this._imgHeight, FISH_SPEED);
this._animations.play(); this._animations.play();
return this._animations.actor; return this._animations.actor;
@@ -134,9 +149,9 @@ const WandaSearchProvider = new Lang.Class({
getInitialResultSet: function(terms) { getInitialResultSet: function(terms) {
if (terms.join(' ') == MAGIC_FISH_KEY) { if (terms.join(' ') == MAGIC_FISH_KEY) {
this.searchSystem.setResults(this, [ FISH_NAME ]); this.searchSystem.pushResults(this, [ FISH_NAME ]);
} else { } else {
this.searchSystem.setResults(this, []); this.searchSystem.pushResults(this, []);
} }
}, },

View File

@@ -4,16 +4,13 @@ const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
const Pango = imports.gi.Pango;
const St = imports.gi.St; const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const AltTab = imports.ui.altTab; const AltTab = imports.ui.altTab;
const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup; const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
const Main = imports.ui.main; const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings'; const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
@@ -22,107 +19,6 @@ const DIM_BRIGHTNESS = -0.3;
const DIM_TIME = 0.500; const DIM_TIME = 0.500;
const UNDIM_TIME = 0.250; const UNDIM_TIME = 0.250;
const DISPLAY_REVERT_TIMEOUT = 20; // in seconds - keep in sync with mutter
const ONE_SECOND = 1000; // in ms
const DisplayChangeDialog = new Lang.Class({
Name: 'DisplayChangeDialog',
Extends: ModalDialog.ModalDialog,
_init: function(wm) {
this.parent({ styleClass: 'prompt-dialog' });
this._wm = wm;
let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout',
vertical: false });
this.contentLayout.add(mainContentBox,
{ x_fill: true,
y_fill: true });
let icon = new St.Icon({ icon_name: 'preferences-desktop-display-symbolic' });
mainContentBox.add(icon,
{ x_fill: true,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.START });
let messageBox = new St.BoxLayout({ style_class: 'prompt-dialog-message-layout',
vertical: true });
mainContentBox.add(messageBox,
{ expand: true, y_align: St.Align.START });
let subjectLabel = new St.Label({ style_class: 'prompt-dialog-headline',
text: _("Do you want to keep these display settings?") });
messageBox.add(subjectLabel,
{ y_fill: false,
y_align: St.Align.START });
this._countDown = DISPLAY_REVERT_TIMEOUT;
let message = this._formatCountDown();
this._descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description',
text: this._formatCountDown() });
this._descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._descriptionLabel.clutter_text.line_wrap = true;
messageBox.add(this._descriptionLabel,
{ y_fill: true,
y_align: St.Align.START });
/* Translators: this and the following message should be limited in lenght,
to avoid ellipsizing the labels.
*/
this._cancelButton = this.addButton({ label: _("Revert Settings"),
action: Lang.bind(this, this._onFailure),
key: Clutter.Escape },
{ expand: true, x_fill: false, x_align: St.Align.START });
this._okButton = this.addButton({ label: _("Keep Changes"),
action: Lang.bind(this, this._onSuccess),
default: true },
{ expand: false, x_fill: false, x_align: St.Align.END });
this._timeoutId = Mainloop.timeout_add(ONE_SECOND, Lang.bind(this, this._tick));
},
close: function(timestamp) {
if (this._timeoutId > 0) {
Mainloop.source_remove(this._timeoutId);
this._timeoutId = 0;
}
this.parent(timestamp);
},
_formatCountDown: function() {
let fmt = ngettext("Settings changes will revert in %d second",
"Settings changes will revert in %d seconds");
return fmt.format(this._countDown);
},
_tick: function() {
this._countDown--;
if (this._countDown == 0) {
/* mutter already takes care of failing at timeout */
this._timeoutId = 0;
this.close();
return false;
}
this._descriptionLabel.text = this._formatCountDown();
return true;
},
_onFailure: function() {
this._wm.complete_display_change(false);
this.close();
},
_onSuccess: function() {
this._wm.complete_display_change(true);
this.close();
},
});
const WindowDimmer = new Lang.Class({ const WindowDimmer = new Lang.Class({
Name: 'WindowDimmer', Name: 'WindowDimmer',
@@ -170,209 +66,6 @@ function getWindowDimmer(actor) {
} }
} }
/*
* 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;
const WorkspaceTracker = new Lang.Class({
Name: 'WorkspaceTracker',
_init: function(wm) {
this._wm = wm;
this._workspaces = [];
this._checkWorkspacesId = 0;
let tracker = Shell.WindowTracker.get_default();
tracker.connect('startup-sequence-changed', Lang.bind(this, this._queueCheckWorkspaces));
global.screen.connect('notify::n-workspaces', Lang.bind(this, this._nWorkspacesChanged));
global.screen.connect('window-entered-monitor', Lang.bind(this, this._windowEnteredMonitor));
global.screen.connect('window-left-monitor', Lang.bind(this, this._windowLeftMonitor));
global.screen.connect('restacked', Lang.bind(this, this._windowsRestacked));
this._workspaceSettings = new Gio.Settings({ schema: Main.dynamicWorkspacesSchema });
this._workspaceSettings.connect('changed::dynamic-workspaces', Lang.bind(this, this._queueCheckWorkspaces));
this._nWorkspacesChanged();
},
_checkWorkspaces: function() {
let i;
let emptyWorkspaces = [];
if (!Meta.prefs_get_dynamic_workspaces()) {
this._checkWorkspacesId = 0;
return false;
}
for (i = 0; i < this._workspaces.length; i++) {
let lastRemoved = this._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)) ||
this._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
this._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(this._workspaces[i], global.get_current_time());
}
if (removingCurrentWorkspace) {
global.screen.get_workspace_by_index(global.screen.n_workspaces - 1).activate(global.get_current_time());
this._wm.unblockAnimations();
if (!Main.overview.visible && showOverview)
Main.overview.show();
}
this._checkWorkspacesId = 0;
return false;
},
keepWorkspaceAlive: function(workspace, duration) {
if (workspace._keepAliveId)
Mainloop.source_remove(workspace._keepAliveId);
workspace._keepAliveId = Mainloop.timeout_add(duration, Lang.bind(this, function() {
workspace._keepAliveId = 0;
this._queueCheckWorkspaces();
return false;
}));
},
_windowRemoved: function(workspace, window) {
workspace._lastRemovedWindow = window;
this._queueCheckWorkspaces();
Mainloop.timeout_add(LAST_WINDOW_GRACE_TIME, Lang.bind(this, function() {
if (workspace._lastRemovedWindow == window) {
workspace._lastRemovedWindow = null;
this._queueCheckWorkspaces();
}
return false;
}));
},
_windowLeftMonitor: function(metaScreen, monitorIndex, metaWin) {
// If the window left the primary monitor, that
// might make that workspace empty
if (monitorIndex == Main.layoutManager.primaryIndex)
this._queueCheckWorkspaces();
},
_windowEnteredMonitor: function(metaScreen, monitorIndex, metaWin) {
// If the window entered the primary monitor, that
// might make that workspace non-empty
if (monitorIndex == Main.layoutManager.primaryIndex)
this._queueCheckWorkspaces();
},
_windowsRestacked: function() {
// 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();
},
_queueCheckWorkspaces: function() {
if (this._checkWorkspacesId == 0)
this._checkWorkspacesId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, this._checkWorkspaces));
},
_nWorkspacesChanged: function() {
let oldNumWorkspaces = this._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++)
this._workspaces[w] = global.screen.get_workspace_by_index(w);
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
let workspace = this._workspaces[w];
workspace._windowAddedId = workspace.connect('window-added', Lang.bind(this, this._queueCheckWorkspaces));
workspace._windowRemovedId = workspace.connect('window-removed', Lang.bind(this, this._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 (this._workspaces[w] != workspace) {
removedIndex = w;
break;
}
}
let lostWorkspaces = this._workspaces.splice(removedIndex, removedNum);
lostWorkspaces.forEach(function(workspace) {
workspace.disconnect(workspace._windowAddedId);
workspace.disconnect(workspace._windowRemovedId);
});
}
this._queueCheckWorkspaces();
return false;
}
});
const WindowManager = new Lang.Class({ const WindowManager = new Lang.Class({
Name: 'WindowManager', Name: 'WindowManager',
@@ -409,7 +102,6 @@ const WindowManager = new Lang.Class({
this._shellwm.connect('map', Lang.bind(this, this._mapWindow)); this._shellwm.connect('map', Lang.bind(this, this._mapWindow));
this._shellwm.connect('destroy', Lang.bind(this, this._destroyWindow)); this._shellwm.connect('destroy', Lang.bind(this, this._destroyWindow));
this._shellwm.connect('filter-keybinding', Lang.bind(this, this._filterKeybinding)); this._shellwm.connect('filter-keybinding', Lang.bind(this, this._filterKeybinding));
this._shellwm.connect('confirm-display-change', Lang.bind(this, this._confirmDisplayChange));
this._workspaceSwitcherPopup = null; this._workspaceSwitcherPopup = null;
this.setCustomKeybindingHandler('switch-to-workspace-left', this.setCustomKeybindingHandler('switch-to-workspace-left',
@@ -444,90 +136,6 @@ const WindowManager = new Lang.Class({
Shell.KeyBindingMode.NORMAL | Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW, Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher)); Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-1',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-2',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-3',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-4',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-5',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-6',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-7',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-8',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-9',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-10',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-11',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-to-workspace-12',
Shell.KeyBindingMode.NORMAL |
Shell.KeyBindingMode.OVERVIEW,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-1',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-2',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-3',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-4',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-5',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-6',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-7',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-8',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-9',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-10',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-11',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('move-to-workspace-12',
Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._showWorkspaceSwitcher));
this.setCustomKeybindingHandler('switch-applications', this.setCustomKeybindingHandler('switch-applications',
Shell.KeyBindingMode.NORMAL, Shell.KeyBindingMode.NORMAL,
Lang.bind(this, this._startAppSwitcher)); Lang.bind(this, this._startAppSwitcher));
@@ -564,31 +172,8 @@ const WindowManager = new Lang.Class({
this.addKeybinding('open-application-menu', this.addKeybinding('open-application-menu',
new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }), new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.NONE, Meta.KeyBindingFlags.NONE,
Shell.KeyBindingMode.NORMAL | Shell.KeyBindingMode.NORMAL,
Shell.KeyBindingMode.TOPBAR_POPUP, Lang.bind(this, this._openAppMenu));
Lang.bind(this, this._toggleAppMenu));
Main.overview.connect('showing', Lang.bind(this, function() {
for (let i = 0; i < this._dimmedWindows.length; i++)
this._undimWindow(this._dimmedWindows[i]);
}));
Main.overview.connect('hiding', Lang.bind(this, function() {
for (let i = 0; i < this._dimmedWindows.length; i++)
this._dimWindow(this._dimmedWindows[i]);
}));
if (Main.sessionMode.hasWorkspaces)
this._workspaceTracker = new WorkspaceTracker(this);
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT,
false, -1, 1);
},
keepWorkspaceAlive: function(workspace, duration) {
if (!this._workspaceTracker)
return;
this._workspaceTracker.keepWorkspaceAlive(workspace, duration);
}, },
setCustomKeybindingHandler: function(name, modes, handler) { setCustomKeybindingHandler: function(name, modes, handler) {
@@ -748,15 +333,13 @@ const WindowManager = new Lang.Class({
if (shouldDim && !window._dimmed) { if (shouldDim && !window._dimmed) {
window._dimmed = true; window._dimmed = true;
this._dimmedWindows.push(window); this._dimmedWindows.push(window);
if (!Main.overview.visible) this._dimWindow(window);
this._dimWindow(window);
} else if (!shouldDim && window._dimmed) { } else if (!shouldDim && window._dimmed) {
window._dimmed = false; window._dimmed = false;
this._dimmedWindows = this._dimmedWindows.filter(function(win) { this._dimmedWindows = this._dimmedWindows.filter(function(win) {
return win != window; return win != window;
}); });
if (!Main.overview.visible) this._undimWindow(window);
this._undimWindow(window);
} }
}, },
@@ -937,7 +520,7 @@ const WindowManager = new Lang.Class({
}, },
_switchWorkspace : function(shellwm, from, to, direction) { _switchWorkspace : function(shellwm, from, to, direction) {
if (!Main.sessionMode.hasWorkspaces || !this._shouldAnimate()) { if (!this._shouldAnimate()) {
shellwm.completed_switch_workspace(); shellwm.completed_switch_workspace();
return; return;
} }
@@ -1085,42 +668,27 @@ const WindowManager = new Lang.Class({
Main.ctrlAltTabManager.popup(backwards, binding.get_name(), binding.get_mask()); Main.ctrlAltTabManager.popup(backwards, binding.get_name(), binding.get_mask());
}, },
_toggleAppMenu : function(display, screen, window, event, binding) { _openAppMenu : function(display, screen, window, event, binding) {
Main.panel.toggleAppMenu(); Main.panel.openAppMenu();
}, },
_showWorkspaceSwitcher : function(display, screen, window, binding) { _showWorkspaceSwitcher : function(display, screen, window, binding) {
if (!Main.sessionMode.hasWorkspaces)
return;
if (screen.n_workspaces == 1) if (screen.n_workspaces == 1)
return; return;
let [action,,,target] = binding.get_name().split('-'); let [action,,,direction] = binding.get_name().split('-');
let direction = Meta.MotionDirection[direction.toUpperCase()];
let newWs; let newWs;
let direction;
if (isNaN(target)) {
direction = Meta.MotionDirection[target.toUpperCase()];
newWs = screen.get_active_workspace().get_neighbor(direction);
} else if (target > 0) {
target--;
newWs = screen.get_workspace_by_index(target);
if (screen.get_active_workspace().index() > target)
direction = Meta.MotionDirection.UP;
else
direction = Meta.MotionDirection.DOWN;
}
if (direction != Meta.MotionDirection.UP && if (direction != Meta.MotionDirection.UP &&
direction != Meta.MotionDirection.DOWN) direction != Meta.MotionDirection.DOWN)
return; return;
if (action == 'switch') if (action == 'switch')
this.actionMoveWorkspace(newWs); newWs = this.actionMoveWorkspace(direction);
else else
this.actionMoveWindow(window, newWs); newWs = this.actionMoveWindow(window, direction);
if (!Main.overview.visible) { if (!Main.overview.visible) {
if (this._workspaceSwitcherPopup == null) { if (this._workspaceSwitcherPopup == null) {
@@ -1133,36 +701,31 @@ const WindowManager = new Lang.Class({
} }
}, },
actionMoveWorkspace: function(workspace) { actionMoveWorkspace: function(direction) {
if (!Main.sessionMode.hasWorkspaces)
return;
let activeWorkspace = global.screen.get_active_workspace(); let activeWorkspace = global.screen.get_active_workspace();
let toActivate = activeWorkspace.get_neighbor(direction);
if (activeWorkspace != workspace) if (activeWorkspace != toActivate)
workspace.activate(global.get_current_time()); toActivate.activate(global.get_current_time());
return toActivate;
}, },
actionMoveWindow: function(window, workspace) { actionMoveWindow: function(window, direction) {
if (!Main.sessionMode.hasWorkspaces)
return;
let activeWorkspace = global.screen.get_active_workspace(); let activeWorkspace = global.screen.get_active_workspace();
let toActivate = activeWorkspace.get_neighbor(direction);
if (activeWorkspace != workspace) { if (activeWorkspace != toActivate) {
// This won't have any effect for "always sticky" windows // This won't have any effect for "always sticky" windows
// (like desktop windows or docks) // (like desktop windows or docks)
this._movingWindow = window; this._movingWindow = window;
window.change_workspace(workspace); window.change_workspace(toActivate);
global.display.clear_mouse_mode(); global.display.clear_mouse_mode();
workspace.activate_with_focus (window, global.get_current_time()); toActivate.activate_with_focus (window, global.get_current_time());
} }
},
_confirmDisplayChange: function() { return toActivate;
let dialog = new DisplayChangeDialog(this._shellwm);
dialog.open();
}, },
}); });

View File

@@ -15,6 +15,7 @@ const Main = imports.ui.main;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const WindowManager = imports.ui.windowManager;
const FOCUS_ANIMATION_TIME = 0.15; const FOCUS_ANIMATION_TIME = 0.15;
@@ -41,6 +42,68 @@ function _interpolate(start, end, step) {
return start + (end - start) * step; return start + (end - start) * step;
} }
const WindowCloneLayout = new Lang.Class({
Name: 'WindowCloneLayout',
Extends: Clutter.LayoutManager,
_init: function(boundingBox) {
this.parent();
this._boundingBox = boundingBox;
},
get boundingBox() {
return this._boundingBox;
},
set boundingBox(b) {
this._boundingBox = b;
this.layout_changed();
},
_makeBoxForWindow: function(window) {
// We need to adjust the position of the actor because of the
// consequences of invisible borders -- in reality, the texture
// has an extra set of "padding" around it that we need to trim
// down.
// The outer rect (from which we compute the bounding box)
// paradoxically is the smaller rectangle, containing the positions
// of the visible frame. The input rect contains everything,
// including the invisible border padding.
let inputRect = window.get_input_rect();
let box = new Clutter.ActorBox();
box.set_origin(inputRect.x - this._boundingBox.x,
inputRect.y - this._boundingBox.y);
box.set_size(inputRect.width, inputRect.height);
return box;
},
vfunc_get_preferred_height: function(container, forWidth) {
return [this._boundingBox.height, this._boundingBox.height];
},
vfunc_get_preferred_width: function(container, forHeight) {
return [this._boundingBox.width, this._boundingBox.width];
},
vfunc_allocate: function(container, box, flags) {
let clone = container.get_children().forEach(function (child) {
let realWindow;
if (child == container._delegate._windowClone)
realWindow = container._delegate.realWindow;
else
realWindow = child.source;
child.allocate(this._makeBoxForWindow(realWindow.meta_window),
flags);
}, this);
},
});
const WindowClone = new Lang.Class({ const WindowClone = new Lang.Class({
Name: 'WindowClone', Name: 'WindowClone',
@@ -50,10 +113,7 @@ const WindowClone = new Lang.Class({
this.metaWindow._delegate = this; this.metaWindow._delegate = this;
this._workspace = workspace; this._workspace = workspace;
let [borderX, borderY] = this._getInvisibleBorderPadding(); this._windowClone = new Clutter.Clone({ source: realWindow.get_texture() });
this._windowClone = new Clutter.Clone({ source: realWindow.get_texture(),
x: -borderX,
y: -borderY });
// We expect this.actor to be used for all interaction rather than // We expect this.actor to be used for all interaction rather than
// this._windowClone; as the former is reactive and the latter // this._windowClone; as the former is reactive and the latter
// is not, this just works for most cases. However, for DND all // is not, this just works for most cases. However, for DND all
@@ -61,20 +121,15 @@ const WindowClone = new Lang.Class({
// To avoid this, we hide it from pick. // To avoid this, we hide it from pick.
Shell.util_set_hidden_from_pick(this._windowClone, true); Shell.util_set_hidden_from_pick(this._windowClone, true);
this.origX = realWindow.x + borderX;
this.origY = realWindow.y + borderY;
let outerRect = realWindow.meta_window.get_outer_rect();
// The MetaShapedTexture that we clone has a size that includes // The MetaShapedTexture that we clone has a size that includes
// the invisible border; this is inconvenient; rather than trying // the invisible border; this is inconvenient; rather than trying
// to compensate all over the place we insert a ClutterActor into // to compensate all over the place we insert a custom container into
// the hierarchy that is sized to only the visible portion. // the hierarchy that is sized to only the visible portion.
// As usual, we cannot use a ShellGenericContainer or StWidget here,
// because Workspace plays dirty tricks with reparenting to do DNDs
// and scroll-to-zoom.
this.actor = new Clutter.Actor({ reactive: true, this.actor = new Clutter.Actor({ reactive: true,
x: this.origX, layout_manager: new WindowCloneLayout() });
y: this.origY,
width: outerRect.width,
height: outerRect.height });
this.actor.add_child(this._windowClone); this.actor.add_child(this._windowClone);
@@ -84,10 +139,19 @@ const WindowClone = new Lang.Class({
this._dragSlot = [0, 0, 0, 0]; this._dragSlot = [0, 0, 0, 0];
this._stackAbove = null; this._stackAbove = null;
this._sizeChangedId = this.realWindow.connect('size-changed', this._windowClone._updateId = this.realWindow.connect('size-changed',
Lang.bind(this, this._onRealWindowSizeChanged)); Lang.bind(this, this._onRealWindowSizeChanged));
this._realWindowDestroyId = this.realWindow.connect('destroy', this._windowClone._destroyId = this.realWindow.connect('destroy', Lang.bind(this, function() {
Lang.bind(this, this._disconnectRealWindowSignals)); // First destroy the clone and then destroy everything
// This will ensure that we never see it in the _disconnectSignals loop
this._windowClone.destroy();
this.destroy();
}));
this._updateAttachedDialogs();
this._computeBoundingBox();
this.actor.x = this._boundingBox.x;
this.actor.y = this._boundingBox.y;
let clickAction = new Clutter.ClickAction(); let clickAction = new Clutter.ClickAction();
clickAction.connect('clicked', Lang.bind(this, this._onClicked)); clickAction.connect('clicked', Lang.bind(this, this._onClicked));
@@ -121,20 +185,95 @@ const WindowClone = new Lang.Class({
return this._slot; return this._slot;
}, },
// Find the actor just below us, respecting reparenting done deleteAll: function() {
// by DND code // Delete all windows, starting from the bottom-most (most-modal) one
getActualStackAbove: function() {
if (this._stackAbove == null)
return null;
if (this.inDrag) { let windows = this.actor.get_children();
if (this._stackAbove._delegate) for (let i = windows.length - 1; i >= 1; i--) {
return this._stackAbove._delegate.getActualStackAbove(); let realWindow = windows[i].source;
else let metaWindow = realWindow.meta_window;
return null;
} else { metaWindow.delete(global.get_current_time());
return this._stackAbove;
} }
this.metaWindow.delete(global.get_current_time());
},
addAttachedDialog: function(win) {
this._doAddAttachedDialog(win, win.get_compositor_private());
this._computeBoundingBox();
this._updateDimmer();
this.emit('size-changed');
},
_doAddAttachedDialog: function(metaWin, realWin) {
let clone = new Clutter.Clone({ source: realWin });
clone._updateId = realWin.connect('size-changed', Lang.bind(this, function() {
this._computeBoundingBox();
this.emit('size-changed');
}));
clone._destroyId = realWin.connect('destroy', Lang.bind(this, function() {
clone.destroy();
this._computeBoundingBox();
this._updateDimmer();
this.emit('size-changed');
}));
this.actor.add_child(clone);
},
_updateAttachedDialogs: function() {
let iter = Lang.bind(this, function(win) {
let actor = win.get_compositor_private();
if (!actor)
return false;
if (!win.is_attached_dialog())
return false;
this._doAddAttachedDialog(win, actor);
win.foreach_transient(iter);
return true;
});
this.metaWindow.foreach_transient(iter);
this._dimmer = new WindowManager.WindowDimmer(this._windowClone);
this._updateDimmer();
},
_updateDimmer: function() {
if (this.actor.get_n_children() > 1) {
this._dimmer.setEnabled(true);
this._dimmer.dimFactor = 1.0;
} else {
this._dimmer.setEnabled(false);
}
},
get boundingBox() {
return this._boundingBox;
},
getOriginalPosition: function() {
return [this._boundingBox.x, this._boundingBox.y];
},
_computeBoundingBox: function() {
let rect = this.metaWindow.get_outer_rect();
this.actor.get_children().forEach(function (child) {
let realWindow;
if (child == this._windowClone)
realWindow = this.realWindow;
else
realWindow = child.source;
let metaWindow = realWindow.meta_window;
rect = rect.union(metaWindow.get_outer_rect());
}, this);
this._boundingBox = rect;
this.actor.layout_manager.boundingBox = rect;
}, },
setStackAbove: function (actor) { setStackAbove: function (actor) {
@@ -142,56 +281,36 @@ const WindowClone = new Lang.Class({
if (this.inDrag) if (this.inDrag)
// We'll fix up the stack after the drag // We'll fix up the stack after the drag
return; return;
if (this._stackAbove == null)
let actualAbove = this.getActualStackAbove();
if (actualAbove == null)
this.actor.lower_bottom(); this.actor.lower_bottom();
else else
this.actor.raise(actualAbove); this.actor.raise(this._stackAbove);
}, },
destroy: function () { destroy: function () {
this.actor.destroy(); this.actor.destroy();
}, },
_disconnectRealWindowSignals: function() { _disconnectSignals: function() {
if (this._sizeChangedId > 0) this.actor.get_children().forEach(Lang.bind(this, function (child) {
this.realWindow.disconnect(this._sizeChangedId); let realWindow;
this._sizeChangedId = 0; if (child == this._windowClone)
realWindow = this.realWindow;
else
realWindow = child.source;
if (this._realWindowDestroyId > 0) realWindow.disconnect(child._updateId);
this.realWindow.disconnect(this._realWindowDestroyId); realWindow.disconnect(child._destroyId);
this._realWindowDestroyId = 0; }));
},
_getInvisibleBorderPadding: function() {
// We need to adjust the position of the actor because of the
// consequences of invisible borders -- in reality, the texture
// has an extra set of "padding" around it that we need to trim
// down.
// The outer rect paradoxically is the smaller rectangle,
// containing the positions of the visible frame. The input
// rect contains everything, including the invisible border
// padding.
let outerRect = this.metaWindow.get_outer_rect();
let inputRect = this.metaWindow.get_input_rect();
let [borderX, borderY] = [outerRect.x - inputRect.x,
outerRect.y - inputRect.y];
return [borderX, borderY];
}, },
_onRealWindowSizeChanged: function() { _onRealWindowSizeChanged: function() {
let [borderX, borderY] = this._getInvisibleBorderPadding(); this._computeBoundingBox();
let outerRect = this.metaWindow.get_outer_rect();
this.actor.set_size(outerRect.width, outerRect.height);
this._windowClone.set_position(-borderX, -borderY);
this.emit('size-changed'); this.emit('size-changed');
}, },
_onDestroy: function() { _onDestroy: function() {
this._disconnectRealWindowSignals(); this._disconnectSignals();
this.metaWindow._delegate = null; this.metaWindow._delegate = null;
this.actor._delegate = null; this.actor._delegate = null;
@@ -450,11 +569,7 @@ const WindowOverlay = new Lang.Class({
Lang.bind(this, Lang.bind(this,
this._onWindowAdded)); this._onWindowAdded));
metaWindow.delete(global.get_current_time()); this._windowClone.deleteAll();
},
_windowCanClose: function() {
return this._windowClone.metaWindow.can_close();
}, },
_onWindowAdded: function(workspace, win) { _onWindowAdded: function(workspace, win) {
@@ -492,14 +607,12 @@ const WindowOverlay = new Lang.Class({
_animateVisible: function() { _animateVisible: function() {
this._parentActor.raise_top(); this._parentActor.raise_top();
if (this._windowCanClose()) { this.closeButton.show();
this.closeButton.show(); this.closeButton.opacity = 0;
this.closeButton.opacity = 0; Tweener.addTween(this.closeButton,
Tweener.addTween(this.closeButton, { opacity: 255,
{ opacity: 255, time: CLOSE_BUTTON_FADE_TIME,
time: CLOSE_BUTTON_FADE_TIME, transition: 'easeOutQuad' });
transition: 'easeOutQuad' });
}
this.border.show(); this.border.show();
this.border.opacity = 0; this.border.opacity = 0;
@@ -759,6 +872,13 @@ const LayoutStrategy = new Lang.Class({
layout.space = space; layout.space = space;
}, },
_getDistance: function (row, actor) {
let dist_x = actor.x - row.x;
let dist_y = actor.y - row.y;
return Math.sqrt(Math.pow(dist_x, 2) + Math.pow(dist_y, 2));
},
computeWindowSlots: function(layout, area) { computeWindowSlots: function(layout, area) {
this._computeRowSizes(layout); this._computeRowSizes(layout);
@@ -766,36 +886,28 @@ const LayoutStrategy = new Lang.Class({
let slots = []; let slots = [];
// Do this in three parts.
let height = 0;
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
height += row.height + this._rowSpacing;
}
height -= this._rowSpacing;
let y = 0; let y = 0;
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
let row = rows[i]; let row = rows[i];
row.x = area.x + (area.width - row.width) / 2;
// If this window layout row doesn't fit in the actual row.y = area.y + y;
// geometry, then apply an additional scale to it.
row.additionalScale = Math.min(1, area.width / row.width, area.height / height);
row.x = area.x + (Math.max(area.width - row.width, 0) / 2) * row.additionalScale;
row.y = area.y + (y + Math.max(area.height - height, 0) / 2) * row.additionalScale;
y += row.height + this._rowSpacing; y += row.height + this._rowSpacing;
row.windows.sort(Lang.bind(this, function(a, b) {
return this._getDistance(row, a.realWindow) - this._getDistance(row, b.realWindow);
}));
} }
let height = y - this._rowSpacing;
let baseY = (area.height - height) / 2;
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
let row = rows[i]; let row = rows[i];
row.y += baseY;
let x = row.x; let x = row.x;
for (let j = 0; j < row.windows.length; j++) { for (let j = 0; j < row.windows.length; j++) {
let window = row.windows[j]; let window = row.windows[j];
let s = scale * this._computeWindowScale(window) * row.additionalScale; let s = scale * this._computeWindowScale(window);
let cellWidth = window.actor.width * s; let cellWidth = window.actor.width * s;
let cellHeight = window.actor.height * s; let cellHeight = window.actor.height * s;
@@ -839,13 +951,6 @@ const UnalignedLayoutStrategy = new Lang.Class({
return false; return false;
}, },
_sortRow: function(row) {
// Sort windows horizontally to minimize travel distance
row.windows.sort(function(a, b) {
return a.realWindow.x - b.realWindow.x;
});
},
computeLayout: function(windows, layout) { computeLayout: function(windows, layout) {
let numRows = layout.numRows; let numRows = layout.numRows;
@@ -876,7 +981,6 @@ const UnalignedLayoutStrategy = new Lang.Class({
row.windows.push(window); row.windows.push(window);
row.fullWidth += width; row.fullWidth += width;
} else { } else {
this._sortRow(row);
break; break;
} }
} }
@@ -898,14 +1002,6 @@ const UnalignedLayoutStrategy = new Lang.Class({
} }
}); });
function padArea(area, padding) {
return {
x: area.x + padding.left,
y: area.y + padding.top,
width: area.width - padding.left - padding.right,
height: area.height - padding.top - padding.bottom,
};
}
/** /**
* @metaWorkspace: a #Meta.Workspace, or null * @metaWorkspace: a #Meta.Workspace, or null
@@ -917,19 +1013,10 @@ const Workspace = new Lang.Class({
// When dragging a window, we use this slot for reserve space. // When dragging a window, we use this slot for reserve space.
this._reservedSlot = null; this._reservedSlot = null;
this.metaWorkspace = metaWorkspace; this.metaWorkspace = metaWorkspace;
this._x = 0;
// The full geometry is the geometry we should try and position this._y = 0;
// windows for. The actual geometry we allocate may be less than this._width = 0;
// this, like if the workspace switcher is slid out. this._height = 0;
this._fullGeometry = null;
// The actual geometry is the geometry we need to arrange windows
// in. If this is a smaller area than the full geometry, we'll
// do some simple aspect ratio like math to fit the layout calculated
// for the full geometry into this area.
this._actualGeometry = null;
this._currentLayout = null;
this.monitorIndex = monitorIndex; this.monitorIndex = monitorIndex;
this._monitor = Main.layoutManager.monitors[this.monitorIndex]; this._monitor = Main.layoutManager.monitors[this.monitorIndex];
@@ -942,7 +1029,7 @@ const Workspace = new Lang.Class({
this.actor.add_style_class_name('external-monitor'); this.actor.add_style_class_name('external-monitor');
this.actor.set_size(0, 0); this.actor.set_size(0, 0);
this._dropRect = new Clutter.Actor({ opacity: 0 }); this._dropRect = new Clutter.Rectangle({ opacity: 0 });
this._dropRect._delegate = this; this._dropRect._delegate = this;
this.actor.add_actor(this._dropRect); this.actor.add_actor(this._dropRect);
@@ -958,7 +1045,7 @@ const Workspace = new Lang.Class({
this._windowOverlays = []; this._windowOverlays = [];
for (let i = 0; i < windows.length; i++) { for (let i = 0; i < windows.length; i++) {
if (this._isOverviewWindow(windows[i])) { if (this._isOverviewWindow(windows[i])) {
this._addWindowClone(windows[i], true); this._addWindowClone(windows[i]);
} }
} }
@@ -979,29 +1066,23 @@ const Workspace = new Lang.Class({
this._positionWindowsFlags = 0; this._positionWindowsFlags = 0;
this._positionWindowsId = 0; this._positionWindowsId = 0;
this._currentLayout = null;
}, },
setFullGeometry: function(geom) { setGeometry: function(x, y, width, height) {
this._fullGeometry = geom; this._x = x;
this._recalculateWindowPositions(WindowPositionFlags.NONE); this._y = y;
}, this._width = width;
this._height = height;
setActualGeometry: function(geom) { Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
this._actualGeometry = geom; this._dropRect.set_position(x, y);
this._dropRect.set_size(width, height);
if (this._actualGeometryLater)
return;
this._actualGeometryLater = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
let geom = this._actualGeometry;
this._dropRect.set_position(geom.x, geom.y);
this._dropRect.set_size(geom.width, geom.height);
this._updateWindowPositions(Main.overview.animationInProgress ? WindowPositionFlags.ANIMATE : WindowPositionFlags.NONE);
this._actualGeometryLater = 0;
return false; return false;
})); }));
this.positionWindows(WindowPositionFlags.NONE);
}, },
_lookupIndex: function (metaWindow) { _lookupIndex: function (metaWindow) {
@@ -1029,32 +1110,37 @@ const Workspace = new Lang.Class({
clone = null; clone = null;
this._reservedSlot = clone; this._reservedSlot = clone;
this._recalculateWindowPositions(WindowPositionFlags.ANIMATE); this._currentLayout = null;
this.positionWindows(WindowPositionFlags.ANIMATE);
}, },
_recalculateWindowPositions: function(flags) { /**
* positionWindows:
* @flags:
* INITIAL - this is the initial positioning of the windows.
* ANIMATE - Indicates that we need animate changing position.
*/
positionWindows: function(flags) {
this._positionWindowsFlags |= flags; this._positionWindowsFlags |= flags;
if (this._positionWindowsId > 0) if (this._positionWindowsId > 0)
return; return;
this._positionWindowsId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { this._positionWindowsId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
this._realRecalculateWindowPositions(this._positionWindowsFlags); this._realPositionWindows(this._positionWindowsFlags);
this._positionWindowsFlags = 0; this._positionWindowsFlags = 0;
this._positionWindowsId = 0; this._positionWindowsId = 0;
return false; return false;
})); }));
}, },
_realRecalculateWindowPositions: function(flags) { _realPositionWindows : function(flags) {
if (this._repositionWindowsId > 0) { if (this._repositionWindowsId > 0) {
Mainloop.source_remove(this._repositionWindowsId); Mainloop.source_remove(this._repositionWindowsId);
this._repositionWindowsId = 0; this._repositionWindowsId = 0;
} }
let clones = this._windows.slice(); let clones = this._windows.slice();
if (clones.length == 0)
return;
clones.sort(function(a, b) { clones.sort(function(a, b) {
return a.metaWindow.get_stable_sequence() - b.metaWindow.get_stable_sequence(); return a.metaWindow.get_stable_sequence() - b.metaWindow.get_stable_sequence();
@@ -1063,25 +1149,11 @@ const Workspace = new Lang.Class({
if (this._reservedSlot) if (this._reservedSlot)
clones.push(this._reservedSlot); clones.push(this._reservedSlot);
this._currentLayout = this._computeLayout(clones);
this._updateWindowPositions(flags);
},
_updateWindowPositions: function(flags) {
if (this._currentLayout == null) {
this._recalculateWindowPositions(flags);
return;
}
let initialPositioning = flags & WindowPositionFlags.INITIAL; let initialPositioning = flags & WindowPositionFlags.INITIAL;
let animate = flags & WindowPositionFlags.ANIMATE; let animate = flags & WindowPositionFlags.ANIMATE;
let layout = this._currentLayout; // Start the animations
let strategy = layout.strategy; let slots = this._computeAllWindowSlots(clones);
let [, , padding] = this._getSpacingAndPadding();
let area = padArea(this._actualGeometry, padding);
let slots = strategy.computeWindowSlots(layout, area);
let currentWorkspace = global.screen.get_active_workspace(); let currentWorkspace = global.screen.get_active_workspace();
let isOnCurrentWorkspace = this.metaWorkspace == null || this.metaWorkspace == currentWorkspace; let isOnCurrentWorkspace = this.metaWorkspace == null || this.metaWorkspace == currentWorkspace;
@@ -1098,23 +1170,11 @@ const Workspace = new Lang.Class({
if (clone.inDrag) if (clone.inDrag)
continue; continue;
let cloneWidth = clone.actor.width * scale; clone.slot = [x, y, clone.actor.width * scale, clone.actor.height * scale];
let cloneHeight = clone.actor.height * scale;
clone.slot = [x, y, cloneWidth, cloneHeight];
if (overlay && (initialPositioning || !clone.positioned)) if (overlay && initialPositioning)
overlay.hide(); overlay.hide();
if (!clone.positioned) {
// This window appeared after the overview was already up
// Grow the clone from the center of the slot
clone.actor.x = x + cloneWidth / 2;
clone.actor.y = y + cloneHeight / 2;
clone.actor.scale_x = 0;
clone.actor.scale_y = 0;
clone.positioned = true;
}
if (animate && isOnCurrentWorkspace) { if (animate && isOnCurrentWorkspace) {
if (!metaWindow.showing_on_its_workspace()) { if (!metaWindow.showing_on_its_workspace()) {
/* Hidden windows should fade in and grow /* Hidden windows should fade in and grow
@@ -1141,7 +1201,6 @@ const Workspace = new Lang.Class({
Tweener.removeTweens(clone.actor); Tweener.removeTweens(clone.actor);
clone.actor.set_position(x, y); clone.actor.set_position(x, y);
clone.actor.set_scale(scale, scale); clone.actor.set_scale(scale, scale);
clone.actor.set_opacity(255);
clone.overlay.relayout(false); clone.overlay.relayout(false);
this._showWindowOverlay(clone, overlay, isOnCurrentWorkspace); this._showWindowOverlay(clone, overlay, isOnCurrentWorkspace);
} }
@@ -1196,8 +1255,8 @@ const Workspace = new Lang.Class({
let [x, y, mask] = global.get_pointer(); let [x, y, mask] = global.get_pointer();
let pointerHasMoved = (this._cursorX != x && this._cursorY != y); let pointerHasMoved = (this._cursorX != x && this._cursorY != y);
let inWorkspace = (this._fullGeometry.x < x && x < this._fullGeometry.x + this._fullGeometry.width && let inWorkspace = (this._x < x && x < this._x + this._width &&
this._fullGeometry.y < y && y < this._fullGeometry.y + this._fullGeometry.height); this._y < y && y < this._y + this._height);
if (pointerHasMoved && inWorkspace) { if (pointerHasMoved && inWorkspace) {
// store current cursor position // store current cursor position
@@ -1212,7 +1271,7 @@ const Workspace = new Lang.Class({
return true; return true;
} }
this._recalculateWindowPositions(WindowPositionFlags.ANIMATE); this.positionWindows(WindowPositionFlags.ANIMATE);
return false; return false;
}, },
@@ -1298,10 +1357,30 @@ const Workspace = new Lang.Class({
if (this._lookupIndex (metaWin) != -1) if (this._lookupIndex (metaWin) != -1)
return; return;
if (!this._isMyWindow(win) || !this._isOverviewWindow(win)) if (!this._isMyWindow(win))
return; return;
let [clone, overlay] = this._addWindowClone(win, false); if (!this._isOverviewWindow(win)) {
if (metaWin.is_attached_dialog()) {
let parent = metaWin.get_transient_for();
while (parent.is_attached_dialog())
parent = metaWin.get_transient_for();
let idx = this._lookupIndex (parent);
if (idx < 0) {
// parent was not created yet, it will take care
// of the dialog when created
return;
}
let clone = this._windows[idx];
clone.addAttachedDialog(metaWin);
}
return;
}
let [clone, overlay] = this._addWindowClone(win);
if (win._overviewHint) { if (win._overviewHint) {
let x = win._overviewHint.x - this.actor.x; let x = win._overviewHint.x - this.actor.x;
@@ -1310,14 +1389,20 @@ const Workspace = new Lang.Class({
delete win._overviewHint; delete win._overviewHint;
clone.slot = [x, y, clone.actor.width * scale, clone.actor.height * scale]; clone.slot = [x, y, clone.actor.width * scale, clone.actor.height * scale];
clone.positioned = true;
clone.actor.set_position (x, y); clone.actor.set_position (x, y);
clone.actor.set_scale (scale, scale); clone.actor.set_scale (scale, scale);
clone.overlay.relayout(false); clone.overlay.relayout(false);
} else {
// Position new windows at the top corner of the workspace rather
// than where they were placed for real to avoid the window
// being clipped to the workspaceView. Its not really more
// natural for the window to suddenly appear in the overview
// on some seemingly random location anyway.
clone.actor.set_position (this._x, this._y);
} }
this._currentLayout = null; this._currentLayout = null;
this._recalculateWindowPositions(WindowPositionFlags.ANIMATE); this.positionWindows(WindowPositionFlags.ANIMATE);
}, },
_windowAdded : function(metaWorkspace, metaWin) { _windowAdded : function(metaWorkspace, metaWin) {
@@ -1354,8 +1439,13 @@ const Workspace = new Lang.Class({
// Animate the full-screen to Overview transition. // Animate the full-screen to Overview transition.
zoomToOverview : function() { zoomToOverview : function() {
this._currentLayout = null;
// Position and scale the windows. // Position and scale the windows.
this._recalculateWindowPositions(WindowPositionFlags.ANIMATE | WindowPositionFlags.INITIAL); if (Main.overview.animationInProgress)
this.positionWindows(WindowPositionFlags.ANIMATE | WindowPositionFlags.INITIAL);
else
this.positionWindows(WindowPositionFlags.INITIAL);
}, },
// Animates the return from Overview mode // Animates the return from Overview mode
@@ -1388,9 +1478,11 @@ const Workspace = new Lang.Class({
overlay.hide(); overlay.hide();
if (clone.metaWindow.showing_on_its_workspace()) { if (clone.metaWindow.showing_on_its_workspace()) {
let [origX, origY] = clone.getOriginalPosition();
Tweener.addTween(clone.actor, Tweener.addTween(clone.actor,
{ x: clone.origX, { x: origX,
y: clone.origY, y: origY,
scale_x: 1.0, scale_x: 1.0,
scale_y: 1.0, scale_y: 1.0,
time: Overview.ANIMATION_TIME, time: Overview.ANIMATION_TIME,
@@ -1455,11 +1547,10 @@ const Workspace = new Lang.Class({
}, },
// Create a clone of a (non-desktop) window and add it to the window list // Create a clone of a (non-desktop) window and add it to the window list
_addWindowClone : function(win, positioned) { _addWindowClone : function(win) {
let clone = new WindowClone(win, this); let clone = new WindowClone(win, this);
let overlay = new WindowOverlay(clone, this._windowOverlaysGroup); let overlay = new WindowOverlay(clone, this._windowOverlaysGroup);
clone.overlay = overlay; clone.overlay = overlay;
clone.positioned = positioned;
clone.connect('selected', clone.connect('selected',
Lang.bind(this, this._onCloneSelected)); Lang.bind(this, this._onCloneSelected));
@@ -1479,7 +1570,7 @@ const Workspace = new Lang.Class({
})); }));
clone.connect('size-changed', clone.connect('size-changed',
Lang.bind(this, function() { Lang.bind(this, function() {
this._recalculateWindowPositions(WindowPositionFlags.NONE); this.positionWindows(0);
})); }));
this.actor.add_actor(clone.actor); this.actor.add_actor(clone.actor);
@@ -1528,14 +1619,12 @@ const Workspace = new Lang.Class({
} }
}, },
_getBestLayout: function(windows, area, rowSpacing, columnSpacing) { _computeLayout: function(windows, area, rowSpacing, columnSpacing) {
// We look for the largest scale that allows us to fit the // We look for the largest scale that allows us to fit the
// largest row/tallest column on the workspace. // largest row/tallest column on the workspace.
let lastLayout = {}; let lastLayout = {};
let strategy = new UnalignedLayoutStrategy(this._monitor, rowSpacing, columnSpacing);
for (let numRows = 1; ; numRows++) { for (let numRows = 1; ; numRows++) {
let numColumns = Math.ceil(windows.length / numRows); let numColumns = Math.ceil(windows.length / numRows);
@@ -1545,6 +1634,8 @@ const Workspace = new Lang.Class({
if (numColumns == lastLayout.numColumns) if (numColumns == lastLayout.numColumns)
break; break;
let strategy = new UnalignedLayoutStrategy(this._monitor, rowSpacing, columnSpacing);
let layout = { area: area, strategy: strategy, numRows: numRows, numColumns: numColumns }; let layout = { area: area, strategy: strategy, numRows: numRows, numColumns: numColumns };
strategy.computeLayout(windows, layout); strategy.computeLayout(windows, layout);
strategy.computeScaleAndSpace(layout); strategy.computeScaleAndSpace(layout);
@@ -1558,7 +1649,18 @@ const Workspace = new Lang.Class({
return lastLayout; return lastLayout;
}, },
_getSpacingAndPadding: function() { _rectEqual: function(one, two) {
if (one == two)
return true;
return (one.x == two.x &&
one.y == two.y &&
one.width == two.width &&
one.height == two.height);
},
_computeAllWindowSlots: function(windows) {
let totalWindows = windows.length;
let node = this.actor.get_theme_node(); let node = this.actor.get_theme_node();
// Window grid spacing // Window grid spacing
@@ -1571,14 +1673,21 @@ const Workspace = new Lang.Class({
right: node.get_padding(St.Side.RIGHT), right: node.get_padding(St.Side.RIGHT),
}; };
if (!totalWindows)
return [];
let closeButtonHeight, captionHeight; let closeButtonHeight, captionHeight;
let leftBorder, rightBorder; let leftBorder, rightBorder;
if (this._windowOverlays.length) {
// All of the overlays have the same chrome sizes, // All of the overlays have the same chrome sizes,
// so just pick the first one. // so just pick the first one.
let overlay = this._windowOverlays[0]; let overlay = this._windowOverlays[0];
[closeButtonHeight, captionHeight] = overlay.chromeHeights(); [closeButtonHeight, captionHeight] = overlay.chromeHeights();
[leftBorder, rightBorder] = overlay.chromeWidths(); [leftBorder, rightBorder] = overlay.chromeWidths();
} else {
[closeButtonHeight, captionHeight] = [0, 0];
[leftBorder, rightBorder] = [0, 0];
}
rowSpacing += captionHeight; rowSpacing += captionHeight;
columnSpacing += (rightBorder + leftBorder) / 2; columnSpacing += (rightBorder + leftBorder) / 2;
@@ -1587,13 +1696,25 @@ const Workspace = new Lang.Class({
padding.left += leftBorder; padding.left += leftBorder;
padding.right += rightBorder; padding.right += rightBorder;
return [rowSpacing, columnSpacing, padding]; let area = {
}, x: this._x + padding.left,
y: this._y + padding.top,
width: this._width - padding.left - padding.right,
height: this._height - padding.top - padding.bottom,
};
_computeLayout: function(windows) { if (!this._currentLayout)
let [rowSpacing, columnSpacing, padding] = this._getSpacingAndPadding(); this._currentLayout = this._computeLayout(windows, area, rowSpacing, columnSpacing);
let area = padArea(this._fullGeometry, padding);
return this._getBestLayout(windows, area, rowSpacing, columnSpacing); let layout = this._currentLayout;
let strategy = layout.strategy;
if (!this._rectEqual(area, layout.area)) {
layout.area = area;
strategy.computeScaleAndSpace(layout);
}
return strategy.computeWindowSlots(layout, area);
}, },
_onCloneSelected : function (clone, time) { _onCloneSelected : function (clone, time) {

View File

@@ -13,6 +13,7 @@ const Background = imports.ui.background;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const WindowManager = imports.ui.windowManager;
const Workspace = imports.ui.workspace; const Workspace = imports.ui.workspace;
const WorkspacesView = imports.ui.workspacesView; const WorkspacesView = imports.ui.workspacesView;
@@ -31,20 +32,49 @@ const WORKSPACE_KEEP_ALIVE_TIME = 100;
const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides'; const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides';
/* A layout manager that requests size only for primary_actor, but then allocates
all using a fixed layout */
const PrimaryActorLayout = new Lang.Class({
Name: 'PrimaryActorLayout',
Extends: Clutter.FixedLayout,
_init: function(primaryActor) {
this.parent();
this.primaryActor = primaryActor;
},
vfunc_get_preferred_width: function(forHeight) {
return this.primaryActor.get_preferred_width(forHeight);
},
vfunc_get_preferred_height: function(forWidth) {
return this.primaryActor.get_preferred_height(forWidth);
},
});
const WindowClone = new Lang.Class({ const WindowClone = new Lang.Class({
Name: 'WindowClone', Name: 'WindowClone',
_init : function(realWindow) { _init : function(realWindow) {
this.actor = new Clutter.Clone({ source: realWindow.get_texture(), this.clone = new Clutter.Clone({ source: realWindow });
/* Can't use a Shell.GenericContainer because of DND and reparenting... */
this.actor = new Clutter.Actor({ layout_manager: new PrimaryActorLayout(this.clone),
reactive: true }); reactive: true });
this.actor._delegate = this; this.actor._delegate = this;
this.actor.add_child(this.clone);
this.realWindow = realWindow; this.realWindow = realWindow;
this.metaWindow = realWindow.meta_window; this.metaWindow = realWindow.meta_window;
this._positionChangedId = this.realWindow.connect('position-changed', this.clone._updateId = this.realWindow.connect('position-changed',
Lang.bind(this, this._onPositionChanged)); Lang.bind(this, this._onPositionChanged));
this._realWindowDestroyedId = this.realWindow.connect('destroy', this.clone._destroyId = this.realWindow.connect('destroy', Lang.bind(this, function() {
Lang.bind(this, this._disconnectRealWindowSignals)); // First destroy the clone and then destroy everything
// This will ensure that we never see it in the _disconnectSignals loop
this.clone.destroy();
this.destroy();
}));
this._onPositionChanged(); this._onPositionChanged();
this.actor.connect('button-release-event', this.actor.connect('button-release-event',
@@ -60,62 +90,89 @@ const WindowClone = new Lang.Class({
this._draggable.connect('drag-cancelled', Lang.bind(this, this._onDragCancelled)); this._draggable.connect('drag-cancelled', Lang.bind(this, this._onDragCancelled));
this._draggable.connect('drag-end', Lang.bind(this, this._onDragEnd)); this._draggable.connect('drag-end', Lang.bind(this, this._onDragEnd));
this.inDrag = false; this.inDrag = false;
},
// Find the actor just below us, respecting reparenting done let iter = Lang.bind(this, function(win) {
// by DND code let actor = win.get_compositor_private();
getActualStackAbove: function() {
if (this._stackAbove == null)
return null;
if (this.inDrag) { if (!actor)
if (this._stackAbove._delegate) return false;
return this._stackAbove._delegate.getActualStackAbove(); if (!win.is_attached_dialog())
else return false;
return null;
} else { this._doAddAttachedDialog(win, actor);
return this._stackAbove; win.foreach_transient(iter);
}
return true;
});
this.metaWindow.foreach_transient(iter);
this._dimmer = new WindowManager.WindowDimmer(this.clone);
this._updateDimmer();
}, },
setStackAbove: function (actor) { setStackAbove: function (actor) {
this._stackAbove = actor; this._stackAbove = actor;
if (this._stackAbove == null)
// Don't apply the new stacking now, it will be applied
// when dragging ends and window are stacked again
if (actor.inDrag)
return;
let actualAbove = this.getActualStackAbove();
if (actualAbove == null)
this.actor.lower_bottom(); this.actor.lower_bottom();
else else
this.actor.raise(actualAbove); this.actor.raise(this._stackAbove);
}, },
destroy: function () { destroy: function () {
this.actor.destroy(); this.actor.destroy();
}, },
addAttachedDialog: function(win) {
this._doAddAttachedDialog(win, win.get_compositor_private());
this._updateDimmer();
},
_doAddAttachedDialog: function(metaDialog, realDialog) {
let clone = new Clutter.Clone({ source: realDialog });
this._updateDialogPosition(realDialog, clone);
clone._updateId = realDialog.connect('position-changed',
Lang.bind(this, this._updateDialogPosition, clone));
clone._destroyId = realDialog.connect('destroy', Lang.bind(this, function() {
clone.destroy();
this._updateDimmer();
}));
this.actor.add_child(clone);
},
_updateDimmer: function() {
if (this.actor.get_n_children() > 1) {
this._dimmer.setEnabled(true);
this._dimmer.dimFactor = 1.0;
} else {
this._dimmer.setEnabled(false);
}
},
_updateDialogPosition: function(realDialog, cloneDialog) {
let metaDialog = realDialog.meta_window;
let dialogRect = metaDialog.get_outer_rect();
let rect = this.metaWindow.get_outer_rect();
cloneDialog.set_position(dialogRect.x - rect.x, dialogRect.y - rect.y);
},
_onPositionChanged: function() { _onPositionChanged: function() {
let rect = this.metaWindow.get_outer_rect(); let rect = this.metaWindow.get_outer_rect();
this.actor.set_position(this.realWindow.x, this.realWindow.y); this.actor.set_position(this.realWindow.x, this.realWindow.y);
}, },
_disconnectRealWindowSignals: function() { _disconnectSignals: function() {
if (this._positionChangedId != 0) { this.actor.get_children().forEach(function(child) {
this.realWindow.disconnect(this._positionChangedId); let realWindow = child.source;
this._positionChangedId = 0;
}
if (this._realWindowDestroyedId != 0) { realWindow.disconnect(child._updateId);
this.realWindow.disconnect(this._realWindowDestroyedId); realWindow.disconnect(child._destroyId);
this._realWindowDestroyedId = 0; });
}
}, },
_onDestroy: function() { _onDestroy: function() {
this._disconnectRealWindowSignals(); this._disconnectSignals();
this.actor._delegate = null; this.actor._delegate = null;
@@ -343,10 +400,26 @@ const WorkspaceThumbnail = new Lang.Class({
if (this._lookupIndex (metaWin) != -1) if (this._lookupIndex (metaWin) != -1)
return; return;
if (!this._isMyWindow(win) || !this._isOverviewWindow(win)) if (!this._isMyWindow(win))
return; return;
let clone = this._addWindowClone(win); if (this._isOverviewWindow(win)) {
this._addWindowClone(win);
} else if (metaWin.is_attached_dialog()) {
let parent = metaWin.get_transient_for();
while (parent.is_attached_dialog())
parent = metaWin.get_transient_for();
let idx = this._lookupIndex (parent);
if (idx < 0) {
// parent was not created yet, it will take care
// of the dialog when created
return;
}
let clone = this._windows[idx];
clone.addAttachedDialog(metaWin);
}
}, },
_windowAdded : function(metaWorkspace, metaWin) { _windowAdded : function(metaWorkspace, metaWin) {
@@ -384,8 +457,9 @@ const WorkspaceThumbnail = new Lang.Class({
}, },
destroy : function() { destroy : function() {
if (this.actor) this.actor.destroy();
this.actor.destroy(); this._bgManager.destroy();
this._bgManager = null;
}, },
workspaceRemoved : function() { workspaceRemoved : function() {
@@ -406,11 +480,6 @@ const WorkspaceThumbnail = new Lang.Class({
_onDestroy: function(actor) { _onDestroy: function(actor) {
this.workspaceRemoved(); this.workspaceRemoved();
if (this._bgManager) {
this._bgManager.destroy();
this._bgManager = null;
}
this._windows = []; this._windows = [];
this.actor = null; this.actor = null;
}, },
@@ -562,7 +631,6 @@ const ThumbnailsBox = new Lang.Class({
this._dropPlaceholderPos = -1; this._dropPlaceholderPos = -1;
this._dropPlaceholder = new St.Bin({ style_class: 'placeholder' }); this._dropPlaceholder = new St.Bin({ style_class: 'placeholder' });
this.actor.add_actor(this._dropPlaceholder); this.actor.add_actor(this._dropPlaceholder);
this._spliceIndex = -1;
this._targetScale = 0; this._targetScale = 0;
this._scale = 0; this._scale = 0;
@@ -739,18 +807,12 @@ const ThumbnailsBox = new Lang.Class({
// Nab all the windows below us. // Nab all the windows below us.
let windows = global.get_window_actors().filter(function(win) { let windows = global.get_window_actors().filter(function(win) {
// If the window is attached to an ancestor, we don't need/want to move it
if (!!win.meta_window.get_transient_for())
return false;
if (isWindow) if (isWindow)
return win.get_workspace() >= newWorkspaceIndex && win != source; return win.get_workspace() >= newWorkspaceIndex && win != source;
else else
return win.get_workspace() >= newWorkspaceIndex; return win.get_workspace() >= newWorkspaceIndex;
}); });
this._spliceIndex = newWorkspaceIndex;
// ... move them down one. // ... move them down one.
windows.forEach(function(win) { windows.forEach(function(win) {
win.meta_window.change_workspace_by_index(win.get_workspace() + 1, win.meta_window.change_workspace_by_index(win.get_workspace() + 1,
@@ -768,18 +830,10 @@ const ThumbnailsBox = new Lang.Class({
// to open its first window within some time, as tracked by Shell.WindowTracker. // to open its first window within some time, as tracked by Shell.WindowTracker.
// Here, we only add a very brief timeout to avoid the _immediate_ removal of the // Here, we only add a very brief timeout to avoid the _immediate_ removal of the
// workspace while we wait for the startup sequence to load. // workspace while we wait for the startup sequence to load.
Main.wm.keepWorkspaceAlive(global.screen.get_workspace_by_index(newWorkspaceIndex), Main.keepWorkspaceAlive(global.screen.get_workspace_by_index(newWorkspaceIndex),
WORKSPACE_KEEP_ALIVE_TIME); WORKSPACE_KEEP_ALIVE_TIME);
} }
// Start the animation on the workspace (which is actually
// an old one which just became empty)
let thumbnail = this._thumbnails[newWorkspaceIndex];
this._setThumbnailState(thumbnail, ThumbnailState.NEW);
thumbnail.slidePosition = 1;
this._queueUpdateStates();
return true; return true;
} else { } else {
return false; return false;
@@ -867,8 +921,7 @@ const ThumbnailsBox = new Lang.Class({
this._thumbnails.push(thumbnail); this._thumbnails.push(thumbnail);
this.actor.add_actor(thumbnail.actor); this.actor.add_actor(thumbnail.actor);
if (start > 0 && this._spliceIndex == -1) { if (start > 0) { // not the initial fill
// not the initial fill, and not splicing via DND
thumbnail.state = ThumbnailState.NEW; thumbnail.state = ThumbnailState.NEW;
thumbnail.slidePosition = 1; // start slid out thumbnail.slidePosition = 1; // start slid out
this._haveNewThumbnails = true; this._haveNewThumbnails = true;
@@ -883,9 +936,6 @@ const ThumbnailsBox = new Lang.Class({
// The thumbnails indicator actually needs to be on top of the thumbnails // The thumbnails indicator actually needs to be on top of the thumbnails
this._indicator.raise_top(); this._indicator.raise_top();
// Clear the splice index, we got the message
this._spliceIndex = -1;
}, },
removeThumbnails: function(start, count) { removeThumbnails: function(start, count) {

View File

@@ -23,18 +23,6 @@ const MAX_WORKSPACES = 16;
const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides'; const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides';
function rectEqual(one, two) {
if (one == two)
return true;
if (!one || !two)
return false;
return (one.x == two.x &&
one.y == two.y &&
one.width == two.width &&
one.height == two.height);
}
const WorkspacesView = new Lang.Class({ const WorkspacesView = new Lang.Class({
Name: 'WorkspacesView', Name: 'WorkspacesView',
@@ -55,9 +43,10 @@ const WorkspacesView = new Lang.Class({
this._updateWorkspaceActors(false); this._updateWorkspaceActors(false);
})); }));
this._fullGeometry = null; this._width = 0;
this._actualGeometry = null; this._height = 0;
this._x = 0;
this._y = 0;
this._spacing = 0; this._spacing = 0;
this._animating = false; // tweening this._animating = false; // tweening
this._scrolling = false; // swipe-scrolling this._scrolling = false; // swipe-scrolling
@@ -96,8 +85,8 @@ const WorkspacesView = new Lang.Class({
this._overviewShownId = this._overviewShownId =
Main.overview.connect('shown', Main.overview.connect('shown',
Lang.bind(this, function() { Lang.bind(this, function() {
this.actor.set_clip(this._fullGeometry.x, this._fullGeometry.y, this.actor.set_clip(this._x, this._y,
this._fullGeometry.width, this._fullGeometry.height); this._width, this._height);
})); }));
this.scrollAdjustment = new St.Adjustment({ value: activeWorkspaceIndex, this.scrollAdjustment = new St.Adjustment({ value: activeWorkspaceIndex,
@@ -135,9 +124,11 @@ const WorkspacesView = new Lang.Class({
continue; continue;
let ws = new Workspace.Workspace(null, i); let ws = new Workspace.Workspace(null, i);
ws.setFullGeometry(monitors[i]); ws.setGeometry(monitors[i].x,
ws.setActualGeometry(monitors[i]); monitors[i].y,
Main.layoutManager.overviewGroup.add_actor(ws.actor); monitors[i].width,
monitors[i].height);
global.overlay_group.add_actor(ws.actor);
this._extraWorkspaces.push(ws); this._extraWorkspaces.push(ws);
} }
}, },
@@ -148,24 +139,18 @@ const WorkspacesView = new Lang.Class({
this._extraWorkspaces = []; this._extraWorkspaces = [];
}, },
setFullGeometry: function(geom) { setGeometry: function(x, y, width, height) {
if (rectEqual(this._fullGeometry, geom)) if (this._x == x && this._y == y &&
return; this._width == width && this._height == height)
return;
this._fullGeometry = geom; this._width = width;
this._height = height;
this._x = x;
this._y = y;
for (let i = 0; i < this._workspaces.length; i++) for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].setFullGeometry(geom); this._workspaces[i].setGeometry(x, y, width, height);
},
setActualGeometry: function(geom) {
if (rectEqual(this._actualGeometry, geom))
return;
this._actualGeometry = geom;
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].setActualGeometry(geom);
}, },
_lookupWorkspaceForMetaWindow: function (metaWindow) { _lookupWorkspaceForMetaWindow: function (metaWindow) {
@@ -225,7 +210,7 @@ const WorkspacesView = new Lang.Class({
Tweener.removeTweens(workspace.actor); Tweener.removeTweens(workspace.actor);
let y = (w - active) * (this._fullGeometry.height + this._spacing); let y = (w - active) * (this._height + this._spacing);
if (showAnimation) { if (showAnimation) {
let params = { y: y, let params = { y: y,
@@ -296,9 +281,8 @@ const WorkspacesView = new Lang.Class({
if (newNumWorkspaces > oldNumWorkspaces) { if (newNumWorkspaces > oldNumWorkspaces) {
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) { for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
this._workspaces[w].setFullGeometry(this._fullGeometry); this._workspaces[w].setGeometry(this._x, this._y,
if (this._actualGeometry) this._width, this._height);
this._workspaces[w].setActualGeometry(this._actualGeometry);
this.actor.add_actor(this._workspaces[w].actor); this.actor.add_actor(this._workspaces[w].actor);
} }
@@ -402,15 +386,6 @@ const WorkspacesView = new Lang.Class({
let current = Math.round(adj.value); let current = Math.round(adj.value);
if (active != current) { if (active != current) {
if (!this._workspaces[current]) {
// The current workspace was destroyed. This could happen
// when you are on the last empty workspace, and consolidate
// windows using the thumbnail bar.
// In that case, the intended behavior is to stay on the empty
// workspace, which is the last one, so pick it.
current = this._workspaces.length - 1;
}
let metaWorkspace = this._workspaces[current].metaWorkspace; let metaWorkspace = this._workspaces[current].metaWorkspace;
metaWorkspace.activate(global.get_current_time()); metaWorkspace.activate(global.get_current_time());
} }
@@ -446,7 +421,7 @@ const WorkspacesDisplay = new Lang.Class({
_init: function() { _init: function() {
this.actor = new St.Widget({ clip_to_allocation: true }); this.actor = new St.Widget({ clip_to_allocation: true });
this.actor.connect('notify::allocation', Lang.bind(this, this._updateWorkspacesActualGeometry)); this.actor.connect('notify::allocation', Lang.bind(this, this._updateWorkspacesGeometry));
this.actor.connect('parent-set', Lang.bind(this, this._parentSet)); this.actor.connect('parent-set', Lang.bind(this, this._parentSet));
let clickAction = new Clutter.ClickAction() let clickAction = new Clutter.ClickAction()
@@ -500,8 +475,6 @@ const WorkspacesDisplay = new Lang.Class({
this._notifyOpacityId = 0; this._notifyOpacityId = 0;
this._scrollEventId = 0; this._scrollEventId = 0;
this._fullGeometry = null;
}, },
_onPan: function(action) { _onPan: function(action) {
@@ -590,11 +563,10 @@ const WorkspacesDisplay = new Lang.Class({
this._workspacesViews.push(view); this._workspacesViews.push(view);
} }
this._updateWorkspacesFullGeometry(); this._updateWorkspacesGeometry();
this._updateWorkspacesActualGeometry();
for (let i = 0; i < this._workspacesViews.length; i++) for (let i = 0; i < this._workspacesViews.length; i++)
Main.layoutManager.overviewGroup.add_actor(this._workspacesViews[i].actor); global.overlay_group.add_actor(this._workspacesViews[i].actor);
}, },
_scrollValueChanged: function() { _scrollValueChanged: function() {
@@ -638,7 +610,7 @@ const WorkspacesDisplay = new Lang.Class({
// This is kinda hackish - we want the primary view to // This is kinda hackish - we want the primary view to
// appear as parent of this.actor, though in reality it // appear as parent of this.actor, though in reality it
// is added directly to Main.layoutManager.overviewGroup // is added directly to overlay_group
this._notifyOpacityId = newParent.connect('notify::opacity', this._notifyOpacityId = newParent.connect('notify::opacity',
Lang.bind(this, function() { Lang.bind(this, function() {
let opacity = this.actor.get_parent().opacity; let opacity = this.actor.get_parent().opacity;
@@ -651,48 +623,31 @@ const WorkspacesDisplay = new Lang.Class({
})); }));
}, },
// This geometry should always be the fullest geometry _updateWorkspacesGeometry: function() {
// the workspaces switcher can ever be allocated, as if
// the sliding controls were never slid in at all.
setWorkspacesFullGeometry: function(geom) {
this._fullGeometry = geom;
this._updateWorkspacesFullGeometry();
},
_updateWorkspacesFullGeometry: function() {
if (!this._workspacesViews.length) if (!this._workspacesViews.length)
return; return;
let monitors = Main.layoutManager.monitors; let fullWidth = this.actor.allocation.x2 - this.actor.allocation.x1;
let m = 0; let fullHeight = this.actor.allocation.y2 - this.actor.allocation.y1;
for (let i = 0; i < monitors.length; i++) {
if (i == this._primaryIndex) {
this._workspacesViews[m].setFullGeometry(this._fullGeometry);
m++;
} else if (!this._workspacesOnlyOnPrimary) {
this._workspacesViews[m].setFullGeometry(monitors[i]);
m++;
}
}
},
_updateWorkspacesActualGeometry: function() { let width = fullWidth;
if (!this._workspacesViews.length) let height = fullHeight;
return;
let [x, y] = this.actor.get_transformed_position(); let [x, y] = this.actor.get_transformed_position();
let width = this.actor.allocation.x2 - this.actor.allocation.x1;
let height = this.actor.allocation.y2 - this.actor.allocation.y1; let rtl = (Clutter.get_default_text_direction () == Clutter.TextDirection.RTL);
let geometry = { x: x, y: y, width: width, height: height };
let monitors = Main.layoutManager.monitors; let monitors = Main.layoutManager.monitors;
let m = 0; let m = 0;
for (let i = 0; i < monitors.length; i++) { for (let i = 0; i < monitors.length; i++) {
if (i == this._primaryIndex) { if (i == this._primaryIndex) {
this._workspacesViews[m].setActualGeometry(geometry); this._workspacesViews[m].setGeometry(x, y, width, height);
m++; m++;
} else if (!this._workspacesOnlyOnPrimary) { } else if (!this._workspacesOnlyOnPrimary) {
this._workspacesViews[m].setActualGeometry(monitors[i]); this._workspacesViews[m].setGeometry(monitors[i].x,
monitors[i].y,
monitors[i].width,
monitors[i].height);
m++; m++;
} }
} }
@@ -760,20 +715,15 @@ const WorkspacesDisplay = new Lang.Class({
_onScrollEvent: function(actor, event) { _onScrollEvent: function(actor, event) {
if (!this.actor.mapped) if (!this.actor.mapped)
return false; return false;
let activeWs = global.screen.get_active_workspace();
let ws;
switch (event.get_scroll_direction()) { switch (event.get_scroll_direction()) {
case Clutter.ScrollDirection.UP: case Clutter.ScrollDirection.UP:
ws = activeWs.get_neighbor(Meta.MotionDirection.UP); Main.wm.actionMoveWorkspace(Meta.MotionDirection.UP);
break; return true;
case Clutter.ScrollDirection.DOWN: case Clutter.ScrollDirection.DOWN:
ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN); Main.wm.actionMoveWorkspace(Meta.MotionDirection.DOWN);
break; return true;
default:
return false;
} }
Main.wm.actionMoveWorkspace(ws); return false;
return true;
} }
}); });
Signals.addSignalMethods(WorkspacesDisplay.prototype); Signals.addSignalMethods(WorkspacesDisplay.prototype);

View File

@@ -16,8 +16,9 @@ const XdndHandler = new Lang.Class({
this._cursorWindowClone = null; this._cursorWindowClone = null;
// Used as a drag actor in case we don't have a cursor window clone // Used as a drag actor in case we don't have a cursor window clone
this._dummy = new Clutter.Actor({ width: 1, height: 1, opacity: 0 }); this._dummy = new Clutter.Rectangle({ width: 1, height: 1, opacity: 0 });
Main.uiGroup.add_actor(this._dummy); Main.uiGroup.add_actor(this._dummy);
Shell.util_set_hidden_from_pick(this._dummy, true);
this._dummy.hide(); this._dummy.hide();
global.init_xdnd(); global.init_xdnd();
@@ -68,6 +69,7 @@ const XdndHandler = new Lang.Class({
this._cursorWindowClone = new Clutter.Clone({ source: cursorWindow }); this._cursorWindowClone = new Clutter.Clone({ source: cursorWindow });
Main.uiGroup.add_actor(this._cursorWindowClone); Main.uiGroup.add_actor(this._cursorWindowClone);
Shell.util_set_hidden_from_pick(this._cursorWindowClone, true);
// Make sure that the clone has the same position as the source // Make sure that the clone has the same position as the source
this._cursorWindowClone.add_constraint(constraint_position); this._cursorWindowClone.add_constraint(constraint_position);

View File

@@ -125,12 +125,6 @@
<listitem><para>List possible modes and exit</para></listitem> <listitem><para>List possible modes and exit</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>--clutter-display=<replaceable>DISPLAY</replaceable></option></term>
<listitem><para>Clutter the option display (otherwise ignored)</para></listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View File

@@ -28,7 +28,6 @@ gu
he he
hi hi
hu hu
ia
id id
it it
ja ja
@@ -61,7 +60,6 @@ sr@latin
sv sv
ta ta
te te
tg
th th
tr tr
ug ug

View File

@@ -1,13 +1,14 @@
# List of source files containing translatable strings. # List of source files containing translatable strings.
# Please keep this file sorted alphabetically. # Please keep this file sorted alphabetically.
[encoding: UTF-8] [encoding: UTF-8]
data/50-gnome-shell-screenshot.xml.in
data/50-gnome-shell-system.xml.in data/50-gnome-shell-system.xml.in
data/gnome-shell.desktop.in.in data/gnome-shell.desktop.in.in
data/gnome-shell-extension-prefs.desktop.in.in data/gnome-shell-extension-prefs.desktop.in.in
data/org.gnome.shell.gschema.xml.in.in data/org.gnome.shell.gschema.xml.in.in
js/extensionPrefs/main.js js/extensionPrefs/main.js
js/gdm/authPrompt.js
js/gdm/loginDialog.js js/gdm/loginDialog.js
js/gdm/powerMenu.js
js/gdm/util.js js/gdm/util.js
js/misc/util.js js/misc/util.js
js/ui/appDisplay.js js/ui/appDisplay.js
@@ -19,6 +20,7 @@ js/ui/components/autorunManager.js
js/ui/components/keyring.js js/ui/components/keyring.js
js/ui/components/networkAgent.js js/ui/components/networkAgent.js
js/ui/components/polkitAgent.js js/ui/components/polkitAgent.js
js/ui/components/recorder.js
js/ui/components/telepathyClient.js js/ui/components/telepathyClient.js
js/ui/ctrlAltTab.js js/ui/ctrlAltTab.js
js/ui/dash.js js/ui/dash.js
@@ -43,18 +45,16 @@ js/ui/shellMountOperation.js
js/ui/status/accessibility.js js/ui/status/accessibility.js
js/ui/status/bluetooth.js js/ui/status/bluetooth.js
js/ui/status/keyboard.js js/ui/status/keyboard.js
js/ui/status/lockScreenMenu.js
js/ui/status/network.js js/ui/status/network.js
js/ui/status/power.js js/ui/status/power.js
js/ui/status/rfkill.js
js/ui/status/system.js
js/ui/status/volume.js js/ui/status/volume.js
js/ui/unlockDialog.js js/ui/unlockDialog.js
js/ui/userMenu.js
js/ui/viewSelector.js js/ui/viewSelector.js
js/ui/wanda.js js/ui/wanda.js
js/ui/windowAttentionHandler.js js/ui/windowAttentionHandler.js
js/ui/windowManager.js
src/calendar-server/evolution-calendar.desktop.in.in src/calendar-server/evolution-calendar.desktop.in.in
# Please do not remove this file from POTFILES.in. Run "git submodule init && git submodule update" to get it.
src/gvc/gvc-mixer-control.c src/gvc/gvc-mixer-control.c
src/main.c src/main.c
src/shell-app.c src/shell-app.c

2453
po/af.po

File diff suppressed because it is too large Load Diff

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