Compare commits
	
		
			2 Commits
		
	
	
		
			3.14.4
			...
			3.7.3.1-br
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 7d598b7725 | ||
|   | 9f890982b2 | 
							
								
								
									
										12
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -19,18 +19,13 @@ configure | ||||
| data/50-gnome-shell-*.xml | ||||
| data/gnome-shell.desktop | ||||
| data/gnome-shell.desktop.in | ||||
| data/gnome-shell-wayland.desktop | ||||
| data/gnome-shell-wayland.desktop.in | ||||
| data/gnome-shell-extension-prefs.desktop | ||||
| data/gnome-shell-extension-prefs.desktop.in | ||||
| data/gschemas.compiled | ||||
| data/perf-background.xml | ||||
| data/org.gnome.shell.gschema.xml | ||||
| data/org.gnome.shell.gschema.valid | ||||
| data/org.gnome.shell.evolution.calendar.gschema.xml | ||||
| data/org.gnome.shell.evolution.calendar.gschema.valid | ||||
| data/org.gnome.Shell.PortalHelper.desktop | ||||
| data/org.gnome.Shell.PortalHelper.service | ||||
| docs/reference/*/*.args | ||||
| docs/reference/*/*.bak | ||||
| docs/reference/*/*.hierarchy | ||||
| @@ -46,8 +41,6 @@ docs/reference/*/xml/ | ||||
| docs/reference/shell/doc-gen-* | ||||
| gtk-doc.make | ||||
| js/misc/config.js | ||||
| js/js-resources.c | ||||
| js/js-resources.h | ||||
| intltool-extract.in | ||||
| intltool-merge.in | ||||
| intltool-update.in | ||||
| @@ -78,12 +71,13 @@ src/calendar-server/evolution-calendar.desktop.in | ||||
| src/calendar-server/org.gnome.Shell.CalendarServer.service | ||||
| src/gnome-shell | ||||
| src/gnome-shell-calendar-server | ||||
| src/gnome-shell-extension-prefs | ||||
| src/gnome-shell-extension-tool | ||||
| src/gnome-shell-extension-prefs | ||||
| src/gnome-shell-hotplug-sniffer | ||||
| src/gnome-shell-jhbuild | ||||
| src/gnome-shell-perf-helper | ||||
| src/gnome-shell-perf-tool | ||||
| src/gnome-shell-portal-helper | ||||
| src/gnome-shell-real | ||||
| src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service | ||||
| src/run-js-test | ||||
| src/test-recorder | ||||
|   | ||||
							
								
								
									
										41
									
								
								COPYING
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								COPYING
									
									
									
									
									
								
							| @@ -1,12 +1,12 @@ | ||||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|                        Version 2, June 1991 | ||||
| 		    GNU GENERAL PUBLIC LICENSE | ||||
| 		       Version 2, June 1991 | ||||
|  | ||||
|  Copyright (C) 1989, 1991 Free Software Foundation, Inc., | ||||
|  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
|  Copyright (C) 1989, 1991 Free Software Foundation, Inc. | ||||
|      59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
|  | ||||
|                             Preamble | ||||
| 			    Preamble | ||||
|  | ||||
|   The licenses for most software are designed to take away your | ||||
| freedom to share and change it.  By contrast, the GNU General Public | ||||
| @@ -15,7 +15,7 @@ software--to make sure the software is free for all its users.  This | ||||
| General Public License applies to most of the Free Software | ||||
| Foundation's software and to any other program whose authors commit to | ||||
| using it.  (Some other Free Software Foundation software is covered by | ||||
| the GNU Lesser General Public License instead.)  You can apply it to | ||||
| the GNU Library General Public License instead.)  You can apply it to | ||||
| your programs, too. | ||||
|  | ||||
|   When we speak of free software, we are referring to freedom, not | ||||
| @@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all. | ||||
|  | ||||
|   The precise terms and conditions for copying, distribution and | ||||
| modification follow. | ||||
|  | ||||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|  | ||||
| 		    GNU GENERAL PUBLIC LICENSE | ||||
|    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | ||||
|  | ||||
|   0. This License applies to any program or other work which contains | ||||
| @@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions: | ||||
|     License.  (Exception: if the Program itself is interactive but | ||||
|     does not normally print such an announcement, your work based on | ||||
|     the Program is not required to print an announcement.) | ||||
|  | ||||
|  | ||||
| These requirements apply to the modified work as a whole.  If | ||||
| identifiable sections of that work are not derived from the Program, | ||||
| and can be reasonably considered independent and separate works in | ||||
| @@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent | ||||
| access to copy the source code from the same place counts as | ||||
| distribution of the source code, even though third parties are not | ||||
| compelled to copy the source along with the object code. | ||||
|  | ||||
|  | ||||
|   4. You may not copy, modify, sublicense, or distribute the Program | ||||
| except as expressly provided under this License.  Any attempt | ||||
| otherwise to copy, modify, sublicense or distribute the Program is | ||||
| @@ -225,7 +225,7 @@ impose that choice. | ||||
|  | ||||
| This section is intended to make thoroughly clear what is believed to | ||||
| be a consequence of the rest of this License. | ||||
|  | ||||
|  | ||||
|   8. If the distribution and/or use of the Program is restricted in | ||||
| certain countries either by patents or by copyrighted interfaces, the | ||||
| original copyright holder who places the Program under this License | ||||
| @@ -255,7 +255,7 @@ make exceptions for this.  Our decision will be guided by the two goals | ||||
| of preserving the free status of all derivatives of our free software and | ||||
| of promoting the sharing and reuse of software generally. | ||||
|  | ||||
|                             NO WARRANTY | ||||
| 			    NO WARRANTY | ||||
|  | ||||
|   11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | ||||
| FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN | ||||
| @@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | ||||
| PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | ||||
| POSSIBILITY OF SUCH DAMAGES. | ||||
|  | ||||
|                      END OF TERMS AND CONDITIONS | ||||
|  | ||||
|             How to Apply These Terms to Your New Programs | ||||
| 		     END OF TERMS AND CONDITIONS | ||||
|  | ||||
| 	    How to Apply These Terms to Your New Programs | ||||
|  | ||||
|   If you develop a new program, and you want it to be of the greatest | ||||
| possible use to the public, the best way to achieve this is to make it | ||||
| @@ -303,16 +303,17 @@ the "copyright" line and a pointer to where the full notice is found. | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|     GNU General Public License for more details. | ||||
|  | ||||
|     You should have received a copy of the GNU General Public License along | ||||
|     with this program; if not, write to the Free Software Foundation, Inc., | ||||
|     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with this program; if not, write to the Free Software | ||||
|     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
|  | ||||
|  | ||||
| Also add information on how to contact you by electronic and paper mail. | ||||
|  | ||||
| If the program is interactive, make it output a short notice like this | ||||
| when it starts in an interactive mode: | ||||
|  | ||||
|     Gnomovision version 69, Copyright (C) year name of author | ||||
|     Gnomovision version 69, Copyright (C) year  name of author | ||||
|     Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||||
|     This is free software, and you are welcome to redistribute it | ||||
|     under certain conditions; type `show c' for details. | ||||
| @@ -335,5 +336,5 @@ necessary.  Here is a sample; alter the names: | ||||
| This General Public License does not permit incorporating your program into | ||||
| proprietary programs.  If your program is a subroutine library, you may | ||||
| consider it more useful to permit linking proprietary applications with the | ||||
| library.  If this is what you want to do, use the GNU Lesser General | ||||
| library.  If this is what you want to do, use the GNU Library General | ||||
| Public License instead of this License. | ||||
|   | ||||
							
								
								
									
										4
									
								
								HACKING
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								HACKING
									
									
									
									
									
								
							| @@ -138,8 +138,8 @@ GObjects, although this feature isn't used very often in the Shell itself. | ||||
|  | ||||
|         _init: function(icon, label) { | ||||
|             this.parent({ reactive: false }); | ||||
|             this.actor.add_child(icon); | ||||
|             this.actor.add_child(label); | ||||
|             this.addActor(icon); | ||||
|             this.addActor(label); | ||||
|         }, | ||||
|  | ||||
|         open: function() { | ||||
|   | ||||
| @@ -1,11 +1,7 @@ | ||||
| # Point to our macro directory and pick up user flags from the environment | ||||
| ACLOCAL_AMFLAGS  = -I m4 ${ACLOCAL_FLAGS} | ||||
|  | ||||
| SUBDIRS = data js src  tests po docs | ||||
|  | ||||
| if BUILD_BROWSER_PLUGIN | ||||
| SUBDIRS += browser-plugin | ||||
| endif | ||||
| SUBDIRS = data js src browser-plugin tests po docs | ||||
|  | ||||
| if ENABLE_MAN | ||||
| SUBDIRS += man | ||||
|   | ||||
							
								
								
									
										2
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								README
									
									
									
									
									
								
							| @@ -8,7 +8,7 @@ For more information about GNOME Shell, including instructions on how | ||||
| to build GNOME Shell from source and how to get involved with the project, | ||||
| see: | ||||
|  | ||||
|  https://wiki.gnome.org/Projects/GnomeShell | ||||
|  http://live.gnome.org/GnomeShell | ||||
|  | ||||
| Bugs should be reported at http://bugzilla.gnome.org against the 'gnome-shell' | ||||
| product. | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| #!/bin/sh | ||||
| #!/bin/bash | ||||
| # Run this to generate all the initial makefiles, etc. | ||||
|  | ||||
| srcdir=`dirname $0` | ||||
|   | ||||
| @@ -17,4 +17,5 @@ libgnome_shell_browser_plugin_la_SOURCES = 	\ | ||||
|  | ||||
| libgnome_shell_browser_plugin_la_CFLAGS = 	\ | ||||
| 	$(BROWSER_PLUGIN_CFLAGS)		\ | ||||
| 	-DG_DISABLE_DEPRECATED			\ | ||||
| 	-DG_LOG_DOMAIN=\"GnomeShellBrowserPlugin\" | ||||
|   | ||||
| @@ -13,7 +13,9 @@ | ||||
|  * General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  * | ||||
|  * Authors: | ||||
|  *      Jasper St. Pierre <jstpierre@mecheye.net> | ||||
| @@ -41,8 +43,6 @@ | ||||
|  | ||||
| #define PLUGIN_API_VERSION 5 | ||||
|  | ||||
| #define EXTENSION_DISABLE_VERSION_CHECK_KEY "disable-extension-version-validation" | ||||
|  | ||||
| typedef struct { | ||||
|   GDBusProxy *proxy; | ||||
| } PluginData; | ||||
| @@ -833,16 +833,6 @@ plugin_get_shell_version (PluginObject  *obj, | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| static gboolean | ||||
| plugin_get_version_validation_enabled (PluginObject  *obj, | ||||
|                                        NPVariant     *result) | ||||
| { | ||||
|   gboolean is_enabled = !g_settings_get_boolean (obj->settings, EXTENSION_DISABLE_VERSION_CHECK_KEY); | ||||
|   BOOLEAN_TO_NPVARIANT(is_enabled, *result); | ||||
|  | ||||
|   return TRUE; | ||||
| } | ||||
|  | ||||
| #define METHODS                                 \ | ||||
|   METHOD (list_extensions)                      \ | ||||
|   METHOD (get_info)                             \ | ||||
| @@ -862,8 +852,6 @@ static NPIdentifier api_version_id; | ||||
| static NPIdentifier shell_version_id; | ||||
| static NPIdentifier onextension_changed_id; | ||||
| static NPIdentifier onrestart_id; | ||||
| static NPIdentifier version_validation_enabled_id; | ||||
|  | ||||
|  | ||||
| static bool | ||||
| plugin_object_has_method (NPObject     *npobj, | ||||
| @@ -906,8 +894,7 @@ plugin_object_has_property (NPObject     *npobj, | ||||
|   return (name == onextension_changed_id || | ||||
|           name == onrestart_id || | ||||
|           name == api_version_id || | ||||
|           name == shell_version_id || | ||||
|           name == version_validation_enabled_id); | ||||
|           name == shell_version_id); | ||||
| } | ||||
|  | ||||
| static bool | ||||
| @@ -925,8 +912,6 @@ plugin_object_get_property (NPObject     *npobj, | ||||
|     return plugin_get_api_version (obj, result); | ||||
|   else if (name == shell_version_id) | ||||
|     return plugin_get_shell_version (obj, result); | ||||
|   else if (name == version_validation_enabled_id) | ||||
|     return plugin_get_version_validation_enabled (obj, result); | ||||
|   else if (name == onextension_changed_id) | ||||
|     { | ||||
|       if (obj->listener) | ||||
| @@ -1005,7 +990,6 @@ init_methods_and_properties (void) | ||||
|   /* this is the JS public API; it is manipulated through NPIdentifiers for speed */ | ||||
|   api_version_id = funcs.getstringidentifier ("apiVersion"); | ||||
|   shell_version_id = funcs.getstringidentifier ("shellVersion"); | ||||
|   version_validation_enabled_id = funcs.getstringidentifier ("versionValidationEnabled"); | ||||
|  | ||||
|   get_info_id = funcs.getstringidentifier ("getExtensionInfo"); | ||||
|   list_extensions_id = funcs.getstringidentifier ("listExtensions"); | ||||
|   | ||||
							
								
								
									
										271
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										271
									
								
								configure.ac
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| AC_PREREQ(2.63) | ||||
| AC_INIT([gnome-shell],[3.14.4],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell]) | ||||
| AC_INIT([gnome-shell],[3.7.3.1],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell]) | ||||
|  | ||||
| AC_CONFIG_HEADERS([config.h]) | ||||
| AC_CONFIG_SRCDIR([src/shell-global.c]) | ||||
| @@ -16,7 +16,8 @@ m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) | ||||
|  | ||||
| # Checks for programs. | ||||
| AC_PROG_CC | ||||
| AC_PROG_CXX | ||||
| # Needed for per-target cflags, like in gnomeshell-taskpanel | ||||
| AM_PROG_CC_C_O | ||||
|  | ||||
| # Initialize libtool | ||||
| LT_PREREQ([2.2.6]) | ||||
| @@ -25,6 +26,9 @@ LT_INIT([disable-static]) | ||||
| # i18n | ||||
| IT_PROG_INTLTOOL([0.40]) | ||||
|  | ||||
| AM_GNU_GETTEXT([external]) | ||||
| AM_GNU_GETTEXT_VERSION([0.17]) | ||||
|  | ||||
| GETTEXT_PACKAGE=gnome-shell | ||||
| AC_SUBST(GETTEXT_PACKAGE) | ||||
| AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", | ||||
| @@ -51,113 +55,131 @@ if $PKG_CONFIG --exists gstreamer-1.0 '>=' $GSTREAMER_MIN_VERSION ; then | ||||
|    AC_MSG_RESULT(yes) | ||||
|    build_recorder=true | ||||
|    recorder_modules="gstreamer-1.0 gstreamer-base-1.0 x11 gtk+-3.0" | ||||
|    PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0) | ||||
|    PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0 xfixes gl) | ||||
| else | ||||
|    AC_MSG_RESULT(no) | ||||
| fi | ||||
|  | ||||
| AM_CONDITIONAL(BUILD_RECORDER, $build_recorder) | ||||
|  | ||||
| AC_ARG_ENABLE([systemd], | ||||
|               AS_HELP_STRING([--enable-systemd], [Use systemd]), | ||||
|               [enable_systemd=$enableval], | ||||
|               [enable_systemd=auto]) | ||||
| AS_IF([test x$enable_systemd != xno], [ | ||||
|   AC_MSG_CHECKING([for libsystemd-journal]) | ||||
|   PKG_CHECK_EXISTS([libsystemd-journal], | ||||
|                    [have_systemd=yes | ||||
|                     AC_DEFINE([HAVE_SYSTEMD], [1], [Define if we have systemd])], | ||||
|                    [have_systemd=no]) | ||||
|   AC_MSG_RESULT($have_systemd) | ||||
| ]) | ||||
|  | ||||
| AC_MSG_RESULT($enable_systemd) | ||||
|  | ||||
| CLUTTER_MIN_VERSION=1.15.90 | ||||
| CLUTTER_MIN_VERSION=1.11.11 | ||||
| GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1 | ||||
| GJS_MIN_VERSION=1.39.0 | ||||
| MUTTER_MIN_VERSION=3.14.4 | ||||
| GTK_MIN_VERSION=3.13.2 | ||||
| GIO_MIN_VERSION=2.37.0 | ||||
| GJS_MIN_VERSION=1.33.2 | ||||
| MUTTER_MIN_VERSION=3.7.3 | ||||
| GTK_MIN_VERSION=3.3.9 | ||||
| GIO_MIN_VERSION=2.35.0 | ||||
| LIBECAL_MIN_VERSION=3.5.3 | ||||
| LIBEDATASERVER_MIN_VERSION=3.5.3 | ||||
| LIBEDATASERVERUI_MIN_VERSION=3.5.3 | ||||
| TELEPATHY_GLIB_MIN_VERSION=0.17.5 | ||||
| TELEPATHY_LOGGER_MIN_VERSION=0.2.4 | ||||
| POLKIT_MIN_VERSION=0.100 | ||||
| STARTUP_NOTIFICATION_MIN_VERSION=0.11 | ||||
| GCR_MIN_VERSION=3.7.5 | ||||
| GNOME_DESKTOP_REQUIRED_VERSION=3.7.90 | ||||
| NETWORKMANAGER_MIN_VERSION=0.9.8 | ||||
| GCR_MIN_VERSION=3.3.90 | ||||
| GNOME_DESKTOP_REQUIRED_VERSION=3.7.1 | ||||
| GNOME_MENUS_REQUIRED_VERSION=3.5.3 | ||||
| PULSE_MIN_VERS=2.0 | ||||
|  | ||||
| # Collect more than 20 libraries for a prize! | ||||
| SHARED_PCS="gio-unix-2.0 >= $GIO_MIN_VERSION | ||||
|             libxml-2.0 | ||||
|             gtk+-3.0 >= $GTK_MIN_VERSION | ||||
|             atk-bridge-2.0 | ||||
|             gjs-internals-1.0 >= $GJS_MIN_VERSION | ||||
|             $recorder_modules | ||||
|             gdk-x11-3.0 libsoup-2.4 | ||||
|             xtst | ||||
|             clutter-x11-1.0 >= $CLUTTER_MIN_VERSION | ||||
|             clutter-glx-1.0 >= $CLUTTER_MIN_VERSION | ||||
|             libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION | ||||
|             gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION | ||||
|             libcanberra libcanberra-gtk3 | ||||
|             telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION | ||||
|             polkit-agent-1 >= $POLKIT_MIN_VERSION | ||||
|             gcr-base-3 >= $GCR_MIN_VERSION" | ||||
| if test x$have_systemd = xyes; then | ||||
|   SHARED_PCS="${SHARED_PCS} libsystemd-journal" | ||||
| fi | ||||
| PKG_CHECK_MODULES(GNOME_SHELL, gio-unix-2.0 >= $GIO_MIN_VERSION | ||||
| 			       libxml-2.0 | ||||
|                                gtk+-3.0 >= $GTK_MIN_VERSION | ||||
|                                atk-bridge-2.0 | ||||
|                                libmutter >= $MUTTER_MIN_VERSION | ||||
|                                gjs-internals-1.0 >= $GJS_MIN_VERSION | ||||
| 			       libgnome-menu-3.0 >= $GNOME_MENUS_REQUIRED_VERSION | ||||
|                                $recorder_modules | ||||
|                                gdk-x11-3.0 libsoup-2.4 | ||||
|                                gl | ||||
| 			       clutter-x11-1.0 >= $CLUTTER_MIN_VERSION | ||||
| 			       clutter-glx-1.0 >= $CLUTTER_MIN_VERSION | ||||
|                                libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_MIN_VERSION | ||||
|                                gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION | ||||
| 			       libcanberra | ||||
|                                telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION | ||||
|                                telepathy-logger-0.2 >= $TELEPATHY_LOGGER_MIN_VERSION | ||||
|                                polkit-agent-1 >= $POLKIT_MIN_VERSION xfixes | ||||
|                                libnm-glib libnm-util gnome-keyring-1 | ||||
|                                gcr-3 >= $GCR_MIN_VERSION) | ||||
|  | ||||
| PKG_CHECK_MODULES(GNOME_SHELL, $SHARED_PCS) | ||||
| PKG_CHECK_MODULES(MUTTER, libmutter >= $MUTTER_MIN_VERSION) | ||||
|  | ||||
| PKG_CHECK_MODULES(GNOME_SHELL_JS, gio-2.0 gjs-internals-1.0 >= $GJS_MIN_VERSION) | ||||
| PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 >= 0.6.8 x11) | ||||
| PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0) | ||||
|  | ||||
| PKG_CHECK_MODULES(SHELL_HOTPLUG_SNIFFER, gio-2.0 gdk-pixbuf-2.0) | ||||
| PKG_CHECK_MODULES(TRAY, gtk+-3.0) | ||||
| PKG_CHECK_MODULES(GVC, libpulse >= $PULSE_MIN_VERS libpulse-mainloop-glib gobject-2.0) | ||||
| PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.7.4) | ||||
| PKG_CHECK_MODULES(CARIBOU, caribou-1.0 >= 0.4.8) | ||||
|  | ||||
| AC_ARG_ENABLE(browser-plugin, | ||||
|               [AS_HELP_STRING([--enable-browser-plugin], | ||||
|                               [Enable browser plugin [default=yes]])],, | ||||
|               enable_browser_plugin=yes) | ||||
| AS_IF([test x$enable_browser_plugin = xyes], [ | ||||
|   PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2) | ||||
| ]) | ||||
| AM_CONDITIONAL(BUILD_BROWSER_PLUGIN, test x$enable_browser_plugin = xyes) | ||||
|  | ||||
| PKG_CHECK_MODULES(BLUETOOTH, gnome-bluetooth-1.0 >= 3.9.0, | ||||
|         [AC_DEFINE([HAVE_BLUETOOTH],[1],[Define if you have libgnome-bluetooth-applet]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[1])], | ||||
| 	[AC_DEFINE([HAVE_BLUETOOTH],[0]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[0])]) | ||||
|  | ||||
| PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION gio-2.0) | ||||
| AC_SUBST(CALENDAR_SERVER_CFLAGS) | ||||
| AC_SUBST(CALENDAR_SERVER_LIBS) | ||||
| PKG_CHECK_MODULES(BROWSER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION json-glib-1.0 >= 0.13.2) | ||||
|  | ||||
| GNOME_KEYBINDINGS_KEYSDIR=`$PKG_CONFIG --variable keysdir gnome-keybindings` | ||||
| AC_SUBST([GNOME_KEYBINDINGS_KEYSDIR]) | ||||
|  | ||||
| GOBJECT_INTROSPECTION_CHECK([$GOBJECT_INTROSPECTION_MIN_VERSION]) | ||||
|  | ||||
| MUTTER_GIR_DIR=`$PKG_CONFIG --variable=girdir libmutter` | ||||
| AC_SUBST(MUTTER_GIR_DIR) | ||||
| saved_CFLAGS=$CFLAGS | ||||
| saved_LIBS=$LIBS | ||||
| CFLAGS=$GNOME_SHELL_CFLAGS | ||||
| LIBS=$GNOME_SHELL_LIBS | ||||
| AC_CHECK_FUNCS(JS_NewGlobalObject XFixesCreatePointerBarrier) | ||||
| CFLAGS=$saved_CFLAGS | ||||
| LIBS=$saved_LIBS | ||||
|  | ||||
| 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(TRAY, gtk+-3.0) | ||||
| PKG_CHECK_MODULES(GVC, libpulse >= $PULSE_MIN_VERS libpulse-mainloop-glib gobject-2.0) | ||||
| PKG_CHECK_MODULES(DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.7.2.2) | ||||
|  | ||||
| AC_MSG_CHECKING([for bluetooth support]) | ||||
| PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 3.1.0], | ||||
|         [BLUETOOTH_DIR=`$PKG_CONFIG --variable=applet_libdir gnome-bluetooth-1.0` | ||||
| 	 BLUETOOTH_LIBS=`$PKG_CONFIG --variable=applet_libs gnome-bluetooth-1.0` | ||||
| 	 AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"]) | ||||
| 	 AC_SUBST([BLUETOOTH_DIR],["$BLUETOOTH_DIR"]) | ||||
| 	 AC_DEFINE_UNQUOTED([BLUETOOTH_DIR],["$BLUETOOTH_DIR"],[Path to installed GnomeBluetooth typelib and library]) | ||||
| 	 AC_DEFINE([HAVE_BLUETOOTH],[1],[Define if you have libgnome-bluetooth-applet]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[1]) | ||||
| 	 AC_MSG_RESULT([yes])], | ||||
| 	[AC_DEFINE([HAVE_BLUETOOTH],[0]) | ||||
| 	 AC_SUBST([HAVE_BLUETOOTH],[0]) | ||||
| 	 AC_MSG_RESULT([no])]) | ||||
|  | ||||
| PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION gio-2.0) | ||||
| AC_SUBST(CALENDAR_SERVER_CFLAGS) | ||||
| AC_SUBST(CALENDAR_SERVER_LIBS) | ||||
|  | ||||
| AC_ARG_WITH(systemd, | ||||
|             AS_HELP_STRING([--with-systemd], | ||||
|                            [Add systemd support]), | ||||
|             [with_systemd=$withval], [with_systemd=auto]) | ||||
|  | ||||
| PKG_CHECK_MODULES(SYSTEMD, | ||||
|                   [libsystemd-login libsystemd-daemon], | ||||
|                   [have_systemd=yes], [have_systemd=no]) | ||||
|  | ||||
| if test "x$with_systemd" = "xauto" ; then | ||||
|         if test x$have_systemd = xno ; then | ||||
|                 use_systemd=no | ||||
|         else | ||||
|                 use_systemd=yes | ||||
|         fi | ||||
| else | ||||
|         use_systemd=$with_systemd | ||||
| fi | ||||
|  | ||||
| if test "x$use_systemd" = "xyes"; then | ||||
|         if test "x$have_systemd" = "xno"; then | ||||
|                 AC_MSG_ERROR([Systemd support explicitly required, but systemd not found]) | ||||
|         fi | ||||
|  | ||||
|         AC_DEFINE(WITH_SYSTEMD, 1, [systemd support]) | ||||
| fi | ||||
|  | ||||
| MUTTER_GIR_DIR=`$PKG_CONFIG --variable=girdir libmutter` | ||||
| MUTTER_TYPELIB_DIR=`$PKG_CONFIG --variable=typelibdir libmutter` | ||||
| AC_SUBST(MUTTER_GIR_DIR) | ||||
| AC_SUBST(MUTTER_TYPELIB_DIR) | ||||
|  | ||||
| GJS_CONSOLE=`$PKG_CONFIG --variable=gjs_console gjs-1.0` | ||||
| AC_SUBST(GJS_CONSOLE) | ||||
|  | ||||
| GLIB_COMPILE_RESOURCES=`$PKG_CONFIG --variable glib_compile_resources gio-2.0` | ||||
| AC_SUBST(GLIB_COMPILE_RESOURCES) | ||||
|  | ||||
| AC_CHECK_FUNCS(fdwalk) | ||||
| AC_CHECK_FUNCS(mallinfo) | ||||
| AC_CHECK_HEADERS([sys/resource.h]) | ||||
| @@ -173,40 +195,18 @@ if test "$langinfo_ok" = "yes"; then | ||||
|             [Define if _NL_TIME_FIRST_WEEKDAY is available]) | ||||
| fi | ||||
|  | ||||
| AC_ARG_ENABLE(networkmanager, | ||||
|              AS_HELP_STRING([--disable-networkmanager], | ||||
|                             [disable NetworkManager support  @<:@default=auto@:>@]),, | ||||
|               [enable_networkmanager=auto]) | ||||
|  | ||||
| if test "x$enable_networkmanager" != "xno"; then | ||||
|    PKG_CHECK_MODULES(NETWORKMANAGER, | ||||
|                      [libnm-glib | ||||
|                      libnm-util >= $NETWORKMANAGER_MIN_VERSION | ||||
|                      libnm-gtk >= $NETWORKMANAGER_MIN_VERSION | ||||
|                      libsecret-unstable], | ||||
|                      [have_networkmanager=yes], | ||||
|                      [have_networkmanager=no]) | ||||
|  | ||||
|    GNOME_SHELL_CFLAGS="$GNOME_SHELL_CFLAGS $NETWORKMANAGER_CFLAGS" | ||||
|    GNOME_SHELL_LIBS="$GNOME_SHELL_LIBS $NETWORKMANAGER_LIBS" | ||||
| else | ||||
|    have_networkmanager="no  (disabled)" | ||||
| fi | ||||
|  | ||||
| if test "x$have_networkmanager" = "xyes"; then | ||||
|    AC_DEFINE(HAVE_NETWORKMANAGER, [1], [Define if we have NetworkManager]) | ||||
|    AC_SUBST([HAVE_NETWORKMANAGER], [1]) | ||||
| else | ||||
|    if test "x$enable_networkmanager" = "xyes"; then | ||||
|       AC_MSG_ERROR([Couldn't find NetworkManager.]) | ||||
|    fi | ||||
|    AC_SUBST([HAVE_NETWORKMANAGER], [0]) | ||||
| fi | ||||
|  | ||||
| AM_CONDITIONAL(HAVE_NETWORKMANAGER, test "$have_networkmanager" = "yes") | ||||
|  | ||||
| # Sets GLIB_GENMARSHAL and GLIB_MKENUMS | ||||
| AM_PATH_GLIB_2_0() | ||||
| G_IR_SCANNER=`$PKG_CONFIG --variable=g_ir_scanner gobject-introspection-1.0` | ||||
| AC_SUBST(G_IR_SCANNER) | ||||
| G_IR_COMPILER=`$PKG_CONFIG --variable=g_ir_compiler gobject-introspection-1.0` | ||||
| AC_SUBST(G_IR_COMPILER) | ||||
| G_IR_GENERATE=`$PKG_CONFIG --variable=g_ir_generate gobject-introspection-1.0` | ||||
| AC_SUBST(G_IR_GENERATE) | ||||
| GIRDIR=`$PKG_CONFIG --variable=girdir gobject-introspection-1.0` | ||||
| AC_SUBST(GIRDIR) | ||||
| TYPELIBDIR="$($PKG_CONFIG --variable=typelibdir gobject-introspection-1.0)" | ||||
| AC_SUBST(TYPELIBDIR) | ||||
|  | ||||
| GTK_DOC_CHECK([1.15], [--flavour no-tmpl]) | ||||
|  | ||||
| @@ -222,19 +222,38 @@ if test "$enable_man" != no; then | ||||
| fi | ||||
| AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no) | ||||
|  | ||||
| GNOME_COMPILE_WARNINGS([error]) | ||||
| case "$WARN_CFLAGS" in | ||||
|     *-Werror*) | ||||
|         WARN_CFLAGS="$WARN_CFLAGS -Wno-error=deprecated-declarations" | ||||
|         ;; | ||||
| esac | ||||
| # Stay command-line compatible with the gnome-common configure option. Here | ||||
| # minimum/yes/maximum are the same, however. | ||||
| AC_ARG_ENABLE(compile_warnings, | ||||
|   AS_HELP_STRING([--enable-compile-warnings=@<:@no/minimum/yes/maximum/error@:>@],[Turn on compiler warnings]),, | ||||
|   enable_compile_warnings=error) | ||||
|  | ||||
| AM_CFLAGS="$AM_CFLAGS $WARN_CFLAGS" | ||||
| AC_SUBST(AM_CFLAGS) | ||||
|  | ||||
| if test -z "${BROWSER_PLUGIN_DIR}"; then | ||||
|   BROWSER_PLUGIN_DIR="\${libdir}/mozilla/plugins" | ||||
| changequote(,)dnl | ||||
| if test "$enable_compile_warnings" != no ; then | ||||
|   if test "x$GCC" = "xyes"; then | ||||
|     case " $CFLAGS " in | ||||
|     *[\ \	]-Wall[\ \	]*) ;; | ||||
|     *) CFLAGS="$CFLAGS -Wall" ;; | ||||
|     esac | ||||
|     case " $CFLAGS " in | ||||
|     *[\ \	]-Wmissing-prototypes[\ \	]*) ;; | ||||
|     *) CFLAGS="$CFLAGS -Wmissing-prototypes" ;; | ||||
|     esac | ||||
|     if test "$enable_compile_warnings" = error ; then | ||||
|       case " $CFLAGS " in | ||||
|       *[\ \	]-Werror[\ \	]*) ;; | ||||
|       *) CFLAGS="$CFLAGS -Werror -Wno-error=deprecated-declarations" ;; | ||||
|       esac | ||||
|     fi | ||||
|   fi | ||||
| fi | ||||
| changequote([,])dnl | ||||
|  | ||||
| AC_ARG_ENABLE(jhbuild-wrapper-script, | ||||
|   AS_HELP_STRING([--enable-jhbuild-wrapper-script],[Make "gnome-shell" script work for jhbuild]),,enable_jhbuild_wrapper_script=no) | ||||
| AM_CONDITIONAL(USE_JHBUILD_WRAPPER_SCRIPT, test "x$enable_jhbuild_wrapper_script" = xyes) | ||||
|  | ||||
| BROWSER_PLUGIN_DIR="${BROWSER_PLUGIN_DIR:-"\${libdir}/mozilla/plugins"}" | ||||
| AC_ARG_VAR([BROWSER_PLUGIN_DIR],[Where to install the plugin to]) | ||||
|  | ||||
| AC_CONFIG_FILES([ | ||||
| @@ -256,15 +275,3 @@ AC_CONFIG_FILES([ | ||||
|   man/Makefile | ||||
| ]) | ||||
| AC_OUTPUT | ||||
|  | ||||
| echo " | ||||
| Build configuration: | ||||
|  | ||||
|        Prefix:                                 ${prefix} | ||||
|        Source code location:                   ${srcdir} | ||||
|        Compiler:                               ${CC} | ||||
|        Compiler Warnings:                      $enable_compile_warnings | ||||
|  | ||||
|        Support for NetworkManager:             $have_networkmanager | ||||
|        Support for GStreamer recording:        $build_recorder | ||||
| " | ||||
|   | ||||
							
								
								
									
										12
									
								
								data/50-gnome-shell-screenshot.xml.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								data/50-gnome-shell-screenshot.xml.in
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" ?> | ||||
| <KeyListEntries schema="org.gnome.shell.keybindings" | ||||
|                 group="system" | ||||
|                 _name="Screenshots" | ||||
|                 wm_name="GNOME Shell" | ||||
|                 package="gnome-shell"> | ||||
|  | ||||
| 	<KeyListEntry name="toggle-recording" | ||||
|                       _description="Record a screencast"/> | ||||
|  | ||||
| </KeyListEntries> | ||||
|  | ||||
| @@ -11,9 +11,6 @@ | ||||
| 	<KeyListEntry name="focus-active-notification" | ||||
|                       _description="Focus the active notification"/> | ||||
|  | ||||
| 	<KeyListEntry name="toggle-overview" | ||||
|                       _description="Show the overview"/> | ||||
|  | ||||
| 	<KeyListEntry name="toggle-application-view" | ||||
|                       _description="Show all applications"/> | ||||
|  | ||||
|   | ||||
| @@ -1,23 +1,5 @@ | ||||
| CLEANFILES = | ||||
|  | ||||
| desktopdir=$(datadir)/applications | ||||
| desktop_DATA = gnome-shell.desktop gnome-shell-wayland.desktop  gnome-shell-extension-prefs.desktop | ||||
|  | ||||
| if HAVE_NETWORKMANAGER | ||||
| desktop_DATA += org.gnome.Shell.PortalHelper.desktop | ||||
|  | ||||
| servicedir = $(datadir)/dbus-1/services | ||||
| service_DATA = org.gnome.Shell.PortalHelper.service | ||||
|  | ||||
| CLEANFILES += \ | ||||
| 	org.gnome.Shell.PortalHelper.service \ | ||||
| 	org.gnome.Shell.PortalHelper.desktop | ||||
|  | ||||
| endif | ||||
|  | ||||
| %.service: %.service.in | ||||
| 	$(AM_V_GEN) sed -e "s|@libexecdir[@]|$(libexecdir)|" \ | ||||
| 	    $< > $@ || rm $@ | ||||
| desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop | ||||
|  | ||||
| # We substitute in bindir so it works as an autostart | ||||
| # file when built in a non-system prefix | ||||
| @@ -30,8 +12,6 @@ endif | ||||
|  | ||||
| introspectiondir = $(datadir)/dbus-1/interfaces | ||||
| introspection_DATA =				\ | ||||
| 	org.gnome.Shell.Screencast.xml		\ | ||||
| 	org.gnome.Shell.Screenshot.xml		\ | ||||
| 	org.gnome.ShellSearchProvider.xml	\ | ||||
| 	org.gnome.ShellSearchProvider2.xml | ||||
|  | ||||
| @@ -56,10 +36,6 @@ dist_theme_DATA =				\ | ||||
| 	theme/message-tray-background.png	\ | ||||
| 	theme/more-results.svg			\ | ||||
| 	theme/noise-texture.png			\ | ||||
| 	theme/page-indicator-active.svg		\ | ||||
| 	theme/page-indicator-inactive.svg	\ | ||||
| 	theme/page-indicator-checked.svg	\ | ||||
| 	theme/page-indicator-hover.svg		\ | ||||
| 	theme/panel-button-border.svg		\ | ||||
| 	theme/panel-button-highlight-narrow.svg	\ | ||||
| 	theme/panel-button-highlight-wide.svg	\ | ||||
| @@ -74,15 +50,11 @@ dist_theme_DATA =				\ | ||||
| 	theme/ws-switch-arrow-up.png		\ | ||||
| 	theme/ws-switch-arrow-down.png | ||||
|  | ||||
| backgrounddir = $(pkgdatadir) | ||||
| background_DATA = perf-background.xml | ||||
|  | ||||
| perf-background.xml: perf-background.xml.in | ||||
| 	$(AM_V_GEN) sed -e "s|@datadir[@]|$(datadir)|" \ | ||||
| 	    $< > $@ || rm $@ | ||||
|  | ||||
| keysdir = @GNOME_KEYBINDINGS_KEYSDIR@ | ||||
| keys_in_files = 50-gnome-shell-system.xml.in | ||||
| keys_in_files =					\ | ||||
| 	50-gnome-shell-screenshot.xml.in	\ | ||||
| 	50-gnome-shell-system.xml.in		\ | ||||
| 	$(NULL) | ||||
| keys_DATA = $(keys_in_files:.xml.in=.xml) | ||||
|  | ||||
| gsettings_SCHEMAS = org.gnome.shell.gschema.xml | ||||
| @@ -107,25 +79,19 @@ convert_DATA = gnome-shell-overrides.convert | ||||
|  | ||||
| EXTRA_DIST =						\ | ||||
| 	gnome-shell.desktop.in.in			\ | ||||
| 	gnome-shell-wayland.desktop.in.in		\ | ||||
| 	gnome-shell-extension-prefs.desktop.in.in	\ | ||||
| 	$(introspection_DATA)				\ | ||||
| 	$(menu_DATA)					\ | ||||
| 	$(convert_DATA)					\ | ||||
| 	$(keys_in_files)				\ | ||||
| 	perf-background.xml.in				\ | ||||
| 	org.gnome.Shell.PortalHelper.desktop.in		\ | ||||
| 	org.gnome.Shell.PortalHelper.service.in		\ | ||||
| 	org.gnome.shell.gschema.xml.in.in | ||||
|  | ||||
| CLEANFILES +=						\ | ||||
| CLEANFILES =						\ | ||||
| 	gnome-shell.desktop.in				\ | ||||
| 	gnome-shell-wayland.desktop.in			\ | ||||
| 	gnome-shell-extension-prefs.in			\ | ||||
| 	$(desktop_DATA)					\ | ||||
| 	$(keys_DATA)					\ | ||||
| 	$(gsettings_SCHEMAS)				\ | ||||
| 	perf-background.xml				\ | ||||
| 	gschemas.compiled				\ | ||||
| 	org.gnome.shell.gschema.valid			\ | ||||
| 	org.gnome.shell.gschema.xml.in | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| [org.gnome.shell.overrides] | ||||
| attach-modal-dialogs = /desktop/gnome/shell/windows/attach_modal_dialogs | ||||
| button-layout = /desktop/gnome/shell/windows/button_layout | ||||
| edge-tiling = /desktop/gnome/shell/windows/edge_tiling | ||||
| workspaces-only-on-primary = /desktop/gnome/shell/windows/workspaces_only_on_primary | ||||
|   | ||||
| @@ -1,15 +0,0 @@ | ||||
| [Desktop Entry] | ||||
| Type=Application | ||||
| _Name=GNOME Shell (wayland compositor) | ||||
| _Comment=Window management and application launching | ||||
| Exec=@bindir@/gnome-shell --wayland --display-server | ||||
| X-GNOME-Bugzilla-Bugzilla=GNOME | ||||
| X-GNOME-Bugzilla-Product=gnome-shell | ||||
| X-GNOME-Bugzilla-Component=general | ||||
| X-GNOME-Bugzilla-Version=@VERSION@ | ||||
| Categories=GNOME;GTK;Core; | ||||
| OnlyShowIn=GNOME; | ||||
| NoDisplay=true | ||||
| X-GNOME-Autostart-Phase=DisplayServer | ||||
| X-GNOME-Autostart-Notify=true | ||||
| X-GNOME-AutoRestart=false | ||||
| @@ -1,9 +0,0 @@ | ||||
| [Desktop Entry] | ||||
| _Name=Captive Portal | ||||
| Type=Application | ||||
| Exec=gapplication launch org.gnome.Shell.PortalHelper | ||||
| DBusActivatable=true | ||||
| NoDisplay=true | ||||
| Icon=network-workgroup | ||||
| StartupNotify=true | ||||
| OnlyShowIn=GNOME; | ||||
| @@ -1,3 +0,0 @@ | ||||
| [D-BUS Service] | ||||
| Name=org.gnome.Shell.PortalHelper | ||||
| Exec=@libexecdir@/gnome-shell-portal-helper | ||||
| @@ -1,95 +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="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> | ||||
| @@ -1,128 +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.Screenshot: | ||||
|       @short_description: Screenshot interface | ||||
|  | ||||
|       The interface used to capture pictures of the screen contents. | ||||
|   --> | ||||
|   <interface name="org.gnome.Shell.Screenshot"> | ||||
|  | ||||
|     <!-- | ||||
|         Screenshot: | ||||
|         @filename: The filename for the screenshot | ||||
|         @include_cursor: Whether to include the cursor image or not | ||||
|         @flash: Whether to flash the screen or not | ||||
|         @success: whether the screenshot was captured | ||||
|         @filename_used: the file where the screenshot was saved | ||||
|  | ||||
|         Takes a screenshot of the whole screen and saves it | ||||
|         in @filename as png image, it returns a boolean | ||||
|         indicating whether the operation was successful or not. | ||||
|         @filename can either be an absolute path or a basename, in | ||||
|         which case the screenshot will be saved in the $XDG_PICTURES_DIR | ||||
|         or the home directory if it doesn't exist. The filename used | ||||
|         to save the screenshot will be returned in @filename_used. | ||||
|     --> | ||||
|     <method name="Screenshot"> | ||||
|       <arg type="b" direction="in" name="include_cursor"/> | ||||
|       <arg type="b" direction="in" name="flash"/> | ||||
|       <arg type="s" direction="in" name="filename"/> | ||||
|       <arg type="b" direction="out" name="success"/> | ||||
|       <arg type="s" direction="out" name="filename_used"/> | ||||
|     </method> | ||||
|  | ||||
|     <!-- | ||||
|         ScreenshotWindow: | ||||
|         @include_frame: Whether to include the frame or not | ||||
|         @include_cursor: Whether to include the cursor image or not | ||||
|         @flash: Whether to flash the window area or not | ||||
|         @filename: The filename for the screenshot | ||||
|         @success: whether the screenshot was captured | ||||
|         @filename_used: the file where the screenshot was saved | ||||
|  | ||||
|         Takes a screenshot of the focused window (optionally omitting the frame) | ||||
|         and saves it in @filename as png image, it returns a boolean | ||||
|         indicating whether the operation was successful or not. | ||||
|         @filename can either be an absolute path or a basename, in | ||||
|         which case the screenshot will be saved in the $XDG_PICTURES_DIR | ||||
|         or the home directory if it doesn't exist. The filename used | ||||
|         to save the screenshot will be returned in @filename_used. | ||||
|     --> | ||||
|     <method name="ScreenshotWindow"> | ||||
|       <arg type="b" direction="in" name="include_frame"/> | ||||
|       <arg type="b" direction="in" name="include_cursor"/> | ||||
|       <arg type="b" direction="in" name="flash"/> | ||||
|       <arg type="s" direction="in" name="filename"/> | ||||
|       <arg type="b" direction="out" name="success"/> | ||||
|       <arg type="s" direction="out" name="filename_used"/> | ||||
|     </method> | ||||
|  | ||||
|     <!-- | ||||
|         ScreenshotArea: | ||||
|         @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 | ||||
|         @flash: whether to flash the area or not | ||||
|         @filename: the filename for the screenshot | ||||
|         @success: whether the screenshot was captured | ||||
|         @filename_used: the file where the screenshot was saved | ||||
|  | ||||
|         Takes a screenshot of the passed in area and saves it | ||||
|         in @filename as png image, it returns a boolean | ||||
|         indicating whether the operation was successful or not. | ||||
|         @filename can either be an absolute path or a basename, in | ||||
|         which case the screenshot will be saved in the $XDG_PICTURES_DIR | ||||
|         or the home directory if it doesn't exist. The filename used | ||||
|         to save the screenshot will be returned in @filename_used. | ||||
|     --> | ||||
|     <method name="ScreenshotArea"> | ||||
|       <arg type="i" direction="in" name="x"/> | ||||
|       <arg type="i" direction="in" name="y"/> | ||||
|       <arg type="i" direction="in" name="width"/> | ||||
|       <arg type="i" direction="in" name="height"/> | ||||
|       <arg type="b" direction="in" name="flash"/> | ||||
|       <arg type="s" direction="in" name="filename"/> | ||||
|       <arg type="b" direction="out" name="success"/> | ||||
|       <arg type="s" direction="out" name="filename_used"/> | ||||
|     </method> | ||||
|  | ||||
|     <!-- | ||||
|         FlashArea: | ||||
|         @x: the X coordinate of the area to flash | ||||
|         @y: the Y coordinate of the area to flash | ||||
|         @width: the width of the area to flash | ||||
|         @height: the height of the area to flash | ||||
|  | ||||
|         Renders a flash spot effect in the specified rectangle of the screen. | ||||
|     --> | ||||
|     <method name="FlashArea"> | ||||
|       <arg type="i" direction="in" name="x"/> | ||||
|       <arg type="i" direction="in" name="y"/> | ||||
|       <arg type="i" direction="in" name="width"/> | ||||
|       <arg type="i" direction="in" name="height"/> | ||||
|     </method> | ||||
|  | ||||
|     <!-- | ||||
|         SelectArea: | ||||
|         @x: the X coordinate of the selected area | ||||
|         @y: the Y coordinate of the selected area | ||||
|         @width: the width of the selected area | ||||
|         @height: the height of the selected area | ||||
|  | ||||
|         Interactively allows the user to select a rectangular area of | ||||
|         the screen, and returns its coordinates. | ||||
|     --> | ||||
|     <method name="SelectArea"> | ||||
|       <arg type="i" direction="out" name="x"/> | ||||
|       <arg type="i" direction="out" name="y"/> | ||||
|       <arg type="i" direction="out" name="width"/> | ||||
|       <arg type="i" direction="out" name="height"/> | ||||
|     </method> | ||||
|  | ||||
|   </interface> | ||||
| </node> | ||||
| @@ -46,7 +46,7 @@ | ||||
|     <!-- | ||||
|         GetResultMetas: | ||||
|         @identifiers: An array of result identifiers as returned by GetInitialResultSet() or GetSubsearchResultSet() | ||||
|         @metas: A dictionary describing the given search result, containing a human-readable 'name' (string), along with the result identifier this meta is for, 'id' (string). Optionally, 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. | ||||
|         @metas: A dictionary describing the given search result, containing 'id' and 'name' (both strings). 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). | ||||
|  | ||||
|         Return an array of meta data used to display each given result | ||||
|     --> | ||||
|   | ||||
| @@ -46,7 +46,7 @@ | ||||
|     <!-- | ||||
|         GetResultMetas: | ||||
|         @identifiers: An array of result identifiers as returned by GetInitialResultSet() or GetSubsearchResultSet() | ||||
|         @metas: A dictionary describing the given search result, containing a human-readable 'name' (string), along with the result identifier this meta is for, 'id' (string). Optionally, 'icon' (a serialized GIcon as obtained by g_icon_serialize) can be specified if the result can be better served with a thumbnail of the content (such as with images). 'gicon' (a serialized GIcon as obtained by g_icon_to_string) or 'icon-data' (raw image data as (iiibiiay) - width, height, rowstride, has-alpha, bits per sample, channels, data) are deprecated values that can also be used for that purpose. A 'description' field (string) may also be specified if more context would help the user find the desired result. | ||||
|         @metas: A dictionary describing the given search result, containing 'id' and 'name' (both strings). Optionally, either 'gicon' (a serialized GIcon) or 'icon-data' (raw image data as (iiibiiay) - width, height, rowstride, has-alpha, bits per sample, channels, data) can be specified if the result can be better served with a thumbnail of the content (such as with images). A 'description' field (string) may also be specified if more context would help the user find the desired result. | ||||
|  | ||||
|         Return an array of meta data used to display each given result | ||||
|     --> | ||||
|   | ||||
| @@ -13,38 +13,32 @@ | ||||
|     </key> | ||||
|     <key name="enabled-extensions" type="as"> | ||||
|       <default>[]</default> | ||||
|       <_summary>UUIDs of extensions to enable</_summary> | ||||
|       <_summary>Uuids of extensions to enable</_summary> | ||||
|       <_description> | ||||
|         GNOME Shell extensions have a UUID property; this key lists extensions | ||||
|         GNOME Shell extensions have a uuid property; this key lists extensions | ||||
|         which should be loaded. Any extension that wants to be loaded needs | ||||
|         to be in this list. You can also manipulate this list with the | ||||
|         EnableExtension and DisableExtension D-Bus methods on org.gnome.Shell. | ||||
|         EnableExtension and DisableExtension DBus methods on org.gnome.Shell. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="disable-extension-version-validation" type="b"> | ||||
|       <default>false</default> | ||||
|       <_summary>Disables the validation of extension version compatibility</_summary> | ||||
|     <key name="enable-app-monitoring" type="b"> | ||||
|       <default>true</default> | ||||
|       <_summary>Whether to collect stats about applications usage</_summary> | ||||
|       <_description> | ||||
|         GNOME Shell will only load extensions that claim to support the current | ||||
|         running version. Enabling this option will disable this check and try to | ||||
|         load all extensions regardless of the versions they claim to support. | ||||
|         The shell normally monitors active applications in order to present | ||||
|         the most used ones (e.g. in launchers). While this data will be | ||||
|         kept private, you may want to disable this for privacy reasons. | ||||
|         Please note that doing so won't remove already saved data. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="favorite-apps" type="as"> | ||||
|       <default>[ 'epiphany.desktop', 'evolution.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.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> | ||||
|       <_description> | ||||
|         The applications corresponding to these identifiers | ||||
|         will be displayed in the favorites area. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="app-picker-view" type="u"> | ||||
|       <default>0</default> | ||||
|       <_summary>App Picker View</_summary> | ||||
|       <_description> | ||||
|         Index of the currently selected view in the application picker. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="command-history" type="as"> | ||||
|       <default>[]</default> | ||||
|       <_summary>History for command (Alt-F2) dialog</_summary> | ||||
| @@ -53,14 +47,29 @@ | ||||
|       <default>[]</default> | ||||
|       <_summary>History for the looking glass dialog</_summary> | ||||
|     </key> | ||||
|     <key name="saved-im-presence" type="i"> | ||||
|       <default>1</default> | ||||
|       <_summary>Internally used to store the last IM presence explicitly set by the user. The | ||||
| value here is from the TpConnectionPresenceType enumeration.</_summary> | ||||
|     </key> | ||||
|     <key name="saved-session-presence" type="i"> | ||||
|       <default>0</default> | ||||
|       <_summary>Internally used to store the last session presence status for the user. The | ||||
| value here is from the GsmPresenceStatus enumeration.</_summary> | ||||
|     </key> | ||||
|     <key name="always-show-log-out" type="b"> | ||||
|       <default>false</default> | ||||
|       <_summary>Always show the 'Log out' menu item in the user menu.</_summary> | ||||
|       <_summary>Always show the 'Log out' menuitem in the user menu.</_summary> | ||||
|       <_description> | ||||
|         This key overrides the automatic hiding of the 'Log out' | ||||
|         menu item in single-user, single-session situations. | ||||
|         menuitem in single-user, single-session situations. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="show-full-name" type="b"> | ||||
|       <default>true</default> | ||||
|       <_summary>Show full name in the user menu</_summary> | ||||
|       <_description>Whether the users full name is shown in the user menu or not.</_description> | ||||
|     </key> | ||||
|     <key name="remember-mount-password" type="b"> | ||||
|       <default>false</default> | ||||
|       <_summary>Whether to remember password for mounting encrypted or remote filesystems</_summary> | ||||
| @@ -72,6 +81,7 @@ | ||||
|       </_description> | ||||
|     </key> | ||||
|     <child name="calendar" schema="org.gnome.shell.calendar"/> | ||||
|     <child name="recorder" schema="org.gnome.shell.recorder"/> | ||||
|     <child name="keybindings" schema="org.gnome.shell.keybindings"/> | ||||
|     <child name="keyboard" schema="org.gnome.shell.keyboard"/> | ||||
|   </schema> | ||||
| @@ -104,13 +114,6 @@ | ||||
|         Overview. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="toggle-overview" type="as"> | ||||
|       <default>["<Super>s"]</default> | ||||
|       <_summary>Keybinding to open the overview</_summary> | ||||
|       <_description> | ||||
|         Keybinding to open the Activities Overview. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="toggle-message-tray" type="as"> | ||||
|       <default>["<Super>m"]</default> | ||||
|       <_summary>Keybinding to toggle the visibility of the message tray</_summary> | ||||
| @@ -125,10 +128,12 @@ | ||||
|         Keybinding to focus the active notification. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="pause-resume-tweens" type="as"> | ||||
|       <default>[]</default> | ||||
|       <_summary>Keybinding that pauses and resumes all running tweens, for debugging purposes</_summary> | ||||
|       <_description></_description> | ||||
|     <key name="toggle-recording" type="as"> | ||||
|       <default><![CDATA[['<Control><Shift><Alt>r']]]></default> | ||||
|       <_summary>Keybinding to toggle the screen recorder</_summary> | ||||
|       <_description> | ||||
|         Keybinding to start/stop the builtin screen recorder. | ||||
|       </_description> | ||||
|     </key> | ||||
|   </schema> | ||||
|  | ||||
| @@ -143,15 +148,40 @@ | ||||
|     </key> | ||||
|   </schema> | ||||
|  | ||||
|   <schema id="org.gnome.shell.app-switcher" | ||||
|           path="/org/gnome/shell/app-switcher/" | ||||
|   <schema id="org.gnome.shell.recorder" path="/org/gnome/shell/recorder/" | ||||
|           gettext-domain="@GETTEXT_PACKAGE@"> | ||||
|     <key type="b" name="current-workspace-only"> | ||||
|       <default>false</default> | ||||
|       <_summary>Limit switcher to current workspace.</_summary> | ||||
|     <key name="framerate" type="i"> | ||||
|       <default>30</default> | ||||
|       <_summary>Framerate used for recording screencasts.</_summary> | ||||
|       <_description> | ||||
| 	If true, only applications that have windows on the current workspace are shown in the switcher. | ||||
| 	Otherwise, all applications are included. | ||||
|         The framerate of the resulting screencast recordered | ||||
|         by GNOME Shell's screencast recorder in frames-per-second. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="pipeline" type="s"> | ||||
|       <default>''</default> | ||||
|       <_summary>The gstreamer pipeline used to encode the screencast</_summary> | ||||
|       <_description> | ||||
|         Sets the GStreamer pipeline used to encode recordings. | ||||
|         It follows the syntax used for gst-launch. The pipeline should have | ||||
|         an unconnected sink pad where the recorded video is recorded. It will | ||||
|         normally have a unconnected source pad; output from that pad | ||||
|         will be written into the output file. However the pipeline can also | ||||
|         take care of its own output - this might be used to send the output | ||||
|         to an icecast server via shout2send or similar. When unset or set | ||||
|         to an empty value, the default pipeline will be used. This is currently | ||||
|         'vp8enc min_quantizer=13 max_quantizer=13 cpu-used=5 deadline=1000000 threads=%T ! queue ! webmmux' | ||||
|         and records to WEBM using the VP8 codec. %T is used as a placeholder | ||||
|         for a guess at the optimal thread count on the system. | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key name="file-extension" type="s"> | ||||
|       <default>'webm'</default> | ||||
|       <_summary>File extension used for storing the screencast</_summary> | ||||
|       <_description> | ||||
|         The filename for recorded screencasts will be a unique filename | ||||
|         based on the current date, and use this extension. It should be | ||||
|         changed when recording to a different container format. | ||||
|       </_description> | ||||
|     </key> | ||||
|   </schema> | ||||
| @@ -174,12 +204,12 @@ | ||||
|       </_description> | ||||
|     </key> | ||||
|     <key type="b" name="current-workspace-only"> | ||||
|       <default>true</default> | ||||
|       <_summary>Limit switcher to current workspace.</_summary> | ||||
|       <_description> | ||||
|       <default>false</default> | ||||
|       <summary>Limit switcher to current workspace.</summary> | ||||
|       <description> | ||||
| 	If true, only windows from the current workspace are shown in the switcher. | ||||
| 	Otherwise, all windows are included. | ||||
|       </_description> | ||||
|       </description> | ||||
|     </key> | ||||
|   </schema> | ||||
|  | ||||
| @@ -194,6 +224,15 @@ | ||||
|       </_description> | ||||
|     </key> | ||||
|  | ||||
|     <key name="button-layout" type="s"> | ||||
|       <default>":close"</default> | ||||
|       <_summary>Arrangement of buttons on the titlebar</_summary> | ||||
|       <_description> | ||||
|         This key overrides the key in org.gnome.desktop.wm.preferences when | ||||
|         running GNOME Shell. | ||||
|       </_description> | ||||
|     </key> | ||||
|  | ||||
|     <key name="edge-tiling" type="b"> | ||||
|       <default>true</default> | ||||
|       <_summary>Enable edge tiling when dropping windows on screen edges</_summary> | ||||
| @@ -220,10 +259,10 @@ | ||||
|  | ||||
|     <key name="focus-change-on-pointer-rest" type="b"> | ||||
|       <default>true</default> | ||||
|       <_summary>Delay focus changes in mouse mode until the pointer stops moving</_summary> | ||||
|       <_description> | ||||
|       <summary>Delay focus changes in mouse mode until the pointer stops moving</summary> | ||||
|       <description> | ||||
|         This key overrides the key in org.gnome.mutter when running GNOME Shell. | ||||
|       </_description> | ||||
|       </description> | ||||
|     </key> | ||||
|   </schema> | ||||
| </schemalist> | ||||
|   | ||||
| @@ -1,31 +0,0 @@ | ||||
| <!-- With an animated background, performance will differ depending on whether | ||||
|      one layer or two layers are being blended together. This messes up our | ||||
|      benchmarks. We could just benchmark a single image, but since blended | ||||
|      images are present for much of the day with the GNOME default background, | ||||
|      we want to make sure that also performs well; for that reason we ship | ||||
|      an "animated" background that animates super-slowly to use during | ||||
|      performance tests; it will be in the blended state until 2030. --> | ||||
| <background> | ||||
|   <starttime> | ||||
|     <year>1990</year> | ||||
|     <month>1</month> | ||||
|     <day>1</day> | ||||
|     <hour>0</hour> | ||||
|     <minute>00</minute> | ||||
|     <second>00</second> | ||||
|   </starttime> | ||||
|  | ||||
| <!-- One transition that takes 40 years --> | ||||
| <transition type="overlay"> | ||||
| <duration>1261440000.0</duration> | ||||
| <from>@datadir@/backgrounds/gnome/adwaita-morning.jpg</from> | ||||
| <to>@datadir@/backgrounds/gnome/adwaita-day.jpg</to> | ||||
| </transition> | ||||
|  | ||||
| <!-- A single slide doesn't work, so another slide for 1 minute after 40 years --> | ||||
| <static> | ||||
| <duration>60</duration> | ||||
| <file>/usr/share/backgrounds/gnome/Sandstone.jpg</file> | ||||
| </static> | ||||
|  | ||||
| </background> | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -14,7 +14,7 @@ | ||||
|    height="16" | ||||
|    id="svg12430" | ||||
|    version="1.1" | ||||
|    inkscape:version="0.48.4 r9939" | ||||
|    inkscape:version="0.48.3.1 r9886" | ||||
|    sodipodi:docname="more-results.svg"> | ||||
|   <defs | ||||
|      id="defs12432" /> | ||||
| @@ -25,18 +25,18 @@ | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="1" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="90.509668" | ||||
|      inkscape:cx="6.5009792" | ||||
|      inkscape:cy="8.3589595" | ||||
|      inkscape:zoom="1" | ||||
|      inkscape:cx="8.3155237" | ||||
|      inkscape:cy="0.89548874" | ||||
|      inkscape:document-units="px" | ||||
|      inkscape:current-layer="g14642-3-0" | ||||
|      showgrid="false" | ||||
|      borderlayer="true" | ||||
|      inkscape:showpageshadow="false" | ||||
|      inkscape:window-width="1440" | ||||
|      inkscape:window-height="840" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="27" | ||||
|      inkscape:window-width="2560" | ||||
|      inkscape:window-height="1376" | ||||
|      inkscape:window-x="1200" | ||||
|      inkscape:window-y="187" | ||||
|      inkscape:window-maximized="1"> | ||||
|     <inkscape:grid | ||||
|        type="xygrid" | ||||
| @@ -90,11 +90,6 @@ | ||||
|          transform="translate(-2,0)" | ||||
|          width="16" | ||||
|          height="16" /> | ||||
|       <path | ||||
|          style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" | ||||
|          d="M 7 5 L 7 7 L 5 7 L 5 9 L 7 9 L 7 11 L 9 11 L 9 9 L 11 9 L 11 7 L 9 7 L 9 5 L 7 5 z " | ||||
|          transform="translate(141.99984,397.99107)" | ||||
|          id="rect3757" /> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          style="color:#bebebe;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible" | ||||
|   | ||||
| Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.1 KiB | 
| @@ -1,71 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
|  | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="18" | ||||
|    height="18" | ||||
|    id="svg4703" | ||||
|    version="1.1" | ||||
|    inkscape:version="0.48.4 r9939" | ||||
|    sodipodi:docname="page-indicator-pushed.svg"> | ||||
|   <defs | ||||
|      id="defs4705" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0.0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="31.392433" | ||||
|      inkscape:cx="1.0245308" | ||||
|      inkscape:cy="13.3715" | ||||
|      inkscape:current-layer="layer1" | ||||
|      showgrid="true" | ||||
|      inkscape:grid-bbox="true" | ||||
|      inkscape:document-units="px" | ||||
|      inkscape:window-width="2560" | ||||
|      inkscape:window-height="1374" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="27" | ||||
|      inkscape:window-maximized="1"> | ||||
|     <inkscape:grid | ||||
|        type="xygrid" | ||||
|        id="grid6140" /> | ||||
|   </sodipodi:namedview> | ||||
|   <metadata | ||||
|      id="metadata4708"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title></dc:title> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      id="layer1" | ||||
|      inkscape:label="Layer 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      transform="translate(0,2)"> | ||||
|     <path | ||||
|        transform="matrix(0.54617904,0,0,0.62523128,-1131.9904,-392.39214)" | ||||
|        d="m 2099.9808,638.83099 a 10.985409,9.5964489 0 1 1 -21.9708,0 10.985409,9.5964489 0 1 1 21.9708,0 z" | ||||
|        sodipodi:ry="9.5964489" | ||||
|        sodipodi:rx="10.985409" | ||||
|        sodipodi:cy="638.83099" | ||||
|        sodipodi:cx="2088.9954" | ||||
|        id="path4711" | ||||
|        style="fill:#fdffff;fill-opacity:1;stroke:none" | ||||
|        sodipodi:type="arc" /> | ||||
|   </g> | ||||
| </svg> | ||||
| Before Width: | Height: | Size: 2.1 KiB | 
| @@ -1,67 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
|  | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="18" | ||||
|    height="18" | ||||
|    id="svg4703" | ||||
|    version="1.1" | ||||
|    inkscape:version="0.48.4 r9939" | ||||
|    sodipodi:docname="page-indicator-active.svg"> | ||||
|   <defs | ||||
|      id="defs4705" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0.0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="22.197802" | ||||
|      inkscape:cx="2.1522887" | ||||
|      inkscape:cy="16.782904" | ||||
|      inkscape:current-layer="layer1" | ||||
|      showgrid="true" | ||||
|      inkscape:grid-bbox="true" | ||||
|      inkscape:document-units="px" | ||||
|      inkscape:window-width="1920" | ||||
|      inkscape:window-height="1021" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="27" | ||||
|      inkscape:window-maximized="1" /> | ||||
|   <metadata | ||||
|      id="metadata4708"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title /> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      id="layer1" | ||||
|      inkscape:label="Layer 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      transform="translate(0,2)"> | ||||
|     <path | ||||
|        transform="matrix(0.72823872,0,0,0.8336417,-1512.2872,-525.55618)" | ||||
|        d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 -10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z" | ||||
|        sodipodi:ry="9.5964489" | ||||
|        sodipodi:rx="10.985409" | ||||
|        sodipodi:cy="638.83099" | ||||
|        sodipodi:cx="2088.9954" | ||||
|        id="path4711" | ||||
|        style="fill:#fdffff;fill-opacity:0.94117647;stroke:none" | ||||
|        sodipodi:type="arc" /> | ||||
|   </g> | ||||
| </svg> | ||||
| Before Width: | Height: | Size: 2.1 KiB | 
| @@ -1,67 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
|  | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="18" | ||||
|    height="18" | ||||
|    id="svg5266" | ||||
|    version="1.1" | ||||
|    inkscape:version="0.48.4 r9939" | ||||
|    sodipodi:docname="page-indicator-inactive.svg"> | ||||
|   <defs | ||||
|      id="defs5268" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="11.313709" | ||||
|      inkscape:cx="-2.307566" | ||||
|      inkscape:cy="17.859535" | ||||
|      inkscape:current-layer="layer1" | ||||
|      showgrid="true" | ||||
|      inkscape:grid-bbox="true" | ||||
|      inkscape:document-units="px" | ||||
|      inkscape:window-width="2560" | ||||
|      inkscape:window-height="1374" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="27" | ||||
|      inkscape:window-maximized="1" /> | ||||
|   <metadata | ||||
|      id="metadata5271"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title></dc:title> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      id="layer1" | ||||
|      inkscape:label="Layer 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      transform="translate(0,2)"> | ||||
|     <path | ||||
|        sodipodi:type="arc" | ||||
|        style="fill:none;fill-opacity:0;stroke:#ffffff;stroke-width:2.93356276000000005;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" | ||||
|        id="path5274" | ||||
|        sodipodi:cx="2088.9954" | ||||
|        sodipodi:cy="638.83099" | ||||
|        sodipodi:rx="10.985409" | ||||
|        sodipodi:ry="9.5964489" | ||||
|        d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 -10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z" | ||||
|        transform="matrix(0.63720887,0,0,0.72943648,-1322.1264,-458.98661)" /> | ||||
|   </g> | ||||
| </svg> | ||||
| Before Width: | Height: | Size: 2.2 KiB | 
| @@ -1,67 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
|  | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="18" | ||||
|    height="18" | ||||
|    id="svg5266" | ||||
|    version="1.1" | ||||
|    inkscape:version="0.48.4 r9939" | ||||
|    sodipodi:docname="page-indicator-inactive.svg"> | ||||
|   <defs | ||||
|      id="defs5268" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="11.313709" | ||||
|      inkscape:cx="-2.307566" | ||||
|      inkscape:cy="17.859535" | ||||
|      inkscape:current-layer="layer1" | ||||
|      showgrid="true" | ||||
|      inkscape:grid-bbox="true" | ||||
|      inkscape:document-units="px" | ||||
|      inkscape:window-width="2560" | ||||
|      inkscape:window-height="1374" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="27" | ||||
|      inkscape:window-maximized="1" /> | ||||
|   <metadata | ||||
|      id="metadata5271"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title /> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      id="layer1" | ||||
|      inkscape:label="Layer 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      transform="translate(0,2)"> | ||||
|     <path | ||||
|        sodipodi:type="arc" | ||||
|        style="fill:none;fill-opacity:0;stroke:#ffffff;stroke-width:2.93356276000000005;stroke-miterlimit:4;stroke-opacity:0.39215686000000000;stroke-dasharray:none" | ||||
|        id="path5274" | ||||
|        sodipodi:cx="2088.9954" | ||||
|        sodipodi:cy="638.83099" | ||||
|        sodipodi:rx="10.985409" | ||||
|        sodipodi:ry="9.5964489" | ||||
|        d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 -10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z" | ||||
|        transform="matrix(0.63720887,0,0,0.72943648,-1322.1264,-458.98661)" /> | ||||
|   </g> | ||||
| </svg> | ||||
| Before Width: | Height: | Size: 2.2 KiB | 
| @@ -66,7 +66,6 @@ IGNORE_HFILES=					\ | ||||
| 	gactionmuxer.h				\ | ||||
| 	gactionobservable.h			\ | ||||
| 	gactionobserver.h			\ | ||||
| 	shell-network-agent.h			\ | ||||
| 	shell-recorder-src.h | ||||
|  | ||||
| if !BUILD_RECORDER | ||||
| @@ -89,18 +88,11 @@ doc-gen-org.gnome.Shell.SearchProvider2.xml: $(top_srcdir)/data/org.gnome.ShellS | ||||
| 	--generate-docbook doc-gen				\ | ||||
| 	$(top_srcdir)/data/org.gnome.ShellSearchProvider2.xml | ||||
|  | ||||
| doc-gen-org.gnome.Shell.Screenshot.xml: $(top_srcdir)/data/org.gnome.Shell.Screenshot.xml | ||||
| 	gdbus-codegen 						\ | ||||
| 	--interface-prefix org.gnome.Shell.Screenshot.		\ | ||||
| 	--generate-docbook doc-gen				\ | ||||
| 	$(top_srcdir)/data/org.gnome.Shell.Screenshot.xml | ||||
|  | ||||
| # Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). | ||||
| # e.g. content_files=running.sgml building.sgml changes-2.0.sgml | ||||
| content_files= \ | ||||
| 	doc-gen-org.gnome.Shell.SearchProvider.xml	\ | ||||
| 	doc-gen-org.gnome.Shell.SearchProvider2.xml	\ | ||||
| 	doc-gen-org.gnome.Shell.Screenshot.xml | ||||
| 	doc-gen-org.gnome.Shell.SearchProvider2.xml | ||||
|  | ||||
| # SGML files where gtk-doc abbrevations (#GtkWidget) are expanded | ||||
| # These files must be listed here *and* in content_files | ||||
| @@ -113,7 +105,7 @@ expand_content_files= | ||||
| # e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS) | ||||
| # e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) | ||||
| GTKDOC_CFLAGS=$(GNOME_SHELL_CFLAGS) | ||||
| GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(top_builddir)/src/libgnome-shell-menu.la $(top_builddir)/src/libgnome-shell-base.la $(top_builddir)/src/libgnome-shell.la | ||||
| GTKDOC_LIBS=$(GNOME_SHELL_LIBS) $(BLUETOOTH_LIBS) $(top_builddir)/src/libgnome-shell.la | ||||
|  | ||||
| # This includes the standard gtk-doc make rules, copied by gtkdocize. | ||||
| include $(top_srcdir)/gtk-doc.make | ||||
|   | ||||
| @@ -46,10 +46,12 @@ | ||||
|     <xi:include href="doc-gen-org.gnome.Shell.SearchProvider.xml"/> | ||||
|     <xi:include href="doc-gen-org.gnome.Shell.SearchProvider2.xml"/> | ||||
|     <xi:include href="xml/shell-global.xml"/> | ||||
|     <xi:include href="xml/shell-keybinding-modes.xml"/> | ||||
|     <xi:include href="xml/shell-wm.xml"/> | ||||
|     <xi:include href="xml/shell-xfixes-cursor.xml"/> | ||||
|     <xi:include href="xml/shell-util.xml"/> | ||||
|     <xi:include href="xml/shell-mount-operation.xml"/> | ||||
|     <xi:include href="xml/shell-mobile-providers.xml"/> | ||||
|     <xi:include href="xml/shell-network-agent.xml"/> | ||||
|     <xi:include href="xml/shell-polkit-authentication-agent.xml"/> | ||||
|     <xi:include href="xml/shell-tp-client.xml"/> | ||||
|   </chapter> | ||||
|   | ||||
| @@ -17,19 +17,19 @@ packages. If you are interested in building GNOME Shell from source, | ||||
| we would recommend building from version control using the build | ||||
| script described at: | ||||
|  | ||||
|  https://wiki.gnome.org/Projects/GnomeShell | ||||
|  http://live.gnome.org/GnomeShell | ||||
|  | ||||
| Not only will that give you the very latest version of this rapidly | ||||
| changing project, it will be much easier than get GNOME Shell and | ||||
| its dependencies to build from tarballs.</description> | ||||
|   <homepage rdf:resource="https://wiki.gnome.org/Projects/GnomeShell" /> | ||||
|   <!-- | ||||
|   <homepage rdf:resource="http://live.gnome.org/GnomeShell" /> | ||||
|   --> | ||||
|   <mailing-list rdf:resource="http://mail.gnome.org/mailman/listinfo/gnome-shell-list" /> | ||||
|   <download-page rdf:resource="http://download.gnome.org/sources/gnome-shell/" /> | ||||
|   <bug-database rdf:resource="https://bugzilla.gnome.org/browse.cgi?product=gnome-shell" /> | ||||
|   <bug-database rdf:resource="http://bugzilla.gnome.org/browse.cgi?product=gnome-shell" /> | ||||
|  | ||||
|   <category rdf:resource="http://api.gnome.org/doap-extensions#core" /> | ||||
|   <programming-language>JavaScript</programming-language> | ||||
|   <programming-language>C</programming-language> | ||||
|   <category rdf:resource="http://api.gnome.org/doap-extensions#desktop" /> | ||||
|  | ||||
|   <maintainer> | ||||
|     <foaf:Person> | ||||
|   | ||||
							
								
								
									
										118
									
								
								js/Makefile.am
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								js/Makefile.am
									
									
									
									
									
								
							| @@ -1,38 +1,110 @@ | ||||
| NULL = | ||||
| BUILT_SOURCES = | ||||
|  | ||||
| EXTRA_DIST = misc/config.js.in | ||||
| CLEANFILES = misc/config.js | ||||
|  | ||||
| misc/config.js: misc/config.js.in Makefile | ||||
| 	[ -d $(@D) ] || $(mkdir_p) $(@D) ; \ | ||||
| 	sed -e "s|[@]PACKAGE_NAME@|$(PACKAGE_NAME)|g" \ | ||||
| 	    -e "s|[@]PACKAGE_VERSION@|$(PACKAGE_VERSION)|g" \ | ||||
| 	    -e "s|[@]HAVE_BLUETOOTH@|$(HAVE_BLUETOOTH)|g" \ | ||||
| 	    -e "s|[@]HAVE_NETWORKMANAGER@|$(HAVE_NETWORKMANAGER)|g" \ | ||||
| 	    -e "s|[@]GETTEXT_PACKAGE@|$(GETTEXT_PACKAGE)|g" \ | ||||
| 	    -e "s|[@]datadir@|$(datadir)|g" \ | ||||
| 	    -e "s|[@]libexecdir@|$(libexecdir)|g" \ | ||||
| 	    -e "s|[@]sysconfdir@|$(sysconfdir)|g" \ | ||||
|                $< > $@ | ||||
|  | ||||
| js_resource_files = $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(srcdir) --sourcedir=$(builddir) --generate-dependencies $(srcdir)/js-resources.gresource.xml) | ||||
| js-resources.h: js-resources.gresource.xml $(js_resource_files) misc/config.js | ||||
| 	$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --sourcedir=$(builddir) --generate --c-name shell_js_resources $< | ||||
| js-resources.c: js-resources.gresource.xml $(js_resource_files) misc/config.js | ||||
| 	$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --sourcedir=$(builddir) --generate --c-name shell_js_resources $< | ||||
| jsdir = $(pkgdatadir)/js | ||||
|  | ||||
| js_built_sources = js-resources.c js-resources.h | ||||
|  | ||||
| BUILT_SOURCES += $(js_built_sources) | ||||
|  | ||||
| all-local: $(js_built_sources) | ||||
|  | ||||
| js_resource_dist_files = $(filter-out misc/config.js, $(js_resource_files)) | ||||
|  | ||||
| EXTRA_DIST = \ | ||||
| 	$(js_resource_dist_files) \ | ||||
| 	js-resources.gresource.xml \ | ||||
| 	misc/config.js.in \ | ||||
| 	$(NULL) | ||||
|  | ||||
| CLEANFILES = \ | ||||
| 	$(js_built_sources) \ | ||||
| nobase_dist_js_DATA = 	\ | ||||
| 	gdm/batch.js		\ | ||||
| 	gdm/fingerprint.js	\ | ||||
| 	gdm/loginDialog.js	\ | ||||
| 	gdm/powerMenu.js	\ | ||||
| 	gdm/realmd.js		\ | ||||
| 	gdm/util.js		\ | ||||
| 	extensionPrefs/main.js	\ | ||||
| 	misc/config.js		\ | ||||
| 	misc/extensionUtils.js	\ | ||||
| 	misc/fileUtils.js	\ | ||||
| 	misc/gnomeSession.js	\ | ||||
| 	misc/history.js		\ | ||||
| 	misc/jsParse.js		\ | ||||
| 	misc/loginManager.js	\ | ||||
| 	misc/modemManager.js	\ | ||||
| 	misc/params.js		\ | ||||
| 	misc/util.js		\ | ||||
| 	perf/core.js		\ | ||||
| 	ui/altTab.js		\ | ||||
| 	ui/appDisplay.js	\ | ||||
| 	ui/appFavorites.js	\ | ||||
| 	ui/boxpointer.js	\ | ||||
| 	ui/calendar.js		\ | ||||
| 	ui/checkBox.js		\ | ||||
| 	ui/ctrlAltTab.js	\ | ||||
| 	ui/dash.js		\ | ||||
| 	ui/dateMenu.js		\ | ||||
| 	ui/dnd.js		\ | ||||
| 	ui/endSessionDialog.js	\ | ||||
| 	ui/extensionSystem.js	\ | ||||
| 	ui/extensionDownloader.js \ | ||||
| 	ui/environment.js	\ | ||||
| 	ui/flashspot.js		\ | ||||
| 	ui/ibusCandidatePopup.js\ | ||||
| 	ui/grabHelper.js	\ | ||||
| 	ui/iconGrid.js		\ | ||||
| 	ui/keyboard.js		\ | ||||
| 	ui/layout.js		\ | ||||
| 	ui/lightbox.js		\ | ||||
| 	ui/lookingGlass.js	\ | ||||
| 	ui/magnifier.js		\ | ||||
| 	ui/magnifierDBus.js	\ | ||||
| 	ui/main.js		\ | ||||
| 	ui/messageTray.js	\ | ||||
| 	ui/modalDialog.js	\ | ||||
| 	ui/separator.js		\ | ||||
| 	ui/sessionMode.js	\ | ||||
| 	ui/shellEntry.js	\ | ||||
| 	ui/shellMountOperation.js \ | ||||
| 	ui/notificationDaemon.js \ | ||||
| 	ui/overview.js		\ | ||||
| 	ui/panel.js		\ | ||||
| 	ui/panelMenu.js		\ | ||||
| 	ui/pointerWatcher.js    \ | ||||
| 	ui/popupMenu.js		\ | ||||
| 	ui/remoteSearch.js	\ | ||||
| 	ui/runDialog.js		\ | ||||
|         ui/screenShield.js	\ | ||||
| 	ui/scripting.js		\ | ||||
| 	ui/search.js		\ | ||||
| 	ui/searchDisplay.js	\ | ||||
| 	ui/shellDBus.js		\ | ||||
| 	ui/status/accessibility.js	\ | ||||
| 	ui/status/keyboard.js	\ | ||||
| 	ui/status/lockScreenMenu.js	\ | ||||
| 	ui/status/network.js	\ | ||||
| 	ui/status/power.js	\ | ||||
| 	ui/status/volume.js	\ | ||||
| 	ui/status/bluetooth.js	\ | ||||
| 	ui/switcherPopup.js	\ | ||||
| 	ui/tweener.js		\ | ||||
| 	ui/unlockDialog.js	\ | ||||
| 	ui/userMenu.js		\ | ||||
| 	ui/viewSelector.js	\ | ||||
| 	ui/wanda.js		\ | ||||
| 	ui/windowAttentionHandler.js	\ | ||||
| 	ui/windowManager.js	\ | ||||
| 	ui/workspace.js		\ | ||||
| 	ui/workspaceThumbnail.js	\ | ||||
| 	ui/workspacesView.js	\ | ||||
| 	ui/workspaceSwitcherPopup.js    \ | ||||
| 	ui/xdndHandler.js	\ | ||||
| 	ui/components/__init__.js		\ | ||||
| 	ui/components/autorunManager.js		\ | ||||
| 	ui/components/automountManager.js	\ | ||||
| 	ui/components/networkAgent.js		\ | ||||
| 	ui/components/polkitAgent.js		\ | ||||
| 	ui/components/recorder.js		\ | ||||
| 	ui/components/telepathyClient.js	\ | ||||
| 	ui/components/keyring.js		\ | ||||
| 	$(NULL) | ||||
|   | ||||
| @@ -13,20 +13,13 @@ const _ = Gettext.gettext; | ||||
| const Config = imports.misc.config; | ||||
| const ExtensionUtils = imports.misc.extensionUtils; | ||||
|  | ||||
| const GnomeShellIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.Extensions"> \ | ||||
| <signal name="ExtensionStatusChanged"> \ | ||||
|     <arg type="s" name="uuid"/> \ | ||||
|     <arg type="i" name="state"/> \ | ||||
|     <arg type="s" name="error"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const customCss = '.prefs-button { \ | ||||
|                        padding: 8px; \ | ||||
|                        border-radius: 20px; \ | ||||
|                    }'; | ||||
| const GnomeShellIface = <interface name="org.gnome.Shell.Extensions"> | ||||
| <signal name="ExtensionStatusChanged"> | ||||
|     <arg type="s" name="uuid"/> | ||||
|     <arg type="i" name="state"/> | ||||
|     <arg type="s" name="error"/> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface); | ||||
|  | ||||
| @@ -51,10 +44,12 @@ const Application = new Lang.Class({ | ||||
|  | ||||
|         this._extensionPrefsModules = {}; | ||||
|  | ||||
|         this._startupUuid = null; | ||||
|         this._loaded = false; | ||||
|         this._skipMainWindow = false; | ||||
|         this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' }); | ||||
|         this._extensionIters = {}; | ||||
|     }, | ||||
|  | ||||
|     _buildModel: function() { | ||||
|         this._model = new Gtk.ListStore(); | ||||
|         this._model.set_column_types([GObject.TYPE_STRING, GObject.TYPE_STRING]); | ||||
|     }, | ||||
|  | ||||
|     _extensionAvailable: function(uuid) { | ||||
| @@ -63,12 +58,20 @@ const Application = new Lang.Class({ | ||||
|         if (!extension) | ||||
|             return false; | ||||
|  | ||||
|         if (ExtensionUtils.isOutOfDate(extension)) | ||||
|             return false; | ||||
|  | ||||
|         if (!extension.dir.get_child('prefs.js').query_exists(null)) | ||||
|             return false; | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _setExtensionInsensitive: function(layout, cell, model, iter, data) { | ||||
|         let uuid = model.get_value(iter, 0); | ||||
|         cell.set_sensitive(this._extensionAvailable(uuid)); | ||||
|     }, | ||||
|  | ||||
|     _getExtensionPrefsModule: function(extension) { | ||||
|         let uuid = extension.metadata.uuid; | ||||
|  | ||||
| @@ -98,23 +101,21 @@ const Application = new Lang.Class({ | ||||
|             widget = this._buildErrorUI(extension, e); | ||||
|         } | ||||
|  | ||||
|         let dialog = new Gtk.Dialog({ use_header_bar: true, | ||||
|                                       modal: true, | ||||
|                                       title: extension.metadata.name }); | ||||
|         // Destroy the current prefs widget, if it exists | ||||
|         if (this._extensionPrefsBin.get_child()) | ||||
|             this._extensionPrefsBin.get_child().destroy(); | ||||
|  | ||||
|         if (this._skipMainWindow) { | ||||
|             this.application.add_window(dialog); | ||||
|             if (this._window) | ||||
|                 this._window.destroy(); | ||||
|             this._window = dialog; | ||||
|             this._window.window_position = Gtk.WindowPosition.CENTER; | ||||
|         } else { | ||||
|             dialog.transient_for = this._window; | ||||
|         } | ||||
|         this._extensionPrefsBin.add(widget); | ||||
|         this._extensionSelector.set_active_iter(this._extensionIters[uuid]); | ||||
|     }, | ||||
|  | ||||
|         dialog.set_default_size(600, 400); | ||||
|         dialog.get_content_area().add(widget); | ||||
|         dialog.show(); | ||||
|     _extensionSelected: function() { | ||||
|         let [success, iter] = this._extensionSelector.get_active_iter(); | ||||
|         if (!success) | ||||
|             return; | ||||
|  | ||||
|         let uuid = this._model.get_value(iter, 0); | ||||
|         this._selectExtension(uuid); | ||||
|     }, | ||||
|  | ||||
|     _buildErrorUI: function(extension, exc) { | ||||
| @@ -147,26 +148,48 @@ const Application = new Lang.Class({ | ||||
|  | ||||
|     _buildUI: function(app) { | ||||
|         this._window = new Gtk.ApplicationWindow({ application: app, | ||||
|                                                    window_position: Gtk.WindowPosition.CENTER }); | ||||
|                                                    window_position: Gtk.WindowPosition.CENTER, | ||||
|                                                    title: _("GNOME Shell Extension Preferences") }); | ||||
|  | ||||
|         this._window.set_size_request(800, 500); | ||||
|         this._window.set_size_request(600, 400); | ||||
|  | ||||
|         this._titlebar = new Gtk.HeaderBar({ show_close_button: true, | ||||
|                                              title: _("GNOME Shell Extensions") }); | ||||
|         this._window.set_titlebar(this._titlebar); | ||||
|         let vbox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); | ||||
|         this._window.add(vbox); | ||||
|  | ||||
|         let scroll = new Gtk.ScrolledWindow({ hscrollbar_policy: Gtk.PolicyType.NEVER, | ||||
|                                               shadow_type: Gtk.ShadowType.IN, | ||||
|                                               halign: Gtk.Align.CENTER, | ||||
|                                               margin: 18 }); | ||||
|         this._window.add(scroll); | ||||
|         let toolbar = new Gtk.Toolbar(); | ||||
|         toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR); | ||||
|         vbox.add(toolbar); | ||||
|         let toolitem; | ||||
|  | ||||
|         this._extensionSelector = new Gtk.ListBox({ selection_mode: Gtk.SelectionMode.NONE }); | ||||
|         this._extensionSelector.set_sort_func(Lang.bind(this, this._sortList)); | ||||
|         this._extensionSelector.set_header_func(Lang.bind(this, this._updateHeader)); | ||||
|         let label = new Gtk.Label({ label: '<b>' + _("Extension") + '</b>', | ||||
|                                     use_markup: true }); | ||||
|         toolitem = new Gtk.ToolItem({ child: label }); | ||||
|         toolbar.add(toolitem); | ||||
|  | ||||
|         scroll.add(this._extensionSelector); | ||||
|         this._extensionSelector = new Gtk.ComboBox({ model: this._model, | ||||
|                                                      margin_left: 8, | ||||
|                                                      hexpand: true }); | ||||
|         this._extensionSelector.get_style_context().add_class(Gtk.STYLE_CLASS_RAISED); | ||||
|  | ||||
|         let renderer = new Gtk.CellRendererText(); | ||||
|         this._extensionSelector.pack_start(renderer, true); | ||||
|         this._extensionSelector.add_attribute(renderer, 'text', 1); | ||||
|         this._extensionSelector.set_cell_data_func(renderer, Lang.bind(this, this._setExtensionInsensitive)); | ||||
|         this._extensionSelector.connect('changed', Lang.bind(this, this._extensionSelected)); | ||||
|  | ||||
|         toolitem = new Gtk.ToolItem({ child: this._extensionSelector }); | ||||
|         toolitem.set_expand(true); | ||||
|         toolbar.add(toolitem); | ||||
|  | ||||
|         this._extensionPrefsBin = new Gtk.Frame(); | ||||
|         vbox.add(this._extensionPrefsBin); | ||||
|  | ||||
|         let label = new Gtk.Label({ | ||||
|             label: _("Select an extension to configure using the combobox above."), | ||||
|             vexpand: true | ||||
|         }); | ||||
|  | ||||
|         this._extensionPrefsBin.add(label); | ||||
|  | ||||
|         this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell'); | ||||
|         this._shellProxy.connectSignal('ExtensionStatusChanged', Lang.bind(this, function(proxy, senderName, [uuid, state, error]) { | ||||
| @@ -177,199 +200,47 @@ const Application = new Lang.Class({ | ||||
|         this._window.show_all(); | ||||
|     }, | ||||
|  | ||||
|     _addCustomStyle: function() { | ||||
|         let provider = new Gtk.CssProvider(); | ||||
|  | ||||
|         try { | ||||
|             provider.load_from_data(customCss, -1); | ||||
|         } catch(e) { | ||||
|             log('Failed to add application style'); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let screen = this._window.window.get_screen(); | ||||
|         let priority = Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION; | ||||
|         Gtk.StyleContext.add_provider_for_screen(screen, provider, priority); | ||||
|     }, | ||||
|  | ||||
|     _sortList: function(row1, row2) { | ||||
|         let name1 = ExtensionUtils.extensions[row1.uuid].metadata.name; | ||||
|         let name2 = ExtensionUtils.extensions[row2.uuid].metadata.name; | ||||
|         return name1.localeCompare(name2); | ||||
|     }, | ||||
|  | ||||
|     _updateHeader: function(row, before) { | ||||
|         if (!before || row.get_header()) | ||||
|             return; | ||||
|  | ||||
|         let sep = new Gtk.Separator({ orientation: Gtk.Orientation.HORIZONTAL }); | ||||
|         row.set_header(sep); | ||||
|     }, | ||||
|  | ||||
|     _scanExtensions: function() { | ||||
|         let finder = new ExtensionUtils.ExtensionFinder(); | ||||
|         finder.connect('extension-found', Lang.bind(this, this._extensionFound)); | ||||
|         finder.scanExtensions(); | ||||
|         this._extensionsLoaded(); | ||||
|     }, | ||||
|  | ||||
|     _extensionFound: function(finder, extension) { | ||||
|         let row = new ExtensionRow(extension.uuid); | ||||
|  | ||||
|         row.prefsButton.visible = this._extensionAvailable(row.uuid); | ||||
|         row.prefsButton.connect('clicked', Lang.bind(this, | ||||
|             function() { | ||||
|                 this._selectExtension(row.uuid); | ||||
|             })); | ||||
|  | ||||
|         row.show_all(); | ||||
|         this._extensionSelector.add(row); | ||||
|     _extensionFound: function(signals, extension) { | ||||
|         let iter = this._model.append(); | ||||
|         this._model.set(iter, [0, 1], [extension.uuid, extension.metadata.name]); | ||||
|         this._extensionIters[extension.uuid] = iter; | ||||
|     }, | ||||
|  | ||||
|     _extensionsLoaded: function() { | ||||
|         if (this._startupUuid && this._extensionAvailable(this._startupUuid)) | ||||
|             this._selectExtension(this._startupUuid); | ||||
|         this._startupUuid = null; | ||||
|         this._skipMainWindow = false; | ||||
|         this._loaded = true; | ||||
|     }, | ||||
|  | ||||
|     _onActivate: function() { | ||||
|         this._window.present(); | ||||
|     }, | ||||
|  | ||||
|     _onStartup: function(app) { | ||||
|         this._buildModel(); | ||||
|         this._buildUI(app); | ||||
|         this._addCustomStyle(); | ||||
|         this._scanExtensions(); | ||||
|     }, | ||||
|  | ||||
|     _onCommandLine: function(app, commandLine) { | ||||
|         app.activate(); | ||||
|         let args = commandLine.get_arguments(); | ||||
|  | ||||
|         if (args.length) { | ||||
|             let uuid = args[0]; | ||||
|  | ||||
|             this._skipMainWindow = true; | ||||
|  | ||||
|             // Strip off "extension:///" prefix which fakes a URI, if it exists | ||||
|             uuid = stripPrefix(uuid, "extension:///"); | ||||
|  | ||||
|             if (this._extensionAvailable(uuid)) | ||||
|                 this._selectExtension(uuid); | ||||
|             else if (!this._loaded) | ||||
|                 this._startupUuid = uuid; | ||||
|             else | ||||
|                 this._skipMainWindow = false; | ||||
|             if (!this._extensionAvailable(uuid)) | ||||
|                 return 1; | ||||
|  | ||||
|             this._selectExtension(uuid); | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ExtensionRow = new Lang.Class({ | ||||
|     Name: 'ExtensionRow', | ||||
|     Extends: Gtk.ListBoxRow, | ||||
|  | ||||
|     _init: function(uuid) { | ||||
|         this.parent(); | ||||
|  | ||||
|         this.uuid = uuid; | ||||
|  | ||||
|         this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' }); | ||||
|         this._settings.connect('changed::enabled-extensions', Lang.bind(this, | ||||
|             function() { | ||||
|                 this._switch.state = this._isEnabled(); | ||||
|             })); | ||||
|         this._settings.connect('changed::disable-extension-version-validation', | ||||
|             Lang.bind(this, function() { | ||||
|                 this._switch.sensitive = this._canEnable(); | ||||
|             })); | ||||
|  | ||||
|         this._buildUI(); | ||||
|     }, | ||||
|  | ||||
|     _buildUI: function() { | ||||
|         let extension = ExtensionUtils.extensions[this.uuid]; | ||||
|  | ||||
|         let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, | ||||
|                                  hexpand: true, margin: 12, spacing: 6 }); | ||||
|         this.add(hbox); | ||||
|  | ||||
|         let vbox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, | ||||
|                                  spacing: 6, hexpand: true }); | ||||
|         hbox.add(vbox); | ||||
|  | ||||
|         let name = GLib.markup_escape_text(extension.metadata.name, -1); | ||||
|         let label = new Gtk.Label({ label: '<b>' + name + '</b>', | ||||
|                                     use_markup: true, | ||||
|                                     halign: Gtk.Align.START }); | ||||
|         vbox.add(label); | ||||
|  | ||||
|         let desc = extension.metadata.description.split('\n')[0]; | ||||
|         label = new Gtk.Label({ label: desc, | ||||
|                                 ellipsize: Pango.EllipsizeMode.END, | ||||
|                                 halign: Gtk.Align.START }); | ||||
|         vbox.add(label); | ||||
|  | ||||
|         let button = new Gtk.Button({ valign: Gtk.Align.CENTER, | ||||
|                                       no_show_all: true }); | ||||
|         button.add(new Gtk.Image({ icon_name: 'emblem-system-symbolic', | ||||
|                                    icon_size: Gtk.IconSize.BUTTON, | ||||
|                                    visible: true })); | ||||
|         button.get_style_context().add_class('prefs-button'); | ||||
|         hbox.add(button); | ||||
|  | ||||
|         this.prefsButton = button; | ||||
|  | ||||
|         this._switch = new Gtk.Switch({ valign: Gtk.Align.CENTER, | ||||
|                                         sensitive: this._canEnable(), | ||||
|                                         state: this._isEnabled() }); | ||||
|         this._switch.connect('notify::active', Lang.bind(this, | ||||
|             function() { | ||||
|                 if (this._switch.active) | ||||
|                     this._enable(); | ||||
|                 else | ||||
|                     this._disable(); | ||||
|             })); | ||||
|         this._switch.connect('state-set', function() { return true; }); | ||||
|         hbox.add(this._switch); | ||||
|     }, | ||||
|  | ||||
|     _canEnable: function() { | ||||
|         let extension = ExtensionUtils.extensions[this.uuid]; | ||||
|         let checkVersion = !this._settings.get_boolean('disable-extension-version-validation'); | ||||
|  | ||||
|         return !(checkVersion && ExtensionUtils.isOutOfDate(extension)); | ||||
|     }, | ||||
|  | ||||
|     _isEnabled: function() { | ||||
|         let extensions = this._settings.get_strv('enabled-extensions'); | ||||
|         return extensions.indexOf(this.uuid) != -1; | ||||
|     }, | ||||
|  | ||||
|     _enable: function() { | ||||
|         let extensions = this._settings.get_strv('enabled-extensions'); | ||||
|         if (extensions.indexOf(this.uuid) != -1) | ||||
|             return; | ||||
|  | ||||
|         extensions.push(this.uuid); | ||||
|         this._settings.set_strv('enabled-extensions', extensions); | ||||
|     }, | ||||
|  | ||||
|     _disable: function() { | ||||
|         let extensions = this._settings.get_strv('enabled-extensions'); | ||||
|         let pos = extensions.indexOf(this.uuid); | ||||
|         if (pos == -1) | ||||
|             return; | ||||
|         do { | ||||
|             extensions.splice(pos, 1); | ||||
|             pos = extensions.indexOf(this.uuid); | ||||
|         } while (pos != -1); | ||||
|         this._settings.set_strv('enabled-extensions', extensions); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| function initEnvironment() { | ||||
|     // Monkey-patch in a "global" object that fakes some Shell utilities | ||||
|     // that ExtensionUtils depends on. | ||||
|   | ||||
| @@ -1,511 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Animation = imports.ui.animation; | ||||
| const Batch = imports.gdm.batch; | ||||
| const GdmUtil = imports.gdm.util; | ||||
| const Params = imports.misc.params; | ||||
| const ShellEntry = imports.ui.shellEntry; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const UserWidget = imports.ui.userWidget; | ||||
|  | ||||
| const DEFAULT_BUTTON_WELL_ICON_SIZE = 24; | ||||
| const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; | ||||
| const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; | ||||
|  | ||||
| const MESSAGE_FADE_OUT_ANIMATION_TIME = 0.5; | ||||
|  | ||||
| const AuthPromptMode = { | ||||
|     UNLOCK_ONLY: 0, | ||||
|     UNLOCK_OR_LOG_IN: 1 | ||||
| }; | ||||
|  | ||||
| const AuthPromptStatus = { | ||||
|     NOT_VERIFYING: 0, | ||||
|     VERIFYING: 1, | ||||
|     VERIFICATION_FAILED: 2, | ||||
|     VERIFICATION_SUCCEEDED: 3 | ||||
| }; | ||||
|  | ||||
| const BeginRequestType = { | ||||
|     PROVIDE_USERNAME: 0, | ||||
|     DONT_PROVIDE_USERNAME: 1 | ||||
| }; | ||||
|  | ||||
| const AuthPrompt = new Lang.Class({ | ||||
|     Name: 'AuthPrompt', | ||||
|  | ||||
|     _init: function(gdmClient, mode) { | ||||
|         this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; | ||||
|  | ||||
|         this._gdmClient = gdmClient; | ||||
|         this._mode = mode; | ||||
|  | ||||
|         let reauthenticationOnly; | ||||
|         if (this._mode == AuthPromptMode.UNLOCK_ONLY) | ||||
|             reauthenticationOnly = true; | ||||
|         else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN) | ||||
|             reauthenticationOnly = false; | ||||
|  | ||||
|         this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly }); | ||||
|  | ||||
|         this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion)); | ||||
|         this._userVerifier.connect('show-message', Lang.bind(this, this._onShowMessage)); | ||||
|         this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed)); | ||||
|         this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); | ||||
|         this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); | ||||
|         this._userVerifier.connect('smartcard-status-changed', Lang.bind(this, this._onSmartcardStatusChanged)); | ||||
|         this._userVerifier.connect('ovirt-user-authenticated', Lang.bind(this, this._onOVirtUserAuthenticated)); | ||||
|         this.smartcardDetected = this._userVerifier.smartcardDetected; | ||||
|  | ||||
|         this.connect('next', Lang.bind(this, function() { | ||||
|                          this.updateSensitivity(false); | ||||
|                          this.startSpinning(); | ||||
|                          if (this._queryingService) { | ||||
|                              this._userVerifier.answerQuery(this._queryingService, this._entry.text); | ||||
|                          } else { | ||||
|                              this._preemptiveAnswer = this._entry.text; | ||||
|                          } | ||||
|                      })); | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', | ||||
|                                         vertical: true }); | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|         this.actor.connect('key-press-event', | ||||
|                            Lang.bind(this, function(actor, event) { | ||||
|                                if (event.get_key_symbol() == Clutter.KEY_Escape) { | ||||
|                                    this.cancel(); | ||||
|                                } | ||||
|                                return Clutter.EVENT_PROPAGATE; | ||||
|                            })); | ||||
|  | ||||
|         this._userWell = new St.Bin({ x_fill: true, | ||||
|                                       x_align: St.Align.START }); | ||||
|         this.actor.add(this._userWell, | ||||
|                        { x_align: St.Align.START, | ||||
|                          x_fill: true, | ||||
|                          y_fill: true, | ||||
|                          expand: true }); | ||||
|         this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); | ||||
|  | ||||
|         this.actor.add(this._label, | ||||
|                        { expand: true, | ||||
|                          x_fill: false, | ||||
|                          y_fill: true, | ||||
|                          x_align: St.Align.START }); | ||||
|         this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', | ||||
|                                      can_focus: true }); | ||||
|         ShellEntry.addContextMenu(this._entry, { isPassword: true }); | ||||
|  | ||||
|         this.actor.add(this._entry, | ||||
|                        { expand: true, | ||||
|                          x_fill: true, | ||||
|                          y_fill: false, | ||||
|                          x_align: St.Align.START }); | ||||
|  | ||||
|         this._entry.grab_key_focus(); | ||||
|  | ||||
|         this._message = new St.Label({ opacity: 0, | ||||
|                                        styleClass: 'login-dialog-message' }); | ||||
|         this._message.clutter_text.line_wrap = true; | ||||
|         this.actor.add(this._message, { x_fill: false, x_align: St.Align.START, y_align: St.Align.START }); | ||||
|  | ||||
|         this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', | ||||
|                                              vertical: false }); | ||||
|         this.actor.add(this._buttonBox, | ||||
|                        { expand:  true, | ||||
|                          x_align: St.Align.MIDDLE, | ||||
|                          y_align: St.Align.END }); | ||||
|  | ||||
|         this._defaultButtonWell = new St.Widget({ layout_manager: new Clutter.BinLayout() }); | ||||
|         this._defaultButtonWellActor = null; | ||||
|  | ||||
|         this._initButtons(); | ||||
|  | ||||
|         let spinnerIcon = global.datadir + '/theme/process-working.svg'; | ||||
|         this._spinner = new Animation.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); | ||||
|         this._spinner.actor.opacity = 0; | ||||
|         this._spinner.actor.show(); | ||||
|         this._defaultButtonWell.add_child(this._spinner.actor); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy: function() { | ||||
|         this._userVerifier.destroy(); | ||||
|         this._userVerifier = null; | ||||
|     }, | ||||
|  | ||||
|     _initButtons: function() { | ||||
|         this.cancelButton = new St.Button({ style_class: 'modal-dialog-button', | ||||
|                                             button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, | ||||
|                                             reactive: true, | ||||
|                                             can_focus: true, | ||||
|                                             label: _("Cancel") }); | ||||
|         this.cancelButton.connect('clicked', | ||||
|                                    Lang.bind(this, function() { | ||||
|                                        this.cancel(); | ||||
|                                    })); | ||||
|         this._buttonBox.add(this.cancelButton, | ||||
|                             { expand: false, | ||||
|                               x_fill: false, | ||||
|                               y_fill: false, | ||||
|                               x_align: St.Align.START, | ||||
|                               y_align: St.Align.END }); | ||||
|  | ||||
|         this._buttonBox.add(this._defaultButtonWell, | ||||
|                             { expand: true, | ||||
|                               x_fill: false, | ||||
|                               y_fill: false, | ||||
|                               x_align: St.Align.END, | ||||
|                               y_align: St.Align.MIDDLE }); | ||||
|         this.nextButton = new St.Button({ style_class: 'modal-dialog-button', | ||||
|                                           button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, | ||||
|                                           reactive: true, | ||||
|                                           can_focus: true, | ||||
|                                           label: _("Next") }); | ||||
|         this.nextButton.connect('clicked', | ||||
|                                  Lang.bind(this, function() { | ||||
|                                      this.emit('next'); | ||||
|                                  })); | ||||
|         this.nextButton.add_style_pseudo_class('default'); | ||||
|         this._buttonBox.add(this.nextButton, | ||||
|                             { expand: false, | ||||
|                               x_fill: false, | ||||
|                               y_fill: false, | ||||
|                               x_align: St.Align.END, | ||||
|                               y_align: St.Align.END }); | ||||
|  | ||||
|         this._updateNextButtonSensitivity(this._entry.text.length > 0); | ||||
|  | ||||
|         this._entry.clutter_text.connect('text-changed', | ||||
|                                          Lang.bind(this, function() { | ||||
|                                              if (!this._userVerifier.hasPendingMessages) | ||||
|                                                  this._fadeOutMessage(); | ||||
|  | ||||
|                                              this._updateNextButtonSensitivity(this._entry.text.length > 0); | ||||
|                                          })); | ||||
|         this._entry.clutter_text.connect('activate', Lang.bind(this, function() { | ||||
|             this.emit('next'); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _onAskQuestion: function(verifier, serviceName, question, passwordChar) { | ||||
|         if (this._preemptiveAnswer) { | ||||
|             if (this._queryingService) | ||||
|                 this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer); | ||||
|             this._preemptiveAnswer = null; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (this._queryingService) | ||||
|             this.clear(); | ||||
|  | ||||
|         this._queryingService = serviceName; | ||||
|         this.setPasswordChar(passwordChar); | ||||
|         this.setQuestion(question); | ||||
|  | ||||
|         if (passwordChar) { | ||||
|             if (this._userVerifier.reauthenticating) | ||||
|                 this.nextButton.label = _("Unlock"); | ||||
|             else | ||||
|                 this.nextButton.label = C_("button", "Sign In"); | ||||
|         } else { | ||||
|             this.nextButton.label = _("Next"); | ||||
|         } | ||||
|  | ||||
|         this.updateSensitivity(true); | ||||
|         this.emit('prompted'); | ||||
|     }, | ||||
|  | ||||
|     _onOVirtUserAuthenticated: function() { | ||||
|         if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) | ||||
|             this.reset(); | ||||
|     }, | ||||
|  | ||||
|     _onSmartcardStatusChanged: function() { | ||||
|         this.smartcardDetected = this._userVerifier.smartcardDetected; | ||||
|  | ||||
|         // Most of the time we want to reset if the user inserts or removes | ||||
|         // a smartcard. Smartcard insertion "preempts" what the user was | ||||
|         // doing, and smartcard removal aborts the preemption. | ||||
|         // The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying | ||||
|         //                        with a smartcard | ||||
|         //                     2) Don't reset if we've already succeeded at verification and | ||||
|         //                        the user is getting logged in. | ||||
|         if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) && | ||||
|             this.verificationStatus == AuthPromptStatus.VERIFYING && | ||||
|             this.smartcardDetected) | ||||
|             return; | ||||
|  | ||||
|         if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) | ||||
|             this.reset(); | ||||
|     }, | ||||
|  | ||||
|     _onShowMessage: function(userVerifier, message, type) { | ||||
|         this.setMessage(message, type); | ||||
|         this.emit('prompted'); | ||||
|     }, | ||||
|  | ||||
|     _onVerificationFailed: function() { | ||||
|         this._queryingService = null; | ||||
|         this.clear(); | ||||
|  | ||||
|         this.updateSensitivity(true); | ||||
|         this.setActorInDefaultButtonWell(null); | ||||
|         this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; | ||||
|     }, | ||||
|  | ||||
|     _onVerificationComplete: function() { | ||||
|         this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; | ||||
| 	this.cancelButton.reactive = false; | ||||
|     }, | ||||
|  | ||||
|     _onReset: function() { | ||||
|         this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; | ||||
|         this.reset(); | ||||
|     }, | ||||
|  | ||||
|     addActorToDefaultButtonWell: function(actor) { | ||||
|         this._defaultButtonWell.add_child(actor); | ||||
|     }, | ||||
|  | ||||
|     setActorInDefaultButtonWell: function(actor, animate) { | ||||
|         if (!this._defaultButtonWellActor && | ||||
|             !actor) | ||||
|             return; | ||||
|  | ||||
|         let oldActor = this._defaultButtonWellActor; | ||||
|  | ||||
|         if (oldActor) | ||||
|             Tweener.removeTweens(oldActor); | ||||
|  | ||||
|         let isSpinner; | ||||
|         if (actor == this._spinner.actor) | ||||
|             isSpinner = true; | ||||
|         else | ||||
|             isSpinner = false; | ||||
|  | ||||
|         if (this._defaultButtonWellActor != actor && oldActor) { | ||||
|             if (!animate) { | ||||
|                 oldActor.opacity = 0; | ||||
|             } else { | ||||
|                 Tweener.addTween(oldActor, | ||||
|                                  { opacity: 0, | ||||
|                                    time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, | ||||
|                                    delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, | ||||
|                                    transition: 'linear', | ||||
|                                    onCompleteScope: this, | ||||
|                                    onComplete: function() { | ||||
|                                       if (isSpinner) { | ||||
|                                           if (this._spinner) | ||||
|                                               this._spinner.stop(); | ||||
|                                       } | ||||
|                                    } | ||||
|                                  }); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (actor) { | ||||
|             if (isSpinner) | ||||
|                 this._spinner.play(); | ||||
|  | ||||
|             if (!animate) | ||||
|                 actor.opacity = 255; | ||||
|             else | ||||
|                 Tweener.addTween(actor, | ||||
|                                  { opacity: 255, | ||||
|                                    time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, | ||||
|                                    delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, | ||||
|                                    transition: 'linear' }); | ||||
|         } | ||||
|  | ||||
|         this._defaultButtonWellActor = actor; | ||||
|     }, | ||||
|  | ||||
|     startSpinning: function() { | ||||
|         this.setActorInDefaultButtonWell(this._spinner.actor, true); | ||||
|     }, | ||||
|  | ||||
|     stopSpinning: function() { | ||||
|         this.setActorInDefaultButtonWell(null, false); | ||||
|     }, | ||||
|  | ||||
|     clear: function() { | ||||
|         this._entry.text = ''; | ||||
|         this.stopSpinning(); | ||||
|     }, | ||||
|  | ||||
|     setPasswordChar: function(passwordChar) { | ||||
|         this._entry.clutter_text.set_password_char(passwordChar); | ||||
|         this._entry.menu.isPassword = passwordChar != ''; | ||||
|     }, | ||||
|  | ||||
|     setQuestion: function(question) { | ||||
|         this._label.set_text(question); | ||||
|  | ||||
|         this._label.show(); | ||||
|         this._entry.show(); | ||||
|  | ||||
|         this._entry.grab_key_focus(); | ||||
|     }, | ||||
|  | ||||
|     getAnswer: function() { | ||||
|         let text; | ||||
|  | ||||
|         if (this._preemptiveAnswer) { | ||||
|             text = this._preemptiveAnswer; | ||||
|             this._preemptiveAnswer = null; | ||||
|         } else { | ||||
|             text = this._entry.get_text(); | ||||
|         } | ||||
|  | ||||
|         return text; | ||||
|     }, | ||||
|  | ||||
|     _fadeOutMessage: function() { | ||||
|         if (this._message.opacity == 0) | ||||
|             return; | ||||
|         Tweener.removeTweens(this._message); | ||||
|         Tweener.addTween(this._message, | ||||
|                          { opacity: 0, | ||||
|                            time: MESSAGE_FADE_OUT_ANIMATION_TIME, | ||||
|                            transition: 'easeOutQuad' | ||||
|                          }); | ||||
|     }, | ||||
|  | ||||
|     setMessage: function(message, type) { | ||||
|         if (type == GdmUtil.MessageType.ERROR) | ||||
|             this._message.add_style_class_name('login-dialog-message-warning'); | ||||
|         else | ||||
|             this._message.remove_style_class_name('login-dialog-message-warning'); | ||||
|  | ||||
|         if (type == GdmUtil.MessageType.HINT) | ||||
|             this._message.add_style_class_name('login-dialog-message-hint'); | ||||
|         else | ||||
|             this._message.remove_style_class_name('login-dialog-message-hint'); | ||||
|  | ||||
|         if (message) { | ||||
|             Tweener.removeTweens(this._message); | ||||
|             this._message.text = message; | ||||
|             this._message.opacity = 255; | ||||
|         } else { | ||||
|             this._message.opacity = 0; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateNextButtonSensitivity: function(sensitive) { | ||||
|         this.nextButton.reactive = sensitive; | ||||
|         this.nextButton.can_focus = sensitive; | ||||
|     }, | ||||
|  | ||||
|     updateSensitivity: function(sensitive) { | ||||
|         this._updateNextButtonSensitivity(sensitive); | ||||
|         this._entry.reactive = sensitive; | ||||
|         this._entry.clutter_text.editable = sensitive; | ||||
|     }, | ||||
|  | ||||
|     hide: function() { | ||||
|         this.setActorInDefaultButtonWell(null, true); | ||||
|         this.actor.hide(); | ||||
|         this._message.opacity = 0; | ||||
|  | ||||
|         this.setUser(null); | ||||
|  | ||||
|         this.updateSensitivity(true); | ||||
|         this._entry.set_text(''); | ||||
|     }, | ||||
|  | ||||
|     setUser: function(user) { | ||||
|         let oldChild = this._userWell.get_child(); | ||||
|         if (oldChild) | ||||
|             oldChild.destroy(); | ||||
|  | ||||
|         if (user) { | ||||
|             let userWidget = new UserWidget.UserWidget(user); | ||||
|             this._userWell.set_child(userWidget.actor); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     reset: function() { | ||||
|         let oldStatus = this.verificationStatus; | ||||
|         this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; | ||||
|         this.cancelButton.reactive = true; | ||||
|  | ||||
|         if (oldStatus == AuthPromptStatus.VERIFYING) | ||||
|             this._userVerifier.cancel(); | ||||
|  | ||||
|         this._queryingService = null; | ||||
|         this.clear(); | ||||
|         this._message.opacity = 0; | ||||
|         this.setUser(null); | ||||
|         this.stopSpinning(); | ||||
|  | ||||
|         if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED) | ||||
|             this.emit('failed'); | ||||
|  | ||||
|         let beginRequestType; | ||||
|  | ||||
|         if (this._mode == AuthPromptMode.UNLOCK_ONLY) { | ||||
|             // The user is constant at the unlock screen, so it will immediately | ||||
|             // respond to the request with the username | ||||
|             beginRequestType = BeginRequestType.PROVIDE_USERNAME; | ||||
|         } else if (this._userVerifier.serviceIsForeground(GdmUtil.OVIRT_SERVICE_NAME) || | ||||
|                    this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME)) { | ||||
|             // We don't need to know the username if the user preempted the login screen | ||||
|             // with a smartcard or with preauthenticated oVirt credentials | ||||
|             beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME; | ||||
|         } else { | ||||
|             // In all other cases, we should get the username up front. | ||||
|             beginRequestType = BeginRequestType.PROVIDE_USERNAME; | ||||
|         } | ||||
|  | ||||
|         this.emit('reset', beginRequestType); | ||||
|     }, | ||||
|  | ||||
|     addCharacter: function(unichar) { | ||||
|         if (!this._entry.visible) | ||||
|             return; | ||||
|  | ||||
|         this._entry.grab_key_focus(); | ||||
|         this._entry.clutter_text.insert_unichar(unichar); | ||||
|     }, | ||||
|  | ||||
|     begin: function(params) { | ||||
|         params = Params.parse(params, { userName: null, | ||||
|                                         hold: null }); | ||||
|  | ||||
|         this.updateSensitivity(false); | ||||
|  | ||||
|         let hold = params.hold; | ||||
|         if (!hold) | ||||
|             hold = new Batch.Hold(); | ||||
|  | ||||
|         this._userVerifier.begin(params.userName, hold); | ||||
|         this.verificationStatus = AuthPromptStatus.VERIFYING; | ||||
|     }, | ||||
|  | ||||
|     finish: function(onComplete) { | ||||
|         if (!this._userVerifier.hasPendingMessages) { | ||||
|             onComplete(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let signalId = this._userVerifier.connect('no-more-messages', | ||||
|                                                   Lang.bind(this, function() { | ||||
|                                                       this._userVerifier.disconnect(signalId); | ||||
|                                                       onComplete(); | ||||
|                                                   })); | ||||
|     }, | ||||
|  | ||||
|     cancel: function() { | ||||
|         if (this.verificationStatus == AuthPromptStatus.NOT_VERIFYING || this.verificationStatus == AuthPromptStatus.VERIFICATION_SUCCEEDED) { | ||||
|             return; | ||||
|         } | ||||
|         this.reset(); | ||||
|         this.emit('cancelled'); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(AuthPrompt.prototype); | ||||
| @@ -13,7 +13,9 @@ | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  */ | ||||
|  | ||||
| const Lang = imports.lang; | ||||
|   | ||||
| @@ -5,13 +5,11 @@ const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const FprintManagerIface = '<node> \ | ||||
| <interface name="net.reactivated.Fprint.Manager"> \ | ||||
| <method name="GetDefaultDevice"> \ | ||||
|     <arg type="o" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const FprintManagerIface = <interface name='net.reactivated.Fprint.Manager'> | ||||
| <method name='GetDefaultDevice'> | ||||
|     <arg type='o' direction='out' /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const FprintManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(FprintManagerIface); | ||||
|  | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,64 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const OVirtCredentialsIface = '<node> \ | ||||
| <interface name="org.ovirt.vdsm.Credentials"> \ | ||||
| <signal name="UserAuthenticated"> \ | ||||
|     <arg type="s" name="token"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const OVirtCredentialsInfo = Gio.DBusInterfaceInfo.new_for_xml(OVirtCredentialsIface); | ||||
|  | ||||
| let _oVirtCredentialsManager = null; | ||||
|  | ||||
| function OVirtCredentials() { | ||||
|     var self = new Gio.DBusProxy({ g_connection: Gio.DBus.system, | ||||
|                                    g_interface_name: OVirtCredentialsInfo.name, | ||||
|                                    g_interface_info: OVirtCredentialsInfo, | ||||
|                                    g_name: 'org.ovirt.vdsm.Credentials', | ||||
|                                    g_object_path: '/org/ovirt/vdsm/Credentials', | ||||
|                                    g_flags: (Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) }); | ||||
|     self.init(null); | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| const OVirtCredentialsManager = new Lang.Class({ | ||||
|     Name: 'OVirtCredentialsManager', | ||||
|     _init: function() { | ||||
|         this._token = null; | ||||
|  | ||||
|         this._credentials = new OVirtCredentials(); | ||||
|         this._credentials.connectSignal('UserAuthenticated', | ||||
|                                         Lang.bind(this, this._onUserAuthenticated)); | ||||
|     }, | ||||
|  | ||||
|     _onUserAuthenticated: function(proxy, sender, [token]) { | ||||
|         this._token = token; | ||||
|         this.emit('user-authenticated', token); | ||||
|     }, | ||||
|  | ||||
|     hasToken: function() { | ||||
|         return this._token != null; | ||||
|     }, | ||||
|  | ||||
|     getToken: function() { | ||||
|         return this._token; | ||||
|     }, | ||||
|  | ||||
|     resetToken: function() { | ||||
|         this._token = null; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(OVirtCredentialsManager.prototype); | ||||
|  | ||||
| function getOVirtCredentialsManager() { | ||||
|     if (!_oVirtCredentialsManager) | ||||
|         _oVirtCredentialsManager = new OVirtCredentialsManager(); | ||||
|  | ||||
|     return _oVirtCredentialsManager; | ||||
| } | ||||
							
								
								
									
										129
									
								
								js/gdm/powerMenu.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								js/gdm/powerMenu.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
| /* | ||||
|  * Copyright 2011 Red Hat, Inc | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation; either version 2, or (at your option) | ||||
|  * any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  */ | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
|  | ||||
| const LoginManager = imports.misc.loginManager; | ||||
|  | ||||
| const GdmUtil = imports.gdm.util; | ||||
| const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const PowerMenuButton = new Lang.Class({ | ||||
|     Name: 'PowerMenuButton', | ||||
|     Extends: PanelMenu.SystemStatusButton, | ||||
|  | ||||
|     _init: function() { | ||||
|         /* Translators: accessible name of the power menu in the login screen */ | ||||
|         this.parent('system-shutdown-symbolic', _("Power")); | ||||
|  | ||||
|         this._loginManager = LoginManager.getLoginManager(); | ||||
|  | ||||
|         this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA }); | ||||
|         this._settings.connect('changed::disable-restart-buttons', | ||||
|                                Lang.bind(this, this._updateVisibility)); | ||||
|  | ||||
|         this._createSubMenu(); | ||||
|  | ||||
|         // ConsoleKit doesn't send notifications when shutdown/reboot | ||||
|         // are disabled, so we update the menu item each time the menu opens | ||||
|         this.menu.connect('open-state-changed', Lang.bind(this, | ||||
|             function(menu, open) { | ||||
|                 if (open) { | ||||
|                     this._updateHaveShutdown(); | ||||
|                     this._updateHaveRestart(); | ||||
|                     this._updateHaveSuspend(); | ||||
|                 } | ||||
|             })); | ||||
|         this._updateHaveShutdown(); | ||||
|         this._updateHaveRestart(); | ||||
|         this._updateHaveSuspend(); | ||||
|     }, | ||||
|  | ||||
|     _updateVisibility: function() { | ||||
|         let shouldBeVisible = (this._haveSuspend || this._haveShutdown || this._haveRestart); | ||||
|         this.actor.visible = shouldBeVisible && !this._settings.get_boolean('disable-restart-buttons'); | ||||
|     }, | ||||
|  | ||||
|     _updateHaveShutdown: function() { | ||||
|         this._loginManager.canPowerOff(Lang.bind(this, function(result) { | ||||
|             this._haveShutdown = result; | ||||
|             this._powerOffItem.actor.visible = this._haveShutdown; | ||||
|             this._updateVisibility(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _updateHaveRestart: function() { | ||||
|         this._loginManager.canReboot(Lang.bind(this, function(result) { | ||||
|             this._haveRestart = result; | ||||
|             this._restartItem.actor.visible = this._haveRestart; | ||||
|             this._updateVisibility(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _updateHaveSuspend: function() { | ||||
|         this._loginManager.canSuspend(Lang.bind(this, function(result) { | ||||
|             this._haveSuspend = result; | ||||
|             this._suspendItem.actor.visible = this._haveSuspend; | ||||
|             this._updateVisibility(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _createSubMenu: function() { | ||||
|         let item; | ||||
|  | ||||
|         item = new PopupMenu.PopupMenuItem(_("Suspend")); | ||||
|         item.connect('activate', Lang.bind(this, this._onActivateSuspend)); | ||||
|         this.menu.addMenuItem(item); | ||||
|         this._suspendItem = item; | ||||
|  | ||||
|         item = new PopupMenu.PopupMenuItem(_("Restart")); | ||||
|         item.connect('activate', Lang.bind(this, this._onActivateRestart)); | ||||
|         this.menu.addMenuItem(item); | ||||
|         this._restartItem = item; | ||||
|  | ||||
|         item = new PopupMenu.PopupMenuItem(_("Power Off")); | ||||
|         item.connect('activate', Lang.bind(this, this._onActivatePowerOff)); | ||||
|         this.menu.addMenuItem(item); | ||||
|         this._powerOffItem = item; | ||||
|     }, | ||||
|  | ||||
|     _onActivateSuspend: function() { | ||||
|         if (!this._haveSuspend) | ||||
|             return; | ||||
|  | ||||
|         this._loginManager.suspend(); | ||||
|     }, | ||||
|  | ||||
|     _onActivateRestart: function() { | ||||
|         if (!this._haveRestart) | ||||
|             return; | ||||
|  | ||||
|         this._loginManager.reboot(); | ||||
|     }, | ||||
|  | ||||
|     _onActivatePowerOff: function() { | ||||
|         if (!this._haveShutdown) | ||||
|             return; | ||||
|  | ||||
|         this._loginManager.powerOff(); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										106
									
								
								js/gdm/realmd.js
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								js/gdm/realmd.js
									
									
									
									
									
								
							| @@ -5,58 +5,52 @@ const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const ProviderIface = '<node> \ | ||||
| <interface name="org.freedesktop.realmd.Provider"> \ | ||||
|     <property name="Name" type="s" access="read"/> \ | ||||
|     <property name="Version" type="s" access="read"/> \ | ||||
|     <property name="Realms" type="ao" access="read"/> \ | ||||
|     <method name="Discover"> \ | ||||
|         <arg name="string" type="s" direction="in"/> \ | ||||
|         <arg name="options" type="a{sv}" direction="in"/> \ | ||||
|         <arg name="relevance" type="i" direction="out"/> \ | ||||
|         <arg name="realm" type="ao" direction="out"/> \ | ||||
|     </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ProviderIface = <interface name='org.freedesktop.realmd.Provider'> | ||||
|     <property name="Name" type="s" access="read"/> | ||||
|     <property name="Version" type="s" access="read"/> | ||||
|     <property name="Realms" type="ao" access="read"/> | ||||
|     <method name="Discover"> | ||||
|         <arg name="string" type="s" direction="in"/> | ||||
|         <arg name="options" type="a{sv}" direction="in"/> | ||||
|         <arg name="relevance" type="i" direction="out"/> | ||||
|         <arg name="realm" type="ao" direction="out"/> | ||||
|     </method> | ||||
| </interface>; | ||||
| const Provider = Gio.DBusProxy.makeProxyWrapper(ProviderIface); | ||||
|  | ||||
| const ServiceIface = '<node> \ | ||||
| <interface name="org.freedesktop.realmd.Service"> \ | ||||
|     <method name="Cancel"> \ | ||||
|         <arg name="operation" type="s" direction="in"/> \ | ||||
|     </method> \ | ||||
|     <method name="Release" /> \ | ||||
|     <method name="SetLocale"> \ | ||||
|         <arg name="locale" type="s" direction="in"/> \ | ||||
|     </method> \ | ||||
|     <signal name="Diagnostics"> \ | ||||
|         <arg name="data" type="s"/> \ | ||||
|         <arg name="operation" type="s"/> \ | ||||
|     </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ServiceIface = <interface name="org.freedesktop.realmd.Service"> | ||||
|     <method name="Cancel"> | ||||
|         <arg name="operation" type="s" direction="in"/> | ||||
|     </method> | ||||
|     <method name="Release" /> | ||||
|     <method name="SetLocale"> | ||||
|         <arg name="locale" type="s" direction="in"/> | ||||
|     </method> | ||||
|     <signal name="Diagnostics"> | ||||
|         <arg name="data" type="s"/> | ||||
|         <arg name="operation" type="s"/> | ||||
|     </signal> | ||||
| </interface>; | ||||
| const Service = Gio.DBusProxy.makeProxyWrapper(ServiceIface); | ||||
|  | ||||
| const RealmIface = '<node> \ | ||||
| <interface name="org.freedesktop.realmd.Realm"> \ | ||||
|     <property name="Name" type="s" access="read"/> \ | ||||
|     <property name="Configured" type="s" access="read"/> \ | ||||
|     <property name="Details" type="a(ss)" access="read"/> \ | ||||
|     <property name="LoginFormats" type="as" access="read"/> \ | ||||
|     <property name="LoginPolicy" type="s" access="read"/> \ | ||||
|     <property name="PermittedLogins" type="as" access="read"/> \ | ||||
|     <property name="SupportedInterfaces" type="as" access="read"/> \ | ||||
|     <method name="ChangeLoginPolicy"> \ | ||||
|         <arg name="login_policy" type="s" direction="in"/> \ | ||||
|         <arg name="permitted_add" type="as" direction="in"/> \ | ||||
|         <arg name="permitted_remove" type="as" direction="in"/> \ | ||||
|         <arg name="options" type="a{sv}" direction="in"/> \ | ||||
|     </method> \ | ||||
|     <method name="Deconfigure"> \ | ||||
|         <arg name="options" type="a{sv}" direction="in"/> \ | ||||
|     </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const RealmIface = <interface name="org.freedesktop.realmd.Realm"> | ||||
|     <property name="Name" type="s" access="read"/> | ||||
|     <property name="Configured" type="s" access="read"/> | ||||
|     <property name="Details" type="a(ss)" access="read"/> | ||||
|     <property name="LoginFormats" type="as" access="read"/> | ||||
|     <property name="LoginPolicy" type="s" access="read"/> | ||||
|     <property name="PermittedLogins" type="as" access="read"/> | ||||
|     <property name="SupportedInterfaces" type="as" access="read"/> | ||||
|     <method name="ChangeLoginPolicy"> | ||||
|         <arg name="login_policy" type="s" direction="in"/> | ||||
|         <arg name="permitted_add" type="as" direction="in"/> | ||||
|         <arg name="permitted_remove" type="as" direction="in"/> | ||||
|         <arg name="options" type="a{sv}" direction="in"/> | ||||
|     </method> | ||||
|     <method name="Deconfigure"> | ||||
|         <arg name="options" type="a{sv}" direction="in"/> | ||||
|     </method> | ||||
| </interface>; | ||||
| const Realm = Gio.DBusProxy.makeProxyWrapper(RealmIface); | ||||
|  | ||||
| const Manager = new Lang.Class({ | ||||
| @@ -69,7 +63,7 @@ const Manager = new Lang.Class({ | ||||
|                                            Lang.bind(this, this._reloadRealms)) | ||||
|         this._realms = {}; | ||||
|  | ||||
|         this._signalId = this._aggregateProvider.connect('g-properties-changed', | ||||
|         this._aggregateProvider.connect('g-properties-changed', | ||||
|                                         Lang.bind(this, function(proxy, properties) { | ||||
|                                             if ('Realms' in properties.deep_unpack()) | ||||
|                                                 this._reloadRealms(); | ||||
| @@ -112,7 +106,7 @@ const Manager = new Lang.Class({ | ||||
|         realm.connect('g-properties-changed', | ||||
|                       Lang.bind(this, function(proxy, properties) { | ||||
|                                 if ('Configured' in properties.deep_unpack()) | ||||
|                                     this._reloadRealm(realm); | ||||
|                                     this._reloadRealm(); | ||||
|                                 })); | ||||
|     }, | ||||
|  | ||||
| @@ -140,18 +134,6 @@ const Manager = new Lang.Class({ | ||||
|         this._updateLoginFormat(); | ||||
|  | ||||
|         return this._loginFormat; | ||||
|     }, | ||||
|  | ||||
|     release: function() { | ||||
|         Service(Gio.DBus.system, | ||||
|                 'org.freedesktop.realmd', | ||||
|                 '/org/freedesktop/realmd', | ||||
|                 function(service) { | ||||
|                     service.ReleaseRemote(); | ||||
|                 }); | ||||
|         this._aggregateProvider.disconnect(this._signalId); | ||||
|         this._realms = { }; | ||||
|         this._updateLoginFormat(); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(Manager.prototype) | ||||
|   | ||||
							
								
								
									
										435
									
								
								js/gdm/util.js
									
									
									
									
									
								
							
							
						
						
									
										435
									
								
								js/gdm/util.js
									
									
									
									
									
								
							| @@ -1,33 +1,23 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Batch = imports.gdm.batch; | ||||
| const Fprint = imports.gdm.fingerprint; | ||||
| const OVirt = imports.gdm.oVirt; | ||||
| const Realmd = imports.gdm.realmd; | ||||
| const Main = imports.ui.main; | ||||
| const Params = imports.misc.params; | ||||
| const ShellEntry = imports.ui.shellEntry; | ||||
| const SmartcardManager = imports.misc.smartcardManager; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const PASSWORD_SERVICE_NAME = 'gdm-password'; | ||||
| const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; | ||||
| const SMARTCARD_SERVICE_NAME = 'gdm-smartcard'; | ||||
| const OVIRT_SERVICE_NAME = 'gdm-ovirtcred'; | ||||
| const FADE_ANIMATION_TIME = 0.16; | ||||
| const CLONE_FADE_ANIMATION_TIME = 0.25; | ||||
|  | ||||
| const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; | ||||
| const PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication'; | ||||
| const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; | ||||
| const SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication'; | ||||
| const BANNER_MESSAGE_KEY = 'banner-message-enable'; | ||||
| const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; | ||||
| const ALLOWED_FAILURES_KEY = 'allowed-failures'; | ||||
| @@ -35,16 +25,6 @@ const ALLOWED_FAILURES_KEY = 'allowed-failures'; | ||||
| const LOGO_KEY = 'logo'; | ||||
| const DISABLE_USER_LIST_KEY = 'disable-user-list'; | ||||
|  | ||||
| // Give user 16ms to read each character of a PAM message | ||||
| const USER_READ_TIME = 16 | ||||
|  | ||||
| const MessageType = { | ||||
|     NONE: 0, | ||||
|     ERROR: 1, | ||||
|     INFO: 2, | ||||
|     HINT: 3 | ||||
| }; | ||||
|  | ||||
| function fadeInActor(actor) { | ||||
|     if (actor.opacity == 255 && actor.visible) | ||||
|         return null; | ||||
| @@ -91,34 +71,6 @@ function fadeOutActor(actor) { | ||||
|     return hold; | ||||
| } | ||||
|  | ||||
| function cloneAndFadeOutActor(actor) { | ||||
|     // Immediately hide actor so its sibling can have its space | ||||
|     // and position, but leave a non-reactive clone on-screen, | ||||
|     // so from the user's point of view it smoothly fades away | ||||
|     // and reveals its sibling. | ||||
|     actor.hide(); | ||||
|  | ||||
|     let clone = new Clutter.Clone({ source: actor, | ||||
|                                     reactive: false }); | ||||
|  | ||||
|     Main.uiGroup.add_child(clone); | ||||
|  | ||||
|     let [x, y] = actor.get_transformed_position(); | ||||
|     clone.set_position(x, y); | ||||
|  | ||||
|     let hold = new Batch.Hold(); | ||||
|     Tweener.addTween(clone, | ||||
|                      { opacity: 0, | ||||
|                        time: CLONE_FADE_ANIMATION_TIME, | ||||
|                        transition: 'easeOutQuad', | ||||
|                        onComplete: function() { | ||||
|                            clone.destroy(); | ||||
|                            hold.release(); | ||||
|                        } | ||||
|                      }); | ||||
|     return hold; | ||||
| } | ||||
|  | ||||
| const ShellUserVerifier = new Lang.Class({ | ||||
|     Name: 'ShellUserVerifier', | ||||
|  | ||||
| @@ -128,46 +80,18 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|  | ||||
|         this._client = client; | ||||
|  | ||||
|         this._settings = new Gio.Settings({ schema_id: LOGIN_SCREEN_SCHEMA }); | ||||
|         this._settings.connect('changed', | ||||
|                                Lang.bind(this, this._updateDefaultService)); | ||||
|         this._updateDefaultService(); | ||||
|         this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA }); | ||||
|  | ||||
|         this._fprintManager = new Fprint.FprintManager(); | ||||
|         this._smartcardManager = SmartcardManager.getSmartcardManager(); | ||||
|  | ||||
|         // We check for smartcards right away, since an inserted smartcard | ||||
|         // at startup should result in immediately initiating authentication. | ||||
|         // This is different than fingeprint readers, where we only check them | ||||
|         // after a user has been picked. | ||||
|         this._checkForSmartcard(); | ||||
|  | ||||
|         this._smartcardInsertedId = this._smartcardManager.connect('smartcard-inserted', | ||||
|                                                                    Lang.bind(this, this._checkForSmartcard)); | ||||
|         this._smartcardRemovedId = this._smartcardManager.connect('smartcard-removed', | ||||
|                                                                   Lang.bind(this, this._checkForSmartcard)); | ||||
|  | ||||
|         this._messageQueue = []; | ||||
|         this._messageQueueTimeoutId = 0; | ||||
|         this.hasPendingMessages = false; | ||||
|         this.reauthenticating = false; | ||||
|         this._realmManager = new Realmd.Manager(); | ||||
|  | ||||
|         this._failCounter = 0; | ||||
|  | ||||
|         this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager(); | ||||
|  | ||||
|         if (this._oVirtCredentialsManager.hasToken()) | ||||
|             this._oVirtUserAuthenticated(this._oVirtCredentialsManager.getToken()); | ||||
|  | ||||
|         this._oVirtUserAuthenticatedId = this._oVirtCredentialsManager.connect('user-authenticated', | ||||
|                                                                                Lang.bind(this, this._oVirtUserAuthenticated)); | ||||
|     }, | ||||
|  | ||||
|     begin: function(userName, hold) { | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|         this._hold = hold; | ||||
|         this._userName = userName; | ||||
|         this.reauthenticating = false; | ||||
|  | ||||
|         this._checkForFingerprintReader(); | ||||
|  | ||||
| @@ -185,17 +109,8 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|         if (this._cancellable) | ||||
|             this._cancellable.cancel(); | ||||
|  | ||||
|         if (this._userVerifier) { | ||||
|         if (this._userVerifier) | ||||
|             this._userVerifier.call_cancel_sync(null); | ||||
|             this.clear(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _clearUserVerifier: function() { | ||||
|         if (this._userVerifier) { | ||||
|             this._userVerifier.run_dispose(); | ||||
|             this._userVerifier = null; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     clear: function() { | ||||
| @@ -204,147 +119,42 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|             this._cancellable = null; | ||||
|         } | ||||
|  | ||||
|         this._clearUserVerifier(); | ||||
|         this._clearMessageQueue(); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this.clear(); | ||||
|  | ||||
|         this._settings.run_dispose(); | ||||
|         this._settings = null; | ||||
|  | ||||
|         this._smartcardManager.disconnect(this._smartcardInsertedId); | ||||
|         this._smartcardManager.disconnect(this._smartcardRemovedId); | ||||
|         this._smartcardManager = null; | ||||
|  | ||||
|         this._oVirtCredentialsManager.disconnect(this._oVirtUserAuthenticatedId); | ||||
|         this._oVirtCredentialsManager = null; | ||||
|         if (this._userVerifier) { | ||||
|             this._userVerifier.run_dispose(); | ||||
|             this._userVerifier = null; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     answerQuery: function(serviceName, answer) { | ||||
|         if (!this.hasPendingMessages) { | ||||
|             this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); | ||||
|         } else { | ||||
|             let signalId = this.connect('no-more-messages', | ||||
|                                         Lang.bind(this, function() { | ||||
|                                             this.disconnect(signalId); | ||||
|                                             this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); | ||||
|                                         })); | ||||
|         } | ||||
|     }, | ||||
|         // Clear any previous message | ||||
|         this.emit('show-message', null, null); | ||||
|  | ||||
|     _getIntervalForMessage: function(message) { | ||||
|         // We probably could be smarter here | ||||
|         return message.length * USER_READ_TIME; | ||||
|     }, | ||||
|  | ||||
|     finishMessageQueue: function() { | ||||
|         if (!this.hasPendingMessages) | ||||
|             return; | ||||
|  | ||||
|         this._messageQueue = []; | ||||
|  | ||||
|         this.hasPendingMessages = false; | ||||
|         this.emit('no-more-messages'); | ||||
|     }, | ||||
|  | ||||
|     _queueMessageTimeout: function() { | ||||
|         if (this._messageQueue.length == 0) { | ||||
|             this.finishMessageQueue(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (this._messageQueueTimeoutId != 0) | ||||
|             return; | ||||
|  | ||||
|         let message = this._messageQueue.shift(); | ||||
|  | ||||
|         this.emit('show-message', message.text, message.type); | ||||
|  | ||||
|         this._messageQueueTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, | ||||
|                                                        message.interval, | ||||
|                                                        Lang.bind(this, function() { | ||||
|                                                            this._messageQueueTimeoutId = 0; | ||||
|                                                            this._queueMessageTimeout(); | ||||
|                                                            return GLib.SOURCE_REMOVE; | ||||
|                                                        })); | ||||
|         GLib.Source.set_name_by_id(this._messageQueueTimeoutId, '[gnome-shell] this._queueMessageTimeout'); | ||||
|     }, | ||||
|  | ||||
|     _queueMessage: function(message, messageType) { | ||||
|         let interval = this._getIntervalForMessage(message); | ||||
|  | ||||
|         this.hasPendingMessages = true; | ||||
|         this._messageQueue.push({ text: message, type: messageType, interval: interval }); | ||||
|         this._queueMessageTimeout(); | ||||
|     }, | ||||
|  | ||||
|     _clearMessageQueue: function() { | ||||
|         this.finishMessageQueue(); | ||||
|  | ||||
|         if (this._messageQueueTimeoutId != 0) { | ||||
|             GLib.source_remove(this._messageQueueTimeoutId); | ||||
|             this._messageQueueTimeoutId = 0; | ||||
|         } | ||||
|         this.emit('show-message', null, MessageType.NONE); | ||||
|         this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); | ||||
|     }, | ||||
|  | ||||
|     _checkForFingerprintReader: function() { | ||||
|         this._haveFingerprintReader = false; | ||||
|  | ||||
|         if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) { | ||||
|             this._updateDefaultService(); | ||||
|         if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this, | ||||
|             function(device, error) { | ||||
|                 if (!error && device) { | ||||
|                 if (!error && device) | ||||
|                     this._haveFingerprintReader = true; | ||||
|                     this._updateDefaultService(); | ||||
|                 } | ||||
|             })); | ||||
|     }, | ||||
|  | ||||
|     _oVirtUserAuthenticated: function(token) { | ||||
|         this._preemptingService = OVIRT_SERVICE_NAME; | ||||
|         this.emit('ovirt-user-authenticated'); | ||||
|     }, | ||||
|  | ||||
|     _checkForSmartcard: function() { | ||||
|         let smartcardDetected; | ||||
|  | ||||
|         if (!this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) | ||||
|             smartcardDetected = false; | ||||
|         else if (this._reauthOnly) | ||||
|             smartcardDetected = this._smartcardManager.hasInsertedLoginToken(); | ||||
|         else | ||||
|             smartcardDetected = this._smartcardManager.hasInsertedTokens(); | ||||
|  | ||||
|         if (smartcardDetected != this.smartcardDetected) { | ||||
|             this.smartcardDetected = smartcardDetected; | ||||
|  | ||||
|             if (this.smartcardDetected) | ||||
|                 this._preemptingService = SMARTCARD_SERVICE_NAME; | ||||
|             else if (this._preemptingService == SMARTCARD_SERVICE_NAME) | ||||
|                 this._preemptingService = null; | ||||
|  | ||||
|             this.emit('smartcard-status-changed'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _reportInitError: function(where, error) { | ||||
|         logError(error, where); | ||||
|         this._hold.release(); | ||||
|  | ||||
|         this._queueMessage(_("Authentication error"), MessageType.ERROR); | ||||
|         this.emit('show-message', _("Authentication error"), 'login-dialog-message-warning'); | ||||
|         this._verificationFailed(false); | ||||
|     }, | ||||
|  | ||||
|     _reauthenticationChannelOpened: function(client, result) { | ||||
|         try { | ||||
|             this._clearUserVerifier(); | ||||
|             this._userVerifier = client.open_reauthentication_channel_finish(result); | ||||
|         } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|             return; | ||||
| @@ -360,7 +170,6 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.reauthenticating = true; | ||||
|         this._connectSignals(); | ||||
|         this._beginVerification(); | ||||
|         this._hold.release(); | ||||
| @@ -368,7 +177,6 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|  | ||||
|     _userVerifierGot: function(client, result) { | ||||
|         try { | ||||
|             this._clearUserVerifier(); | ||||
|             this._userVerifier = client.get_user_verifier_finish(result); | ||||
|         } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|             return; | ||||
| @@ -392,119 +200,126 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|         this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); | ||||
|     }, | ||||
|  | ||||
|     _getForegroundService: function() { | ||||
|         if (this._preemptingService) | ||||
|             return this._preemptingService; | ||||
|  | ||||
|         return this._defaultService; | ||||
|     }, | ||||
|  | ||||
|     serviceIsForeground: function(serviceName) { | ||||
|         return serviceName == this._getForegroundService(); | ||||
|     }, | ||||
|  | ||||
|     serviceIsDefault: function(serviceName) { | ||||
|         return serviceName == this._defaultService; | ||||
|     }, | ||||
|  | ||||
|     _updateDefaultService: function() { | ||||
|         if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) | ||||
|             this._defaultService = PASSWORD_SERVICE_NAME; | ||||
|         else if (this.smartcardDetected) | ||||
|             this._defaultService = SMARTCARD_SERVICE_NAME; | ||||
|         else if (this._haveFingerprintReader) | ||||
|             this._defaultService = FINGERPRINT_SERVICE_NAME; | ||||
|     }, | ||||
|  | ||||
|     _startService: function(serviceName) { | ||||
|     _beginVerification: function() { | ||||
|         this._hold.acquire(); | ||||
|  | ||||
|         if (this._userName) { | ||||
|            this._userVerifier.call_begin_verification_for_user(serviceName, | ||||
|                                                                this._userName, | ||||
|                                                                this._cancellable, | ||||
|                                                                Lang.bind(this, function(obj, result) { | ||||
|                try { | ||||
|                    obj.call_begin_verification_for_user_finish(result); | ||||
|                } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|                    return; | ||||
|                } catch(e) { | ||||
|                    this._reportInitError('Failed to start verification for user', e); | ||||
|                    return; | ||||
|                } | ||||
|             this._userVerifier.call_begin_verification_for_user(PASSWORD_SERVICE_NAME, | ||||
|                                                                 this._userName, | ||||
|                                                                 this._cancellable, | ||||
|                                                                 Lang.bind(this, function(obj, result) { | ||||
|                 try { | ||||
|                     obj.call_begin_verification_for_user_finish(result); | ||||
|                 } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|                     return; | ||||
|                 } catch(e) { | ||||
|                     this._reportInitError('Failed to start verification for user', e); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                this._hold.release(); | ||||
|            })); | ||||
|                 this._hold.release(); | ||||
|             })); | ||||
|  | ||||
|             if (this._haveFingerprintReader) { | ||||
|                 this._hold.acquire(); | ||||
|  | ||||
|                 this._userVerifier.call_begin_verification_for_user(FINGERPRINT_SERVICE_NAME, | ||||
|                                                                     this._userName, | ||||
|                                                                     this._cancellable, | ||||
|                                                                     Lang.bind(this, function(obj, result) { | ||||
|                     try { | ||||
|                         obj.call_begin_verification_for_user_finish(result); | ||||
|                     } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|                         return; | ||||
|                     } catch(e) { | ||||
|                         this._reportInitError('Failed to start fingerprint verification for user', e); | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
|                     this._hold.release(); | ||||
|                 })); | ||||
|             } | ||||
|         } else { | ||||
|            this._userVerifier.call_begin_verification(serviceName, | ||||
|                                                       this._cancellable, | ||||
|                                                       Lang.bind(this, function(obj, result) { | ||||
|                try { | ||||
|                    obj.call_begin_verification_finish(result); | ||||
|                } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|                    return; | ||||
|                } catch(e) { | ||||
|                    this._reportInitError('Failed to start verification', e); | ||||
|                    return; | ||||
|                } | ||||
|             this._userVerifier.call_begin_verification(PASSWORD_SERVICE_NAME, | ||||
|                                                        this._cancellable, | ||||
|                                                        Lang.bind(this, function(obj, result) { | ||||
|                 try { | ||||
|                     obj.call_begin_verification_finish(result); | ||||
|                 } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { | ||||
|                     return; | ||||
|                 } catch(e) { | ||||
|                     this._reportInitError('Failed to start verification', e); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                this._hold.release(); | ||||
|            })); | ||||
|                 this._hold.release(); | ||||
|             })); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _beginVerification: function() { | ||||
|         this._startService(this._getForegroundService()); | ||||
|  | ||||
|         if (this._userName && this._haveFingerprintReader && !this.serviceIsForeground(FINGERPRINT_SERVICE_NAME)) | ||||
|             this._startService(FINGERPRINT_SERVICE_NAME); | ||||
|     }, | ||||
|  | ||||
|     _onInfo: function(client, serviceName, info) { | ||||
|         if (this.serviceIsForeground(serviceName)) { | ||||
|             this._queueMessage(info, MessageType.INFO); | ||||
|         } else if (serviceName == FINGERPRINT_SERVICE_NAME && | ||||
|         // We don't display fingerprint messages, because they | ||||
|         // have words like UPEK in them. Instead we use the messages | ||||
|         // as a cue to display our own message. | ||||
|         if (serviceName == FINGERPRINT_SERVICE_NAME && | ||||
|             this._haveFingerprintReader) { | ||||
|             // We don't show fingerprint messages directly since it's | ||||
|             // not the main auth service. Instead we use the messages | ||||
|             // as a cue to display our own message. | ||||
|  | ||||
|             // Translators: this message is shown below the password entry field | ||||
|             // to indicate the user can swipe their finger instead | ||||
|             this._queueMessage(_("(or swipe finger)"), MessageType.HINT); | ||||
|             this.emit('show-login-hint', _("(or swipe finger)")); | ||||
|         } else if (serviceName == PASSWORD_SERVICE_NAME) { | ||||
|             this.emit('show-message', info, 'login-dialog-message-info'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onProblem: function(client, serviceName, problem) { | ||||
|         if (!this.serviceIsForeground(serviceName)) | ||||
|         // we don't want to show auth failed messages to | ||||
|         // users who haven't enrolled their fingerprint. | ||||
|         if (serviceName != PASSWORD_SERVICE_NAME) | ||||
|             return; | ||||
|         this.emit('show-message', problem, 'login-dialog-message-warning'); | ||||
|     }, | ||||
|  | ||||
|         this._queueMessage(problem, MessageType.ERROR); | ||||
|     _showRealmLoginHint: function() { | ||||
|         if (this._realmManager.loginFormat) { | ||||
|             let hint = this._realmManager.loginFormat; | ||||
|  | ||||
|             hint = hint.replace(/%U/g, 'user'); | ||||
|             hint = hint.replace(/%D/g, 'DOMAIN'); | ||||
|             hint = hint.replace(/%[^UD]/g, ''); | ||||
|  | ||||
|             // Translators: this message is shown below the username entry field | ||||
|             // to clue the user in on how to login to the local network realm | ||||
|             this.emit('show-login-hint', | ||||
|                       _("(e.g., user or %s)").format(hint)); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onInfoQuery: function(client, serviceName, question) { | ||||
|         if (!this.serviceIsForeground(serviceName)) | ||||
|         // We only expect questions to come from the main auth service | ||||
|         if (serviceName != PASSWORD_SERVICE_NAME) | ||||
|             return; | ||||
|  | ||||
|         this._showRealmLoginHint(); | ||||
|         this._realmLoginHintSignalId = this._realmManager.connect('login-format-changed', | ||||
|                                                                   Lang.bind(this, this._showRealmLoginHint)); | ||||
|  | ||||
|         this.emit('ask-question', serviceName, question, ''); | ||||
|     }, | ||||
|  | ||||
|     _onSecretInfoQuery: function(client, serviceName, secretQuestion) { | ||||
|         if (!this.serviceIsForeground(serviceName)) | ||||
|         // We only expect secret requests to come from the main auth service | ||||
|         if (serviceName != PASSWORD_SERVICE_NAME) | ||||
|             return; | ||||
|  | ||||
|         if (serviceName == OVIRT_SERVICE_NAME) { | ||||
|             // The only question asked by this service is "Token?" | ||||
|             this.answerQuery(serviceName, this._oVirtCredentialsManager.getToken()); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.emit('ask-question', serviceName, secretQuestion, '\u25cf'); | ||||
|     }, | ||||
|  | ||||
|     _onReset: function() { | ||||
|         this.clear(); | ||||
|  | ||||
|         // Clear previous attempts to authenticate | ||||
|         this._failCounter = 0; | ||||
|         this._updateDefaultService(); | ||||
|  | ||||
|         this.emit('reset'); | ||||
|     }, | ||||
| @@ -513,15 +328,6 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|         this.emit('verification-complete'); | ||||
|     }, | ||||
|  | ||||
|     _cancelAndReset: function() { | ||||
|         this.cancel(); | ||||
|         this._onReset(); | ||||
|     }, | ||||
|  | ||||
|     _retry: function() { | ||||
|         this.begin(this._userName, new Batch.Hold()); | ||||
|     }, | ||||
|  | ||||
|     _verificationFailed: function(retry) { | ||||
|         // For Not Listed / enterprise logins, immediately reset | ||||
|         // the dialog | ||||
| @@ -533,47 +339,34 @@ const ShellUserVerifier = new Lang.Class({ | ||||
|             this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY); | ||||
|  | ||||
|         if (canRetry) { | ||||
|             if (!this.hasPendingMessages) { | ||||
|                 this._retry(); | ||||
|             } else { | ||||
|                 let signalId = this.connect('no-more-messages', | ||||
|                                             Lang.bind(this, function() { | ||||
|                                                 this.disconnect(signalId); | ||||
|                                                 this._retry(); | ||||
|                                             })); | ||||
|             } | ||||
|             this.clear(); | ||||
|             this.begin(this._userName, new Batch.Hold()); | ||||
|         } else { | ||||
|             if (!this.hasPendingMessages) { | ||||
|                 this._cancelAndReset(); | ||||
|             } else { | ||||
|                 let signalId = this.connect('no-more-messages', | ||||
|                                             Lang.bind(this, function() { | ||||
|                                                 this.disconnect(signalId); | ||||
|                                                 this._cancelAndReset(); | ||||
|                                             })); | ||||
|             } | ||||
|             // Allow some time to see the message, then reset everything | ||||
|             Mainloop.timeout_add(3000, Lang.bind(this, function() { | ||||
|                 this.cancel(); | ||||
|  | ||||
|                 this._onReset(); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         this.emit('verification-failed'); | ||||
|     }, | ||||
|  | ||||
|     _onConversationStopped: function(client, serviceName) { | ||||
|         // If the login failed with the preauthenticated oVirt credentials | ||||
|         // then discard the credentials and revert to default authentication | ||||
|         // mechanism. | ||||
|         if (this.serviceIsForeground(OVIRT_SERVICE_NAME)) { | ||||
|             this._oVirtCredentialsManager.resetToken(); | ||||
|             this._preemptingService = null; | ||||
|             this._verificationFailed(false); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // if the password service fails, then cancel everything. | ||||
|         // But if, e.g., fingerprint fails, still give | ||||
|         // password authentication a chance to succeed | ||||
|         if (this.serviceIsForeground(serviceName)) { | ||||
|         if (serviceName == PASSWORD_SERVICE_NAME) { | ||||
|             this._verificationFailed(true); | ||||
|         } | ||||
|  | ||||
|         this.emit('hide-login-hint'); | ||||
|  | ||||
|         if (this._realmLoginHintSignalId) { | ||||
|             this._realmManager.disconnect(this._realmLoginHintSignalId); | ||||
|             this._realmLoginHintSignalId = 0; | ||||
|         } | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(ShellUserVerifier.prototype); | ||||
|   | ||||
| @@ -1,121 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <gresources> | ||||
|   <gresource prefix="/org/gnome/shell"> | ||||
|     <file>gdm/authPrompt.js</file> | ||||
|     <file>gdm/batch.js</file> | ||||
|     <file>gdm/fingerprint.js</file> | ||||
|     <file>gdm/loginDialog.js</file> | ||||
|     <file>gdm/oVirt.js</file> | ||||
|     <file>gdm/realmd.js</file> | ||||
|     <file>gdm/util.js</file> | ||||
|  | ||||
|     <file>extensionPrefs/main.js</file> | ||||
|  | ||||
|     <file>misc/config.js</file> | ||||
|     <file>misc/extensionUtils.js</file> | ||||
|     <file>misc/fileUtils.js</file> | ||||
|     <file>misc/gnomeSession.js</file> | ||||
|     <file>misc/history.js</file> | ||||
|     <file>misc/ibusManager.js</file> | ||||
|     <file>misc/jsParse.js</file> | ||||
|     <file>misc/keyboardManager.js</file> | ||||
|     <file>misc/loginManager.js</file> | ||||
|     <file>misc/modemManager.js</file> | ||||
|     <file>misc/objectManager.js</file> | ||||
|     <file>misc/params.js</file> | ||||
|     <file>misc/smartcardManager.js</file> | ||||
|     <file>misc/util.js</file> | ||||
|  | ||||
|     <file>perf/core.js</file> | ||||
|     <file>perf/hwtest.js</file> | ||||
|  | ||||
|     <file>portalHelper/main.js</file> | ||||
|  | ||||
|     <file>ui/altTab.js</file> | ||||
|     <file>ui/animation.js</file> | ||||
|     <file>ui/appDisplay.js</file> | ||||
|     <file>ui/appFavorites.js</file> | ||||
|     <file>ui/backgroundMenu.js</file> | ||||
|     <file>ui/background.js</file> | ||||
|     <file>ui/boxpointer.js</file> | ||||
|     <file>ui/calendar.js</file> | ||||
|     <file>ui/checkBox.js</file> | ||||
|     <file>ui/ctrlAltTab.js</file> | ||||
|     <file>ui/dash.js</file> | ||||
|     <file>ui/dateMenu.js</file> | ||||
|     <file>ui/dnd.js</file> | ||||
|     <file>ui/edgeDragAction.js</file> | ||||
|     <file>ui/endSessionDialog.js</file> | ||||
|     <file>ui/environment.js</file> | ||||
|     <file>ui/extensionDownloader.js</file> | ||||
|     <file>ui/extensionSystem.js</file> | ||||
|     <file>ui/focusCaretTracker.js</file> | ||||
|     <file>ui/grabHelper.js</file> | ||||
|     <file>ui/ibusCandidatePopup.js</file> | ||||
|     <file>ui/iconGrid.js</file> | ||||
|     <file>ui/keyboard.js</file> | ||||
|     <file>ui/layout.js</file> | ||||
|     <file>ui/lightbox.js</file> | ||||
|     <file>ui/lookingGlass.js</file> | ||||
|     <file>ui/magnifier.js</file> | ||||
|     <file>ui/magnifierDBus.js</file> | ||||
|     <file>ui/main.js</file> | ||||
|     <file>ui/messageTray.js</file> | ||||
|     <file>ui/modalDialog.js</file> | ||||
|     <file>ui/notificationDaemon.js</file> | ||||
|     <file>ui/osdWindow.js</file> | ||||
|     <file>ui/overview.js</file> | ||||
|     <file>ui/overviewControls.js</file> | ||||
|     <file>ui/panel.js</file> | ||||
|     <file>ui/panelMenu.js</file> | ||||
|     <file>ui/pointerWatcher.js</file> | ||||
|     <file>ui/popupMenu.js</file> | ||||
|     <file>ui/remoteMenu.js</file> | ||||
|     <file>ui/remoteSearch.js</file> | ||||
|     <file>ui/runDialog.js</file> | ||||
|     <file>ui/screenShield.js</file> | ||||
|     <file>ui/screencast.js</file> | ||||
|     <file>ui/screenshot.js</file> | ||||
|     <file>ui/scripting.js</file> | ||||
|     <file>ui/search.js</file> | ||||
|     <file>ui/separator.js</file> | ||||
|     <file>ui/sessionMode.js</file> | ||||
|     <file>ui/shellDBus.js</file> | ||||
|     <file>ui/shellEntry.js</file> | ||||
|     <file>ui/shellMountOperation.js</file> | ||||
|     <file>ui/slider.js</file> | ||||
|     <file>ui/switcherPopup.js</file> | ||||
|     <file>ui/tweener.js</file> | ||||
|     <file>ui/unlockDialog.js</file> | ||||
|     <file>ui/userWidget.js</file> | ||||
|     <file>ui/viewSelector.js</file> | ||||
|     <file>ui/windowAttentionHandler.js</file> | ||||
|     <file>ui/windowMenu.js</file> | ||||
|     <file>ui/windowManager.js</file> | ||||
|     <file>ui/workspace.js</file> | ||||
|     <file>ui/workspaceSwitcherPopup.js</file> | ||||
|     <file>ui/workspaceThumbnail.js</file> | ||||
|     <file>ui/workspacesView.js</file> | ||||
|     <file>ui/xdndHandler.js</file> | ||||
|  | ||||
|     <file>ui/components/__init__.js</file> | ||||
|     <file>ui/components/autorunManager.js</file> | ||||
|     <file>ui/components/automountManager.js</file> | ||||
|     <file>ui/components/networkAgent.js</file> | ||||
|     <file>ui/components/polkitAgent.js</file> | ||||
|     <file>ui/components/telepathyClient.js</file> | ||||
|     <file>ui/components/keyring.js</file> | ||||
|  | ||||
|     <file>ui/status/accessibility.js</file> | ||||
|     <file>ui/status/brightness.js</file> | ||||
|     <file>ui/status/location.js</file> | ||||
|     <file>ui/status/keyboard.js</file> | ||||
|     <file>ui/status/network.js</file> | ||||
|     <file>ui/status/power.js</file> | ||||
|     <file>ui/status/rfkill.js</file> | ||||
|     <file>ui/status/volume.js</file> | ||||
|     <file>ui/status/bluetooth.js</file> | ||||
|     <file>ui/status/screencast.js</file> | ||||
|     <file>ui/status/system.js</file> | ||||
|   </gresource> | ||||
| </gresources> | ||||
| @@ -6,8 +6,6 @@ const PACKAGE_NAME = '@PACKAGE_NAME@'; | ||||
| const PACKAGE_VERSION = '@PACKAGE_VERSION@'; | ||||
| /* 1 if gnome-bluetooth is available, 0 otherwise */ | ||||
| const HAVE_BLUETOOTH = @HAVE_BLUETOOTH@; | ||||
| /* 1 if networkmanager is available, 0 otherwise */ | ||||
| const HAVE_NETWORKMANAGER = @HAVE_NETWORKMANAGER@; | ||||
| /* gettext package */ | ||||
| const GETTEXT_PACKAGE = '@GETTEXT_PACKAGE@'; | ||||
| /* locale dir */ | ||||
|   | ||||
| @@ -176,7 +176,10 @@ const ExtensionFinder = new Lang.Class({ | ||||
|  | ||||
|     scanExtensions: function() { | ||||
|         let perUserDir = Gio.File.new_for_path(global.userdatadir); | ||||
|         FileUtils.collectFromDatadirs('extensions', true, Lang.bind(this, this._loadExtension, perUserDir)); | ||||
|         FileUtils.collectFromDatadirsAsync('extensions', | ||||
|                                            { processFile: Lang.bind(this, this._loadExtension), | ||||
|                                              includeUserDir: true, | ||||
|                                              data: perUserDir }); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ExtensionFinder.prototype); | ||||
|   | ||||
| @@ -5,27 +5,80 @@ const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Params = imports.misc.params; | ||||
|  | ||||
| function collectFromDatadirs(subdir, includeUserDir, processFile) { | ||||
| function listDirAsync(file, callback) { | ||||
|     let allFiles = []; | ||||
|     file.enumerate_children_async('standard::name,standard::type', | ||||
|                                   Gio.FileQueryInfoFlags.NONE, | ||||
|                                   GLib.PRIORITY_LOW, null, function (obj, res) { | ||||
|         let enumerator = obj.enumerate_children_finish(res); | ||||
|         function onNextFileComplete(obj, res) { | ||||
|             let files = obj.next_files_finish(res); | ||||
|             if (files.length) { | ||||
|                 allFiles = allFiles.concat(files); | ||||
|                 enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, onNextFileComplete); | ||||
|             } else { | ||||
|                 enumerator.close(null); | ||||
|                 callback(allFiles); | ||||
|             } | ||||
|         } | ||||
|         enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, onNextFileComplete); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function _collectFromDirectoryAsync(dir, loadState) { | ||||
|     function done() { | ||||
|         loadState.numLoading--; | ||||
|         if (loadState.loadedCallback && | ||||
|             loadState.numLoading == 0) | ||||
|             loadState.loadedCallback(loadState.data); | ||||
|     } | ||||
|  | ||||
|     dir.query_info_async('standard::type', Gio.FileQueryInfoFlags.NONE, | ||||
|         GLib.PRIORITY_DEFAULT, null, function(object, res) { | ||||
|             try { | ||||
|                 object.query_info_finish(res); | ||||
|             } catch (e) { | ||||
|                 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) | ||||
|                     log(e.message); | ||||
|                 done(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             listDirAsync(dir, Lang.bind(this, function(infos) { | ||||
|                 for (let i = 0; i < infos.length; i++) | ||||
|                     loadState.processFile(dir.get_child(infos[i].get_name()), | ||||
|                                           infos[i], loadState.data); | ||||
|                 done(); | ||||
|             })); | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function collectFromDatadirsAsync(subdir, params) { | ||||
|     params = Params.parse(params, { includeUserDir: false, | ||||
|                                     processFile: null, | ||||
|                                     loadedCallback: null, | ||||
|                                     data: null }); | ||||
|     let loadState = { data: params.data, | ||||
|                       numLoading: 0, | ||||
|                       loadedCallback: params.loadedCallback, | ||||
|                       processFile: params.processFile }; | ||||
|  | ||||
|     if (params.processFile == null) { | ||||
|         if (params.loadedCallback) | ||||
|             params.loadedCallback(params.data); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     let dataDirs = GLib.get_system_data_dirs(); | ||||
|     if (includeUserDir) | ||||
|     if (params.includeUserDir) | ||||
|         dataDirs.unshift(GLib.get_user_data_dir()); | ||||
|     loadState.numLoading = dataDirs.length; | ||||
|  | ||||
|     for (let i = 0; i < dataDirs.length; i++) { | ||||
|         let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', subdir]); | ||||
|         let dir = Gio.File.new_for_path(path); | ||||
|  | ||||
|         let fileEnum; | ||||
|         try { | ||||
|             fileEnum = dir.enumerate_children('standard::name,standard::type', | ||||
|                                               Gio.FileQueryInfoFlags.NONE, null); | ||||
|         } catch (e) { | ||||
|             fileEnum = null; | ||||
|         } | ||||
|         if (fileEnum != null) { | ||||
|             let info; | ||||
|             while ((info = fileEnum.next_file(null))) | ||||
|                 processFile(fileEnum.get_child(info), info); | ||||
|         } | ||||
|         _collectFromDirectoryAsync(dir, loadState); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -64,6 +117,7 @@ function recursivelyMoveDir(srcDir, destDir) { | ||||
|         let type = info.get_file_type(); | ||||
|         let srcChild = srcDir.get_child(info.get_name()); | ||||
|         let destChild = destDir.get_child(info.get_name()); | ||||
|         log([srcChild.get_path(), destChild.get_path()]); | ||||
|         if (type == Gio.FileType.REGULAR) | ||||
|             srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null); | ||||
|         else if (type == Gio.FileType.DIRECTORY) | ||||
|   | ||||
| @@ -4,17 +4,15 @@ const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const PresenceIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager.Presence"> \ | ||||
| <method name="SetStatus"> \ | ||||
|     <arg type="u" direction="in"/> \ | ||||
| </method> \ | ||||
| <property name="status" type="u" access="readwrite"/> \ | ||||
| <signal name="StatusChanged"> \ | ||||
|     <arg type="u" direction="out"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const PresenceIface = <interface name="org.gnome.SessionManager.Presence"> | ||||
| <method name="SetStatus"> | ||||
|     <arg type="u" direction="in"/> | ||||
| </method> | ||||
| <property name="status" type="u" access="readwrite"/> | ||||
| <signal name="StatusChanged"> | ||||
|     <arg type="u" direction="out"/> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const PresenceStatus = { | ||||
|     AVAILABLE: 0, | ||||
| @@ -32,16 +30,14 @@ function Presence(initCallback, cancellable) { | ||||
| // Note inhibitors are immutable objects, so they don't | ||||
| // change at runtime (changes always come in the form | ||||
| // of new inhibitors) | ||||
| const InhibitorIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager.Inhibitor"> \ | ||||
| <method name="GetAppId"> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetReason"> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const InhibitorIface = <interface name="org.gnome.SessionManager.Inhibitor"> | ||||
| <method name="GetAppId"> | ||||
|     <arg type="s" direction="out" /> | ||||
| </method> | ||||
| <method name="GetReason"> | ||||
|     <arg type="s" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| var InhibitorProxy = Gio.DBusProxy.makeProxyWrapper(InhibitorIface); | ||||
| function Inhibitor(objectPath, initCallback, cancellable) { | ||||
| @@ -49,29 +45,26 @@ function Inhibitor(objectPath, initCallback, cancellable) { | ||||
| } | ||||
|  | ||||
| // Not the full interface, only the methods we use | ||||
| const SessionManagerIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager"> \ | ||||
| <method name="Logout"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Shutdown" /> \ | ||||
| <method name="Reboot" /> \ | ||||
| <method name="CanShutdown"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="IsInhibited"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <property name="SessionIsActive" type="b" access="read"/> \ | ||||
| <signal name="InhibitorAdded"> \ | ||||
|     <arg type="o" direction="out"/> \ | ||||
| </signal> \ | ||||
| <signal name="InhibitorRemoved"> \ | ||||
|     <arg type="o" direction="out"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const SessionManagerIface = <interface name="org.gnome.SessionManager"> | ||||
| <method name="Logout"> | ||||
|     <arg type="u" direction="in" /> | ||||
| </method> | ||||
| <method name="Shutdown" /> | ||||
| <method name="Reboot" /> | ||||
| <method name="CanShutdown"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="IsInhibited"> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <signal name="InhibitorAdded"> | ||||
|     <arg type="o" direction="out"/> | ||||
| </signal> | ||||
| <signal name="InhibitorRemoved"> | ||||
|     <arg type="o" direction="out"/> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| var SessionManagerProxy = Gio.DBusProxy.makeProxyWrapper(SessionManagerIface); | ||||
| function SessionManager(initCallback, cancellable) { | ||||
|   | ||||
| @@ -89,7 +89,7 @@ const HistoryManager = new Lang.Class({ | ||||
|         } else if (symbol == Clutter.KEY_Down) { | ||||
|             return this._setNextItem(entry.get_text()); | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _indexChanged: function() { | ||||
|   | ||||
| @@ -1,180 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| try { | ||||
|     var IBus = imports.gi.IBus; | ||||
|     if (!('new_async' in IBus.Bus)) | ||||
|         throw "IBus version is too old"; | ||||
|     const IBusCandidatePopup = imports.ui.ibusCandidatePopup; | ||||
| } catch (e) { | ||||
|     var IBus = null; | ||||
|     log(e); | ||||
| } | ||||
|  | ||||
| let _ibusManager = null; | ||||
|  | ||||
| function getIBusManager() { | ||||
|     if (_ibusManager == null) | ||||
|         _ibusManager = new IBusManager(); | ||||
|     return _ibusManager; | ||||
| } | ||||
|  | ||||
| const IBusManager = new Lang.Class({ | ||||
|     Name: 'IBusManager', | ||||
|  | ||||
|     // This is the longest we'll keep the keyboard frozen until an input | ||||
|     // source is active. | ||||
|     _MAX_INPUT_SOURCE_ACTIVATION_TIME: 4000, // ms | ||||
|  | ||||
|     _init: function() { | ||||
|         if (!IBus) | ||||
|             return; | ||||
|  | ||||
|         IBus.init(); | ||||
|  | ||||
|         this._candidatePopup = new IBusCandidatePopup.CandidatePopup(); | ||||
|  | ||||
|         this._panelService = null; | ||||
|         this._engines = {}; | ||||
|         this._ready = false; | ||||
|         this._registerPropertiesId = 0; | ||||
|         this._currentEngineName = null; | ||||
|  | ||||
|         this._ibus = IBus.Bus.new_async(); | ||||
|         this._ibus.connect('connected', Lang.bind(this, this._onConnected)); | ||||
|         this._ibus.connect('disconnected', Lang.bind(this, this._clear)); | ||||
|         // Need to set this to get 'global-engine-changed' emitions | ||||
|         this._ibus.set_watch_ibus_signal(true); | ||||
|         this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged)); | ||||
|  | ||||
|         this._spawn(); | ||||
|     }, | ||||
|  | ||||
|     _spawn: function() { | ||||
|         try { | ||||
|             Gio.Subprocess.new(['ibus-daemon', '--xim', '--panel', 'disable'], | ||||
|                                Gio.SubprocessFlags.NONE); | ||||
|         } catch(e) { | ||||
|             log('Failed to launch ibus-daemon: ' + e.message); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _clear: function() { | ||||
|         if (this._panelService) | ||||
|             this._panelService.destroy(); | ||||
|  | ||||
|         this._panelService = null; | ||||
|         this._candidatePopup.setPanelService(null); | ||||
|         this._engines = {}; | ||||
|         this._ready = false; | ||||
|         this._registerPropertiesId = 0; | ||||
|         this._currentEngineName = null; | ||||
|  | ||||
|         this.emit('ready', false); | ||||
|  | ||||
|         this._spawn(); | ||||
|     }, | ||||
|  | ||||
|     _onConnected: function() { | ||||
|         this._ibus.list_engines_async(-1, null, Lang.bind(this, this._initEngines)); | ||||
|         this._ibus.request_name_async(IBus.SERVICE_PANEL, | ||||
|                                       IBus.BusNameFlag.REPLACE_EXISTING, | ||||
|                                       -1, null, | ||||
|                                       Lang.bind(this, this._initPanelService)); | ||||
|     }, | ||||
|  | ||||
|     _initEngines: function(ibus, result) { | ||||
|         let enginesList = this._ibus.list_engines_async_finish(result); | ||||
|         if (enginesList) { | ||||
|             for (let i = 0; i < enginesList.length; ++i) { | ||||
|                 let name = enginesList[i].get_name(); | ||||
|                 this._engines[name] = enginesList[i]; | ||||
|             } | ||||
|             this._updateReadiness(); | ||||
|         } else { | ||||
|             this._clear(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _initPanelService: function(ibus, result) { | ||||
|         let success = this._ibus.request_name_async_finish(result); | ||||
|         if (success) { | ||||
|             this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(), | ||||
|                                                          object_path: IBus.PATH_PANEL }); | ||||
|             this._candidatePopup.setPanelService(this._panelService); | ||||
|             this._panelService.connect('update-property', Lang.bind(this, this._updateProperty)); | ||||
|             // If an engine is already active we need to get its properties | ||||
|             this._ibus.get_global_engine_async(-1, null, Lang.bind(this, function(i, result) { | ||||
|                 let engine; | ||||
|                 try { | ||||
|                     engine = this._ibus.get_global_engine_async_finish(result); | ||||
|                     if (!engine) | ||||
|                         return; | ||||
|                 } catch(e) { | ||||
|                     return; | ||||
|                 } | ||||
|                 this._engineChanged(this._ibus, engine.get_name()); | ||||
|             })); | ||||
|             this._updateReadiness(); | ||||
|         } else { | ||||
|             this._clear(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateReadiness: function() { | ||||
|         this._ready = (Object.keys(this._engines).length > 0 && | ||||
|                        this._panelService != null); | ||||
|         this.emit('ready', this._ready); | ||||
|     }, | ||||
|  | ||||
|     _engineChanged: function(bus, engineName) { | ||||
|         if (!this._ready) | ||||
|             return; | ||||
|  | ||||
|         this._currentEngineName = engineName; | ||||
|  | ||||
|         if (this._registerPropertiesId != 0) | ||||
|             return; | ||||
|  | ||||
|         this._registerPropertiesId = | ||||
|             this._panelService.connect('register-properties', Lang.bind(this, function(p, props) { | ||||
|                 if (!props.get(0)) | ||||
|                     return; | ||||
|  | ||||
|                 this._panelService.disconnect(this._registerPropertiesId); | ||||
|                 this._registerPropertiesId = 0; | ||||
|  | ||||
|                 this.emit('properties-registered', this._currentEngineName, props); | ||||
|             })); | ||||
|     }, | ||||
|  | ||||
|     _updateProperty: function(panel, prop) { | ||||
|         this.emit('property-updated', this._currentEngineName, prop); | ||||
|     }, | ||||
|  | ||||
|     activateProperty: function(key, state) { | ||||
|         this._panelService.property_activate(key, state); | ||||
|     }, | ||||
|  | ||||
|     getEngineDesc: function(id) { | ||||
|         if (!IBus || !this._ready) | ||||
|             return null; | ||||
|  | ||||
|         return this._engines[id]; | ||||
|     }, | ||||
|  | ||||
|     setEngine: function(id, callback) { | ||||
|         if (!IBus || !this._ready || id == this._currentEngineName) { | ||||
|             if (callback) | ||||
|                 callback(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._ibus.set_global_engine_async(id, this._MAX_INPUT_SOURCE_ACTIVATION_TIME, | ||||
|                                            null, callback); | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(IBusManager.prototype); | ||||
| @@ -1,153 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const GLib = imports.gi.GLib; | ||||
| const GnomeDesktop = imports.gi.GnomeDesktop; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
|  | ||||
| const DEFAULT_LOCALE = 'en_US'; | ||||
| const DEFAULT_LAYOUT = 'us'; | ||||
| const DEFAULT_VARIANT = ''; | ||||
|  | ||||
| let _xkbInfo = null; | ||||
|  | ||||
| function getXkbInfo() { | ||||
|     if (_xkbInfo == null) | ||||
|         _xkbInfo = new GnomeDesktop.XkbInfo(); | ||||
|     return _xkbInfo; | ||||
| } | ||||
|  | ||||
| let _keyboardManager = null; | ||||
|  | ||||
| function getKeyboardManager() { | ||||
|     if (_keyboardManager == null) | ||||
|         _keyboardManager = new KeyboardManager(); | ||||
|     return _keyboardManager; | ||||
| } | ||||
|  | ||||
| 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 KeyboardManager = new Lang.Class({ | ||||
|     Name: 'KeyboardManager', | ||||
|  | ||||
|     // The XKB protocol doesn't allow for more that 4 layouts in a | ||||
|     // keymap. Wayland doesn't impose this limit and libxkbcommon can | ||||
|     // handle up to 32 layouts but since we need to support X clients | ||||
|     // even as a Wayland compositor, we can't bump this. | ||||
|     MAX_LAYOUTS_PER_GROUP: 4, | ||||
|  | ||||
|     _init: function() { | ||||
|         this._xkbInfo = getXkbInfo(); | ||||
|         this._current = null; | ||||
|         this._localeLayoutInfo = this._getLocaleLayout(); | ||||
|         this._layoutInfos = {}; | ||||
|     }, | ||||
|  | ||||
|     _applyLayoutGroup: function(group) { | ||||
|         let options = this._buildOptionsString(); | ||||
|         let [layouts, variants] = this._buildGroupStrings(group); | ||||
|         Meta.get_backend().set_keymap(layouts, variants, options); | ||||
|     }, | ||||
|  | ||||
|     _applyLayoutGroupIndex: function(idx) { | ||||
|         Meta.get_backend().lock_layout_group(idx); | ||||
|     }, | ||||
|  | ||||
|     apply: function(id) { | ||||
|         let info = this._layoutInfos[id]; | ||||
|         if (!info) | ||||
|             return; | ||||
|  | ||||
|         if (this._current && this._current.group == info.group) { | ||||
|             if (this._current.groupIndex != info.groupIndex) | ||||
|                 this._applyLayoutGroupIndex(info.groupIndex); | ||||
|         } else { | ||||
|             this._applyLayoutGroup(info.group); | ||||
|             this._applyLayoutGroupIndex(info.groupIndex); | ||||
|         } | ||||
|  | ||||
|         this._current = info; | ||||
|     }, | ||||
|  | ||||
|     reapply: function() { | ||||
|         if (!this._current) | ||||
|             return; | ||||
|  | ||||
|         this._applyLayoutGroup(this._current.group); | ||||
|         this._applyLayoutGroupIndex(this._current.groupIndex); | ||||
|     }, | ||||
|  | ||||
|     setUserLayouts: function(ids) { | ||||
|         this._current = null; | ||||
|         this._layoutInfos = {}; | ||||
|  | ||||
|         for (let i = 0; i < ids.length; ++i) { | ||||
|             let [found, , , _layout, _variant] = this._xkbInfo.get_layout_info(ids[i]); | ||||
|             if (found) | ||||
|                 this._layoutInfos[ids[i]] = { id: ids[i], layout: _layout, variant: _variant }; | ||||
|         } | ||||
|  | ||||
|         let i = 0; | ||||
|         let group = []; | ||||
|         for (let id in this._layoutInfos) { | ||||
|             // We need to leave one slot on each group free so that we | ||||
|             // can add a layout containing the symbols for the | ||||
|             // language used in UI strings to ensure that toolkits can | ||||
|             // handle mnemonics like Alt+Ф even if the user is | ||||
|             // actually typing in a different layout. | ||||
|             let groupIndex = i % (this.MAX_LAYOUTS_PER_GROUP - 1); | ||||
|             if (groupIndex == 0) | ||||
|                 group = []; | ||||
|  | ||||
|             let info = this._layoutInfos[id]; | ||||
|             group[groupIndex] = info; | ||||
|             info.group = group; | ||||
|             info.groupIndex = groupIndex; | ||||
|  | ||||
|             i += 1; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _getLocaleLayout: function() { | ||||
|         let locale = GLib.get_language_names()[0]; | ||||
|         if (locale.indexOf('_') == -1) | ||||
|             locale = DEFAULT_LOCALE; | ||||
|  | ||||
|         let [found, , id] = GnomeDesktop.get_input_source_from_locale(locale); | ||||
|         if (!found) | ||||
|             [, , id] = GnomeDesktop.get_input_source_from_locale(DEFAULT_LOCALE); | ||||
|  | ||||
|         let [found, , , _layout, _variant] = this._xkbInfo.get_layout_info(id); | ||||
|         if (found) | ||||
|             return { layout: _layout, variant: _variant }; | ||||
|         else | ||||
|             return { layout: DEFAULT_LAYOUT, variant: DEFAULT_VARIANT }; | ||||
|     }, | ||||
|  | ||||
|     _buildGroupStrings: function(_group) { | ||||
|         let group = _group.concat(this._localeLayoutInfo); | ||||
|         let layouts = group.map(function(g) { return g.layout; }).join(','); | ||||
|         let variants = group.map(function(g) { return g.variant; }).join(','); | ||||
|         return [layouts, variants]; | ||||
|     }, | ||||
|  | ||||
|     setKeyboardOptions: function(options) { | ||||
|         this._xkbOptions = options; | ||||
|     }, | ||||
|  | ||||
|     _buildOptionsString: function() { | ||||
|         let options = this._xkbOptions.join(','); | ||||
|         return options; | ||||
|     } | ||||
| }); | ||||
| @@ -5,80 +5,67 @@ const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| const UPowerGlib = imports.gi.UPowerGlib; | ||||
|  | ||||
| const SystemdLoginManagerIface = '<node> \ | ||||
| <interface name="org.freedesktop.login1.Manager"> \ | ||||
| <method name="Suspend"> \ | ||||
|     <arg type="b" direction="in"/> \ | ||||
| </method> \ | ||||
| <method name="CanSuspend"> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="Inhibit"> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="h" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="GetSession"> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="o" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="ListSessions"> \ | ||||
|     <arg name="sessions" type="a(susso)" direction="out"/> \ | ||||
| </method> \ | ||||
| <signal name="PrepareForSleep"> \ | ||||
|     <arg type="b" direction="out"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager'> | ||||
| <method name='PowerOff'> | ||||
|     <arg type='b' direction='in'/> | ||||
| </method> | ||||
| <method name='Reboot'> | ||||
|     <arg type='b' direction='in'/> | ||||
| </method> | ||||
| <method name='Suspend'> | ||||
|     <arg type='b' direction='in'/> | ||||
| </method> | ||||
| <method name='CanPowerOff'> | ||||
|     <arg type='s' direction='out'/> | ||||
| </method> | ||||
| <method name='CanReboot'> | ||||
|     <arg type='s' direction='out'/> | ||||
| </method> | ||||
| <method name='CanSuspend'> | ||||
|     <arg type='s' direction='out'/> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const SystemdLoginSessionIface = '<node> \ | ||||
| <interface name="org.freedesktop.login1.Session"> \ | ||||
| <signal name="Lock" /> \ | ||||
| <signal name="Unlock" /> \ | ||||
| <property name="Active" type="b" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'> | ||||
| <signal name='Lock' /> | ||||
| <signal name='Unlock' /> | ||||
| </interface>; | ||||
|  | ||||
| const SystemdLoginManager = Gio.DBusProxy.makeProxyWrapper(SystemdLoginManagerIface); | ||||
| const SystemdLoginSession = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface); | ||||
|  | ||||
| const ConsoleKitManagerIface = <interface name='org.freedesktop.ConsoleKit.Manager'> | ||||
| <method name='CanRestart'> | ||||
|     <arg type='b' direction='out'/> | ||||
| </method> | ||||
| <method name='CanStop'> | ||||
|     <arg type='b' direction='out'/> | ||||
| </method> | ||||
| <method name='Restart' /> | ||||
| <method name='Stop' /> | ||||
| <method name='GetCurrentSession'> | ||||
|     <arg type='o' direction='out' /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const ConsoleKitSessionIface = <interface name='org.freedesktop.ConsoleKit.Session'> | ||||
| <method name='IsActive'> | ||||
|     <arg type='b' direction='out' /> | ||||
| </method> | ||||
| <signal name='ActiveChanged'> | ||||
|     <arg type='b' direction='out' /> | ||||
| </signal> | ||||
| <signal name='Lock' /> | ||||
| <signal name='Unlock' /> | ||||
| </interface>; | ||||
|  | ||||
| const ConsoleKitSession = Gio.DBusProxy.makeProxyWrapper(ConsoleKitSessionIface); | ||||
| const ConsoleKitManager = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface); | ||||
|  | ||||
| function haveSystemd() { | ||||
|     return GLib.access("/run/systemd/seats", 0) >= 0; | ||||
| } | ||||
|  | ||||
| function versionCompare(required, reference) { | ||||
|     required = required.split('.'); | ||||
|     reference = reference.split('.'); | ||||
|  | ||||
|     for (let i = 0; i < required.length; i++) { | ||||
|         let requiredInt = parseInt(required[i]); | ||||
|         let referenceInt = parseInt(reference[i]); | ||||
|         if (requiredInt != referenceInt) | ||||
|             return requiredInt < referenceInt; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function canLock() { | ||||
|     try { | ||||
|         let params = GLib.Variant.new('(ss)', ['org.gnome.DisplayManager.Manager', 'Version']); | ||||
|         let result = Gio.DBus.system.call_sync('org.gnome.DisplayManager', | ||||
|                                                '/org/gnome/DisplayManager/Manager', | ||||
|                                                'org.freedesktop.DBus.Properties', | ||||
|                                                'Get', params, null, | ||||
|                                                Gio.DBusCallFlags.NONE, | ||||
|                                                -1, null); | ||||
|  | ||||
|         let version = result.deep_unpack()[0].deep_unpack(); | ||||
|         return haveSystemd() && versionCompare('3.5.91', version); | ||||
|     } catch(e) { | ||||
|         return false; | ||||
|     } | ||||
|     return GLib.access("/sys/fs/cgroup/systemd", 0) >= 0; | ||||
| } | ||||
|  | ||||
| let _loginManager = null; | ||||
| @@ -93,7 +80,7 @@ function getLoginManager() { | ||||
|         if (haveSystemd()) | ||||
|             _loginManager = new LoginManagerSystemd(); | ||||
|         else | ||||
|             _loginManager = new LoginManagerDummy(); | ||||
|             _loginManager = new LoginManagerConsoleKit(); | ||||
|     } | ||||
|  | ||||
|     return _loginManager; | ||||
| @@ -106,27 +93,42 @@ const LoginManagerSystemd = new Lang.Class({ | ||||
|         this._proxy = new SystemdLoginManager(Gio.DBus.system, | ||||
|                                               'org.freedesktop.login1', | ||||
|                                               '/org/freedesktop/login1'); | ||||
|         this._proxy.connectSignal('PrepareForSleep', | ||||
|                                   Lang.bind(this, this._prepareForSleep)); | ||||
|     }, | ||||
|  | ||||
|     getCurrentSessionProxy: function(callback) { | ||||
|         if (this._currentSession) { | ||||
|             callback (this._currentSession); | ||||
|             return; | ||||
|     // Having this function is a bit of a hack since the Systemd and ConsoleKit | ||||
|     // session objects have different interfaces - but in both cases there are | ||||
|     // Lock/Unlock signals, and that's all we count upon at the moment. | ||||
|     getCurrentSessionProxy: function() { | ||||
|         if (!this._currentSession) { | ||||
|             this._currentSession = new SystemdLoginSession(Gio.DBus.system, | ||||
|                                                            'org.freedesktop.login1', | ||||
|                                                            '/org/freedesktop/login1/session/' + | ||||
|                                                            GLib.getenv('XDG_SESSION_ID')); | ||||
|         } | ||||
|  | ||||
|         this._proxy.GetSessionRemote(GLib.getenv('XDG_SESSION_ID'), Lang.bind(this, | ||||
|             function(result, error) { | ||||
|                 if (error) { | ||||
|                     logError(error, 'Could not get a proxy for the current session'); | ||||
|                 } else { | ||||
|                     this._currentSession = new SystemdLoginSession(Gio.DBus.system, | ||||
|                                                                    'org.freedesktop.login1', | ||||
|                                                                    result[0]); | ||||
|                     callback(this._currentSession); | ||||
|                 } | ||||
|             })); | ||||
|         return this._currentSession; | ||||
|     }, | ||||
|  | ||||
|     get sessionActive() { | ||||
|         return Shell.session_is_active_for_systemd(); | ||||
|     }, | ||||
|  | ||||
|     canPowerOff: function(asyncCallback) { | ||||
|         this._proxy.CanPowerOffRemote(function(result, error) { | ||||
|             if (error) | ||||
|                 asyncCallback(false); | ||||
|             else | ||||
|                 asyncCallback(result[0] != 'no'); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     canReboot: function(asyncCallback) { | ||||
|         this._proxy.CanRebootRemote(function(result, error) { | ||||
|             if (error) | ||||
|                 asyncCallback(false); | ||||
|             else | ||||
|                 asyncCallback(result[0] != 'no'); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     canSuspend: function(asyncCallback) { | ||||
| @@ -138,69 +140,90 @@ const LoginManagerSystemd = new Lang.Class({ | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     listSessions: function(asyncCallback) { | ||||
|         this._proxy.ListSessionsRemote(function(result, error) { | ||||
|     powerOff: function() { | ||||
|         this._proxy.PowerOffRemote(true); | ||||
|     }, | ||||
|  | ||||
|     reboot: function() { | ||||
|         this._proxy.RebootRemote(true); | ||||
|     }, | ||||
|  | ||||
|     suspend: function() { | ||||
|         this._proxy.SuspendRemote(true); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const LoginManagerConsoleKit = new Lang.Class({ | ||||
|     Name: 'LoginManagerConsoleKit', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._proxy = new ConsoleKitManager(Gio.DBus.system, | ||||
|                                             'org.freedesktop.ConsoleKit', | ||||
|                                             '/org/freedesktop/ConsoleKit/Manager'); | ||||
|         this._upClient = new UPowerGlib.Client(); | ||||
|     }, | ||||
|  | ||||
|     // Having this function is a bit of a hack since the Systemd and ConsoleKit | ||||
|     // session objects have different interfaces - but in both cases there are | ||||
|     // Lock/Unlock signals, and that's all we count upon at the moment. | ||||
|     getCurrentSessionProxy: function() { | ||||
|         if (!this._currentSession) { | ||||
|             let [currentSessionId] = this._proxy.GetCurrentSessionSync(); | ||||
|             this._currentSession = new ConsoleKitSession(Gio.DBus.system, | ||||
|                                                          'org.freedesktop.ConsoleKit', | ||||
|                                                          currentSessionId); | ||||
|         } | ||||
|  | ||||
|         return this._currentSession; | ||||
|     }, | ||||
|  | ||||
|     get sessionActive() { | ||||
|         if (this._sessionActive !== undefined) | ||||
|             return this._sessionActive; | ||||
|  | ||||
|         let session = this.getCurrentSessionProxy(); | ||||
|         session.connectSignal('ActiveChanged', Lang.bind(this, function(object, senderName, [isActive]) { | ||||
|             this._sessionActive = isActive; | ||||
|         })); | ||||
|         [this._sessionActive] = session.IsActiveSync(); | ||||
|  | ||||
|         return this._sessionActive; | ||||
|     }, | ||||
|  | ||||
|     canPowerOff: function(asyncCallback) { | ||||
|         this._proxy.CanStopRemote(function(result, error) { | ||||
|             if (error) | ||||
|                 asyncCallback([]); | ||||
|                 asyncCallback(false); | ||||
|             else | ||||
|                 asyncCallback(result[0]); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     suspend: function() { | ||||
|         this._proxy.SuspendRemote(true); | ||||
|     }, | ||||
|  | ||||
|     inhibit: function(reason, callback) { | ||||
|         let inVariant = GLib.Variant.new('(ssss)', | ||||
|                                          ['sleep', | ||||
|                                           'GNOME Shell', | ||||
|                                           reason, | ||||
|                                           'delay']); | ||||
|         this._proxy.call_with_unix_fd_list('Inhibit', inVariant, 0, -1, null, null, | ||||
|             Lang.bind(this, function(proxy, result) { | ||||
|                 let fd = -1; | ||||
|                 try { | ||||
|                     let [outVariant, fdList] = proxy.call_with_unix_fd_list_finish(result); | ||||
|                     fd = fdList.steal_fds()[0]; | ||||
|                     callback(new Gio.UnixInputStream({ fd: fd })); | ||||
|                 } catch(e) { | ||||
|                     logError(e, "Error getting systemd inhibitor"); | ||||
|                     callback(null); | ||||
|                 } | ||||
|             })); | ||||
|     }, | ||||
|  | ||||
|     _prepareForSleep: function(proxy, sender, [aboutToSuspend]) { | ||||
|         this.emit('prepare-for-sleep', aboutToSuspend); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(LoginManagerSystemd.prototype); | ||||
|  | ||||
| const LoginManagerDummy = new Lang.Class({ | ||||
|     Name: 'LoginManagerDummy', | ||||
|  | ||||
|     getCurrentSessionProxy: function(callback) { | ||||
|         // we could return a DummySession object that fakes whatever callers | ||||
|         // expect (at the time of writing: connect() and connectSignal() | ||||
|         // methods), but just never calling the callback should be safer | ||||
|     canReboot: function(asyncCallback) { | ||||
|         this._proxy.CanRestartRemote(function(result, error) { | ||||
|             if (error) | ||||
|                 asyncCallback(false); | ||||
|             else | ||||
|                 asyncCallback(result[0]); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     canSuspend: function(asyncCallback) { | ||||
|         asyncCallback(false); | ||||
|         Mainloop.idle_add(Lang.bind(this, function() { | ||||
|             asyncCallback(this._upClient.get_can_suspend()); | ||||
|             return false; | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     listSessions: function(asyncCallback) { | ||||
|         asyncCallback([]); | ||||
|     powerOff: function() { | ||||
|         this._proxy.StopRemote(); | ||||
|     }, | ||||
|  | ||||
|     reboot: function() { | ||||
|         this._proxy.RestartRemote(); | ||||
|     }, | ||||
|  | ||||
|     suspend: function() { | ||||
|         this.emit('prepare-for-sleep', true); | ||||
|         this.emit('prepare-for-sleep', false); | ||||
|     }, | ||||
|  | ||||
|     inhibit: function(reason, callback) { | ||||
|         callback(null); | ||||
|         this._upClient.suspend_sync(null); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(LoginManagerDummy.prototype); | ||||
|   | ||||
| @@ -2,134 +2,116 @@ | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const NMGtk = imports.gi.NMGtk; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| // _getMobileProvidersDatabase: | ||||
| // | ||||
| // Gets the database of mobile providers, with references between MCCMNC/SID and | ||||
| // operator name | ||||
| // | ||||
| let _mpd; | ||||
| function _getMobileProvidersDatabase() { | ||||
|     if (_mpd == null) { | ||||
|         try { | ||||
|             _mpd = new NMGtk.MobileProvidersDatabase(); | ||||
|             _mpd.init(null); | ||||
|         } catch (e) { | ||||
|             log(e.message); | ||||
|             _mpd = null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return _mpd; | ||||
| } | ||||
|  | ||||
| // _findProviderForMccMnc: | ||||
| // @operator_name: operator name | ||||
| // @operator_code: operator code | ||||
| // | ||||
| // Given an operator name string (which may not be a real operator name) and an | ||||
| // operator code string, tries to find a proper operator name to display. | ||||
| // | ||||
| function _findProviderForMccMnc(operator_name, operator_code) { | ||||
|     if (operator_name) { | ||||
|         if (operator_name.length != 0 && | ||||
|             (operator_name.length > 6 || operator_name.length < 5)) { | ||||
|             // this looks like a valid name, i.e. not an MCCMNC (that some | ||||
|             // devices return when not yet connected | ||||
|             return operator_name; | ||||
|         } | ||||
|  | ||||
|         if (isNaN(parseInt(operator_name))) { | ||||
|             // name is definitely not a MCCMNC, so it may be a name | ||||
|             // after all; return that | ||||
|             return operator_name; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let needle; | ||||
|     if ((!operator_name || operator_name.length == 0) && operator_code) | ||||
|         needle = operator_code; | ||||
|     else if (operator_name && (operator_name.length == 6 || operator_name.length == 5)) | ||||
|         needle = operator_name; | ||||
|     else // nothing to search | ||||
|         return null; | ||||
|  | ||||
|     let mpd = _getMobileProvidersDatabase(); | ||||
|     if (mpd) { | ||||
|         let provider = mpd.lookup_3gpp_mcc_mnc(needle); | ||||
|         if (provider) | ||||
|             return provider.get_name(); | ||||
|     } | ||||
|     return null; | ||||
| } | ||||
|  | ||||
| // _findProviderForSid: | ||||
| // @sid: System Identifier of the serving CDMA network | ||||
| // | ||||
| // Tries to find the operator name corresponding to the given SID | ||||
| // | ||||
| function _findProviderForSid(sid) { | ||||
|     if (sid == 0) | ||||
|         return null; | ||||
|  | ||||
|     let mpd = _getMobileProvidersDatabase(); | ||||
|     if (mpd) { | ||||
|         let provider = mpd.lookup_cdma_sid(sid); | ||||
|         if (provider) | ||||
|             return provider.get_name(); | ||||
|     } | ||||
|     return null; | ||||
| } | ||||
|  | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Support for the old ModemManager interface (MM < 0.7) | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
| // The following are not the complete interfaces, just the methods we need | ||||
| // (or may need in the future) | ||||
|  | ||||
| const ModemGsmNetworkInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager.Modem.Gsm.Network"> \ | ||||
| <method name="GetRegistrationInfo"> \ | ||||
|     <arg type="(uss)" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetSignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| <property name="AccessTechnology" type="u" access="read" /> \ | ||||
| <signal name="SignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </signal> \ | ||||
| <signal name="RegistrationInfo"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
|     <arg type="s" direction="out" /> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ModemGsmNetworkInterface = <interface name="org.freedesktop.ModemManager.Modem.Gsm.Network"> | ||||
| <method name="GetRegistrationInfo"> | ||||
|     <arg type="(uss)" direction="out" /> | ||||
| </method> | ||||
| <method name="GetSignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| <property name="AccessTechnology" type="u" access="read" /> | ||||
| <signal name="SignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </signal> | ||||
| <signal name="RegistrationInfo"> | ||||
|     <arg type="u" direction="out" /> | ||||
|     <arg type="s" direction="out" /> | ||||
|     <arg type="s" direction="out" /> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const ModemGsmNetworkProxy = Gio.DBusProxy.makeProxyWrapper(ModemGsmNetworkInterface); | ||||
|  | ||||
| const ModemCdmaInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager.Modem.Cdma"> \ | ||||
| <method name="GetSignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="GetServingSystem"> \ | ||||
|     <arg type="(usu)" direction="out" /> \ | ||||
| </method> \ | ||||
| <signal name="SignalQuality"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ModemCdmaInterface = <interface name="org.freedesktop.ModemManager.Modem.Cdma"> | ||||
| <method name="GetSignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| <method name="GetServingSystem"> | ||||
|     <arg type="(usu)" direction="out" /> | ||||
| </method> | ||||
| <signal name="SignalQuality"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const ModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(ModemCdmaInterface); | ||||
|  | ||||
| let _providersTable; | ||||
| function _getProvidersTable() { | ||||
|     if (_providersTable) | ||||
|         return _providersTable; | ||||
|     return _providersTable = Shell.mobile_providers_parse(null,null); | ||||
| } | ||||
|  | ||||
| function findProviderForMCCMNC(table, needle) { | ||||
|     let needlemcc = needle.substring(0, 3); | ||||
|     let needlemnc = needle.substring(3, needle.length); | ||||
|  | ||||
|     let name2, name3; | ||||
|     for (let iter in table) { | ||||
|         let country = table[iter]; | ||||
|         let providers = country.get_providers(); | ||||
|  | ||||
|         // Search through each country's providers | ||||
|         for (let i = 0; i < providers.length; i++) { | ||||
|             let provider = providers[i]; | ||||
|  | ||||
|             // Search through MCC/MNC list | ||||
|             let list = provider.get_gsm_mcc_mnc(); | ||||
|             for (let j = 0; j < list.length; j++) { | ||||
|                 let mccmnc = list[j]; | ||||
|  | ||||
|                 // Match both 2-digit and 3-digit MNC; prefer a | ||||
|                 // 3-digit match if found, otherwise a 2-digit one. | ||||
|                 if (mccmnc.mcc != needlemcc) | ||||
|                     continue;  // MCC was wrong | ||||
|  | ||||
|                 if (!name3 && needle.length == 6 && needlemnc == mccmnc.mnc) | ||||
|                     name3 = provider.name; | ||||
|  | ||||
|                 if (!name2 && needlemnc.substring(0, 2) == mccmnc.mnc.substring(0, 2)) | ||||
|                     name2 = provider.name; | ||||
|  | ||||
|                 if (name2 && name3) | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return name3 || name2 || null; | ||||
| } | ||||
|  | ||||
| function findProviderForSid(table, sid) { | ||||
|     if (sid == 0) | ||||
|         return null; | ||||
|  | ||||
|     // Search through each country | ||||
|     for (let iter in table) { | ||||
|         let country = table[iter]; | ||||
|         let providers = country.get_providers(); | ||||
|  | ||||
|         // Search through each country's providers | ||||
|         for (let i = 0; i < providers.length; i++) { | ||||
|             let provider = providers[i]; | ||||
|             let cdma_sid = provider.get_cdma_sid(); | ||||
|  | ||||
|             // Search through CDMA SID list | ||||
|             for (let j = 0; j < cdma_sid.length; j++) { | ||||
|                 if (cdma_sid[j] == sid) | ||||
|                     return provider.name; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return null; | ||||
| } | ||||
|  | ||||
| const ModemGsm = new Lang.Class({ | ||||
|     Name: 'ModemGsm', | ||||
|  | ||||
| @@ -145,7 +127,7 @@ const ModemGsm = new Lang.Class({ | ||||
|             this.emit('notify::signal-quality'); | ||||
|         })); | ||||
|         this._proxy.connectSignal('RegistrationInfo', Lang.bind(this, function(proxy, sender, [status, code, name]) { | ||||
|             this.operator_name = _findProviderForMccMnc(name, code); | ||||
|             this.operator_name = this._findOperatorName(name, code); | ||||
|             this.emit('notify::operator-name'); | ||||
|         })); | ||||
|         this._proxy.GetRegistrationInfoRemote(Lang.bind(this, function([result], err) { | ||||
| @@ -155,7 +137,7 @@ const ModemGsm = new Lang.Class({ | ||||
|             } | ||||
|  | ||||
|             let [status, code, name] = result; | ||||
|             this.operator_name = _findProviderForMccMnc(name, code); | ||||
|             this.operator_name = this._findOperatorName(name, code); | ||||
|             this.emit('notify::operator-name'); | ||||
|         })); | ||||
|         this._proxy.GetSignalQualityRemote(Lang.bind(this, function(result, err) { | ||||
| @@ -168,6 +150,32 @@ const ModemGsm = new Lang.Class({ | ||||
|             } | ||||
|             this.emit('notify::signal-quality'); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _findOperatorName: function(name, opCode) { | ||||
|         if (name) { | ||||
|             if (name && name.length != 0 && (name.length > 6 || name.length < 5)) { | ||||
|                 // this looks like a valid name, i.e. not an MCCMNC (that some | ||||
|                 // devices return when not yet connected | ||||
|                 return name; | ||||
|             } | ||||
|             if (isNaN(parseInt(name))) { | ||||
|                 // name is definitely not a MCCMNC, so it may be a name | ||||
|                 // after all; return that | ||||
|                 return name; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let needle; | ||||
|         if ((name == null || name.length == 0) && opCode) | ||||
|             needle = opCode; | ||||
|         else if (name.length == 6 || name.length == 5) | ||||
|             needle = name; | ||||
|         else // nothing to search | ||||
|             return null; | ||||
|  | ||||
|         let table = _getProvidersTable(); | ||||
|         return findProviderForMCCMNC(table, needle); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ModemGsm.prototype); | ||||
| @@ -207,105 +215,15 @@ const ModemCdma = new Lang.Class({ | ||||
|                 // it will return an error if the device is not connected | ||||
|                 this.operator_name = null; | ||||
|             } else { | ||||
|                 let [bandClass, band, sid] = result; | ||||
|  | ||||
|                 this.operator_name = _findProviderForSid(sid) | ||||
|                 let [bandClass, band, id] = result; | ||||
|                 if (name.length > 0) { | ||||
|                     let table = _getProvidersTable(); | ||||
|                     this.operator_name = findProviderForSid(table, id); | ||||
|                 } else | ||||
|                     this.operator_name = null; | ||||
|             } | ||||
|             this.emit('notify::operator-name'); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ModemCdma.prototype); | ||||
|  | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Support for the new ModemManager1 interface (MM >= 0.7) | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const BroadbandModemInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager1.Modem"> \ | ||||
| <property name="SignalQuality" type="(ub)" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const BroadbandModemProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemInterface); | ||||
|  | ||||
| const BroadbandModem3gppInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager1.Modem.Modem3gpp"> \ | ||||
| <property name="OperatorCode" type="s" access="read" /> \ | ||||
| <property name="OperatorName" type="s" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const BroadbandModem3gppProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModem3gppInterface); | ||||
|  | ||||
| const BroadbandModemCdmaInterface = '<node> \ | ||||
| <interface name="org.freedesktop.ModemManager1.Modem.ModemCdma"> \ | ||||
| <property name="Sid" type="u" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const BroadbandModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemCdmaInterface); | ||||
|  | ||||
| const BroadbandModem = new Lang.Class({ | ||||
|     Name: 'BroadbandModem', | ||||
|  | ||||
|     _init: function(path, capabilities) { | ||||
|         this._proxy = new BroadbandModemProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path); | ||||
|         this._proxy_3gpp = new BroadbandModem3gppProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path); | ||||
|         this._proxy_cdma = new BroadbandModemCdmaProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path); | ||||
|         this._capabilities = capabilities; | ||||
|  | ||||
|         this._proxy.connect('g-properties-changed', Lang.bind(this, function(proxy, properties) { | ||||
|             if ('SignalQuality' in properties.deep_unpack()) | ||||
|                 this._reloadSignalQuality(); | ||||
|         })); | ||||
|         this._reloadSignalQuality(); | ||||
|  | ||||
|         this._proxy_3gpp.connect('g-properties-changed', Lang.bind(this, function(proxy, properties) { | ||||
|             let unpacked = properties.deep_unpack(); | ||||
|             if ('OperatorName' in unpacked || 'OperatorCode' in unpacked) | ||||
|                 this._reload3gppOperatorName(); | ||||
|         })); | ||||
|         this._reload3gppOperatorName(); | ||||
|  | ||||
|         this._proxy_cdma.connect('g-properties-changed', Lang.bind(this, function(proxy, properties) { | ||||
|             let unpacked = properties.deep_unpack(); | ||||
|             if ('Nid' in unpacked || 'Sid' in unpacked) | ||||
|                 this._reloadCdmaOperatorName(); | ||||
|         })); | ||||
|         this._reloadCdmaOperatorName(); | ||||
|     }, | ||||
|  | ||||
|     _reloadSignalQuality: function() { | ||||
|         let [quality, recent] = this._proxy.SignalQuality; | ||||
|         this.signal_quality = quality; | ||||
|         this.emit('notify::signal-quality'); | ||||
|     }, | ||||
|  | ||||
|     _reloadOperatorName: function() { | ||||
|         let new_name = ""; | ||||
|         if (this.operator_name_3gpp && this.operator_name_3gpp.length > 0) | ||||
|             new_name += this.operator_name_3gpp; | ||||
|  | ||||
|         if (this.operator_name_cdma && this.operator_name_cdma.length > 0) { | ||||
|             if (new_name != "") | ||||
|                 new_name += ", "; | ||||
|             new_name += this.operator_name_cdma; | ||||
|         } | ||||
|  | ||||
|         this.operator_name = new_name; | ||||
|         this.emit('notify::operator-name'); | ||||
|     }, | ||||
|  | ||||
|     _reload3gppOperatorName: function() { | ||||
|         let name = this._proxy_3gpp.OperatorName; | ||||
|         let code = this._proxy_3gpp.OperatorCode; | ||||
|         this.operator_name_3gpp = _findProviderForMccMnc(name, code); | ||||
|         this._reloadOperatorName(); | ||||
|     }, | ||||
|  | ||||
|     _reloadCdmaOperatorName: function() { | ||||
|         let sid = this._proxy_cdma.Sid; | ||||
|         this.operator_name_cdma = _findProviderForSid(sid); | ||||
|         this._reloadOperatorName(); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(BroadbandModem.prototype); | ||||
|   | ||||
| @@ -1,259 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Params = imports.misc.params; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| // Specified in the D-Bus specification here: | ||||
| // http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager | ||||
| const ObjectManagerIface = '<node> \ | ||||
| <interface name="org.freedesktop.DBus.ObjectManager"> \ | ||||
|   <method name="GetManagedObjects"> \ | ||||
|     <arg name="objects" type="a{oa{sa{sv}}}" direction="out"/> \ | ||||
|   </method> \ | ||||
|   <signal name="InterfacesAdded"> \ | ||||
|     <arg name="objectPath" type="o"/> \ | ||||
|     <arg name="interfaces" type="a{sa{sv}}" /> \ | ||||
|   </signal> \ | ||||
|   <signal name="InterfacesRemoved"> \ | ||||
|     <arg name="objectPath" type="o"/> \ | ||||
|     <arg name="interfaces" type="as" /> \ | ||||
|   </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface); | ||||
|  | ||||
| const ObjectManager = new Lang.Class({ | ||||
|     Name: 'ObjectManager', | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { connection: null, | ||||
|                                         name: null, | ||||
|                                         objectPath: null, | ||||
|                                         knownInterfaces: null, | ||||
|                                         cancellable: null, | ||||
|                                         onLoaded: null }); | ||||
|  | ||||
|         this._connection = params.connection; | ||||
|         this._serviceName = params.name; | ||||
|         this._managerPath = params.objectPath; | ||||
|         this._cancellable = params.cancellable; | ||||
|  | ||||
|         this._managerProxy = new Gio.DBusProxy({ g_connection: this._connection, | ||||
|                                                  g_interface_name: ObjectManagerInfo.name, | ||||
|                                                  g_interface_info: ObjectManagerInfo, | ||||
|                                                  g_name: this._serviceName, | ||||
|                                                  g_object_path: this._managerPath, | ||||
|                                                  g_flags: Gio.DBusProxyFlags.NONE }); | ||||
|  | ||||
|         this._interfaceInfos = {}; | ||||
|         this._objects = {}; | ||||
|         this._interfaces = {}; | ||||
|         this._onLoaded = params.onLoaded; | ||||
|  | ||||
|         if (params.knownInterfaces) | ||||
|             this._registerInterfaces(params.knownInterfaces); | ||||
|  | ||||
|         // Start out inhibiting load until at least the proxy | ||||
|         // manager is loaded and the remote objects are fetched | ||||
|         this._numLoadInhibitors = 1; | ||||
|         this._managerProxy.init_async(GLib.PRIORITY_DEFAULT, | ||||
|                                       this._cancellable, | ||||
|                                       Lang.bind(this, this._onManagerProxyLoaded)); | ||||
|     }, | ||||
|  | ||||
|     _tryToCompleteLoad: function() { | ||||
|         this._numLoadInhibitors--; | ||||
|         if (this._numLoadInhibitors == 0) { | ||||
|             if (this._onLoaded) | ||||
|                 this._onLoaded(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _addInterface: function(objectPath, interfaceName, onFinished) { | ||||
|         let info = this._interfaceInfos[interfaceName]; | ||||
|  | ||||
|         if (!info) { | ||||
|            if (onFinished) | ||||
|                onFinished(); | ||||
|            return; | ||||
|         } | ||||
|  | ||||
|         let proxy = new Gio.DBusProxy({ g_connection: this._connection, | ||||
|                                        g_name: this._serviceName, | ||||
|                                        g_object_path: objectPath, | ||||
|                                        g_interface_name: interfaceName, | ||||
|                                        g_interface_info: info, | ||||
|                                        g_flags: Gio.DBusProxyFlags.NONE }); | ||||
|  | ||||
|         proxy.init_async(GLib.PRIORITY_DEFAULT, | ||||
|                          this._cancellable, | ||||
|                          Lang.bind(this, function(initable, result) { | ||||
|                let error = null; | ||||
|                try { | ||||
|                    initable.init_finish(result); | ||||
|                } catch(e) { | ||||
|                    logError(e, 'could not initialize proxy for interface ' + interfaceName); | ||||
|  | ||||
|                    if (onFinished) | ||||
|                        onFinished(); | ||||
|                    return; | ||||
|                } | ||||
|  | ||||
|                let isNewObject; | ||||
|                if (!this._objects[objectPath]) { | ||||
|                    this._objects[objectPath] = {}; | ||||
|                    isNewObject = true; | ||||
|                } else { | ||||
|                    isNewObject = false; | ||||
|                } | ||||
|  | ||||
|                this._objects[objectPath][interfaceName] = proxy; | ||||
|  | ||||
|                if (!this._interfaces[interfaceName]) | ||||
|                    this._interfaces[interfaceName] = []; | ||||
|  | ||||
|                this._interfaces[interfaceName].push(proxy); | ||||
|  | ||||
|                if (isNewObject) | ||||
|                    this.emit('object-added', objectPath); | ||||
|  | ||||
|                this.emit('interface-added', interfaceName, proxy); | ||||
|  | ||||
|                if (onFinished) | ||||
|                    onFinished(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _removeInterface: function(objectPath, interfaceName) { | ||||
|         if (!this._objects[objectPath]) | ||||
|             return; | ||||
|  | ||||
|         let proxy = this._objects[objectPath][interfaceName]; | ||||
|  | ||||
|         if (this._interfaces[interfaceName]) { | ||||
|             let index = this._interfaces[interfaceName].indexOf(proxy); | ||||
|  | ||||
|             if (index >= 0) | ||||
|                 this._interfaces[interfaceName].splice(index, 1); | ||||
|  | ||||
|             if (this._interfaces[interfaceName].length == 0) | ||||
|                 delete this._interfaces[interfaceName]; | ||||
|         } | ||||
|  | ||||
|         this.emit('interface-removed', interfaceName, proxy); | ||||
|  | ||||
|         this._objects[objectPath][interfaceName] = null; | ||||
|  | ||||
|         if (Object.keys(this._objects[objectPath]).length == 0) { | ||||
|             delete this._objects[objectPath]; | ||||
|             this.emit('object-removed', objectPath); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onManagerProxyLoaded: function(initable, result) { | ||||
|         let error = null; | ||||
|         try { | ||||
|             initable.init_finish(result); | ||||
|         } catch(e) { | ||||
|             logError(e, 'could not initialize object manager for object ' + params.name); | ||||
|  | ||||
|             this._tryToCompleteLoad(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._managerProxy.connectSignal('InterfacesAdded', | ||||
|                                          Lang.bind(this, function(objectManager, sender, [objectPath, interfaces]) { | ||||
|                                              let interfaceNames = Object.keys(interfaces); | ||||
|                                              for (let i = 0; i < interfaceNames.length; i++) | ||||
|                                                  this._addInterface(objectPath, interfaceNames[i]); | ||||
|                                          })); | ||||
|         this._managerProxy.connectSignal('InterfacesRemoved', | ||||
|                                          Lang.bind(this, function(objectManager, sender, [objectPath, interfaceNames]) { | ||||
|                                              for (let i = 0; i < interfaceNames.length; i++) | ||||
|                                                  this._removeInterface(objectPath, interfaceNames[i]); | ||||
|                                          })); | ||||
|  | ||||
|         if (Object.keys(this._interfaceInfos).length == 0) { | ||||
|             this._tryToCompleteLoad(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._managerProxy.GetManagedObjectsRemote(Lang.bind(this, function(result, error) { | ||||
|             if (!result) { | ||||
|                 if (error) { | ||||
|                    logError(error, 'could not get remote objects for service ' + this._serviceName + ' path ' + this._managerPath); | ||||
|                 } | ||||
|  | ||||
|                 this._tryToCompleteLoad(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let [objects] = result; | ||||
|  | ||||
|             let objectPaths = Object.keys(objects); | ||||
|             for (let i = 0; i < objectPaths.length; i++) { | ||||
|                 let objectPath = objectPaths[i]; | ||||
|                 let object = objects[objectPath]; | ||||
|  | ||||
|                 let interfaceNames = Object.getOwnPropertyNames(object); | ||||
|                 for (let j = 0; j < interfaceNames.length; j++) { | ||||
|                     let interfaceName = interfaceNames[j]; | ||||
|  | ||||
|                     // Prevent load from completing until the interface is loaded | ||||
|                     this._numLoadInhibitors++; | ||||
|                     this._addInterface(objectPath, | ||||
|                                        interfaceName, | ||||
|                                        Lang.bind(this, this._tryToCompleteLoad)); | ||||
|                 } | ||||
|             } | ||||
|             this._tryToCompleteLoad(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _registerInterfaces: function(interfaces) { | ||||
|         for (let i = 0; i < interfaces.length; i++) { | ||||
|             let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]); | ||||
|             this._interfaceInfos[info.name] = info; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     getProxy: function(objectPath, interfaceName) { | ||||
|         let object = this._objects[objectPath]; | ||||
|  | ||||
|         if (!object) | ||||
|             return null; | ||||
|  | ||||
|         return object[interfaceName]; | ||||
|     }, | ||||
|  | ||||
|     getProxiesForInterface: function(interfaceName) { | ||||
|         let proxyList = this._interfaces[interfaceName]; | ||||
|  | ||||
|         if (!proxyList) | ||||
|             return []; | ||||
|  | ||||
|         return proxyList; | ||||
|     }, | ||||
|  | ||||
|     getAllProxies: function() { | ||||
|         let proxies = []; | ||||
|  | ||||
|         let objectPaths = Object.keys(this._objects); | ||||
|         for (let i = 0; i < objectPaths.length; i++) { | ||||
|             let object = this._objects[objectPaths]; | ||||
|  | ||||
|             let interfaceNames = Object.keys(object); | ||||
|             for (let j = 0; i < interfaceNames.length; i++) { | ||||
|                 let interfaceName = interfaceNames[i]; | ||||
|                 if (object[interfaceName]) | ||||
|                     proxies.push(object(interfaceName)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return proxies; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ObjectManager.prototype); | ||||
| @@ -1,119 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const ObjectManager = imports.misc.objectManager; | ||||
|  | ||||
| const SmartcardTokenIface = '<node> \ | ||||
| <interface name="org.gnome.SettingsDaemon.Smartcard.Token"> \ | ||||
|   <property name="Name" type="s" access="read"/> \ | ||||
|   <property name="Driver" type="o" access="read"/> \ | ||||
|   <property name="IsInserted" type="b" access="read"/> \ | ||||
|   <property name="UsedToLogin" type="b" access="read"/> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| let _smartcardManager = null; | ||||
|  | ||||
| function getSmartcardManager() { | ||||
|     if (_smartcardManager == null) | ||||
|         _smartcardManager = new SmartcardManager(); | ||||
|  | ||||
|     return _smartcardManager; | ||||
| } | ||||
|  | ||||
| const SmartcardManager = new Lang.Class({ | ||||
|     Name: 'SmartcardManager', | ||||
|     _init: function() { | ||||
|         this._objectManager = new ObjectManager.ObjectManager({ connection: Gio.DBus.session, | ||||
|                                                                 name: "org.gnome.SettingsDaemon.Smartcard", | ||||
|                                                                 objectPath: '/org/gnome/SettingsDaemon/Smartcard', | ||||
|                                                                 knownInterfaces: [ SmartcardTokenIface ], | ||||
|                                                                 onLoaded: Lang.bind(this, this._onLoaded) }); | ||||
|         this._insertedTokens = {}; | ||||
|         this._loginToken = null; | ||||
|     }, | ||||
|  | ||||
|     _onLoaded: function() { | ||||
|         let tokens = this._objectManager.getProxiesForInterface('org.gnome.SettingsDaemon.Smartcard.Token'); | ||||
|  | ||||
|         for (let i = 0; i < tokens.length; i++) | ||||
|             this._addToken(tokens[i]); | ||||
|  | ||||
|         this._objectManager.connect('interface-added', Lang.bind(this, function(objectManager, interfaceName, proxy) { | ||||
|             if (interfaceName == 'org.gnome.SettingsDaemon.Smartcard.Token') | ||||
|                 this._addToken(proxy); | ||||
|         })); | ||||
|  | ||||
|         this._objectManager.connect('interface-removed', Lang.bind(this, function(objectManager, interfaceName, proxy) { | ||||
|             if (interfaceName == 'org.gnome.SettingsDaemon.Smartcard.Token') | ||||
|                 this._removeToken(proxy); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _updateToken: function(token) { | ||||
|         let objectPath = token.get_object_path(); | ||||
|  | ||||
|         delete this._insertedTokens[objectPath]; | ||||
|  | ||||
|         if (token.IsInserted) | ||||
|             this._insertedTokens[objectPath] = token; | ||||
|  | ||||
|         if (token.UsedToLogin) | ||||
|             this._loginToken = token; | ||||
|     }, | ||||
|  | ||||
|     _addToken: function(token) { | ||||
|         this._updateToken(token); | ||||
|  | ||||
|         token.connect('g-properties-changed', | ||||
|                       Lang.bind(this, function(proxy, properties) { | ||||
|                           if ('IsInserted' in properties.deep_unpack()) { | ||||
|                               this._updateToken(token); | ||||
|  | ||||
|                               if (token.IsInserted) { | ||||
|                                   this.emit('smartcard-inserted', token); | ||||
|                               } else { | ||||
|                                   this.emit('smartcard-removed', token); | ||||
|                               } | ||||
|                           } | ||||
|                       })); | ||||
|  | ||||
|         // Emit a smartcard-inserted at startup if it's already plugged in | ||||
|         if (token.IsInserted) | ||||
|             this.emit('smartcard-inserted', token); | ||||
|     }, | ||||
|  | ||||
|     _removeToken: function(token) { | ||||
|         let objectPath = token.get_object_path(); | ||||
|  | ||||
|         if (this._insertedTokens[objectPath] == token) { | ||||
|             delete this._insertedTokens[objectPath]; | ||||
|             this.emit('smartcard-removed', token); | ||||
|         } | ||||
|  | ||||
|         if (this._loginToken == token) | ||||
|             this._loginToken = null; | ||||
|  | ||||
|         token.disconnectAll(); | ||||
|     }, | ||||
|  | ||||
|     hasInsertedTokens: function() { | ||||
|         return Object.keys(this._insertedTokens).length > 0; | ||||
|     }, | ||||
|  | ||||
|     hasInsertedLoginToken: function() { | ||||
|         if (!this._loginToken) | ||||
|             return false; | ||||
|  | ||||
|         if (!this._loginToken.IsInserted) | ||||
|             return false; | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| }); | ||||
| Signals.addSignalMethods(SmartcardManager.prototype); | ||||
							
								
								
									
										223
									
								
								js/misc/util.js
									
									
									
									
									
								
							
							
						
						
									
										223
									
								
								js/misc/util.js
									
									
									
									
									
								
							| @@ -1,15 +1,8 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const SCROLL_TIME = 0.1; | ||||
|  | ||||
| // http://daringfireball.net/2010/07/improved_regex_for_matching_urls | ||||
| const _balancedParens = '\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))*\\)'; | ||||
| @@ -20,7 +13,7 @@ const _urlRegexp = new RegExp( | ||||
|     '(^|' + _leadingJunk + ')' + | ||||
|     '(' + | ||||
|         '(?:' + | ||||
|             '(?:http|https|ftp)://' +             // scheme:// | ||||
|             '[a-z][\\w-]+://' +                   // scheme:// | ||||
|             '|' + | ||||
|             'www\\d{0,3}[.]' +                    // www. | ||||
|             '|' + | ||||
| @@ -80,22 +73,6 @@ function spawnCommandLine(command_line) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| // spawnApp: | ||||
| // @argv: an argv array | ||||
| // | ||||
| // Runs @argv as if it was an application, handling startup notification | ||||
| function spawnApp(argv) { | ||||
|     try { | ||||
|         let app = Gio.AppInfo.create_from_commandline(argv.join(' '), null, | ||||
|                                                       Gio.AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION); | ||||
|  | ||||
|         let context = global.create_app_launch_context(0, -1); | ||||
|         app.launch([], context); | ||||
|     } catch(err) { | ||||
|         _handleSpawnError(argv[0], err); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // trySpawn: | ||||
| // @argv: an argv array | ||||
| // | ||||
| @@ -129,7 +106,7 @@ function trySpawn(argv) | ||||
|     // Dummy child watch; we don't want to double-fork internally | ||||
|     // because then we lose the parent-child relationship, which | ||||
|     // can break polkit.  See https://bugzilla.redhat.com//show_bug.cgi?id=819275 | ||||
|     GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, function () {}); | ||||
|     GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, function () {}, null); | ||||
| } | ||||
|  | ||||
| // trySpawnCommandLine: | ||||
| @@ -153,10 +130,115 @@ function trySpawnCommandLine(command_line) { | ||||
| } | ||||
|  | ||||
| function _handleSpawnError(command, err) { | ||||
|     let title = _("Execution of “%s” failed:").format(command); | ||||
|     let title = _("Execution of '%s' failed:").format(command); | ||||
|     Main.notifyError(title, err.message); | ||||
| } | ||||
|  | ||||
| // killall: | ||||
| // @processName: a process name | ||||
| // | ||||
| // Kills @processName. If no process with the given name is found, | ||||
| // this will fail silently. | ||||
| function killall(processName) { | ||||
|     try { | ||||
|         // pkill is more portable than killall, but on Linux at least | ||||
|         // it won't match if you pass more than 15 characters of the | ||||
|         // process name... However, if you use the '-f' flag to match | ||||
|         // the entire command line, it will work, but we have to be | ||||
|         // careful in that case that we can match | ||||
|         // '/usr/bin/processName' but not 'gedit processName.c' or | ||||
|         // whatever... | ||||
|  | ||||
|         let argv = ['pkill', '-f', '^([^ ]*/)?' + processName + '($| )']; | ||||
|         GLib.spawn_sync(null, argv, null, GLib.SpawnFlags.SEARCH_PATH, null); | ||||
|         // It might be useful to return success/failure, but we'd need | ||||
|         // a wrapper around WIFEXITED and WEXITSTATUS. Since none of | ||||
|         // the current callers care, we don't bother. | ||||
|     } catch (e) { | ||||
|         logError(e, 'Failed to kill ' + processName); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // This was ported from network-manager-applet | ||||
| // Copyright 2007 - 2011 Red Hat, Inc. | ||||
| // Author: Dan Williams <dcbw@redhat.com> | ||||
|  | ||||
| const _IGNORED_WORDS = [ | ||||
|         'Semiconductor', | ||||
|         'Components', | ||||
|         'Corporation', | ||||
|         'Communications', | ||||
|         'Company', | ||||
|         'Corp.', | ||||
|         'Corp', | ||||
|         'Co.', | ||||
|         'Inc.', | ||||
|         'Inc', | ||||
|         'Incorporated', | ||||
|         'Ltd.', | ||||
|         'Limited.', | ||||
|         'Intel', | ||||
|         'chipset', | ||||
|         'adapter', | ||||
|         '[hex]', | ||||
|         'NDIS', | ||||
|         'Module' | ||||
| ]; | ||||
|  | ||||
| const _IGNORED_PHRASES = [ | ||||
|         'Multiprotocol MAC/baseband processor', | ||||
|         'Wireless LAN Controller', | ||||
|         'Wireless LAN Adapter', | ||||
|         'Wireless Adapter', | ||||
|         'Network Connection', | ||||
|         'Wireless Cardbus Adapter', | ||||
|         'Wireless CardBus Adapter', | ||||
|         '54 Mbps Wireless PC Card', | ||||
|         'Wireless PC Card', | ||||
|         'Wireless PC', | ||||
|         'PC Card with XJACK(r) Antenna', | ||||
|         'Wireless cardbus', | ||||
|         'Wireless LAN PC Card', | ||||
|         'Technology Group Ltd.', | ||||
|         'Communication S.p.A.', | ||||
|         'Business Mobile Networks BV', | ||||
|         'Mobile Broadband Minicard Composite Device', | ||||
|         'Mobile Communications AB', | ||||
|         '(PC-Suite Mode)' | ||||
| ]; | ||||
|  | ||||
| function fixupPCIDescription(desc) { | ||||
|     desc = desc.replace(/[_,]/, ' '); | ||||
|  | ||||
|     /* Attempt to shorten ID by ignoring certain phrases */ | ||||
|     for (let i = 0; i < _IGNORED_PHRASES.length; i++) { | ||||
|         let item = _IGNORED_PHRASES[i]; | ||||
|         let pos = desc.indexOf(item); | ||||
|         if (pos != -1) { | ||||
|             let before = desc.substring(0, pos); | ||||
|             let after = desc.substring(pos + item.length, desc.length); | ||||
|             desc = before + after; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Attmept to shorten ID by ignoring certain individual words */ | ||||
|     let words = desc.split(' '); | ||||
|     let out = [ ]; | ||||
|     for (let i = 0; i < words.length; i++) { | ||||
|         let item = words[i]; | ||||
|  | ||||
|         // skip empty items (that come out from consecutive spaces) | ||||
|         if (item.length == 0) | ||||
|             continue; | ||||
|  | ||||
|         if (_IGNORED_WORDS.indexOf(item) == -1) { | ||||
|             out.push(item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return out.join(' '); | ||||
| } | ||||
|  | ||||
| // lowerBound: | ||||
| // @array: an array or array-like object, already sorted | ||||
| //         according to @cmp | ||||
| @@ -206,92 +288,3 @@ function insertSorted(array, val, cmp) { | ||||
|  | ||||
|     return pos; | ||||
| } | ||||
|  | ||||
| const CloseButton = new Lang.Class({ | ||||
|     Name: 'CloseButton', | ||||
|     Extends: St.Button, | ||||
|  | ||||
|     _init: function(boxpointer) { | ||||
|         this.parent({ style_class: 'notification-close'}); | ||||
|  | ||||
|         // This is a bit tricky. St.Bin has its own x-align/y-align properties | ||||
|         // that compete with Clutter's properties. This should be fixed for | ||||
|         // Clutter 2.0. Since St.Bin doesn't define its own setters, the | ||||
|         // setters are a workaround to get Clutter's version. | ||||
|         this.set_x_align(Clutter.ActorAlign.END); | ||||
|         this.set_y_align(Clutter.ActorAlign.START); | ||||
|  | ||||
|         // XXX Clutter 2.0 workaround: ClutterBinLayout needs expand | ||||
|         // to respect the alignments. | ||||
|         this.set_x_expand(true); | ||||
|         this.set_y_expand(true); | ||||
|  | ||||
|         this._boxPointer = boxpointer; | ||||
|         if (boxpointer) | ||||
|             this._boxPointer.connect('arrow-side-changed', Lang.bind(this, this._sync)); | ||||
|     }, | ||||
|  | ||||
|     _computeBoxPointerOffset: function() { | ||||
|         if (!this._boxPointer || !this._boxPointer.actor.get_stage()) | ||||
|             return 0; | ||||
|  | ||||
|         let side = this._boxPointer.arrowSide; | ||||
|         if (side == St.Side.TOP) | ||||
|             return this._boxPointer.getArrowHeight(); | ||||
|         else | ||||
|             return 0; | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let themeNode = this.get_theme_node(); | ||||
|  | ||||
|         let offY = this._computeBoxPointerOffset(); | ||||
|         this.translation_x = themeNode.get_length('-shell-close-overlap-x') | ||||
|         this.translation_y = themeNode.get_length('-shell-close-overlap-y') + offY; | ||||
|     }, | ||||
|  | ||||
|     vfunc_style_changed: function() { | ||||
|         this._sync(); | ||||
|         this.parent(); | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| function makeCloseButton(boxpointer) { | ||||
|     return new CloseButton(boxpointer); | ||||
| } | ||||
|  | ||||
| function ensureActorVisibleInScrollView(scrollView, actor) { | ||||
|     let adjustment = scrollView.vscroll.adjustment; | ||||
|     let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values(); | ||||
|  | ||||
|     let offset = 0; | ||||
|     let vfade = scrollView.get_effect("fade"); | ||||
|     if (vfade) | ||||
|         offset = vfade.vfade_offset; | ||||
|  | ||||
|     let box = actor.get_allocation_box(); | ||||
|     let y1 = box.y1, y2 = box.y2; | ||||
|  | ||||
|     let parent = actor.get_parent(); | ||||
|     while (parent != scrollView) { | ||||
|         if (!parent) | ||||
|             throw new Error("actor not in scroll view"); | ||||
|  | ||||
|         let box = parent.get_allocation_box(); | ||||
|         y1 += box.y1; | ||||
|         y2 += box.y1; | ||||
|         parent = parent.get_parent(); | ||||
|     } | ||||
|  | ||||
|     if (y1 < value + offset) | ||||
|         value = Math.max(0, y1 - offset); | ||||
|     else if (y2 > value + pageSize - offset) | ||||
|         value = Math.min(upper, y2 + offset - pageSize); | ||||
|     else | ||||
|         return; | ||||
|  | ||||
|     Tweener.addTween(adjustment, | ||||
|                      { value: value, | ||||
|                        time: SCROLL_TIME, | ||||
|                        transition: 'easeOutQuad' }); | ||||
| } | ||||
|   | ||||
| @@ -72,9 +72,6 @@ function run() { | ||||
|     Scripting.defineScriptEvent("applicationsShowStart", "Starting to switch to applications view"); | ||||
|     Scripting.defineScriptEvent("applicationsShowDone", "Done switching to applications view"); | ||||
|  | ||||
|     // Enable recording of timestamps for different points in the frame cycle | ||||
|     global.frame_timestamps = true; | ||||
|  | ||||
|     Main.overview.connect('shown', function() { | ||||
|                               Scripting.scriptEvent('overviewShowDone'); | ||||
|                           }); | ||||
| @@ -90,10 +87,7 @@ function run() { | ||||
|             yield Scripting.destroyTestWindows(); | ||||
|  | ||||
|             for (let k = 0; k < config.count; k++) | ||||
|                 yield Scripting.createTestWindow({ width: config.width, | ||||
|                                                    height: config.height, | ||||
|                                                    alpha: config.alpha, | ||||
|                                                    maximized: config.maximized }); | ||||
|                 yield Scripting.createTestWindow(config.width, config.height, config.alpha, config.maximized); | ||||
|  | ||||
|             yield Scripting.waitTestWindows(); | ||||
|             yield Scripting.sleep(1000); | ||||
|   | ||||
| @@ -1,308 +0,0 @@ | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Main = imports.ui.main; | ||||
| const Scripting = imports.ui.scripting; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| let METRICS = { | ||||
|     timeToDesktop: | ||||
|     { description: "Time from starting graphical.target to desktop showing", | ||||
|       units: "us" }, | ||||
|  | ||||
|     overviewShowTime: | ||||
|     { description: "Time to switch to overview view, first time", | ||||
|       units: "us" }, | ||||
|  | ||||
|     applicationsShowTime: | ||||
|     { description: "Time to switch to applications view, first time", | ||||
|       units: "us" }, | ||||
|  | ||||
|     mainViewRedrawTime: | ||||
|     { description: "Time to redraw the main view, full screen", | ||||
|       units: "us" }, | ||||
|  | ||||
|     overviewRedrawTime: | ||||
|     { description: "Time to redraw the overview, full screen, 5 windows", | ||||
|       units: "us" }, | ||||
|  | ||||
|     applicationRedrawTime: | ||||
|     { description: "Time to redraw frame with a maximized application update", | ||||
|       units: "us" }, | ||||
|  | ||||
|     geditStartTime: | ||||
|     { description: "Time from gedit launch to window drawn", | ||||
|       units: "us" }, | ||||
| } | ||||
|  | ||||
| function waitAndDraw(milliseconds) { | ||||
|     let cb; | ||||
|  | ||||
|     let timeline = new Clutter.Timeline({ duration: milliseconds }); | ||||
|     timeline.start(); | ||||
|  | ||||
|     timeline.connect('new-frame', | ||||
|         function(timeline, frame) { | ||||
|             global.stage.queue_redraw(); | ||||
|         }); | ||||
|  | ||||
|     timeline.connect('completed', | ||||
|         function() { | ||||
|             timeline.stop(); | ||||
|             if (cb) | ||||
|                 cb(); | ||||
|         }); | ||||
|  | ||||
|     return function(callback) { | ||||
|         cb = callback; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| function waitSignal(object, signal) { | ||||
|     let cb; | ||||
|  | ||||
|     let id = object.connect(signal, function() { | ||||
|         object.disconnect(id); | ||||
|         if (cb) | ||||
|             cb(); | ||||
|     }); | ||||
|  | ||||
|     return function(callback) { | ||||
|         cb = callback; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| function extractBootTimestamp() { | ||||
|     let sp = Gio.Subprocess.new(['journalctl', '-b', | ||||
|                                  'MESSAGE_ID=7d4958e842da4a758f6c1cdc7b36dcc5', | ||||
|                                  'UNIT=graphical.target', | ||||
|                                  '-o', | ||||
|                                  'json'], | ||||
|                                 Gio.SubprocessFlags.STDOUT_PIPE); | ||||
|     let result = null; | ||||
|  | ||||
|     let datastream = Gio.DataInputStream.new(sp.get_stdout_pipe()); | ||||
|     while (true) { | ||||
|         let [line, length] = datastream.read_line_utf8(null); | ||||
|         if (line === null) | ||||
|             break; | ||||
|  | ||||
|         let fields = JSON.parse(line); | ||||
|         result = Number(fields['__MONOTONIC_TIMESTAMP']); | ||||
|     } | ||||
|     datastream.close(null); | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| function run() { | ||||
|     Scripting.defineScriptEvent("desktopShown", "Finished initial animation"); | ||||
|     Scripting.defineScriptEvent("overviewShowStart", "Starting to show the overview"); | ||||
|     Scripting.defineScriptEvent("overviewShowDone", "Overview finished showing"); | ||||
|     Scripting.defineScriptEvent("applicationsShowStart", "Starting to switch to applications view"); | ||||
|     Scripting.defineScriptEvent("applicationsShowDone", "Done switching to applications view"); | ||||
|     Scripting.defineScriptEvent("mainViewDrawStart", "Drawing main view"); | ||||
|     Scripting.defineScriptEvent("mainViewDrawDone", "Ending timing main view drawing"); | ||||
|     Scripting.defineScriptEvent("overviewDrawStart", "Drawing overview"); | ||||
|     Scripting.defineScriptEvent("overviewDrawDone", "Ending timing overview drawing"); | ||||
|     Scripting.defineScriptEvent("redrawTestStart", "Drawing application window"); | ||||
|     Scripting.defineScriptEvent("redrawTestDone", "Ending timing application window drawing"); | ||||
|     Scripting.defineScriptEvent("collectTimings", "Accumulate frame timings from redraw tests"); | ||||
|     Scripting.defineScriptEvent("geditLaunch", "gedit application launch"); | ||||
|     Scripting.defineScriptEvent("geditFirstFrame", "first frame of gedit window drawn"); | ||||
|  | ||||
|     yield Scripting.waitLeisure(); | ||||
|     Scripting.scriptEvent('desktopShown'); | ||||
|  | ||||
|     Gtk.Settings.get_default().gtk_enable_animations = false; | ||||
|  | ||||
|     Scripting.scriptEvent('overviewShowStart'); | ||||
|     Main.overview.show(); | ||||
|     yield Scripting.waitLeisure(); | ||||
|     Scripting.scriptEvent('overviewShowDone'); | ||||
|  | ||||
|     yield Scripting.sleep(1000); | ||||
|  | ||||
|     Scripting.scriptEvent('applicationsShowStart'); | ||||
|     Main.overview._dash.showAppsButton.checked = true; | ||||
|  | ||||
|     yield Scripting.waitLeisure(); | ||||
|     Scripting.scriptEvent('applicationsShowDone'); | ||||
|  | ||||
|     yield Scripting.sleep(1000); | ||||
|  | ||||
|     Main.overview.hide(); | ||||
|     yield Scripting.waitLeisure(); | ||||
|  | ||||
|     //////////////////////////////////////// | ||||
|     // Tests of redraw speed | ||||
|     //////////////////////////////////////// | ||||
|  | ||||
|     global.frame_timestamps = true; | ||||
|     global.frame_finish_timestamp = true; | ||||
|  | ||||
|     for (let k = 0; k < 5; k++) | ||||
|         yield Scripting.createTestWindow(640, 480, | ||||
|                                          { maximized: true }); | ||||
|     yield Scripting.waitTestWindows(); | ||||
|  | ||||
|     yield Scripting.sleep(1000); | ||||
|  | ||||
|     Scripting.scriptEvent('mainViewDrawStart'); | ||||
|     yield waitAndDraw(1000); | ||||
|     Scripting.scriptEvent('mainViewDrawDone'); | ||||
|  | ||||
|     Main.overview.show(); | ||||
|     Scripting.waitLeisure(); | ||||
|  | ||||
|     yield Scripting.sleep(1500); | ||||
|  | ||||
|     Scripting.scriptEvent('overviewDrawStart'); | ||||
|     yield waitAndDraw(1000); | ||||
|     Scripting.scriptEvent('overviewDrawDone'); | ||||
|  | ||||
|     yield Scripting.destroyTestWindows(); | ||||
|     Main.overview.hide(); | ||||
|  | ||||
|     yield Scripting.createTestWindow(640, 480, | ||||
|                                      { maximized: true, | ||||
|                                        redraws: true}); | ||||
|     yield Scripting.waitTestWindows(); | ||||
|  | ||||
|     yield Scripting.sleep(1000); | ||||
|  | ||||
|     Scripting.scriptEvent('redrawTestStart'); | ||||
|     yield Scripting.sleep(1000); | ||||
|     Scripting.scriptEvent('redrawTestDone'); | ||||
|  | ||||
|     yield Scripting.sleep(1000); | ||||
|     Scripting.scriptEvent('collectTimings'); | ||||
|  | ||||
|     yield Scripting.destroyTestWindows(); | ||||
|  | ||||
|     global.frame_timestamps = false; | ||||
|     global.frame_finish_timestamp = false; | ||||
|  | ||||
|     yield Scripting.sleep(1000); | ||||
|  | ||||
|     //////////////////////////////////////// | ||||
|  | ||||
|     let appSys = Shell.AppSystem.get_default(); | ||||
|     let app = appSys.lookup_app('org.gnome.gedit.desktop'); | ||||
|  | ||||
|     Scripting.scriptEvent('geditLaunch'); | ||||
|     app.activate(); | ||||
|  | ||||
|     let windows = app.get_windows(); | ||||
|     if (windows.length > 0) | ||||
|         throw new Error('gedit was already running'); | ||||
|  | ||||
|     while (windows.length == 0) { | ||||
|         yield waitSignal(global.display, 'window-created'); | ||||
|         windows = app.get_windows(); | ||||
|     } | ||||
|  | ||||
|     let actor = windows[0].get_compositor_private(); | ||||
|     yield waitSignal(actor, 'first-frame'); | ||||
|     Scripting.scriptEvent('geditFirstFrame'); | ||||
|  | ||||
|     yield Scripting.sleep(1000); | ||||
|  | ||||
|     windows[0].delete(global.get_current_time()); | ||||
|  | ||||
|     yield Scripting.sleep(1000); | ||||
|  | ||||
|     Gtk.Settings.get_default().gtk_enable_animations = true; | ||||
| } | ||||
|  | ||||
| let overviewShowStart; | ||||
| let applicationsShowStart; | ||||
| let stagePaintStart; | ||||
| let redrawTiming; | ||||
| let redrawTimes = {}; | ||||
| let geditLaunchTime; | ||||
|  | ||||
| function script_desktopShown(time) { | ||||
|     let bootTimestamp = extractBootTimestamp(); | ||||
|     METRICS.timeToDesktop.value = time - bootTimestamp; | ||||
| } | ||||
|  | ||||
| function script_overviewShowStart(time) { | ||||
|     overviewShowStart = time; | ||||
| } | ||||
|  | ||||
| function script_overviewShowDone(time) { | ||||
|     METRICS.overviewShowTime.value = time - overviewShowStart; | ||||
| } | ||||
|  | ||||
| function script_applicationsShowStart(time) { | ||||
|     applicationsShowStart = time; | ||||
| } | ||||
|  | ||||
| function script_applicationsShowDone(time) { | ||||
|     METRICS.applicationsShowTime.value = time - applicationsShowStart; | ||||
| } | ||||
|  | ||||
| function script_mainViewDrawStart(time) { | ||||
|     redrawTiming = 'mainView'; | ||||
| } | ||||
|  | ||||
| function script_mainViewDrawDone(time) { | ||||
|     redrawTiming = null; | ||||
| } | ||||
|  | ||||
| function script_overviewDrawStart(time) { | ||||
|     redrawTiming = 'overview'; | ||||
| } | ||||
|  | ||||
| function script_overviewDrawDone(time) { | ||||
|     redrawTiming = null; | ||||
| } | ||||
|  | ||||
| function script_redrawTestStart(time) { | ||||
|     redrawTiming = 'application'; | ||||
| } | ||||
|  | ||||
| function script_redrawTestDone(time) { | ||||
|     redrawTiming = null; | ||||
| } | ||||
|  | ||||
| function script_collectTimings(time) { | ||||
|     for (let timing in redrawTimes) { | ||||
|         let times = redrawTimes[timing]; | ||||
|         times.sort(function(a, b) { return a - b }); | ||||
|  | ||||
|         let len = times.length; | ||||
|         let median; | ||||
|  | ||||
|         if (len == 0) | ||||
|             median = -1; | ||||
|         else if (len % 2 == 1) | ||||
|             median = times[(len - 1)/ 2]; | ||||
|         else | ||||
|             median = Math.round((times[len / 2 - 1] + times[len / 2]) / 2); | ||||
|  | ||||
|         METRICS[timing + 'RedrawTime'].value = median; | ||||
|     } | ||||
| } | ||||
|  | ||||
| function script_geditLaunch(time) { | ||||
|     geditLaunchTime = time; | ||||
| } | ||||
|  | ||||
| function script_geditFirstFrame(time) { | ||||
|     METRICS.geditStartTime.value = time - geditLaunchTime; | ||||
| } | ||||
|  | ||||
| function clutter_stagePaintStart(time) { | ||||
|     stagePaintStart = time; | ||||
| } | ||||
|  | ||||
| function clutter_paintCompletedTimestamp(time) { | ||||
|     if (redrawTiming != null && stagePaintStart != null) { | ||||
|         if (!(redrawTiming in redrawTimes)) | ||||
|             redrawTimes[redrawTiming] = []; | ||||
|         redrawTimes[redrawTiming].push(time - stagePaintStart); | ||||
|     } | ||||
|     stagePaintStart = null; | ||||
| } | ||||
| @@ -1,247 +0,0 @@ | ||||
| const Format = imports.format; | ||||
| const Gettext = imports.gettext; | ||||
| const GLib = imports.gi.GLib; | ||||
| const GObject = imports.gi.GObject; | ||||
| const Gio = imports.gi.Gio; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Lang = imports.lang; | ||||
| const Pango = imports.gi.Pango; | ||||
| const Soup = imports.gi.Soup; | ||||
| const WebKit = imports.gi.WebKit2; | ||||
|  | ||||
| const _ = Gettext.gettext; | ||||
|  | ||||
| const Config = imports.misc.config; | ||||
|  | ||||
| const PortalHelperResult = { | ||||
|     CANCELLED: 0, | ||||
|     COMPLETED: 1, | ||||
|     RECHECK: 2 | ||||
| }; | ||||
|  | ||||
| const INACTIVITY_TIMEOUT = 30000; //ms | ||||
| const CONNECTIVITY_RECHECK_RATELIMIT_TIMEOUT = 30 * GLib.USEC_PER_SEC; | ||||
|  | ||||
| const HelperDBusInterface = '<node> \ | ||||
| <interface name="org.gnome.Shell.PortalHelper"> \ | ||||
| <method name="Authenticate"> \ | ||||
|     <arg type="o" direction="in" name="connection" /> \ | ||||
|     <arg type="s" direction="in" name="url" /> \ | ||||
|     <arg type="u" direction="in" name="timestamp" /> \ | ||||
| </method> \ | ||||
| <method name="Close"> \ | ||||
|     <arg type="o" direction="in" name="connection" /> \ | ||||
| </method> \ | ||||
| <method name="Refresh"> \ | ||||
|     <arg type="o" direction="in" name="connection" /> \ | ||||
| </method> \ | ||||
| <signal name="Done"> \ | ||||
|     <arg type="o" name="connection" /> \ | ||||
|     <arg type="u" name="result" /> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const PortalWindow = new Lang.Class({ | ||||
|     Name: 'PortalWindow', | ||||
|     Extends: Gtk.ApplicationWindow, | ||||
|  | ||||
|     _init: function(application, url, timestamp, doneCallback) { | ||||
|         this.parent({ application: application }); | ||||
|  | ||||
|         if (!url) { | ||||
|             url = 'http://www.gnome.org'; | ||||
|             this._originalUrlWasGnome = true; | ||||
|         } else { | ||||
|             this._originalUrlWasGnome = false; | ||||
|         } | ||||
|         this._uri = new Soup.URI(url); | ||||
|         this._everSeenRedirect = false; | ||||
|         this._originalUrl = url; | ||||
|         this._doneCallback = doneCallback; | ||||
|         this._lastRecheck = 0; | ||||
|         this._recheckAtExit = false; | ||||
|  | ||||
|         this._webView = new WebKit.WebView(); | ||||
|         this._webView.connect('decide-policy', Lang.bind(this, this._onDecidePolicy)); | ||||
|         this._webView.load_uri(url); | ||||
|         this._webView.connect('notify::title', Lang.bind(this, this._syncTitle)); | ||||
|         this._syncTitle(); | ||||
|  | ||||
|         this.add(this._webView); | ||||
|         this._webView.show(); | ||||
|         this.maximize(); | ||||
|         this.present_with_time(timestamp); | ||||
|     }, | ||||
|  | ||||
|     _syncTitle: function() { | ||||
|         let title = this._webView.title; | ||||
|  | ||||
|         if (title) { | ||||
|             this.title = title; | ||||
|         } else { | ||||
|             // TRANSLATORS: this is the title of the wifi captive portal login | ||||
|             // window, until we know the title of the actual login page | ||||
|             this.title = _("Web Authentication Redirect"); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     refresh: function() { | ||||
|         this._everSeenRedirect = false; | ||||
|         this._webView.load_uri(this._originalUrl); | ||||
|     }, | ||||
|  | ||||
|     vfunc_delete_event: function(event) { | ||||
|         if (this._recheckAtExit) | ||||
|             this._doneCallback(PortalHelperResult.RECHECK); | ||||
|         else | ||||
|             this._doneCallback(PortalHelperResult.CANCELLED); | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onDecidePolicy: function(view, decision, type) { | ||||
|         if (type == WebKit.PolicyDecisionType.NEW_WINDOW_ACTION) { | ||||
|             decision.ignore(); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (type != WebKit.PolicyDecisionType.NAVIGATION_ACTION) | ||||
|             return false; | ||||
|  | ||||
|         let request = decision.get_request(); | ||||
|         let uri = new Soup.URI(request.get_uri()); | ||||
|  | ||||
|         if (!uri.host_equal(this._uri) && this._originalUrlWasGnome) { | ||||
|             if (uri.get_host() == 'www.gnome.org' && this._everSeenRedirect) { | ||||
|                 // Yay, we got to gnome! | ||||
|                 decision.ignore(); | ||||
|                 this._doneCallback(PortalHelperResult.COMPLETED); | ||||
|                 return true; | ||||
|             } else if (uri.get_host() != 'www.gnome.org') { | ||||
|                 this._everSeenRedirect = true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // We *may* have finished here, but we don't know for | ||||
|         // sure. Tell gnome-shell to run another connectivity check | ||||
|         // (but ratelimit the checks, we don't want to spam | ||||
|         // nmcheck.gnome.org for portals that have 10 or more internal | ||||
|         // redirects - and unfortunately they exist) | ||||
|         // If we hit the rate limit, we also queue a recheck | ||||
|         // when the window is closed, just in case we miss the | ||||
|         // final check and don't realize we're connected | ||||
|         // This should not be a problem in the cancelled logic, | ||||
|         // because if the user doesn't want to start the login, | ||||
|         // we should not see any redirect at all, outside this._uri | ||||
|  | ||||
|         let now = GLib.get_monotonic_time(); | ||||
|         let shouldRecheck = (now - this._lastRecheck) > | ||||
|             CONNECTIVITY_RECHECK_RATELIMIT_TIMEOUT; | ||||
|  | ||||
|         if (shouldRecheck) { | ||||
|             this._lastRecheck = now; | ||||
|             this._recheckAtExit = false; | ||||
|             this._doneCallback(PortalHelperResult.RECHECK); | ||||
|         } else { | ||||
|             this._recheckAtExit = true; | ||||
|         } | ||||
|  | ||||
|         // Update the URI, in case of chained redirects, so we still | ||||
|         // think we're doing the login until gnome-shell kills us | ||||
|         this._uri = uri; | ||||
|  | ||||
|         decision.use(); | ||||
|         return true; | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const WebPortalHelper = new Lang.Class({ | ||||
|     Name: 'WebPortalHelper', | ||||
|     Extends: Gtk.Application, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent({ application_id: 'org.gnome.Shell.PortalHelper', | ||||
|                       flags: Gio.ApplicationFlags.IS_SERVICE, | ||||
|                       inactivity_timeout: 30000 }); | ||||
|  | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(HelperDBusInterface, this); | ||||
|         this._queue = []; | ||||
|     }, | ||||
|  | ||||
|     vfunc_dbus_register: function(connection, path) { | ||||
|         this._dbusImpl.export(connection, path); | ||||
|         this.parent(connection, path); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     vfunc_dbus_unregister: function(connection, path) { | ||||
|         this._dbusImpl.unexport_from_connection(connection); | ||||
|         this.parent(connection, path); | ||||
|     }, | ||||
|  | ||||
|     vfunc_activate: function() { | ||||
|         // If launched manually (for example for testing), force a dummy authentication | ||||
|         // session with the default url | ||||
|         this.Authenticate('/org/gnome/dummy', '', 0); | ||||
|     }, | ||||
|  | ||||
|     Authenticate: function(connection, url, timestamp) { | ||||
|         this._queue.push({ connection: connection, url: url, timestamp: timestamp }); | ||||
|  | ||||
|         this._processQueue(); | ||||
|     }, | ||||
|  | ||||
|     Close: function(connection) { | ||||
|         for (let i = 0; i < this._queue.length; i++) { | ||||
|             let obj = this._queue[i]; | ||||
|  | ||||
|             if (obj.connection == connection) { | ||||
|                 if (obj.window) | ||||
|                     obj.window.destroy(); | ||||
|                 this._queue.splice(i, 1); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._processQueue(); | ||||
|     }, | ||||
|  | ||||
|     Refresh: function(connection) { | ||||
|         for (let i = 0; i < this._queue.length; i++) { | ||||
|             let obj = this._queue[i]; | ||||
|  | ||||
|             if (obj.connection == connection) { | ||||
|                 if (obj.window) | ||||
|                     obj.window.refresh(); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _processQueue: function() { | ||||
|         if (this._queue.length == 0) | ||||
|             return; | ||||
|  | ||||
|         let top = this._queue[0]; | ||||
|         if (top.window != null) | ||||
|             return; | ||||
|  | ||||
|         top.window = new PortalWindow(this, top.uri, top.timestamp, Lang.bind(this, function(result) { | ||||
|             this._dbusImpl.emit_signal('Done', new GLib.Variant('(ou)', [top.connection, result])); | ||||
|         })); | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| function initEnvironment() { | ||||
|     String.prototype.format = Format.format; | ||||
| } | ||||
|  | ||||
| function main(argv) { | ||||
|     initEnvironment(); | ||||
|  | ||||
|     Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR); | ||||
|     Gettext.textdomain(Config.GETTEXT_PACKAGE); | ||||
|  | ||||
|     let app = new WebPortalHelper(); | ||||
|     return app.run(argv); | ||||
| } | ||||
							
								
								
									
										233
									
								
								js/ui/altTab.js
									
									
									
									
									
								
							
							
						
						
									
										233
									
								
								js/ui/altTab.js
									
									
									
									
									
								
							| @@ -2,7 +2,6 @@ | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| @@ -24,7 +23,7 @@ const WINDOW_PREVIEW_SIZE = 128; | ||||
| const APP_ICON_SIZE = 96; | ||||
| const APP_ICON_SIZE_SMALL = 48; | ||||
|  | ||||
| const baseIconSizes = [96, 64, 48, 32, 22]; | ||||
| const iconSizes = [96, 64, 48, 32, 22]; | ||||
|  | ||||
| const AppIconMode = { | ||||
|     THUMBNAIL_ONLY: 1, | ||||
| @@ -58,14 +57,6 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|         this._currentWindow = -1; | ||||
|  | ||||
|         this.thumbnailsVisible = false; | ||||
|  | ||||
|         let apps = Shell.AppSystem.get_default().get_running (); | ||||
|  | ||||
|         if (apps.length == 0) | ||||
|             return; | ||||
|  | ||||
|         this._switcherList = new AppSwitcher(apps, this); | ||||
|         this._items = this._switcherList.icons; | ||||
|     }, | ||||
|  | ||||
|     _allocate: function (actor, box, flags) { | ||||
| @@ -107,6 +98,48 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _getAppLists: function() { | ||||
|         let tracker = Shell.WindowTracker.get_default(); | ||||
|         let appSys = Shell.AppSystem.get_default(); | ||||
|         let allApps = appSys.get_running (); | ||||
|  | ||||
|         let screen = global.screen; | ||||
|         let display = screen.get_display(); | ||||
|         let windows = display.get_tab_list(Meta.TabList.NORMAL_ALL, screen, | ||||
|                                            screen.get_active_workspace()); | ||||
|  | ||||
|         // windows is only the windows on the current workspace. For | ||||
|         // each one, if it corresponds to an app we know, move that | ||||
|         // app from allApps to apps. | ||||
|         let apps = []; | ||||
|         for (let i = 0; i < windows.length && allApps.length != 0; i++) { | ||||
|             let app = tracker.get_window_app(windows[i]); | ||||
|             let index = allApps.indexOf(app); | ||||
|             if (index != -1) { | ||||
|                 apps.push(app); | ||||
|                 allApps.splice(index, 1); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Now @apps is a list of apps on the current workspace, in | ||||
|         // standard Alt+Tab order (MRU except for minimized windows), | ||||
|         // and allApps is a list of apps that only appear on other | ||||
|         // workspaces, sorted by user_time, which is good enough. | ||||
|         return [apps, allApps]; | ||||
|     }, | ||||
|  | ||||
|     _createSwitcher: function() { | ||||
|         let [localApps, otherApps] = this._getAppLists(); | ||||
|  | ||||
|         if (localApps.length == 0 && otherApps.length == 0) | ||||
|             return false; | ||||
|  | ||||
|         this._switcherList = new AppSwitcher(localApps, otherApps, this); | ||||
|         this._items = this._switcherList.icons; | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _initialSelection: function(backward, binding) { | ||||
|         if (binding == 'switch-group') { | ||||
|             if (backward) { | ||||
| @@ -145,13 +178,13 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|                                  this._items[this._selectedIndex].cachedWindows.length); | ||||
|     }, | ||||
|  | ||||
|     _keyPressHandler: function(keysym, action) { | ||||
|     _keyPressHandler: function(keysym, backwards, action) { | ||||
|         if (action == Meta.KeyBindingAction.SWITCH_GROUP) { | ||||
|             this._select(this._selectedIndex, this._nextWindow()); | ||||
|             this._select(this._selectedIndex, backwards ? this._previousWindow() : this._nextWindow()); | ||||
|         } else if (action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) { | ||||
|             this._select(this._selectedIndex, this._previousWindow()); | ||||
|         } else if (action == Meta.KeyBindingAction.SWITCH_APPLICATIONS) { | ||||
|             this._select(this._next()); | ||||
|             this._select(backwards ? this._previous() : this._next()); | ||||
|         } else if (action == Meta.KeyBindingAction.SWITCH_APPLICATIONS_BACKWARD) { | ||||
|             this._select(this._previous()); | ||||
|         } else if (this._thumbnailsFocused) { | ||||
| @@ -161,8 +194,6 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|                 this._select(this._selectedIndex, this._nextWindow()); | ||||
|             else if (keysym == Clutter.Up) | ||||
|                 this._select(this._selectedIndex, null, true); | ||||
|             else | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|         } else { | ||||
|             if (keysym == Clutter.Left) | ||||
|                 this._select(this._previous()); | ||||
| @@ -170,11 +201,7 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|                 this._select(this._next()); | ||||
|             else if (keysym == Clutter.Down) | ||||
|                 this._select(this._selectedIndex, 0); | ||||
|             else | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|         } | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _scrollHandler: function(direction) { | ||||
| @@ -235,13 +262,15 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _finish : function(timestamp) { | ||||
|         let appIcon = this._items[this._selectedIndex]; | ||||
|         if (this._currentWindow < 0) | ||||
|             appIcon.app.activate_window(appIcon.cachedWindows[0], timestamp); | ||||
|         else | ||||
|             Main.activateWindow(appIcon.cachedWindows[this._currentWindow], timestamp); | ||||
|  | ||||
|         this.parent(); | ||||
|  | ||||
|         let appIcon = this._items[this._selectedIndex]; | ||||
|         let window; | ||||
|         if (this._currentWindow >= 0) | ||||
|             window = appIcon.cachedWindows[this._currentWindow]; | ||||
|         else | ||||
|             window = null; | ||||
|         appIcon.app.activate_window(window, timestamp); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy : function() { | ||||
| @@ -305,7 +334,6 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|             this._thumbnailTimeoutId = Mainloop.timeout_add ( | ||||
|                 THUMBNAIL_POPUP_TIME, | ||||
|                 Lang.bind(this, this._timeoutPopupThumbnails)); | ||||
|             GLib.Source.set_name_by_id(this._thumbnailTimeoutId, '[gnome-shell] this._timeoutPopupThumbnails'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -314,7 +342,7 @@ const AppSwitcherPopup = new Lang.Class({ | ||||
|             this._createThumbnails(); | ||||
|         this._thumbnailTimeoutId = 0; | ||||
|         this._thumbnailsFocused = false; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _destroyThumbnails : function() { | ||||
| @@ -359,28 +387,37 @@ const WindowSwitcherPopup = new Lang.Class({ | ||||
|     Name: 'WindowSwitcherPopup', | ||||
|     Extends: SwitcherPopup.SwitcherPopup, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|         this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell.window-switcher' }); | ||||
|     _getWindowList: function() { | ||||
|         let settings = new Gio.Settings({ schema: 'org.gnome.shell.window-switcher' }); | ||||
|         let workspace = settings.get_boolean('current-workspace-only') ? global.screen.get_active_workspace() | ||||
|                                                                        : null; | ||||
|         return global.display.get_tab_list(Meta.TabList.NORMAL, global.screen, workspace); | ||||
|     }, | ||||
|  | ||||
|     _createSwitcher: function() { | ||||
|         let windows = this._getWindowList(); | ||||
|  | ||||
|         if (windows.length == 0) | ||||
|             return; | ||||
|             return false; | ||||
|  | ||||
|         let mode = this._settings.get_enum('app-icon-mode'); | ||||
|         this._switcherList = new WindowList(windows, mode); | ||||
|         this._switcherList = new WindowList(windows); | ||||
|         this._items = this._switcherList.icons; | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _getWindowList: function() { | ||||
|         let workspace = this._settings.get_boolean('current-workspace-only') ? global.screen.get_active_workspace() : null; | ||||
|         return global.display.get_tab_list(Meta.TabList.NORMAL, workspace); | ||||
|     _initialSelection: function(backward, binding) { | ||||
|         if (binding == 'switch-windows-backward' || backward) | ||||
|             this._select(this._items.length - 1); | ||||
|         else if (this._items.length == 1) | ||||
|             this._select(0); | ||||
|         else | ||||
|             this._select(1); | ||||
|     }, | ||||
|  | ||||
|     _keyPressHandler: function(keysym, action) { | ||||
|     _keyPressHandler: function(keysym, backwards, action) { | ||||
|         if (action == Meta.KeyBindingAction.SWITCH_WINDOWS) { | ||||
|             this._select(this._next()); | ||||
|             this._select(backwards ? this._previous() : this._next()); | ||||
|         } else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD) { | ||||
|             this._select(this._previous()); | ||||
|         } else { | ||||
| @@ -388,17 +425,13 @@ const WindowSwitcherPopup = new Lang.Class({ | ||||
|                 this._select(this._previous()); | ||||
|             else if (keysym == Clutter.Right) | ||||
|                 this._select(this._next()); | ||||
|             else | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|         } | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _finish: function() { | ||||
|         Main.activateWindow(this._items[this._selectedIndex].window); | ||||
|  | ||||
|         this.parent(); | ||||
|  | ||||
|         Main.activateWindow(this._items[this._selectedIndex].window); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -419,6 +452,7 @@ const AppIcon = new Lang.Class({ | ||||
|  | ||||
|     set_size: function(size) { | ||||
|         this.icon = this.app.create_icon_texture(size); | ||||
|         this._iconBin.set_size(size, size); | ||||
|         this._iconBin.child = this.icon; | ||||
|     } | ||||
| }); | ||||
| @@ -427,31 +461,34 @@ const AppSwitcher = new Lang.Class({ | ||||
|     Name: 'AppSwitcher', | ||||
|     Extends: SwitcherPopup.SwitcherList, | ||||
|  | ||||
|     _init : function(apps, altTabPopup) { | ||||
|     _init : function(localApps, otherApps, altTabPopup) { | ||||
|         this.parent(true); | ||||
|  | ||||
|         // Construct the AppIcons, add to the popup | ||||
|         let activeWorkspace = global.screen.get_active_workspace(); | ||||
|         let workspaceIcons = []; | ||||
|         let otherIcons = []; | ||||
|         for (let i = 0; i < localApps.length; i++) { | ||||
|             let appIcon = new AppIcon(localApps[i]); | ||||
|             // Cache the window list now; we don't handle dynamic changes here, | ||||
|             // and we don't want to be continually retrieving it | ||||
|             appIcon.cachedWindows = appIcon.app.get_windows(); | ||||
|             workspaceIcons.push(appIcon); | ||||
|         } | ||||
|         for (let i = 0; i < otherApps.length; i++) { | ||||
|             let appIcon = new AppIcon(otherApps[i]); | ||||
|             appIcon.cachedWindows = appIcon.app.get_windows(); | ||||
|             otherIcons.push(appIcon); | ||||
|         } | ||||
|  | ||||
|         this.icons = []; | ||||
|         this._arrows = []; | ||||
|  | ||||
|         let windowTracker = Shell.WindowTracker.get_default(); | ||||
|         let settings = new Gio.Settings({ schema_id: '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, workspace); | ||||
|  | ||||
|         // Construct the AppIcons, add to the popup | ||||
|         for (let i = 0; i < apps.length; i++) { | ||||
|             let appIcon = new AppIcon(apps[i]); | ||||
|             // Cache the window list now; we don't handle dynamic changes here, | ||||
|             // and we don't want to be continually retrieving it | ||||
|             appIcon.cachedWindows = allWindows.filter(function(w) { | ||||
|                 return windowTracker.get_window_app (w) == appIcon.app; | ||||
|             }); | ||||
|             if (appIcon.cachedWindows.length > 0) | ||||
|                 this._addIcon(appIcon); | ||||
|             else if (workspace == null) | ||||
|                 throw new Error('%s appears to be running, but doesn\'t have any windows'.format(appIcon.app.get_name())); | ||||
|         } | ||||
|         for (let i = 0; i < workspaceIcons.length; i++) | ||||
|             this._addIcon(workspaceIcons[i]); | ||||
|         if (workspaceIcons.length > 0 && otherIcons.length > 0) | ||||
|             this.addSeparator(); | ||||
|         for (let i = 0; i < otherIcons.length; i++) | ||||
|             this._addIcon(otherIcons[i]); | ||||
|  | ||||
|         this._curApp = -1; | ||||
|         this._iconSize = 0; | ||||
| @@ -466,39 +503,37 @@ const AppSwitcher = new Lang.Class({ | ||||
|             Mainloop.source_remove(this._mouseTimeOutId); | ||||
|     }, | ||||
|  | ||||
|     _setIconSize: function() { | ||||
|     _getPreferredHeight: function (actor, forWidth, alloc) { | ||||
|         let j = 0; | ||||
|         while(this._items.length > 1 && this._items[j].style_class != 'item-box') { | ||||
|                 j++; | ||||
|         } | ||||
|         let themeNode = this._items[j].get_theme_node(); | ||||
|  | ||||
|         let iconPadding = themeNode.get_horizontal_padding(); | ||||
|         let iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT); | ||||
|         let [iconMinHeight, iconNaturalHeight] = this.icons[j].label.get_preferred_height(-1); | ||||
|         let iconSpacing = iconNaturalHeight + iconPadding + iconBorder; | ||||
|         let totalSpacing = this._list.spacing * (this._items.length - 1); | ||||
|         if (this._separator) | ||||
|            totalSpacing += this._separator.width + this._list.spacing; | ||||
|  | ||||
|         // We just assume the whole screen here due to weirdness happing with the passed width | ||||
|         let primary = Main.layoutManager.primaryMonitor; | ||||
|         let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding(); | ||||
|         let availWidth = primary.width - parentPadding - this.actor.get_theme_node().get_horizontal_padding(); | ||||
|         let height = 0; | ||||
|  | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         let iconSizes = baseIconSizes.map(function(s) { | ||||
|             return s * scaleFactor; | ||||
|         }); | ||||
|  | ||||
|         if (this._items.length == 1) { | ||||
|             this._iconSize = baseIconSizes[0]; | ||||
|         } else { | ||||
|             for(let i =  0; i < baseIconSizes.length; i++) { | ||||
|                 this._iconSize = baseIconSizes[i]; | ||||
|                 let height = iconSizes[i] + iconSpacing; | ||||
|         for(let i =  0; i < iconSizes.length; i++) { | ||||
|                 this._iconSize = iconSizes[i]; | ||||
|                 height = iconSizes[i] + iconSpacing; | ||||
|                 let w = height * this._items.length + totalSpacing; | ||||
|                 if (w <= availWidth) | ||||
|                     break; | ||||
|             } | ||||
|                         break; | ||||
|         } | ||||
|  | ||||
|         if (this._items.length == 1) { | ||||
|             this._iconSize = iconSizes[0]; | ||||
|             height = iconSizes[0] + iconSpacing; | ||||
|         } | ||||
|  | ||||
|         for(let i = 0; i < this.icons.length; i++) { | ||||
| @@ -506,11 +541,9 @@ const AppSwitcher = new Lang.Class({ | ||||
|                 break; | ||||
|             this.icons[i].set_size(this._iconSize); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _getPreferredHeight: function (actor, forWidth, alloc) { | ||||
|         this._setIconSize(); | ||||
|         this.parent(actor, forWidth, alloc); | ||||
|         alloc.min_size = height; | ||||
|         alloc.natural_size = height; | ||||
|     }, | ||||
|  | ||||
|     _allocate: function (actor, box, flags) { | ||||
| @@ -542,9 +575,8 @@ const AppSwitcher = new Lang.Class({ | ||||
|                                                         Lang.bind(this, function () { | ||||
|                                                                             this._enterItem(index); | ||||
|                                                                             this._mouseTimeOutId = 0; | ||||
|                                                                             return GLib.SOURCE_REMOVE; | ||||
|                                                                             return false; | ||||
|                                                         })); | ||||
|             GLib.Source.set_name_by_id(this._mouseTimeOutId, '[gnome-shell] this._enterItem'); | ||||
|         } else | ||||
|            this._itemEntered(index); | ||||
|     }, | ||||
| @@ -606,12 +638,24 @@ const ThumbnailList = new Lang.Class({ | ||||
|     _init : function(windows) { | ||||
|         this.parent(false); | ||||
|  | ||||
|         let activeWorkspace = global.screen.get_active_workspace(); | ||||
|  | ||||
|         // We fake the value of 'separatorAdded' when the app has no window | ||||
|         // on the current workspace, to avoid displaying a useless separator in | ||||
|         // that case. | ||||
|         let separatorAdded = windows.length == 0 || windows[0].get_workspace() != activeWorkspace; | ||||
|  | ||||
|         this._labels = new Array(); | ||||
|         this._thumbnailBins = new Array(); | ||||
|         this._clones = new Array(); | ||||
|         this._windows = windows; | ||||
|  | ||||
|         for (let i = 0; i < windows.length; i++) { | ||||
|             if (!separatorAdded && windows[i].get_workspace() != activeWorkspace) { | ||||
|               this.addSeparator(); | ||||
|               separatorAdded = true; | ||||
|             } | ||||
|  | ||||
|             let box = new St.BoxLayout({ style_class: 'thumbnail-box', | ||||
|                                          vertical: true }); | ||||
|  | ||||
| @@ -644,19 +688,17 @@ const ThumbnailList = new Lang.Class({ | ||||
|         totalPadding += this.actor.get_theme_node().get_horizontal_padding() + this.actor.get_theme_node().get_vertical_padding(); | ||||
|         let [labelMinHeight, labelNaturalHeight] = this._labels[0].get_preferred_height(-1); | ||||
|         let spacing = this._items[0].child.get_theme_node().get_length('spacing'); | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         let thumbnailSize = THUMBNAIL_DEFAULT_SIZE * scaleFactor; | ||||
|  | ||||
|         availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, thumbnailSize); | ||||
|         availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, THUMBNAIL_DEFAULT_SIZE); | ||||
|         let binHeight = availHeight + this._items[0].get_theme_node().get_vertical_padding() + this.actor.get_theme_node().get_vertical_padding() - spacing; | ||||
|         binHeight = Math.min(thumbnailSize, binHeight); | ||||
|         binHeight = Math.min(THUMBNAIL_DEFAULT_SIZE, binHeight); | ||||
|  | ||||
|         for (let i = 0; i < this._thumbnailBins.length; i++) { | ||||
|             let mutterWindow = this._windows[i].get_compositor_private(); | ||||
|             if (!mutterWindow) | ||||
|                 continue; | ||||
|  | ||||
|             let clone = _createWindowClone(mutterWindow, thumbnailSize); | ||||
|             let clone = _createWindowClone(mutterWindow, THUMBNAIL_DEFAULT_SIZE); | ||||
|             this._thumbnailBins[i].set_height(binHeight); | ||||
|             this._thumbnailBins[i].add_actor(clone); | ||||
|             this._clones.push(clone); | ||||
| @@ -670,7 +712,7 @@ const ThumbnailList = new Lang.Class({ | ||||
| const WindowIcon = new Lang.Class({ | ||||
|     Name: 'WindowIcon', | ||||
|  | ||||
|     _init: function(window, mode) { | ||||
|     _init: function(window) { | ||||
|         this.window = window; | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ style_class: 'alt-tab-app', | ||||
| @@ -688,7 +730,8 @@ const WindowIcon = new Lang.Class({ | ||||
|  | ||||
|         this._icon.destroy_all_children(); | ||||
|  | ||||
|         switch (mode) { | ||||
|         let settings = new Gio.Settings({ schema: 'org.gnome.shell.window-switcher' }); | ||||
|         switch (settings.get_enum('app-icon-mode')) { | ||||
|             case AppIconMode.THUMBNAIL_ONLY: | ||||
|                 size = WINDOW_PREVIEW_SIZE; | ||||
|                 this._icon.add_actor(_createWindowClone(mutterWindow, WINDOW_PREVIEW_SIZE)); | ||||
| @@ -726,7 +769,7 @@ const WindowList = new Lang.Class({ | ||||
|     Name: 'WindowList', | ||||
|     Extends: SwitcherPopup.SwitcherList, | ||||
|  | ||||
|     _init : function(windows, mode) { | ||||
|     _init : function(windows) { | ||||
|         this.parent(true); | ||||
|  | ||||
|         this._label = new St.Label({ x_align: Clutter.ActorAlign.CENTER, | ||||
| @@ -738,7 +781,7 @@ const WindowList = new Lang.Class({ | ||||
|  | ||||
|         for (let i = 0; i < windows.length; i++) { | ||||
|             let win = windows[i]; | ||||
|             let icon = new WindowIcon(win, mode); | ||||
|             let icon = new WindowIcon(win); | ||||
|  | ||||
|             this.addItem(icon.actor, icon.label); | ||||
|             this.icons.push(icon); | ||||
|   | ||||
| @@ -1,88 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const St = imports.gi.St; | ||||
| const Signals = imports.signals; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
| const ANIMATED_ICON_UPDATE_TIMEOUT = 100; | ||||
|  | ||||
| const Animation = new Lang.Class({ | ||||
|     Name: 'Animation', | ||||
|  | ||||
|     _init: function(filename, width, height, speed) { | ||||
|         this.actor = new St.Bin(); | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|         this._speed = speed; | ||||
|  | ||||
|         this._isLoaded = false; | ||||
|         this._isPlaying = false; | ||||
|         this._timeoutId = 0; | ||||
|         this._frame = 0; | ||||
|  | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         this._animations = St.TextureCache.get_default().load_sliced_image (filename, width, height, scaleFactor, | ||||
|                                                                             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)); | ||||
|             GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] this._update'); | ||||
|         } | ||||
|  | ||||
|         this._isPlaying = true; | ||||
|     }, | ||||
|  | ||||
|     stop: function() { | ||||
|         if (this._timeoutId > 0) { | ||||
|             Mainloop.source_remove(this._timeoutId); | ||||
|             this._timeoutId = 0; | ||||
|         } | ||||
|  | ||||
|         this._isPlaying = false; | ||||
|     }, | ||||
|  | ||||
|     _showFrame: function(frame) { | ||||
|         let oldFrameActor = this._animations.get_child_at_index(this._frame); | ||||
|         if (oldFrameActor) | ||||
|             oldFrameActor.hide(); | ||||
|  | ||||
|         this._frame = (frame % this._animations.get_n_children()); | ||||
|  | ||||
|         let newFrameActor = this._animations.get_child_at_index(this._frame); | ||||
|         if (newFrameActor) | ||||
|             newFrameActor.show(); | ||||
|     }, | ||||
|  | ||||
|     _update: function() { | ||||
|         this._showFrame(this._frame + 1); | ||||
|         return GLib.SOURCE_CONTINUE; | ||||
|     }, | ||||
|  | ||||
|     _animationsLoaded: function() { | ||||
|         this._isLoaded = true; | ||||
|  | ||||
|         if (this._isPlaying) | ||||
|             this.play(); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy: function() { | ||||
|         this.stop(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const AnimatedIcon = new Lang.Class({ | ||||
|     Name: 'AnimatedIcon', | ||||
|     Extends: Animation, | ||||
|  | ||||
|     _init: function(filename, size) { | ||||
|         this.parent(filename, size, size, ANIMATED_ICON_UPDATE_TIMEOUT); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										1959
									
								
								js/ui/appDisplay.js
									
									
									
									
									
								
							
							
						
						
									
										1959
									
								
								js/ui/appDisplay.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -6,36 +6,6 @@ const Signals = imports.signals; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
|  | ||||
| const RENAMED_DESKTOP_IDS = { | ||||
|     'baobab.desktop': 'org.gnome.baobab.desktop', | ||||
|     'cheese.desktop': 'org.gnome.Cheese.desktop', | ||||
|     'dconf-editor.desktop': 'ca.desrt.dconf-editor.desktop', | ||||
|     'file-roller.desktop': 'org.gnome.FileRoller.desktop', | ||||
|     'gcalctool.desktop': 'gnome-calculator.desktop', | ||||
|     'gedit.desktop': 'org.gnome.gedit.desktop', | ||||
|     'glchess.desktop': 'gnome-chess.desktop', | ||||
|     'glines.desktop': 'five-or-more.desktop', | ||||
|     'gnect.desktop': 'four-in-a-row.desktop', | ||||
|     'gnibbles.desktop': 'gnome-nibbles.desktop', | ||||
|     'gnobots2.desktop': 'gnome-robots.desktop', | ||||
|     'gnome-boxes.desktop': 'org.gnome.Boxes.desktop', | ||||
|     'gnome-clocks.desktop': 'org.gnome.clocks.desktop', | ||||
|     'gnome-contacts.desktop': 'org.gnome.Contacts.desktop', | ||||
|     'gnome-documents.desktop': 'org.gnome.Documents.desktop', | ||||
|     'gnome-font-viewer.desktop': 'org.gnome.font-viewer.desktop', | ||||
|     'gnome-photos.desktop': 'org.gnome.Photos.desktop', | ||||
|     'gnome-screenshot.desktop': 'org.gnome.Screenshot.desktop', | ||||
|     'gnome-software.desktop': 'org.gnome.Software.desktop', | ||||
|     'gnome-weather.desktop': 'org.gnome.Weather.Application.desktop', | ||||
|     'gnomine.desktop': 'gnome-mines.desktop', | ||||
|     'gnotravex.desktop': 'gnome-tetravex.desktop', | ||||
|     'gnotski.desktop': 'gnome-klotski.desktop', | ||||
|     'gtali.desktop': 'tali.desktop', | ||||
|     'nautilus.desktop': 'org.gnome.Nautilus.desktop', | ||||
|     'polari.desktop': 'org.gnome.Polari.desktop', | ||||
|     'totem.desktop': 'org.gnome.Totem.desktop', | ||||
| }; | ||||
|  | ||||
| const AppFavorites = new Lang.Class({ | ||||
|     Name: 'AppFavorites', | ||||
|  | ||||
| @@ -44,31 +14,16 @@ const AppFavorites = new Lang.Class({ | ||||
|     _init: function() { | ||||
|         this._favorites = {}; | ||||
|         global.settings.connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged)); | ||||
|         this.reload(); | ||||
|         this._reload(); | ||||
|     }, | ||||
|  | ||||
|     _onFavsChanged: function() { | ||||
|         this.reload(); | ||||
|         this._reload(); | ||||
|         this.emit('changed'); | ||||
|     }, | ||||
|  | ||||
|     reload: function() { | ||||
|     _reload: function() { | ||||
|         let ids = global.settings.get_strv(this.FAVORITE_APPS_KEY); | ||||
|  | ||||
|         // Map old desktop file names to the current ones | ||||
|         let updated = false; | ||||
|         ids = ids.map(function (id) { | ||||
|             let newId = RENAMED_DESKTOP_IDS[id]; | ||||
|             if (newId !== undefined) { | ||||
|                 updated = true; | ||||
|                 return newId; | ||||
|             } | ||||
|             return id; | ||||
|         }); | ||||
|         // ... and write back the updated desktop file names | ||||
|         if (updated) | ||||
|             global.settings.set_strv(this.FAVORITE_APPS_KEY, ids); | ||||
|  | ||||
|         let appSys = Shell.AppSystem.get_default(); | ||||
|         let apps = ids.map(function (id) { | ||||
|                 return appSys.lookup_app(id); | ||||
|   | ||||
| @@ -1,748 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| // READ THIS FIRST | ||||
| // Background handling is a maze of objects, both objects in this file, and | ||||
| // also objects inside Mutter. They all have a role. | ||||
| // | ||||
| // BackgroundManager | ||||
| //   The only object that other parts of GNOME Shell deal with; a | ||||
| //   BackgroundManager creates background actors and adds them to | ||||
| //   the specified container. When the background is changed by the | ||||
| //   user it will fade out the old actor and fade in the new actor. | ||||
| //   (This is separate from the fading for an animated background, | ||||
| //   since using two actors is quite inefficient.) | ||||
| // | ||||
| // MetaBackgroundImage | ||||
| //   An object represented an image file that will be used for drawing | ||||
| //   the background. MetaBackgroundImage objects asynchronously load, | ||||
| //   so they are first created in an unloaded state, then later emit | ||||
| //   a ::loaded signal when the Cogl object becomes available. | ||||
| // | ||||
| // MetaBackgroundImageCache | ||||
| //   A cache from filename to MetaBackgroundImage. | ||||
| // | ||||
| // BackgroundSource | ||||
| //   An object that is created for each GSettings schema (separate | ||||
| //   settings schemas are used for the lock screen and main background), | ||||
| //   and holds a reference to shared Background objects. | ||||
| // | ||||
| // MetaBackground | ||||
| //   Holds the specification of a background - a background color | ||||
| //   or gradient and one or two images blended together. | ||||
| // | ||||
| // Background | ||||
| //   JS delegate object that Connects a MetaBackground to the GSettings | ||||
| //   schema for the background. | ||||
| // | ||||
| // Animation | ||||
| //   A helper object that handles loading a XML-based animation; it is a | ||||
| //   wrapper for GnomeDesktop.BGSlideShow | ||||
| // | ||||
| // MetaBackgroundActor | ||||
| //   An actor that draws the background for a single monitor | ||||
| // | ||||
| // BackgroundCache | ||||
| //   A cache of Settings schema => BackgroundSource and of a single Animation. | ||||
| //   Also used to share file monitors. | ||||
| // | ||||
| // A static image, background color or gradient is relatively straightforward. The | ||||
| // calling code creates a separate BackgroundManager for each monitor. Since they | ||||
| // are created for the same GSettings schema, they will use the same BackgroundSource | ||||
| // object, which provides a single Background and correspondingly a single | ||||
| // MetaBackground object. | ||||
| // | ||||
| // BackgroundManager               BackgroundManager | ||||
| //        |        \               /        | | ||||
| //        |         BackgroundSource        |        looked up in BackgroundCache | ||||
| //        |                |                | | ||||
| //        |            Background           | | ||||
| //        |                |                | | ||||
| //   MetaBackgroundActor   |    MetaBackgroundActor | ||||
| //         \               |               / | ||||
| //          `------- MetaBackground ------' | ||||
| //                         | | ||||
| //                MetaBackgroundImage            looked up in MetaBackgroundImageCache | ||||
| // | ||||
| // The animated case is tricker because the animation XML file can specify different | ||||
| // files for different monitor resolutions and aspect ratios. For this reason, | ||||
| // the BackgroundSource provides different Background share a single Animation object, | ||||
| // which tracks the animation, but use different MetaBackground objects. In the | ||||
| // common case, the different MetaBackground objects will be created for the | ||||
| // same filename and look up the *same* MetaBackgroundImage object, so there is | ||||
| // little wasted memory: | ||||
| // | ||||
| // BackgroundManager               BackgroundManager | ||||
| //        |        \               /        | | ||||
| //        |         BackgroundSource        |        looked up in BackgroundCache | ||||
| //        |             /      \            | | ||||
| //        |     Background   Background     | | ||||
| //        |       |     \      /   |        | | ||||
| //        |       |    Animation   |        |        looked up in BackgroundCache | ||||
| // MetaBackgroundA|tor           Me|aBackgroundActor | ||||
| //         \      |                |       / | ||||
| //      MetaBackground           MetaBackground | ||||
| //                 \                 / | ||||
| //                MetaBackgroundImage            looked up in MetaBackgroundImageCache | ||||
| //                MetaBackgroundImage | ||||
| // | ||||
| // But the case of different filenames and different background images | ||||
| // is possible as well: | ||||
| //                        .... | ||||
| //      MetaBackground              MetaBackground | ||||
| //             |                          | | ||||
| //     MetaBackgroundImage         MetaBackgroundImage | ||||
| //     MetaBackgroundImage         MetaBackgroundImage | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GDesktopEnums = imports.gi.GDesktopEnums; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const GnomeDesktop = imports.gi.GnomeDesktop; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const Params = imports.misc.params; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff); | ||||
|  | ||||
| const BACKGROUND_SCHEMA = 'org.gnome.desktop.background'; | ||||
| const PRIMARY_COLOR_KEY = 'primary-color'; | ||||
| const SECONDARY_COLOR_KEY = 'secondary-color'; | ||||
| const COLOR_SHADING_TYPE_KEY = 'color-shading-type'; | ||||
| const BACKGROUND_STYLE_KEY = 'picture-options'; | ||||
| const PICTURE_OPACITY_KEY = 'picture-opacity'; | ||||
| const PICTURE_URI_KEY = 'picture-uri'; | ||||
|  | ||||
| const FADE_ANIMATION_TIME = 1.0; | ||||
|  | ||||
| // These parameters affect how often we redraw. | ||||
| // The first is how different (percent crossfaded) the slide show | ||||
| // has to look before redrawing and the second is the minimum | ||||
| // frequency (in seconds) we're willing to wake up | ||||
| const ANIMATION_OPACITY_STEP_INCREMENT = 4.0; | ||||
| const ANIMATION_MIN_WAKEUP_INTERVAL = 1.0; | ||||
|  | ||||
| let _backgroundCache = null; | ||||
|  | ||||
| const BackgroundCache = new Lang.Class({ | ||||
|     Name: 'BackgroundCache', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._pendingFileLoads = []; | ||||
|         this._fileMonitors = {}; | ||||
|         this._backgroundSources = {}; | ||||
|     }, | ||||
|  | ||||
|     monitorFile: function(filename) { | ||||
|         if (this._fileMonitors[filename]) | ||||
|             return; | ||||
|  | ||||
|         let file = Gio.File.new_for_path(filename); | ||||
|         let monitor = file.monitor(Gio.FileMonitorFlags.NONE, null); | ||||
|         monitor.connect('changed', | ||||
|                         Lang.bind(this, function() { | ||||
|                             this.emit('file-changed', filename); | ||||
|                         })); | ||||
|  | ||||
|         this._fileMonitors[filename] = monitor; | ||||
|     }, | ||||
|  | ||||
|     getAnimation: function(params) { | ||||
|         params = Params.parse(params, { filename: null, | ||||
|                                         onLoaded: null }); | ||||
|  | ||||
|         if (this._animationFilename == params.filename) { | ||||
|             if (params.onLoaded) { | ||||
|                 let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|                     params.onLoaded(this._animation); | ||||
|                     return GLib.SOURCE_REMOVE; | ||||
|                 })); | ||||
|                 GLib.Source.set_name_by_id(id, '[gnome-shell] params.onLoaded'); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let animation = new Animation({ filename: params.filename }); | ||||
|  | ||||
|         animation.load(Lang.bind(this, function() { | ||||
|                            this._animationFilename = params.filename; | ||||
|                            this._animation = animation; | ||||
|  | ||||
|                            if (params.onLoaded) { | ||||
|                                let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|                                    params.onLoaded(this._animation); | ||||
|                                    return GLib.SOURCE_REMOVE; | ||||
|                                })); | ||||
|                                GLib.Source.set_name_by_id(id, '[gnome-shell] params.onLoaded'); | ||||
|                            } | ||||
|                        })); | ||||
|     }, | ||||
|  | ||||
|     getBackgroundSource: function(layoutManager, settingsSchema) { | ||||
|         // The layoutManager is always the same one; we pass in it since | ||||
|         // Main.layoutManager may not be set yet | ||||
|  | ||||
|         if (!(settingsSchema in this._backgroundSources)) { | ||||
|             this._backgroundSources[settingsSchema] = new BackgroundSource(layoutManager, settingsSchema); | ||||
|             this._backgroundSources[settingsSchema]._useCount = 1; | ||||
|         } else { | ||||
|             this._backgroundSources[settingsSchema]._useCount++; | ||||
|         } | ||||
|  | ||||
|         return this._backgroundSources[settingsSchema]; | ||||
|     }, | ||||
|  | ||||
|     releaseBackgroundSource: function(settingsSchema) { | ||||
|         if (settingsSchema in this._backgroundSources) { | ||||
|             let source = this._backgroundSources[settingsSchema]; | ||||
|             source._useCount--; | ||||
|             if (source._useCount == 0) { | ||||
|                 delete this._backgroundSources[settingsSchema]; | ||||
|                 source.destroy(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(BackgroundCache.prototype); | ||||
|  | ||||
| function getBackgroundCache() { | ||||
|     if (!_backgroundCache) | ||||
|         _backgroundCache = new BackgroundCache(); | ||||
|     return _backgroundCache; | ||||
| } | ||||
|  | ||||
| const Background = new Lang.Class({ | ||||
|     Name: 'Background', | ||||
|  | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { monitorIndex: 0, | ||||
|                                         layoutManager: Main.layoutManager, | ||||
|                                         settings: null, | ||||
|                                         filename: null, | ||||
|                                         style: null }); | ||||
|  | ||||
|         this.background = new Meta.Background({ meta_screen: global.screen }); | ||||
|         this.background._delegate = this; | ||||
|  | ||||
|         this._settings = params.settings; | ||||
|         this._filename = params.filename; | ||||
|         this._style = params.style; | ||||
|         this._monitorIndex = params.monitorIndex; | ||||
|         this._layoutManager = params.layoutManager; | ||||
|         this._fileWatches = {}; | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|         this.isLoaded = false; | ||||
|  | ||||
|         this._settingsChangedSignalId = this._settings.connect('changed', Lang.bind(this, function() { | ||||
|                                             this.emit('changed'); | ||||
|                                         })); | ||||
|  | ||||
|         this._load(); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this._cancellable.cancel(); | ||||
|         this._removeAnimationTimeout(); | ||||
|  | ||||
|         let i; | ||||
|         let keys = Object.keys(this._fileWatches); | ||||
|         for (i = 0; i < keys.length; i++) { | ||||
|             this._cache.disconnect(this._fileWatches[keys[i]]); | ||||
|         } | ||||
|         this._fileWatches = null; | ||||
|  | ||||
|         if (this._settingsChangedSignalId != 0) | ||||
|             this._settings.disconnect(this._settingsChangedSignalId); | ||||
|         this._settingsChangedSignalId = 0; | ||||
|     }, | ||||
|  | ||||
|     updateResolution: function() { | ||||
|         if (this._animation) { | ||||
|             this._removeAnimationTimeout(); | ||||
|             this._updateAnimation(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _setLoaded: function() { | ||||
|         if (this.isLoaded) | ||||
|             return; | ||||
|  | ||||
|         this.isLoaded = true; | ||||
|  | ||||
|         let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|             this.emit('loaded'); | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|         })); | ||||
|         GLib.Source.set_name_by_id(id, '[gnome-shell] this.emit'); | ||||
|     }, | ||||
|  | ||||
|     _loadPattern: function() { | ||||
|         let colorString, res, color, secondColor; | ||||
|  | ||||
|         colorString = this._settings.get_string(PRIMARY_COLOR_KEY); | ||||
|         [res, color] = Clutter.Color.from_string(colorString); | ||||
|         colorString = this._settings.get_string(SECONDARY_COLOR_KEY); | ||||
|         [res, secondColor] = Clutter.Color.from_string(colorString); | ||||
|  | ||||
|         let shadingType = this._settings.get_enum(COLOR_SHADING_TYPE_KEY); | ||||
|  | ||||
|         if (shadingType == GDesktopEnums.BackgroundShading.SOLID) | ||||
|             this.background.set_color(color); | ||||
|         else | ||||
|             this.background.set_gradient(shadingType, color, secondColor); | ||||
|     }, | ||||
|  | ||||
|     _watchFile: function(filename) { | ||||
|         if (this._fileWatches[filename]) | ||||
|             return; | ||||
|  | ||||
|         this._cache.monitorFile(filename); | ||||
|         let signalId = this._cache.connect('file-changed', | ||||
|                                            Lang.bind(this, function(cache, changedFile) { | ||||
|                                                if (changedFile == filename) { | ||||
|                                                    let imageCache = Meta.BackgroundImageCache.get_default(); | ||||
|                                                    imageCache.purge(changedFile); | ||||
|                                                    this.emit('changed'); | ||||
|                                                } | ||||
|                                            })); | ||||
|         this._fileWatches[filename] = signalId; | ||||
|     }, | ||||
|  | ||||
|     _removeAnimationTimeout: function() { | ||||
|         if (this._updateAnimationTimeoutId) { | ||||
|             GLib.source_remove(this._updateAnimationTimeoutId); | ||||
|             this._updateAnimationTimeoutId = 0; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateAnimation: function() { | ||||
|         this._updateAnimationTimeoutId = 0; | ||||
|  | ||||
|         this._animation.update(this._layoutManager.monitors[this._monitorIndex]); | ||||
|         let files = this._animation.keyFrameFiles; | ||||
|  | ||||
|         let finish = Lang.bind(this, function() { | ||||
|             this._setLoaded(); | ||||
|             if (files.length > 1) { | ||||
|                 this.background.set_blend(files[0], files[1], | ||||
|                                           this._animation.transitionProgress, | ||||
|                                           this._style); | ||||
|             } else if (files.length > 0) { | ||||
|                 this.background.set_filename(files[0], this._style); | ||||
|             } else { | ||||
|                 this.background.set_filename(null, this._style); | ||||
|             } | ||||
|             this._queueUpdateAnimation(); | ||||
|         }); | ||||
|  | ||||
|         let cache = Meta.BackgroundImageCache.get_default(); | ||||
|         let numPendingImages = files.length; | ||||
|         let images = []; | ||||
|         for (let i = 0; i < files.length; i++) { | ||||
|             this._watchFile(files[i]); | ||||
|             let image = cache.load(files[i]); | ||||
|             images.push(image); | ||||
|             if (image.is_loaded()) { | ||||
|                 numPendingImages--; | ||||
|                 if (numPendingImages == 0) | ||||
|                     finish(); | ||||
|             } else { | ||||
|                 let id = image.connect('loaded', | ||||
|                                        Lang.bind(this, function() { | ||||
|                                            image.disconnect(id); | ||||
|                                            numPendingImages--; | ||||
|                                            if (numPendingImages == 0) | ||||
|                                                finish(); | ||||
|                                        })); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _queueUpdateAnimation: function() { | ||||
|         if (this._updateAnimationTimeoutId != 0) | ||||
|             return; | ||||
|  | ||||
|         if (!this._cancellable || this._cancellable.is_cancelled()) | ||||
|             return; | ||||
|  | ||||
|         if (!this._animation.transitionDuration) | ||||
|             return; | ||||
|  | ||||
|         let nSteps = 255 / ANIMATION_OPACITY_STEP_INCREMENT; | ||||
|         let timePerStep = (this._animation.transitionDuration * 1000) / nSteps; | ||||
|  | ||||
|         let interval = Math.max(ANIMATION_MIN_WAKEUP_INTERVAL * 1000, | ||||
|                                 timePerStep); | ||||
|  | ||||
|         if (interval > GLib.MAXUINT32) | ||||
|             return; | ||||
|  | ||||
|         this._updateAnimationTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, | ||||
|                                                       interval, | ||||
|                                                       Lang.bind(this, function() { | ||||
|                                                                     this._updateAnimationTimeoutId = 0; | ||||
|                                                                     this._updateAnimation(); | ||||
|                                                                     return GLib.SOURCE_REMOVE; | ||||
|                                                                 })); | ||||
|         GLib.Source.set_name_by_id(this._updateAnimationTimeoutId, '[gnome-shell] this._updateAnimation'); | ||||
|     }, | ||||
|  | ||||
|     _loadAnimation: function(filename) { | ||||
|         this._cache.getAnimation({ filename: filename, | ||||
|                                              onLoaded: Lang.bind(this, function(animation) { | ||||
|                                                  this._animation = animation; | ||||
|  | ||||
|                                                  if (!this._animation || this._cancellable.is_cancelled()) { | ||||
|                                                      this._setLoaded(); | ||||
|                                                      return; | ||||
|                                                  } | ||||
|  | ||||
|                                                  this._updateAnimation(); | ||||
|                                                  this._watchFile(filename); | ||||
|                                              }) | ||||
|                                            }); | ||||
|     }, | ||||
|  | ||||
|     _loadImage: function(filename) { | ||||
|         this.background.set_filename(filename, this._style); | ||||
|         this._watchFile(filename); | ||||
|  | ||||
|         let cache = Meta.BackgroundImageCache.get_default(); | ||||
|         let image = cache.load(filename); | ||||
|         if (image.is_loaded()) | ||||
|             this._setLoaded(); | ||||
|         else { | ||||
|             let id = image.connect('loaded', | ||||
|                                    Lang.bind(this, function() { | ||||
|                                        this._setLoaded(); | ||||
|                                        image.disconnect(id); | ||||
|                                    })); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _loadFile: function(filename) { | ||||
|         if (filename.endsWith('.xml')) | ||||
|             this._loadAnimation(filename); | ||||
|         else | ||||
|             this._loadImage(filename); | ||||
|     }, | ||||
|  | ||||
|     _load: function () { | ||||
|         this._cache = getBackgroundCache(); | ||||
|  | ||||
|         this._loadPattern(); | ||||
|  | ||||
|         if (!this._filename) { | ||||
|             this._setLoaded(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._loadFile(this._filename); | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(Background.prototype); | ||||
|  | ||||
| let _systemBackground; | ||||
|  | ||||
| const SystemBackground = new Lang.Class({ | ||||
|     Name: 'SystemBackground', | ||||
|  | ||||
|     _init: function() { | ||||
|         let filename = global.datadir + '/theme/noise-texture.png'; | ||||
|  | ||||
|         if (_systemBackground == null) { | ||||
|             _systemBackground = new Meta.Background({ meta_screen: global.screen }); | ||||
|             _systemBackground.set_color(DEFAULT_BACKGROUND_COLOR); | ||||
|             _systemBackground.set_filename(filename, GDesktopEnums.BackgroundStyle.WALLPAPER); | ||||
|         } | ||||
|  | ||||
|         this.actor = new Meta.BackgroundActor({ meta_screen: global.screen, | ||||
|                                                 monitor: 0, | ||||
|                                                 background: _systemBackground }); | ||||
|  | ||||
|         let cache = Meta.BackgroundImageCache.get_default(); | ||||
|         let image = cache.load(filename); | ||||
|         if (image.is_loaded()) { | ||||
|             image = null; | ||||
|             let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() { | ||||
|                 this.emit('loaded'); | ||||
|                 return GLib.SOURCE_REMOVE; | ||||
|             })); | ||||
|             GLib.Source.set_name_by_id(id, '[gnome-shell] SystemBackground.loaded'); | ||||
|         } else { | ||||
|             let id = image.connect('loaded', | ||||
|                                    Lang.bind(this, function() { | ||||
|                                        this.emit('loaded'); | ||||
|                                        image.disconnect(id); | ||||
|                                        image = null; | ||||
|                                    })); | ||||
|         } | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(SystemBackground.prototype); | ||||
|  | ||||
| const BackgroundSource = new Lang.Class({ | ||||
|     Name: 'BackgroundSource', | ||||
|  | ||||
|     _init: function(layoutManager, settingsSchema) { | ||||
|         // Allow override the background image setting for performance testing | ||||
|         this._layoutManager = layoutManager; | ||||
|         this._overrideImage = GLib.getenv('SHELL_BACKGROUND_IMAGE'); | ||||
|         this._settings = new Gio.Settings({ schema_id: settingsSchema }); | ||||
|         this._backgrounds = []; | ||||
|  | ||||
|         this._monitorsChangedId = global.screen.connect('monitors-changed', | ||||
|                                                         Lang.bind(this, this._onMonitorsChanged)); | ||||
|     }, | ||||
|  | ||||
|     _onMonitorsChanged: function() { | ||||
|         for (let monitorIndex in this._backgrounds) { | ||||
|             let background = this._backgrounds[monitorIndex]; | ||||
|  | ||||
|             if (monitorIndex < this._layoutManager.monitors.length) { | ||||
|                 background.updateResolution(); | ||||
|             } else { | ||||
|                 background.disconnect(background._changedId); | ||||
|                 background.destroy(); | ||||
|                 delete this._backgrounds[monitorIndex]; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     getBackground: function(monitorIndex) { | ||||
|         let filename = null; | ||||
|         let style; | ||||
|  | ||||
|         if (this._overrideImage != null) { | ||||
|             filename = this._overrideImage; | ||||
|             style = GDesktopEnums.BackgroundStyle.ZOOM; // Hardcode | ||||
|         } else { | ||||
|             style = this._settings.get_enum(BACKGROUND_STYLE_KEY); | ||||
|             if (style != GDesktopEnums.BackgroundStyle.NONE) { | ||||
|                 let uri = this._settings.get_string(PICTURE_URI_KEY); | ||||
|                 if (GLib.uri_parse_scheme(uri) != null) | ||||
|                     filename = Gio.File.new_for_uri(uri).get_path(); | ||||
|                 else | ||||
|                     filename = uri; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Animated backgrounds are (potentially) per-monitor, since | ||||
|         // they can have variants that depend on the aspect ratio and | ||||
|         // size of the monitor; for other backgrounds we can use the | ||||
|         // same background object for all monitors. | ||||
|         if (filename == null || !filename.endsWith('.xml')) | ||||
|             monitorIndex = 0; | ||||
|  | ||||
|         if (!(monitorIndex in this._backgrounds)) { | ||||
|             let background = new Background({ | ||||
|                 monitorIndex: monitorIndex, | ||||
|                 layoutManager: this._layoutManager, | ||||
|                 settings: this._settings, | ||||
|                 filename: filename, | ||||
|                 style: style | ||||
|             }); | ||||
|  | ||||
|             background._changedId = background.connect('changed', Lang.bind(this, function() { | ||||
|                 background.disconnect(background._changedId); | ||||
|                 background.destroy(); | ||||
|                 delete this._backgrounds[monitorIndex]; | ||||
|             })); | ||||
|  | ||||
|             this._backgrounds[monitorIndex] = background; | ||||
|         } | ||||
|  | ||||
|         return this._backgrounds[monitorIndex]; | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         global.screen.disconnect(this._monitorsChangedId); | ||||
|  | ||||
|         for (let monitorIndex in this._backgrounds) { | ||||
|             let background = this._backgrounds[monitorIndex]; | ||||
|             background.disconnect(background._changedId); | ||||
|             background.destroy(); | ||||
|         } | ||||
|  | ||||
|         this._backgrounds = null; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const Animation = new Lang.Class({ | ||||
|     Name: 'Animation', | ||||
|  | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { filename: null }); | ||||
|  | ||||
|         this.filename = params.filename; | ||||
|         this.keyFrameFiles = []; | ||||
|         this.transitionProgress = 0.0; | ||||
|         this.transitionDuration = 0.0; | ||||
|         this.loaded = false; | ||||
|     }, | ||||
|  | ||||
|     load: function(callback) { | ||||
|         let file = Gio.File.new_for_path(this.filename); | ||||
|  | ||||
|         this._show = new GnomeDesktop.BGSlideShow({ filename: this.filename }); | ||||
|  | ||||
|         this._show.load_async(null, | ||||
|                               Lang.bind(this, | ||||
|                                         function(object, result) { | ||||
|                                             this.loaded = true; | ||||
|                                             if (callback) | ||||
|                                                 callback(); | ||||
|                                         })); | ||||
|     }, | ||||
|  | ||||
|     update: function(monitor) { | ||||
|         this.keyFrameFiles = []; | ||||
|  | ||||
|         if (!this._show) | ||||
|             return; | ||||
|  | ||||
|         if (this._show.get_num_slides() < 1) | ||||
|             return; | ||||
|  | ||||
|         let [progress, duration, isFixed, file1, file2] = this._show.get_current_slide(monitor.width, monitor.height); | ||||
|  | ||||
|         this.transitionDuration = duration; | ||||
|         this.transitionProgress = progress; | ||||
|  | ||||
|         if (file1) | ||||
|             this.keyFrameFiles.push(file1); | ||||
|  | ||||
|         if (file2) | ||||
|             this.keyFrameFiles.push(file2); | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(Animation.prototype); | ||||
|  | ||||
| const BackgroundManager = new Lang.Class({ | ||||
|     Name: 'BackgroundManager', | ||||
|  | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { container: null, | ||||
|                                         layoutManager: Main.layoutManager, | ||||
|                                         monitorIndex: null, | ||||
|                                         vignette: false, | ||||
|                                         controlPosition: true, | ||||
|                                         settingsSchema: BACKGROUND_SCHEMA }); | ||||
|  | ||||
|         let cache = getBackgroundCache(); | ||||
|         this._settingsSchema = params.settingsSchema; | ||||
|         this._backgroundSource = cache.getBackgroundSource(params.layoutManager, params.settingsSchema); | ||||
|  | ||||
|         this._container = params.container; | ||||
|         this._layoutManager = params.layoutManager; | ||||
|         this._vignette = params.vignette; | ||||
|         this._monitorIndex = params.monitorIndex; | ||||
|         this._controlPosition = params.controlPosition; | ||||
|  | ||||
|         this.backgroundActor = this._createBackgroundActor(); | ||||
|         this._newBackgroundActor = null; | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         let cache = getBackgroundCache(); | ||||
|         cache.releaseBackgroundSource(this._settingsSchema); | ||||
|         this._backgroundSource = null; | ||||
|  | ||||
|         if (this._newBackgroundActor) { | ||||
|             this._newBackgroundActor.destroy(); | ||||
|             this._newBackgroundActor = null; | ||||
|         } | ||||
|  | ||||
|         if (this.backgroundActor) { | ||||
|             this.backgroundActor.destroy(); | ||||
|             this.backgroundActor = null; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _swapBackgroundActor: function() { | ||||
|         let oldBackgroundActor = this.backgroundActor; | ||||
|         this.backgroundActor = this._newBackgroundActor; | ||||
|         this._newBackgroundActor = null; | ||||
|         this.emit('changed'); | ||||
|  | ||||
|         Tweener.addTween(oldBackgroundActor, | ||||
|                          { opacity: 0, | ||||
|                            time: FADE_ANIMATION_TIME, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            onComplete: function() { | ||||
|                                oldBackgroundActor.destroy(); | ||||
|                            } | ||||
|                          }); | ||||
|     }, | ||||
|  | ||||
|     _updateBackgroundActor: function() { | ||||
|         if (this._newBackgroundActor) { | ||||
|             /* Skip displaying existing background queued for load */ | ||||
|             this._newBackgroundActor.destroy(); | ||||
|             this._newBackgroundActor = null; | ||||
|         } | ||||
|  | ||||
|         let newBackgroundActor = this._createBackgroundActor(); | ||||
|         newBackgroundActor.vignette_sharpness = this.backgroundActor.vignette_sharpness; | ||||
|         newBackgroundActor.brightness = this.backgroundActor.brightness; | ||||
|         newBackgroundActor.visible = this.backgroundActor.visible; | ||||
|  | ||||
|         this._newBackgroundActor = newBackgroundActor; | ||||
|  | ||||
|         let background = newBackgroundActor.background._delegate; | ||||
|  | ||||
|         if (background.isLoaded) { | ||||
|             this._swapBackgroundActor(); | ||||
|         } else { | ||||
|             newBackgroundActor.loadedSignalId = background.connect('loaded', | ||||
|                 Lang.bind(this, function() { | ||||
|                     background.disconnect(newBackgroundActor.loadedSignalId); | ||||
|                     newBackgroundActor.loadedSignalId = 0; | ||||
|  | ||||
|                     this._swapBackgroundActor(); | ||||
|  | ||||
|                 })); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _createBackgroundActor: function() { | ||||
|         let background = this._backgroundSource.getBackground(this._monitorIndex); | ||||
|         let backgroundActor = new Meta.BackgroundActor({ meta_screen: global.screen, | ||||
|                                                          monitor: this._monitorIndex, | ||||
|                                                          background: background.background, | ||||
|                                                          vignette: this._vignette, | ||||
|                                                          vignette_sharpness: 0.5, | ||||
|                                                          brightness: 0.5, | ||||
|                                                        }); | ||||
|  | ||||
|         this._container.add_child(backgroundActor); | ||||
|  | ||||
|         let monitor = this._layoutManager.monitors[this._monitorIndex]; | ||||
|  | ||||
|         backgroundActor.set_size(monitor.width, monitor.height); | ||||
|         if (this._controlPosition) { | ||||
|             backgroundActor.set_position(monitor.x, monitor.y); | ||||
|             backgroundActor.lower_bottom(); | ||||
|         } | ||||
|  | ||||
|         let changeSignalId = background.connect('changed', Lang.bind(this, function() { | ||||
|             background.disconnect(changeSignalId); | ||||
|             changeSignalId = null; | ||||
|             this._updateBackgroundActor(); | ||||
|         })); | ||||
|  | ||||
|         backgroundActor.connect('destroy', Lang.bind(this, function() { | ||||
|             if (changeSignalId) | ||||
|                 background.disconnect(changeSignalId); | ||||
|  | ||||
|             if (backgroundActor.loadedSignalId) | ||||
|                 background.disconnect(backgroundActor.loadedSignalId); | ||||
|         })); | ||||
|  | ||||
|         return backgroundActor; | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(BackgroundManager.prototype); | ||||
| @@ -1,72 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const BoxPointer = imports.ui.boxpointer; | ||||
| const Main = imports.ui.main; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const BackgroundMenu = new Lang.Class({ | ||||
|     Name: 'BackgroundMenu', | ||||
|     Extends: PopupMenu.PopupMenu, | ||||
|  | ||||
|     _init: function(layoutManager) { | ||||
|         this.parent(layoutManager.dummyCursor, 0, St.Side.TOP); | ||||
|  | ||||
|         this.addSettingsAction(_("Settings"), 'gnome-control-center.desktop'); | ||||
|         this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|         this.addSettingsAction(_("Change Background…"), 'gnome-background-panel.desktop'); | ||||
|  | ||||
|         this.actor.add_style_class_name('background-menu'); | ||||
|  | ||||
|         layoutManager.uiGroup.add_actor(this.actor); | ||||
|         this.actor.hide(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| function addBackgroundMenu(actor, layoutManager) { | ||||
|     actor.reactive = true; | ||||
|     actor._backgroundMenu = new BackgroundMenu(layoutManager); | ||||
|     actor._backgroundManager = new PopupMenu.PopupMenuManager({ actor: actor }); | ||||
|     actor._backgroundManager.addMenu(actor._backgroundMenu); | ||||
|  | ||||
|     function openMenu(x, y) { | ||||
|         Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0); | ||||
|         actor._backgroundMenu.open(BoxPointer.PopupAnimation.NONE); | ||||
|     } | ||||
|  | ||||
|     let clickAction = new Clutter.ClickAction(); | ||||
|     clickAction.connect('long-press', function(action, actor, state) { | ||||
|         if (state == Clutter.LongPressState.QUERY) | ||||
|             return ((action.get_button() == 0 || | ||||
|                      action.get_button() == 1) && | ||||
|                     !actor._backgroundMenu.isOpen); | ||||
|         if (state == Clutter.LongPressState.ACTIVATE) { | ||||
|             let [x, y] = action.get_coords(); | ||||
|             openMenu(x, y); | ||||
|             actor._backgroundManager.ignoreRelease(); | ||||
|         } | ||||
|         return true; | ||||
|     }); | ||||
|     clickAction.connect('clicked', function(action) { | ||||
|         if (action.get_button() == 3) { | ||||
|             let [x, y] = action.get_coords(); | ||||
|             openMenu(x, y); | ||||
|         } | ||||
|     }); | ||||
|     actor.add_action(clickAction); | ||||
|  | ||||
|     let grabOpBeginId = global.display.connect('grab-op-begin', function () { | ||||
|         clickAction.release(); | ||||
|     }); | ||||
|  | ||||
|     actor.connect('destroy', function() { | ||||
|         actor._backgroundMenu.destroy(); | ||||
|         actor._backgroundMenu = null; | ||||
|         actor._backgroundManager = null; | ||||
|         global.display.disconnect(grabOpBeginId); | ||||
|     }); | ||||
| } | ||||
| @@ -3,9 +3,8 @@ | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
| const Tweener = imports.ui.tweener; | ||||
| @@ -39,7 +38,6 @@ const BoxPointer = new Lang.Class({ | ||||
|         this._arrowSide = arrowSide; | ||||
|         this._userArrowSide = arrowSide; | ||||
|         this._arrowOrigin = 0; | ||||
|         this._arrowActor = null; | ||||
|         this.actor = new St.Bin({ x_fill: true, | ||||
|                                   y_fill: true }); | ||||
|         this._container = new Shell.GenericContainer(); | ||||
| @@ -62,14 +60,10 @@ const BoxPointer = new Lang.Class({ | ||||
|         this._muteInput(); | ||||
|     }, | ||||
|  | ||||
|     get arrowSide() { | ||||
|         return this._arrowSide; | ||||
|     }, | ||||
|  | ||||
|     _muteInput: function() { | ||||
|         if (this._capturedEventId == 0) | ||||
|             this._capturedEventId = this.actor.connect('captured-event', | ||||
|                                                        function() { return Clutter.EVENT_STOP; }); | ||||
|                                                        function() { return true; }); | ||||
|     }, | ||||
|  | ||||
|     _unmuteInput: function() { | ||||
| @@ -121,9 +115,6 @@ const BoxPointer = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     hide: function(animate, onComplete) { | ||||
|         if (!this.actor.visible) | ||||
|             return; | ||||
|  | ||||
|         let xOffset = 0; | ||||
|         let yOffset = 0; | ||||
|         let themeNode = this.actor.get_theme_node(); | ||||
| @@ -188,9 +179,7 @@ const BoxPointer = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _getPreferredHeight: function(actor, forWidth, alloc) { | ||||
|         let themeNode = this.actor.get_theme_node(); | ||||
|         let borderWidth = themeNode.get_length('-arrow-border-width'); | ||||
|         let [minSize, naturalSize] = this.bin.get_preferred_height(forWidth - 2 * borderWidth); | ||||
|         let [minSize, naturalSize] = this.bin.get_preferred_height(forWidth); | ||||
|         alloc.min_size = minSize; | ||||
|         alloc.natural_size = naturalSize; | ||||
|         this._adjustAllocationForArrow(false, alloc); | ||||
| @@ -231,27 +220,31 @@ const BoxPointer = new Lang.Class({ | ||||
|         this.bin.allocate(childBox, flags); | ||||
|  | ||||
|         if (this._sourceActor && this._sourceActor.mapped) { | ||||
|             this._reposition(); | ||||
|             this._updateFlip(); | ||||
|             this._reposition(this._sourceActor, this._arrowAlignment); | ||||
|  | ||||
|             if (this._shouldFlip()) { | ||||
|                 switch (this._arrowSide) { | ||||
|                 case St.Side.TOP: | ||||
|                     this._arrowSide = St.Side.BOTTOM; | ||||
|                     break; | ||||
|                 case St.Side.BOTTOM: | ||||
|                     this._arrowSide = St.Side.TOP; | ||||
|                     break; | ||||
|                 case St.Side.LEFT: | ||||
|                     this._arrowSide = St.Side.RIGHT; | ||||
|                     break; | ||||
|                 case St.Side.RIGHT: | ||||
|                     this._arrowSide = St.Side.LEFT; | ||||
|                     break; | ||||
|                 } | ||||
|                 this._reposition(this._sourceActor, this._arrowAlignment); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _drawBorder: function(area) { | ||||
|         let themeNode = this.actor.get_theme_node(); | ||||
|  | ||||
|         if (this._arrowActor) { | ||||
|             let [sourceX, sourceY] = this._arrowActor.get_transformed_position(); | ||||
|             let [sourceWidth, sourceHeight] = this._arrowActor.get_transformed_size(); | ||||
|             let [absX, absY] = this.actor.get_transformed_position(); | ||||
|  | ||||
|             if (this._arrowSide == St.Side.TOP || | ||||
|                 this._arrowSide == St.Side.BOTTOM) { | ||||
|                 this._arrowOrigin = sourceX - absX + sourceWidth / 2; | ||||
|             } else { | ||||
|                 this._arrowOrigin = sourceY - absY + sourceHeight / 2; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let borderWidth = themeNode.get_length('-arrow-border-width'); | ||||
|         let base = themeNode.get_length('-arrow-base'); | ||||
|         let rise = themeNode.get_length('-arrow-rise'); | ||||
| @@ -287,40 +280,38 @@ const BoxPointer = new Lang.Class({ | ||||
|         let skipBottomLeft = false; | ||||
|         let skipBottomRight = false; | ||||
|  | ||||
|         if (rise) { | ||||
|             switch (this._arrowSide) { | ||||
|             case St.Side.TOP: | ||||
|                 if (this._arrowOrigin == x1) | ||||
|                     skipTopLeft = true; | ||||
|                 else if (this._arrowOrigin == x2) | ||||
|                     skipTopRight = true; | ||||
|                 break; | ||||
|         switch (this._arrowSide) { | ||||
|         case St.Side.TOP: | ||||
|             if (this._arrowOrigin == x1) | ||||
|                 skipTopLeft = true; | ||||
|             else if (this._arrowOrigin == x2) | ||||
|                 skipTopRight = true; | ||||
|             break; | ||||
|  | ||||
|             case St.Side.RIGHT: | ||||
|                 if (this._arrowOrigin == y1) | ||||
|                     skipTopRight = true; | ||||
|                 else if (this._arrowOrigin == y2) | ||||
|                     skipBottomRight = true; | ||||
|                 break; | ||||
|         case St.Side.RIGHT: | ||||
|             if (this._arrowOrigin == y1) | ||||
|                 skipTopRight = true; | ||||
|             else if (this._arrowOrigin == y2) | ||||
|                 skipBottomRight = true; | ||||
|             break; | ||||
|  | ||||
|             case St.Side.BOTTOM: | ||||
|                 if (this._arrowOrigin == x1) | ||||
|                     skipBottomLeft = true; | ||||
|                 else if (this._arrowOrigin == x2) | ||||
|                     skipBottomRight = true; | ||||
|                 break; | ||||
|         case St.Side.BOTTOM: | ||||
|             if (this._arrowOrigin == x1) | ||||
|                 skipBottomLeft = true; | ||||
|             else if (this._arrowOrigin == x2) | ||||
|                 skipBottomRight = true; | ||||
|             break; | ||||
|  | ||||
|             case St.Side.LEFT: | ||||
|                 if (this._arrowOrigin == y1) | ||||
|                     skipTopLeft = true; | ||||
|                 else if (this._arrowOrigin == y2) | ||||
|                     skipBottomLeft = true; | ||||
|                 break; | ||||
|             } | ||||
|         case St.Side.LEFT: | ||||
|             if (this._arrowOrigin == y1) | ||||
|                 skipTopLeft = true; | ||||
|             else if (this._arrowOrigin == y2) | ||||
|                 skipBottomLeft = true; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         cr.moveTo(x1 + borderRadius, y1); | ||||
|         if (this._arrowSide == St.Side.TOP && rise) { | ||||
|         if (this._arrowSide == St.Side.TOP) { | ||||
|             if (skipTopLeft) { | ||||
|                 cr.moveTo(x1, y2 - borderRadius); | ||||
|                 cr.lineTo(x1, y1 - rise); | ||||
| @@ -342,7 +333,7 @@ const BoxPointer = new Lang.Class({ | ||||
|                    3*Math.PI/2, Math.PI*2); | ||||
|         } | ||||
|  | ||||
|         if (this._arrowSide == St.Side.RIGHT && rise) { | ||||
|         if (this._arrowSide == St.Side.RIGHT) { | ||||
|             if (skipTopRight) { | ||||
|                 cr.lineTo(x2 + rise, y1); | ||||
|                 cr.lineTo(x2 + rise, y1 + halfBase); | ||||
| @@ -363,7 +354,7 @@ const BoxPointer = new Lang.Class({ | ||||
|                    0, Math.PI/2); | ||||
|         } | ||||
|  | ||||
|         if (this._arrowSide == St.Side.BOTTOM && rise) { | ||||
|         if (this._arrowSide == St.Side.BOTTOM) { | ||||
|             if (skipBottomLeft) { | ||||
|                 cr.lineTo(x1 + halfBase, y2); | ||||
|                 cr.lineTo(x1, y2 + rise); | ||||
| @@ -384,7 +375,7 @@ const BoxPointer = new Lang.Class({ | ||||
|                    Math.PI/2, Math.PI); | ||||
|         } | ||||
|  | ||||
|         if (this._arrowSide == St.Side.LEFT && rise) { | ||||
|         if (this._arrowSide == St.Side.LEFT) { | ||||
|             if (skipTopLeft) { | ||||
|                 cr.lineTo(x1, y1 + halfBase); | ||||
|                 cr.lineTo(x1 - rise, y1); | ||||
| @@ -414,11 +405,11 @@ const BoxPointer = new Lang.Class({ | ||||
|             cr.setLineWidth(borderWidth); | ||||
|             cr.stroke(); | ||||
|         } | ||||
|  | ||||
|         cr.$dispose(); | ||||
|     }, | ||||
|  | ||||
|     setPosition: function(sourceActor, alignment) { | ||||
|         this._arrowSide = this._userArrowSide; | ||||
|  | ||||
|         // We need to show it now to force an allocation, | ||||
|         // so that we can query the correct size. | ||||
|         this.actor.show(); | ||||
| @@ -426,8 +417,7 @@ const BoxPointer = new Lang.Class({ | ||||
|         this._sourceActor = sourceActor; | ||||
|         this._arrowAlignment = alignment; | ||||
|  | ||||
|         this._reposition(); | ||||
|         this._updateFlip(); | ||||
|         this._reposition(sourceActor, alignment); | ||||
|     }, | ||||
|  | ||||
|     setSourceAlignment: function(alignment) { | ||||
| @@ -439,10 +429,7 @@ const BoxPointer = new Lang.Class({ | ||||
|         this.setPosition(this._sourceActor, this._arrowAlignment); | ||||
|     }, | ||||
|  | ||||
|     _reposition: function() { | ||||
|         let sourceActor = this._sourceActor; | ||||
|         let alignment = this._arrowAlignment; | ||||
|  | ||||
|     _reposition: function(sourceActor, alignment) { | ||||
|         // Position correctly relative to the sourceActor | ||||
|         let sourceNode = sourceActor.get_theme_node(); | ||||
|         let sourceContentBox = sourceNode.get_content_box(sourceActor.get_allocation_box()); | ||||
| @@ -563,20 +550,10 @@ const BoxPointer = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     // @actor: an actor relative to which the arrow is positioned. | ||||
|     // Differently from setPosition, this will not move the boxpointer itself, | ||||
|     // on the arrow | ||||
|     setArrowActor: function(actor) { | ||||
|         if (this._arrowActor != actor) { | ||||
|             this._arrowActor = actor; | ||||
|             this._border.queue_repaint(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _shiftActor : function() { | ||||
|         // Since the position of the BoxPointer depends on the allocated size | ||||
|         // of the BoxPointer and the position of the source actor, trying | ||||
|         // to position the BoxPointer via the x/y properties will result in | ||||
|         // to position the BoxPoiner via the x/y properties will result in | ||||
|         // allocation loops and warnings. Instead we do the positioning via | ||||
|         // the anchor point, which is independent of allocation, and leave | ||||
|         // x == y == 0. | ||||
| @@ -584,49 +561,37 @@ const BoxPointer = new Lang.Class({ | ||||
|                                     -(this._yPosition + this._yOffset)); | ||||
|     }, | ||||
|  | ||||
|     _calculateArrowSide: function(arrowSide) { | ||||
|     _shouldFlip: function() { | ||||
|         let sourceAllocation = Shell.util_get_transformed_allocation(this._sourceActor); | ||||
|         let [minWidth, minHeight, boxWidth, boxHeight] = this._container.get_preferred_size(); | ||||
|         let boxAllocation = Shell.util_get_transformed_allocation(this.actor); | ||||
|         let boxWidth = boxAllocation.x2 - boxAllocation.x1; | ||||
|         let boxHeight = boxAllocation.y2 - boxAllocation.y1; | ||||
|         let monitor = Main.layoutManager.findMonitorForActor(this.actor); | ||||
|  | ||||
|         switch (arrowSide) { | ||||
|         switch (this._arrowSide) { | ||||
|         case St.Side.TOP: | ||||
|             if (sourceAllocation.y2 + boxHeight > monitor.y + monitor.height && | ||||
|             if (boxAllocation.y2 > monitor.y + monitor.height && | ||||
|                 boxHeight < sourceAllocation.y1 - monitor.y) | ||||
|                 return St.Side.BOTTOM; | ||||
|                 return true; | ||||
|             break; | ||||
|         case St.Side.BOTTOM: | ||||
|             if (sourceAllocation.y1 - boxHeight < monitor.y && | ||||
|             if (boxAllocation.y1 < monitor.y && | ||||
|                 boxHeight < monitor.y + monitor.height - sourceAllocation.y2) | ||||
|                 return St.Side.TOP; | ||||
|                 return true; | ||||
|             break; | ||||
|         case St.Side.LEFT: | ||||
|             if (sourceAllocation.x2 + boxWidth > monitor.x + monitor.width && | ||||
|             if (boxAllocation.x2 > monitor.x + monitor.width && | ||||
|                 boxWidth < sourceAllocation.x1 - monitor.x) | ||||
|                 return St.Side.RIGHT; | ||||
|                 return true; | ||||
|             break; | ||||
|         case St.Side.RIGHT: | ||||
|             if (sourceAllocation.x1 - boxWidth < monitor.x && | ||||
|             if (boxAllocation.x1 < monitor.x && | ||||
|                 boxWidth < monitor.x + monitor.width - sourceAllocation.x2) | ||||
|                 return St.Side.LEFT; | ||||
|                 return true; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         return arrowSide; | ||||
|     }, | ||||
|  | ||||
|     _updateFlip: function() { | ||||
|         let arrowSide = this._calculateArrowSide(this._userArrowSide); | ||||
|         if (this._arrowSide != arrowSide) { | ||||
|             this._arrowSide = arrowSide; | ||||
|             this._reposition(); | ||||
|             Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { | ||||
|                 this._container.queue_relayout(); | ||||
|                 return false; | ||||
|             })); | ||||
|  | ||||
|             this.emit('arrow-side-changed'); | ||||
|         } | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     set xOffset(offset) { | ||||
| @@ -653,21 +618,5 @@ const BoxPointer = new Lang.Class({ | ||||
|  | ||||
|     get opacity() { | ||||
|         return this.actor.opacity; | ||||
|     }, | ||||
|  | ||||
|     updateArrowSide: function(side) { | ||||
|         this._arrowSide = side; | ||||
|         this._border.queue_repaint(); | ||||
|  | ||||
|         this.emit('arrow-side-changed'); | ||||
|     }, | ||||
|  | ||||
|     getPadding: function(side) { | ||||
|         return this.bin.get_theme_node().get_padding(side); | ||||
|     }, | ||||
|  | ||||
|     getArrowHeight: function() { | ||||
|         return this.actor.get_theme_node().get_length('-arrow-rise'); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(BoxPointer.prototype); | ||||
|   | ||||
| @@ -2,8 +2,6 @@ | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const St = imports.gi.St; | ||||
| const Signals = imports.signals; | ||||
| @@ -14,26 +12,20 @@ const Shell = imports.gi.Shell; | ||||
|  | ||||
| const MSECS_IN_DAY = 24 * 60 * 60 * 1000; | ||||
| const SHOW_WEEKDATE_KEY = 'show-weekdate'; | ||||
| const ELLIPSIS_CHAR = '\u2026'; | ||||
|  | ||||
| // alias to prevent xgettext from picking up strings translated in GTK+ | ||||
| const gtk30_ = Gettext_gtk30.gettext; | ||||
|  | ||||
| // in org.gnome.desktop.interface | ||||
| const CLOCK_FORMAT_KEY        = 'clock-format'; | ||||
|  | ||||
| function _sameDay(dateA, dateB) { | ||||
|     return (dateA.getDate() == dateB.getDate() && | ||||
|             dateA.getMonth() == dateB.getMonth() && | ||||
|             dateA.getYear() == dateB.getYear()); | ||||
| } | ||||
|  | ||||
| function _sameYear(dateA, dateB) { | ||||
|     return (dateA.getYear() == dateB.getYear()); | ||||
| } | ||||
|  | ||||
| function _sameMonth(dateA, dateB) { | ||||
|     return _sameYear(dateA, dateB) && (dateA.getMonth() == dateB.getMonth()); | ||||
| } | ||||
|  | ||||
| function _sameDay(dateA, dateB) { | ||||
|     return _sameMonth(dateA, dateB) && (dateA.getDate() == dateB.getDate()); | ||||
| } | ||||
|  | ||||
| /* TODO: maybe needs config - right now we assume that Saturday and | ||||
|  * Sunday are non-work days (not true in e.g. Israel, it's Sunday and | ||||
|  * Monday there) | ||||
| @@ -60,36 +52,48 @@ function _getEndOfDay(date) { | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| function _formatEventTime(event, clockFormat, periodBegin, periodEnd) { | ||||
| function _formatEventTime(event, clockFormat) { | ||||
|     let ret; | ||||
|     let allDay = (event.allDay || (event.date <= periodBegin && event.end >= periodEnd)); | ||||
|     if (allDay) { | ||||
|     if (event.allDay) { | ||||
|         /* Translators: Shown in calendar event list for all day events | ||||
|          * Keep it short, best if you can use less then 10 characters | ||||
|          */ | ||||
|         ret = C_("event list time", "All Day"); | ||||
|     } else { | ||||
|         let date = event.date >= periodBegin ? event.date : event.end; | ||||
|         switch (clockFormat) { | ||||
|         case '24h': | ||||
|             /* Translators: Shown in calendar event list, if 24h format, | ||||
|                \u2236 is a ratio character, similar to : */ | ||||
|             ret = date.toLocaleFormat(C_("event list time", "%H\u2236%M")); | ||||
|             /* Translators: Shown in calendar event list, if 24h format */ | ||||
|             ret = event.date.toLocaleFormat(C_("event list time", "%H:%M")); | ||||
|             break; | ||||
|  | ||||
|         default: | ||||
|             /* explicit fall-through */ | ||||
|         case '12h': | ||||
|             /* Translators: Shown in calendar event list, if 12h format, | ||||
|                \u2236 is a ratio character, similar to : and \u2009 is | ||||
|                a thin space */ | ||||
|             ret = date.toLocaleFormat(C_("event list time", "%l\u2236%M\u2009%p")); | ||||
|             /* Transators: Shown in calendar event list, if 12h format */ | ||||
|             ret = event.date.toLocaleFormat(C_("event list time", "%l:%M %p")); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| function _getCalendarWeekForDate(date) { | ||||
|     // Based on the algorithms found here: | ||||
|     // http://en.wikipedia.org/wiki/Talk:ISO_week_date | ||||
|     let midnightDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()); | ||||
|     // Need to get Monday to be 1 ... Sunday to be 7 | ||||
|     let dayOfWeek = 1 + ((midnightDate.getDay() + 6) % 7); | ||||
|     let nearestThursday = new Date(midnightDate.getFullYear(), midnightDate.getMonth(), | ||||
|                                    midnightDate.getDate() + (4 - dayOfWeek)); | ||||
|  | ||||
|     let jan1st = new Date(nearestThursday.getFullYear(), 0, 1); | ||||
|     let diffDate = nearestThursday - jan1st; | ||||
|     let dayNumber = Math.floor(Math.abs(diffDate) / MSECS_IN_DAY); | ||||
|     let weekNumber = Math.floor(dayNumber / 7) + 1; | ||||
|  | ||||
|     return weekNumber; | ||||
| } | ||||
|  | ||||
| function _getCalendarDayAbbreviation(dayNumber) { | ||||
|     let abbreviations = [ | ||||
|         /* Translators: Calendar grid abbreviation for Sunday. | ||||
| @@ -160,12 +164,6 @@ const EmptyEventSource = new Lang.Class({ | ||||
|     Name: 'EmptyEventSource', | ||||
|  | ||||
|     _init: function() { | ||||
|         this.isLoading = false; | ||||
|         this.isDummy = true; | ||||
|         this.hasCalendars = false; | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|     }, | ||||
|  | ||||
|     requestRange: function(begin, end) { | ||||
| @@ -182,27 +180,28 @@ const EmptyEventSource = new Lang.Class({ | ||||
| }); | ||||
| Signals.addSignalMethods(EmptyEventSource.prototype); | ||||
|  | ||||
| const CalendarServerIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.CalendarServer"> \ | ||||
| <method name="GetEvents"> \ | ||||
|     <arg type="x" direction="in" /> \ | ||||
|     <arg type="x" direction="in" /> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
|     <arg type="a(sssbxxa{sv})" direction="out" /> \ | ||||
| </method> \ | ||||
| <property name="HasCalendars" type="b" access="read" /> \ | ||||
| <signal name="Changed" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const CalendarServerIface = <interface name="org.gnome.Shell.CalendarServer"> | ||||
| <method name="GetEvents"> | ||||
|     <arg type="x" direction="in" /> | ||||
|     <arg type="x" direction="in" /> | ||||
|     <arg type="b" direction="in" /> | ||||
|     <arg type="a(sssbxxa{sv})" direction="out" /> | ||||
| </method> | ||||
| <signal name="Changed" /> | ||||
| </interface>; | ||||
|  | ||||
| const CalendarServerInfo  = Gio.DBusInterfaceInfo.new_for_xml(CalendarServerIface); | ||||
|  | ||||
| function CalendarServer() { | ||||
|     return new Gio.DBusProxy({ g_connection: Gio.DBus.session, | ||||
|                                g_interface_name: CalendarServerInfo.name, | ||||
|                                g_interface_info: CalendarServerInfo, | ||||
|                                g_name: 'org.gnome.Shell.CalendarServer', | ||||
|                                g_object_path: '/org/gnome/Shell/CalendarServer' }); | ||||
|     var self = new Gio.DBusProxy({ g_connection: Gio.DBus.session, | ||||
|                                    g_interface_name: CalendarServerInfo.name, | ||||
|                                    g_interface_info: CalendarServerInfo, | ||||
|                                    g_name: 'org.gnome.Shell.CalendarServer', | ||||
|                                    g_object_path: '/org/gnome/Shell/CalendarServer', | ||||
|                                    g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES }); | ||||
|  | ||||
|     self.init(null); | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| function _datesEqual(a, b) { | ||||
| @@ -229,64 +228,18 @@ const DBusEventSource = new Lang.Class({ | ||||
|  | ||||
|     _init: function() { | ||||
|         this._resetCache(); | ||||
|         this.isLoading = false; | ||||
|         this.isDummy = false; | ||||
|  | ||||
|         this._initialized = false; | ||||
|         this._dbusProxy = new CalendarServer(); | ||||
|         this._dbusProxy.init_async(GLib.PRIORITY_DEFAULT, null, Lang.bind(this, function(object, result) { | ||||
|             let loaded = false; | ||||
|         this._dbusProxy.connectSignal('Changed', Lang.bind(this, this._onChanged)); | ||||
|  | ||||
|             try { | ||||
|                 this._dbusProxy.init_finish(result); | ||||
|                 loaded = true; | ||||
|             } catch(e) { | ||||
|                 if (e.matches(Gio.DBusError, Gio.DBusError.TIMED_OUT)) { | ||||
|                     // Ignore timeouts and install signals as normal, because with high | ||||
|                     // probability the service will appear later on, and we will get a | ||||
|                     // NameOwnerChanged which will finish loading | ||||
|                     // | ||||
|                     // (But still _initialized to false, because the proxy does not know | ||||
|                     // about the HasCalendars property and would cause an exception trying | ||||
|                     // to read it) | ||||
|                 } else { | ||||
|                     log('Error loading calendars: ' + e.message); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this._dbusProxy.connectSignal('Changed', Lang.bind(this, this._onChanged)); | ||||
|  | ||||
|             this._dbusProxy.connect('notify::g-name-owner', Lang.bind(this, function() { | ||||
|                 if (this._dbusProxy.g_name_owner) | ||||
|                     this._onNameAppeared(); | ||||
|                 else | ||||
|                     this._onNameVanished(); | ||||
|             })); | ||||
|  | ||||
|             this._dbusProxy.connect('g-properties-changed', Lang.bind(this, function() { | ||||
|                 this.emit('notify::has-calendars'); | ||||
|             })); | ||||
|  | ||||
|             this._initialized = loaded; | ||||
|             if (loaded) { | ||||
|                 this.emit('notify::has-calendars'); | ||||
|         this._dbusProxy.connect('notify::g-name-owner', Lang.bind(this, function() { | ||||
|             if (this._dbusProxy.g_name_owner) | ||||
|                 this._onNameAppeared(); | ||||
|             } | ||||
|             else | ||||
|                 this._onNameVanished(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         this._dbusProxy.run_dispose(); | ||||
|     }, | ||||
|  | ||||
|     get hasCalendars() { | ||||
|         if (this._initialized) | ||||
|             return this._dbusProxy.HasCalendars; | ||||
|         else | ||||
|             return false; | ||||
|     }, | ||||
|  | ||||
|     _resetCache: function() { | ||||
|         this._events = []; | ||||
|         this._lastRequestBegin = null; | ||||
| @@ -294,7 +247,6 @@ const DBusEventSource = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onNameAppeared: function(owner) { | ||||
|         this._initialized = true; | ||||
|         this._resetCache(); | ||||
|         this._loadEvents(true); | ||||
|     }, | ||||
| @@ -327,32 +279,29 @@ const DBusEventSource = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         this._events = newEvents; | ||||
|         this.isLoading = false; | ||||
|         this.emit('changed'); | ||||
|     }, | ||||
|  | ||||
|     _loadEvents: function(forceReload) { | ||||
|         // Ignore while loading | ||||
|         if (!this._initialized) | ||||
|             return; | ||||
|  | ||||
|         if (this._curRequestBegin && this._curRequestEnd){ | ||||
|             let callFlags = Gio.DBusCallFlags.NO_AUTO_START; | ||||
|             if (forceReload) | ||||
|                 callFlags = Gio.DBusCallFlags.NONE; | ||||
|             this._dbusProxy.GetEventsRemote(this._curRequestBegin.getTime() / 1000, | ||||
|                                             this._curRequestEnd.getTime() / 1000, | ||||
|                                             forceReload, | ||||
|                                             Lang.bind(this, this._onEventsReceived), | ||||
|                                             Gio.DBusCallFlags.NONE); | ||||
|                                             callFlags); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     requestRange: function(begin, end) { | ||||
|         if (!(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) { | ||||
|             this.isLoading = true; | ||||
|     requestRange: function(begin, end, forceReload) { | ||||
|         if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) { | ||||
|             this._lastRequestBegin = begin; | ||||
|             this._lastRequestEnd = end; | ||||
|             this._curRequestBegin = begin; | ||||
|             this._curRequestEnd = end; | ||||
|             this._loadEvents(false); | ||||
|             this._loadEvents(forceReload); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -364,12 +313,6 @@ const DBusEventSource = new Lang.Class({ | ||||
|                 result.push(event); | ||||
|             } | ||||
|         } | ||||
|         result.sort(function(event1, event2) { | ||||
|             // sort events by end time on ending day | ||||
|             let d1 = event1.date < begin && event1.end <= end ? event1.end : event1.date; | ||||
|             let d2 = event2.date < begin && event2.end <= end ? event2.end : event2.date; | ||||
|             return d1.getTime() - d2.getTime(); | ||||
|         }); | ||||
|         return result; | ||||
|     }, | ||||
|  | ||||
| @@ -392,14 +335,14 @@ const Calendar = new Lang.Class({ | ||||
|  | ||||
|     _init: function() { | ||||
|         this._weekStart = Shell.util_get_week_start(); | ||||
|         this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell.calendar' }); | ||||
|         this._settings = new Gio.Settings({ schema: 'org.gnome.shell.calendar' }); | ||||
|  | ||||
|         this._settings.connect('changed::' + SHOW_WEEKDATE_KEY, Lang.bind(this, this._onSettingsChange)); | ||||
|         this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY); | ||||
|  | ||||
|         // Find the ordering for month/year in the calendar heading | ||||
|         this._headerFormatWithoutYear = '%B'; | ||||
|         switch (gtk30_('calendar:MY')) { | ||||
|         switch (Gettext_gtk30.gettext('calendar:MY')) { | ||||
|         case 'calendar:MY': | ||||
|             this._headerFormat = '%B %Y'; | ||||
|             break; | ||||
| @@ -415,11 +358,9 @@ const Calendar = new Lang.Class({ | ||||
|         // Start off with the current date | ||||
|         this._selectedDate = new Date(); | ||||
|  | ||||
|         this._shouldDateGrabFocus = false; | ||||
|  | ||||
|         this.actor = new St.Widget({ style_class: 'calendar', | ||||
|                                      layout_manager: new Clutter.GridLayout(), | ||||
|                                      reactive: true }); | ||||
|         this.actor = new St.Table({ homogeneous: false, | ||||
|                                     style_class: 'calendar', | ||||
|                                     reactive: true }); | ||||
|  | ||||
|         this.actor.connect('scroll-event', | ||||
|                            Lang.bind(this, this._onScroll)); | ||||
| @@ -430,49 +371,52 @@ const Calendar = new Lang.Class({ | ||||
|     // @eventSource: is an object implementing the EventSource API, e.g. the | ||||
|     // requestRange(), getEvents(), hasEvents() methods and the ::changed signal. | ||||
|     setEventSource: function(eventSource) { | ||||
|         if (this._eventSource) { | ||||
|             this._eventSource.disconnect(this._eventSourceChangedId); | ||||
|             this._eventSource = null; | ||||
|         } | ||||
|  | ||||
|         this._eventSource = eventSource; | ||||
|         this._eventSource.connect('changed', Lang.bind(this, function() { | ||||
|             this._rebuildCalendar(); | ||||
|             this._update(); | ||||
|         })); | ||||
|         this._rebuildCalendar(); | ||||
|         this._update(); | ||||
|  | ||||
|         if (this._eventSource) { | ||||
|             this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, function() { | ||||
|                 this._update(false); | ||||
|             })); | ||||
|             this._update(true); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     // Sets the calendar to show a specific date | ||||
|     setDate: function(date) { | ||||
|         if (_sameDay(date, this._selectedDate)) | ||||
|             return; | ||||
|  | ||||
|         this._selectedDate = date; | ||||
|         this._update(); | ||||
|         this.emit('selected-date-changed', new Date(this._selectedDate)); | ||||
|     setDate: function(date, forceReload) { | ||||
|         if (!_sameDay(date, this._selectedDate)) { | ||||
|             this._selectedDate = date; | ||||
|             this._update(forceReload); | ||||
|             this.emit('selected-date-changed', new Date(this._selectedDate)); | ||||
|         } else { | ||||
|             if (forceReload) | ||||
|                 this._update(forceReload); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _buildHeader: function() { | ||||
|         let layout = this.actor.layout_manager; | ||||
|         let offsetCols = this._useWeekdate ? 1 : 0; | ||||
|         this.actor.destroy_all_children(); | ||||
|  | ||||
|         // Top line of the calendar '<| September 2009 |>' | ||||
|         this._topBox = new St.BoxLayout(); | ||||
|         layout.attach(this._topBox, 0, 0, offsetCols + 7, 1); | ||||
|         this.actor.add(this._topBox, | ||||
|                        { row: 0, col: 0, col_span: offsetCols + 7 }); | ||||
|  | ||||
|         this._backButton = new St.Button({ style_class: 'calendar-change-month-back', | ||||
|                                            accessible_name: _("Previous month"), | ||||
|                                            can_focus: true }); | ||||
|         this._topBox.add(this._backButton); | ||||
|         this._backButton.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked)); | ||||
|         let back = new St.Button({ style_class: 'calendar-change-month-back' }); | ||||
|         this._topBox.add(back); | ||||
|         back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked)); | ||||
|  | ||||
|         this._monthLabel = new St.Label({style_class: 'calendar-month-label', | ||||
|                                          can_focus: true }); | ||||
|         this._monthLabel = new St.Label({style_class: 'calendar-month-label'}); | ||||
|         this._topBox.add(this._monthLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE }); | ||||
|  | ||||
|         this._forwardButton = new St.Button({ style_class: 'calendar-change-month-forward', | ||||
|                                               accessible_name: _("Next month"), | ||||
|                                               can_focus: true }); | ||||
|         this._topBox.add(this._forwardButton); | ||||
|         this._forwardButton.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked)); | ||||
|         let forward = new St.Button({ style_class: 'calendar-change-month-forward' }); | ||||
|         this._topBox.add(forward); | ||||
|         forward.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked)); | ||||
|  | ||||
|         // Add weekday labels... | ||||
|         // | ||||
| @@ -488,12 +432,10 @@ const Calendar = new Lang.Class({ | ||||
|             let customDayAbbrev = _getCalendarDayAbbreviation(iter.getDay()); | ||||
|             let label = new St.Label({ style_class: 'calendar-day-base calendar-day-heading', | ||||
|                                        text: customDayAbbrev }); | ||||
|             let col; | ||||
|             if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) | ||||
|                 col = 6 - (7 + iter.getDay() - this._weekStart) % 7; | ||||
|             else | ||||
|                 col = offsetCols + (7 + iter.getDay() - this._weekStart) % 7; | ||||
|             layout.attach(label, col, 1, 1, 1); | ||||
|             this.actor.add(label, | ||||
|                            { row: 1, | ||||
|                              col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7, | ||||
|                              x_fill: false, x_align: St.Align.MIDDLE }); | ||||
|             iter.setTime(iter.getTime() + MSECS_IN_DAY); | ||||
|         } | ||||
|  | ||||
| @@ -512,7 +454,6 @@ const Calendar = new Lang.Class({ | ||||
|             this._onNextMonthButtonClicked(); | ||||
|             break; | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onPrevMonthButtonClicked: function() { | ||||
| @@ -534,12 +475,10 @@ const Calendar = new Lang.Class({ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._backButton.grab_key_focus(); | ||||
|         this.setDate(newDate, false); | ||||
|    }, | ||||
|  | ||||
|         this.setDate(newDate); | ||||
|     }, | ||||
|  | ||||
|     _onNextMonthButtonClicked: function() { | ||||
|    _onNextMonthButtonClicked: function() { | ||||
|         let newDate = new Date(this._selectedDate); | ||||
|         let oldMonth = newDate.getMonth(); | ||||
|         if (oldMonth == 11) { | ||||
| @@ -558,88 +497,57 @@ const Calendar = new Lang.Class({ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._forwardButton.grab_key_focus(); | ||||
|  | ||||
|         this.setDate(newDate); | ||||
|        this.setDate(newDate, false); | ||||
|     }, | ||||
|  | ||||
|     _onSettingsChange: function() { | ||||
|         this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY); | ||||
|         this._buildHeader(); | ||||
|         this._rebuildCalendar(); | ||||
|         this._update(); | ||||
|         this._update(false); | ||||
|     }, | ||||
|  | ||||
|     _rebuildCalendar: function() { | ||||
|     _update: function(forceReload) { | ||||
|         let now = new Date(); | ||||
|  | ||||
|         if (_sameYear(this._selectedDate, now)) | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormatWithoutYear); | ||||
|         else | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormat); | ||||
|  | ||||
|         // Remove everything but the topBox and the weekday labels | ||||
|         let children = this.actor.get_children(); | ||||
|         for (let i = this._firstDayIndex; i < children.length; i++) | ||||
|             children[i].destroy(); | ||||
|  | ||||
|         this._buttons = []; | ||||
|  | ||||
|         // Start at the beginning of the week before the start of the month | ||||
|         // | ||||
|         // We want to show always 6 weeks (to keep the calendar menu at the same | ||||
|         // height if there are no events), so we pad it according to the following | ||||
|         // policy: | ||||
|         // | ||||
|         // 1 - If a month has 6 weeks, we place no padding (example: Dec 2012) | ||||
|         // 2 - If a month has 5 weeks and it starts on week start, we pad one week | ||||
|         //     before it (example: Apr 2012) | ||||
|         // 3 - If a month has 5 weeks and it starts on any other day, we pad one week | ||||
|         //     after it (example: Nov 2012) | ||||
|         // 4 - If a month has 4 weeks, we pad one week before and one after it | ||||
|         //     (example: Feb 2010) | ||||
|         // | ||||
|         // Actually computing the number of weeks is complex, but we know that the | ||||
|         // problematic categories (2 and 4) always start on week start, and that | ||||
|         // all months at the end have 6 weeks. | ||||
|         let beginDate = new Date(this._selectedDate); | ||||
|         beginDate.setDate(1); | ||||
|         beginDate.setSeconds(0); | ||||
|         beginDate.setHours(12); | ||||
|  | ||||
|         this._calendarBegin = new Date(beginDate); | ||||
|         this._markedAsToday = now; | ||||
|  | ||||
|         let year = beginDate.getYear(); | ||||
|  | ||||
|         let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7; | ||||
|         let startsOnWeekStart = daysToWeekStart == 0; | ||||
|         let weekPadding = startsOnWeekStart ? 7 : 0; | ||||
|         beginDate.setTime(beginDate.getTime() - daysToWeekStart * MSECS_IN_DAY); | ||||
|  | ||||
|         beginDate.setTime(beginDate.getTime() - (weekPadding + daysToWeekStart) * MSECS_IN_DAY); | ||||
|  | ||||
|         let layout = this.actor.layout_manager; | ||||
|         let iter = new Date(beginDate); | ||||
|         let row = 2; | ||||
|         // nRows here means 6 weeks + one header + one navbar | ||||
|         let nRows = 8; | ||||
|         while (row < 8) { | ||||
|             let button = new St.Button({ label: iter.getDate().toString(), | ||||
|                                          can_focus: true }); | ||||
|         while (true) { | ||||
|             let button = new St.Button({ label: iter.getDate().toString() }); | ||||
|             let rtl = button.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|  | ||||
|             if (this._eventSource.isDummy) | ||||
|             if (!this._eventSource) | ||||
|                 button.reactive = false; | ||||
|  | ||||
|             button._date = new Date(iter); | ||||
|             let iterStr = iter.toUTCString(); | ||||
|             button.connect('clicked', Lang.bind(this, function() { | ||||
|                 this._shouldDateGrabFocus = true; | ||||
|                 this.setDate(button._date); | ||||
|                 this._shouldDateGrabFocus = false; | ||||
|                 let newlySelectedDate = new Date(iterStr); | ||||
|                 this.setDate(newlySelectedDate, false); | ||||
|             })); | ||||
|  | ||||
|             let hasEvents = this._eventSource.hasEvents(iter); | ||||
|             let hasEvents = this._eventSource && this._eventSource.hasEvents(iter); | ||||
|             let styleClass = 'calendar-day-base calendar-day'; | ||||
|  | ||||
|             if (_isWorkDay(iter)) | ||||
|                 styleClass += ' calendar-work-day'; | ||||
|                 styleClass += ' calendar-work-day' | ||||
|             else | ||||
|                 styleClass += ' calendar-nonwork-day'; | ||||
|                 styleClass += ' calendar-nonwork-day' | ||||
|  | ||||
|             // Hack used in lieu of border-collapse - see gnome-shell.css | ||||
|             if (row == 2) | ||||
| @@ -655,58 +563,37 @@ const Calendar = new Lang.Class({ | ||||
|             else if (iter.getMonth() != this._selectedDate.getMonth()) | ||||
|                 styleClass += ' calendar-other-month-day'; | ||||
|  | ||||
|             if (_sameDay(this._selectedDate, iter)) | ||||
|                 button.add_style_pseudo_class('active'); | ||||
|  | ||||
|             if (hasEvents) | ||||
|                 styleClass += ' calendar-day-with-events'; | ||||
|                 styleClass += ' calendar-day-with-events' | ||||
|  | ||||
|             button.style_class = styleClass; | ||||
|  | ||||
|             let offsetCols = this._useWeekdate ? 1 : 0; | ||||
|             let col; | ||||
|             if (rtl) | ||||
|                 col = 6 - (7 + iter.getDay() - this._weekStart) % 7; | ||||
|             else | ||||
|                 col = offsetCols + (7 + iter.getDay() - this._weekStart) % 7; | ||||
|             layout.attach(button, col, row, 1, 1); | ||||
|  | ||||
|             this._buttons.push(button); | ||||
|             this.actor.add(button, | ||||
|                            { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 }); | ||||
|  | ||||
|             if (this._useWeekdate && iter.getDay() == 4) { | ||||
|                 let label = new St.Label({ text: iter.toLocaleFormat('%V'), | ||||
|                 let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(), | ||||
|                                            style_class: 'calendar-day-base calendar-week-number'}); | ||||
|                 layout.attach(label, rtl ? 7 : 0, row, 1, 1); | ||||
|                 this.actor.add(label, | ||||
|                                { row: row, col: 0, y_align: St.Align.MIDDLE }); | ||||
|             } | ||||
|  | ||||
|             iter.setTime(iter.getTime() + MSECS_IN_DAY); | ||||
|  | ||||
|             if (iter.getDay() == this._weekStart) | ||||
|             if (iter.getDay() == this._weekStart) { | ||||
|                 // We stop on the first "first day of the week" after the month we are displaying | ||||
|                 if (iter.getMonth() > this._selectedDate.getMonth() || iter.getYear() > this._selectedDate.getYear()) | ||||
|                     break; | ||||
|                 row++; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Signal to the event source that we are interested in events | ||||
|         // only from this date range | ||||
|         this._eventSource.requestRange(beginDate, iter); | ||||
|     }, | ||||
|  | ||||
|     _update: function() { | ||||
|         let now = new Date(); | ||||
|  | ||||
|         if (_sameYear(this._selectedDate, now)) | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormatWithoutYear); | ||||
|         else | ||||
|             this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormat); | ||||
|  | ||||
|         if (!this._calendarBegin || !_sameMonth(this._selectedDate, this._calendarBegin) || !_sameDay(now, this._markedAsToday)) | ||||
|             this._rebuildCalendar(); | ||||
|  | ||||
|         this._buttons.forEach(Lang.bind(this, function(button) { | ||||
|             if (_sameDay(button._date, this._selectedDate)) { | ||||
|                 button.add_style_pseudo_class('active'); | ||||
|                 if (this._shouldDateGrabFocus) | ||||
|                     button.grab_key_focus(); | ||||
|             } | ||||
|             else | ||||
|                 button.remove_style_pseudo_class('active'); | ||||
|         })); | ||||
|         if (this._eventSource) | ||||
|             this._eventSource.requestRange(beginDate, iter, forceReload); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -716,107 +603,83 @@ const EventsList = new Lang.Class({ | ||||
|     Name: 'EventsList', | ||||
|  | ||||
|     _init: function() { | ||||
|         let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL }); | ||||
|         this.actor = new St.Widget({ style_class: 'events-table', | ||||
|                                      layout_manager: layout }); | ||||
|         layout.hookup_style(this.actor); | ||||
|         this.actor = new St.BoxLayout({ vertical: true, style_class: 'events-header-vbox'}); | ||||
|         this._date = new Date(); | ||||
|         this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' }); | ||||
|         this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' }); | ||||
|         this._desktopSettings.connect('changed', Lang.bind(this, this._update)); | ||||
|         this._weekStart = Shell.util_get_week_start(); | ||||
|     }, | ||||
|  | ||||
|     setEventSource: function(eventSource) { | ||||
|         this._eventSource = eventSource; | ||||
|         this._eventSource.connect('changed', Lang.bind(this, this._update)); | ||||
|     }, | ||||
|  | ||||
|     _addEvent: function(event, index, includeDayName, periodBegin, periodEnd) { | ||||
|         let eventBox = new St.BoxLayout(); | ||||
|         eventBox.set_vertical(false); | ||||
|         let dayString; | ||||
|         if (includeDayName) { | ||||
|             if (event.date >= periodBegin) | ||||
|                 dayString = _getEventDayAbbreviation(event.date.getDay()); | ||||
|             else /* show event end day if it began earlier */ | ||||
|                 dayString = _getEventDayAbbreviation(event.end.getDay()); | ||||
|         } else { | ||||
|             dayString = ''; | ||||
|         if (this._eventSource) { | ||||
|             this._eventSource.disconnect(this._eventSourceChangedId); | ||||
|             this._eventSource = null; | ||||
|         } | ||||
|  | ||||
|         let dayLabel = new St.Label({ style_class: 'events-day-dayname', | ||||
|                                       text: dayString, | ||||
|                                       x_align: Clutter.ActorAlign.END, | ||||
|                                       y_align: Clutter.ActorAlign.START }); | ||||
|         dayLabel.clutter_text.line_wrap = false; | ||||
|         dayLabel.clutter_text.ellipsize = false; | ||||
|         this._eventSource = eventSource; | ||||
|  | ||||
|         let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|  | ||||
|         let layout = this.actor.layout_manager; | ||||
|         eventBox.add_actor(dayLabel); | ||||
|         let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY); | ||||
|         let timeString = _formatEventTime(event, clockFormat, periodBegin, periodEnd); | ||||
|         let timeLabel = new St.Label({ style_class: 'events-day-time', | ||||
|                                        text: timeString, | ||||
|                                        y_align: Clutter.ActorAlign.START }); | ||||
|         timeLabel.clutter_text.line_wrap = false; | ||||
|         timeLabel.clutter_text.ellipsize = false; | ||||
|  | ||||
|         let preEllipsisLabel = new St.Label({ style_class: 'events-day-time-ellipses', | ||||
|                                               text: ELLIPSIS_CHAR, | ||||
|                                               y_align: Clutter.ActorAlign.START }); | ||||
|         let postEllipsisLabel = new St.Label({ style_class: 'events-day-time-ellipses', | ||||
|                                                text: ELLIPSIS_CHAR, | ||||
|                                                y_align: Clutter.ActorAlign.START }); | ||||
|         if (event.allDay || event.date >= periodBegin) | ||||
|             preEllipsisLabel.opacity = 0; | ||||
|         if (event.allDay || event.end <= periodEnd) | ||||
|             postEllipsisLabel.opacity = 0; | ||||
|  | ||||
|         let timeLabelBoxLayout = new St.BoxLayout({ x_align: Clutter.ActorAlign.START }); | ||||
|         timeLabelBoxLayout.add(preEllipsisLabel); | ||||
|         timeLabelBoxLayout.add(timeLabel); | ||||
|         timeLabelBoxLayout.add(postEllipsisLabel); | ||||
|         timeLabelBoxLayout.set_size(50, 1); | ||||
|         eventBox.add_actor(timeLabelBoxLayout); | ||||
|  | ||||
|         let titleLabel = new St.Label({ style_class: 'events-day-task', | ||||
|                                         text: event.summary, | ||||
|                                         x_expand: true }); | ||||
|         titleLabel.clutter_text.line_wrap = true; | ||||
|         titleLabel.clutter_text.ellipsize = false; | ||||
|  | ||||
|         eventBox.add_actor(titleLabel); | ||||
|         this._eventListBox.add_actor(eventBox); | ||||
|         if (this._eventSource) { | ||||
|             this._eventSourceChangedId = this._eventSource.connect('changed', Lang.bind(this, this._update)); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _addPeriod: function(header, index, periodBegin, periodEnd, includeDayName, showNothingScheduled) { | ||||
|         let events = this._eventSource.getEvents(periodBegin, periodEnd); | ||||
|     _addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) { | ||||
|         if (includeDayName) { | ||||
|             dayNameBox.add(new St.Label( { style_class: 'events-day-dayname', | ||||
|                                            text: day } ), | ||||
|                            { x_fill: true } ); | ||||
|         } | ||||
|         timeBox.add(new St.Label( { style_class: 'events-day-time', | ||||
|                                     text: time} ), | ||||
|                     { x_fill: true } ); | ||||
|         eventTitleBox.add(new St.Label( { style_class: 'events-day-task', | ||||
|                                           text: desc} )); | ||||
|     }, | ||||
|  | ||||
|     _addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) { | ||||
|         if (!this._eventSource) | ||||
|             return; | ||||
|  | ||||
|         let events = this._eventSource.getEvents(begin, end); | ||||
|  | ||||
|         let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);; | ||||
|  | ||||
|         if (events.length == 0 && !showNothingScheduled) | ||||
|             return index; | ||||
|             return; | ||||
|  | ||||
|         let label = new St.Label({ style_class: 'events-day-header', text: header }); | ||||
|         this._eventListBox.add_actor(label); | ||||
|         index++; | ||||
|         let vbox = new St.BoxLayout( {vertical: true} ); | ||||
|         this.actor.add(vbox); | ||||
|  | ||||
|         vbox.add(new St.Label({ style_class: 'events-day-header', text: header })); | ||||
|         let box = new St.BoxLayout({style_class: 'events-header-hbox'}); | ||||
|         let dayNameBox = new St.BoxLayout({ vertical: true, style_class: 'events-day-name-box' }); | ||||
|         let timeBox = new St.BoxLayout({ vertical: true, style_class: 'events-time-box' }); | ||||
|         let eventTitleBox = new St.BoxLayout({ vertical: true, style_class: 'events-event-box' }); | ||||
|         box.add(dayNameBox, {x_fill: false}); | ||||
|         box.add(timeBox, {x_fill: false}); | ||||
|         box.add(eventTitleBox, {expand: true}); | ||||
|         vbox.add(box); | ||||
|  | ||||
|         for (let n = 0; n < events.length; n++) { | ||||
|             this._addEvent(events[n], index, includeDayName, periodBegin, periodEnd); | ||||
|             index++; | ||||
|             let event = events[n]; | ||||
|             let dayString = _getEventDayAbbreviation(event.date.getDay()); | ||||
|             let timeString = _formatEventTime(event, clockFormat); | ||||
|             let summaryString = event.summary; | ||||
|             this._addEvent(dayNameBox, timeBox, eventTitleBox, includeDayName, dayString, timeString, summaryString); | ||||
|         } | ||||
|  | ||||
|         if (events.length == 0 && showNothingScheduled) { | ||||
|             let now = new Date(); | ||||
|             /* Translators: Text to show if there are no events */ | ||||
|             let nothingEvent = new CalendarEvent(periodBegin, periodBegin, _("Nothing Scheduled"), true); | ||||
|             this._addEvent(nothingEvent, index, false, periodBegin, periodEnd); | ||||
|             index++; | ||||
|             let nothingEvent = new CalendarEvent(now, now, _("Nothing Scheduled"), true); | ||||
|             let timeString = _formatEventTime(nothingEvent, clockFormat); | ||||
|             this._addEvent(dayNameBox, timeBox, eventTitleBox, false, "", timeString, nothingEvent.summary); | ||||
|         } | ||||
|  | ||||
|         return index; | ||||
|     }, | ||||
|  | ||||
|     _showOtherDay: function(day) { | ||||
|         this.actor.destroy_all_children(); | ||||
|  | ||||
|         let dayBegin = _getBeginningOfDay(day); | ||||
|         let dayEnd = _getEndOfDay(day); | ||||
|  | ||||
| @@ -828,20 +691,20 @@ const EventsList = new Lang.Class({ | ||||
|         else | ||||
|             /* Translators: Shown on calendar heading when selected day occurs on different year */ | ||||
|             dayString = day.toLocaleFormat(C_("calendar heading", "%A, %B %d, %Y")); | ||||
|         this._addPeriod(dayString, 0, dayBegin, dayEnd, false, true); | ||||
|         this._addPeriod(dayString, dayBegin, dayEnd, false, true); | ||||
|     }, | ||||
|  | ||||
|     _showToday: function() { | ||||
|         let index = 0; | ||||
|         this.actor.destroy_all_children(); | ||||
|  | ||||
|         let now = new Date(); | ||||
|         let dayBegin = _getBeginningOfDay(now); | ||||
|         let dayEnd = _getEndOfDay(now); | ||||
|         index = this._addPeriod(_("Today"), index, dayBegin, dayEnd, false, true); | ||||
|         this._addPeriod(_("Today"), dayBegin, dayEnd, false, true); | ||||
|  | ||||
|         let tomorrowBegin = new Date(dayBegin.getTime() + 86400 * 1000); | ||||
|         let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000); | ||||
|         index = this._addPeriod(_("Tomorrow"), index, tomorrowBegin, tomorrowEnd, false, true); | ||||
|         this._addPeriod(_("Tomorrow"), tomorrowBegin, tomorrowEnd, false, true); | ||||
|  | ||||
|         let dayInWeek = (dayEnd.getDay() - this._weekStart + 7) % 7; | ||||
|  | ||||
| @@ -852,7 +715,7 @@ const EventsList = new Lang.Class({ | ||||
|              */ | ||||
|             let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000); | ||||
|             let thisWeekEnd = new Date(dayEnd.getTime() + (6 - dayInWeek) * 86400 * 1000); | ||||
|             index = this._addPeriod(_("This week"), index, thisWeekBegin, thisWeekEnd, true, false); | ||||
|             this._addPeriod(_("This week"), thisWeekBegin, thisWeekEnd, true, false); | ||||
|         } else { | ||||
|             /* otherwise it's one of the two last days of the week ... show | ||||
|              * "Next week" and include events up until and including *next* | ||||
| @@ -860,7 +723,7 @@ const EventsList = new Lang.Class({ | ||||
|              */ | ||||
|             let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000); | ||||
|             let nextWeekEnd = new Date(dayEnd.getTime() + (13 - dayInWeek) * 86400 * 1000); | ||||
|             index = this._addPeriod(_("Next week"), index, nextWeekBegin, nextWeekEnd, true, false); | ||||
|             this._addPeriod(_("Next week"), nextWeekBegin, nextWeekEnd, true, false); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -873,27 +736,6 @@ const EventsList = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _update: function() { | ||||
|         if (this._eventSource.isLoading) | ||||
|             return; | ||||
|  | ||||
|         this.actor.destroy_all_children(); | ||||
|  | ||||
|         let layout = this.actor.layout_manager; | ||||
|  | ||||
|         this._eventListContainer = new St.BoxLayout({ x_expand: true, y_expand: true }); | ||||
|         this._eventListContainer.set_vertical(true); | ||||
|  | ||||
|         this._eventListBox = new St.BoxLayout(); | ||||
|         this._eventListBox.set_vertical(true); | ||||
|  | ||||
|         let eventScrollView = new St.ScrollView({style_class: 'vfade', | ||||
|                                                 hscrollbar_policy: Gtk.PolicyType.NEVER, | ||||
|                                                 vscrollbar_policy: Gtk.PolicyType.AUTOMATIC}); | ||||
|         eventScrollView.add_actor(this._eventListBox); | ||||
|         this._eventListContainer.add_actor(eventScrollView); | ||||
|  | ||||
|         layout.attach(this._eventListContainer, 0, 0, 1, 1); | ||||
|  | ||||
|         let today = new Date(); | ||||
|         if (_sameDay (this._date, today)) { | ||||
|             this._showToday(); | ||||
|   | ||||
| @@ -1,40 +1,115 @@ | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Pango = imports.gi.Pango; | ||||
| const Shell = imports.gi.Shell; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Lang = imports.lang; | ||||
|  | ||||
| const CheckBoxContainer = new Lang.Class({ | ||||
|     Name: 'CheckBoxContainer', | ||||
|  | ||||
|     _init: function() { | ||||
|         this.actor = new Shell.GenericContainer(); | ||||
|         this.actor.connect('get-preferred-width', | ||||
|                            Lang.bind(this, this._getPreferredWidth)); | ||||
|         this.actor.connect('get-preferred-height', | ||||
|                            Lang.bind(this, this._getPreferredHeight)); | ||||
|         this.actor.connect('allocate', | ||||
|                            Lang.bind(this, this._allocate)); | ||||
|         this.actor.connect('style-changed', Lang.bind(this, | ||||
|             function() { | ||||
|                 let node = this.actor.get_theme_node(); | ||||
|                 this._spacing = node.get_length('spacing'); | ||||
|             })); | ||||
|         this.actor.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH; | ||||
|  | ||||
|         this._box = new St.Bin(); | ||||
|         this.actor.add_actor(this._box); | ||||
|  | ||||
|         this.label = new St.Label(); | ||||
|         this.label.clutter_text.set_line_wrap(true); | ||||
|         this.label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE); | ||||
|         this.actor.add_actor(this.label); | ||||
|  | ||||
|         this._spacing = 0; | ||||
|     }, | ||||
|  | ||||
|     _getPreferredWidth: function(actor, forHeight, alloc) { | ||||
|         let [minWidth, natWidth] = this._box.get_preferred_width(forHeight); | ||||
|  | ||||
|         alloc.min_size = minWidth + this._spacing; | ||||
|         alloc.natural_size = natWidth + this._spacing; | ||||
|     }, | ||||
|  | ||||
|     _getPreferredHeight: function(actor, forWidth, alloc) { | ||||
|         /* FIXME: StBoxlayout currently does not handle | ||||
|            height-for-width children correctly, so hard-code | ||||
|            two lines for the label until that problem is fixed. | ||||
|  | ||||
|            https://bugzilla.gnome.org/show_bug.cgi?id=672543 */ | ||||
| /* | ||||
|         let [minBoxHeight, natBoxHeight] = | ||||
|             this._box.get_preferred_height(forWidth); | ||||
|         let [minLabelHeight, natLabelHeight] = | ||||
|             this.label.get_preferred_height(forWidth); | ||||
|  | ||||
|         alloc.min_size = Math.max(minBoxHeight, minLabelHeight); | ||||
|         alloc.natural_size = Math.max(natBoxHeight, natLabelHeight); | ||||
| */ | ||||
|         let [minBoxHeight, natBoxHeight] = | ||||
|             this._box.get_preferred_height(-1); | ||||
|         let [minLabelHeight, natLabelHeight] = | ||||
|             this.label.get_preferred_height(-1); | ||||
|  | ||||
|         alloc.min_size = Math.max(minBoxHeight, 2 * minLabelHeight); | ||||
|         alloc.natural_size = Math.max(natBoxHeight, 2 * natLabelHeight); | ||||
|     }, | ||||
|  | ||||
|     _allocate: function(actor, box, flags) { | ||||
|         let availWidth = box.x2 - box.x1; | ||||
|         let availHeight = box.y2 - box.y1; | ||||
|  | ||||
|         let childBox = new Clutter.ActorBox(); | ||||
|         let [minBoxWidth, natBoxWidth] = | ||||
|             this._box.get_preferred_width(-1); | ||||
|         let [minBoxHeight, natBoxHeight] = | ||||
|             this._box.get_preferred_height(-1); | ||||
|         childBox.x1 = box.x1; | ||||
|         childBox.x2 = box.x1 + natBoxWidth; | ||||
|         childBox.y1 = box.y1; | ||||
|         childBox.y2 = box.y1 + natBoxHeight; | ||||
|         this._box.allocate(childBox, flags); | ||||
|  | ||||
|         childBox.x1 = box.x1 + natBoxWidth + this._spacing; | ||||
|         childBox.x2 = availWidth - childBox.x1; | ||||
|         childBox.y1 = box.y1; | ||||
|         childBox.y2 = box.y2; | ||||
|         this.label.allocate(childBox, flags); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const CheckBox = new Lang.Class({ | ||||
|     Name: 'CheckBox', | ||||
|  | ||||
|     _init: function(label) { | ||||
|         let container = new St.BoxLayout(); | ||||
|         this.actor = new St.Button({ style_class: 'check-box', | ||||
|                                      child: container, | ||||
|                                      button_mask: St.ButtonMask.ONE, | ||||
|                                      toggle_mode: true, | ||||
|                                      can_focus: true, | ||||
|                                      x_fill: true, | ||||
|                                      y_fill: true }); | ||||
|  | ||||
|         this._box = new St.Bin(); | ||||
|         this._box.set_y_align(Clutter.ActorAlign.START); | ||||
|         container.add_actor(this._box); | ||||
|  | ||||
|         this._label = new St.Label(); | ||||
|         this._label.clutter_text.set_line_wrap(true); | ||||
|         this._label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE); | ||||
|         container.add_actor(this._label); | ||||
|         this._container = new CheckBoxContainer(); | ||||
|         this.actor.set_child(this._container.actor); | ||||
|  | ||||
|         if (label) | ||||
|             this.setLabel(label); | ||||
|     }, | ||||
|  | ||||
|     setLabel: function(label) { | ||||
|         this._label.set_text(label); | ||||
|         this._container.label.set_text(label); | ||||
|     }, | ||||
|  | ||||
|     getLabelActor: function() { | ||||
|         return this._label; | ||||
|         return this._container.label; | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ const Params = imports.misc.params; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const GnomeSession = imports.misc.gnomeSession; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| const Main = imports.ui.main; | ||||
| const ShellMountOperation = imports.ui.shellMountOperation; | ||||
|  | ||||
| @@ -23,7 +24,7 @@ const AutomountManager = new Lang.Class({ | ||||
|     Name: 'AutomountManager', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA }); | ||||
|         this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA }); | ||||
|         this._volumeQueue = []; | ||||
|         this._session = new GnomeSession.SessionManager(); | ||||
|         this._session.connectSignal('InhibitorAdded', | ||||
| @@ -32,6 +33,7 @@ const AutomountManager = new Lang.Class({ | ||||
|                                     Lang.bind(this, this._InhibitorsChanged)); | ||||
|         this._inhibited = false; | ||||
|  | ||||
|         this._loginManager = LoginManager.getLoginManager(); | ||||
|         this._volumeMonitor = Gio.VolumeMonitor.get(); | ||||
|     }, | ||||
|  | ||||
| @@ -43,7 +45,6 @@ const AutomountManager = new Lang.Class({ | ||||
|         this._driveEjectButtonId = this._volumeMonitor.connect('drive-eject-button', Lang.bind(this, this._onDriveEjectButton)); | ||||
|  | ||||
|         this._mountAllId = Mainloop.idle_add(Lang.bind(this, this._startupMountAll)); | ||||
|         GLib.Source.set_name_by_id(this._mountAllId, '[gnome-shell] this._startupMountAll'); | ||||
|     }, | ||||
|  | ||||
|     disable: function() { | ||||
| @@ -78,35 +79,31 @@ const AutomountManager = new Lang.Class({ | ||||
|         })); | ||||
|  | ||||
|         this._mountAllId = 0; | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onDriveConnected: function() { | ||||
|         // if we're not in the current ConsoleKit session, | ||||
|         // or screensaver is active, don't play sounds | ||||
|         if (!this._session.SessionIsActive) | ||||
|         if (!this._loginManager.sessionActive) | ||||
|             return; | ||||
|  | ||||
|         global.play_theme_sound(0, 'device-added-media', | ||||
|                                 _("External drive connected"), | ||||
|                                 null); | ||||
|         global.play_theme_sound(0, 'device-added-media'); | ||||
|     }, | ||||
|  | ||||
|     _onDriveDisconnected: function() { | ||||
|         // if we're not in the current ConsoleKit session, | ||||
|         // or screensaver is active, don't play sounds | ||||
|         if (!this._session.SessionIsActive) | ||||
|         if (!this._loginManager.sessionActive) | ||||
|             return; | ||||
|  | ||||
|         global.play_theme_sound(0, 'device-removed-media', | ||||
|                                 _("External drive disconnected"), | ||||
|                                 null); | ||||
|         global.play_theme_sound(0, 'device-removed-media');         | ||||
|     }, | ||||
|  | ||||
|     _onDriveEjectButton: function(monitor, drive) { | ||||
|         // TODO: this code path is not tested, as the GVfs volume monitor | ||||
|         // doesn't emit this signal just yet. | ||||
|         if (!this._session.SessionIsActive) | ||||
|         if (!this._loginManager.sessionActive) | ||||
|             return; | ||||
|  | ||||
|         // we force stop/eject in this case, so we don't have to pass a | ||||
| @@ -146,7 +143,7 @@ const AutomountManager = new Lang.Class({ | ||||
|         if (params.checkSession) { | ||||
|             // if we're not in the current ConsoleKit session, | ||||
|             // don't attempt automount | ||||
|             if (!this._session.SessionIsActive) | ||||
|             if (!this._loginManager.sessionActive) | ||||
|                 return; | ||||
|         } | ||||
|  | ||||
| @@ -235,11 +232,10 @@ const AutomountManager = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _allowAutorunExpire: function(volume) { | ||||
|         let id = Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, function() { | ||||
|         Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, function() { | ||||
|             volume.allowAutorun = false; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|             return false; | ||||
|         }); | ||||
|         GLib.Source.set_name_by_id(id, '[gnome-shell] volume.allowAutorun'); | ||||
|     } | ||||
| }); | ||||
| const Component = AutomountManager; | ||||
|   | ||||
| @@ -4,7 +4,7 @@ const Lang = imports.lang; | ||||
| const Gio = imports.gi.Gio; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const GnomeSession = imports.misc.gnomeSession; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| const Main = imports.ui.main; | ||||
| const MessageTray = imports.ui.messageTray; | ||||
| const ShellMountOperation = imports.ui.shellMountOperation; | ||||
| @@ -31,7 +31,7 @@ function shouldAutorunMount(mount, forTransient) { | ||||
|     if (!volume || (!volume.allowAutorun && forTransient)) | ||||
|         return false; | ||||
|  | ||||
|     if (root.is_native() && isMountRootHidden(root)) | ||||
|     if (!root.is_native() || isMountRootHidden(root)) | ||||
|         return false; | ||||
|  | ||||
|     return true; | ||||
| @@ -64,7 +64,7 @@ function startAppForMount(app, mount) { | ||||
|  | ||||
|     try { | ||||
|         retval = app.launch(files,  | ||||
|                             global.create_app_launch_context(0, -1)) | ||||
|                             global.create_app_launch_context()) | ||||
|     } catch (e) { | ||||
|         log('Unable to launch the application ' + app.get_name() | ||||
|             + ': ' + e.toString()); | ||||
| @@ -75,14 +75,12 @@ function startAppForMount(app, mount) { | ||||
|  | ||||
| /******************************************/ | ||||
|  | ||||
| const HotplugSnifferIface = '<node> \ | ||||
| <interface name="org.gnome.Shell.HotplugSniffer"> \ | ||||
| <method name="SniffURI"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="as" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const HotplugSnifferIface = <interface name="org.gnome.Shell.HotplugSniffer"> | ||||
| <method name="SniffURI"> | ||||
|     <arg type="s" direction="in" /> | ||||
|     <arg type="as" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| const HotplugSnifferProxy = Gio.DBusProxy.makeProxyWrapper(HotplugSnifferIface); | ||||
| function HotplugSniffer() { | ||||
| @@ -96,7 +94,7 @@ const ContentTypeDiscoverer = new Lang.Class({ | ||||
|  | ||||
|     _init: function(callback) { | ||||
|         this._callback = callback; | ||||
|         this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA }); | ||||
|         this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA }); | ||||
|     }, | ||||
|  | ||||
|     guessContentTypes: function(mount) { | ||||
| @@ -164,7 +162,8 @@ const AutorunManager = new Lang.Class({ | ||||
|     Name: 'AutorunManager', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._session = new GnomeSession.SessionManager(); | ||||
|         this._loginManager = LoginManager.getLoginManager(); | ||||
|  | ||||
|         this._volumeMonitor = Gio.VolumeMonitor.get(); | ||||
|  | ||||
|         this._transDispatcher = new AutorunTransientDispatcher(this); | ||||
| @@ -216,7 +215,7 @@ const AutorunManager = new Lang.Class({ | ||||
|     _onMountAdded: function(monitor, mount) { | ||||
|         // don't do anything if our session is not the currently | ||||
|         // active one | ||||
|         if (!this._session.SessionIsActive) | ||||
|         if (!this._loginManager.sessionActive) | ||||
|             return; | ||||
|  | ||||
|         this._processMount(mount, true); | ||||
| @@ -294,7 +293,7 @@ const AutorunResidentSource = new Lang.Class({ | ||||
|  | ||||
|     _init: function(manager) { | ||||
|         this.parent(_("Removable Devices"), 'media-removable'); | ||||
|         this.resident = true; | ||||
|         this.showInLockScreen = false; | ||||
|  | ||||
|         this._mounts = []; | ||||
|  | ||||
| @@ -302,10 +301,6 @@ const AutorunResidentSource = new Lang.Class({ | ||||
|         this._notification = new AutorunResidentNotification(this._manager, this); | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationPolicy({ showInLockScreen: false }); | ||||
|     }, | ||||
|  | ||||
|     buildRightClickMenu: function() { | ||||
|         return null; | ||||
|     }, | ||||
| @@ -441,7 +436,7 @@ const AutorunTransientDispatcher = new Lang.Class({ | ||||
|     _init: function(manager) { | ||||
|         this._manager = manager; | ||||
|         this._sources = []; | ||||
|         this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA }); | ||||
|         this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA }); | ||||
|     }, | ||||
|  | ||||
|     _getAutorunSettingForType: function(contentType) { | ||||
|   | ||||
| @@ -13,6 +13,8 @@ const ModalDialog = imports.ui.modalDialog; | ||||
| const ShellEntry = imports.ui.shellEntry; | ||||
| const CheckBox = imports.ui.checkBox; | ||||
|  | ||||
| let prompter = null; | ||||
|  | ||||
| const KeyringDialog = new Lang.Class({ | ||||
|     Name: 'KeyringDialog', | ||||
|     Extends: ModalDialog.ModalDialog, | ||||
| @@ -23,7 +25,7 @@ const KeyringDialog = new Lang.Class({ | ||||
|         this.prompt = new Shell.KeyringPrompt(); | ||||
|         this.prompt.connect('show-password', Lang.bind(this, this._onShowPassword)); | ||||
|         this.prompt.connect('show-confirm', Lang.bind(this, this._onShowConfirm)); | ||||
|         this.prompt.connect('prompt-close', Lang.bind(this, this._onHidePrompt)); | ||||
|         this.prompt.connect('hide-prompt', Lang.bind(this, this._onHidePrompt)); | ||||
|  | ||||
|         let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout', | ||||
|                                                 vertical: false }); | ||||
| @@ -45,9 +47,7 @@ const KeyringDialog = new Lang.Class({ | ||||
|         this.prompt.bind_property('message', subject, 'text', GObject.BindingFlags.SYNC_CREATE); | ||||
|  | ||||
|         this._messageBox.add(subject, | ||||
|                              { x_fill: false, | ||||
|                                y_fill:  false, | ||||
|                                x_align: St.Align.START, | ||||
|                              { y_fill:  false, | ||||
|                                y_align: St.Align.START }); | ||||
|  | ||||
|         let description = new St.Label({ style_class: 'prompt-dialog-description' }); | ||||
| @@ -60,78 +60,60 @@ const KeyringDialog = new Lang.Class({ | ||||
|  | ||||
|         this._controlTable = null; | ||||
|  | ||||
|         let buttons = [{ label: '', | ||||
|                          action: Lang.bind(this, this._onCancelButton), | ||||
|                          key:    Clutter.Escape | ||||
|                        }, | ||||
|                        { label: '', | ||||
|                          action: Lang.bind(this, this._onContinueButton), | ||||
|                          default: true | ||||
|                        }] | ||||
|  | ||||
|         this._cancelButton = this.addButton({ label: '', | ||||
|                                               action: Lang.bind(this, this._onCancelButton), | ||||
|                                               key: Clutter.Escape }, | ||||
|                                             { expand: true, x_fill: false, x_align: St.Align.START }); | ||||
|         this.placeSpinner({ expand: false, | ||||
|                             x_fill: false, | ||||
|                             y_fill: false, | ||||
|                             x_align: St.Align.END, | ||||
|                             y_align: St.Align.MIDDLE }); | ||||
|         this._continueButton = this.addButton({ label: '', | ||||
|                                                 action: Lang.bind(this, this._onContinueButton), | ||||
|                                                 default: true }, | ||||
|                                               { expand: false, x_fill: false, x_align: St.Align.END }); | ||||
|         this.setButtons(buttons); | ||||
|         this._cancelButton = buttons[0].button; | ||||
|         this._continueButton = buttons[1].button; | ||||
|  | ||||
|         this.prompt.bind_property('cancel-label', this._cancelButton, 'label', GObject.BindingFlags.SYNC_CREATE); | ||||
|         this.prompt.bind_property('continue-label', this._continueButton, 'label', GObject.BindingFlags.SYNC_CREATE); | ||||
|     }, | ||||
|  | ||||
|     _buildControlTable: function() { | ||||
|         let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL }); | ||||
|         let table = new St.Widget({ style_class: 'keyring-dialog-control-table', | ||||
|                                     layout_manager: layout }); | ||||
|         layout.hookup_style(table); | ||||
|         let rtl = table.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|         let table = new St.Table({ style_class: 'keyring-dialog-control-table' }); | ||||
|         let row = 0; | ||||
|  | ||||
|         if (this.prompt.password_visible) { | ||||
|             let label = new St.Label({ style_class: 'prompt-dialog-password-label', | ||||
|                                        x_align: Clutter.ActorAlign.START, | ||||
|                                        y_align: Clutter.ActorAlign.CENTER }); | ||||
|             let label = new St.Label(({ style_class: 'prompt-dialog-password-label' })); | ||||
|             label.set_text(_("Password:")); | ||||
|             label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|             table.add(label, { row: row, col: 0, | ||||
|                                x_expand: false, x_fill: true, | ||||
|                                x_align: St.Align.START, | ||||
|                                y_fill: false, y_align: St.Align.MIDDLE }); | ||||
|             this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry', | ||||
|                                                  text: '', | ||||
|                                                  can_focus: true, | ||||
|                                                  x_expand: true }); | ||||
|                                                  can_focus: true}); | ||||
|             this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE | ||||
|             ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true }); | ||||
|             this._passwordEntry.clutter_text.connect('activate', Lang.bind(this, this._onPasswordActivate)); | ||||
|  | ||||
|             if (rtl) { | ||||
|                 layout.attach(this._passwordEntry, 0, row, 1, 1); | ||||
|                 layout.attach(label, 1, row, 1, 1); | ||||
|             } else { | ||||
|                 layout.attach(label, 0, row, 1, 1); | ||||
|                 layout.attach(this._passwordEntry, 1, row, 1, 1); | ||||
|             } | ||||
|             table.add(this._passwordEntry, { row: row, col: 1, x_expand: true, x_fill: true, x_align: St.Align.START }); | ||||
|             row++; | ||||
|         } else { | ||||
|             this._passwordEntry = null; | ||||
|         } | ||||
|  | ||||
|         if (this.prompt.confirm_visible) { | ||||
|             var label = new St.Label(({ style_class: 'prompt-dialog-password-label', | ||||
|                                         x_align: Clutter.ActorAlign.START, | ||||
|                                         y_align: Clutter.ActorAlign.CENTER })); | ||||
|             var label = new St.Label(({ style_class: 'prompt-dialog-password-label' })); | ||||
|             label.set_text(_("Type again:")); | ||||
|             table.add(label, { row: row, col: 0, | ||||
|                                x_expand: false, x_fill: true, | ||||
|                                x_align: St.Align.START, | ||||
|                                y_fill: false, y_align: St.Align.MIDDLE }); | ||||
|             this._confirmEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry', | ||||
|                                                 text: '', | ||||
|                                                 can_focus: true, | ||||
|                                                 x_expand: true }); | ||||
|                                                 can_focus: true}); | ||||
|             this._confirmEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE | ||||
|             ShellEntry.addContextMenu(this._confirmEntry, { isPassword: true }); | ||||
|             this._confirmEntry.clutter_text.connect('activate', Lang.bind(this, this._onConfirmActivate)); | ||||
|             if (rtl) { | ||||
|                 layout.attach(this._confirmEntry, 0, row, 1, 1); | ||||
|                 layout.attach(label, 1, row, 1, 1); | ||||
|             } else { | ||||
|                 layout.attach(label, 0, row, 1, 1); | ||||
|                 layout.attach(this._confirmEntry, 1, row, 1, 1); | ||||
|             } | ||||
|             table.add(this._confirmEntry, { row: row, col: 1, x_expand: true, x_fill: true, x_align: St.Align.START }); | ||||
|             row++; | ||||
|         } else { | ||||
|             this._confirmEntry = null; | ||||
| @@ -144,15 +126,14 @@ const KeyringDialog = new Lang.Class({ | ||||
|             let choice = new CheckBox.CheckBox(); | ||||
|             this.prompt.bind_property('choice-label', choice.getLabelActor(), 'text', GObject.BindingFlags.SYNC_CREATE); | ||||
|             this.prompt.bind_property('choice-chosen', choice.actor, 'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL); | ||||
|             layout.attach(choice.actor, rtl ? 0 : 1, row, 1, 1); | ||||
|             table.add(choice.actor, { row: row, col: 1, x_expand: false, x_fill: true, x_align: St.Align.START }); | ||||
|             row++; | ||||
|         } | ||||
|  | ||||
|         let warning = new St.Label({ style_class: 'prompt-dialog-error-label', | ||||
|                                      x_align: Clutter.ActorAlign.START }); | ||||
|         let warning = new St.Label({ style_class: 'prompt-dialog-error-label' }); | ||||
|         warning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|         warning.clutter_text.line_wrap = true; | ||||
|         layout.attach(warning, rtl ? 0 : 1, row, 1, 1); | ||||
|         table.add(warning, { row: row, col: 1, x_expand: false, x_fill: false, x_align: St.Align.START }); | ||||
|         this.prompt.bind_property('warning-visible', warning, 'visible', GObject.BindingFlags.SYNC_CREATE); | ||||
|         this.prompt.bind_property('warning', warning, 'text', GObject.BindingFlags.SYNC_CREATE); | ||||
|  | ||||
| @@ -165,22 +146,6 @@ const KeyringDialog = new Lang.Class({ | ||||
|         this._messageBox.add(table, { x_fill: true, y_fill: true }); | ||||
|     }, | ||||
|  | ||||
|     _updateSensitivity: function(sensitive) { | ||||
|         if (this._passwordEntry) { | ||||
|             this._passwordEntry.reactive = sensitive; | ||||
|             this._passwordEntry.clutter_text.editable = sensitive; | ||||
|         } | ||||
|  | ||||
|         if (this._confirmEntry) { | ||||
|             this._confirmEntry.reactive = sensitive; | ||||
|             this._confirmEntry.clutter_text.editable = sensitive; | ||||
|         } | ||||
|  | ||||
|         this._continueButton.can_focus = sensitive; | ||||
|         this._continueButton.reactive = sensitive; | ||||
|         this.setWorking(!sensitive); | ||||
|     }, | ||||
|  | ||||
|     _ensureOpen: function() { | ||||
|         // NOTE: ModalDialog.open() is safe to call if the dialog is | ||||
|         // already open - it just returns true without side-effects | ||||
| @@ -202,14 +167,12 @@ const KeyringDialog = new Lang.Class({ | ||||
|     _onShowPassword: function(prompt) { | ||||
|         this._buildControlTable(); | ||||
|         this._ensureOpen(); | ||||
|         this._updateSensitivity(true); | ||||
|         this._passwordEntry.grab_key_focus(); | ||||
|     }, | ||||
|  | ||||
|     _onShowConfirm: function(prompt) { | ||||
|         this._buildControlTable(); | ||||
|         this._ensureOpen(); | ||||
|         this._updateSensitivity(true); | ||||
|         this._continueButton.grab_key_focus(); | ||||
|     }, | ||||
|  | ||||
| @@ -229,7 +192,6 @@ const KeyringDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onContinueButton: function() { | ||||
|         this._updateSensitivity(false); | ||||
|         this.prompt.complete(); | ||||
|     }, | ||||
|  | ||||
| @@ -238,56 +200,27 @@ const KeyringDialog = new Lang.Class({ | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const KeyringDummyDialog = new Lang.Class({ | ||||
|     Name: 'KeyringDummyDialog', | ||||
|  | ||||
|     _init: function() { | ||||
|         this.prompt = new Shell.KeyringPrompt(); | ||||
|         this.prompt.connect('show-password', | ||||
|                             Lang.bind(this, this._cancelPrompt)); | ||||
|         this.prompt.connect('show-confirm', Lang.bind(this, | ||||
|                             this._cancelPrompt)); | ||||
|     }, | ||||
|  | ||||
|     _cancelPrompt: function() { | ||||
|         this.prompt.cancel(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const KeyringPrompter = new Lang.Class({ | ||||
|     Name: 'KeyringPrompter', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._prompter = new Gcr.SystemPrompter(); | ||||
|         this._prompter.connect('new-prompt', Lang.bind(this, | ||||
|             function() { | ||||
|                 let dialog = this._enabled ? new KeyringDialog() | ||||
|                                            : new KeyringDummyDialog(); | ||||
|                 this._currentPrompt = dialog.prompt; | ||||
|                 return this._currentPrompt; | ||||
|             })); | ||||
|         this._prompter.connect('new-prompt', function(prompter) { | ||||
|             let dialog = new KeyringDialog(); | ||||
|             return dialog.prompt; | ||||
|         }); | ||||
|         this._dbusId = null; | ||||
|         this._registered = false; | ||||
|         this._enabled = false; | ||||
|         this._currentPrompt = null; | ||||
|     }, | ||||
|  | ||||
|     enable: function() { | ||||
|         if (!this._registered) { | ||||
|             this._prompter.register(Gio.DBus.session); | ||||
|             this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter', | ||||
|                                                      Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null); | ||||
|             this._registered = true; | ||||
|         } | ||||
|         this._enabled = true; | ||||
|         this._prompter.register(Gio.DBus.session); | ||||
|         this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter', | ||||
|                                                  Gio.BusNameOwnerFlags.REPLACE, null, null); | ||||
|     }, | ||||
|  | ||||
|     disable: function() { | ||||
|         this._enabled = false; | ||||
|  | ||||
|         if (this._prompter.prompting) | ||||
|             this._currentPrompt.cancel(); | ||||
|         this._currentPrompt = null; | ||||
|         this._prompter.unregister(false); | ||||
|         Gio.DBus.session.unown_name(this._dbusId); | ||||
|     } | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -62,9 +62,14 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|  | ||||
|         if (this._content.message != null) { | ||||
|             let descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description', | ||||
|                                                   text: this._content.message }); | ||||
|                                                   text: this._content.message, | ||||
|                                                   // HACK: for reasons unknown to me, the label | ||||
|                                                   // is not asked the correct height for width, | ||||
|                                                   // and thus is underallocated | ||||
|                                                   // place a fixed height to avoid overflowing | ||||
|                                                   style: 'height: 3em' | ||||
|                                                 }); | ||||
|             descriptionLabel.clutter_text.line_wrap = true; | ||||
|             descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|  | ||||
|             messageBox.add(descriptionLabel, | ||||
|                            { y_fill:  true, | ||||
| @@ -72,28 +77,19 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|                              expand: true }); | ||||
|         } | ||||
|  | ||||
|         let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL }); | ||||
|         let secretTable = new St.Widget({ style_class: 'network-dialog-secret-table', | ||||
|                                           layout_manager: layout }); | ||||
|         layout.hookup_style(secretTable); | ||||
|  | ||||
|         let rtl = secretTable.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|         let secretTable = new St.Table({ style_class: 'network-dialog-secret-table' }); | ||||
|         let initialFocusSet = false; | ||||
|         let pos = 0; | ||||
|         for (let i = 0; i < this._content.secrets.length; i++) { | ||||
|             let secret = this._content.secrets[i]; | ||||
|             let label = new St.Label({ style_class: 'prompt-dialog-password-label', | ||||
|                                        text: secret.label, | ||||
|                                        x_align: Clutter.ActorAlign.START, | ||||
|                                        y_align: Clutter.ActorAlign.CENTER }); | ||||
|             label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|                                        text: secret.label }); | ||||
|  | ||||
|             let reactive = secret.key != null; | ||||
|  | ||||
|             secret.entry = new St.Entry({ style_class: 'prompt-dialog-password-entry', | ||||
|                                           text: secret.value, can_focus: reactive, | ||||
|                                           reactive: reactive, | ||||
|                                           x_expand: true }); | ||||
|                                           reactive: reactive }); | ||||
|             ShellEntry.addContextMenu(secret.entry, | ||||
|                                       { isPassword: secret.password }); | ||||
|  | ||||
| @@ -120,13 +116,11 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|             } else | ||||
|                 secret.valid = true; | ||||
|  | ||||
|             if (rtl) { | ||||
|                 layout.attach(secret.entry, 0, pos, 1, 1); | ||||
|                 layout.attach(label, 1, pos, 1, 1); | ||||
|             } else { | ||||
|                 layout.attach(label, 0, pos, 1, 1); | ||||
|                 layout.attach(secret.entry, 1, pos, 1, 1); | ||||
|             } | ||||
|             secretTable.add(label, { row: pos, col: 0, | ||||
|                                      x_expand: false, x_fill: true, | ||||
|                                      x_align: St.Align.START, | ||||
|                                      y_fill: false, y_align: St.Align.MIDDLE }); | ||||
|             secretTable.add(secret.entry, { row: pos, col: 1, x_expand: true, x_fill: true, y_align: St.Align.END }); | ||||
|             pos++; | ||||
|  | ||||
|             if (secret.password) | ||||
| @@ -145,8 +139,6 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|                            key:    Clutter.KEY_Escape, | ||||
|                          }, | ||||
|                          this._okButton]); | ||||
|  | ||||
|         this._updateOkButton(); | ||||
|     }, | ||||
|  | ||||
|     _updateOkButton: function() { | ||||
| @@ -262,7 +254,6 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|         case 'leap': | ||||
|         case 'ttls': | ||||
|         case 'peap': | ||||
|         case 'fast': | ||||
|             // TTLS and PEAP are actually much more complicated, but this complication | ||||
|             // is not visible here since we only care about phase2 authentication | ||||
|             // (and don't even care of which one) | ||||
| @@ -316,7 +307,7 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|             wirelessSetting = this._connection.get_setting_wireless(); | ||||
|             ssid = NetworkManager.utils_ssid_to_utf8(wirelessSetting.get_ssid()); | ||||
|             content.title = _("Authentication required by wireless network"); | ||||
|             content.message = _("Passwords or encryption keys are required to access the wireless network “%s”.").format(ssid); | ||||
|             content.message = _("Passwords or encryption keys are required to access the wireless network '%s'.").format(ssid); | ||||
|             this._getWirelessSecrets(content.secrets, wirelessSetting); | ||||
|             break; | ||||
|         case '802-3-ethernet': | ||||
| @@ -343,7 +334,7 @@ const NetworkSecretDialog = new Lang.Class({ | ||||
|         case 'cdma': | ||||
|         case 'bluetooth': | ||||
|             content.title = _("Mobile broadband network password"); | ||||
|             content.message = _("A password is required to connect to “%s”.").format(connectionSetting.get_id()); | ||||
|             content.message = _("A password is required to connect to '%s'.").format(connectionSetting.get_id()); | ||||
|             this._getMobileSecrets(content.secrets, connectionType); | ||||
|             break; | ||||
|         default: | ||||
| @@ -380,12 +371,6 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|             argv.push('-i'); | ||||
|         if (flags & NMClient.SecretAgentGetSecretsFlags.REQUEST_NEW) | ||||
|             argv.push('-r'); | ||||
|         if (authHelper.supportsHints) { | ||||
|             for (let i = 0; i < hints.length; i++) { | ||||
|                 argv.push('-t'); | ||||
|                 argv.push(hints[i]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._newStylePlugin = authHelper.externalUIMode; | ||||
|  | ||||
| @@ -400,7 +385,11 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|             this._childPid = pid; | ||||
|             this._stdin = new Gio.UnixOutputStream({ fd: stdin, close_fd: true }); | ||||
|             this._stdout = new Gio.UnixInputStream({ fd: stdout, close_fd: true }); | ||||
|             GLib.close(stderr); | ||||
|             // We need this one too, even if don't actually care of what the process | ||||
|             // has to say on stderr, because otherwise the fd opened by g_spawn_async_with_pipes | ||||
|             // is kept open indefinitely | ||||
|             let stderrStream = new Gio.UnixInputStream({ fd: stderr, close_fd: true }); | ||||
|             stderrStream.close(null); | ||||
|             this._dataStdout = new Gio.DataInputStream({ base_stream: this._stdout }); | ||||
|  | ||||
|             if (this._newStylePlugin) | ||||
| @@ -448,7 +437,6 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _vpnChildFinished: function(pid, status, requestObj) { | ||||
|         this._childWatch = 0; | ||||
|         if (this._newStylePlugin) { | ||||
|             // For new style plugin, all work is done in the async reading functions | ||||
|             // Just reap the process here | ||||
| @@ -523,12 +511,10 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|  | ||||
|     _showNewStyleDialog: function() { | ||||
|         let keyfile = new GLib.KeyFile(); | ||||
|         let data; | ||||
|         let contentOverride; | ||||
|  | ||||
|         try { | ||||
|             data = this._dataStdout.peek_buffer(); | ||||
|  | ||||
|             let data = this._dataStdout.peek_buffer(); | ||||
|             keyfile.load_from_data(data.toString(), data.length, | ||||
|                                    GLib.KeyFileFlags.NONE); | ||||
|  | ||||
| @@ -561,16 +547,13 @@ const VPNRequestHandler = new Lang.Class({ | ||||
|                 } | ||||
|             } | ||||
|         } catch(e) { | ||||
|             // No output is a valid case it means "both secrets are stored" | ||||
|             if (data.length > 0) { | ||||
|                 logError(e, 'error while reading VPN plugin output keyfile'); | ||||
|             logError(e, 'error while reading VPN plugin output keyfile'); | ||||
|  | ||||
|                 this._agent.respond(this._requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR); | ||||
|                 return; | ||||
|             } | ||||
|             this._agent.respond(this._requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (contentOverride && contentOverride.secrets.length) { | ||||
|         if (contentOverride.secrets.length) { | ||||
|             // Only show the dialog if we actually have something to ask | ||||
|             this._shellDialog = new NetworkSecretDialog(this._agent, this._requestId, this._connection, 'vpn', [], contentOverride); | ||||
|             this._shellDialog.open(global.get_current_time()); | ||||
| @@ -604,27 +587,18 @@ const NetworkAgent = new Lang.Class({ | ||||
|     Name: 'NetworkAgent', | ||||
|  | ||||
|     _init: function() { | ||||
|         try { | ||||
|             this._native = new Shell.NetworkAgent({ identifier: 'org.gnome.Shell.NetworkAgent', | ||||
|                                                     capabilities: NMClient.SecretAgentCapabilities.VPN_HINTS | ||||
|                                                   }); | ||||
|         } catch(e) { | ||||
|             // Support older versions without NetworkAgent:capabilities | ||||
|             this._native = new Shell.NetworkAgent({ identifier: 'org.gnome.Shell.NetworkAgent' | ||||
|                                                   }); | ||||
|         } | ||||
|         this._native = new Shell.NetworkAgent({ auto_register: false, | ||||
|                                                 identifier: 'org.gnome.Shell.NetworkAgent' }); | ||||
|  | ||||
|         this._dialogs = { }; | ||||
|         this._vpnRequests = { }; | ||||
|  | ||||
|         this._native.connect('new-request', Lang.bind(this, this._newRequest)); | ||||
|         this._native.connect('cancel-request', Lang.bind(this, this._cancelRequest)); | ||||
|  | ||||
|         this._enabled = false; | ||||
|     }, | ||||
|  | ||||
|     enable: function() { | ||||
|         this._enabled = true; | ||||
|         this._native.auto_register = true; | ||||
|     }, | ||||
|  | ||||
|     disable: function() { | ||||
| @@ -638,15 +612,12 @@ const NetworkAgent = new Lang.Class({ | ||||
|             this._vpnRequests[requestId].cancel(true); | ||||
|         this._vpnRequests = { }; | ||||
|  | ||||
|         this._enabled = false; | ||||
|         this._native.auto_register = false; | ||||
|         if (this._native.registered) | ||||
|             this._native.unregister(); | ||||
|     }, | ||||
|  | ||||
|     _newRequest:  function(agent, requestId, connection, settingName, hints, flags) { | ||||
|         if (!this._enabled) { | ||||
|             agent.respond(requestId, Shell.NetworkAgentResponse.USER_CANCELED); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (settingName == 'vpn') { | ||||
|             this._vpnRequest(requestId, connection, hints, flags); | ||||
|             return; | ||||
| @@ -712,23 +683,16 @@ const NetworkAgent = new Lang.Class({ | ||||
|                     let service = keyfile.get_string('VPN Connection', 'service'); | ||||
|                     let binary = keyfile.get_string('GNOME', 'auth-dialog'); | ||||
|                     let externalUIMode = false; | ||||
|                     let hints = false; | ||||
|  | ||||
|                     try { | ||||
|                         externalUIMode = keyfile.get_boolean('GNOME', 'supports-external-ui-mode'); | ||||
|                     } catch(e) { } // ignore errors if key does not exist | ||||
|  | ||||
|                     try { | ||||
|                         hints = keyfile.get_boolean('GNOME', 'supports-hints'); | ||||
|                     } catch(e) { } // ignore errors if key does not exist | ||||
|  | ||||
|                     let path = binary; | ||||
|                     if (!GLib.path_is_absolute(path)) { | ||||
|                         path = GLib.build_filenamev([Config.LIBEXECDIR, path]); | ||||
|                     } | ||||
|  | ||||
|                     if (GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE)) | ||||
|                         this._vpnBinaries[service] = { fileName: path, externalUIMode: externalUIMode, supportsHints: hints }; | ||||
|                         this._vpnBinaries[service] = { fileName: path, externalUIMode: externalUIMode }; | ||||
|                     else | ||||
|                         throw new Error('VPN plugin at %s is not executable'.format(path)); | ||||
|                 } catch(e) { | ||||
|   | ||||
| @@ -16,7 +16,7 @@ const PolkitAgent = imports.gi.PolkitAgent; | ||||
| const Components = imports.ui.components; | ||||
| const ModalDialog = imports.ui.modalDialog; | ||||
| const ShellEntry = imports.ui.shellEntry; | ||||
| const UserWidget = imports.ui.userWidget; | ||||
| const UserMenu = imports.ui.userMenu; | ||||
|  | ||||
| const DIALOG_ICON_SIZE = 48; | ||||
|  | ||||
| @@ -31,6 +31,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         this.message = message; | ||||
|         this.userNames = userNames; | ||||
|         this._wasDismissed = false; | ||||
|         this._completed = false; | ||||
|  | ||||
|         let mainContentBox = new St.BoxLayout({ style_class: 'prompt-dialog-main-layout', | ||||
|                                                 vertical: false }); | ||||
| @@ -54,9 +55,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|                                             text: _("Authentication Required") }); | ||||
|  | ||||
|         messageBox.add(this._subjectLabel, | ||||
|                        { x_fill: false, | ||||
|                          y_fill:  false, | ||||
|                          x_align: St.Align.START, | ||||
|                        { y_fill:  false, | ||||
|                          y_align: St.Align.START }); | ||||
|  | ||||
|         this._descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description', | ||||
| @@ -65,9 +64,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         this._descriptionLabel.clutter_text.line_wrap = true; | ||||
|  | ||||
|         messageBox.add(this._descriptionLabel, | ||||
|                        { x_fill: false, | ||||
|                          y_fill:  true, | ||||
|                          x_align: St.Align.START, | ||||
|                        { y_fill:  true, | ||||
|                          y_align: St.Align.START }); | ||||
|  | ||||
|         if (userNames.length > 1) { | ||||
| @@ -99,15 +96,14 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         if (userIsRoot) { | ||||
|             let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-root-label', | ||||
|                                             text: userRealName })); | ||||
|             messageBox.add(userLabel, { x_fill: false, | ||||
|                                         x_align: St.Align.START }); | ||||
|             messageBox.add(userLabel); | ||||
|         } else { | ||||
|             let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout', | ||||
|                                              vertical: false }); | ||||
|             messageBox.add(userBox); | ||||
|             this._userAvatar = new UserWidget.Avatar(this._user, | ||||
|                                                      { iconSize: DIALOG_ICON_SIZE, | ||||
|                                                        styleClass: 'polkit-dialog-user-icon' }); | ||||
|             this._userAvatar = new UserMenu.UserAvatarWidget(this._user, | ||||
|                                                              { iconSize: DIALOG_ICON_SIZE, | ||||
|                                                                styleClass: 'polkit-dialog-user-icon' }); | ||||
|             this._userAvatar.actor.hide(); | ||||
|             userBox.add(this._userAvatar.actor, | ||||
|                         { x_fill:  true, | ||||
| @@ -142,7 +138,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label' }); | ||||
|         this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|         this._errorMessageLabel.clutter_text.line_wrap = true; | ||||
|         messageBox.add(this._errorMessageLabel, { x_fill: false, x_align: St.Align.START }); | ||||
|         messageBox.add(this._errorMessageLabel); | ||||
|         this._errorMessageLabel.hide(); | ||||
|  | ||||
|         this._infoMessageLabel = new St.Label({ style_class: 'prompt-dialog-info-label' }); | ||||
| @@ -163,34 +159,29 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         messageBox.add(this._nullMessageLabel); | ||||
|         this._nullMessageLabel.show(); | ||||
|  | ||||
|         this._cancelButton = this.addButton({ label: _("Cancel"), | ||||
|                                               action: Lang.bind(this, this.cancel), | ||||
|                                               key: Clutter.Escape }, | ||||
|                                             { expand: true, x_fill: false, x_align: St.Align.START }); | ||||
|         this.placeSpinner({ expand: false, | ||||
|                             x_fill: false, | ||||
|                             y_fill: false, | ||||
|                             x_align: St.Align.END, | ||||
|                             y_align: St.Align.MIDDLE }); | ||||
|         this._okButton = this.addButton({ label:  _("Authenticate"), | ||||
|                                           action: Lang.bind(this, this._onAuthenticateButtonPressed), | ||||
|                                           default: true }, | ||||
|                                         { expand: false, x_fill: false, x_align: St.Align.END }); | ||||
|         this.setButtons([{ label: _("Cancel"), | ||||
|                            action: Lang.bind(this, this.cancel), | ||||
|                            key:    Clutter.Escape | ||||
|                          }, | ||||
|                          { label:  _("Authenticate"), | ||||
|                            action: Lang.bind(this, this._onAuthenticateButtonPressed), | ||||
|                            default: true | ||||
|                          }]); | ||||
|  | ||||
|         this._doneEmitted = false; | ||||
|  | ||||
|         this._identityToAuth = Polkit.UnixUser.new_for_name(userName); | ||||
|         this._cookie = cookie; | ||||
|     }, | ||||
|  | ||||
|     performAuthentication: function() { | ||||
|         this.destroySession(); | ||||
|         this._session = new PolkitAgent.Session({ identity: this._identityToAuth, | ||||
|                                                   cookie: this._cookie }); | ||||
|         this._session.connect('completed', Lang.bind(this, this._onSessionCompleted)); | ||||
|         this._session.connect('request', Lang.bind(this, this._onSessionRequest)); | ||||
|         this._session.connect('show-error', Lang.bind(this, this._onSessionShowError)); | ||||
|         this._session.connect('show-info', Lang.bind(this, this._onSessionShowInfo)); | ||||
|     }, | ||||
|  | ||||
|     startAuthentication: function() { | ||||
|         this._session.initiate(); | ||||
|     }, | ||||
|  | ||||
| @@ -212,29 +203,19 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|             log('polkitAuthenticationAgent: Failed to show modal dialog.' + | ||||
|                 ' Dismissing authentication request for action-id ' + this.actionId + | ||||
|                 ' cookie ' + this._cookie); | ||||
|             this._emitDone(true); | ||||
|             this._emitDone(false, true); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _emitDone: function(dismissed) { | ||||
|     _emitDone: function(keepVisible, dismissed) { | ||||
|         if (!this._doneEmitted) { | ||||
|             this._doneEmitted = true; | ||||
|             this.emit('done', dismissed); | ||||
|             this.emit('done', keepVisible, dismissed); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateSensitivity: function(sensitive) { | ||||
|         this._passwordEntry.reactive = sensitive; | ||||
|         this._passwordEntry.clutter_text.editable = sensitive; | ||||
|  | ||||
|         this._okButton.can_focus = sensitive; | ||||
|         this._okButton.reactive = sensitive; | ||||
|         this.setWorking(!sensitive); | ||||
|     }, | ||||
|  | ||||
|     _onEntryActivate: function() { | ||||
|         let response = this._passwordEntry.get_text(); | ||||
|         this._updateSensitivity(false); | ||||
|         this._session.response(response); | ||||
|         // When the user responds, dismiss already shown info and | ||||
|         // error texts (if any) | ||||
| @@ -248,16 +229,12 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onSessionCompleted: function(session, gainedAuthorization) { | ||||
|         if (this._completed || this._doneEmitted) | ||||
|         if (this._completed) | ||||
|             return; | ||||
|  | ||||
|         this._completed = true; | ||||
|  | ||||
|         /* Yay, all done */ | ||||
|         if (gainedAuthorization) { | ||||
|             this._emitDone(false); | ||||
|  | ||||
|         } else { | ||||
|         if (!gainedAuthorization) { | ||||
|             /* Unless we are showing an existing error message from the PAM | ||||
|              * module (the PAM module could be reporting the authentication | ||||
|              * error providing authentication-method specific information), | ||||
| @@ -273,10 +250,8 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|                 this._infoMessageLabel.hide(); | ||||
|                 this._nullMessageLabel.hide(); | ||||
|             } | ||||
|  | ||||
|             /* Try and authenticate again */ | ||||
|             this.performAuthentication(); | ||||
|         } | ||||
|         this._emitDone(!gainedAuthorization, false); | ||||
|     }, | ||||
|  | ||||
|     _onSessionRequest: function(session, request, echo_on) { | ||||
| @@ -294,7 +269,6 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         this._passwordBox.show(); | ||||
|         this._passwordEntry.set_text(''); | ||||
|         this._passwordEntry.grab_key_focus(); | ||||
|         this._updateSensitivity(true); | ||||
|         this._ensureOpen(); | ||||
|     }, | ||||
|  | ||||
| @@ -320,7 +294,6 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|         if (this._session) { | ||||
|             if (!this._completed) | ||||
|                 this._session.cancel(); | ||||
|             this._completed = false; | ||||
|             this._session = null; | ||||
|         } | ||||
|     }, | ||||
| @@ -335,7 +308,7 @@ const AuthenticationDialog = new Lang.Class({ | ||||
|     cancel: function() { | ||||
|         this._wasDismissed = true; | ||||
|         this.close(global.get_current_time()); | ||||
|         this._emitDone(true); | ||||
|         this._emitDone(false, true); | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(AuthenticationDialog.prototype); | ||||
| @@ -345,6 +318,7 @@ const AuthenticationAgent = new Lang.Class({ | ||||
|  | ||||
|     _init: function() { | ||||
|         this._currentDialog = null; | ||||
|         this._isCompleting = false; | ||||
|         this._handle = null; | ||||
|         this._native = new Shell.PolkitAuthenticationAgent(); | ||||
|         this._native.connect('initiate', Lang.bind(this, this._onInitiate)); | ||||
| @@ -352,19 +326,11 @@ const AuthenticationAgent = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     enable: function() { | ||||
|         try { | ||||
|             this._native.register(); | ||||
|         } catch(e) { | ||||
|             log('Failed to register AuthenticationAgent'); | ||||
|         } | ||||
|         this._native.register(); | ||||
|     }, | ||||
|  | ||||
|     disable: function() { | ||||
|         try { | ||||
|             this._native.unregister(); | ||||
|         } catch(e) { | ||||
|             log('Failed to unregister AuthenticationAgent'); | ||||
|         } | ||||
|         this._native.unregister(); | ||||
|     }, | ||||
|  | ||||
|     _onInitiate: function(nativeAgent, actionId, message, iconName, cookie, userNames) { | ||||
| @@ -381,24 +347,45 @@ const AuthenticationAgent = new Lang.Class({ | ||||
|         // discussion. | ||||
|  | ||||
|         this._currentDialog.connect('done', Lang.bind(this, this._onDialogDone)); | ||||
|         this._currentDialog.performAuthentication(); | ||||
|         this._currentDialog.startAuthentication(); | ||||
|     }, | ||||
|  | ||||
|     _onCancel: function(nativeAgent) { | ||||
|         this._completeRequest(false); | ||||
|         this._completeRequest(false, false); | ||||
|     }, | ||||
|  | ||||
|     _onDialogDone: function(dialog, dismissed) { | ||||
|         this._completeRequest(dismissed); | ||||
|     _onDialogDone: function(dialog, keepVisible, dismissed) { | ||||
|         this._completeRequest(keepVisible, dismissed); | ||||
|     }, | ||||
|  | ||||
|     _completeRequest: function(dismissed) { | ||||
|     _reallyCompleteRequest: function(dismissed) { | ||||
|         this._currentDialog.close(); | ||||
|         this._currentDialog.destroySession(); | ||||
|         this._currentDialog = null; | ||||
|         this._isCompleting = false; | ||||
|  | ||||
|         this._native.complete(dismissed); | ||||
|         this._native.complete(dismissed) | ||||
|     }, | ||||
|  | ||||
|     _completeRequest: function(keepVisible, wasDismissed) { | ||||
|         if (this._isCompleting) | ||||
|             return; | ||||
|  | ||||
|         this._isCompleting = true; | ||||
|  | ||||
|         if (keepVisible) { | ||||
|             // Give the user 2 seconds to read 'Authentication Failure' before | ||||
|             // dismissing the dialog | ||||
|             Mainloop.timeout_add(2000, | ||||
|                                  Lang.bind(this, | ||||
|                                            function() { | ||||
|                                                this._reallyCompleteRequest(wasDismissed); | ||||
|                                                return false; | ||||
|                                            })); | ||||
|         } else { | ||||
|             this._reallyCompleteRequest(wasDismissed); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const Component = AuthenticationAgent; | ||||
|   | ||||
							
								
								
									
										62
									
								
								js/ui/components/recorder.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								js/ui/components/recorder.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Main = imports.ui.main; | ||||
|  | ||||
| const Gio = imports.gi.Gio; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const Recorder = new Lang.Class({ | ||||
|     Name: 'Recorder', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._recorderSettings = new Gio.Settings({ schema: 'org.gnome.shell.recorder' }); | ||||
|         this._desktopLockdownSettings = new Gio.Settings({ schema: 'org.gnome.desktop.lockdown' }); | ||||
|         this._bindingSettings = new Gio.Settings({ schema: 'org.gnome.shell.keybindings' }); | ||||
|         this._recorder = null; | ||||
|     }, | ||||
|  | ||||
|     enable: function() { | ||||
|         Main.wm.addKeybinding('toggle-recording', | ||||
|                               this._bindingSettings, | ||||
|                               Meta.KeyBindingFlags.NONE, | ||||
|                               Main.KeybindingMode.NORMAL | | ||||
|                               Main.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; | ||||
| @@ -1,6 +1,5 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| @@ -18,7 +17,7 @@ const Params = imports.misc.params; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| // See Notification.appendMessage | ||||
| const SCROLLBACK_IMMEDIATE_TIME = 3 * 60; // 3 minutes | ||||
| const SCROLLBACK_IMMEDIATE_TIME = 60; // 1 minute | ||||
| const SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes | ||||
| const SCROLLBACK_RECENT_LENGTH = 20; | ||||
| const SCROLLBACK_IDLE_LENGTH = 5; | ||||
| @@ -29,8 +28,6 @@ const SCROLLBACK_HISTORY_LINES = 10; | ||||
| // See Notification._onEntryChanged | ||||
| const COMPOSING_STOP_TIMEOUT = 5; | ||||
|  | ||||
| const CLOCK_FORMAT_KEY = 'clock-format'; | ||||
|  | ||||
| const NotificationDirection = { | ||||
|     SENT: 'chat-sent', | ||||
|     RECEIVED: 'chat-received' | ||||
| @@ -418,8 +415,6 @@ const TelepathyClient = new Lang.Class({ | ||||
|     _ensureAppSource: function() { | ||||
|         if (this._appSource == null) { | ||||
|             this._appSource = new MessageTray.Source(_("Chat"), 'empathy'); | ||||
|             this._appSource.policy = new MessageTray.NotificationApplicationPolicy('empathy'); | ||||
|  | ||||
|             Main.messageTray.add(this._appSource); | ||||
|             this._appSource.connect('destroy', Lang.bind(this, function () { | ||||
|                 this._appSource = null; | ||||
| @@ -449,7 +444,6 @@ const ChatSource = new Lang.Class({ | ||||
|         this._closedId = this._channel.connect('invalidated', Lang.bind(this, this._channelClosed)); | ||||
|  | ||||
|         this._notification = new ChatNotification(this); | ||||
|         this._notification.connect('clicked', Lang.bind(this, this.open)); | ||||
|         this._notification.setUrgency(MessageTray.Urgency.HIGH); | ||||
|         this._notifyTimeoutId = 0; | ||||
|  | ||||
| @@ -490,10 +484,6 @@ const ChatSource = new Lang.Class({ | ||||
|         return rightClickMenu; | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationApplicationPolicy('empathy'); | ||||
|     }, | ||||
|  | ||||
|     _updateAlias: function() { | ||||
|         let oldAlias = this.title; | ||||
|         let newAlias = this._contact.get_alias(); | ||||
| @@ -548,19 +538,20 @@ const ChatSource = new Lang.Class({ | ||||
|         this._notification.update(this._notification.title, null, { customContent: true }); | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
|         if (this._client.is_handling_channel(this._channel)) { | ||||
|             // We are handling the channel, try to pass it to Empathy | ||||
|             this._client.delegate_channels_async([this._channel], | ||||
|                                                  global.get_current_time(), | ||||
|                                                  'org.freedesktop.Telepathy.Client.Empathy.Chat', null); | ||||
|         } else { | ||||
|             // We are not the handler, just ask to present the channel | ||||
|             let dbus = Tp.DBusDaemon.dup(); | ||||
|             let cd = Tp.ChannelDispatcher.new(dbus); | ||||
|     open: function(notification) { | ||||
|           if (this._client.is_handling_channel(this._channel)) { | ||||
|               // We are handling the channel, try to pass it to Empathy | ||||
|               this._client.delegate_channels_async([this._channel], | ||||
|                   global.get_current_time(), | ||||
|                   'org.freedesktop.Telepathy.Client.Empathy.Chat', null); | ||||
|           } | ||||
|           else { | ||||
|               // We are not the handler, just ask to present the channel | ||||
|               let dbus = Tp.DBusDaemon.dup(); | ||||
|               let cd = Tp.ChannelDispatcher.new(dbus); | ||||
|  | ||||
|             cd.present_channel_async(this._channel, global.get_current_time(), null); | ||||
|         } | ||||
|               cd.present_channel_async(this._channel, global.get_current_time(), null); | ||||
|           } | ||||
|     }, | ||||
|  | ||||
|     _getLogMessages: function() { | ||||
| @@ -624,11 +615,7 @@ const ChatSource = new Lang.Class({ | ||||
|             this.notify(); | ||||
|     }, | ||||
|  | ||||
|     destroy: function(reason) { | ||||
|         if (this._destroyed) | ||||
|             return; | ||||
|  | ||||
|         this._destroyed = true; | ||||
|     _channelClosed: function() { | ||||
|         this._channel.disconnect(this._closedId); | ||||
|         this._channel.disconnect(this._receivedId); | ||||
|         this._channel.disconnect(this._pendingId); | ||||
| @@ -638,14 +625,7 @@ const ChatSource = new Lang.Class({ | ||||
|         this._contact.disconnect(this._notifyAvatarId); | ||||
|         this._contact.disconnect(this._presenceChangedId); | ||||
|  | ||||
|         if (this._timestampTimeoutId) | ||||
|             Mainloop.source_remove(this._timestampTimeoutId); | ||||
|  | ||||
|         this.parent(reason); | ||||
|     }, | ||||
|  | ||||
|     _channelClosed: function() { | ||||
|         this.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED); | ||||
|         this.destroy(); | ||||
|     }, | ||||
|  | ||||
|     /* All messages are new messages for Telepathy sources */ | ||||
| @@ -653,10 +633,6 @@ const ChatSource = new Lang.Class({ | ||||
|         return this._pendingMessages.length; | ||||
|     }, | ||||
|  | ||||
|     get indicatorCount() { | ||||
|         return this.count; | ||||
|     }, | ||||
|  | ||||
|     get unseenCount() { | ||||
|         return this.count; | ||||
|     }, | ||||
| @@ -681,7 +657,6 @@ const ChatSource = new Lang.Class({ | ||||
|             Mainloop.source_remove(this._notifyTimeoutId); | ||||
|         this._notifyTimeoutId = Mainloop.timeout_add(500, | ||||
|             Lang.bind(this, this._notifyTimeout)); | ||||
|         GLib.Source.set_name_by_id(this._notifyTimeoutId, '[gnome-shell] this._notifyTimeout'); | ||||
|     }, | ||||
|  | ||||
|     _notifyTimeout: function() { | ||||
| @@ -690,7 +665,7 @@ const ChatSource = new Lang.Class({ | ||||
|  | ||||
|         this._notifyTimeoutId = 0; | ||||
|  | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     // This is called for both messages we send from | ||||
| @@ -783,6 +758,7 @@ const ChatNotification = new Lang.Class({ | ||||
|  | ||||
|         this._createScrollArea(); | ||||
|         this._lastGroup = null; | ||||
|         this._lastGroupActor = null; | ||||
|  | ||||
|         // Keep track of the bottom position for the current adjustment and | ||||
|         // force a scroll to the bottom if things change while we were at the | ||||
| @@ -863,6 +839,13 @@ const ChatNotification = new Lang.Class({ | ||||
|             for (let i = 0; i < expired.length; i++) | ||||
|                 expired[i].actor.destroy(); | ||||
|         } | ||||
|  | ||||
|         let groups = this._contentArea.get_children(); | ||||
|         for (let i = 0; i < groups.length; i++) { | ||||
|             let group = groups[i]; | ||||
|             if (group.get_n_children() == 0) | ||||
|                 group.destroy(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -901,35 +884,30 @@ const ChatNotification = new Lang.Class({ | ||||
|  | ||||
|         let group = props.group; | ||||
|         if (group != this._lastGroup) { | ||||
|             let style = 'chat-group-' + group; | ||||
|             this._lastGroup = group; | ||||
|             let emptyLine = new St.Label({ style_class: 'chat-empty-line' }); | ||||
|             this.addActor(emptyLine); | ||||
|             this._history.unshift({ actor: emptyLine, time: timestamp, | ||||
|                                     realMessage: false }); | ||||
|             this._lastGroupActor = new St.BoxLayout({ style_class: style, | ||||
|                                                       vertical: true }); | ||||
|             this.addActor(this._lastGroupActor); | ||||
|         } | ||||
|  | ||||
|         let lineBox = new St.BoxLayout({ vertical: false }); | ||||
|         lineBox.add(body, props.childProps); | ||||
|         this.addActor(lineBox); | ||||
|         this._lastMessageBox = lineBox; | ||||
|         this._lastGroupActor.add(body, props.childProps); | ||||
|  | ||||
|         this.updated(); | ||||
|  | ||||
|         let timestamp = props.timestamp; | ||||
|         this._history.unshift({ actor: lineBox, time: timestamp, | ||||
|         this._history.unshift({ actor: body, time: timestamp, | ||||
|                                 realMessage: group != 'meta' }); | ||||
|  | ||||
|         if (!props.noTimestamp) { | ||||
|             if (timestamp < currentTime - SCROLLBACK_IMMEDIATE_TIME) { | ||||
|             if (timestamp < currentTime - SCROLLBACK_IMMEDIATE_TIME) | ||||
|                 this.appendTimestamp(); | ||||
|             } else { | ||||
|             else | ||||
|                 // Schedule a new timestamp in SCROLLBACK_IMMEDIATE_TIME | ||||
|                 // from the timestamp of the message. | ||||
|                 this._timestampTimeoutId = Mainloop.timeout_add_seconds( | ||||
|                     SCROLLBACK_IMMEDIATE_TIME - (currentTime - timestamp), | ||||
|                     Lang.bind(this, this.appendTimestamp)); | ||||
|                 GLib.Source.set_name_by_id(this._timestampTimeoutId, '[gnome-shell] this.appendTimestamp'); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this._filterMessages(); | ||||
| @@ -942,98 +920,49 @@ const ChatNotification = new Lang.Class({ | ||||
|  | ||||
|         let format; | ||||
|  | ||||
|         let desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' }); | ||||
|         let clockFormat = desktopSettings.get_string(CLOCK_FORMAT_KEY); | ||||
|         let hasAmPm = date.toLocaleFormat('%p') != ''; | ||||
|  | ||||
|         if (clockFormat == '24h' || !hasAmPm) { | ||||
|             // Show only the time if date is on today | ||||
|             if(daysAgo < 1){ | ||||
|                 /* Translators: Time in 24h format */ | ||||
|                 format = _("%H\u2236%M"); | ||||
|             } | ||||
|             // Show the word "Yesterday" and time if date is on yesterday | ||||
|             else if(daysAgo <2){ | ||||
|                 /* Translators: this is the word "Yesterday" followed by a | ||||
|                  time string in 24h format. i.e. "Yesterday, 14:30" */ | ||||
|                 // xgettext:no-c-format | ||||
|                 format = _("Yesterday, %H\u2236%M"); | ||||
|             } | ||||
|             // Show a week day and time if date is in the last week | ||||
|             else if (daysAgo < 7) { | ||||
|                 /* Translators: this is the week day name followed by a time | ||||
|                  string in 24h format. i.e. "Monday, 14:30" */ | ||||
|                 // xgettext:no-c-format | ||||
|                 format = _("%A, %H\u2236%M"); | ||||
|  | ||||
|             } else if (date.getYear() == now.getYear()) { | ||||
|                 /* Translators: this is the month name and day number | ||||
|                  followed by a time string in 24h format. | ||||
|                  i.e. "May 25, 14:30" */ | ||||
|                 // xgettext:no-c-format | ||||
|                 format = _("%B %d, %H\u2236%M"); | ||||
|             } else { | ||||
|                 /* Translators: this is the month name, day number, year | ||||
|                  number followed by a time string in 24h format. | ||||
|                  i.e. "May 25 2012, 14:30" */ | ||||
|                 // xgettext:no-c-format | ||||
|                 format = _("%B %d %Y, %H\u2236%M"); | ||||
|             } | ||||
|         } else { | ||||
|             // Show only the time if date is on today | ||||
|             if(daysAgo < 1){ | ||||
|                 /* Translators: Time in 24h format */ | ||||
|                 format = _("%l\u2236%M %p"); | ||||
|             } | ||||
|             // Show the word "Yesterday" and time if date is on yesterday | ||||
|             else if(daysAgo <2){ | ||||
|                 /* Translators: this is the word "Yesterday" followed by a | ||||
|                  time string in 12h format. i.e. "Yesterday, 2:30 pm" */ | ||||
|                 // xgettext:no-c-format | ||||
|                 format = _("Yesterday, %l\u2236%M %p"); | ||||
|             } | ||||
|             // Show a week day and time if date is in the last week | ||||
|             else if (daysAgo < 7) { | ||||
|                 /* Translators: this is the week day name followed by a time | ||||
|                  string in 12h format. i.e. "Monday, 2:30 pm" */ | ||||
|                 // xgettext:no-c-format | ||||
|                 format = _("%A, %l\u2236%M %p"); | ||||
|  | ||||
|             } else if (date.getYear() == now.getYear()) { | ||||
|                 /* Translators: this is the month name and day number | ||||
|                  followed by a time string in 12h format. | ||||
|                  i.e. "May 25, 2:30 pm" */ | ||||
|                 // xgettext:no-c-format | ||||
|                 format = _("%B %d, %l\u2236%M %p"); | ||||
|             } else { | ||||
|                 /* Translators: this is the month name, day number, year | ||||
|                  number followed by a time string in 12h format. | ||||
|                  i.e. "May 25 2012, 2:30 pm"*/ | ||||
|                 // xgettext:no-c-format | ||||
|                 format = _("%B %d %Y, %l\u2236%M %p"); | ||||
|             } | ||||
|         // Show only the hour if date is on today | ||||
|         if(daysAgo < 1){ | ||||
|             format = "<b>%H:%M</b>"; | ||||
|         } | ||||
|         // Show the word "Yesterday" and time if date is on yesterday | ||||
|         else if(daysAgo <2){ | ||||
|             /* Translators: this is the word "Yesterday" followed by a time string. i.e. "Yesterday, 14:30"*/ | ||||
|             // xgettext:no-c-format | ||||
|             format = _("<b>Yesterday</b>, <b>%H:%M</b>"); | ||||
|         } | ||||
|         // Show a week day and time if date is in the last week | ||||
|         else if (daysAgo < 7) { | ||||
|             /* Translators: this is the week day name followed by a time string. i.e. "Monday, 14:30*/ | ||||
|             // xgettext:no-c-format | ||||
|             format = _("<b>%A</b>, <b>%H:%M</b>"); | ||||
|  | ||||
|         } else if (date.getYear() == now.getYear()) { | ||||
|             /* Translators: this is the month name and day number followed by a time string. i.e. "May 25, 14:30"*/ | ||||
|             // xgettext:no-c-format | ||||
|             format = _("<b>%B</b> <b>%d</b>, <b>%H:%M</b>"); | ||||
|         } else { | ||||
|             /* Translators: this is the month name, day number, year number followed by a time string. i.e. "May 25 2012, 14:30"*/ | ||||
|             // xgettext:no-c-format | ||||
|             format = _("<b>%B</b> <b>%d</b> <b>%Y</b>, <b>%H:%M</b> "); | ||||
|         } | ||||
|  | ||||
|         return date.toLocaleFormat(format); | ||||
|     }, | ||||
|  | ||||
|     appendTimestamp: function() { | ||||
|         this._timestampTimeoutId = 0; | ||||
|  | ||||
|         let lastMessageTime = this._history[0].time; | ||||
|         let lastMessageDate = new Date(lastMessageTime * 1000); | ||||
|  | ||||
|         let timeLabel = new St.Label({ text: this._formatTimestamp(lastMessageDate), | ||||
|                                        style_class: 'chat-meta-message', | ||||
|                                        x_expand: true, | ||||
|                                        y_expand: true, | ||||
|                                        x_align: Clutter.ActorAlign.END, | ||||
|                                        y_align: Clutter.ActorAlign.END }); | ||||
|  | ||||
|         this._lastMessageBox.add_actor(timeLabel); | ||||
|         let timeLabel = this._append({ body: this._formatTimestamp(lastMessageDate), | ||||
|                                        group: 'meta', | ||||
|                                        styles: ['chat-meta-message'], | ||||
|                                        childProps: { expand: true, x_fill: false }, | ||||
|                                        noTimestamp: true, | ||||
|                                        timestamp: lastMessageTime }); | ||||
|  | ||||
|         this._filterMessages(); | ||||
|  | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     appendAliasChange: function(oldAlias, newAlias) { | ||||
| @@ -1071,7 +1000,7 @@ const ChatNotification = new Lang.Class({ | ||||
|  | ||||
|         this.source.setChatState(Tp.ChannelChatState.PAUSED); | ||||
|  | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onEntryChanged: function() { | ||||
| @@ -1094,7 +1023,6 @@ const ChatNotification = new Lang.Class({ | ||||
|             this._composingTimeoutId = Mainloop.timeout_add_seconds( | ||||
|                 COMPOSING_STOP_TIMEOUT, | ||||
|                 Lang.bind(this, this._composingStopTimeout)); | ||||
|             GLib.Source.set_name_by_id(this._composingTimeoutId, '[gnome-shell] this._composingStopTimeout'); | ||||
|         } else { | ||||
|             this.source.setChatState(Tp.ChannelChatState.ACTIVE); | ||||
|         } | ||||
| @@ -1120,10 +1048,6 @@ const ApproverSource = new Lang.Class({ | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationApplicationPolicy('empathy'); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         if (this._invalidId != 0) { | ||||
|             this._dispatchOp.disconnect(this._invalidId); | ||||
| @@ -1156,16 +1080,22 @@ const RoomInviteNotification = new Lang.Class({ | ||||
|          * for example. */ | ||||
|         this.addBody(_("%s is inviting you to join %s").format(inviter.get_alias(), channel.get_identifier())); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|         this.addButton('decline', _("Decline")); | ||||
|         this.addButton('accept', _("Accept")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'decline': | ||||
|                 dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, | ||||
|                                                 '', function(src, result) { | ||||
|                     src.leave_channels_finish(result)}); | ||||
|                 break; | ||||
|             case 'accept': | ||||
|                 dispatchOp.handle_with_time_async('', global.get_current_time(), | ||||
|                                                   function(src, result) { | ||||
|                     src.handle_with_time_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| @@ -1189,19 +1119,23 @@ const AudioVideoNotification = new Lang.Class({ | ||||
|         this.parent(source, title, null, { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this.setUrgency(MessageTray.Urgency.CRITICAL); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         this.addButton('reject', _("Decline")); | ||||
|         /* translators: this is a button label (verb), not a noun */ | ||||
|         this.addAction(_("Answer"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|         this.addButton('answer', _("Answer")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'reject': | ||||
|                 dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, | ||||
|                                                 '', function(src, result) { | ||||
|                     src.leave_channels_finish(result)}); | ||||
|                 break; | ||||
|             case 'answer': | ||||
|                 dispatchOp.handle_with_time_async('', global.get_current_time(), | ||||
|                                                   function(src, result) { | ||||
|                     src.handle_with_time_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| @@ -1225,16 +1159,22 @@ const FileTransferNotification = new Lang.Class({ | ||||
|                     { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|         this.addButton('decline', _("Decline")); | ||||
|         this.addButton('accept', _("Accept")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'decline': | ||||
|                 dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, | ||||
|                                                 '', function(src, result) { | ||||
|                     src.leave_channels_finish(result)}); | ||||
|                 break; | ||||
|             case 'accept': | ||||
|                 dispatchOp.handle_with_time_async('', global.get_current_time(), | ||||
|                                                   function(src, result) { | ||||
|                     src.handle_with_time_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| @@ -1265,8 +1205,7 @@ const SubscriptionRequestNotification = new Lang.Class({ | ||||
|  | ||||
|         if (file) { | ||||
|             let uri = file.get_uri(); | ||||
|             let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|             iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size, scaleFactor); | ||||
|             iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size); | ||||
|         } | ||||
|         else { | ||||
|             iconBox.child = new St.Icon({ icon_name: 'avatar-default', | ||||
| @@ -1283,20 +1222,27 @@ const SubscriptionRequestNotification = new Lang.Class({ | ||||
|  | ||||
|         this.addActor(layout); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             contact.remove_async(function(src, result) { | ||||
|                 src.remove_finish(result); | ||||
|             }); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             // Authorize the contact and request to see his status as well | ||||
|             contact.authorize_publication_async(function(src, result) { | ||||
|                 src.authorize_publication_finish(result); | ||||
|             }); | ||||
|         this.addButton('decline', _("Decline")); | ||||
|         this.addButton('accept', _("Accept")); | ||||
|  | ||||
|             contact.request_subscription_async('', function(src, result) { | ||||
|                 src.request_subscription_finish(result); | ||||
|             }); | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'decline': | ||||
|                 contact.remove_async(function(src, result) { | ||||
|                     src.remove_finish(result)}); | ||||
|                 break; | ||||
|             case 'accept': | ||||
|                 // Authorize the contact and request to see his status as well | ||||
|                 contact.authorize_publication_async(function(src, result) { | ||||
|                     src.authorize_publication_finish(result)}); | ||||
|  | ||||
|                 contact.request_subscription_async('', function(src, result) { | ||||
|                     src.request_subscription_finish(result)}); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             // rely on _subscriptionStatesChangedCb to destroy the | ||||
|             // notification | ||||
|         })); | ||||
|  | ||||
|         this._changedId = contact.connect('subscription-states-changed', | ||||
| @@ -1395,11 +1341,19 @@ const AccountNotification = new Lang.Class({ | ||||
|  | ||||
|         this._account = account; | ||||
|  | ||||
|         this.addAction(_("View account"), Lang.bind(this, function() { | ||||
|             let cmd = 'empathy-accounts --select-account=' + | ||||
|                 account.get_path_suffix(); | ||||
|             let app_info = Gio.app_info_create_from_commandline(cmd, null, 0); | ||||
|             app_info.launch([], global.create_app_launch_context(0, -1)); | ||||
|         this.addButton('view', _("View account")); | ||||
|  | ||||
|         this.connect('action-invoked', Lang.bind(this, function(self, action) { | ||||
|             switch (action) { | ||||
|             case 'view': | ||||
|                 let cmd = '/usr/bin/empathy-accounts' | ||||
|                         + ' --select-account=%s' | ||||
|                         .format(account.get_path_suffix()); | ||||
|                 let app_info = Gio.app_info_create_from_commandline(cmd, null, 0); | ||||
|                 app_info.launch([], global.create_app_launch_context()); | ||||
|                 break; | ||||
|             } | ||||
|             this.destroy(); | ||||
|         })); | ||||
|  | ||||
|         this._enabledId = account.connect('notify::enabled', | ||||
| @@ -1417,12 +1371,7 @@ const AccountNotification = new Lang.Class({ | ||||
|                 if (status == Tp.ConnectionStatus.CONNECTED) { | ||||
|                     this.destroy(); | ||||
|                 } else if (status == Tp.ConnectionStatus.DISCONNECTED) { | ||||
|                     let connectionError = account.connection_error; | ||||
|  | ||||
|                     if (connectionError == Tp.error_get_dbus_name(Tp.Error.CANCELLED)) | ||||
|                         this.destroy(); | ||||
|                     else | ||||
|                         this.update(this.title, this._getMessage(connectionError)); | ||||
|                     this.update(this.title, this._getMessage(account.connection_error)); | ||||
|                 } | ||||
|             })); | ||||
|     }, | ||||
|   | ||||
| @@ -58,10 +58,15 @@ const CtrlAltTabManager = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     focusGroup: function(item, timestamp) { | ||||
|         if (item.focusCallback) | ||||
|         if (item.focusCallback) { | ||||
|             item.focusCallback(timestamp); | ||||
|         else | ||||
|         } else { | ||||
|             if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE || | ||||
|                 global.stage_input_mode == Shell.StageInputMode.NORMAL) | ||||
|                 global.set_stage_input_mode(Shell.StageInputMode.FOCUSED); | ||||
|  | ||||
|             item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     // Sort the items into a consistent order; panel first, tray last, | ||||
| @@ -84,33 +89,21 @@ const CtrlAltTabManager = new Lang.Class({ | ||||
|         let items = this._items.filter(function (item) { return item.proxy.mapped; }); | ||||
|  | ||||
|         // And add the windows metacity would show in its Ctrl-Alt-Tab list | ||||
|         if (Main.sessionMode.hasWindows && !Main.overview.visible) { | ||||
|         if (!Main.overview.visible) { | ||||
|             let screen = global.screen; | ||||
|             let display = screen.get_display(); | ||||
|             let windows = display.get_tab_list(Meta.TabList.DOCKS, screen.get_active_workspace ()); | ||||
|             let windows = display.get_tab_list(Meta.TabList.DOCKS, screen, screen.get_active_workspace ()); | ||||
|             let windowTracker = Shell.WindowTracker.get_default(); | ||||
|             let textureCache = St.TextureCache.get_default(); | ||||
|             for (let i = 0; i < windows.length; i++) { | ||||
|                 let icon = null; | ||||
|                 let iconName = null; | ||||
|                 if (windows[i].get_window_type () == Meta.WindowType.DESKTOP) { | ||||
|                     iconName = 'video-display-symbolic'; | ||||
|                 } else { | ||||
|                     let app = windowTracker.get_window_app(windows[i]); | ||||
|                     if (app) | ||||
|                         icon = app.create_icon_texture(POPUP_APPICON_SIZE); | ||||
|                     else | ||||
|                         icon = textureCache.bind_pixbuf_property(windows[i], 'icon'); | ||||
|                 } | ||||
|  | ||||
|                 let icon; | ||||
|                 let app = windowTracker.get_window_app(windows[i]); | ||||
|                 if (app) | ||||
|                     icon = app.create_icon_texture(POPUP_APPICON_SIZE); | ||||
|                 else | ||||
|                     icon = textureCache.bind_pixbuf_property(windows[i], 'icon'); | ||||
|                 items.push({ name: windows[i].title, | ||||
|                              proxy: windows[i].get_compositor_private(), | ||||
|                              focusCallback: Lang.bind(windows[i], | ||||
|                                  function(timestamp) { | ||||
|                                      Main.activateWindow(this, timestamp); | ||||
|                                  }), | ||||
|                              iconActor: icon, | ||||
|                              iconName: iconName, | ||||
|                              sortGroup: SortGroup.MIDDLE }); | ||||
|             } | ||||
|         } | ||||
| @@ -132,6 +125,8 @@ const CtrlAltTabManager = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _focusWindows: function(timestamp) { | ||||
|         global.set_stage_input_mode(Shell.StageInputMode.NORMAL); | ||||
|         global.stage.key_focus = null; | ||||
|         global.screen.focus_default_window(timestamp); | ||||
|     } | ||||
| }); | ||||
| @@ -140,25 +135,31 @@ const CtrlAltTabPopup = new Lang.Class({ | ||||
|     Name: 'CtrlAltTabPopup', | ||||
|     Extends: SwitcherPopup.SwitcherPopup, | ||||
|  | ||||
|     _init: function(items) { | ||||
|         this.parent(items); | ||||
|  | ||||
|     _createSwitcher: function() { | ||||
|         this._switcherList = new CtrlAltTabSwitcher(this._items); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _keyPressHandler: function(keysym, action) { | ||||
|     _initialSelection: function(backward, binding) { | ||||
|         if (binding == 'switch-panels') { | ||||
|             if (backward) | ||||
|                 this._selectedIndex = this._items.length - 1; | ||||
|         } else if (binding == 'switch-panels-backward') { | ||||
|             if (!backward) | ||||
|                 this._selectedIndex = this._items.length - 1; | ||||
|         } | ||||
|         this._select(this._selectedIndex); | ||||
|     }, | ||||
|  | ||||
|     _keyPressHandler: function(keysym, backwards, action) { | ||||
|         if (action == Meta.KeyBindingAction.SWITCH_PANELS) | ||||
|             this._select(this._next()); | ||||
|             this._select(backwards ? this._previous() : this._next()); | ||||
|         else if (action == Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD) | ||||
|             this._select(this._previous()); | ||||
|             this._select(backwards ? this._next() : this._previous()); | ||||
|         else if (keysym == Clutter.Left) | ||||
|             this._select(this._previous()); | ||||
|         else if (keysym == Clutter.Right) | ||||
|             this._select(this._next()); | ||||
|         else | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|     }, | ||||
|  | ||||
|     _finish : function(time) { | ||||
|   | ||||
							
								
								
									
										316
									
								
								js/ui/dash.js
									
									
									
									
									
								
							
							
						
						
									
										316
									
								
								js/ui/dash.js
									
									
									
									
									
								
							| @@ -1,7 +1,6 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Signals = imports.signals; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| @@ -23,8 +22,11 @@ const DASH_ITEM_LABEL_HIDE_TIME = 0.1; | ||||
| const DASH_ITEM_HOVER_TIMEOUT = 300; | ||||
|  | ||||
| function getAppFromSource(source) { | ||||
|     if (source instanceof AppDisplay.AppIcon) { | ||||
|     if (source instanceof AppDisplay.AppWellIcon) { | ||||
|         return source.app; | ||||
|     } else if (source.metaWindow) { | ||||
|         let tracker = Shell.WindowTracker.get_default(); | ||||
|         return tracker.get_window_app(source.metaWindow); | ||||
|     } else { | ||||
|         return null; | ||||
|     } | ||||
| @@ -34,26 +36,30 @@ function getAppFromSource(source) { | ||||
| // when requesting a size | ||||
| const DashItemContainer = new Lang.Class({ | ||||
|     Name: 'DashItemContainer', | ||||
|     Extends: St.Widget, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent({ style_class: 'dash-item-container' }); | ||||
|         this.actor = new Shell.GenericContainer({ style_class: 'dash-item-container' }); | ||||
|         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._delegate = this; | ||||
|  | ||||
|         this._labelText = ""; | ||||
|         this.label = new St.Label({ style_class: 'dash-label'}); | ||||
|         this.label.hide(); | ||||
|         Main.layoutManager.addChrome(this.label); | ||||
|         this.label_actor = this.label; | ||||
|         this.actor.label_actor = this.label; | ||||
|  | ||||
|         this.child = null; | ||||
|         this._childScale = 0; | ||||
|         this._childOpacity = 0; | ||||
|         this._childScale = 1; | ||||
|         this._childOpacity = 255; | ||||
|         this.animatingOut = false; | ||||
|     }, | ||||
|  | ||||
|     vfunc_allocate: function(box, flags) { | ||||
|         this.set_allocation(box, flags); | ||||
|  | ||||
|     _allocate: function(actor, box, flags) { | ||||
|         if (this.child == null) | ||||
|             return; | ||||
|  | ||||
| @@ -75,28 +81,28 @@ const DashItemContainer = new Lang.Class({ | ||||
|         this.child.allocate(childBox, flags); | ||||
|     }, | ||||
|  | ||||
|     vfunc_get_preferred_height: function(forWidth) { | ||||
|         let themeNode = this.get_theme_node(); | ||||
|     _getPreferredHeight: function(actor, forWidth, alloc) { | ||||
|         alloc.min_size = 0; | ||||
|         alloc.natural_size = 0; | ||||
|  | ||||
|         if (this.child == null) | ||||
|             return [0, 0]; | ||||
|             return; | ||||
|  | ||||
|         forWidth = themeNode.adjust_for_width(forWidth); | ||||
|         let [minHeight, natHeight] = this.child.get_preferred_height(forWidth); | ||||
|         return themeNode.adjust_preferred_height(minHeight * this.child.scale_y, | ||||
|                                                  natHeight * this.child.scale_y); | ||||
|         alloc.min_size += minHeight * this.child.scale_y; | ||||
|         alloc.natural_size += natHeight * this.child.scale_y; | ||||
|     }, | ||||
|  | ||||
|     vfunc_get_preferred_width: function(forHeight) { | ||||
|         let themeNode = this.get_theme_node(); | ||||
|     _getPreferredWidth: function(actor, forHeight, alloc) { | ||||
|         alloc.min_size = 0; | ||||
|         alloc.natural_size = 0; | ||||
|  | ||||
|         if (this.child == null) | ||||
|             return [0, 0]; | ||||
|             return; | ||||
|  | ||||
|         forHeight = themeNode.adjust_for_height(forHeight); | ||||
|         let [minWidth, natWidth] = this.child.get_preferred_width(forHeight); | ||||
|         return themeNode.adjust_preferred_width(minWidth * this.child.scale_y, | ||||
|                                                 natWidth * this.child.scale_y); | ||||
|         alloc.min_size = minWidth * this.child.scale_y; | ||||
|         alloc.natural_size = natWidth * this.child.scale_y; | ||||
|     }, | ||||
|  | ||||
|     showLabel: function() { | ||||
| @@ -107,9 +113,9 @@ const DashItemContainer = new Lang.Class({ | ||||
|         this.label.opacity = 0; | ||||
|         this.label.show(); | ||||
|  | ||||
|         let [stageX, stageY] = this.get_transformed_position(); | ||||
|         let [stageX, stageY] = this.actor.get_transformed_position(); | ||||
|  | ||||
|         let itemHeight = this.allocation.y2 - this.allocation.y1; | ||||
|         let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1; | ||||
|  | ||||
|         let labelHeight = this.label.get_height(); | ||||
|         let yOffset = Math.floor((itemHeight - labelHeight) / 2) | ||||
| @@ -123,7 +129,7 @@ const DashItemContainer = new Lang.Class({ | ||||
|         if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) | ||||
|             x = stageX - this.label.get_width() - xOffset; | ||||
|         else | ||||
|             x = stageX + this.get_width() + xOffset; | ||||
|             x = stageX + this.actor.get_width() + xOffset; | ||||
|  | ||||
|         this.label.set_position(x, y); | ||||
|         Tweener.addTween(this.label, | ||||
| @@ -153,25 +159,22 @@ const DashItemContainer = new Lang.Class({ | ||||
|         if (this.child == actor) | ||||
|             return; | ||||
|  | ||||
|         this.destroy_all_children(); | ||||
|         this.actor.destroy_all_children(); | ||||
|  | ||||
|         this.child = actor; | ||||
|         this.add_actor(this.child); | ||||
|  | ||||
|         this.child.set_scale_with_gravity(this._childScale, this._childScale, | ||||
|                                           Clutter.Gravity.CENTER); | ||||
|         this.child.set_opacity(this._childOpacity); | ||||
|         this.actor.add_actor(this.child); | ||||
|     }, | ||||
|  | ||||
|     show: function(animate) { | ||||
|     animateIn: function() { | ||||
|         if (this.child == null) | ||||
|             return; | ||||
|  | ||||
|         let time = animate ? DASH_ANIMATION_TIME : 0; | ||||
|         this.childScale = 0; | ||||
|         this.childOpacity = 0; | ||||
|         Tweener.addTween(this, | ||||
|                          { childScale: 1.0, | ||||
|                            childOpacity: 255, | ||||
|                            time: time, | ||||
|                            time: DASH_ANIMATION_TIME, | ||||
|                            transition: 'easeOutQuad' | ||||
|                          }); | ||||
|     }, | ||||
| @@ -180,7 +183,7 @@ const DashItemContainer = new Lang.Class({ | ||||
|         if (this.label) | ||||
|             this.label.destroy(); | ||||
|  | ||||
|         this.parent(); | ||||
|         this.actor.destroy(); | ||||
|     }, | ||||
|  | ||||
|     animateOutAndDestroy: function() { | ||||
| @@ -188,18 +191,19 @@ const DashItemContainer = new Lang.Class({ | ||||
|             this.label.destroy(); | ||||
|  | ||||
|         if (this.child == null) { | ||||
|             this.destroy(); | ||||
|             this.actor.destroy(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.animatingOut = true; | ||||
|         this.childScale = 1.0; | ||||
|         Tweener.addTween(this, | ||||
|                          { childScale: 0.0, | ||||
|                            childOpacity: 0, | ||||
|                            time: DASH_ANIMATION_TIME, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            onComplete: Lang.bind(this, function() { | ||||
|                                this.destroy(); | ||||
|                                this.actor.destroy(); | ||||
|                            }) | ||||
|                          }); | ||||
|     }, | ||||
| @@ -212,7 +216,7 @@ const DashItemContainer = new Lang.Class({ | ||||
|  | ||||
|         this.child.set_scale_with_gravity(scale, scale, | ||||
|                                           Clutter.Gravity.CENTER); | ||||
|         this.queue_relayout(); | ||||
|         this.actor.queue_relayout(); | ||||
|     }, | ||||
|  | ||||
|     get childScale() { | ||||
| @@ -226,7 +230,7 @@ const DashItemContainer = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         this.child.set_opacity(opacity); | ||||
|         this.queue_redraw(); | ||||
|         this.actor.queue_redraw(); | ||||
|     }, | ||||
|  | ||||
|     get childOpacity() { | ||||
| @@ -288,7 +292,13 @@ const ShowAppsIcon = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     handleDragOver: function(source, actor, x, y, time) { | ||||
|         if (!this._canRemoveApp(getAppFromSource(source))) | ||||
|         let app = getAppFromSource(source); | ||||
|         if (app == null) | ||||
|             return DND.DragMotionResult.NO_DROP; | ||||
|  | ||||
|         let id = app.get_id(); | ||||
|         let isFavorite = AppFavorites.getAppFavorites().isFavorite(id); | ||||
|         if (!isFavorite) | ||||
|             return DND.DragMotionResult.NO_DROP; | ||||
|  | ||||
|         return DND.DragMotionResult.MOVE_DROP; | ||||
| @@ -296,7 +306,7 @@ const ShowAppsIcon = new Lang.Class({ | ||||
|  | ||||
|     acceptDrop: function(source, actor, x, y, time) { | ||||
|         let app = getAppFromSource(source); | ||||
|         if (!this._canRemoveApp(app)) | ||||
|         if (app == null) | ||||
|             return false; | ||||
|  | ||||
|         let id = app.get_id(); | ||||
| @@ -321,16 +331,6 @@ const DragPlaceholderItem = new Lang.Class({ | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const EmptyDropTargetItem = new Lang.Class({ | ||||
|     Name: 'EmptyDropTargetItem', | ||||
|     Extends: DashItemContainer, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent(); | ||||
|         this.setChild(new St.Bin({ style_class: 'empty-dash-drop-target' })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const DashActor = new Lang.Class({ | ||||
|     Name: 'DashActor', | ||||
|     Extends: St.Widget, | ||||
| @@ -361,28 +361,9 @@ const DashActor = new Lang.Class({ | ||||
|         childBox.y1 = contentBox.y2 - showAppsNatHeight; | ||||
|         childBox.y2 = contentBox.y2; | ||||
|         showAppsButton.allocate(childBox, flags); | ||||
|     }, | ||||
|  | ||||
|     vfunc_get_preferred_height: function(forWidth) { | ||||
|         // We want to request the natural height of all our children | ||||
|         // as our natural height, so we chain up to StWidget (which | ||||
|         // then calls BoxLayout), but we only request the showApps | ||||
|         // button as the minimum size | ||||
|  | ||||
|         let [, natHeight] = this.parent(forWidth); | ||||
|  | ||||
|         let themeNode = this.get_theme_node(); | ||||
|         let adjustedForWidth = themeNode.adjust_for_width(forWidth); | ||||
|         let [, showAppsButton] = this.get_children(); | ||||
|         let [minHeight, ] = showAppsButton.get_preferred_height(adjustedForWidth); | ||||
|         [minHeight, ] = themeNode.adjust_preferred_height(minHeight, natHeight); | ||||
|  | ||||
|         return [minHeight, natHeight]; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const baseIconSizes = [ 16, 22, 24, 32, 48, 64 ]; | ||||
|  | ||||
| const Dash = new Lang.Class({ | ||||
|     Name: 'Dash', | ||||
|  | ||||
| @@ -405,14 +386,12 @@ const Dash = new Lang.Class({ | ||||
|         this._container.add_actor(this._box); | ||||
|  | ||||
|         this._showAppsIcon = new ShowAppsIcon(); | ||||
|         this._showAppsIcon.childScale = 1; | ||||
|         this._showAppsIcon.childOpacity = 255; | ||||
|         this._showAppsIcon.icon.setIconSize(this.iconSize); | ||||
|         this._hookUpLabel(this._showAppsIcon); | ||||
|  | ||||
|         this.showAppsButton = this._showAppsIcon.toggleButton; | ||||
|  | ||||
|         this._container.add_actor(this._showAppsIcon); | ||||
|         this._container.add_actor(this._showAppsIcon.actor); | ||||
|  | ||||
|         this.actor = new St.Bin({ child: this._container }); | ||||
|         this.actor.connect('notify::height', Lang.bind(this, | ||||
| @@ -426,10 +405,7 @@ const Dash = new Lang.Class({ | ||||
|  | ||||
|         this._appSystem = Shell.AppSystem.get_default(); | ||||
|  | ||||
|         this._appSystem.connect('installed-changed', Lang.bind(this, function() { | ||||
|             AppFavorites.getAppFavorites().reload(); | ||||
|             this._queueRedisplay(); | ||||
|         })); | ||||
|         this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay)); | ||||
|         AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay)); | ||||
|         this._appSystem.connect('app-state-changed', Lang.bind(this, this._queueRedisplay)); | ||||
|  | ||||
| @@ -439,10 +415,12 @@ const Dash = new Lang.Class({ | ||||
|                               Lang.bind(this, this._onDragEnd)); | ||||
|         Main.overview.connect('item-drag-cancelled', | ||||
|                               Lang.bind(this, this._onDragCancelled)); | ||||
|  | ||||
|         // Translators: this is the name of the dock/favorites area on | ||||
|         // the left of the overview | ||||
|         Main.ctrlAltTabManager.addGroup(this.actor, _("Dash"), 'user-bookmarks-symbolic'); | ||||
|         Main.overview.connect('window-drag-begin', | ||||
|                               Lang.bind(this, this._onDragBegin)); | ||||
|         Main.overview.connect('window-drag-cancelled', | ||||
|                               Lang.bind(this, this._onDragCancelled)); | ||||
|         Main.overview.connect('window-drag-end', | ||||
|                               Lang.bind(this, this._onDragEnd)); | ||||
|     }, | ||||
|  | ||||
|     _onDragBegin: function() { | ||||
| @@ -451,12 +429,6 @@ const Dash = new Lang.Class({ | ||||
|             dragMotion: Lang.bind(this, this._onDragMotion) | ||||
|         }; | ||||
|         DND.addDragMonitor(this._dragMonitor); | ||||
|  | ||||
|         if (this._box.get_n_children() == 0) { | ||||
|             this._emptyDropTarget = new EmptyDropTargetItem(); | ||||
|             this._box.insert_child_at_index(this._emptyDropTarget, 0); | ||||
|             this._emptyDropTarget.show(true); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onDragCancelled: function() { | ||||
| @@ -473,7 +445,6 @@ const Dash = new Lang.Class({ | ||||
|  | ||||
|     _endDrag: function() { | ||||
|         this._clearDragPlaceholder(); | ||||
|         this._clearEmptyDropTarget(); | ||||
|         this._showAppsIcon.setDragApp(null); | ||||
|         DND.removeDragMonitor(this._dragMonitor); | ||||
|     }, | ||||
| @@ -484,7 +455,7 @@ const Dash = new Lang.Class({ | ||||
|             return DND.DragMotionResult.CONTINUE; | ||||
|  | ||||
|         let showAppsHovered = | ||||
|                 this._showAppsIcon.contains(dragEvent.targetActor); | ||||
|                 this._showAppsIcon.actor.contains(dragEvent.targetActor); | ||||
|  | ||||
|         if (!this._box.contains(dragEvent.targetActor) || showAppsHovered) | ||||
|             this._clearDragPlaceholder(); | ||||
| @@ -508,30 +479,21 @@ const Dash = new Lang.Class({ | ||||
|         Main.queueDeferredWork(this._workId); | ||||
|     }, | ||||
|  | ||||
|     _hookUpLabel: function(item, appIcon) { | ||||
|     _hookUpLabel: function(item) { | ||||
|         item.child.connect('notify::hover', Lang.bind(this, function() { | ||||
|             this._syncLabel(item, appIcon); | ||||
|             this._onHover(item); | ||||
|         })); | ||||
|  | ||||
|         let id = Main.overview.connect('hiding', Lang.bind(this, function() { | ||||
|         Main.overview.connect('hiding', Lang.bind(this, function() { | ||||
|             this._labelShowing = false; | ||||
|             item.hideLabel(); | ||||
|         })); | ||||
|         item.child.connect('destroy', function() { | ||||
|             Main.overview.disconnect(id); | ||||
|         }); | ||||
|  | ||||
|         if (appIcon) { | ||||
|             appIcon.connect('sync-tooltip', Lang.bind(this, function() { | ||||
|                 this._syncLabel(item, appIcon); | ||||
|             })); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _createAppItem: function(app) { | ||||
|         let appIcon = new AppDisplay.AppIcon(app, | ||||
|                                              { setSizeManually: true, | ||||
|                                                showLabel: false }); | ||||
|         let appIcon = new AppDisplay.AppWellIcon(app, | ||||
|                                                  { setSizeManually: true, | ||||
|                                                    showLabel: false }); | ||||
|         appIcon._draggable.connect('drag-begin', | ||||
|                                    Lang.bind(this, function() { | ||||
|                                        appIcon.actor.opacity = 50; | ||||
| @@ -548,13 +510,13 @@ const Dash = new Lang.Class({ | ||||
|         let item = new DashItemContainer(); | ||||
|         item.setChild(appIcon.actor); | ||||
|  | ||||
|         // Override default AppIcon label_actor, now the | ||||
|         // Override default AppWellIcon label_actor, now the | ||||
|         // accessible_name is set at DashItemContainer.setLabelText | ||||
|         appIcon.actor.label_actor = null; | ||||
|         item.setLabelText(app.get_name()); | ||||
|  | ||||
|         appIcon.icon.setIconSize(this.iconSize); | ||||
|         this._hookUpLabel(item, appIcon); | ||||
|         this._hookUpLabel(item); | ||||
|  | ||||
|         return item; | ||||
|     }, | ||||
| @@ -572,20 +534,16 @@ const Dash = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _syncLabel: function (item, appIcon) { | ||||
|         let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover(); | ||||
|  | ||||
|         if (shouldShow) { | ||||
|     _onHover: function (item) { | ||||
|         if (item.child.get_hover()) { | ||||
|             if (this._showLabelTimeoutId == 0) { | ||||
|                 let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT; | ||||
|                 this._showLabelTimeoutId = Mainloop.timeout_add(timeout, | ||||
|                     Lang.bind(this, function() { | ||||
|                         this._labelShowing = true; | ||||
|                         item.showLabel(); | ||||
|                         this._showLabelTimeoutId = 0; | ||||
|                         return GLib.SOURCE_REMOVE; | ||||
|                         return false; | ||||
|                     })); | ||||
|                 GLib.Source.set_name_by_id(this._showLabelTimeoutId, '[gnome-shell] item.showLabel'); | ||||
|                 if (this._resetHoverTimeoutId > 0) { | ||||
|                     Mainloop.source_remove(this._resetHoverTimeoutId); | ||||
|                     this._resetHoverTimeoutId = 0; | ||||
| @@ -600,10 +558,8 @@ const Dash = new Lang.Class({ | ||||
|                 this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT, | ||||
|                     Lang.bind(this, function() { | ||||
|                         this._labelShowing = false; | ||||
|                         this._resetHoverTimeoutId = 0; | ||||
|                         return GLib.SOURCE_REMOVE; | ||||
|                         return false; | ||||
|                     })); | ||||
|                 GLib.Source.set_name_by_id(this._resetHoverTimeoutId, '[gnome-shell] this._labelShowing'); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
| @@ -614,13 +570,13 @@ const Dash = new Lang.Class({ | ||||
|         // animating out (which means they will be destroyed at the end of | ||||
|         // the animation) | ||||
|         let iconChildren = this._box.get_children().filter(function(actor) { | ||||
|             return actor.child && | ||||
|                    actor.child._delegate && | ||||
|                    actor.child._delegate.icon && | ||||
|                    !actor.animatingOut; | ||||
|             return actor._delegate.child && | ||||
|                    actor._delegate.child._delegate && | ||||
|                    actor._delegate.child._delegate.icon && | ||||
|                    !actor._delegate.animatingOut; | ||||
|         }); | ||||
|  | ||||
|         iconChildren.push(this._showAppsIcon); | ||||
|         iconChildren.push(this._showAppsIcon.actor); | ||||
|  | ||||
|         if (this._maxHeight == -1) | ||||
|             return; | ||||
| @@ -633,30 +589,36 @@ const Dash = new Lang.Class({ | ||||
|         let availHeight = maxContent.y2 - maxContent.y1; | ||||
|         let spacing = themeNode.get_length('spacing'); | ||||
|  | ||||
|         let firstButton = iconChildren[0].child; | ||||
|         let firstIcon = firstButton._delegate.icon; | ||||
|  | ||||
|         let firstIcon = iconChildren[0]._delegate.child._delegate.icon; | ||||
|  | ||||
|         let minHeight, natHeight; | ||||
|  | ||||
|         // Enforce the current icon size during the size request | ||||
|         firstIcon.setIconSize(this.iconSize); | ||||
|         [minHeight, natHeight] = firstButton.get_preferred_height(-1); | ||||
|         // Enforce the current icon size during the size request if | ||||
|         // the icon is animating | ||||
|         if (firstIcon._animating) { | ||||
|             let [currentWidth, currentHeight] = firstIcon.icon.get_size(); | ||||
|  | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         let iconSizes = baseIconSizes.map(function(s) { | ||||
|             return s * scaleFactor; | ||||
|         }); | ||||
|             firstIcon.icon.set_size(this.iconSize, this.iconSize); | ||||
|             [minHeight, natHeight] = iconChildren[0].get_preferred_height(-1); | ||||
|  | ||||
|             firstIcon.icon.set_size(currentWidth, currentHeight); | ||||
|         } else { | ||||
|             [minHeight, natHeight] = iconChildren[0].get_preferred_height(-1); | ||||
|         } | ||||
|  | ||||
|         // Subtract icon padding and box spacing from the available height | ||||
|         availHeight -= iconChildren.length * (natHeight - this.iconSize * scaleFactor) + | ||||
|         availHeight -= iconChildren.length * (natHeight - this.iconSize) + | ||||
|                        (iconChildren.length - 1) * spacing; | ||||
|  | ||||
|         let availSize = availHeight / iconChildren.length; | ||||
|  | ||||
|         let newIconSize = baseIconSizes[0]; | ||||
|         let iconSizes = [ 16, 22, 24, 32, 48, 64 ]; | ||||
|  | ||||
|         let newIconSize = 16; | ||||
|         for (let i = 0; i < iconSizes.length; i++) { | ||||
|             if (iconSizes[i] < availSize) | ||||
|                 newIconSize = baseIconSizes[i]; | ||||
|                 newIconSize = iconSizes[i]; | ||||
|         } | ||||
|  | ||||
|         if (newIconSize == this.iconSize) | ||||
| @@ -668,7 +630,7 @@ const Dash = new Lang.Class({ | ||||
|  | ||||
|         let scale = oldIconSize / newIconSize; | ||||
|         for (let i = 0; i < iconChildren.length; i++) { | ||||
|             let icon = iconChildren[i].child._delegate.icon; | ||||
|             let icon = iconChildren[i]._delegate.child._delegate.icon; | ||||
|  | ||||
|             // Set the new size immediately, to keep the icons' sizes | ||||
|             // in sync with this.iconSize | ||||
| @@ -688,11 +650,15 @@ const Dash = new Lang.Class({ | ||||
|             icon.icon.set_size(icon.icon.width * scale, | ||||
|                                icon.icon.height * scale); | ||||
|  | ||||
|             icon._animating = true; | ||||
|             Tweener.addTween(icon.icon, | ||||
|                              { width: targetWidth, | ||||
|                                height: targetHeight, | ||||
|                                time: DASH_ANIMATION_TIME, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: function() { | ||||
|                                    icon._animating = false; | ||||
|                                } | ||||
|                              }); | ||||
|         } | ||||
|     }, | ||||
| @@ -703,13 +669,13 @@ const Dash = new Lang.Class({ | ||||
|         let running = this._appSystem.get_running(); | ||||
|  | ||||
|         let children = this._box.get_children().filter(function(actor) { | ||||
|                 return actor.child && | ||||
|                        actor.child._delegate && | ||||
|                        actor.child._delegate.app; | ||||
|                 return actor._delegate.child && | ||||
|                        actor._delegate.child._delegate && | ||||
|                        actor._delegate.child._delegate.app; | ||||
|             }); | ||||
|         // Apps currently in the dash | ||||
|         let oldApps = children.map(function(actor) { | ||||
|                 return actor.child._delegate.app; | ||||
|                 return actor._delegate.child._delegate.app; | ||||
|             }); | ||||
|         // Apps supposed to be in the dash | ||||
|         let newApps = []; | ||||
| @@ -775,7 +741,7 @@ const Dash = new Lang.Class({ | ||||
|             let insertHere = newApps[newIndex + 1] && | ||||
|                              newApps[newIndex + 1] == oldApps[oldIndex]; | ||||
|             let alreadyRemoved = removedActors.reduce(function(result, actor) { | ||||
|                 let removedApp = actor.child._delegate.app; | ||||
|                 let removedApp = actor._delegate.child._delegate.app; | ||||
|                 return result || removedApp == newApps[newIndex]; | ||||
|             }, false); | ||||
|  | ||||
| @@ -792,11 +758,11 @@ const Dash = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         for (let i = 0; i < addedItems.length; i++) | ||||
|             this._box.insert_child_at_index(addedItems[i].item, | ||||
|             this._box.insert_child_at_index(addedItems[i].item.actor, | ||||
|                                             addedItems[i].pos); | ||||
|  | ||||
|         for (let i = 0; i < removedActors.length; i++) { | ||||
|             let item = removedActors[i]; | ||||
|             let item = removedActors[i]._delegate; | ||||
|  | ||||
|             // Don't animate item removal when the overview is transitioning | ||||
|             // or hidden | ||||
| @@ -810,39 +776,25 @@ const Dash = new Lang.Class({ | ||||
|  | ||||
|         // Skip animations on first run when adding the initial set | ||||
|         // of items, to avoid all items zooming in at once | ||||
|  | ||||
|         let animate = this._shownInitially && Main.overview.visible && | ||||
|             !Main.overview.animationInProgress; | ||||
|  | ||||
|         if (!this._shownInitially) | ||||
|         if (!this._shownInitially) { | ||||
|             this._shownInitially = true; | ||||
|  | ||||
|         for (let i = 0; i < addedItems.length; i++) { | ||||
|             addedItems[i].item.show(animate); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 | ||||
|         // Without it, StBoxLayout may use a stale size cache | ||||
|         this._box.queue_relayout(); | ||||
|         // Don't animate item addition when the overview is transitioning | ||||
|         // or hidden | ||||
|         if (!Main.overview.visible || Main.overview.animationInProgress) | ||||
|             return; | ||||
|  | ||||
|         for (let i = 0; i < addedItems.length; i++) | ||||
|             addedItems[i].item.animateIn(); | ||||
|     }, | ||||
|  | ||||
|     _clearDragPlaceholder: function() { | ||||
|         if (this._dragPlaceholder) { | ||||
|             this._animatingPlaceholdersCount++; | ||||
|             this._dragPlaceholder.animateOutAndDestroy(); | ||||
|             this._dragPlaceholder.connect('destroy', | ||||
|                 Lang.bind(this, function() { | ||||
|                     this._animatingPlaceholdersCount--; | ||||
|                 })); | ||||
|             this._dragPlaceholder = null; | ||||
|         } | ||||
|         this._dragPlaceholderPos = -1; | ||||
|     }, | ||||
|  | ||||
|     _clearEmptyDropTarget: function() { | ||||
|         if (this._emptyDropTarget) { | ||||
|             this._emptyDropTarget.animateOutAndDestroy(); | ||||
|             this._emptyDropTarget = null; | ||||
|             this._dragPlaceholderPos = -1; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -866,22 +818,27 @@ const Dash = new Lang.Class({ | ||||
|         // the remove target has the same size as "normal" items, we don't | ||||
|         // need to do the same adjustment there. | ||||
|         if (this._dragPlaceholder) { | ||||
|             boxHeight -= this._dragPlaceholder.height; | ||||
|             boxHeight -= this._dragPlaceholder.actor.height; | ||||
|             numChildren--; | ||||
|         } | ||||
|  | ||||
|         let pos; | ||||
|         if (!this._emptyDropTarget) | ||||
|             pos = Math.floor(y * numChildren / boxHeight); | ||||
|         else | ||||
|             pos = 0; // always insert at the top when dash is empty | ||||
|         let pos = Math.floor(y * numChildren / boxHeight); | ||||
|  | ||||
|         if (pos != this._dragPlaceholderPos && pos <= numFavorites && this._animatingPlaceholdersCount == 0) { | ||||
|             this._dragPlaceholderPos = pos; | ||||
|  | ||||
|             // Don't allow positioning before or after self | ||||
|             if (favPos != -1 && (pos == favPos || pos == favPos + 1)) { | ||||
|                 this._clearDragPlaceholder(); | ||||
|                 if (this._dragPlaceholder) { | ||||
|                     this._dragPlaceholder.animateOutAndDestroy(); | ||||
|                     this._animatingPlaceholdersCount++; | ||||
|                     this._dragPlaceholder.actor.connect('destroy', | ||||
|                         Lang.bind(this, function() { | ||||
|                             this._animatingPlaceholdersCount--; | ||||
|                         })); | ||||
|                 } | ||||
|                 this._dragPlaceholder = null; | ||||
|  | ||||
|                 return DND.DragMotionResult.CONTINUE; | ||||
|             } | ||||
|  | ||||
| @@ -890,7 +847,7 @@ const Dash = new Lang.Class({ | ||||
|             // an animation | ||||
|             let fadeIn; | ||||
|             if (this._dragPlaceholder) { | ||||
|                 this._dragPlaceholder.destroy(); | ||||
|                 this._dragPlaceholder.actor.destroy(); | ||||
|                 fadeIn = false; | ||||
|             } else { | ||||
|                 fadeIn = true; | ||||
| @@ -899,16 +856,17 @@ const Dash = new Lang.Class({ | ||||
|             this._dragPlaceholder = new DragPlaceholderItem(); | ||||
|             this._dragPlaceholder.child.set_width (this.iconSize); | ||||
|             this._dragPlaceholder.child.set_height (this.iconSize / 2); | ||||
|             this._box.insert_child_at_index(this._dragPlaceholder, | ||||
|             this._box.insert_child_at_index(this._dragPlaceholder.actor, | ||||
|                                             this._dragPlaceholderPos); | ||||
|             this._dragPlaceholder.show(fadeIn); | ||||
|             if (fadeIn) | ||||
|                 this._dragPlaceholder.animateIn(); | ||||
|         } | ||||
|  | ||||
|         // Remove the drag placeholder if we are not in the | ||||
|         // "favorites zone" | ||||
|         if (pos > numFavorites) | ||||
|         if (pos > numFavorites && this._dragPlaceholder) { | ||||
|             this._clearDragPlaceholder(); | ||||
|  | ||||
|         } | ||||
|         if (!this._dragPlaceholder) | ||||
|             return DND.DragMotionResult.NO_DROP; | ||||
|  | ||||
| @@ -939,10 +897,10 @@ const Dash = new Lang.Class({ | ||||
|         let children = this._box.get_children(); | ||||
|         for (let i = 0; i < this._dragPlaceholderPos; i++) { | ||||
|             if (this._dragPlaceholder && | ||||
|                 children[i] == this._dragPlaceholder) | ||||
|                 children[i] == this._dragPlaceholder.actor) | ||||
|                 continue; | ||||
|  | ||||
|             let childId = children[i].child._delegate.app.get_id(); | ||||
|             let childId = children[i]._delegate.child._delegate.app.get_id(); | ||||
|             if (childId == id) | ||||
|                 continue; | ||||
|             if (childId in favorites) | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GnomeDesktop = imports.gi.GnomeDesktop; | ||||
| const GObject = imports.gi.GObject; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Cairo = imports.cairo; | ||||
| @@ -19,7 +18,8 @@ const PanelMenu = imports.ui.panelMenu; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
| const Calendar = imports.ui.calendar; | ||||
|  | ||||
| function _onVertSepRepaint(area) { | ||||
| function _onVertSepRepaint (area) | ||||
| { | ||||
|     let cr = area.get_context(); | ||||
|     let themeNode = area.get_theme_node(); | ||||
|     let [width, height] = area.get_surface_size(); | ||||
| @@ -32,8 +32,7 @@ function _onVertSepRepaint(area) { | ||||
|     cr.setDash([1, 3], 1); // Hard-code for now | ||||
|     cr.setLineWidth(stippleWidth); | ||||
|     cr.stroke(); | ||||
|     cr.$dispose(); | ||||
| } | ||||
| }; | ||||
|  | ||||
| const DateMenuButton = new Lang.Class({ | ||||
|     Name: 'DateMenuButton', | ||||
| @@ -49,31 +48,27 @@ const DateMenuButton = new Lang.Class({ | ||||
|             menuAlignment = 1.0 - menuAlignment; | ||||
|         this.parent(menuAlignment); | ||||
|  | ||||
|         this._clockDisplay = new St.Label({ y_align: Clutter.ActorAlign.CENTER }); | ||||
|         this.actor.label_actor = this._clockDisplay; | ||||
|         this.actor.add_actor(this._clockDisplay); | ||||
|         this.actor.add_style_class_name ('clock-display'); | ||||
|         // At this moment calendar menu is not keyboard navigable at | ||||
|         // all (so not accessible), so it doesn't make sense to set as | ||||
|         // role ATK_ROLE_MENU like other elements of the panel. | ||||
|         this.actor.accessible_role = Atk.Role.LABEL; | ||||
|  | ||||
|         hbox = new St.BoxLayout({ name: 'calendarArea' }); | ||||
|         this.menu.box.add_child(hbox); | ||||
|         this._clockDisplay = new St.Label(); | ||||
|         this.actor.add_actor(this._clockDisplay); | ||||
|  | ||||
|         hbox = new St.BoxLayout({name: 'calendarArea' }); | ||||
|         this.menu.addActor(hbox); | ||||
|  | ||||
|         // Fill up the first column | ||||
|  | ||||
|         vbox = new St.BoxLayout({vertical: true, x_expand: true, y_expand: true }); | ||||
|         vbox = new St.BoxLayout({vertical: true}); | ||||
|         hbox.add(vbox); | ||||
|  | ||||
|         // Date | ||||
|         // Having the ability to go to the current date if the user is already | ||||
|         // on the current date can be confusing. So don't make the button reactive | ||||
|         // until the selected date changes. | ||||
|         this._date = new St.Button({ style_class: 'datemenu-date-label', | ||||
|                                      reactive: false | ||||
|                                    }); | ||||
|         this._date.connect('clicked', | ||||
|                            Lang.bind(this, function() { | ||||
|                                this._calendar.setDate(new Date(), false); | ||||
|                            })); | ||||
|         vbox.add(this._date, { x_fill: false  }); | ||||
|         this._date = new St.Label(); | ||||
|         this.actor.label_actor = this._clockDisplay; | ||||
|         this._date.style_class = 'datemenu-date-label'; | ||||
|         vbox.add(this._date); | ||||
|  | ||||
|         this._eventList = new Calendar.EventsList(); | ||||
|         this._calendar = new Calendar.Calendar(); | ||||
| @@ -85,29 +80,17 @@ const DateMenuButton = new Lang.Class({ | ||||
|                                   // and the calender makes those dates unclickable when instantiated with | ||||
|                                   // a null event source | ||||
|                                    this._eventList.setDate(date); | ||||
|  | ||||
|                                    // Make the button reactive only if the selected date is not the current date. | ||||
|                                    this._date.can_focus = this._date.reactive = !this._isToday(date) | ||||
|                                })); | ||||
|         vbox.add(this._calendar.actor); | ||||
|  | ||||
|         let separator = new PopupMenu.PopupSeparatorMenuItem(); | ||||
|         vbox.add(separator.actor, { y_align: St.Align.END, expand: true, y_fill: false }); | ||||
|  | ||||
|         this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar")); | ||||
|         this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate)); | ||||
|         vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: false, y_fill: false}); | ||||
|  | ||||
|         this._openClocksItem = new PopupMenu.PopupMenuItem(_("Open Clocks")); | ||||
|         this._openClocksItem.connect('activate', Lang.bind(this, this._onOpenClocksActivate)); | ||||
|         vbox.add(this._openClocksItem.actor, {y_align: St.Align.END, expand: false, y_fill: false}); | ||||
|  | ||||
|         Shell.AppSystem.get_default().connect('installed-changed', | ||||
|                                               Lang.bind(this, this._appInstalledChanged)); | ||||
|  | ||||
|         item = this.menu.addSettingsAction(_("Date & Time Settings"), 'gnome-datetime-panel.desktop'); | ||||
|         item = this.menu.addSettingsAction(_("Date and Time Settings"), 'gnome-datetime-panel.desktop'); | ||||
|         if (item) { | ||||
|             let separator = new PopupMenu.PopupSeparatorMenuItem(); | ||||
|             separator.setColumnWidths(1); | ||||
|             vbox.add(separator.actor, {y_align: St.Align.END, expand: true, y_fill: false}); | ||||
|  | ||||
|             item.actor.show_on_set_parent = false; | ||||
|             item.actor.can_focus = false; | ||||
|             item.actor.reparent(vbox); | ||||
|             this._dateAndTimeSeparator = separator; | ||||
|         } | ||||
| @@ -118,72 +101,80 @@ const DateMenuButton = new Lang.Class({ | ||||
|         hbox.add(this._separator); | ||||
|  | ||||
|         // Fill up the second column | ||||
|         hbox.add(this._eventList.actor, { expand: true, y_fill: false, y_align: St.Align.START }); | ||||
|         vbox = new St.BoxLayout({ name: 'calendarEventsArea', | ||||
|                                   vertical: true }); | ||||
|         hbox.add(vbox, { expand: true }); | ||||
|  | ||||
|         // Event list | ||||
|         vbox.add(this._eventList.actor, { expand: true }); | ||||
|  | ||||
|         this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar")); | ||||
|         this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate)); | ||||
|         this._openCalendarItem.actor.can_focus = false; | ||||
|         vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: true, y_fill: false}); | ||||
|  | ||||
|         this._calendarSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.office.calendar' }); | ||||
|         this._calendarSettings.connect('changed::exec', | ||||
|                                        Lang.bind(this, this._calendarSettingsChanged)); | ||||
|         this._calendarSettingsChanged(); | ||||
|  | ||||
|         // Whenever the menu is opened, select today | ||||
|         this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) { | ||||
|             if (isOpen) { | ||||
|                 let now = new Date(); | ||||
|                 this._calendar.setDate(now); | ||||
|  | ||||
|                 /* Translators: This is the date format to use when the calendar popup is | ||||
|                  * shown - it is shown just below the time in the shell (e.g. "Tue 9:29 AM"). | ||||
|                 /* Passing true to setDate() forces events to be reloaded. We | ||||
|                  * want this behavior, because | ||||
|                  * | ||||
|                  *   o It will cause activation of the calendar server which is | ||||
|                  *     useful if it has crashed | ||||
|                  * | ||||
|                  *   o It will cause the calendar server to reload events which | ||||
|                  *     is useful if dynamic updates are not supported or not | ||||
|                  *     properly working | ||||
|                  * | ||||
|                  * Since this only happens when the menu is opened, the cost | ||||
|                  * isn't very big. | ||||
|                  */ | ||||
|                 let dateFormat = _("%A %B %e, %Y"); | ||||
|                 this._date.set_label(now.toLocaleFormat(dateFormat)); | ||||
|                 this._calendar.setDate(now, true); | ||||
|                 // No need to update this._eventList as ::selected-date-changed | ||||
|                 // signal will fire | ||||
|             } | ||||
|         })); | ||||
|  | ||||
|         // Done with hbox for calendar and event list | ||||
|  | ||||
|         this._clock = new GnomeDesktop.WallClock(); | ||||
|         this._clock.bind_property('clock', this._clockDisplay, 'text', GObject.BindingFlags.SYNC_CREATE); | ||||
|         this._clock.connect('notify::clock', Lang.bind(this, this._updateClockAndDate)); | ||||
|         this._updateClockAndDate(); | ||||
|  | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); | ||||
|         this._sessionUpdated(); | ||||
|     }, | ||||
|  | ||||
|     _isToday: function(date) { | ||||
|         let now = new Date(); | ||||
|         return now.getYear() == date.getYear() && | ||||
|                now.getMonth() == date.getMonth() && | ||||
|                now.getDate() == date.getDate(); | ||||
|     _calendarSettingsChanged: function() { | ||||
|         let exec = this._calendarSettings.get_string('exec'); | ||||
|         let fullExec = GLib.find_program_in_path(exec); | ||||
|         this._openCalendarItem.actor.visible = fullExec != null; | ||||
|     }, | ||||
|  | ||||
|     _appInstalledChanged: function() { | ||||
|         this._calendarApp = undefined; | ||||
|         this._updateEventsVisibility(); | ||||
|     }, | ||||
|  | ||||
|     _updateEventsVisibility: function() { | ||||
|         let visible = this._eventSource.hasCalendars; | ||||
|         this._openCalendarItem.actor.visible = visible && | ||||
|             (this._getCalendarApp() != null); | ||||
|         this._openClocksItem.actor.visible = visible && | ||||
|             (this._getClockApp() != null); | ||||
|     _setEventsVisibility: function(visible) { | ||||
|         this._openCalendarItem.actor.visible = visible; | ||||
|         this._separator.visible = visible; | ||||
|         this._eventList.actor.visible = visible; | ||||
|         if (visible) { | ||||
|             let alignment = 0.25; | ||||
|             if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) | ||||
|                 alignment = 1.0 - alignment; | ||||
|             this.menu._arrowAlignment = alignment; | ||||
|           let alignment = 0.25; | ||||
|           if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) | ||||
|             alignment = 1.0 - alignment; | ||||
|           this.menu._arrowAlignment = alignment; | ||||
|           this._eventList.actor.get_parent().show(); | ||||
|         } else { | ||||
|             this.menu._arrowAlignment = 0.5; | ||||
|           this.menu._arrowAlignment = 0.5; | ||||
|           this._eventList.actor.get_parent().hide(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _setEventSource: function(eventSource) { | ||||
|         if (this._eventSource) | ||||
|             this._eventSource.destroy(); | ||||
|  | ||||
|         this._calendar.setEventSource(eventSource); | ||||
|         this._eventList.setEventSource(eventSource); | ||||
|  | ||||
|         this._eventSource = eventSource; | ||||
|         this._eventSource.connect('notify::has-calendars', Lang.bind(this, function() { | ||||
|             this._updateEventsVisibility(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _sessionUpdated: function() { | ||||
| @@ -192,47 +183,46 @@ const DateMenuButton = new Lang.Class({ | ||||
|         if (showEvents) { | ||||
|             eventSource = new Calendar.DBusEventSource(); | ||||
|         } else { | ||||
|             eventSource = new Calendar.EmptyEventSource(); | ||||
|             eventSource = null; | ||||
|         } | ||||
|         this._setEventSource(eventSource); | ||||
|         this._updateEventsVisibility(); | ||||
|         this._setEventsVisibility(showEvents); | ||||
|  | ||||
|         // This needs to be handled manually, as the code to | ||||
|         // autohide separators doesn't work across the vbox | ||||
|         this._dateAndTimeSeparator.actor.visible = Main.sessionMode.allowSettings; | ||||
|     }, | ||||
|  | ||||
|     _getCalendarApp: function() { | ||||
|         if (this._calendarApp !== undefined) | ||||
|             return this._calendarApp; | ||||
|  | ||||
|         let apps = Gio.AppInfo.get_recommended_for_type('text/calendar'); | ||||
|         if (apps && (apps.length > 0)) { | ||||
|             let app = Gio.AppInfo.get_default_for_type('text/calendar', false); | ||||
|             let defaultInRecommended = apps.some(function(a) { return a.equal(app); }); | ||||
|             this._calendarApp = defaultInRecommended ? app : apps[0]; | ||||
|         } else { | ||||
|             this._calendarApp = null; | ||||
|         } | ||||
|         return this._calendarApp; | ||||
|     }, | ||||
|  | ||||
|     _getClockApp: function() { | ||||
|         return Shell.AppSystem.get_default().lookup_app('org.gnome.clocks.desktop'); | ||||
|     _updateClockAndDate: function() { | ||||
|         this._clockDisplay.set_text(this._clock.clock); | ||||
|         /* Translators: This is the date format to use when the calendar popup is | ||||
|          * shown - it is shown just below the time in the shell (e.g. "Tue 9:29 AM"). | ||||
|          */ | ||||
|         let dateFormat = _("%A %B %e, %Y"); | ||||
|         let displayDate = new Date(); | ||||
|         this._date.set_text(displayDate.toLocaleFormat(dateFormat)); | ||||
|     }, | ||||
|  | ||||
|     _onOpenCalendarActivate: function() { | ||||
|         this.menu.close(); | ||||
|  | ||||
|         let app = this._getCalendarApp(); | ||||
|         if (app.get_id() == 'evolution.desktop') | ||||
|             app = Gio.DesktopAppInfo.new('evolution-calendar.desktop'); | ||||
|         app.launch([], global.create_app_launch_context(0, -1)); | ||||
|     }, | ||||
|  | ||||
|     _onOpenClocksActivate: function() { | ||||
|         this.menu.close(); | ||||
|         let app = this._getClockApp(); | ||||
|         app.activate(); | ||||
|         let tool = this._calendarSettings.get_string('exec'); | ||||
|         if (tool.length == 0 || tool.substr(0, 9) == 'evolution') { | ||||
|             // TODO: pass the selected day | ||||
|             let app = Shell.AppSystem.get_default().lookup_app('evolution-calendar.desktop'); | ||||
|             app.activate(); | ||||
|         } else { | ||||
|             let needTerm = this._calendarSettings.get_boolean('needs-term'); | ||||
|             if (needTerm) { | ||||
|                 let terminalSettings = new Gio.Settings({ schema: 'org.gnome.desktop.default-applications.terminal' }); | ||||
|                 let term = terminalSettings.get_string('exec'); | ||||
|                 let arg = terminalSettings.get_string('exec-arg'); | ||||
|                 if (arg != '') | ||||
|                     Util.spawn([term, arg, tool]); | ||||
|                 else | ||||
|                     Util.spawn([term, tool]); | ||||
|             } else { | ||||
|                 Util.spawnCommandLine(tool) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|   | ||||
							
								
								
									
										235
									
								
								js/ui/dnd.js
									
									
									
									
									
								
							
							
						
						
									
										235
									
								
								js/ui/dnd.js
									
									
									
									
									
								
							| @@ -1,11 +1,9 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const St = imports.gi.St; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| const Tweener = imports.ui.tweener; | ||||
| @@ -28,9 +26,9 @@ const DragMotionResult = { | ||||
| }; | ||||
|  | ||||
| const DRAG_CURSOR_MAP = { | ||||
|     0: Meta.Cursor.DND_UNSUPPORTED_TARGET, | ||||
|     1: Meta.Cursor.DND_COPY, | ||||
|     2: Meta.Cursor.DND_MOVE | ||||
|     0: Shell.Cursor.DND_UNSUPPORTED_TARGET, | ||||
|     1: Shell.Cursor.DND_COPY, | ||||
|     2: Shell.Cursor.DND_MOVE | ||||
| }; | ||||
|  | ||||
| const DragDropResult = { | ||||
| @@ -45,7 +43,9 @@ let dragMonitors = []; | ||||
|  | ||||
| function _getEventHandlerActor() { | ||||
|     if (!eventHandlerActor) { | ||||
|         eventHandlerActor = new Clutter.Actor({ width: 0, height: 0 }); | ||||
|         eventHandlerActor = new Clutter.Rectangle(); | ||||
|         eventHandlerActor.width = 0; | ||||
|         eventHandlerActor.height = 0; | ||||
|         Main.uiGroup.add_actor(eventHandlerActor); | ||||
|         // We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen | ||||
|         // when you've grabbed the pointer. | ||||
| @@ -85,8 +85,11 @@ const _Draggable = new Lang.Class({ | ||||
|  | ||||
|         this.actor.connect('destroy', Lang.bind(this, function() { | ||||
|             this._actorDestroyed = true; | ||||
|  | ||||
|             if (this._dragInProgress && this._dragCancellable) | ||||
|             // If the drag actor is destroyed and we were going to fix | ||||
|             // up its hover state, fix up the parent hover state instead | ||||
|             if (this.actor == this._firstLeaveActor) | ||||
|                 this._firstLeaveActor = this._dragOrigParent; | ||||
|             if (this._dragInProgress) | ||||
|                 this._cancelDrag(global.get_current_time()); | ||||
|             this.disconnectAll(); | ||||
|         })); | ||||
| @@ -99,17 +102,22 @@ const _Draggable = new Lang.Class({ | ||||
|         this._buttonDown = false; // The mouse button has been pressed and has not yet been released. | ||||
|         this._dragInProgress = false; // The drag has been started, and has not been dropped or cancelled yet. | ||||
|         this._animationInProgress = false; // The drag is over and the item is in the process of animating to its original position (snapping back or reverting). | ||||
|         this._dragCancellable = true; | ||||
|  | ||||
|         // During the drag, we eat enter/leave events so that actors don't prelight. | ||||
|         // But we remember the actors that we first left/last entered so we can | ||||
|         // fix up the hover state after the drag ends. | ||||
|         this._firstLeaveActor = null; | ||||
|         this._lastEnterActor = null; | ||||
|  | ||||
|         this._eventsGrabbed = false; | ||||
|     }, | ||||
|  | ||||
|     _onButtonPress : function (actor, event) { | ||||
|         if (event.get_button() != 1) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         if (Tweener.getTweenCount(actor)) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         this._buttonDown = true; | ||||
|         this._grabActor(); | ||||
| @@ -118,7 +126,7 @@ const _Draggable = new Lang.Class({ | ||||
|         this._dragStartX = stageX; | ||||
|         this._dragStartY = stageY; | ||||
|  | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _grabActor: function() { | ||||
| @@ -128,26 +136,25 @@ const _Draggable = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _ungrabActor: function() { | ||||
|         Clutter.ungrab_pointer(); | ||||
|         if (!this._onEventId) | ||||
|             return; | ||||
|  | ||||
|         Clutter.ungrab_pointer(); | ||||
|         this.actor.disconnect(this._onEventId); | ||||
|         this._onEventId = null; | ||||
|     }, | ||||
|  | ||||
|     _grabEvents: function() { | ||||
|         if (!this._eventsGrabbed) { | ||||
|             this._eventsGrabbed = Main.pushModal(_getEventHandlerActor()); | ||||
|             if (this._eventsGrabbed) | ||||
|                 Clutter.grab_pointer(_getEventHandlerActor()); | ||||
|             Clutter.grab_pointer(_getEventHandlerActor()); | ||||
|             Clutter.grab_keyboard(_getEventHandlerActor()); | ||||
|             this._eventsGrabbed = true; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _ungrabEvents: function() { | ||||
|         if (this._eventsGrabbed) { | ||||
|             Clutter.ungrab_pointer(); | ||||
|             Main.popModal(_getEventHandlerActor()); | ||||
|             Clutter.ungrab_keyboard(); | ||||
|             this._eventsGrabbed = false; | ||||
|         } | ||||
|     }, | ||||
| @@ -164,11 +171,11 @@ const _Draggable = new Lang.Class({ | ||||
|             } else if (this._dragActor != null && !this._animationInProgress) { | ||||
|                 // Drag must have been cancelled with Esc. | ||||
|                 this._dragComplete(); | ||||
|                 return Clutter.EVENT_STOP; | ||||
|                 return true; | ||||
|             } else { | ||||
|                 // Drag has never started. | ||||
|                 this._ungrabActor(); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|                 return false; | ||||
|             } | ||||
|         // We intercept MOTION event to figure out if the drag has started and to draw | ||||
|         // this._dragActor under the pointer when dragging is in progress | ||||
| @@ -184,24 +191,16 @@ const _Draggable = new Lang.Class({ | ||||
|             let symbol = event.get_key_symbol(); | ||||
|             if (symbol == Clutter.Escape) { | ||||
|                 this._cancelDrag(event.get_time()); | ||||
|                 return Clutter.EVENT_STOP; | ||||
|                 return true; | ||||
|             } | ||||
|         } else if (event.type() == Clutter.EventType.LEAVE) { | ||||
|             if (this._firstLeaveActor == null) | ||||
|                 this._firstLeaveActor = event.get_source(); | ||||
|         } else if (event.type() == Clutter.EventType.ENTER) { | ||||
|             this._lastEnterActor = event.get_source(); | ||||
|         } | ||||
|  | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * fakeRelease: | ||||
|      * | ||||
|      * Fake a release event. | ||||
|      * Must be called if you want to intercept release events on draggable | ||||
|      * actors for other purposes (for example if you're using | ||||
|      * PopupMenu.ignoreRelease()) | ||||
|      */ | ||||
|     fakeRelease: function() { | ||||
|         this._buttonDown = false; | ||||
|         this._ungrabActor(); | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -229,14 +228,14 @@ const _Draggable = new Lang.Class({ | ||||
|         if (this._onEventId) | ||||
|             this._ungrabActor(); | ||||
|         this._grabEvents(); | ||||
|         global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG); | ||||
|         global.set_cursor(Shell.Cursor.DND_IN_DRAG); | ||||
|  | ||||
|         this._dragX = this._dragStartX = stageX; | ||||
|         this._dragY = this._dragStartY = stageY; | ||||
|  | ||||
|         if (this.actor._delegate && this.actor._delegate.getDragActor) { | ||||
|             this._dragActor = this.actor._delegate.getDragActor(); | ||||
|             Main.uiGroup.add_child(this._dragActor); | ||||
|             this._dragActor.reparent(Main.uiGroup); | ||||
|             this._dragActor.raise_top(); | ||||
|             Shell.util_set_hidden_from_pick(this._dragActor, true); | ||||
|  | ||||
| @@ -275,20 +274,19 @@ const _Draggable = new Lang.Class({ | ||||
|             this._dragOrigY = this._dragActor.y; | ||||
|             this._dragOrigScale = this._dragActor.scale_x; | ||||
|  | ||||
|             // Set the actor's scale such that it will keep the same | ||||
|             // transformed size when it's reparented to the uiGroup | ||||
|             let [scaledWidth, scaledHeight] = this.actor.get_transformed_size(); | ||||
|             this._dragActor.set_scale(scaledWidth / this.actor.width, | ||||
|                                       scaledHeight / this.actor.height); | ||||
|             this._dragActor.reparent(Main.uiGroup); | ||||
|             this._dragActor.raise_top(); | ||||
|             Shell.util_set_hidden_from_pick(this._dragActor, true); | ||||
|  | ||||
|             let [actorStageX, actorStageY] = this.actor.get_transformed_position(); | ||||
|             this._dragOffsetX = actorStageX - this._dragStartX; | ||||
|             this._dragOffsetY = actorStageY - this._dragStartY; | ||||
|  | ||||
|             this._dragOrigParent.remove_actor(this._dragActor); | ||||
|             Main.uiGroup.add_child(this._dragActor); | ||||
|             this._dragActor.raise_top(); | ||||
|             Shell.util_set_hidden_from_pick(this._dragActor, true); | ||||
|             // Set the actor's scale such that it will keep the same | ||||
|             // transformed size when it's reparented to the uiGroup | ||||
|             let [scaledWidth, scaledHeight] = this.actor.get_transformed_size(); | ||||
|             this.actor.set_scale(scaledWidth / this.actor.width, | ||||
|                                  scaledHeight / this.actor.height); | ||||
|         } | ||||
|  | ||||
|         this._dragOrigOpacity = this._dragActor.opacity; | ||||
| @@ -345,67 +343,60 @@ const _Draggable = new Lang.Class({ | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _updateDragHover : function () { | ||||
|         this._updateHoverId = 0; | ||||
|         let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL, | ||||
|                                                                   this._dragX, this._dragY); | ||||
|         let dragEvent = { | ||||
|             x: this._dragX, | ||||
|             y: this._dragY, | ||||
|             dragActor: this._dragActor, | ||||
|             source: this.actor._delegate, | ||||
|             targetActor: target | ||||
|         }; | ||||
|         for (let i = 0; i < dragMonitors.length; i++) { | ||||
|             let motionFunc = dragMonitors[i].dragMotion; | ||||
|             if (motionFunc) { | ||||
|                 let result = motionFunc(dragEvent); | ||||
|                 if (result != DragMotionResult.CONTINUE) { | ||||
|                     global.screen.set_cursor(DRAG_CURSOR_MAP[result]); | ||||
|                     return GLib.SOURCE_REMOVE; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         while (target) { | ||||
|             if (target._delegate && target._delegate.handleDragOver) { | ||||
|                 let [r, targX, targY] = target.transform_stage_point(this._dragX, this._dragY); | ||||
|                 // We currently loop through all parents on drag-over even if one of the children has handled it. | ||||
|                 // We can check the return value of the function and break the loop if it's true if we don't want | ||||
|                 // to continue checking the parents. | ||||
|                 let result = target._delegate.handleDragOver(this.actor._delegate, | ||||
|                                                              this._dragActor, | ||||
|                                                              targX, | ||||
|                                                              targY, | ||||
|                                                              0); | ||||
|                 if (result != DragMotionResult.CONTINUE) { | ||||
|                     global.screen.set_cursor(DRAG_CURSOR_MAP[result]); | ||||
|                     return GLib.SOURCE_REMOVE; | ||||
|                 } | ||||
|             } | ||||
|             target = target.get_parent(); | ||||
|         } | ||||
|         global.screen.set_cursor(Meta.Cursor.DND_IN_DRAG); | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _queueUpdateDragHover: function() { | ||||
|         if (this._updateHoverId) | ||||
|             return; | ||||
|  | ||||
|         this._updateHoverId = GLib.idle_add(GLib.PRIORITY_DEFAULT, | ||||
|                                             Lang.bind(this, this._updateDragHover)); | ||||
|         GLib.Source.set_name_by_id(this._updateHoverId, '[gnome-shell] this._updateDragHover'); | ||||
|     }, | ||||
|  | ||||
|     _updateDragPosition : function (event) { | ||||
|         let [stageX, stageY] = event.get_coords(); | ||||
|         this._dragX = stageX; | ||||
|         this._dragY = stageY; | ||||
|         this._dragActor.set_position(stageX + this._dragOffsetX, | ||||
|                                      stageY + this._dragOffsetY); | ||||
|  | ||||
|         this._queueUpdateDragHover(); | ||||
|         // If we are dragging, update the position | ||||
|         if (this._dragActor) { | ||||
|             this._dragActor.set_position(stageX + this._dragOffsetX, | ||||
|                                          stageY + this._dragOffsetY); | ||||
|  | ||||
|             let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL, | ||||
|                                                                       stageX, stageY); | ||||
|  | ||||
|             // We call observers only once per motion with the innermost | ||||
|             // target actor. If necessary, the observer can walk the | ||||
|             // parent itself. | ||||
|             let dragEvent = { | ||||
|                 x: stageX, | ||||
|                 y: stageY, | ||||
|                 dragActor: this._dragActor, | ||||
|                 source: this.actor._delegate, | ||||
|                 targetActor: target | ||||
|             }; | ||||
|             for (let i = 0; i < dragMonitors.length; i++) { | ||||
|                 let motionFunc = dragMonitors[i].dragMotion; | ||||
|                 if (motionFunc) { | ||||
|                     let result = motionFunc(dragEvent); | ||||
|                     if (result != DragMotionResult.CONTINUE) { | ||||
|                         global.set_cursor(DRAG_CURSOR_MAP[result]); | ||||
|                         return true; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             while (target) { | ||||
|                 if (target._delegate && target._delegate.handleDragOver) { | ||||
|                     let [r, targX, targY] = target.transform_stage_point(stageX, stageY); | ||||
|                     // We currently loop through all parents on drag-over even if one of the children has handled it. | ||||
|                     // We can check the return value of the function and break the loop if it's true if we don't want | ||||
|                     // to continue checking the parents. | ||||
|                     let result = target._delegate.handleDragOver(this.actor._delegate, | ||||
|                                                                  this._dragActor, | ||||
|                                                                  targX, | ||||
|                                                                  targY, | ||||
|                                                                  event.get_time()); | ||||
|                     if (result != DragMotionResult.CONTINUE) { | ||||
|                         global.set_cursor(DRAG_CURSOR_MAP[result]); | ||||
|                         return true; | ||||
|                     } | ||||
|                 } | ||||
|                 target = target.get_parent(); | ||||
|             } | ||||
|             global.set_cursor(Shell.Cursor.DND_IN_DRAG); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
| @@ -434,11 +425,6 @@ const _Draggable = new Lang.Class({ | ||||
|                 } | ||||
|         } | ||||
|  | ||||
|         // At this point it is too late to cancel a drag by destroying | ||||
|         // the actor, the fate of which is decided by acceptDrop and its | ||||
|         // side-effects | ||||
|         this._dragCancellable = false; | ||||
|  | ||||
|         while (target) { | ||||
|             if (target._delegate && target._delegate.acceptDrop) { | ||||
|                 let [r, targX, targY] = target.transform_stage_point(dropX, dropY); | ||||
| @@ -447,6 +433,8 @@ const _Draggable = new Lang.Class({ | ||||
|                                                 targX, | ||||
|                                                 targY, | ||||
|                                                 event.get_time())) { | ||||
|                     if (this._actorDestroyed) | ||||
|                         return true; | ||||
|                     // If it accepted the drop without taking the actor, | ||||
|                     // handle it ourselves. | ||||
|                     if (this._dragActor.get_parent() == Main.uiGroup) { | ||||
| @@ -458,7 +446,7 @@ const _Draggable = new Lang.Class({ | ||||
|                     } | ||||
|  | ||||
|                     this._dragInProgress = false; | ||||
|                     global.screen.set_cursor(Meta.Cursor.DEFAULT); | ||||
|                     global.unset_cursor(); | ||||
|                     this.emit('drag-end', event.get_time(), true); | ||||
|                     this._dragComplete(); | ||||
|                     return true; | ||||
| @@ -510,7 +498,7 @@ const _Draggable = new Lang.Class({ | ||||
|         let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation(); | ||||
|  | ||||
|         if (this._actorDestroyed) { | ||||
|             global.screen.set_cursor(Meta.Cursor.DEFAULT); | ||||
|             global.unset_cursor(); | ||||
|             if (!this._buttonDown) | ||||
|                 this._dragComplete(); | ||||
|             this.emit('drag-end', eventTime, false); | ||||
| @@ -558,14 +546,13 @@ const _Draggable = new Lang.Class({ | ||||
|  | ||||
|     _onAnimationComplete : function (dragActor, eventTime) { | ||||
|         if (this._dragOrigParent) { | ||||
|             Main.uiGroup.remove_child(this._dragActor); | ||||
|             this._dragOrigParent.add_actor(this._dragActor); | ||||
|             dragActor.reparent(this._dragOrigParent); | ||||
|             dragActor.set_scale(this._dragOrigScale, this._dragOrigScale); | ||||
|             dragActor.set_position(this._dragOrigX, this._dragOrigY); | ||||
|         } else { | ||||
|             dragActor.destroy(); | ||||
|         } | ||||
|         global.screen.set_cursor(Meta.Cursor.DEFAULT); | ||||
|         global.unset_cursor(); | ||||
|         this.emit('drag-end', eventTime, false); | ||||
|  | ||||
|         this._animationInProgress = false; | ||||
| @@ -573,16 +560,32 @@ const _Draggable = new Lang.Class({ | ||||
|             this._dragComplete(); | ||||
|     }, | ||||
|  | ||||
|     // Actor is an actor we have entered or left during the drag; call | ||||
|     // st_widget_sync_hover on all StWidget ancestors | ||||
|     _syncHover: function(actor) { | ||||
|         while (actor) { | ||||
|             let parent = actor.get_parent(); | ||||
|             if (actor instanceof St.Widget) | ||||
|                 actor.sync_hover(); | ||||
|  | ||||
|             actor = parent; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _dragComplete: function() { | ||||
|         if (!this._actorDestroyed) | ||||
|             Shell.util_set_hidden_from_pick(this._dragActor, false); | ||||
|  | ||||
|         this._ungrabEvents(); | ||||
|         global.sync_pointer(); | ||||
|  | ||||
|         if (this._updateHoverId) { | ||||
|             GLib.source_remove(this._updateHoverId); | ||||
|             this._updateHoverId = 0; | ||||
|         if (this._firstLeaveActor) { | ||||
|             this._syncHover(this._firstLeaveActor); | ||||
|             this._firstLeaveActor = null; | ||||
|         } | ||||
|  | ||||
|         if (this._lastEnterActor) { | ||||
|             this._syncHover(this._lastEnterActor); | ||||
|             this._lastEnterActor = null; | ||||
|         } | ||||
|  | ||||
|         this._dragActor = undefined; | ||||
|   | ||||
| @@ -1,84 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Main = imports.ui.main; | ||||
|  | ||||
| const EDGE_THRESHOLD = 20; | ||||
| const DRAG_DISTANCE = 80; | ||||
|  | ||||
| const EdgeDragAction = new Lang.Class({ | ||||
|     Name: 'EdgeDragAction', | ||||
|     Extends: Clutter.GestureAction, | ||||
|  | ||||
|     _init : function(side, allowedModes) { | ||||
|         this.parent(); | ||||
|         this._side = side; | ||||
|         this._allowedModes = allowedModes; | ||||
|         this.set_n_touch_points(1); | ||||
|  | ||||
|         global.display.connect('grab-op-begin', Lang.bind(this, function() { | ||||
|             this.cancel(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _getMonitorRect : function (x, y) { | ||||
|         let rect = new Meta.Rectangle({ x: x - 1, y: y - 1, width: 1, height: 1 }); | ||||
|         let monitorIndex = global.screen.get_monitor_index_for_rect(rect); | ||||
|  | ||||
|         return global.screen.get_monitor_geometry(monitorIndex); | ||||
|     }, | ||||
|  | ||||
|     vfunc_gesture_prepare : function(action, actor) { | ||||
|         if (this.get_n_current_points() == 0) | ||||
|             return false; | ||||
|  | ||||
|         if (!(this._allowedModes & Main.keybindingMode)) | ||||
|             return false; | ||||
|  | ||||
|         let [x, y] = this.get_press_coords(0); | ||||
|         let monitorRect = this._getMonitorRect(x, y); | ||||
|  | ||||
|         return ((this._side == St.Side.LEFT && x < monitorRect.x + EDGE_THRESHOLD) || | ||||
|                 (this._side == St.Side.RIGHT && x > monitorRect.x + monitorRect.width - EDGE_THRESHOLD) || | ||||
|                 (this._side == St.Side.TOP && y < monitorRect.y + EDGE_THRESHOLD) || | ||||
|                 (this._side == St.Side.BOTTOM && y > monitorRect.y + monitorRect.height - EDGE_THRESHOLD)); | ||||
|     }, | ||||
|  | ||||
|     vfunc_gesture_progress : function (action, actor) { | ||||
|         let [startX, startY] = this.get_press_coords(0); | ||||
|         let [x, y] = this.get_motion_coords(0); | ||||
|         let offsetX = Math.abs (x - startX); | ||||
|         let offsetY = Math.abs (y - startY); | ||||
|  | ||||
|         if (offsetX < EDGE_THRESHOLD && offsetY < EDGE_THRESHOLD) | ||||
|             return true; | ||||
|  | ||||
|         if ((offsetX > offsetY && | ||||
|              (this._side == St.Side.TOP || this._side == St.Side.BOTTOM)) || | ||||
|             (offsetY > offsetX && | ||||
|              (this._side == St.Side.LEFT || this._side == St.Side.RIGHT))) { | ||||
|             this.cancel(); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     vfunc_gesture_end : function (action, actor) { | ||||
|         let [startX, startY] = this.get_press_coords(0); | ||||
|         let [x, y] = this.get_motion_coords(0); | ||||
|         let monitorRect = this._getMonitorRect(startX, startY); | ||||
|  | ||||
|         if ((this._side == St.Side.TOP && y > monitorRect.y + DRAG_DISTANCE) || | ||||
|             (this._side == St.Side.BOTTOM && y < monitorRect.y + monitorRect.height - DRAG_DISTANCE) || | ||||
|             (this._side == St.Side.LEFT && x > monitorRect.x + DRAG_DISTANCE) || | ||||
|             (this._side == St.Side.RIGHT && x < monitorRect.x + monitorRect.width - DRAG_DISTANCE)) | ||||
|             this.emit('activated'); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(EdgeDragAction.prototype); | ||||
| @@ -13,11 +13,13 @@ | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | ||||
|  * 02111-1307, USA. | ||||
|  */ | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const AccountsService = imports.gi.AccountsService; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| @@ -25,172 +27,96 @@ const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Pango = imports.gi.Pango; | ||||
| const Polkit = imports.gi.Polkit; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const CheckBox = imports.ui.checkBox; | ||||
| const GnomeSession = imports.misc.gnomeSession; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| const Main = imports.ui.main; | ||||
| const ModalDialog = imports.ui.modalDialog; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const UserWidget = imports.ui.userWidget; | ||||
| const UserMenu = imports.ui.userMenu; | ||||
|  | ||||
| let _endSessionDialog = null; | ||||
|  | ||||
| const _ITEM_ICON_SIZE = 48; | ||||
| const _DIALOG_ICON_SIZE = 48; | ||||
| const _DIALOG_ICON_SIZE = 32; | ||||
|  | ||||
| const GSM_SESSION_MANAGER_LOGOUT_FORCE = 2; | ||||
|  | ||||
| const EndSessionDialogIface = '<node> \ | ||||
| <interface name="org.gnome.SessionManager.EndSessionDialog"> \ | ||||
| <method name="Open"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
|     <arg type="ao" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Close" /> \ | ||||
| <signal name="ConfirmedLogout" /> \ | ||||
| <signal name="ConfirmedReboot" /> \ | ||||
| <signal name="ConfirmedShutdown" /> \ | ||||
| <signal name="Canceled" /> \ | ||||
| <signal name="Closed" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const EndSessionDialogIface = <interface name="org.gnome.SessionManager.EndSessionDialog"> | ||||
| <method name="Open"> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="u" direction="in" /> | ||||
|     <arg type="ao" direction="in" /> | ||||
| </method> | ||||
| <signal name="ConfirmedLogout" /> | ||||
| <signal name="ConfirmedReboot" /> | ||||
| <signal name="ConfirmedShutdown" /> | ||||
| <signal name="Canceled" /> | ||||
| <signal name="Closed" /> | ||||
| </interface>; | ||||
|  | ||||
| const logoutDialogContent = { | ||||
|     subjectWithUser: C_("title", "Log Out %s"), | ||||
|     subject: C_("title", "Log Out"), | ||||
|     descriptionWithUser: function(user, seconds) { | ||||
|     inhibitedDescription: _("Click Log Out to quit these applications and log out of the system."), | ||||
|     uninhibitedDescriptionWithUser: function(user, seconds) { | ||||
|         return ngettext("%s will be logged out automatically in %d second.", | ||||
|                         "%s will be logged out automatically in %d seconds.", | ||||
|                         seconds).format(user, seconds); | ||||
|     }, | ||||
|     description: function(seconds) { | ||||
|     uninhibitedDescription: function(seconds) { | ||||
|         return ngettext("You will be logged out automatically in %d second.", | ||||
|                         "You will be logged out automatically in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     showBatteryWarning: false, | ||||
|     endDescription: _("Logging out of the system."), | ||||
|     confirmButtons: [{ signal: 'ConfirmedLogout', | ||||
|                        label:  C_("button", "Log Out") }], | ||||
|     iconStyleClass: 'end-session-dialog-logout-icon', | ||||
|     showOtherSessions: false, | ||||
|     iconStyleClass: 'end-session-dialog-logout-icon' | ||||
| }; | ||||
|  | ||||
| const shutdownDialogContent = { | ||||
|     subject: C_("title", "Power Off"), | ||||
|     subjectWithUpdates: C_("title", "Install Updates & Power Off"), | ||||
|     description: function(seconds) { | ||||
|     inhibitedDescription: _("Click Power Off to quit these applications and power off the system."), | ||||
|     uninhibitedDescription: function(seconds) { | ||||
|         return ngettext("The system will power off automatically in %d second.", | ||||
|                         "The system will power off automatically in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     checkBoxText: C_("checkbox", "Install pending software updates"), | ||||
|     showBatteryWarning: true, | ||||
|     endDescription: _("Powering off the system."), | ||||
|     confirmButtons: [{ signal: 'ConfirmedReboot', | ||||
|                        label:  C_("button", "Restart") }, | ||||
|                      { signal: 'ConfirmedShutdown', | ||||
|                        label:  C_("button", "Power Off") }], | ||||
|     iconName: 'system-shutdown-symbolic', | ||||
|     iconStyleClass: 'end-session-dialog-shutdown-icon', | ||||
|     showOtherSessions: true, | ||||
|     iconStyleClass: 'end-session-dialog-shutdown-icon' | ||||
| }; | ||||
|  | ||||
| const restartDialogContent = { | ||||
|     subject: C_("title", "Restart"), | ||||
|     description: function(seconds) { | ||||
|     inhibitedDescription: _("Click Restart to quit these applications and restart the system."), | ||||
|     uninhibitedDescription: function(seconds) { | ||||
|         return ngettext("The system will restart automatically in %d second.", | ||||
|                         "The system will restart automatically in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     showBatteryWarning: false, | ||||
|     endDescription: _("Restarting the system."), | ||||
|     confirmButtons: [{ signal: 'ConfirmedReboot', | ||||
|                        label:  C_("button", "Restart") }], | ||||
|     iconName: 'view-refresh-symbolic', | ||||
|     iconStyleClass: 'end-session-dialog-shutdown-icon', | ||||
|     showOtherSessions: true, | ||||
| }; | ||||
|  | ||||
| const restartInstallDialogContent = { | ||||
|  | ||||
|     subject: C_("title", "Restart & Install Updates"), | ||||
|     description: function(seconds) { | ||||
|         return ngettext("The system will automatically restart and install updates in %d second.", | ||||
|                         "The system will automatically restart and install updates in %d seconds.", | ||||
|                         seconds).format(seconds); | ||||
|     }, | ||||
|     showBatteryWarning: true, | ||||
|     confirmButtons: [{ signal: 'ConfirmedReboot', | ||||
|                        label:  C_("button", "Restart & Install") }], | ||||
|     unusedFutureButtonForTranslation: C_("button", "Install & Power Off"), | ||||
|     unusedFutureCheckBoxForTranslation: C_("checkbox", "Power off after updates are installed"), | ||||
|     iconName: 'view-refresh-symbolic', | ||||
|     iconStyleClass: 'end-session-dialog-shutdown-icon', | ||||
|     showOtherSessions: true, | ||||
| }; | ||||
|  | ||||
| const DialogType = { | ||||
|   LOGOUT: 0 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT */, | ||||
|   SHUTDOWN: 1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */, | ||||
|   RESTART: 2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */, | ||||
|   UPDATE_RESTART: 3 | ||||
|     iconStyleClass: 'end-session-dialog-shutdown-icon' | ||||
| }; | ||||
|  | ||||
| const DialogContent = { | ||||
|     0 /* DialogType.LOGOUT */: logoutDialogContent, | ||||
|     1 /* DialogType.SHUTDOWN */: shutdownDialogContent, | ||||
|     2 /* DialogType.RESTART */: restartDialogContent, | ||||
|     3 /* DialogType.UPDATE_RESTART */: restartInstallDialogContent | ||||
|     0 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT */: logoutDialogContent, | ||||
|     1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */: shutdownDialogContent, | ||||
|     2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */: restartDialogContent | ||||
| }; | ||||
|  | ||||
| const MAX_USERS_IN_SESSION_DIALOG = 5; | ||||
|  | ||||
| const LogindSessionIface = '<node> \ | ||||
| <interface name="org.freedesktop.login1.Session"> \ | ||||
|     <property name="Id" type="s" access="read"/> \ | ||||
|     <property name="Remote" type="b" access="read"/> \ | ||||
|     <property name="Class" type="s" access="read"/> \ | ||||
|     <property name="Type" type="s" access="read"/> \ | ||||
|     <property name="State" type="s" access="read"/> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const LogindSession = Gio.DBusProxy.makeProxyWrapper(LogindSessionIface); | ||||
|  | ||||
| const PkOfflineIface = '<node> \ | ||||
| <interface name="org.freedesktop.PackageKit.Offline"> \ | ||||
|     <property name="UpdatePrepared" type="b" access="read"/> \ | ||||
|     <property name="TriggerAction" type="s" access="read"/> \ | ||||
|     <method name="Trigger"> \ | ||||
|         <arg type="s" name="action" direction="in"/> \ | ||||
|     </method> \ | ||||
|     <method name="Cancel"/> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const PkOfflineProxy = Gio.DBusProxy.makeProxyWrapper(PkOfflineIface); | ||||
|  | ||||
| const UPowerIface = '<node> \ | ||||
| <interface name="org.freedesktop.UPower"> \ | ||||
|     <property name="OnBattery" type="b" access="read"/> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const UPowerProxy = Gio.DBusProxy.makeProxyWrapper(UPowerIface); | ||||
|  | ||||
| function findAppFromInhibitor(inhibitor) { | ||||
|     let desktopFile; | ||||
|     try { | ||||
|         [desktopFile] = inhibitor.GetAppIdSync(); | ||||
|     } catch(e) { | ||||
|         // XXX -- sometimes JIT inhibitors generated by gnome-session | ||||
|         // get removed too soon. Don't fail in this case. | ||||
|         log('gnome-session gave us a dead inhibitor: %s'.format(inhibitor.get_object_path())); | ||||
|         return null; | ||||
|     } | ||||
|     let [desktopFile] = inhibitor.GetAppIdSync(); | ||||
|  | ||||
|     if (!GLib.str_has_suffix(desktopFile, '.desktop')) | ||||
|         desktopFile += '.desktop'; | ||||
| @@ -198,6 +124,58 @@ function findAppFromInhibitor(inhibitor) { | ||||
|     return Shell.AppSystem.get_default().lookup_heuristic_basename(desktopFile); | ||||
| } | ||||
|  | ||||
| const ListItem = new Lang.Class({ | ||||
|     Name: 'ListItem', | ||||
|  | ||||
|     _init: function(app, reason) { | ||||
|         this._app = app; | ||||
|         this._reason = reason; | ||||
|  | ||||
|         if (this._reason == null) | ||||
|           this._reason = ''; | ||||
|  | ||||
|         let layout = new St.BoxLayout({ vertical: false}); | ||||
|  | ||||
|         this.actor = new St.Button({ style_class: 'end-session-dialog-app-list-item', | ||||
|                                      can_focus:   true, | ||||
|                                      child:       layout, | ||||
|                                      reactive:    true, | ||||
|                                      x_align:     St.Align.START, | ||||
|                                      x_fill:      true }); | ||||
|  | ||||
|         this._icon = this._app.create_icon_texture(_ITEM_ICON_SIZE); | ||||
|  | ||||
|         let iconBin = new St.Bin({ style_class: 'end-session-dialog-app-list-item-icon', | ||||
|                                    child:       this._icon }); | ||||
|         layout.add(iconBin); | ||||
|  | ||||
|         let textLayout = new St.BoxLayout({ style_class: 'end-session-dialog-app-list-item-text-box', | ||||
|                                             vertical:    true }); | ||||
|         layout.add(textLayout); | ||||
|  | ||||
|         this._nameLabel = new St.Label({ text:        this._app.get_name(), | ||||
|                                          style_class: 'end-session-dialog-app-list-item-name' }); | ||||
|         textLayout.add(this._nameLabel, | ||||
|                        { expand: false, | ||||
|                          x_fill: true }); | ||||
|  | ||||
|         this._descriptionLabel = new St.Label({ text:        this._reason, | ||||
|                                                 style_class: 'end-session-dialog-app-list-item-description' }); | ||||
|         this.actor.label_actor = this._nameLabel; | ||||
|         textLayout.add(this._descriptionLabel, | ||||
|                        { expand: true, | ||||
|                          x_fill: true }); | ||||
|  | ||||
|         this.actor.connect('clicked', Lang.bind(this, this._onClicked)); | ||||
|     }, | ||||
|  | ||||
|     _onClicked: function() { | ||||
|         this.emit('activate'); | ||||
|         this._app.activate(); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ListItem.prototype); | ||||
|  | ||||
| // The logout timer only shows updates every 10 seconds | ||||
| // until the last 10 seconds, then it shows updates every | ||||
| // second.  This function takes a given time and returns | ||||
| @@ -233,18 +211,6 @@ function _setLabelText(label, text) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function _setCheckBoxLabel(checkBox, text) { | ||||
|     let label = checkBox.getLabelActor(); | ||||
|  | ||||
|     if (text) { | ||||
|         label.set_text(text); | ||||
|         checkBox.actor.show(); | ||||
|     } else { | ||||
|         label.set_text(''); | ||||
|         checkBox.actor.hide(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function init() { | ||||
|     // This always returns the same singleton object | ||||
|     // By instantiating it initially, we register the | ||||
| @@ -257,45 +223,24 @@ const EndSessionDialog = new Lang.Class({ | ||||
|     Extends: ModalDialog.ModalDialog, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.parent({ styleClass: 'end-session-dialog', | ||||
|                       destroyOnClose: false }); | ||||
|         this.parent({ styleClass: 'end-session-dialog' }); | ||||
|  | ||||
|         this._loginManager = LoginManager.getLoginManager(); | ||||
|         this._userManager = AccountsService.UserManager.get_default(); | ||||
|         this._user = this._userManager.get_user(GLib.get_user_name()); | ||||
|  | ||||
|         this._pkOfflineProxy = new PkOfflineProxy(Gio.DBus.system, | ||||
|                                                   'org.freedesktop.PackageKit', | ||||
|                                                   '/org/freedesktop/PackageKit', | ||||
|                                                   Lang.bind(this, function(proxy, error) { | ||||
|                                                       if (error) | ||||
|                                                           log(error.message); | ||||
|                                                   })); | ||||
|         this._powerProxy = new UPowerProxy(Gio.DBus.system, | ||||
|                                            'org.freedesktop.UPower', | ||||
|                                            '/org/freedesktop/UPower', | ||||
|                                            Lang.bind(this, function(proxy, error) { | ||||
|                                                if (error) { | ||||
|                                                    log(error.message); | ||||
|                                                    return; | ||||
|                                                } | ||||
|                                                this._powerProxy.connect('g-properties-changed', | ||||
|                                                                         Lang.bind(this, this._sync)); | ||||
|                                                this._sync(); | ||||
|                                            })); | ||||
|         this._user = AccountsService.UserManager.get_default().get_user(GLib.get_user_name()); | ||||
|  | ||||
|         this._secondsLeft = 0; | ||||
|         this._totalSecondsToStayOpen = 0; | ||||
|         this._applications = []; | ||||
|         this._sessions = []; | ||||
|         this._inhibitors = []; | ||||
|  | ||||
|         this.connect('destroy', | ||||
|                      Lang.bind(this, this._onDestroy)); | ||||
|         this.connect('opened', | ||||
|                      Lang.bind(this, this._onOpened)); | ||||
|  | ||||
|         this._userLoadedId = this._user.connect('notify::is_loaded', Lang.bind(this, this._sync)); | ||||
|         this._userChangedId = this._user.connect('changed', Lang.bind(this, this._sync)); | ||||
|         this._userLoadedId = this._user.connect('notify::is_loaded', | ||||
|                                                 Lang.bind(this, this._updateContent)); | ||||
|  | ||||
|         this._userChangedId = this._user.connect('changed', | ||||
|                                                  Lang.bind(this, this._updateContent)); | ||||
|  | ||||
|         let mainContentLayout = new St.BoxLayout({ vertical: false }); | ||||
|         this.contentLayout.add(mainContentLayout, | ||||
| @@ -309,17 +254,14 @@ const EndSessionDialog = new Lang.Class({ | ||||
|                                 x_align: St.Align.END, | ||||
|                                 y_align: St.Align.START }); | ||||
|  | ||||
|         let messageLayout = new St.BoxLayout({ vertical: true, | ||||
|                                                style_class: 'end-session-dialog-layout' }); | ||||
|         let messageLayout = new St.BoxLayout({ vertical: true }); | ||||
|         mainContentLayout.add(messageLayout, | ||||
|                               { y_align: St.Align.START }); | ||||
|  | ||||
|         this._subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject' }); | ||||
|  | ||||
|         messageLayout.add(this._subjectLabel, | ||||
|                           { x_fill: false, | ||||
|                             y_fill:  false, | ||||
|                             x_align: St.Align.START, | ||||
|                           { y_fill:  false, | ||||
|                             y_align: St.Align.START }); | ||||
|  | ||||
|         this._descriptionLabel = new St.Label({ style_class: 'end-session-dialog-description' }); | ||||
| @@ -330,46 +272,28 @@ const EndSessionDialog = new Lang.Class({ | ||||
|                           { y_fill:  true, | ||||
|                             y_align: St.Align.START }); | ||||
|  | ||||
|         this._checkBox = new CheckBox.CheckBox(); | ||||
|         this._checkBox.actor.connect('clicked', Lang.bind(this, this._sync)); | ||||
|         messageLayout.add(this._checkBox.actor); | ||||
|  | ||||
|         this._batteryWarning = new St.Label({ style_class: 'end-session-dialog-warning', | ||||
|                                               text: _("Running on battery power: please plug in before installing updates.") }); | ||||
|         this._batteryWarning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|         this._batteryWarning.clutter_text.line_wrap = true; | ||||
|         messageLayout.add(this._batteryWarning); | ||||
|  | ||||
|         this._scrollView = new St.ScrollView({ style_class: 'end-session-dialog-list' }); | ||||
|         this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); | ||||
|         this.contentLayout.add(this._scrollView, | ||||
|         let scrollView = new St.ScrollView({ style_class: 'end-session-dialog-app-list'}); | ||||
|         scrollView.set_policy(Gtk.PolicyType.NEVER, | ||||
|                               Gtk.PolicyType.AUTOMATIC); | ||||
|         this.contentLayout.add(scrollView, | ||||
|                                { x_fill: true, | ||||
|                                  y_fill: true }); | ||||
|         this._scrollView.hide(); | ||||
|         scrollView.hide(); | ||||
|  | ||||
|         this._inhibitorSection = new St.BoxLayout({ vertical: true, | ||||
|                                                     style_class: 'end-session-dialog-inhibitor-layout' }); | ||||
|         this._scrollView.add_actor(this._inhibitorSection); | ||||
|         this._applicationList = new St.BoxLayout({ vertical: true }); | ||||
|         scrollView.add_actor(this._applicationList); | ||||
|  | ||||
|         this._applicationHeader = new St.Label({ style_class: 'end-session-dialog-list-header', | ||||
|                                                  text: _("Some applications are busy or have unsaved work.") }); | ||||
|         this._applicationList = new St.BoxLayout({ style_class: 'end-session-dialog-app-list', | ||||
|                                                    vertical: true }); | ||||
|         this._inhibitorSection.add_actor(this._applicationHeader); | ||||
|         this._inhibitorSection.add_actor(this._applicationList); | ||||
|         this._applicationList.connect('actor-added', | ||||
|                                       Lang.bind(this, function() { | ||||
|                                           if (this._applicationList.get_n_children() == 1) | ||||
|                                               scrollView.show(); | ||||
|                                       })); | ||||
|  | ||||
|         this._sessionHeader = new St.Label({ style_class: 'end-session-dialog-list-header', | ||||
|                                              text: _("Other users are logged in.") }); | ||||
|         this._sessionList = new St.BoxLayout({ style_class: 'end-session-dialog-session-list', | ||||
|                                                vertical: true }); | ||||
|         this._inhibitorSection.add_actor(this._sessionHeader); | ||||
|         this._inhibitorSection.add_actor(this._sessionList); | ||||
|  | ||||
|         try { | ||||
|             this._updatesPermission = Polkit.Permission.new_sync("org.freedesktop.packagekit.trigger-offline-update", null, null); | ||||
|         } catch(e) { | ||||
|             log('No permission to trigger offline updates: %s'.format(e.toString())); | ||||
|         } | ||||
|         this._applicationList.connect('actor-removed', | ||||
|                                       Lang.bind(this, function() { | ||||
|                                           if (this._applicationList.get_n_children() == 0) | ||||
|                                               scrollView.hide(); | ||||
|                                       })); | ||||
|  | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(EndSessionDialogIface, this); | ||||
|         this._dbusImpl.export(Gio.DBus.session, '/org/gnome/SessionManager/EndSessionDialog'); | ||||
| @@ -380,51 +304,52 @@ const EndSessionDialog = new Lang.Class({ | ||||
|         this._user.disconnect(this._userChangedId); | ||||
|     }, | ||||
|  | ||||
|     _sync: function() { | ||||
|         let open = (this.state == ModalDialog.State.OPENING || this.state == ModalDialog.State.OPENED); | ||||
|         if (!open) | ||||
|     _updateDescription: function() { | ||||
|         if (this.state != ModalDialog.State.OPENING && | ||||
|             this.state != ModalDialog.State.OPENED) | ||||
|             return; | ||||
|  | ||||
|         let dialogContent = DialogContent[this._type]; | ||||
|  | ||||
|         let subject = dialogContent.subject; | ||||
|  | ||||
|         // Use different title when we are installing updates | ||||
|         if (dialogContent.subjectWithUpdates && this._checkBox.actor.checked) | ||||
|             subject = dialogContent.subjectWithUpdates; | ||||
|  | ||||
|         if (dialogContent.showBatteryWarning) { | ||||
|             // Warn when running on battery power | ||||
|             if (this._powerProxy.OnBattery && this._checkBox.actor.checked) | ||||
|                 this._batteryWarning.opacity = 255; | ||||
|             else | ||||
|                 this._batteryWarning.opacity = 0; | ||||
|         } | ||||
|  | ||||
|         let description; | ||||
|         let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen, | ||||
|                                                   this._secondsLeft, | ||||
|                                                   10); | ||||
|  | ||||
|         if (this._user.is_loaded) { | ||||
|             let realName = this._user.get_real_name(); | ||||
|         if (this._inhibitors.length > 0) { | ||||
|             this._stopTimer(); | ||||
|             description = dialogContent.inhibitedDescription; | ||||
|         } else if (this._secondsLeft > 0 && this._inhibitors.length == 0) { | ||||
|             let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen, | ||||
|                                                       this._secondsLeft, | ||||
|                                                       10); | ||||
|  | ||||
|             if (realName != null) { | ||||
|                 if (dialogContent.subjectWithUser) | ||||
|                     subject = dialogContent.subjectWithUser.format(realName); | ||||
|             if (this._user.is_loaded) { | ||||
|                 let realName = this._user.get_real_name(); | ||||
|  | ||||
|                 if (dialogContent.descriptionWithUser) | ||||
|                     description = dialogContent.descriptionWithUser(realName, displayTime); | ||||
|                 else | ||||
|                     description = dialogContent.description(displayTime); | ||||
|                 if (realName != null) { | ||||
|                     if (dialogContent.subjectWithUser) | ||||
|                         subject = dialogContent.subjectWithUser.format(realName); | ||||
|  | ||||
|                     if (dialogContent.uninhibitedDescriptionWithUser) | ||||
|                         description = dialogContent.uninhibitedDescriptionWithUser(realName, displayTime); | ||||
|                     else | ||||
|                         description = dialogContent.uninhibitedDescription(displayTime); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (!description) | ||||
|                 description = dialogContent.uninhibitedDescription(displayTime); | ||||
|         } else { | ||||
|             description = dialogContent.endDescription; | ||||
|         } | ||||
|  | ||||
|         if (!description) | ||||
|             description = dialogContent.description(displayTime); | ||||
|  | ||||
|         _setLabelText(this._descriptionLabel, description); | ||||
|         _setLabelText(this._subjectLabel, subject); | ||||
|         _setLabelText(this._descriptionLabel, description); | ||||
|     }, | ||||
|  | ||||
|     _updateContent: function() { | ||||
|         if (this.state != ModalDialog.State.OPENING && | ||||
|             this.state != ModalDialog.State.OPENED) | ||||
|             return; | ||||
|  | ||||
|         let dialogContent = DialogContent[this._type]; | ||||
|         if (dialogContent.iconName) { | ||||
| @@ -432,18 +357,14 @@ const EndSessionDialog = new Lang.Class({ | ||||
|                                                 icon_size: _DIALOG_ICON_SIZE, | ||||
|                                                 style_class: dialogContent.iconStyleClass }); | ||||
|         } else { | ||||
|             let avatarWidget = new UserWidget.Avatar(this._user, | ||||
|                                                      { iconSize: _DIALOG_ICON_SIZE, | ||||
|                                                        styleClass: dialogContent.iconStyleClass }); | ||||
|             let avatarWidget = new UserMenu.UserAvatarWidget(this._user, | ||||
|                                                              { iconSize: _DIALOG_ICON_SIZE, | ||||
|                                                                styleClass: dialogContent.iconStyleClass }); | ||||
|             this._iconBin.child = avatarWidget.actor; | ||||
|             avatarWidget.update(); | ||||
|         } | ||||
|  | ||||
|         let hasApplications = this._applications.length > 0; | ||||
|         let hasSessions = this._sessions.length > 0; | ||||
|         this._scrollView.visible = hasApplications || hasSessions; | ||||
|         this._applicationHeader.visible = hasApplications; | ||||
|         this._sessionHeader.visible = hasSessions; | ||||
|         this._updateDescription(); | ||||
|     }, | ||||
|  | ||||
|     _updateButtons: function() { | ||||
| @@ -456,12 +377,7 @@ const EndSessionDialog = new Lang.Class({ | ||||
|             let signal = dialogContent.confirmButtons[i].signal; | ||||
|             let label = dialogContent.confirmButtons[i].label; | ||||
|             buttons.push({ action: Lang.bind(this, function() { | ||||
|                                        this.close(true); | ||||
|                                        let signalId = this.connect('closed', | ||||
|                                                                    Lang.bind(this, function() { | ||||
|                                                                        this.disconnect(signalId); | ||||
|                                                                        this._confirm(signal); | ||||
|                                                                    })); | ||||
|                                        this._confirm(signal); | ||||
|                                    }), | ||||
|                            label: label }); | ||||
|         } | ||||
| @@ -469,148 +385,50 @@ const EndSessionDialog = new Lang.Class({ | ||||
|         this.setButtons(buttons); | ||||
|     }, | ||||
|  | ||||
|     close: function(skipSignal) { | ||||
|     close: function() { | ||||
|         this.parent(); | ||||
|  | ||||
|         if (!skipSignal) | ||||
|             this._dbusImpl.emit_signal('Closed', null); | ||||
|         this._dbusImpl.emit_signal('Closed', null); | ||||
|     }, | ||||
|  | ||||
|     cancel: function() { | ||||
|         this._stopTimer(); | ||||
|         this._dbusImpl.emit_signal('Canceled', null); | ||||
|         this.close(); | ||||
|         this.close(global.get_current_time()); | ||||
|     }, | ||||
|  | ||||
|     _confirm: function(signal) { | ||||
|         let callback = Lang.bind(this, function() { | ||||
|             this._fadeOutDialog(); | ||||
|             this._stopTimer(); | ||||
|             this._dbusImpl.emit_signal(signal, null); | ||||
|         }); | ||||
|  | ||||
|         // Offline update not available; just emit the signal | ||||
|         if (!this._checkBox.actor.visible) { | ||||
|             callback(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Trigger the offline update as requested | ||||
|         if (this._checkBox.actor.checked) { | ||||
|             switch (signal) { | ||||
|                 case "ConfirmedReboot": | ||||
|                     this._triggerOfflineUpdateReboot(callback); | ||||
|                     break; | ||||
|                 case "ConfirmedShutdown": | ||||
|                     // To actually trigger the offline update, we need to | ||||
|                     // reboot to do the upgrade. When the upgrade is complete, | ||||
|                     // the computer will shut down automatically. | ||||
|                     signal = "ConfirmedReboot"; | ||||
|                     this._triggerOfflineUpdateShutdown(callback); | ||||
|                     break; | ||||
|                 default: | ||||
|                     callback(); | ||||
|                     break; | ||||
|             } | ||||
|         } else { | ||||
|             this._triggerOfflineUpdateCancel(callback); | ||||
|         } | ||||
|         this._fadeOutDialog(); | ||||
|         this._stopTimer(); | ||||
|         this._dbusImpl.emit_signal(signal, null); | ||||
|     }, | ||||
|  | ||||
|     _onOpened: function() { | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     _triggerOfflineUpdateReboot: function(callback) { | ||||
|         this._pkOfflineProxy.TriggerRemote('reboot', | ||||
|                                            function (result, error) { | ||||
|             if (error) | ||||
|                 log(error.message); | ||||
|  | ||||
|             callback(); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     _triggerOfflineUpdateShutdown: function(callback) { | ||||
|         this._pkOfflineProxy.TriggerRemote('power-off', | ||||
|                                            function (result, error) { | ||||
|             if (error) | ||||
|                 log(error.message); | ||||
|  | ||||
|             callback(); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     _triggerOfflineUpdateCancel: function(callback) { | ||||
|         this._pkOfflineProxy.CancelRemote(function (result, error) { | ||||
|             if (error) | ||||
|                 log(error.message); | ||||
|  | ||||
|             callback(); | ||||
|         }); | ||||
|         if (this._inhibitors.length == 0) | ||||
|             this._startTimer(); | ||||
|     }, | ||||
|  | ||||
|     _startTimer: function() { | ||||
|         let startTime = GLib.get_monotonic_time(); | ||||
|         this._secondsLeft = this._totalSecondsToStayOpen; | ||||
|  | ||||
|         this._timerId = Mainloop.timeout_add_seconds(1, Lang.bind(this, | ||||
|             function() { | ||||
|                 let currentTime = GLib.get_monotonic_time(); | ||||
|                 let secondsElapsed = ((currentTime - startTime) / 1000000); | ||||
|  | ||||
|                 this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed; | ||||
|                 if (this._secondsLeft > 0) { | ||||
|                     this._sync(); | ||||
|                     return GLib.SOURCE_CONTINUE; | ||||
|                 } | ||||
|  | ||||
|                 let dialogContent = DialogContent[this._type]; | ||||
|                 let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1]; | ||||
|                 this._confirm(button.signal); | ||||
|                 this._timerId = 0; | ||||
|  | ||||
|                 return GLib.SOURCE_REMOVE; | ||||
|             })); | ||||
|         GLib.Source.set_name_by_id(this._timerId, '[gnome-shell] this._confirm'); | ||||
|         Tweener.addTween(this, | ||||
|                          { _secondsLeft: 0, | ||||
|                            time: this._secondsLeft, | ||||
|                            transition: 'linear', | ||||
|                            onUpdate: Lang.bind(this, this._updateDescription), | ||||
|                            onComplete: Lang.bind(this, function() { | ||||
|                                            let dialogContent = DialogContent[this._type]; | ||||
|                                            let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1]; | ||||
|                                            this._confirm(button.signal); | ||||
|                                        }), | ||||
|                          }); | ||||
|     }, | ||||
|  | ||||
|     _stopTimer: function() { | ||||
|         if (this._timerId > 0) { | ||||
|             Mainloop.source_remove(this._timerId); | ||||
|             this._timerId = 0; | ||||
|         } | ||||
|  | ||||
|         Tweener.removeTweens(this); | ||||
|         this._secondsLeft = 0; | ||||
|     }, | ||||
|  | ||||
|     _constructListItemForApp: function(inhibitor, app) { | ||||
|         let actor = new St.BoxLayout({ style_class: 'end-session-dialog-app-list-item', | ||||
|                                        can_focus: true }); | ||||
|         actor.add(app.create_icon_texture(_ITEM_ICON_SIZE)); | ||||
|  | ||||
|         let textLayout = new St.BoxLayout({ vertical: true, | ||||
|                                             y_expand: true, | ||||
|                                             y_align: Clutter.ActorAlign.CENTER }); | ||||
|         actor.add(textLayout); | ||||
|  | ||||
|         let nameLabel = new St.Label({ text: app.get_name(), | ||||
|                                        style_class: 'end-session-dialog-app-list-item-name' }); | ||||
|         textLayout.add(nameLabel); | ||||
|         actor.label_actor = nameLabel; | ||||
|  | ||||
|         let [reason] = inhibitor.GetReasonSync(); | ||||
|         if (reason) { | ||||
|             let reasonLabel = new St.Label({ text: reason, | ||||
|                                              style_class: 'end-session-dialog-app-list-item-description' }); | ||||
|             textLayout.add(reasonLabel); | ||||
|         } | ||||
|  | ||||
|         return actor; | ||||
|     }, | ||||
|  | ||||
|     _onInhibitorLoaded: function(inhibitor) { | ||||
|         if (this._applications.indexOf(inhibitor) < 0) { | ||||
|         if (this._inhibitors.indexOf(inhibitor) < 0) { | ||||
|             // Stale inhibitor | ||||
|             return; | ||||
|         } | ||||
| @@ -618,95 +436,28 @@ const EndSessionDialog = new Lang.Class({ | ||||
|         let app = findAppFromInhibitor(inhibitor); | ||||
|  | ||||
|         if (app) { | ||||
|             let actor = this._constructListItemForApp(inhibitor, app); | ||||
|             this._applicationList.add(actor); | ||||
|             let [reason] = inhibitor.GetReasonSync(); | ||||
|             let item = new ListItem(app, reason); | ||||
|             item.connect('activate', | ||||
|                          Lang.bind(this, function() { | ||||
|                              this.close(global.get_current_time()); | ||||
|                          })); | ||||
|             this._applicationList.add(item.actor, { x_fill: true }); | ||||
|             this._stopTimer(); | ||||
|         } else { | ||||
|             // inhibiting app is a service, not an application | ||||
|             this._applications.splice(this._applications.indexOf(inhibitor), 1); | ||||
|             this._inhibitors.splice(this._inhibitors.indexOf(inhibitor), 1); | ||||
|         } | ||||
|  | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     _constructListItemForSession: function(session) { | ||||
|         let avatar = new UserWidget.Avatar(session.user, { iconSize: _ITEM_ICON_SIZE }); | ||||
|         avatar.update(); | ||||
|  | ||||
|         let userName = session.user.get_real_name() ? session.user.get_real_name() : session.username; | ||||
|         let userLabelText; | ||||
|  | ||||
|         if (session.remote) | ||||
|             /* Translators: Remote here refers to a remote session, like a ssh login */ | ||||
|             userLabelText = _("%s (remote)").format(userName); | ||||
|         else if (session.type == "tty") | ||||
|             /* Translators: Console here refers to a tty like a VT console */ | ||||
|             userLabelText = _("%s (console)").format(userName); | ||||
|         else | ||||
|             userLabelText = userName; | ||||
|  | ||||
|         let actor = new St.BoxLayout({ style_class: 'end-session-dialog-session-list-item', | ||||
|                                        can_focus: true }); | ||||
|         actor.add(avatar.actor); | ||||
|  | ||||
|         let nameLabel = new St.Label({ text: userLabelText, | ||||
|                                        style_class: 'end-session-dialog-session-list-item-name', | ||||
|                                        y_expand: true, | ||||
|                                        y_align: Clutter.ActorAlign.CENTER }); | ||||
|         actor.add(nameLabel); | ||||
|         actor.label_actor = nameLabel; | ||||
|  | ||||
|         return actor; | ||||
|     }, | ||||
|  | ||||
|     _loadSessions: function() { | ||||
|         this._loginManager.listSessions(Lang.bind(this, function(result) { | ||||
|             let n = 0; | ||||
|             for (let i = 0; i < result.length; i++) { | ||||
|                 let[id, uid, userName, seat, sessionPath] = result[i]; | ||||
|                 let proxy = new LogindSession(Gio.DBus.system, 'org.freedesktop.login1', sessionPath); | ||||
|  | ||||
|                 if (proxy.Class != 'user') | ||||
|                     continue; | ||||
|  | ||||
|                 if (proxy.State == 'closing') | ||||
|                     continue; | ||||
|  | ||||
|                 if (proxy.Id == GLib.getenv('XDG_SESSION_ID')) | ||||
|                     continue; | ||||
|  | ||||
|                 let session = { user: this._userManager.get_user(userName), | ||||
|                                 username: userName, | ||||
|                                 type: proxy.Type, | ||||
|                                 remote: proxy.Remote }; | ||||
|                 this._sessions.push(session); | ||||
|  | ||||
|                 let actor = this._constructListItemForSession(session); | ||||
|                 this._sessionList.add(actor); | ||||
|  | ||||
|                 // limit the number of entries | ||||
|                 n++; | ||||
|                 if (n == MAX_USERS_IN_SESSION_DIALOG) | ||||
|                     break; | ||||
|             } | ||||
|  | ||||
|             this._sync(); | ||||
|         })); | ||||
|         this._updateContent(); | ||||
|     }, | ||||
|  | ||||
|     OpenAsync: function(parameters, invocation) { | ||||
|         let [type, timestamp, totalSecondsToStayOpen, inhibitorObjectPaths] = parameters; | ||||
|         this._totalSecondsToStayOpen = totalSecondsToStayOpen; | ||||
|         this._type = type; | ||||
|  | ||||
|         if (this._type == DialogType.RESTART && | ||||
|             this._pkOfflineProxy.TriggerAction == 'reboot') | ||||
|             this._type = DialogType.UPDATE_RESTART; | ||||
|  | ||||
|         this._applications = []; | ||||
|         this._inhibitors = []; | ||||
|         this._applicationList.destroy_all_children(); | ||||
|  | ||||
|         this._sessions = []; | ||||
|         this._sessionList.destroy_all_children(); | ||||
|         this._type = type; | ||||
|  | ||||
|         if (!(this._type in DialogContent)) { | ||||
|             invocation.return_dbus_error('org.gnome.Shell.ModalDialog.TypeError', | ||||
| @@ -714,33 +465,14 @@ const EndSessionDialog = new Lang.Class({ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let dialogContent = DialogContent[this._type]; | ||||
|  | ||||
|         for (let i = 0; i < inhibitorObjectPaths.length; i++) { | ||||
|             let inhibitor = new GnomeSession.Inhibitor(inhibitorObjectPaths[i], Lang.bind(this, function(proxy, error) { | ||||
|                 this._onInhibitorLoaded(proxy); | ||||
|             })); | ||||
|  | ||||
|             this._applications.push(inhibitor); | ||||
|             this._inhibitors.push(inhibitor); | ||||
|         } | ||||
|  | ||||
|         if (dialogContent.showOtherSessions) | ||||
|             this._loadSessions(); | ||||
|  | ||||
|         let updateAlreadyTriggered = this._pkOfflineProxy.TriggerAction == 'power-off' || this._pkOfflineProxy.TriggerAction == 'reboot'; | ||||
|         let updatePrepared = this._pkOfflineProxy.UpdatePrepared; | ||||
|         let updatesAllowed = this._updatesPermission && this._updatesPermission.allowed; | ||||
|  | ||||
|         _setCheckBoxLabel(this._checkBox, dialogContent.checkBoxText); | ||||
|         this._checkBox.actor.visible = (dialogContent.checkBoxText && updatePrepared && updatesAllowed); | ||||
|         this._checkBox.actor.checked = (updatePrepared && updateAlreadyTriggered); | ||||
|  | ||||
|         // We show the warning either together with the checkbox, or when | ||||
|         // updates have already been triggered, but the user doesn't have | ||||
|         // enough permissions to cancel them. | ||||
|         this._batteryWarning.visible = (dialogContent.showBatteryWarning && | ||||
|                                         (this._checkBox.actor.visible || updatePrepared && updateAlreadyTriggered && !updatesAllowed)); | ||||
|  | ||||
|         this._updateButtons(); | ||||
|  | ||||
|         if (!this.open(timestamp)) { | ||||
| @@ -749,17 +481,12 @@ const EndSessionDialog = new Lang.Class({ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._startTimer(); | ||||
|         this._sync(); | ||||
|         this._updateContent(); | ||||
|  | ||||
|         let signalId = this.connect('opened', | ||||
|                                     Lang.bind(this, function() { | ||||
|                                         invocation.return_value(null); | ||||
|                                         this.disconnect(signalId); | ||||
|                                     })); | ||||
|     }, | ||||
|  | ||||
|     Close: function(parameters, invocation) { | ||||
|         this.close(); | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -5,14 +5,11 @@ imports.gi.versions.Gio = '2.0'; | ||||
| imports.gi.versions.Gdk = '3.0'; | ||||
| imports.gi.versions.GdkPixbuf = '2.0'; | ||||
| imports.gi.versions.Gtk = '3.0'; | ||||
| imports.gi.versions.TelepathyGLib = '0.12'; | ||||
| imports.gi.versions.TelepathyLogger = '0.2'; | ||||
|  | ||||
| const Clutter = imports.gi.Clutter;; | ||||
| const Gettext = imports.gettext; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| @@ -42,25 +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) { | ||||
|                     let [found, length] = node.lookup_length(styleProps[prop], false); | ||||
|                     if (found) | ||||
|                         this[prop] = length; | ||||
|                 } | ||||
|             })); | ||||
|         }; | ||||
|     layoutClass.prototype.child_set = function(actor, props) { | ||||
|         let meta = this.get_child_meta(actor.get_parent(), actor); | ||||
|         for (let prop in props) | ||||
|             meta[prop] = props[prop]; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| function _makeLoggingFunc(func) { | ||||
|     return function() { | ||||
|         return func([].join.call(arguments, ', ')); | ||||
| @@ -82,12 +60,6 @@ function init() { | ||||
|     _patchContainerClass(St.BoxLayout); | ||||
|     _patchContainerClass(St.Table); | ||||
|  | ||||
|     _patchLayoutClass(Clutter.TableLayout, { row_spacing: 'spacing-rows', | ||||
|                                              column_spacing: 'spacing-columns' }); | ||||
|     _patchLayoutClass(Clutter.GridLayout, { row_spacing: 'spacing-rows', | ||||
|                                             column_spacing: 'spacing-columns' }); | ||||
|     _patchLayoutClass(Clutter.BoxLayout, { spacing: 'spacing' }); | ||||
|  | ||||
|     Clutter.Actor.prototype.toString = function() { | ||||
|         return St.describe_actor(this); | ||||
|     }; | ||||
|   | ||||
| @@ -201,7 +201,7 @@ const InstallExtensionDialog = new Lang.Class({ | ||||
|                            default: true | ||||
|                          }]); | ||||
|  | ||||
|         let message = _("Download and install “%s” from extensions.gnome.org?").format(info.name); | ||||
|         let message = _("Download and install '%s' from extensions.gnome.org?").format(info.name); | ||||
|  | ||||
|         let box = new St.BoxLayout(); | ||||
|         this.contentLayout.add(box); | ||||
| @@ -215,7 +215,7 @@ const InstallExtensionDialog = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onCancelButtonPressed: function(button, event) { | ||||
|         this.close(); | ||||
|         this.close(global.get_current_time()); | ||||
|         this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled'])); | ||||
|     }, | ||||
|  | ||||
| @@ -257,7 +257,7 @@ const InstallExtensionDialog = new Lang.Class({ | ||||
|             gotExtensionZipFile(session, message, uuid, dir, callback, errback); | ||||
|         })); | ||||
|  | ||||
|         this.close(); | ||||
|         this.close(global.get_current_time()); | ||||
|     } | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -38,7 +38,6 @@ const connect = Lang.bind(_signals, _signals.connect); | ||||
| const disconnect = Lang.bind(_signals, _signals.disconnect); | ||||
|  | ||||
| const ENABLED_EXTENSIONS_KEY = 'enabled-extensions'; | ||||
| const EXTENSION_DISABLE_VERSION_CHECK_KEY = 'disable-extension-version-validation'; | ||||
|  | ||||
| var initted = false; | ||||
| var enabled; | ||||
| @@ -77,11 +76,7 @@ function disableExtension(uuid) { | ||||
|         theme.unload_stylesheet(extension.stylesheet.get_path()); | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|         extension.stateObj.disable(); | ||||
|     } catch(e) { | ||||
|         logExtensionError(uuid, e); | ||||
|     } | ||||
|     extension.stateObj.disable(); | ||||
|  | ||||
|     for (let i = 0; i < order.length; i++) { | ||||
|         let uuid = order[i]; | ||||
| @@ -94,10 +89,8 @@ function disableExtension(uuid) { | ||||
|  | ||||
|     extensionOrder.splice(orderIdx, 1); | ||||
|  | ||||
|     if ( extension.state != ExtensionState.ERROR ) { | ||||
|         extension.state = ExtensionState.DISABLED; | ||||
|         _signals.emit('extension-state-changed', extension); | ||||
|     } | ||||
|     extension.state = ExtensionState.DISABLED; | ||||
|     _signals.emit('extension-state-changed', extension); | ||||
| } | ||||
|  | ||||
| function enableExtension(uuid) { | ||||
| @@ -113,26 +106,17 @@ function enableExtension(uuid) { | ||||
|  | ||||
|     extensionOrder.push(uuid); | ||||
|  | ||||
|     let stylesheetNames = [global.session_mode + '.css', 'stylesheet.css']; | ||||
|     for (let i = 0; i < stylesheetNames.length; i++) { | ||||
|         let stylesheetFile = extension.dir.get_child(stylesheetNames[i]); | ||||
|         if (stylesheetFile.query_exists(null)) { | ||||
|             let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); | ||||
|             theme.load_stylesheet(stylesheetFile.get_path()); | ||||
|             extension.stylesheet = stylesheetFile; | ||||
|             break; | ||||
|         } | ||||
|     let stylesheetFile = extension.dir.get_child('stylesheet.css'); | ||||
|     if (stylesheetFile.query_exists(null)) { | ||||
|         let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); | ||||
|         theme.load_stylesheet(stylesheetFile.get_path()); | ||||
|         extension.stylesheet = stylesheetFile; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|         extension.stateObj.enable(); | ||||
|         extension.state = ExtensionState.ENABLED; | ||||
|         _signals.emit('extension-state-changed', extension); | ||||
|         return; | ||||
|     } catch(e) { | ||||
|         logExtensionError(uuid, e); | ||||
|         return; | ||||
|     } | ||||
|     extension.stateObj.enable(); | ||||
|  | ||||
|     extension.state = ExtensionState.ENABLED; | ||||
|     _signals.emit('extension-state-changed', extension); | ||||
| } | ||||
|  | ||||
| function logExtensionError(uuid, error) { | ||||
| @@ -157,15 +141,12 @@ function loadExtension(extension) { | ||||
|     // Default to error, we set success as the last step | ||||
|     extension.state = ExtensionState.ERROR; | ||||
|  | ||||
|     let checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY); | ||||
|  | ||||
|     if (checkVersion && ExtensionUtils.isOutOfDate(extension)) { | ||||
|     if (ExtensionUtils.isOutOfDate(extension)) { | ||||
|         extension.state = ExtensionState.OUT_OF_DATE; | ||||
|     } else { | ||||
|         let enabled = enabledExtensions.indexOf(extension.uuid) != -1; | ||||
|         if (enabled) { | ||||
|             if (!initExtension(extension.uuid)) | ||||
|                 return; | ||||
|             initExtension(extension.uuid); | ||||
|             if (extension.state == ExtensionState.DISABLED) | ||||
|                 enableExtension(extension.uuid); | ||||
|         } else { | ||||
| @@ -220,12 +201,7 @@ function initExtension(uuid) { | ||||
|     extensionModule = extension.imports.extension; | ||||
|  | ||||
|     if (extensionModule.init) { | ||||
|         try { | ||||
|             extensionState = extensionModule.init(extension); | ||||
|         } catch(e) { | ||||
|             logExtensionError(uuid, e); | ||||
|             return false; | ||||
|         } | ||||
|         extensionState = extensionModule.init(extension); | ||||
|     } | ||||
|  | ||||
|     if (!extensionState) | ||||
| @@ -234,7 +210,6 @@ function initExtension(uuid) { | ||||
|  | ||||
|     extension.state = ExtensionState.DISABLED; | ||||
|     _signals.emit('extension-loaded', uuid); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function getEnabledExtensions() { | ||||
| @@ -256,7 +231,11 @@ function onEnabledExtensionsChanged() { | ||||
|     newEnabledExtensions.filter(function(uuid) { | ||||
|         return enabledExtensions.indexOf(uuid) == -1; | ||||
|     }).forEach(function(uuid) { | ||||
|         enableExtension(uuid); | ||||
|         try { | ||||
|             enableExtension(uuid); | ||||
|         } catch(e) { | ||||
|             logExtensionError(uuid, e); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     // Find and disable all the newly disabled extensions: UUIDs found in the | ||||
| @@ -264,37 +243,27 @@ function onEnabledExtensionsChanged() { | ||||
|     enabledExtensions.filter(function(item) { | ||||
|         return newEnabledExtensions.indexOf(item) == -1; | ||||
|     }).forEach(function(uuid) { | ||||
|         disableExtension(uuid); | ||||
|         try { | ||||
|             disableExtension(uuid); | ||||
|         } catch(e) { | ||||
|             logExtensionError(uuid, e); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     enabledExtensions = newEnabledExtensions; | ||||
| } | ||||
|  | ||||
| function _onVersionValidationChanged() { | ||||
|     // we want to reload all extensions, but only enable | ||||
|     // extensions when allowed by the sessionMode, so | ||||
|     // temporarily disable them all | ||||
|     enabledExtensions = []; | ||||
|     for (let uuid in ExtensionUtils.extensions) | ||||
|         reloadExtension(ExtensionUtils.extensions[uuid]); | ||||
|     enabledExtensions = getEnabledExtensions(); | ||||
|  | ||||
|     if (Main.sessionMode.allowExtensions) { | ||||
|         enabledExtensions.forEach(function(uuid) { | ||||
|             enableExtension(uuid); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function _loadExtensions() { | ||||
|     global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged); | ||||
|     global.settings.connect('changed::' + EXTENSION_DISABLE_VERSION_CHECK_KEY, _onVersionValidationChanged); | ||||
|  | ||||
|     enabledExtensions = getEnabledExtensions(); | ||||
|  | ||||
|     let finder = new ExtensionUtils.ExtensionFinder(); | ||||
|     finder.connect('extension-found', function(finder, extension) { | ||||
|         loadExtension(extension); | ||||
|     finder.connect('extension-found', function(signals, extension) { | ||||
|         try { | ||||
|             loadExtension(extension); | ||||
|         } catch(e) { | ||||
|             logExtensionError(extension.uuid, e); | ||||
|         } | ||||
|     }); | ||||
|     finder.scanExtensions(); | ||||
| } | ||||
| @@ -319,7 +288,7 @@ function disableAllExtensions() { | ||||
|         return; | ||||
|  | ||||
|     if (initted) { | ||||
|         extensionOrder.slice().reverse().forEach(function(uuid) { | ||||
|         enabledExtensions.forEach(function(uuid) { | ||||
|             disableExtension(uuid); | ||||
|         }); | ||||
|     } | ||||
|   | ||||
							
								
								
									
										45
									
								
								js/ui/flashspot.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								js/ui/flashspot.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Lang = imports.lang; | ||||
|  | ||||
| const Lightbox = imports.ui.lightbox; | ||||
| const Main = imports.ui.main; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const FLASHSPOT_ANIMATION_TIME = 0.25; // seconds | ||||
|  | ||||
| const Flashspot = new Lang.Class({ | ||||
|     Name: 'Flashspot', | ||||
|     Extends: Lightbox.Lightbox, | ||||
|  | ||||
|     _init: function(area) { | ||||
|         this.parent(Main.uiGroup, { inhibitEvents: true, | ||||
|                                     width: area.width, | ||||
|                                     height: area.height }); | ||||
|  | ||||
|         this.actor.style_class = 'flashspot'; | ||||
|         this.actor.set_position(area.x, area.y); | ||||
|     }, | ||||
|  | ||||
|     fire: function() { | ||||
|         this.actor.opacity = 0; | ||||
|         Tweener.addTween(this.actor, | ||||
|                          { opacity: 255, | ||||
|                            time: FLASHSPOT_ANIMATION_TIME, | ||||
|                            transition: 'linear', | ||||
|                            onComplete: Lang.bind(this, this._onFireShowComplete) | ||||
|                          }); | ||||
|         this.actor.show(); | ||||
|     }, | ||||
|  | ||||
|     _onFireShowComplete: function() { | ||||
|         Tweener.addTween(this.actor, | ||||
|                          { opacity: 0, | ||||
|                            time: FLASHSPOT_ANIMATION_TIME, | ||||
|                            transition: 'linear', | ||||
|                            onComplete: Lang.bind(this, function() { | ||||
|                                this.destroy(); | ||||
|                            }) | ||||
|                          }); | ||||
|     } | ||||
| }); | ||||
| @@ -1,95 +0,0 @@ | ||||
| /** -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ | ||||
| /* | ||||
|  * Copyright 2012 Inclusive Design Research Centre, OCAD University. | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or | ||||
|  * modify it under the terms of the GNU Lesser General Public | ||||
|  * License as published by the Free Software Foundation; either | ||||
|  * version 2 of the License, or (at your option) any later version. | ||||
|  * | ||||
|  * This library is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * Author: | ||||
|  *   Joseph Scheuhammer <clown@alum.mit.edu> | ||||
|  * Contributor: | ||||
|  *   Magdalen Berns <m.berns@sms.ed.ac.uk> | ||||
|  */ | ||||
|  | ||||
| const Atspi = imports.gi.Atspi; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const CARETMOVED        = 'object:text-caret-moved'; | ||||
| const STATECHANGED      = 'object:state-changed'; | ||||
|  | ||||
| const FocusCaretTracker = new Lang.Class({ | ||||
|     Name: 'FocusCaretTracker', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._atspiListener = Atspi.EventListener.new(Lang.bind(this, this._onChanged)); | ||||
|  | ||||
|         this._atspiInited = false; | ||||
|         this._focusListenerRegistered = false; | ||||
|         this._caretListenerRegistered = false; | ||||
|     }, | ||||
|  | ||||
|     _onChanged: function(event) { | ||||
|         if (event.type.indexOf(STATECHANGED) == 0) | ||||
|             this.emit('focus-changed', event); | ||||
|         else if (event.type == CARETMOVED) | ||||
|             this.emit('caret-moved', event); | ||||
|     }, | ||||
|  | ||||
|     _initAtspi: function() { | ||||
|         if (!this._atspiInited) { | ||||
|             Atspi.init(); | ||||
|             Atspi.set_timeout(250, 250); | ||||
|             this._atspiInited = true; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     registerFocusListener: function() { | ||||
|         if (this._focusListenerRegistered) | ||||
|             return; | ||||
|  | ||||
|         this._initAtspi(); | ||||
|  | ||||
|         this._atspiListener.register(STATECHANGED + ':focused'); | ||||
|         this._atspiListener.register(STATECHANGED + ':selected'); | ||||
|         this._focusListenerRegistered = true; | ||||
|     }, | ||||
|  | ||||
|     registerCaretListener: function() { | ||||
|         if (this._caretListenerRegistered) | ||||
|             return; | ||||
|  | ||||
|         this._initAtspi(); | ||||
|  | ||||
|         this._atspiListener.register(CARETMOVED); | ||||
|         this._caretListenerRegistered = true; | ||||
|     }, | ||||
|  | ||||
|     deregisterFocusListener: function() { | ||||
|         if (!this._focusListenerRegistered) | ||||
|             return; | ||||
|  | ||||
|         this._atspiListener.deregister(STATECHANGED + ':focused'); | ||||
|         this._atspiListener.deregister(STATECHANGED + ':selected'); | ||||
|         this._focusListenerRegistered = false; | ||||
|     }, | ||||
|  | ||||
|     deregisterCaretListener: function() { | ||||
|         if (!this._caretListenerRegistered) | ||||
|             return; | ||||
|  | ||||
|         this._atspiListener.deregister(CARETMOVED); | ||||
|         this._caretListenerRegistered = false; | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(FocusCaretTracker.prototype); | ||||
| @@ -1,4 +1,4 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
| /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| @@ -10,29 +10,15 @@ const St = imports.gi.St; | ||||
| const Main = imports.ui.main; | ||||
| const Params = imports.misc.params; | ||||
|  | ||||
| let _capturedEventId = 0; | ||||
| let _grabHelperStack = []; | ||||
| function _onCapturedEvent(actor, event) { | ||||
|     let grabHelper = _grabHelperStack[_grabHelperStack.length - 1]; | ||||
|     return grabHelper.onCapturedEvent(event); | ||||
| } | ||||
| function _navigateActor(actor) { | ||||
|     if (!actor) | ||||
|         return; | ||||
|  | ||||
| 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; | ||||
|     } | ||||
|     let needsGrab = true; | ||||
|     if (actor instanceof St.Widget) | ||||
|         needsGrab = !actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); | ||||
|     if (needsGrab) | ||||
|         actor.grab_key_focus(); | ||||
| } | ||||
|  | ||||
| // GrabHelper: | ||||
| @@ -56,9 +42,13 @@ const GrabHelper = new Lang.Class({ | ||||
|         this._grabStack = []; | ||||
|  | ||||
|         this._actors = []; | ||||
|         this._ignoreUntilRelease = false; | ||||
|         this._capturedEventId = 0; | ||||
|         this._keyFocusNotifyId = 0; | ||||
|         this._focusWindowChangedId = 0; | ||||
|         this._ignoreRelease = false; | ||||
|  | ||||
|         this._modalCount = 0; | ||||
|         this._grabFocusCount = 0; | ||||
|     }, | ||||
|  | ||||
|     // addActor: | ||||
| @@ -87,7 +77,7 @@ const GrabHelper = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _isWithinGrabbedActor: function(actor) { | ||||
|         let currentActor = this.currentGrab.actor; | ||||
|        let currentActor = this.currentGrab.actor; | ||||
|         while (actor) { | ||||
|             if (this._actors.indexOf(actor) != -1) | ||||
|                 return true; | ||||
| @@ -138,36 +128,38 @@ const GrabHelper = new Lang.Class({ | ||||
|     // grab: | ||||
|     // @params: A bunch of parameters, see below | ||||
|     // | ||||
|     // The general effect of a "grab" is to ensure that the passed in actor | ||||
|     // and all actors inside the grab get exclusive control of the mouse and | ||||
|     // keyboard, with the grab automatically being dropped if the user tries | ||||
|     // to dismiss it. The actor is passed in through @params.actor. | ||||
|     // Grabs the mouse and keyboard, according to the GrabHelper's | ||||
|     // parameters. If @newFocus is not %null, then the keyboard focus | ||||
|     // is moved to the first #StWidget:can-focus widget inside it. | ||||
|     // | ||||
|     // grab() can be called multiple times, with the scope of the grab being | ||||
|     // changed to a different actor every time. A nested grab does not have | ||||
|     // to have its grabbed actor inside the parent grab actors. | ||||
|     // The grab will automatically be dropped if: | ||||
|     //   - The user clicks outside the grabbed actors | ||||
|     //   - The user types Escape | ||||
|     //   - The keyboard focus is moved outside the grabbed actors | ||||
|     //   - A window is focused | ||||
|     // | ||||
|     // Grabs can be automatically dropped if the user tries to dismiss it | ||||
|     // in one of two ways: the user clicking outside the currently grabbed | ||||
|     // actor, or the user typing the Escape key. | ||||
|     // If @params.actor is not null, then it will be focused as the | ||||
|     // new actor. If you attempt to grab an already focused actor, the | ||||
|     // request to be focused will be ignored. The actor will not be | ||||
|     // added to the grab stack, so do not call a paired ungrab(). | ||||
|     // | ||||
|     // If the user clicks outside the grabbed actors, and the clicked on | ||||
|     // actor is part of a previous grab in the stack, grabs will be popped | ||||
|     // until that grab is active. However, the click event will not be | ||||
|     // replayed to the actor. | ||||
|     // If @params contains { modal: true }, then grab() will push a modal | ||||
|     // on the owner of the GrabHelper. As long as there is at least one | ||||
|     // { modal: true } actor on the grab stack, the grab will be kept. | ||||
|     // When the last { modal: true } actor is ungrabbed, then the modal | ||||
|     // will be dropped. A modal grab can fail if there is already a grab | ||||
|     // in effect from aother application; in this case the function returns | ||||
|     // false and nothing happens. Non-modal grabs can never fail. | ||||
|     // | ||||
|     // If the user types the Escape key, one grab from the grab stack will | ||||
|     // be popped. | ||||
|     // | ||||
|     // When a grab is popped by user interacting as described above, if you | ||||
|     // pass a callback as @params.onUngrab, it will be called with %true. | ||||
|     // | ||||
|     // If @params.focus is not null, we'll set the key focus directly | ||||
|     // to that actor instead of navigating in @params.actor. This is for | ||||
|     // use cases like menus, where we want to grab the menu actor, but keep | ||||
|     // focus on the clicked on menu item. | ||||
|     // If @params contains { grabFocus: true }, then if you call grab() | ||||
|     // while the shell is outside the overview, it will set the stage | ||||
|     // input mode to %Shell.StageInputMode.FOCUSED, and ungrab() will | ||||
|     // revert it back, and re-focus the previously-focused window (if | ||||
|     // another window hasn't been explicitly focused before then). | ||||
|     grab: function(params) { | ||||
|         params = Params.parse(params, { actor: null, | ||||
|                                         modal: false, | ||||
|                                         grabFocus: false, | ||||
|                                         focus: null, | ||||
|                                         onUngrab: null }); | ||||
|  | ||||
| @@ -180,18 +172,18 @@ const GrabHelper = new Lang.Class({ | ||||
|  | ||||
|         params.savedFocus = focus; | ||||
|  | ||||
|         if (!this._takeModalGrab()) | ||||
|         if (params.modal && !this._takeModalGrab()) | ||||
|             return false; | ||||
|  | ||||
|         this._grabStack.push(params); | ||||
|         if (params.grabFocus && !this._takeFocusGrab(hadFocus)) | ||||
|             return false; | ||||
|  | ||||
|         if (params.focus) { | ||||
|         if (params.focus) | ||||
|             params.focus.grab_key_focus(); | ||||
|         } else if (newFocus && hadFocus) { | ||||
|             if (!newFocus.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false)) | ||||
|                 newFocus.grab_key_focus(); | ||||
|         } | ||||
|         else if (hadFocus || params.grabFocus) | ||||
|             _navigateActor(newFocus); | ||||
|  | ||||
|         this._grabStack.push(params); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
| @@ -201,7 +193,7 @@ const GrabHelper = new Lang.Class({ | ||||
|             if (!Main.pushModal(this._owner, this._modalParams)) | ||||
|                 return false; | ||||
|  | ||||
|             _pushGrabHelper(this); | ||||
|             this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent)); | ||||
|         } | ||||
|  | ||||
|         this._modalCount++; | ||||
| @@ -213,14 +205,63 @@ const GrabHelper = new Lang.Class({ | ||||
|         if (this._modalCount > 0) | ||||
|             return; | ||||
|  | ||||
|         _popGrabHelper(this); | ||||
|  | ||||
|         this._ignoreUntilRelease = false; | ||||
|         if (this._capturedEventId > 0) { | ||||
|             global.stage.disconnect(this._capturedEventId); | ||||
|             this._capturedEventId = 0; | ||||
|         } | ||||
|  | ||||
|         Main.popModal(this._owner); | ||||
|         global.sync_pointer(); | ||||
|     }, | ||||
|  | ||||
|     _takeFocusGrab: function(hadFocus) { | ||||
|         let firstGrab = (this._grabFocusCount == 0); | ||||
|         if (firstGrab) { | ||||
|             let metaDisplay = global.screen.get_display(); | ||||
|  | ||||
|             this._grabbedFromKeynav = hadFocus; | ||||
|             this._preGrabInputMode = global.stage_input_mode; | ||||
|  | ||||
|             if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE || | ||||
|                 this._preGrabInputMode == Shell.StageInputMode.NORMAL) { | ||||
|                 global.set_stage_input_mode(Shell.StageInputMode.FOCUSED); | ||||
|             } | ||||
|  | ||||
|             this._keyFocusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged)); | ||||
|             this._focusWindowChangedId = metaDisplay.connect('notify::focus-window', Lang.bind(this, this._focusWindowChanged)); | ||||
|         } | ||||
|  | ||||
|         this._grabFocusCount++; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _releaseFocusGrab: function() { | ||||
|         this._grabFocusCount--; | ||||
|         if (this._grabFocusCount > 0) | ||||
|             return; | ||||
|  | ||||
|         if (this._keyFocusNotifyId > 0) { | ||||
|             global.stage.disconnect(this._keyFocusNotifyId); | ||||
|             this._keyFocusNotifyId = 0; | ||||
|         } | ||||
|  | ||||
|         if (!this._focusWindowChanged > 0) { | ||||
|             let metaDisplay = global.screen.get_display(); | ||||
|             metaDisplay.disconnect(this._focusWindowChangedId); | ||||
|             this._focusWindowChangedId = 0; | ||||
|         } | ||||
|  | ||||
|         let prePopInputMode = global.stage_input_mode; | ||||
|  | ||||
|         if (this._grabbedFromKeynav) { | ||||
|             if (this._preGrabInputMode == Shell.StageInputMode.FOCUSED && | ||||
|                 prePopInputMode != Shell.StageInputMode.FULLSCREEN) | ||||
|                 global.set_stage_input_mode(Shell.StageInputMode.FOCUSED); | ||||
|         } | ||||
|  | ||||
|         global.screen.focus_default_window(global.get_current_time()); | ||||
|     }, | ||||
|  | ||||
|     // ignoreRelease: | ||||
|     // | ||||
|     // Make sure that the next button release event evaluated by the | ||||
| @@ -228,20 +269,16 @@ const GrabHelper = new Lang.Class({ | ||||
|     // like the ComboBoxMenu that go away on press, but need to eat | ||||
|     // the next release event. | ||||
|     ignoreRelease: function() { | ||||
|         this._ignoreUntilRelease = true; | ||||
|         this._ignoreRelease = true; | ||||
|     }, | ||||
|  | ||||
|     // ungrab: | ||||
|     // @params: The parameters for the grab; see below. | ||||
|     // | ||||
|     // Pops @params.actor from the grab stack, potentially dropping | ||||
|     // the grab. If the actor is not on the grab stack, this call is | ||||
|     // ignored with no ill effects. | ||||
|     // Pops an actor from the grab stack, potentially dropping the grab. | ||||
|     // | ||||
|     // If the actor is not at the top of the grab stack, grabs are | ||||
|     // popped until the grabbed actor is at the top of the grab stack. | ||||
|     // The onUngrab callback for every grab is called for every popped | ||||
|     // grab with the parameter %false. | ||||
|     // If the actor that was popped from the grab stack was not the actor | ||||
|     // That was passed in, this call is ignored. | ||||
|     ungrab: function(params) { | ||||
|         params = Params.parse(params, { actor: this.currentGrab.actor, | ||||
|                                         isUser: false }); | ||||
| @@ -264,61 +301,66 @@ const GrabHelper = new Lang.Class({ | ||||
|             if (poppedGrab.onUngrab) | ||||
|                 poppedGrab.onUngrab(params.isUser); | ||||
|  | ||||
|             this._releaseModalGrab(); | ||||
|             if (poppedGrab.modal) | ||||
|                 this._releaseModalGrab(); | ||||
|  | ||||
|             if (poppedGrab.grabFocus) | ||||
|                 this._releaseFocusGrab(); | ||||
|         } | ||||
|  | ||||
|         if (hadFocus) { | ||||
|             let poppedGrab = poppedGrabs[0]; | ||||
|             if (poppedGrab.savedFocus) | ||||
|                 poppedGrab.savedFocus.grab_key_focus(); | ||||
|             _navigateActor(poppedGrab.savedFocus); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onCapturedEvent: function(event) { | ||||
|     _onCapturedEvent: function(actor, event) { | ||||
|         let type = event.type(); | ||||
|  | ||||
|         if (type == Clutter.EventType.KEY_PRESS && | ||||
|             event.get_key_symbol() == Clutter.KEY_Escape) { | ||||
|             this.ungrab({ isUser: true }); | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         let motion = type == Clutter.EventType.MOTION; | ||||
|         let press = type == Clutter.EventType.BUTTON_PRESS; | ||||
|         let release = type == Clutter.EventType.BUTTON_RELEASE; | ||||
|         let button = press || release; | ||||
|  | ||||
|         let touchUpdate = type == Clutter.EventType.TOUCH_UPDATE; | ||||
|         let touchBegin = type == Clutter.EventType.TOUCH_BEGIN; | ||||
|         let touchEnd = type == Clutter.EventType.TOUCH_END; | ||||
|         let touch = touchUpdate || touchBegin || touchEnd; | ||||
|         if (release && this._ignoreRelease) { | ||||
|             this._ignoreRelease = false; | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (touch && !global.display.is_pointer_emulating_sequence (event.get_event_sequence())) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|         if (!button && this._modalCount == 0) | ||||
|             return false; | ||||
|  | ||||
|         if (this._ignoreUntilRelease && (motion || release || touch)) { | ||||
|             if (release || touchEnd) | ||||
|                 this._ignoreUntilRelease = false; | ||||
|             return Clutter.EVENT_STOP; | ||||
|         if (type == Clutter.EventType.KEY_PRESS && | ||||
|             event.get_key_symbol() == Clutter.KEY_Escape) { | ||||
|             this.ungrab({ isUser: true }); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (this._isWithinGrabbedActor(event.get_source())) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         if (Main.keyboard.shouldTakeEvent(event)) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         if (button || touchBegin) { | ||||
|             // If we have a press event, ignore the next | ||||
|             // motion/release events. | ||||
|             if (press || touchBegin) | ||||
|                 this._ignoreUntilRelease = true; | ||||
|             return false; | ||||
|  | ||||
|         if (button) { | ||||
|             // If we have a press event, ignore the next event, | ||||
|             // which should be a release event. | ||||
|             if (press) | ||||
|                 this._ignoreRelease = true; | ||||
|             let i = this._actorInGrabStack(event.get_source()) + 1; | ||||
|             this.ungrab({ actor: this._grabStack[i].actor, isUser: true }); | ||||
|             return Clutter.EVENT_STOP; | ||||
|         } | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return this._modalCount > 0; | ||||
|     }, | ||||
|  | ||||
|     _onKeyFocusChanged: function() { | ||||
|         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 }); | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -3,150 +3,107 @@ | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const IBus = imports.gi.IBus; | ||||
| const Lang = imports.lang; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const BoxPointer = imports.ui.boxpointer; | ||||
| const Main = imports.ui.main; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
|  | ||||
| const MAX_CANDIDATES_PER_PAGE = 16; | ||||
|  | ||||
| const DEFAULT_INDEX_LABELS = [ '1', '2', '3', '4', '5', '6', '7', '8', | ||||
|                                '9', '0', 'a', 'b', 'c', 'd', 'e', 'f' ]; | ||||
|  | ||||
| const CandidateArea = new Lang.Class({ | ||||
|     Name: 'CandidateArea', | ||||
|     Extends: PopupMenu.PopupBaseMenuItem, | ||||
|  | ||||
|     _init: function() { | ||||
|         this.actor = new St.BoxLayout({ vertical: true, | ||||
|                                         visible: false }); | ||||
|         this._candidateBoxes = []; | ||||
|         this.parent({ reactive: false }); | ||||
|  | ||||
|         // St.Table exhibits some sizing problems so let's go with a | ||||
|         // clutter layout manager for now. | ||||
|         this._table = new Clutter.Actor(); | ||||
|         this.addActor(this._table); | ||||
|  | ||||
|         this._tableLayout = new Clutter.TableLayout(); | ||||
|         this._table.set_layout_manager(this._tableLayout); | ||||
|  | ||||
|         this._indexLabels = []; | ||||
|         this._candidateLabels = []; | ||||
|         for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) { | ||||
|             let box = new St.BoxLayout({ style_class: 'candidate-box', | ||||
|                                          reactive: true, | ||||
|                                          track_hover: true }); | ||||
|             box._indexLabel = new St.Label({ style_class: 'candidate-index' }); | ||||
|             box._candidateLabel = new St.Label({ style_class: 'candidate-label' }); | ||||
|             box.add(box._indexLabel, { y_fill: false }); | ||||
|             box.add(box._candidateLabel, { y_fill: false }); | ||||
|             this._candidateBoxes.push(box); | ||||
|             this.actor.add(box); | ||||
|  | ||||
|             let j = i; | ||||
|             box.connect('button-release-event', Lang.bind(this, function(actor, event) { | ||||
|                 this.emit('candidate-clicked', j, event.get_button(), event.get_state()); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|             })); | ||||
|             this._indexLabels.push(new St.Label({ style_class: 'candidate-index' })); | ||||
|             this._candidateLabels.push(new St.Label({ style_class: 'candidate-label' })); | ||||
|         } | ||||
|  | ||||
|         this._buttonBox = new St.BoxLayout({ style_class: 'candidate-page-button-box' }); | ||||
|  | ||||
|         this._previousButton = new St.Button({ style_class: 'candidate-page-button candidate-page-button-previous' }); | ||||
|         this._previousButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' }); | ||||
|         this._buttonBox.add(this._previousButton, { expand: true }); | ||||
|  | ||||
|         this._nextButton = new St.Button({ style_class: 'candidate-page-button candidate-page-button-next' }); | ||||
|         this._nextButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' }); | ||||
|         this._buttonBox.add(this._nextButton, { expand: true }); | ||||
|  | ||||
|         this.actor.add(this._buttonBox); | ||||
|  | ||||
|         this._previousButton.connect('clicked', Lang.bind(this, function() { | ||||
|             this.emit('previous-page'); | ||||
|         })); | ||||
|         this._nextButton.connect('clicked', Lang.bind(this, function() { | ||||
|             this.emit('next-page'); | ||||
|         })); | ||||
|  | ||||
|         this._orientation = -1; | ||||
|         this._cursorPosition = 0; | ||||
|     }, | ||||
|  | ||||
|     setOrientation: function(orientation) { | ||||
|     _setOrientation: function(orientation) { | ||||
|         if (this._orientation == orientation) | ||||
|             return; | ||||
|  | ||||
|         this._orientation = orientation; | ||||
|  | ||||
|         if (this._orientation == IBus.Orientation.HORIZONTAL) { | ||||
|             this.actor.vertical = false; | ||||
|             this.actor.remove_style_class_name('vertical'); | ||||
|             this.actor.add_style_class_name('horizontal'); | ||||
|             this._previousButton.child.icon_name = 'go-previous-symbolic'; | ||||
|             this._nextButton.child.icon_name = 'go-next-symbolic'; | ||||
|         } else {                // VERTICAL || SYSTEM | ||||
|             this.actor.vertical = true; | ||||
|             this.actor.add_style_class_name('vertical'); | ||||
|             this.actor.remove_style_class_name('horizontal'); | ||||
|             this._previousButton.child.icon_name = 'go-up-symbolic'; | ||||
|             this._nextButton.child.icon_name = 'go-down-symbolic'; | ||||
|         } | ||||
|         this._table.remove_all_children(); | ||||
|  | ||||
|         if (this._orientation == IBus.Orientation.HORIZONTAL) | ||||
|             for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) { | ||||
|                 this._tableLayout.pack(this._indexLabels[i], i*2, 0); | ||||
|                 this._tableLayout.pack(this._candidateLabels[i], i*2 + 1, 0); | ||||
|             } | ||||
|         else                    // VERTICAL || SYSTEM | ||||
|             for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) { | ||||
|                 this._tableLayout.pack(this._indexLabels[i], 0, i); | ||||
|                 this._tableLayout.pack(this._candidateLabels[i], 1, i); | ||||
|             } | ||||
|     }, | ||||
|  | ||||
|     setCandidates: function(indexes, candidates, cursorPosition, cursorVisible) { | ||||
|     setCandidates: function(indexes, candidates, orientation, cursorPosition, cursorVisible) { | ||||
|         this._setOrientation(orientation); | ||||
|  | ||||
|         for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) { | ||||
|             let visible = i < candidates.length; | ||||
|             let box = this._candidateBoxes[i]; | ||||
|             box.visible = visible; | ||||
|             this._indexLabels[i].visible = visible; | ||||
|             this._candidateLabels[i].visible = visible; | ||||
|  | ||||
|             if (!visible) | ||||
|                 continue; | ||||
|  | ||||
|             box._indexLabel.text = ((indexes && indexes[i]) ? indexes[i] : DEFAULT_INDEX_LABELS[i]); | ||||
|             box._candidateLabel.text = candidates[i]; | ||||
|             this._indexLabels[i].text = ((indexes && indexes[i]) ? indexes[i] : '%x.'.format(i + 1)); | ||||
|             this._candidateLabels[i].text = candidates[i]; | ||||
|         } | ||||
|  | ||||
|         this._candidateBoxes[this._cursorPosition].remove_style_pseudo_class('selected'); | ||||
|         this._candidateLabels[this._cursorPosition].remove_style_pseudo_class('selected'); | ||||
|         this._cursorPosition = cursorPosition; | ||||
|         if (cursorVisible) | ||||
|             this._candidateBoxes[cursorPosition].add_style_pseudo_class('selected'); | ||||
|     }, | ||||
|  | ||||
|     updateButtons: function(wrapsAround, page, nPages) { | ||||
|         if (nPages < 2) { | ||||
|             this._buttonBox.hide(); | ||||
|             return; | ||||
|         } | ||||
|         this._buttonBox.show(); | ||||
|         this._previousButton.reactive = wrapsAround || page > 0; | ||||
|         this._nextButton.reactive = wrapsAround || page < nPages - 1; | ||||
|             this._candidateLabels[cursorPosition].add_style_pseudo_class('selected'); | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(CandidateArea.prototype); | ||||
|  | ||||
| const CandidatePopup = new Lang.Class({ | ||||
|     Name: 'CandidatePopup', | ||||
|     Extends: PopupMenu.PopupMenu, | ||||
|  | ||||
|     _init: function() { | ||||
|         this._boxPointer = new BoxPointer.BoxPointer(St.Side.TOP); | ||||
|         this._boxPointer.actor.visible = false; | ||||
|         this._boxPointer.actor.style_class = 'candidate-popup-boxpointer'; | ||||
|         Main.layoutManager.addChrome(this._boxPointer.actor); | ||||
|         this._cursor = new St.Bin({ opacity: 0 }); | ||||
|         Main.uiGroup.add_actor(this._cursor); | ||||
|  | ||||
|         let box = new St.BoxLayout({ style_class: 'candidate-popup-content', | ||||
|                                      vertical: true }); | ||||
|         this._boxPointer.bin.set_child(box); | ||||
|         this.parent(this._cursor, 0, St.Side.TOP); | ||||
|         this.actor.hide(); | ||||
|         Main.uiGroup.add_actor(this.actor); | ||||
|  | ||||
|         this._preeditText = new St.Label({ style_class: 'candidate-popup-text', | ||||
|                                            visible: false }); | ||||
|         box.add(this._preeditText); | ||||
|         this._preeditTextItem = new PopupMenu.PopupMenuItem('', { reactive: false }); | ||||
|         this._preeditTextItem.actor.hide(); | ||||
|         this.addMenuItem(this._preeditTextItem); | ||||
|  | ||||
|         this._auxText = new St.Label({ style_class: 'candidate-popup-text', | ||||
|                                        visible: false }); | ||||
|         box.add(this._auxText); | ||||
|         this._auxTextItem = new PopupMenu.PopupMenuItem('', { reactive: false }); | ||||
|         this._auxTextItem.actor.hide(); | ||||
|         this.addMenuItem(this._auxTextItem); | ||||
|  | ||||
|         this._candidateArea = new CandidateArea(); | ||||
|         box.add(this._candidateArea.actor); | ||||
|         this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); | ||||
|  | ||||
|         this._candidateArea.connect('previous-page', Lang.bind(this, function() { | ||||
|             this._panelService.page_up(); | ||||
|         })); | ||||
|         this._candidateArea.connect('next-page', Lang.bind(this, function() { | ||||
|             this._panelService.page_down(); | ||||
|         })); | ||||
|         this._candidateArea.connect('candidate-clicked', Lang.bind(this, function(ca, index, button, state) { | ||||
|             this._panelService.candidate_clicked(index, button, state); | ||||
|         })); | ||||
|         this._lookupTableItem = new CandidateArea(); | ||||
|         this._lookupTableItem.actor.hide(); | ||||
|         this.addMenuItem(this._lookupTableItem); | ||||
|  | ||||
|         this._panelService = null; | ||||
|     }, | ||||
| @@ -158,62 +115,68 @@ const CandidatePopup = new Lang.Class({ | ||||
|  | ||||
|         panelService.connect('set-cursor-location', | ||||
|                              Lang.bind(this, function(ps, x, y, w, h) { | ||||
|                                  Main.layoutManager.setDummyCursorGeometry(x, y, w, h); | ||||
|                                  if (this._boxPointer.actor.visible) | ||||
|                                      this._boxPointer.setPosition(Main.layoutManager.dummyCursor, 0); | ||||
|                                  this._cursor.set_position(x, y); | ||||
|                                  this._cursor.set_size(w, h); | ||||
|                              })); | ||||
|         panelService.connect('update-preedit-text', | ||||
|                              Lang.bind(this, function(ps, text, cursorPosition, visible) { | ||||
|                                  this._preeditText.visible = visible; | ||||
|                                  if (visible) | ||||
|                                      this._preeditTextItem.actor.show(); | ||||
|                                  else | ||||
|                                      this._preeditTextItem.actor.hide(); | ||||
|                                  this._updateVisibility(); | ||||
|  | ||||
|                                  this._preeditText.text = text.get_text(); | ||||
|                                  this._preeditTextItem.actor.label_actor.text = text.get_text(); | ||||
|  | ||||
|                                  let attrs = text.get_attributes(); | ||||
|                                  if (attrs) | ||||
|                                      this._setTextAttributes(this._preeditText.clutter_text, | ||||
|                                      this._setTextAttributes(this._preeditTextItem.actor.label_actor.clutter_text, | ||||
|                                                              attrs); | ||||
|                              })); | ||||
|         panelService.connect('show-preedit-text', | ||||
|                              Lang.bind(this, function(ps) { | ||||
|                                  this._preeditText.show(); | ||||
|                                  this._preeditTextItem.actor.show(); | ||||
|                                  this._updateVisibility(); | ||||
|                              })); | ||||
|         panelService.connect('hide-preedit-text', | ||||
|                              Lang.bind(this, function(ps) { | ||||
|                                  this._preeditText.hide(); | ||||
|                                  this._preeditTextItem.actor.hide(); | ||||
|                                  this._updateVisibility(); | ||||
|                              })); | ||||
|         panelService.connect('update-auxiliary-text', | ||||
|                              Lang.bind(this, function(ps, text, visible) { | ||||
|                                  this._auxText.visible = visible; | ||||
|                                  if (visible) | ||||
|                                      this._auxTextItem.actor.show(); | ||||
|                                  else | ||||
|                                      this._auxTextItem.actor.hide(); | ||||
|                                  this._updateVisibility(); | ||||
|  | ||||
|                                  this._auxText.text = text.get_text(); | ||||
|                                  this._auxTextItem.actor.label_actor.text = text.get_text(); | ||||
|                              })); | ||||
|         panelService.connect('show-auxiliary-text', | ||||
|                              Lang.bind(this, function(ps) { | ||||
|                                  this._auxText.show(); | ||||
|                                  this._auxTextItem.actor.show(); | ||||
|                                  this._updateVisibility(); | ||||
|                              })); | ||||
|         panelService.connect('hide-auxiliary-text', | ||||
|                              Lang.bind(this, function(ps) { | ||||
|                                  this._auxText.hide(); | ||||
|                                  this._auxTextItem.actor.hide(); | ||||
|                                  this._updateVisibility(); | ||||
|                              })); | ||||
|         panelService.connect('update-lookup-table', | ||||
|                              Lang.bind(this, function(ps, lookupTable, visible) { | ||||
|                                  this._candidateArea.actor.visible = visible; | ||||
|                                  if (visible) | ||||
|                                      this._lookupTableItem.actor.show(); | ||||
|                                  else | ||||
|                                      this._lookupTableItem.actor.hide(); | ||||
|                                  this._updateVisibility(); | ||||
|  | ||||
|                                  let nCandidates = lookupTable.get_number_of_candidates(); | ||||
|                                  let cursorPos = lookupTable.get_cursor_pos(); | ||||
|                                  let pageSize = lookupTable.get_page_size(); | ||||
|                                  let nPages = Math.ceil(nCandidates / pageSize); | ||||
|                                  let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize)); | ||||
|                                  let startIndex = page * pageSize; | ||||
|                                  let endIndex = Math.min((page + 1) * pageSize, nCandidates); | ||||
|  | ||||
|                                  let endIndex = Math.min((page + 1) * pageSize, | ||||
|                                                          lookupTable.get_number_of_candidates()); | ||||
|                                  let indexes = []; | ||||
|                                  let indexLabel; | ||||
|                                  for (let i = 0; indexLabel = lookupTable.get_label(i); ++i) | ||||
| @@ -223,41 +186,37 @@ const CandidatePopup = new Lang.Class({ | ||||
|                                  for (let i = startIndex; i < endIndex; ++i) | ||||
|                                      candidates.push(lookupTable.get_candidate(i).get_text()); | ||||
|  | ||||
|                                  this._candidateArea.setCandidates(indexes, | ||||
|                                                                    candidates, | ||||
|                                                                    cursorPos % pageSize, | ||||
|                                                                    lookupTable.is_cursor_visible()); | ||||
|                                  this._candidateArea.setOrientation(lookupTable.get_orientation()); | ||||
|                                  this._candidateArea.updateButtons(lookupTable.is_round(), page, nPages); | ||||
|                                  this._lookupTableItem.setCandidates(indexes, | ||||
|                                                                      candidates, | ||||
|                                                                      lookupTable.get_orientation(), | ||||
|                                                                      cursorPos % pageSize, | ||||
|                                                                      lookupTable.is_cursor_visible()); | ||||
|                              })); | ||||
|         panelService.connect('show-lookup-table', | ||||
|                              Lang.bind(this, function(ps) { | ||||
|                                  this._candidateArea.actor.show(); | ||||
|                                  this._lookupTableItem.actor.show(); | ||||
|                                  this._updateVisibility(); | ||||
|                              })); | ||||
|         panelService.connect('hide-lookup-table', | ||||
|                              Lang.bind(this, function(ps) { | ||||
|                                  this._candidateArea.actor.hide(); | ||||
|                                  this._lookupTableItem.actor.hide(); | ||||
|                                  this._updateVisibility(); | ||||
|                              })); | ||||
|         panelService.connect('focus-out', | ||||
|                              Lang.bind(this, function(ps) { | ||||
|                                  this._boxPointer.hide(BoxPointer.PopupAnimation.NONE); | ||||
|                                  this.close(BoxPointer.PopupAnimation.NONE); | ||||
|                              })); | ||||
|     }, | ||||
|  | ||||
|     _updateVisibility: function() { | ||||
|         let isVisible = (this._preeditText.visible || | ||||
|                          this._auxText.visible || | ||||
|                          this._candidateArea.actor.visible); | ||||
|         let isVisible = (this._preeditTextItem.actor.visible || | ||||
|                          this._auxTextItem.actor.visible || | ||||
|                          this._lookupTableItem.actor.visible); | ||||
|  | ||||
|         if (isVisible) { | ||||
|             this._boxPointer.setPosition(Main.layoutManager.dummyCursor, 0); | ||||
|             this._boxPointer.show(BoxPointer.PopupAnimation.NONE); | ||||
|             this._boxPointer.actor.raise_top(); | ||||
|         } else { | ||||
|             this._boxPointer.hide(BoxPointer.PopupAnimation.NONE); | ||||
|         } | ||||
|         if (isVisible) | ||||
|             this.open(BoxPointer.PopupAnimation.NONE); | ||||
|         else | ||||
|             this.close(BoxPointer.PopupAnimation.NONE); | ||||
|     }, | ||||
|  | ||||
|     _setTextAttributes: function(clutterText, ibusAttrList) { | ||||
|   | ||||
| @@ -1,38 +1,14 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Params = imports.misc.params; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const Main = imports.ui.main; | ||||
|  | ||||
| const ICON_SIZE = 96; | ||||
| const MIN_ICON_SIZE = 16; | ||||
| const ICON_SIZE = 48; | ||||
|  | ||||
| const EXTRA_SPACE_ANIMATION_TIME = 0.25; | ||||
|  | ||||
| const ANIMATION_TIME_IN = 0.350; | ||||
| const ANIMATION_TIME_OUT = 1/2 * ANIMATION_TIME_IN; | ||||
| const ANIMATION_MAX_DELAY_FOR_ITEM = 2/3 * ANIMATION_TIME_IN; | ||||
| const ANIMATION_BASE_DELAY_FOR_ITEM = 1/4 * ANIMATION_MAX_DELAY_FOR_ITEM; | ||||
| const ANIMATION_MAX_DELAY_OUT_FOR_ITEM = 2/3 * ANIMATION_TIME_OUT; | ||||
| const ANIMATION_FADE_IN_TIME_FOR_ITEM = 1/4 * ANIMATION_TIME_IN; | ||||
|  | ||||
| const ANIMATION_BOUNCE_ICON_SCALE = 1.1; | ||||
|  | ||||
| const AnimationDirection = { | ||||
|     IN: 0, | ||||
|     OUT: 1 | ||||
| }; | ||||
|  | ||||
| const APPICON_ANIMATION_OUT_SCALE = 3; | ||||
| const APPICON_ANIMATION_OUT_TIME = 0.25; | ||||
|  | ||||
| const BaseIcon = new Lang.Class({ | ||||
|     Name: 'BaseIcon', | ||||
| @@ -41,12 +17,7 @@ const BaseIcon = new Lang.Class({ | ||||
|         params = Params.parse(params, { createIcon: null, | ||||
|                                         setSizeManually: false, | ||||
|                                         showLabel: true }); | ||||
|  | ||||
|         let styleClass = 'overview-icon'; | ||||
|         if (params.showLabel) | ||||
|             styleClass += ' overview-icon-with-label'; | ||||
|  | ||||
|         this.actor = new St.Bin({ style_class: styleClass, | ||||
|         this.actor = new St.Bin({ style_class: 'overview-icon', | ||||
|                                   x_fill: true, | ||||
|                                   y_fill: true }); | ||||
|         this.actor._delegate = this; | ||||
| @@ -161,6 +132,11 @@ const BaseIcon = new Lang.Class({ | ||||
|         this.icon = this.createIcon(this.iconSize); | ||||
|  | ||||
|         this._iconBin.child = this.icon; | ||||
|  | ||||
|         // The icon returned by createIcon() might actually be smaller than | ||||
|         // the requested icon size (for instance StTextureCache does this | ||||
|         // for fallback icons), so set the size explicitly. | ||||
|         this._iconBin.set_size(this.iconSize, this.iconSize); | ||||
|     }, | ||||
|  | ||||
|     _onStyleChanged: function() { | ||||
| @@ -191,86 +167,25 @@ const BaseIcon = new Lang.Class({ | ||||
|  | ||||
|     _onIconThemeChanged: function() { | ||||
|         this._createIconTexture(this.iconSize); | ||||
|     }, | ||||
|  | ||||
|     animateZoomOut: function() { | ||||
|         // Animate only the child instead of the entire actor, so the | ||||
|         // styles like hover and running are not applied while | ||||
|         // animating. | ||||
|         zoomOutActor(this.actor.child); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| function clamp(value, min, max) { | ||||
|     return Math.max(Math.min(value, max), min); | ||||
| }; | ||||
|  | ||||
| function zoomOutActor(actor) { | ||||
|     let actorClone = new Clutter.Clone({ source: actor, | ||||
|                                          reactive: false }); | ||||
|     let [width, height] = actor.get_transformed_size(); | ||||
|     let [x, y] = actor.get_transformed_position(); | ||||
|     actorClone.set_size(width, height); | ||||
|     actorClone.set_position(x, y); | ||||
|     actorClone.opacity = 255; | ||||
|     actorClone.set_pivot_point(0.5, 0.5); | ||||
|  | ||||
|     Main.uiGroup.add_actor(actorClone); | ||||
|  | ||||
|     // Avoid monitor edges to not zoom outside the current monitor | ||||
|     let monitor = Main.layoutManager.findMonitorForActor(actor); | ||||
|     let scaledWidth = width * APPICON_ANIMATION_OUT_SCALE; | ||||
|     let scaledHeight = height * APPICON_ANIMATION_OUT_SCALE; | ||||
|     let scaledX = x - (scaledWidth - width) / 2; | ||||
|     let scaledY = y - (scaledHeight - height) / 2; | ||||
|     let containedX = clamp(scaledX, monitor.x, monitor.x + monitor.width - scaledWidth); | ||||
|     let containedY = clamp(scaledY, monitor.y, monitor.y + monitor.height - scaledHeight); | ||||
|  | ||||
|     Tweener.addTween(actorClone, | ||||
|                      { time: APPICON_ANIMATION_OUT_TIME, | ||||
|                        scale_x: APPICON_ANIMATION_OUT_SCALE, | ||||
|                        scale_y: APPICON_ANIMATION_OUT_SCALE, | ||||
|                        translation_x: containedX - scaledX, | ||||
|                        translation_y: containedY - scaledY, | ||||
|                        opacity: 0, | ||||
|                        transition: 'easeOutQuad', | ||||
|                        onComplete: function() { | ||||
|                            actorClone.destroy(); | ||||
|                        } | ||||
|                     }); | ||||
| } | ||||
|  | ||||
| const IconGrid = new Lang.Class({ | ||||
|     Name: 'IconGrid', | ||||
|  | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { rowLimit: null, | ||||
|                                         columnLimit: null, | ||||
|                                         minRows: 1, | ||||
|                                         minColumns: 1, | ||||
|                                         fillParent: false, | ||||
|                                         xAlign: St.Align.MIDDLE, | ||||
|                                         padWithSpacing: false }); | ||||
|                                         xAlign: St.Align.MIDDLE }); | ||||
|         this._rowLimit = params.rowLimit; | ||||
|         this._colLimit = params.columnLimit; | ||||
|         this._minRows = params.minRows; | ||||
|         this._minColumns = params.minColumns; | ||||
|         this._xAlign = params.xAlign; | ||||
|         this._fillParent = params.fillParent; | ||||
|         this._padWithSpacing = params.padWithSpacing; | ||||
|  | ||||
|         this.topPadding = 0; | ||||
|         this.bottomPadding = 0; | ||||
|         this.rightPadding = 0; | ||||
|         this.leftPadding = 0; | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ style_class: 'icon-grid', | ||||
|                                         vertical: true }); | ||||
|         this._items = []; | ||||
|         // Pulled from CSS, but hardcode some defaults here | ||||
|         this._spacing = 0; | ||||
|         this._hItemSize = this._vItemSize = ICON_SIZE; | ||||
|         this._fixedHItemSize = this._fixedVItemSize = undefined; | ||||
|         this._grid = new Shell.GenericContainer(); | ||||
|         this.actor.add(this._grid, { expand: true, y_align: St.Align.START }); | ||||
|         this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged)); | ||||
| @@ -278,38 +193,19 @@ const IconGrid = new Lang.Class({ | ||||
|         this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); | ||||
|         this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); | ||||
|         this._grid.connect('allocate', Lang.bind(this, this._allocate)); | ||||
|         this._grid.connect('actor-added', Lang.bind(this, this._childAdded)); | ||||
|         this._grid.connect('actor-removed', Lang.bind(this, this._childRemoved)); | ||||
|     }, | ||||
|  | ||||
|     _keyFocusIn: function(actor) { | ||||
|         this.emit('key-focus-in', actor); | ||||
|     }, | ||||
|  | ||||
|     _childAdded: function(grid, child) { | ||||
|         child._iconGridKeyFocusInId = child.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); | ||||
|     }, | ||||
|  | ||||
|     _childRemoved: function(grid, child) { | ||||
|         child.disconnect(child._iconGridKeyFocusInId); | ||||
|     }, | ||||
|  | ||||
|     _getPreferredWidth: function (grid, forHeight, alloc) { | ||||
|         if (this._fillParent) | ||||
|             // Ignore all size requests of children and request a size of 0; | ||||
|             // later we'll allocate as many children as fit the parent | ||||
|             return; | ||||
|  | ||||
|         let nChildren = this._grid.get_n_children(); | ||||
|         let children = this._grid.get_children(); | ||||
|         let nColumns = this._colLimit ? Math.min(this._colLimit, | ||||
|                                                  nChildren) | ||||
|                                       : nChildren; | ||||
|         let totalSpacing = Math.max(0, nColumns - 1) * this._getSpacing(); | ||||
|                                                  children.length) | ||||
|                                       : children.length; | ||||
|         let totalSpacing = Math.max(0, nColumns - 1) * this._spacing; | ||||
|         // Kind of a lie, but not really an issue right now.  If | ||||
|         // we wanted to support some sort of hidden/overflow that would | ||||
|         // need higher level design | ||||
|         alloc.min_size = this._getHItemSize() + this.leftPadding + this.rightPadding; | ||||
|         alloc.natural_size = nColumns * this._getHItemSize() + totalSpacing + this.leftPadding + this.rightPadding; | ||||
|         alloc.min_size = this._hItemSize; | ||||
|         alloc.natural_size = nColumns * this._hItemSize + totalSpacing; | ||||
|     }, | ||||
|  | ||||
|     _getVisibleChildren: function() { | ||||
| @@ -321,18 +217,12 @@ const IconGrid = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _getPreferredHeight: function (grid, forWidth, alloc) { | ||||
|         if (this._fillParent) | ||||
|             // Ignore all size requests of children and request a size of 0; | ||||
|             // later we'll allocate as many children as fit the parent | ||||
|             return; | ||||
|  | ||||
|         let children = this._getVisibleChildren(); | ||||
|         let nColumns; | ||||
|         if (forWidth < 0) | ||||
|             nColumns = children.length; | ||||
|         else | ||||
|             [nColumns, ] = this._computeLayout(forWidth); | ||||
|  | ||||
|             nColumns = this._computeLayout(forWidth)[0]; | ||||
|         let nRows; | ||||
|         if (nColumns > 0) | ||||
|             nRows = Math.ceil(children.length / nColumns); | ||||
| @@ -340,47 +230,57 @@ const IconGrid = new Lang.Class({ | ||||
|             nRows = 0; | ||||
|         if (this._rowLimit) | ||||
|             nRows = Math.min(nRows, this._rowLimit); | ||||
|         let totalSpacing = Math.max(0, nRows - 1) * this._getSpacing(); | ||||
|         let height = nRows * this._getVItemSize() + totalSpacing + this.topPadding + this.bottomPadding; | ||||
|         let totalSpacing = Math.max(0, nRows - 1) * this._spacing; | ||||
|         let height = nRows * this._vItemSize + totalSpacing; | ||||
|         alloc.min_size = height; | ||||
|         alloc.natural_size = height; | ||||
|     }, | ||||
|  | ||||
|     _allocate: function (grid, box, flags) { | ||||
|         if (this._fillParent) { | ||||
|             // Reset the passed in box to fill the parent | ||||
|             let parentBox = this.actor.get_parent().allocation; | ||||
|             let gridBox = this.actor.get_theme_node().get_content_box(parentBox); | ||||
|             box = this._grid.get_theme_node().get_content_box(gridBox); | ||||
|         } | ||||
|  | ||||
|         let children = this._getVisibleChildren(); | ||||
|         let availWidth = box.x2 - box.x1; | ||||
|         let availHeight = box.y2 - box.y1; | ||||
|         let spacing = this._getSpacing(); | ||||
|  | ||||
|         let [nColumns, usedWidth] = this._computeLayout(availWidth); | ||||
|  | ||||
|         let leftEmptySpace; | ||||
|         let leftPadding; | ||||
|         switch(this._xAlign) { | ||||
|             case St.Align.START: | ||||
|                 leftEmptySpace = 0; | ||||
|                 leftPadding = 0; | ||||
|                 break; | ||||
|             case St.Align.MIDDLE: | ||||
|                 leftEmptySpace = Math.floor((availWidth - usedWidth) / 2); | ||||
|                 leftPadding = Math.floor((availWidth - usedWidth) / 2); | ||||
|                 break; | ||||
|             case St.Align.END: | ||||
|                 leftEmptySpace = availWidth - usedWidth; | ||||
|                 leftPadding = availWidth - usedWidth; | ||||
|         } | ||||
|  | ||||
|         let x = box.x1 + leftEmptySpace + this.leftPadding; | ||||
|         let y = box.y1 + this.topPadding; | ||||
|         let x = box.x1 + leftPadding; | ||||
|         let y = box.y1; | ||||
|         let columnIndex = 0; | ||||
|         let rowIndex = 0; | ||||
|         for (let i = 0; i < children.length; i++) { | ||||
|             let childBox = this._calculateChildBox(children[i], x, y, box); | ||||
|             let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight] | ||||
|                 = children[i].get_preferred_size(); | ||||
|  | ||||
|             if (this._rowLimit && rowIndex >= this._rowLimit || | ||||
|                 this._fillParent && childBox.y2 > availHeight - this.bottomPadding) { | ||||
|             /* Center the item in its allocation horizontally */ | ||||
|             let width = Math.min(this._hItemSize, childNaturalWidth); | ||||
|             let childXSpacing = Math.max(0, width - childNaturalWidth) / 2; | ||||
|             let height = Math.min(this._vItemSize, childNaturalHeight); | ||||
|             let childYSpacing = Math.max(0, height - childNaturalHeight) / 2; | ||||
|  | ||||
|             let childBox = new Clutter.ActorBox(); | ||||
|             if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) { | ||||
|                 let _x = box.x2 - (x + width); | ||||
|                 childBox.x1 = Math.floor(_x - childXSpacing); | ||||
|             } else { | ||||
|                 childBox.x1 = Math.floor(x + childXSpacing); | ||||
|             } | ||||
|             childBox.y1 = Math.floor(y + childYSpacing); | ||||
|             childBox.x2 = childBox.x1 + width; | ||||
|             childBox.y2 = childBox.y1 + height; | ||||
|  | ||||
|             if (this._rowLimit && rowIndex >= this._rowLimit) { | ||||
|                 this._grid.set_skip_paint(children[i], true); | ||||
|             } else { | ||||
|                 children[i].allocate(childBox, flags); | ||||
| @@ -394,225 +294,15 @@ const IconGrid = new Lang.Class({ | ||||
|             } | ||||
|  | ||||
|             if (columnIndex == 0) { | ||||
|                 y += this._getVItemSize() + spacing; | ||||
|                 x = box.x1 + leftEmptySpace + this.leftPadding; | ||||
|                 y += this._vItemSize + this._spacing; | ||||
|                 x = box.x1 + leftPadding; | ||||
|             } else { | ||||
|                 x += this._getHItemSize() + spacing; | ||||
|                 x += this._hItemSize + this._spacing; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Intended to be override by subclasses if they need a different | ||||
|      * set of items to be animated. | ||||
|      */ | ||||
|     _getChildrenToAnimate: function() { | ||||
|         return this._getVisibleChildren(); | ||||
|     }, | ||||
|  | ||||
|     _animationDone: function() { | ||||
|         this._animating = false; | ||||
|         this.emit('animation-done'); | ||||
|     }, | ||||
|  | ||||
|     animatePulse: function(animationDirection) { | ||||
|         if (animationDirection != AnimationDirection.IN) | ||||
|             throw new Error("Pulse animation only implements 'in' animation direction"); | ||||
|  | ||||
|         if (this._animating) | ||||
|             return; | ||||
|  | ||||
|         this._animating = true; | ||||
|  | ||||
|         let actors = this._getChildrenToAnimate(); | ||||
|         if (actors.length == 0) { | ||||
|             this._animationDone(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // For few items the animation can be slow, so use a smaller | ||||
|         // delay when there are less than 4 items | ||||
|         // (ANIMATION_BASE_DELAY_FOR_ITEM = 1/4 * | ||||
|         // ANIMATION_MAX_DELAY_FOR_ITEM) | ||||
|         let maxDelay = Math.min(ANIMATION_BASE_DELAY_FOR_ITEM * actors.length, | ||||
|                                 ANIMATION_MAX_DELAY_FOR_ITEM); | ||||
|  | ||||
|         for (let index = 0; index < actors.length; index++) { | ||||
|             let actor = actors[index]; | ||||
|             actor.reactive = false; | ||||
|             actor.set_scale(0, 0); | ||||
|             actor.set_pivot_point(0.5, 0.5); | ||||
|  | ||||
|             let delay = index / actors.length * maxDelay; | ||||
|             let bounceUpTime = ANIMATION_TIME_IN / 4; | ||||
|             let isLastItem = index == actors.length - 1; | ||||
|             Tweener.addTween(actor, | ||||
|                             { time: bounceUpTime, | ||||
|                               transition: 'easeInOutQuad', | ||||
|                               delay: delay, | ||||
|                               scale_x: ANIMATION_BOUNCE_ICON_SCALE, | ||||
|                               scale_y: ANIMATION_BOUNCE_ICON_SCALE, | ||||
|                               onComplete: Lang.bind(this, function() { | ||||
|                                   Tweener.addTween(actor, | ||||
|                                                    { time: ANIMATION_TIME_IN - bounceUpTime, | ||||
|                                                      transition: 'easeInOutQuad', | ||||
|                                                      scale_x: 1, | ||||
|                                                      scale_y: 1, | ||||
|                                                      onComplete: Lang.bind(this, function() { | ||||
|                                                         if (isLastItem) | ||||
|                                                             this._animationDone(); | ||||
|                                                         actor.reactive = true; | ||||
|                                                     }) | ||||
|                                                    }); | ||||
|                               }) | ||||
|                             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     animateSpring: function(animationDirection, sourceActor) { | ||||
|         if (this._animating) | ||||
|             return; | ||||
|  | ||||
|         this._animating = true; | ||||
|  | ||||
|         let actors = this._getChildrenToAnimate(); | ||||
|         if (actors.length == 0) { | ||||
|             this._animationDone(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let [sourceX, sourceY] = sourceActor.get_transformed_position(); | ||||
|         let [sourceWidth, sourceHeight] = sourceActor.get_size(); | ||||
|         // Get the center | ||||
|         let [sourceCenterX, sourceCenterY] = [sourceX + sourceWidth / 2, sourceY + sourceHeight / 2]; | ||||
|         // Design decision, 1/2 of the source actor size. | ||||
|         let [sourceScaledWidth, sourceScaledHeight] = [sourceWidth / 2, sourceHeight / 2]; | ||||
|  | ||||
|         actors.forEach(function(actor) { | ||||
|             let [actorX, actorY] = actor._transformedPosition = actor.get_transformed_position(); | ||||
|             let [x, y] = [actorX - sourceX, actorY - sourceY]; | ||||
|             actor._distance = Math.sqrt(x * x + y * y); | ||||
|         }); | ||||
|         let maxDist = actors.reduce(function(prev, cur) { | ||||
|             return Math.max(prev, cur._distance); | ||||
|         }, 0); | ||||
|         let minDist = actors.reduce(function(prev, cur) { | ||||
|             return Math.min(prev, cur._distance); | ||||
|         }, Infinity); | ||||
|         let normalization = maxDist - minDist; | ||||
|  | ||||
|         for (let index = 0; index < actors.length; index++) { | ||||
|             let actor = actors[index]; | ||||
|             actor.opacity = 0; | ||||
|             actor.reactive = false; | ||||
|  | ||||
|             let actorClone = new Clutter.Clone({ source: actor }); | ||||
|             Main.uiGroup.add_actor(actorClone); | ||||
|  | ||||
|             let [width, height,,] = this._getAllocatedChildSizeAndSpacing(actor); | ||||
|             actorClone.set_size(width, height); | ||||
|             let scaleX = sourceScaledWidth / width; | ||||
|             let scaleY = sourceScaledHeight / height; | ||||
|             let [adjustedSourcePositionX, adjustedSourcePositionY] = [sourceCenterX - sourceScaledWidth / 2, sourceCenterY - sourceScaledHeight / 2]; | ||||
|  | ||||
|             let movementParams, fadeParams; | ||||
|             if (animationDirection == AnimationDirection.IN) { | ||||
|                 let isLastItem = actor._distance == minDist; | ||||
|  | ||||
|                 actorClone.opacity = 0; | ||||
|                 actorClone.set_scale(scaleX, scaleY); | ||||
|  | ||||
|                 actorClone.set_position(adjustedSourcePositionX, adjustedSourcePositionY); | ||||
|  | ||||
|                 let delay = (1 - (actor._distance - minDist) / normalization) * ANIMATION_MAX_DELAY_FOR_ITEM; | ||||
|                 let [finalX, finalY]  = actor._transformedPosition; | ||||
|                 movementParams = { time: ANIMATION_TIME_IN, | ||||
|                                    transition: 'easeInOutQuad', | ||||
|                                    delay: delay, | ||||
|                                    x: finalX, | ||||
|                                    y: finalY, | ||||
|                                    scale_x: 1, | ||||
|                                    scale_y: 1, | ||||
|                                    onComplete: Lang.bind(this, function() { | ||||
|                                        if (isLastItem) | ||||
|                                            this._animationDone(); | ||||
|  | ||||
|                                        actor.opacity = 255; | ||||
|                                        actor.reactive = true; | ||||
|                                        actorClone.destroy(); | ||||
|                                    })}; | ||||
|                 fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM, | ||||
|                                transition: 'easeInOutQuad', | ||||
|                                delay: delay, | ||||
|                                opacity: 255 }; | ||||
|             } else { | ||||
|                 let isLastItem = actor._distance == maxDist; | ||||
|  | ||||
|                 let [startX, startY]  = actor._transformedPosition; | ||||
|                 actorClone.set_position(startX, startY); | ||||
|  | ||||
|                 let delay = (actor._distance - minDist) / normalization * ANIMATION_MAX_DELAY_OUT_FOR_ITEM; | ||||
|                 movementParams = { time: ANIMATION_TIME_OUT, | ||||
|                                    transition: 'easeInOutQuad', | ||||
|                                    delay: delay, | ||||
|                                    x: adjustedSourcePositionX, | ||||
|                                    y: adjustedSourcePositionY, | ||||
|                                    scale_x: scaleX, | ||||
|                                    scale_y: scaleY, | ||||
|                                    onComplete: Lang.bind(this, function() { | ||||
|                                        if (isLastItem) { | ||||
|                                            this._animationDone(); | ||||
|                                            this._restoreItemsOpacity(); | ||||
|                                        } | ||||
|                                        actor.reactive = true; | ||||
|                                        actorClone.destroy(); | ||||
|                                    })}; | ||||
|                 fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM, | ||||
|                                transition: 'easeInOutQuad', | ||||
|                                delay: ANIMATION_TIME_OUT + delay - ANIMATION_FADE_IN_TIME_FOR_ITEM, | ||||
|                                opacity: 0 }; | ||||
|             } | ||||
|  | ||||
|  | ||||
|             Tweener.addTween(actorClone, movementParams); | ||||
|             Tweener.addTween(actorClone, fadeParams); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _restoreItemsOpacity: function() { | ||||
|         for (let index = 0; index < this._items.length; index++) { | ||||
|             this._items[index].actor.opacity = 255; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _getAllocatedChildSizeAndSpacing: function(child) { | ||||
|         let [,, natWidth, natHeight] = child.get_preferred_size(); | ||||
|         let width = Math.min(this._getHItemSize(), natWidth); | ||||
|         let xSpacing = Math.max(0, width - natWidth) / 2; | ||||
|         let height = Math.min(this._getVItemSize(), natHeight); | ||||
|         let ySpacing = Math.max(0, height - natHeight) / 2; | ||||
|         return [width, height, xSpacing, ySpacing]; | ||||
|     }, | ||||
|  | ||||
|     _calculateChildBox: function(child, x, y, box) { | ||||
|         /* Center the item in its allocation horizontally */ | ||||
|         let [width, height, childXSpacing, childYSpacing] = | ||||
|             this._getAllocatedChildSizeAndSpacing(child); | ||||
|  | ||||
|         let childBox = new Clutter.ActorBox(); | ||||
|         if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) { | ||||
|             let _x = box.x2 - (x + width); | ||||
|             childBox.x1 = Math.floor(_x - childXSpacing); | ||||
|         } else { | ||||
|             childBox.x1 = Math.floor(x + childXSpacing); | ||||
|         } | ||||
|         childBox.y1 = Math.floor(y + childYSpacing); | ||||
|         childBox.x2 = childBox.x1 + width; | ||||
|         childBox.y2 = childBox.y1 + height; | ||||
|         return childBox; | ||||
|     }, | ||||
|  | ||||
|     columnsForWidth: function(rowWidth) { | ||||
|     childrenInRow: function(rowWidth) { | ||||
|         return this._computeLayout(rowWidth)[0]; | ||||
|     }, | ||||
|  | ||||
| @@ -622,17 +312,15 @@ const IconGrid = new Lang.Class({ | ||||
|  | ||||
|     _computeLayout: function (forWidth) { | ||||
|         let nColumns = 0; | ||||
|         let usedWidth = this.leftPadding + this.rightPadding; | ||||
|         let spacing = this._getSpacing(); | ||||
|  | ||||
|         let usedWidth = 0; | ||||
|         while ((this._colLimit == null || nColumns < this._colLimit) && | ||||
|                (usedWidth + this._getHItemSize() <= forWidth)) { | ||||
|             usedWidth += this._getHItemSize() + spacing; | ||||
|                (usedWidth + this._hItemSize <= forWidth)) { | ||||
|             usedWidth += this._hItemSize + this._spacing; | ||||
|             nColumns += 1; | ||||
|         } | ||||
|  | ||||
|         if (nColumns > 0) | ||||
|             usedWidth -= spacing; | ||||
|             usedWidth -= this._spacing; | ||||
|  | ||||
|         return [nColumns, usedWidth]; | ||||
|     }, | ||||
| @@ -645,56 +333,15 @@ const IconGrid = new Lang.Class({ | ||||
|         this._grid.queue_relayout(); | ||||
|     }, | ||||
|  | ||||
|     nRows: function(forWidth) { | ||||
|         let children = this._getVisibleChildren(); | ||||
|         let nColumns = (forWidth < 0) ? children.length : this._computeLayout(forWidth)[0]; | ||||
|         let nRows = (nColumns > 0) ? Math.ceil(children.length / nColumns) : 0; | ||||
|         if (this._rowLimit) | ||||
|             nRows = Math.min(nRows, this._rowLimit); | ||||
|         return nRows; | ||||
|     }, | ||||
|  | ||||
|     rowsForHeight: function(forHeight) { | ||||
|         return Math.floor((forHeight - (this.topPadding + this.bottomPadding) + this._getSpacing()) / (this._getVItemSize() + this._getSpacing())); | ||||
|     }, | ||||
|  | ||||
|     usedHeightForNRows: function(nRows) { | ||||
|         return (this._getVItemSize() + this._getSpacing()) * nRows - this._getSpacing() + this.topPadding + this.bottomPadding; | ||||
|     }, | ||||
|  | ||||
|     usedWidth: function(forWidth) { | ||||
|         return this.usedWidthForNColumns(this.columnsForWidth(forWidth)); | ||||
|     }, | ||||
|  | ||||
|     usedWidthForNColumns: function(columns) { | ||||
|         let usedWidth = columns  * (this._getHItemSize() + this._getSpacing()); | ||||
|         usedWidth -= this._getSpacing(); | ||||
|         return usedWidth + this.leftPadding + this.rightPadding; | ||||
|     }, | ||||
|  | ||||
|     removeAll: function() { | ||||
|         this._items = []; | ||||
|         this._grid.remove_all_children(); | ||||
|     }, | ||||
|  | ||||
|     destroyAll: function() { | ||||
|         this._items = []; | ||||
|         this._grid.destroy_all_children(); | ||||
|     }, | ||||
|  | ||||
|     addItem: function(item, index) { | ||||
|         if (!item.icon instanceof BaseIcon) | ||||
|             throw new Error('Only items with a BaseIcon icon property can be added to IconGrid'); | ||||
|  | ||||
|         this._items.push(item); | ||||
|     addItem: function(actor, index) { | ||||
|         if (index !== undefined) | ||||
|             this._grid.insert_child_at_index(item.actor, index); | ||||
|             this._grid.insert_child_at_index(actor, index); | ||||
|         else | ||||
|             this._grid.add_actor(item.actor); | ||||
|     }, | ||||
|  | ||||
|     removeItem: function(item) { | ||||
|         this._grid.remove_child(item.actor); | ||||
|             this._grid.add_actor(actor); | ||||
|     }, | ||||
|  | ||||
|     getItemAtIndex: function(index) { | ||||
| @@ -703,322 +350,5 @@ const IconGrid = new Lang.Class({ | ||||
|  | ||||
|     visibleItemsCount: function() { | ||||
|         return this._grid.get_n_children() - this._grid.get_n_skip_paint(); | ||||
|     }, | ||||
|  | ||||
|     setSpacing: function(spacing) { | ||||
|         this._fixedSpacing = spacing; | ||||
|     }, | ||||
|  | ||||
|     _getSpacing: function() { | ||||
|         return this._fixedSpacing ? this._fixedSpacing : this._spacing; | ||||
|     }, | ||||
|  | ||||
|     _getHItemSize: function() { | ||||
|         return this._fixedHItemSize ? this._fixedHItemSize : this._hItemSize; | ||||
|     }, | ||||
|  | ||||
|     _getVItemSize: function() { | ||||
|         return this._fixedVItemSize ? this._fixedVItemSize : this._vItemSize; | ||||
|     }, | ||||
|  | ||||
|     _updateSpacingForSize: function(availWidth, availHeight) { | ||||
|         let maxEmptyVArea = availHeight - this._minRows * this._getVItemSize(); | ||||
|         let maxEmptyHArea = availWidth - this._minColumns * this._getHItemSize(); | ||||
|         let maxHSpacing, maxVSpacing; | ||||
|  | ||||
|         if (this._padWithSpacing) { | ||||
|             // minRows + 1 because we want to put spacing before the first row, so it is like we have one more row | ||||
|             // to divide the empty space | ||||
|             maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows +1)); | ||||
|             maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns +1)); | ||||
|         } else { | ||||
|             if (this._minRows <=  1) | ||||
|                 maxVSpacing = maxEmptyVArea; | ||||
|             else | ||||
|                 maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows - 1)); | ||||
|  | ||||
|             if (this._minColumns <=  1) | ||||
|                 maxHSpacing = maxEmptyHArea; | ||||
|             else | ||||
|                 maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns - 1)); | ||||
|         } | ||||
|  | ||||
|         let maxSpacing = Math.min(maxHSpacing, maxVSpacing); | ||||
|         // Limit spacing to the item size | ||||
|         maxSpacing = Math.min(maxSpacing, Math.min(this._getVItemSize(), this._getHItemSize())); | ||||
|         // The minimum spacing, regardless of whether it satisfies the row/columng minima, | ||||
|         // is the spacing we get from CSS. | ||||
|         let spacing = Math.max(this._spacing, maxSpacing); | ||||
|         this.setSpacing(spacing); | ||||
|         if (this._padWithSpacing) | ||||
|             this.topPadding = this.rightPadding = this.bottomPadding = this.leftPadding = spacing; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * This function must to be called before iconGrid allocation, | ||||
|      * to know how much spacing can the grid has | ||||
|      */ | ||||
|     adaptToSize: function(availWidth, availHeight) { | ||||
|         this._fixedHItemSize = this._hItemSize; | ||||
|         this._fixedVItemSize = this._vItemSize; | ||||
|         this._updateSpacingForSize(availWidth, availHeight); | ||||
|         let spacing = this._getSpacing(); | ||||
|  | ||||
|         if (this.columnsForWidth(availWidth) < this._minColumns || this.rowsForHeight(availHeight) < this._minRows) { | ||||
|             let neededWidth = this.usedWidthForNColumns(this._minColumns) - availWidth ; | ||||
|             let neededHeight = this.usedHeightForNRows(this._minRows) - availHeight ; | ||||
|  | ||||
|             let neededSpacePerItem = (neededWidth > neededHeight) ? Math.ceil(neededWidth / this._minColumns) | ||||
|                                                                   : Math.ceil(neededHeight / this._minRows); | ||||
|             this._fixedHItemSize = Math.max(this._hItemSize - neededSpacePerItem, MIN_ICON_SIZE); | ||||
|             this._fixedVItemSize = Math.max(this._vItemSize - neededSpacePerItem, MIN_ICON_SIZE); | ||||
|  | ||||
|             this._updateSpacingForSize(availWidth, availHeight); | ||||
|         } | ||||
|         Meta.later_add(Meta.LaterType.BEFORE_REDRAW, | ||||
|                        Lang.bind(this, this._updateIconSizes)); | ||||
|     }, | ||||
|  | ||||
|     // Note that this is ICON_SIZE as used by BaseIcon, not elsewhere in IconGrid; it's a bit messed up | ||||
|     _updateIconSizes: function() { | ||||
|         let scale = Math.min(this._fixedHItemSize, this._fixedVItemSize) / Math.max(this._hItemSize, this._vItemSize); | ||||
|         let newIconSize = Math.floor(ICON_SIZE * scale); | ||||
|         for (let i in this._items) { | ||||
|             this._items[i].icon.setIconSize(newIconSize); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(IconGrid.prototype); | ||||
|  | ||||
| const PaginatedIconGrid = new Lang.Class({ | ||||
|     Name: 'PaginatedIconGrid', | ||||
|     Extends: IconGrid, | ||||
|  | ||||
|     _init: function(params) { | ||||
|         this.parent(params); | ||||
|         this._nPages = 0; | ||||
|         this.currentPage = 0; | ||||
|         this._rowsPerPage = 0; | ||||
|         this._spaceBetweenPages = 0; | ||||
|         this._childrenPerPage = 0; | ||||
|     }, | ||||
|  | ||||
|     _getPreferredHeight: function (grid, forWidth, alloc) { | ||||
|         alloc.min_size = (this._availableHeightPerPageForItems() + this.bottomPadding + this.topPadding) * this._nPages + this._spaceBetweenPages * this._nPages; | ||||
|         alloc.natural_size = (this._availableHeightPerPageForItems() + this.bottomPadding + this.topPadding) * this._nPages + this._spaceBetweenPages * this._nPages; | ||||
|     }, | ||||
|  | ||||
|     _allocate: function (grid, box, flags) { | ||||
|          if (this._childrenPerPage == 0) | ||||
|             log('computePages() must be called before allocate(); pagination will not work.'); | ||||
|  | ||||
|         if (this._fillParent) { | ||||
|             // Reset the passed in box to fill the parent | ||||
|             let parentBox = this.actor.get_parent().allocation; | ||||
|             let gridBox = this.actor.get_theme_node().get_content_box(parentBox); | ||||
|             box = this._grid.get_theme_node().get_content_box(gridBox); | ||||
|         } | ||||
|         let children = this._getVisibleChildren(); | ||||
|         let availWidth = box.x2 - box.x1; | ||||
|         let availHeight = box.y2 - box.y1; | ||||
|         let spacing = this._getSpacing(); | ||||
|         let [nColumns, usedWidth] = this._computeLayout(availWidth); | ||||
|  | ||||
|         let leftEmptySpace; | ||||
|         switch(this._xAlign) { | ||||
|             case St.Align.START: | ||||
|                 leftEmptySpace = 0; | ||||
|                 break; | ||||
|             case St.Align.MIDDLE: | ||||
|                 leftEmptySpace = Math.floor((availWidth - usedWidth) / 2); | ||||
|                 break; | ||||
|             case St.Align.END: | ||||
|                 leftEmptySpace = availWidth - usedWidth; | ||||
|         } | ||||
|  | ||||
|         let x = box.x1 + leftEmptySpace + this.leftPadding; | ||||
|         let y = box.y1 + this.topPadding; | ||||
|         let columnIndex = 0; | ||||
|         let rowIndex = 0; | ||||
|  | ||||
|         for (let i = 0; i < children.length; i++) { | ||||
|             let childBox = this._calculateChildBox(children[i], x, y, box); | ||||
|             children[i].allocate(childBox, flags); | ||||
|             this._grid.set_skip_paint(children[i], false); | ||||
|  | ||||
|             columnIndex++; | ||||
|             if (columnIndex == nColumns) { | ||||
|                 columnIndex = 0; | ||||
|                 rowIndex++; | ||||
|             } | ||||
|             if (columnIndex == 0) { | ||||
|                 y += this._getVItemSize() + spacing; | ||||
|                 if ((i + 1) % this._childrenPerPage == 0) | ||||
|                     y +=  this._spaceBetweenPages - spacing + this.bottomPadding + this.topPadding; | ||||
|                 x = box.x1 + leftEmptySpace + this.leftPadding; | ||||
|             } else | ||||
|                 x += this._getHItemSize() + spacing; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     // Overriden from IconGrid | ||||
|     _getChildrenToAnimate: function() { | ||||
|         let children = this._getVisibleChildren(); | ||||
|         let firstIndex = this._childrenPerPage * this.currentPage; | ||||
|         let lastIndex = firstIndex + this._childrenPerPage; | ||||
|  | ||||
|         return children.slice(firstIndex, lastIndex); | ||||
|     }, | ||||
|  | ||||
|     _computePages: function (availWidthPerPage, availHeightPerPage) { | ||||
|         let [nColumns, usedWidth] = this._computeLayout(availWidthPerPage); | ||||
|         let nRows; | ||||
|         let children = this._getVisibleChildren(); | ||||
|         if (nColumns > 0) | ||||
|             nRows = Math.ceil(children.length / nColumns); | ||||
|         else | ||||
|             nRows = 0; | ||||
|         if (this._rowLimit) | ||||
|             nRows = Math.min(nRows, this._rowLimit); | ||||
|  | ||||
|         let spacing = this._getSpacing(); | ||||
|         // We want to contain the grid inside the parent box with padding | ||||
|         this._rowsPerPage = this.rowsForHeight(availHeightPerPage); | ||||
|         this._nPages = Math.ceil(nRows / this._rowsPerPage); | ||||
|         this._spaceBetweenPages = availHeightPerPage - (this.topPadding + this.bottomPadding) - this._availableHeightPerPageForItems(); | ||||
|         this._childrenPerPage = nColumns * this._rowsPerPage; | ||||
|     }, | ||||
|  | ||||
|     adaptToSize: function(availWidth, availHeight) { | ||||
|         this.parent(availWidth, availHeight); | ||||
|         this._computePages(availWidth, availHeight); | ||||
|     }, | ||||
|  | ||||
|     _availableHeightPerPageForItems: function() { | ||||
|         return this.usedHeightForNRows(this._rowsPerPage) - (this.topPadding + this.bottomPadding); | ||||
|     }, | ||||
|  | ||||
|     nPages: function() { | ||||
|         return this._nPages; | ||||
|     }, | ||||
|  | ||||
|     getPageHeight: function() { | ||||
|         return this._availableHeightPerPageForItems(); | ||||
|     }, | ||||
|  | ||||
|     getPageY: function(pageNumber) { | ||||
|         if (!this._nPages) | ||||
|             return 0; | ||||
|  | ||||
|         let firstPageItem = pageNumber * this._childrenPerPage | ||||
|         let childBox = this._getVisibleChildren()[firstPageItem].get_allocation_box(); | ||||
|         return childBox.y1 - this.topPadding; | ||||
|     }, | ||||
|  | ||||
|     getItemPage: function(item) { | ||||
|         let children = this._getVisibleChildren(); | ||||
|         let index = children.indexOf(item); | ||||
|         if (index == -1) { | ||||
|             throw new Error('Item not found.'); | ||||
|             return 0; | ||||
|         } | ||||
|         return Math.floor(index / this._childrenPerPage); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|     * openExtraSpace: | ||||
|     * @sourceItem: the item for which to create extra space | ||||
|     * @side: where @sourceItem should be located relative to the created space | ||||
|     * @nRows: the amount of space to create | ||||
|     * | ||||
|     * Pan view to create extra space for @nRows above or below @sourceItem. | ||||
|     */ | ||||
|     openExtraSpace: function(sourceItem, side, nRows) { | ||||
|         let children = this._getVisibleChildren(); | ||||
|         let index = children.indexOf(sourceItem.actor); | ||||
|         if (index == -1) { | ||||
|             throw new Error('Item not found.'); | ||||
|             return; | ||||
|         } | ||||
|         let pageIndex = Math.floor(index / this._childrenPerPage); | ||||
|         let pageOffset = pageIndex * this._childrenPerPage; | ||||
|  | ||||
|         let childrenPerRow = this._childrenPerPage / this._rowsPerPage; | ||||
|         let sourceRow = Math.floor((index - pageOffset) / childrenPerRow); | ||||
|  | ||||
|         let nRowsAbove = (side == St.Side.TOP) ? sourceRow + 1 | ||||
|                                                : sourceRow; | ||||
|         let nRowsBelow = this._rowsPerPage - nRowsAbove; | ||||
|  | ||||
|         let nRowsUp, nRowsDown; | ||||
|         if (side == St.Side.TOP) { | ||||
|             nRowsDown = Math.min(nRowsBelow, nRows); | ||||
|             nRowsUp = nRows - nRowsDown; | ||||
|         } else { | ||||
|             nRowsUp = Math.min(nRowsAbove, nRows); | ||||
|             nRowsDown = nRows - nRowsUp; | ||||
|         } | ||||
|  | ||||
|         let childrenDown = children.splice(pageOffset + | ||||
|                                            nRowsAbove * childrenPerRow, | ||||
|                                            nRowsBelow * childrenPerRow); | ||||
|         let childrenUp = children.splice(pageOffset, | ||||
|                                          nRowsAbove * childrenPerRow); | ||||
|  | ||||
|         // Special case: On the last row with no rows below the icon, | ||||
|         // there's no need to move any rows either up or down | ||||
|         if (childrenDown.length == 0 && nRowsUp == 0) { | ||||
|             this._translatedChildren = []; | ||||
|             this.emit('space-opened'); | ||||
|         } else { | ||||
|             this._translateChildren(childrenUp, Gtk.DirectionType.UP, nRowsUp); | ||||
|             this._translateChildren(childrenDown, Gtk.DirectionType.DOWN, nRowsDown); | ||||
|             this._translatedChildren = childrenUp.concat(childrenDown); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _translateChildren: function(children, direction, nRows) { | ||||
|         let translationY = nRows * (this._getVItemSize() + this._getSpacing()); | ||||
|         if (translationY == 0) | ||||
|             return; | ||||
|  | ||||
|         if (direction == Gtk.DirectionType.UP) | ||||
|             translationY *= -1; | ||||
|  | ||||
|         for (let i = 0; i < children.length; i++) { | ||||
|             children[i].translation_y = 0; | ||||
|             let params = { translation_y: translationY, | ||||
|                            time: EXTRA_SPACE_ANIMATION_TIME, | ||||
|                            transition: 'easeInOutQuad' | ||||
|                          }; | ||||
|             if (i == (children.length - 1)) | ||||
|                 params.onComplete = Lang.bind(this, | ||||
|                     function() { | ||||
|                         this.emit('space-opened'); | ||||
|                     }); | ||||
|             Tweener.addTween(children[i], params); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     closeExtraSpace: function() { | ||||
|         if (!this._translatedChildren || !this._translatedChildren.length) { | ||||
|             this.emit('space-closed'); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         for (let i = 0; i < this._translatedChildren.length; i++) { | ||||
|             if (!this._translatedChildren[i].translation_y) | ||||
|                 continue; | ||||
|             Tweener.addTween(this._translatedChildren[i], | ||||
|                              { translation_y: 0, | ||||
|                                time: EXTRA_SPACE_ANIMATION_TIME, | ||||
|                                transition: 'easeInOutQuad', | ||||
|                                onComplete: Lang.bind(this, | ||||
|                                    function() { | ||||
|                                        this.emit('space-closed'); | ||||
|                                    }) | ||||
|                              }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(PaginatedIconGrid.prototype); | ||||
|   | ||||
| @@ -2,80 +2,53 @@ | ||||
|  | ||||
| const Caribou = imports.gi.Caribou; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const DBus = imports.dbus; | ||||
| const Gdk = imports.gi.Gdk; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const BoxPointer = imports.ui.boxpointer; | ||||
| const Layout = imports.ui.layout; | ||||
| const Main = imports.ui.main; | ||||
| const MessageTray = imports.ui.messageTray; | ||||
|  | ||||
| const KEYBOARD_REST_TIME = Layout.KEYBOARD_ANIMATION_TIME * 2 * 1000; | ||||
|  | ||||
| const KEYBOARD_SCHEMA = 'org.gnome.shell.keyboard'; | ||||
| const KEYBOARD_TYPE = 'keyboard-type'; | ||||
|  | ||||
| const A11Y_APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications'; | ||||
| const SHOW_KEYBOARD = 'screen-keyboard-enabled'; | ||||
|  | ||||
| const CURSOR_BUS_NAME = 'org.gnome.SettingsDaemon.Cursor'; | ||||
| const CURSOR_OBJECT_PATH = '/org/gnome/SettingsDaemon/Cursor'; | ||||
|  | ||||
| const CARIBOU_BUS_NAME = 'org.gnome.Caribou.Daemon'; | ||||
| const CARIBOU_OBJECT_PATH = '/org/gnome/Caribou/Daemon'; | ||||
|  | ||||
| const CaribouKeyboardIface = '<node> \ | ||||
| <interface name="org.gnome.Caribou.Keyboard"> \ | ||||
| <method name="Show"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Hide"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="SetCursorLocation"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="SetEntryLocation"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <property name="Name" access="read" type="s" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const CaribouDaemonIface = '<node> \ | ||||
| <interface name="org.gnome.Caribou.Daemon"> \ | ||||
| <method name="Run" /> \ | ||||
| <method name="Quit" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const CursorManagerIface = '<node> \ | ||||
| <interface name="org.gnome.SettingsDaemon.Cursor"> \ | ||||
| <property name="ShowOSK" type="b" access="read" /> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const CaribouDaemonProxy = Gio.DBusProxy.makeProxyWrapper(CaribouDaemonIface); | ||||
| const CursorManagerProxy = Gio.DBusProxy.makeProxyWrapper(CursorManagerIface); | ||||
| const CaribouKeyboardIface = <interface name='org.gnome.Caribou.Keyboard'> | ||||
| <method name='Show'> | ||||
|     <arg type='u' direction='in' /> | ||||
| </method> | ||||
| <method name='Hide'> | ||||
|     <arg type='u' direction='in' /> | ||||
| </method> | ||||
| <method name='SetCursorLocation'> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
| </method> | ||||
| <method name='SetEntryLocation'> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
|     <arg type='i' direction='in' /> | ||||
| </method> | ||||
| <property name='Name' access='read' type='s' /> | ||||
| </interface>; | ||||
|  | ||||
| const Key = new Lang.Class({ | ||||
|     Name: 'Key', | ||||
|  | ||||
|     _init : function(key) { | ||||
|         this._key = key; | ||||
|         this.actor = this._makeKey(key, GLib.markup_escape_text(key.label, -1)); | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|  | ||||
|         this.actor = this._makeKey(); | ||||
|  | ||||
|         this._extended_keys = this._key.get_extended_keys(); | ||||
|         this._extended_keyboard = null; | ||||
| @@ -83,7 +56,14 @@ const Key = new Lang.Class({ | ||||
|         if (this._key.name == 'Control_L' || this._key.name == 'Alt_L') | ||||
|             this._key.latch = true; | ||||
|  | ||||
|         this._key.connect('key-pressed', Lang.bind(this, function () | ||||
|                                                    { this.actor.checked = true })); | ||||
|         this._key.connect('key-released', Lang.bind(this, function () | ||||
|                                                     { this.actor.checked = false; })); | ||||
|  | ||||
|         if (this._extended_keys.length > 0) { | ||||
|             this._grabbed = false; | ||||
|             this._eventCaptureId = 0; | ||||
|             this._key.connect('notify::show-subkeys', Lang.bind(this, this._onShowSubkeysChanged)); | ||||
|             this._boxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM, | ||||
|                                                          { x_fill: true, | ||||
| @@ -98,28 +78,14 @@ const Key = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onDestroy: function() { | ||||
|         if (this._boxPointer) { | ||||
|             this._boxPointer.actor.destroy(); | ||||
|             this._boxPointer = null; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _makeKey: function (key, label) { | ||||
|     _makeKey: function () { | ||||
|         let label = GLib.markup_escape_text(this._key.label, -1); | ||||
|         let button = new St.Button ({ label: label, | ||||
|                                       style_class: 'keyboard-key' }); | ||||
|  | ||||
|         button.key_width = this._key.width; | ||||
|         button.connect('button-press-event', Lang.bind(this, | ||||
|             function () { | ||||
|                 key.press(); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|             })); | ||||
|         button.connect('button-release-event', Lang.bind(this, | ||||
|             function () { | ||||
|                 key.release(); | ||||
|                 return Clutter.EVENT_PROPAGATE; | ||||
|             })); | ||||
|         button.connect('button-press-event', Lang.bind(this, function () { this._key.press(); })); | ||||
|         button.connect('button-release-event', Lang.bind(this, function () { this._key.release(); })); | ||||
|  | ||||
|         return button; | ||||
|     }, | ||||
| @@ -140,31 +106,61 @@ const Key = new Lang.Class({ | ||||
|         for (let i = 0; i < this._extended_keys.length; ++i) { | ||||
|             let extended_key = this._extended_keys[i]; | ||||
|             let label = this._getUnichar(extended_key); | ||||
|             let key = this._makeKey(extended_key, label); | ||||
|  | ||||
|             let key = new St.Button({ label: label, style_class: 'keyboard-key' }); | ||||
|             key.extended_key = extended_key; | ||||
|             key.connect('button-press-event', Lang.bind(this, function () { extended_key.press(); })); | ||||
|             key.connect('button-release-event', Lang.bind(this, function () { extended_key.release(); })); | ||||
|             this._extended_keyboard.add(key); | ||||
|         } | ||||
|         this._boxPointer.bin.add_actor(this._extended_keyboard); | ||||
|     }, | ||||
|  | ||||
|     get subkeys() { | ||||
|         return this._boxPointer; | ||||
|     _onEventCapture: function (actor, event) { | ||||
|         let source = event.get_source(); | ||||
|         let type = event.type(); | ||||
|  | ||||
|         if ((type == Clutter.EventType.BUTTON_PRESS || | ||||
|              type == Clutter.EventType.BUTTON_RELEASE) && | ||||
|             this._extended_keyboard.contains(source)) { | ||||
|             source.extended_key.press(); | ||||
|             source.extended_key.release(); | ||||
|             return false; | ||||
|         } | ||||
|         if (type == Clutter.EventType.BUTTON_PRESS) { | ||||
|             this._boxPointer.actor.hide(); | ||||
|             this._ungrab(); | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _ungrab: function () { | ||||
|         global.stage.disconnect(this._eventCaptureId); | ||||
|         this._eventCaptureId = 0; | ||||
|         this._grabbed = false; | ||||
|         Main.popModal(this.actor); | ||||
|     }, | ||||
|  | ||||
|     _onShowSubkeysChanged: function () { | ||||
|         if (this._key.show_subkeys) { | ||||
|             this.actor.fake_release(); | ||||
|             this._boxPointer.actor.raise_top(); | ||||
|             this._boxPointer.setPosition(this.actor, 0.5); | ||||
|             this.emit('show-subkeys'); | ||||
|             this.actor.fake_release(); | ||||
|             this._boxPointer.show(BoxPointer.PopupAnimation.FULL); | ||||
|             this.actor.set_hover(false); | ||||
|             if (!this._grabbed) { | ||||
|                  Main.pushModal(this.actor); | ||||
|                  this._eventCaptureId = global.stage.connect('captured-event', Lang.bind(this, this._onEventCapture)); | ||||
|                  this._grabbed = true; | ||||
|             } | ||||
|             this._key.release(); | ||||
|         } else { | ||||
|             this.emit('hide-subkeys'); | ||||
|             if (this._grabbed) | ||||
|                 this._ungrab(); | ||||
|             this._boxPointer.hide(BoxPointer.PopupAnimation.FULL); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(Key.prototype); | ||||
|  | ||||
| const Keyboard = new Lang.Class({ | ||||
|     // HACK: we can't set Name, because it collides with Name dbus property | ||||
| @@ -179,54 +175,21 @@ const Keyboard = new Lang.Class({ | ||||
|         this._focusInExtendedKeys = false; | ||||
|  | ||||
|         this._timestamp = global.display.get_current_time_roundtrip(); | ||||
|  | ||||
|         this._keyboardSettings = new Gio.Settings({ schema_id: KEYBOARD_SCHEMA }); | ||||
|         this._keyboardSettings.connect('changed', Lang.bind(this, this._sync)); | ||||
|         this._a11yApplicationsSettings = new Gio.Settings({ schema_id: A11Y_APPLICATIONS_SCHEMA }); | ||||
|         this._a11yApplicationsSettings.connect('changed', Lang.bind(this, this._sync)); | ||||
|         this._watchNameId = Gio.bus_watch_name(Gio.BusType.SESSION, CURSOR_BUS_NAME, 0, | ||||
|                                                Lang.bind(this, this._sync), | ||||
|                                                Lang.bind(this, this._sync)); | ||||
|         this._daemonProxy = new CaribouDaemonProxy(Gio.DBus.session, CARIBOU_BUS_NAME, | ||||
|                                                    CARIBOU_OBJECT_PATH, | ||||
|                                                    Lang.bind(this, function(proxy, error) { | ||||
|                                                        if (error) { | ||||
|                                                            log(error.message); | ||||
|                                                            return; | ||||
|                                                        } | ||||
|                                                    })); | ||||
|         this._cursorProxy = new CursorManagerProxy(Gio.DBus.session, CURSOR_BUS_NAME, | ||||
|                                                    CURSOR_OBJECT_PATH, | ||||
|                                                    Lang.bind(this, function(proxy, error) { | ||||
|                                                        if (error) { | ||||
|                                                            log(error.message); | ||||
|                                                            return; | ||||
|                                                        } | ||||
|                                                        this._cursorProxy.connect('g-properties-changed', | ||||
|                                                                                  Lang.bind(this, this._sync)); | ||||
|                                                        this._sync(); | ||||
|                                                    })); | ||||
|         this._sync(); | ||||
|  | ||||
|         this._showIdleId = 0; | ||||
|         this._subkeysBoxPointer = null; | ||||
|         this._capturedEventId = 0; | ||||
|         this._capturedPress = false; | ||||
|  | ||||
|         this._keyboardVisible = false; | ||||
|         Main.layoutManager.connect('keyboard-visible-changed', Lang.bind(this, function(o, visible) { | ||||
|             this._keyboardVisible = visible; | ||||
|         })); | ||||
|         this._keyboardRequested = false; | ||||
|         this._keyboardRestingId = 0; | ||||
|  | ||||
|         Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._redraw)); | ||||
|  | ||||
|         this._keyboardSettings = new Gio.Settings({ schema: KEYBOARD_SCHEMA }); | ||||
|         this._keyboardSettings.connect('changed', Lang.bind(this, this._settingsChanged)); | ||||
|         this._a11yApplicationsSettings = new Gio.Settings({ schema: A11Y_APPLICATIONS_SCHEMA }); | ||||
|         this._a11yApplicationsSettings.connect('changed', Lang.bind(this, this._settingsChanged)); | ||||
|         this._settingsChanged(); | ||||
|     }, | ||||
|  | ||||
|     init: function () { | ||||
|         this._redraw(); | ||||
|     }, | ||||
|  | ||||
|     _sync: function () { | ||||
|         this._enableKeyboard = this._a11yApplicationsSettings.get_boolean(SHOW_KEYBOARD) || | ||||
|                                this._cursorProxy.ShowOSK; | ||||
|     _settingsChanged: function (settings, key) { | ||||
|         this._enableKeyboard = this._a11yApplicationsSettings.get_boolean(SHOW_KEYBOARD); | ||||
|         if (!this._enableKeyboard && !this._keyboard) | ||||
|             return; | ||||
|         if (this._enableKeyboard && this._keyboard && | ||||
| @@ -236,19 +199,25 @@ const Keyboard = new Lang.Class({ | ||||
|         if (this._keyboard) | ||||
|             this._destroyKeyboard(); | ||||
|  | ||||
|         if (this._enableKeyboard) | ||||
|             this._setupKeyboard(); | ||||
|         else | ||||
|         if (this._enableKeyboard) { | ||||
|             // If we've been called because the setting actually just | ||||
|             // changed to true (as opposed to being called from | ||||
|             // this._init()), then we want to pop up the keyboard. | ||||
|             let showKeyboard = (settings != null); | ||||
|  | ||||
|             // However, caribou-gtk-module or this._onKeyFocusChanged | ||||
|             // will probably immediately tell us to hide it, so we | ||||
|             // have to fake things out so we'll ignore that request. | ||||
|             if (showKeyboard) | ||||
|                 this._timestamp = global.display.get_current_time_roundtrip() + 1; | ||||
|             this._setupKeyboard(showKeyboard); | ||||
|         } else | ||||
|             Main.layoutManager.hideKeyboard(true); | ||||
|     }, | ||||
|  | ||||
|     _destroyKeyboard: function() { | ||||
|         if (this._keyboardNotifyId) | ||||
|             this._keyboard.disconnect(this._keyboardNotifyId); | ||||
|         if (this._keyboardGroupAddedId) | ||||
|             this._keyboard.disconnect(this._keyboardGroupAddedId); | ||||
|         if (this._keyboardGroupRemovedId) | ||||
|             this._keyboard.disconnect(this._keyboardGroupRemovedId); | ||||
|         if (this._focusNotifyId) | ||||
|             global.stage.disconnect(this._focusNotifyId); | ||||
|         this._keyboard = null; | ||||
| @@ -256,22 +225,9 @@ const Keyboard = new Lang.Class({ | ||||
|         this.actor = null; | ||||
|  | ||||
|         this._destroySource(); | ||||
|         this._daemonProxy.QuitRemote(function (result, error) { | ||||
|             if (error) { | ||||
|                 log(error.message); | ||||
|                 return; | ||||
|             } | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     _setupKeyboard: function() { | ||||
|         this._daemonProxy.RunRemote(function (result, error) { | ||||
|             if (error) { | ||||
|                 log(error.message); | ||||
|                 return; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     _setupKeyboard: function(show) { | ||||
|         this.actor = new St.BoxLayout({ name: 'keyboard', vertical: true, reactive: true }); | ||||
|         Main.layoutManager.keyboardBox.add_actor(this.actor); | ||||
|         Main.layoutManager.trackChrome(this.actor); | ||||
| @@ -292,11 +248,12 @@ const Keyboard = new Lang.Class({ | ||||
|         this.actor.text_direction = Clutter.TextDirection.LTR; | ||||
|  | ||||
|         this._keyboardNotifyId = this._keyboard.connect('notify::active-group', Lang.bind(this, this._onGroupChanged)); | ||||
|         this._keyboardGroupAddedId = this._keyboard.connect('group-added', Lang.bind(this, this._onGroupAdded)); | ||||
|         this._keyboardGroupRemovedId = this._keyboard.connect('group-removed', Lang.bind(this, this._onGroupRemoved)); | ||||
|         this._focusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged)); | ||||
|  | ||||
|         this._createSource(); | ||||
|         if (show) | ||||
|             this.show(Main.layoutManager.focusIndex); | ||||
|         else | ||||
|             this._createSource(); | ||||
|     }, | ||||
|  | ||||
|     _onKeyFocusChanged: function () { | ||||
| @@ -316,107 +273,88 @@ const Keyboard = new Lang.Class({ | ||||
|             return; | ||||
|  | ||||
|         let time = global.get_current_time(); | ||||
|         if (!(focus instanceof Clutter.Text)) { | ||||
|         if (focus instanceof Clutter.Text) | ||||
|             this.Show(time); | ||||
|         else | ||||
|             this.Hide(time); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (!this._showIdleId) { | ||||
|           this._showIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, | ||||
|                                            Lang.bind(this, function() { | ||||
|                                                this.Show(time); | ||||
|                                                return GLib.SOURCE_REMOVE; | ||||
|                                            })); | ||||
|           GLib.Source.set_name_by_id(this._showIdleId, '[gnome-shell] this.Show'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _createLayersForGroup: function (gname) { | ||||
|         let group = this._keyboard.get_group(gname); | ||||
|         group.connect('notify::active-level', Lang.bind(this, this._onLevelChanged)); | ||||
|         let layers = {}; | ||||
|         let levels = group.get_levels(); | ||||
|         for (let j = 0; j < levels.length; ++j) { | ||||
|             let lname = levels[j]; | ||||
|             let level = group.get_level(lname); | ||||
|             let layout = new St.BoxLayout({ style_class: 'keyboard-layout', | ||||
|                                                  vertical: true }); | ||||
|             this._loadRows(level, layout); | ||||
|             layers[lname] = layout; | ||||
|             this.actor.add(layout, { x_fill: false }); | ||||
|  | ||||
|             layout.hide(); | ||||
|         } | ||||
|         return layers; | ||||
|     }, | ||||
|  | ||||
|     _addKeys: function () { | ||||
|         let groups = this._keyboard.get_groups(); | ||||
|         for (let i = 0; i < groups.length; ++i) { | ||||
|              let gname = groups[i]; | ||||
|              this._groups[gname] = this._createLayersForGroup(gname); | ||||
|              let group = this._keyboard.get_group(gname); | ||||
|              group.connect('notify::active-level', Lang.bind(this, this._onLevelChanged)); | ||||
|              let layers = {}; | ||||
|              let levels = group.get_levels(); | ||||
|              for (let j = 0; j < levels.length; ++j) { | ||||
|                  let lname = levels[j]; | ||||
|                  let level = group.get_level(lname); | ||||
|                  let layout = new St.BoxLayout({ style_class: 'keyboard-layout', | ||||
|                                                  vertical: true }); | ||||
|                  this._loadRows(level, layout); | ||||
|                  layers[lname] = layout; | ||||
|                  this.actor.add(layout, { x_fill: false }); | ||||
|  | ||||
|                  layout.hide(); | ||||
|              } | ||||
|              this._groups[gname] = layers; | ||||
|         } | ||||
|  | ||||
|         this._setActiveLayer(); | ||||
|     }, | ||||
|  | ||||
|     _onCapturedEvent: function(actor, event) { | ||||
|         let type = event.type(); | ||||
|         let press = type == Clutter.EventType.BUTTON_PRESS; | ||||
|         let release = type == Clutter.EventType.BUTTON_RELEASE; | ||||
|     _getTrayIcon: function () { | ||||
|         let trayButton = new St.Button ({ label: _("tray"), | ||||
|                                           style_class: 'keyboard-key' }); | ||||
|         trayButton.key_width = 1; | ||||
|         trayButton.connect('button-press-event', Lang.bind(this, function () { | ||||
|             Main.messageTray.toggle(); | ||||
|         })); | ||||
|  | ||||
|         if (press) | ||||
|             this._capturedPress = true; | ||||
|         else if (release && this._capturedPress) | ||||
|             this._hideSubkeys(); | ||||
|         Main.overview.connect('showing', Lang.bind(this, function () { | ||||
|             trayButton.reactive = false; | ||||
|             trayButton.add_style_pseudo_class('grayed'); | ||||
|         })); | ||||
|         Main.overview.connect('hiding', Lang.bind(this, function () { | ||||
|             trayButton.reactive = true; | ||||
|             trayButton.remove_style_pseudo_class('grayed'); | ||||
|         })); | ||||
|         Main.sessionMode.connect('updated', Lang.bind(this, function() { | ||||
|             trayButton.reactive = !Main.sessionMode.isLocked; | ||||
|             if (Main.sessionMode.isLocked) | ||||
|                 trayButton.add_style_pseudo_class('grayed'); | ||||
|             else | ||||
|                 trayButton.remove_style_pseudo_class('grayed'); | ||||
|         })); | ||||
|  | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return trayButton; | ||||
|     }, | ||||
|  | ||||
|     _addRows : function (keys, layout) { | ||||
|         let keyboard_row = new St.BoxLayout(); | ||||
|         for (let i = 0; i < keys.length; ++i) { | ||||
|             let children = keys[i].get_children(); | ||||
|             let left_box = new St.BoxLayout({ style_class: 'keyboard-row' }); | ||||
|             let center_box = new St.BoxLayout({ style_class: 'keyboard-row' }); | ||||
|             let right_box = new St.BoxLayout({ style_class: 'keyboard-row' }); | ||||
|             let left_box = new St.BoxLayout({ style_class: 'keyboard-row' }); | ||||
|             for (let j = 0; j < children.length; ++j) { | ||||
|                 if (this._numOfHorizKeys == 0) | ||||
|                     this._numOfHorizKeys = children.length; | ||||
|                 let key = children[j]; | ||||
|                 let button = new Key(key); | ||||
|  | ||||
|                 switch (key.align) { | ||||
|                 case 'right': | ||||
|                 if (key.align == 'right') | ||||
|                     right_box.add(button.actor); | ||||
|                     break; | ||||
|                 case 'center': | ||||
|                     center_box.add(button.actor); | ||||
|                     break; | ||||
|                 case 'left': | ||||
|                 default: | ||||
|                 else | ||||
|                     left_box.add(button.actor); | ||||
|                     break; | ||||
|                 } | ||||
|                 if (key.name == 'Caribou_Prefs') { | ||||
|                     key.connect('key-released', Lang.bind(this, this.hide)); | ||||
|                 } | ||||
|  | ||||
|                 button.connect('show-subkeys', Lang.bind(this, function() { | ||||
|                     if (this._subkeysBoxPointer) | ||||
|                         this._subkeysBoxPointer.hide(BoxPointer.PopupAnimation.FULL); | ||||
|                     this._subkeysBoxPointer = button.subkeys; | ||||
|                     this._subkeysBoxPointer.show(BoxPointer.PopupAnimation.FULL); | ||||
|                     if (!this._capturedEventId) | ||||
|                         this._capturedEventId = this.actor.connect('captured-event', | ||||
|                                                                    Lang.bind(this, this._onCapturedEvent)); | ||||
|                 })); | ||||
|                 button.connect('hide-subkeys', Lang.bind(this, function() { | ||||
|                     this._hideSubkeys(); | ||||
|                 })); | ||||
|                     // Add new key for hiding message tray | ||||
|                     right_box.add(this._getTrayIcon()); | ||||
|                 } | ||||
|             } | ||||
|             keyboard_row.add(left_box, { expand: true, x_fill: false, x_align: St.Align.START }); | ||||
|             keyboard_row.add(center_box, { expand: true, x_fill: false, x_align: St.Align.MIDDLE }); | ||||
|             keyboard_row.add(right_box, { expand: true, x_fill: false, x_align: St.Align.END }); | ||||
|         } | ||||
|         layout.add(keyboard_row); | ||||
| @@ -489,14 +427,6 @@ const Keyboard = new Lang.Class({ | ||||
|         this._redraw(); | ||||
|     }, | ||||
|  | ||||
|     _onGroupAdded: function (keyboard, gname) { | ||||
|         this._groups[gname] = this._createLayersForGroup(gname); | ||||
|     }, | ||||
|  | ||||
|     _onGroupRemoved: function (keyboard, gname) { | ||||
|         delete this._groups[gname]; | ||||
|     }, | ||||
|  | ||||
|     _setActiveLayer: function () { | ||||
|         let active_group_name = this._keyboard.active_group; | ||||
|         let active_group = this._keyboard.get_group(active_group_name); | ||||
| @@ -514,6 +444,7 @@ const Keyboard = new Lang.Class({ | ||||
|     _createSource: function () { | ||||
|         if (this._source == null) { | ||||
|             this._source = new KeyboardSource(this); | ||||
|             this._source.setTransient(true); | ||||
|             Main.messageTray.add(this._source); | ||||
|         } | ||||
|     }, | ||||
| @@ -531,39 +462,7 @@ const Keyboard = new Lang.Class({ | ||||
|                actor._extended_keys || actor.extended_key; | ||||
|     }, | ||||
|  | ||||
|     _clearKeyboardRestTimer: function() { | ||||
|         if (!this._keyboardRestingId) | ||||
|             return; | ||||
|         GLib.source_remove(this._keyboardRestingId); | ||||
|         this._keyboardRestingId = 0; | ||||
|     }, | ||||
|  | ||||
|     show: function (monitor) { | ||||
|         this._keyboardRequested = true; | ||||
|  | ||||
|         if (this._keyboardVisible) { | ||||
|             if (monitor != Main.layoutManager.keyboardIndex) { | ||||
|                 Main.layoutManager.keyboardIndex = monitor; | ||||
|                 this._redraw(); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._clearKeyboardRestTimer(); | ||||
|         this._keyboardRestingId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, | ||||
|                                                    KEYBOARD_REST_TIME, | ||||
|                                                    Lang.bind(this, function() { | ||||
|                                                        this._clearKeyboardRestTimer(); | ||||
|                                                        this._show(monitor); | ||||
|                                                        return GLib.SOURCE_REMOVE; | ||||
|                                                    })); | ||||
|         GLib.Source.set_name_by_id(this._keyboardRestingId, '[gnome-shell] this._clearKeyboardRestTimer'); | ||||
|     }, | ||||
|  | ||||
|     _show: function(monitor) { | ||||
|         if (!this._keyboardRequested) | ||||
|             return; | ||||
|  | ||||
|         Main.layoutManager.keyboardIndex = monitor; | ||||
|         this._redraw(); | ||||
|         Main.layoutManager.showKeyboard(); | ||||
| @@ -571,46 +470,13 @@ const Keyboard = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     hide: function () { | ||||
|         this._keyboardRequested = false; | ||||
|  | ||||
|         if (!this._keyboardVisible) | ||||
|             return; | ||||
|  | ||||
|         this._clearKeyboardRestTimer(); | ||||
|         this._keyboardRestingId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, | ||||
|                                                    KEYBOARD_REST_TIME, | ||||
|                                                    Lang.bind(this, function() { | ||||
|                                                        this._clearKeyboardRestTimer(); | ||||
|                                                        this._hide(); | ||||
|                                                        return GLib.SOURCE_REMOVE; | ||||
|                                                    })); | ||||
|         GLib.Source.set_name_by_id(this._keyboardRestingId, '[gnome-shell] this._clearKeyboardRestTimer'); | ||||
|     }, | ||||
|  | ||||
|     _hide: function() { | ||||
|         if (this._keyboardRequested) | ||||
|             return; | ||||
|  | ||||
|         this._hideSubkeys(); | ||||
|         Main.layoutManager.hideKeyboard(); | ||||
|         this._createSource(); | ||||
|     }, | ||||
|  | ||||
|     _hideSubkeys: function() { | ||||
|         if (this._subkeysBoxPointer) { | ||||
|             this._subkeysBoxPointer.hide(BoxPointer.PopupAnimation.FULL); | ||||
|             this._subkeysBoxPointer = null; | ||||
|         } | ||||
|         if (this._capturedEventId) { | ||||
|             this.actor.disconnect(this._capturedEventId); | ||||
|             this._capturedEventId = 0; | ||||
|         } | ||||
|         this._capturedPress = false; | ||||
|     }, | ||||
|  | ||||
|     _moveTemporarily: function () { | ||||
|         let currentWindow = global.screen.get_display().focus_window; | ||||
|         let rect = currentWindow.get_frame_rect(); | ||||
|         let rect = currentWindow.get_outer_rect(); | ||||
|  | ||||
|         let newX = rect.x; | ||||
|         let newY = 3 * this.actor.height / 2; | ||||
| @@ -636,13 +502,6 @@ const Keyboard = new Lang.Class({ | ||||
|         return one - two; | ||||
|     }, | ||||
|  | ||||
|     _clearShowIdle: function() { | ||||
|         if (!this._showIdleId) | ||||
|             return; | ||||
|         GLib.source_remove(this._showIdleId); | ||||
|         this._showIdleId = 0; | ||||
|     }, | ||||
|  | ||||
|     // D-Bus methods | ||||
|     Show: function(timestamp) { | ||||
|         if (!this._enableKeyboard) | ||||
| @@ -651,8 +510,6 @@ const Keyboard = new Lang.Class({ | ||||
|         if (this._compareTimestamp(timestamp, this._timestamp) < 0) | ||||
|             return; | ||||
|  | ||||
|         this._clearShowIdle(); | ||||
|  | ||||
|         if (timestamp != Clutter.CURRENT_TIME) | ||||
|             this._timestamp = timestamp; | ||||
|         this.show(Main.layoutManager.focusIndex); | ||||
| @@ -665,8 +522,6 @@ const Keyboard = new Lang.Class({ | ||||
|         if (this._compareTimestamp(timestamp, this._timestamp) < 0) | ||||
|             return; | ||||
|  | ||||
|         this._clearShowIdle(); | ||||
|  | ||||
|         if (timestamp != Clutter.CURRENT_TIME) | ||||
|             this._timestamp = timestamp; | ||||
|         this.hide(); | ||||
| @@ -701,7 +556,11 @@ const KeyboardSource = new Lang.Class({ | ||||
|         this.keepTrayOnSummaryClick = true; | ||||
|     }, | ||||
|  | ||||
|     handleSummaryClick: function(button) { | ||||
|     handleSummaryClick: function() { | ||||
|         let event = Clutter.get_current_event(); | ||||
|         if (event.type() != Clutter.EventType.BUTTON_RELEASE) | ||||
|             return false; | ||||
|  | ||||
|         this.open(); | ||||
|         return true; | ||||
|     }, | ||||
|   | ||||
							
								
								
									
										1575
									
								
								js/ui/layout.js
									
									
									
									
									
								
							
							
						
						
									
										1575
									
								
								js/ui/layout.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -3,69 +3,12 @@ | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
|  | ||||
| const Params = imports.misc.params; | ||||
| const Tweener = imports.ui.tweener; | ||||
|  | ||||
| const DEFAULT_FADE_FACTOR = 0.4; | ||||
| const VIGNETTE_BRIGHTNESS = 0.8; | ||||
| const VIGNETTE_SHARPNESS = 0.7; | ||||
|  | ||||
| const VIGNETTE_DECLARATIONS = '\ | ||||
| uniform float brightness;\n\ | ||||
| uniform float vignette_sharpness;\n'; | ||||
|  | ||||
| const VIGNETTE_CODE = '\ | ||||
| cogl_color_out.a = cogl_color_in.a;\n\ | ||||
| cogl_color_out.rgb = vec3(0.0, 0.0, 0.0);\n\ | ||||
| vec2 position = cogl_tex_coord_in[0].xy - 0.5;\n\ | ||||
| float t = length(2.0 * position);\n\ | ||||
| t = clamp(t, 0.0, 1.0);\n\ | ||||
| float pixel_brightness = mix(1.0, 1.0 - vignette_sharpness, t);\n\ | ||||
| cogl_color_out.a = cogl_color_out.a * (1 - pixel_brightness * brightness);'; | ||||
|  | ||||
| const RadialShaderQuad = new Lang.Class({ | ||||
|     Name: 'RadialShaderQuad', | ||||
|     Extends: Shell.GLSLQuad, | ||||
|  | ||||
|     _init: function(params) { | ||||
|         this.parent(params); | ||||
|  | ||||
|         this._brightnessLocation = this.get_uniform_location('brightness'); | ||||
|         this._sharpnessLocation = this.get_uniform_location('vignette_sharpness'); | ||||
|  | ||||
|         this.brightness = 1.0; | ||||
|         this.vignetteSharpness = 0.0; | ||||
|     }, | ||||
|  | ||||
|     vfunc_build_pipeline: function() { | ||||
|         this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, | ||||
|                               VIGNETTE_DECLARATIONS, VIGNETTE_CODE, true); | ||||
|     }, | ||||
|  | ||||
|     get brightness() { | ||||
|         return this._brightness; | ||||
|     }, | ||||
|  | ||||
|     set brightness(v) { | ||||
|         this._brightness = v; | ||||
|         this.set_uniform_float(this._brightnessLocation, | ||||
|                                1, [this._brightness]); | ||||
|     }, | ||||
|  | ||||
|     get vignetteSharpness() { | ||||
|         return this._sharpness; | ||||
|     }, | ||||
|  | ||||
|     set vignetteSharpness(v) { | ||||
|         this._sharpness = v; | ||||
|         this.set_uniform_float(this._sharpnessLocation, | ||||
|                                1, [this._sharpness]); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * Lightbox: | ||||
| @@ -98,24 +41,20 @@ const Lightbox = new Lang.Class({ | ||||
|         params = Params.parse(params, { inhibitEvents: false, | ||||
|                                         width: null, | ||||
|                                         height: null, | ||||
|                                         fadeFactor: DEFAULT_FADE_FACTOR, | ||||
|                                         radialEffect: false, | ||||
|                                         fadeInTime: null, | ||||
|                                         fadeOutTime: null, | ||||
|                                         fadeFactor: DEFAULT_FADE_FACTOR | ||||
|                                       }); | ||||
|  | ||||
|         this._container = container; | ||||
|         this._children = container.get_children(); | ||||
|         this._fadeInTime = params.fadeInTime; | ||||
|         this._fadeOutTime = params.fadeOutTime; | ||||
|         this._fadeFactor = params.fadeFactor; | ||||
|         this._radialEffect = Clutter.feature_available(Clutter.FeatureFlags.SHADERS_GLSL) && params.radialEffect; | ||||
|         if (this._radialEffect) | ||||
|             this.actor = new RadialShaderQuad({ x: 0, | ||||
|                                                 y: 0, | ||||
|                                                 reactive: params.inhibitEvents }); | ||||
|         else | ||||
|             this.actor = new St.Bin({ x: 0, | ||||
|                                       y: 0, | ||||
|                                       opacity: 0, | ||||
|                                       style_class: 'lightbox', | ||||
|                                       reactive: params.inhibitEvents }); | ||||
|         this.actor = new St.Bin({ x: 0, | ||||
|                                   y: 0, | ||||
|                                   style_class: 'lightbox', | ||||
|                                   reactive: params.inhibitEvents }); | ||||
|  | ||||
|         container.add_actor(this.actor); | ||||
|         this.actor.raise_top(); | ||||
| @@ -161,61 +100,38 @@ const Lightbox = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     show: function(fadeInTime) { | ||||
|         fadeInTime = fadeInTime || 0; | ||||
|  | ||||
|         Tweener.removeTweens(this.actor); | ||||
|         if (this._radialEffect) { | ||||
|     show: function() { | ||||
|         if (this._fadeInTime) { | ||||
|             this.shown = false; | ||||
|             this.actor.opacity = 0; | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { brightness: VIGNETTE_BRIGHTNESS, | ||||
|                                vignetteSharpness: VIGNETTE_SHARPNESS, | ||||
|                                time: fadeInTime, | ||||
|                              { opacity: 255 * this._fadeFactor, | ||||
|                                time: this._fadeInTime, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: Lang.bind(this, function() { | ||||
|                                    this.shown = true; | ||||
|                                    this.emit('shown'); | ||||
|                                }) | ||||
|                              }); | ||||
|         } else { | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { opacity: 255 * this._fadeFactor, | ||||
|                                time: fadeInTime, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: Lang.bind(this, function() { | ||||
|                                    this.shown = true; | ||||
|                                    this.emit('shown'); | ||||
|                                }) | ||||
|                              }); | ||||
|             this.actor.opacity = 255 * this._fadeFactor; | ||||
|             this.shown = true; | ||||
|         } | ||||
|  | ||||
|         this.actor.show(); | ||||
|     }, | ||||
|  | ||||
|     hide: function(fadeOutTime) { | ||||
|         fadeOutTime = fadeOutTime || 0; | ||||
|  | ||||
|     hide: function() { | ||||
|         this.shown = false; | ||||
|         Tweener.removeTweens(this.actor); | ||||
|         if (this._radialEffect) { | ||||
|         if (this._fadeOutTime) { | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { brightness: 1.0, | ||||
|                                vignetteSharpness: 0.0, | ||||
|                                opacity: 0, | ||||
|                                time: fadeOutTime, | ||||
|                              { opacity: 0, | ||||
|                                time: this._fadeOutTime, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: Lang.bind(this, function() { | ||||
|                                    this.actor.hide(); | ||||
|                                }) | ||||
|                              }); | ||||
|         } else { | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { opacity: 0, | ||||
|                                time: fadeOutTime, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: Lang.bind(this, function() { | ||||
|                                    this.actor.hide(); | ||||
|                                }) | ||||
|                              }); | ||||
|             this.actor.hide(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -281,4 +197,3 @@ const Lightbox = new Lang.Class({ | ||||
|         this.highlight(null); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(Lightbox.prototype); | ||||
|   | ||||
| @@ -27,8 +27,6 @@ const CHEVRON = '>>> '; | ||||
| /* Imports...feel free to add here as needed */ | ||||
| var commandHeader = 'const Clutter = imports.gi.Clutter; ' + | ||||
|                     'const GLib = imports.gi.GLib; ' + | ||||
|                     'const GObject = imports.gi.GObject; ' + | ||||
|                     'const Gio = imports.gi.Gio; ' + | ||||
|                     'const Gtk = imports.gi.Gtk; ' + | ||||
|                     'const Mainloop = imports.mainloop; ' + | ||||
|                     'const Meta = imports.gi.Meta; ' + | ||||
| @@ -41,7 +39,6 @@ var commandHeader = 'const Clutter = imports.gi.Clutter; ' + | ||||
|                      * in the shell core code too. */ | ||||
|                     'const stage = global.stage; ' + | ||||
|                     /* Special lookingGlass functions */ | ||||
|                     'const inspect = Lang.bind(Main.lookingGlass, Main.lookingGlass.inspect); ' + | ||||
|                     'const it = Main.lookingGlass.getIt(); ' + | ||||
|                     'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); '; | ||||
|  | ||||
| @@ -111,7 +108,6 @@ const AutoComplete = new Lang.Class({ | ||||
|             } | ||||
|             this._lastTabTime = currTime; | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     // Insert characters of text not already included in head at cursor position.  i.e., if text="abc" and head="a", | ||||
| @@ -311,6 +307,10 @@ const Result = new Lang.Class({ | ||||
|         box.add(resultTxt); | ||||
|         let objLink = new ObjLink(this._lookingGlass, o); | ||||
|         box.add(objLink.actor); | ||||
|         let line = new Clutter.Rectangle({ name: 'Separator' }); | ||||
|         let padBin = new St.Bin({ name: 'Separator', x_fill: true, y_fill: true }); | ||||
|         padBin.add_actor(line); | ||||
|         this.actor.add(padBin); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| @@ -561,7 +561,7 @@ const Inspector = new Lang.Class({ | ||||
|     _onKeyPressEvent: function (actor, event) { | ||||
|         if (event.get_key_symbol() == Clutter.Escape) | ||||
|             this._close(); | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _onButtonPressEvent: function (actor, event) { | ||||
| @@ -570,7 +570,7 @@ const Inspector = new Lang.Class({ | ||||
|             this.emit('target', this._target, stageX, stageY); | ||||
|         } | ||||
|         this._close(); | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _onScrollEvent: function (actor, event) { | ||||
| @@ -604,12 +604,12 @@ const Inspector = new Lang.Class({ | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _onMotionEvent: function (actor, event) { | ||||
|         this._update(event); | ||||
|         return Clutter.EVENT_STOP; | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _update: function(event) { | ||||
| @@ -632,11 +632,59 @@ const Inspector = new Lang.Class({ | ||||
|  | ||||
| Signals.addSignalMethods(Inspector.prototype); | ||||
|  | ||||
| const Memory = new Lang.Class({ | ||||
|     Name: 'Memory', | ||||
|  | ||||
|     _init: function() { | ||||
|         this.actor = new St.BoxLayout({ vertical: true }); | ||||
|         this._glibc_uordblks = new St.Label(); | ||||
|         this.actor.add(this._glibc_uordblks); | ||||
|  | ||||
|         this._js_bytes = new St.Label(); | ||||
|         this.actor.add(this._js_bytes); | ||||
|  | ||||
|         this._gjs_boxed = new St.Label(); | ||||
|         this.actor.add(this._gjs_boxed); | ||||
|  | ||||
|         this._gjs_gobject = new St.Label(); | ||||
|         this.actor.add(this._gjs_gobject); | ||||
|  | ||||
|         this._gjs_function = new St.Label(); | ||||
|         this.actor.add(this._gjs_function); | ||||
|  | ||||
|         this._gjs_closure = new St.Label(); | ||||
|         this.actor.add(this._gjs_closure); | ||||
|  | ||||
|         this._last_gc_seconds_ago = new St.Label(); | ||||
|         this.actor.add(this._last_gc_seconds_ago); | ||||
|  | ||||
|         this._gcbutton = new St.Button({ label: 'Full GC', | ||||
|                                          style_class: 'lg-obj-inspector-button' }); | ||||
|         this._gcbutton.connect('clicked', Lang.bind(this, function () { System.gc(); this._renderText(); })); | ||||
|         this.actor.add(this._gcbutton, { x_align: St.Align.START, | ||||
|                                          x_fill: false }); | ||||
|  | ||||
|         this.actor.connect('notify::mapped', Lang.bind(this, this._renderText)); | ||||
|     }, | ||||
|  | ||||
|     _renderText: function() { | ||||
|         if (!this.actor.mapped) | ||||
|             return; | ||||
|         let memInfo = global.get_memory_info(); | ||||
|         this._glibc_uordblks.text = 'glibc_uordblks: ' + memInfo.glibc_uordblks; | ||||
|         this._js_bytes.text = 'js bytes: ' + memInfo.js_bytes; | ||||
|         this._gjs_boxed.text = 'gjs_boxed: ' + memInfo.gjs_boxed; | ||||
|         this._gjs_gobject.text = 'gjs_gobject: ' + memInfo.gjs_gobject; | ||||
|         this._gjs_function.text = 'gjs_function: ' + memInfo.gjs_function; | ||||
|         this._gjs_closure.text = 'gjs_closure: ' + memInfo.gjs_closure; | ||||
|         this._last_gc_seconds_ago.text = 'last_gc_seconds_ago: ' + memInfo.last_gc_seconds_ago; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const Extensions = new Lang.Class({ | ||||
|     Name: 'Extensions', | ||||
|  | ||||
|     _init: function(lookingGlass) { | ||||
|         this._lookingGlass = lookingGlass; | ||||
|     _init: function() { | ||||
|         this.actor = new St.BoxLayout({ vertical: true, | ||||
|                                         name: 'lookingGlassExtensions' }); | ||||
|         this._noExtensions = new St.Label({ style_class: 'lg-extensions-none', | ||||
| @@ -672,13 +720,13 @@ const Extensions = new Lang.Class({ | ||||
|     _onViewSource: function (actor) { | ||||
|         let extension = actor._extension; | ||||
|         let uri = extension.dir.get_uri(); | ||||
|         Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context(0, -1)); | ||||
|         Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context()); | ||||
|         this._lookingGlass.close(); | ||||
|     }, | ||||
|  | ||||
|     _onWebPage: function (actor) { | ||||
|         let extension = actor._extension; | ||||
|         Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context(0, -1)); | ||||
|         Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context()); | ||||
|         this._lookingGlass.close(); | ||||
|     }, | ||||
|  | ||||
| @@ -797,15 +845,14 @@ const LookingGlass = new Lang.Class({ | ||||
|                                         reactive: true }); | ||||
|         this.actor.connect('key-press-event', Lang.bind(this, this._globalKeyPressEvent)); | ||||
|  | ||||
|         this._interfaceSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' }); | ||||
|         this._interfaceSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' }); | ||||
|         this._interfaceSettings.connect('changed::monospace-font-name', | ||||
|                                         Lang.bind(this, this._updateFont)); | ||||
|         this._updateFont(); | ||||
|  | ||||
|         // We want it to appear to slide out from underneath the panel | ||||
|         Main.uiGroup.add_actor(this.actor); | ||||
|         Main.uiGroup.set_child_below_sibling(this.actor, | ||||
|                                              Main.layoutManager.panelBox); | ||||
|         Main.layoutManager.panelBox.add_actor(this.actor); | ||||
|         this.actor.lower_bottom(); | ||||
|         Main.layoutManager.panelBox.connect('allocation-changed', | ||||
|                                             Lang.bind(this, this._queueResize)); | ||||
|         Main.layoutManager.keyboardBox.connect('allocation-changed', | ||||
| @@ -824,30 +871,15 @@ const LookingGlass = new Lang.Class({ | ||||
|         inspectIcon.connect('button-press-event', Lang.bind(this, function () { | ||||
|             let inspector = new Inspector(this); | ||||
|             inspector.connect('target', Lang.bind(this, function(i, target, stageX, stageY) { | ||||
|                 this._pushResult('inspect(' + Math.round(stageX) + ', ' + Math.round(stageY) + ')', target); | ||||
|                 this._pushResult('<inspect x:' + stageX + ' y:' + stageY + '>', | ||||
|                                  target); | ||||
|             })); | ||||
|             inspector.connect('closed', Lang.bind(this, function() { | ||||
|                 this.actor.show(); | ||||
|                 global.stage.set_key_focus(this._entry); | ||||
|             })); | ||||
|             this.actor.hide(); | ||||
|             return Clutter.EVENT_STOP; | ||||
|         })); | ||||
|  | ||||
|         let gcIcon = new St.Icon({ icon_name: 'gnome-fs-trash-full', | ||||
|                                    icon_size: 24 }); | ||||
|         toolbar.add_actor(gcIcon); | ||||
|         gcIcon.reactive = true; | ||||
|         gcIcon.connect('button-press-event', Lang.bind(this, function () { | ||||
|            gcIcon.icon_name = 'gnome-fs-trash-empty'; | ||||
|            System.gc(); | ||||
|            this._timeoutId = Mainloop.timeout_add(500, Lang.bind(this, function () { | ||||
|                 gcIcon.icon_name = 'gnome-fs-trash-full'; | ||||
|                 this._timeoutId = 0; | ||||
|                 return GLib.SOURCE_REMOVE; | ||||
|            })); | ||||
|            GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] gcIcon.icon_name = \'gnome-fs-trash-full\''); | ||||
|            return Clutter.EVENT_PROPAGATE; | ||||
|             return true; | ||||
|         })); | ||||
|  | ||||
|         let notebook = new Notebook(); | ||||
| @@ -877,7 +909,10 @@ const LookingGlass = new Lang.Class({ | ||||
|         this._windowList = new WindowList(this); | ||||
|         notebook.appendPage('Windows', this._windowList.actor); | ||||
|  | ||||
|         this._extensions = new Extensions(this); | ||||
|         this._memory = new Memory(); | ||||
|         notebook.appendPage('Memory', this._memory.actor); | ||||
|  | ||||
|         this._extensions = new Extensions(); | ||||
|         notebook.appendPage('Extensions', this._extensions.actor); | ||||
|  | ||||
|         this._entry.clutter_text.connect('activate', Lang.bind(this, function (o, e) { | ||||
| @@ -887,7 +922,7 @@ const LookingGlass = new Lang.Class({ | ||||
|             let text = o.get_text(); | ||||
|             // Ensure we don't get newlines in the command; the history file is | ||||
|             // newline-separated. | ||||
|             text = text.replace('\n', ' '); | ||||
|             text.replace('\n', ' '); | ||||
|             // Strip leading and trailing whitespace | ||||
|             text = text.replace(/^\s+/g, '').replace(/\s+$/g, ''); | ||||
|             if (text == '') | ||||
| @@ -915,7 +950,9 @@ const LookingGlass = new Lang.Class({ | ||||
|  | ||||
|     _updateFont: function() { | ||||
|         let fontName = this._interfaceSettings.get_string('monospace-font-name'); | ||||
|         let fontDesc = Pango.FontDescription.from_string(fontName); | ||||
|         // This is mishandled by the scanner - should by Pango.FontDescription_from_string(fontName); | ||||
|         // https://bugzilla.gnome.org/show_bug.cgi?id=595889 | ||||
|         let fontDesc = Pango.font_description_from_string(fontName); | ||||
|         // We ignore everything but size and style; you'd be crazy to set your system-wide | ||||
|         // monospace font to be bold/oblique/etc. Could easily be added here. | ||||
|         this.actor.style = | ||||
| @@ -953,18 +990,28 @@ const LookingGlass = new Lang.Class({ | ||||
|  | ||||
|     _showCompletions: function(completions) { | ||||
|         if (!this._completionActor) { | ||||
|             this._completionActor = new St.Label({ name: 'LookingGlassAutoCompletionText', style_class: 'lg-completions-text' }); | ||||
|             this._completionActor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|             this._completionActor.clutter_text.line_wrap = true; | ||||
|             let actor = new St.BoxLayout({ vertical: true }); | ||||
|  | ||||
|             this._completionText = new St.Label({ name: 'LookingGlassAutoCompletionText', style_class: 'lg-completions-text' }); | ||||
|             this._completionText.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||
|             this._completionText.clutter_text.line_wrap = true; | ||||
|             actor.add(this._completionText); | ||||
|  | ||||
|             let line = new Clutter.Rectangle(); | ||||
|             let padBin = new St.Bin({ x_fill: true, y_fill: true }); | ||||
|             padBin.add_actor(line); | ||||
|             actor.add(padBin); | ||||
|  | ||||
|             this._completionActor = actor; | ||||
|             this._evalBox.insert_child_below(this._completionActor, this._entryArea); | ||||
|         } | ||||
|  | ||||
|         this._completionActor.set_text(completions.join(', ')); | ||||
|         this._completionText.set_text(completions.join(', ')); | ||||
|  | ||||
|         // Setting the height to -1 allows us to get its actual preferred height rather than | ||||
|         // whatever was last given in set_height by Tweener. | ||||
|         this._completionActor.set_height(-1); | ||||
|         let [minHeight, naturalHeight] = this._completionActor.get_preferred_height(this._resultsArea.get_width()); | ||||
|         let [minHeight, naturalHeight] = this._completionText.get_preferred_height(this._resultsArea.get_width()); | ||||
|  | ||||
|         // Don't reanimate if we are already visible | ||||
|         if (this._completionActor.visible) { | ||||
| @@ -1010,10 +1057,6 @@ const LookingGlass = new Lang.Class({ | ||||
|         this._entry.text = ''; | ||||
|     }, | ||||
|  | ||||
|     inspect: function(x, y) { | ||||
|         return global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y); | ||||
|     }, | ||||
|  | ||||
|     getIt: function () { | ||||
|         return this._it; | ||||
|     }, | ||||
| @@ -1039,15 +1082,15 @@ const LookingGlass = new Lang.Class({ | ||||
|         let myWidth = primary.width * 0.7; | ||||
|         let availableHeight = primary.height - Main.layoutManager.keyboardBox.height; | ||||
|         let myHeight = Math.min(primary.height * 0.7, availableHeight * 0.9); | ||||
|         this.actor.x = primary.x + (primary.width - myWidth) / 2; | ||||
|         this._hiddenY = primary.y + Main.layoutManager.panelBox.height - myHeight - 4; // -4 to hide the top corners | ||||
|         this.actor.x = (primary.width - myWidth) / 2; | ||||
|         this._hiddenY = this.actor.get_parent().height - myHeight - 4; // -4 to hide the top corners | ||||
|         this._targetY = this._hiddenY + myHeight; | ||||
|         this.actor.y = this._hiddenY; | ||||
|         this.actor.width = myWidth; | ||||
|         this.actor.height = myHeight; | ||||
|         this._objInspector.actor.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8)); | ||||
|         this._objInspector.actor.set_position(this.actor.x + Math.floor(myWidth * 0.1), | ||||
|                                               this._targetY + Math.floor(myHeight * 0.1)); | ||||
|         this._objInspector.actor.set_position(primary.x + this.actor.x + Math.floor(myWidth * 0.1), | ||||
|                                               primary.y + this._targetY + Math.floor(myHeight * 0.1)); | ||||
|     }, | ||||
|  | ||||
|     insertObject: function(obj) { | ||||
| @@ -1069,7 +1112,7 @@ const LookingGlass = new Lang.Class({ | ||||
|             } else { | ||||
|                 this.close(); | ||||
|             } | ||||
|             return Clutter.EVENT_STOP; | ||||
|             return true; | ||||
|         } | ||||
|         // Ctrl+PgUp and Ctrl+PgDown switches tabs in the notebook view | ||||
|         if (modifierState & Clutter.ModifierType.CONTROL_MASK) { | ||||
| @@ -1079,14 +1122,14 @@ const LookingGlass = new Lang.Class({ | ||||
|                 this._notebook.nextTab(); | ||||
|             } | ||||
|         } | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     open : function() { | ||||
|         if (this._open) | ||||
|             return; | ||||
|  | ||||
|         if (!Main.pushModal(this._entry, { keybindingMode: Shell.KeyBindingMode.LOOKING_GLASS })) | ||||
|         if (!Main.pushModal(this._entry, { keybindingMode: Main.KeybindingMode.LOOKING_GLASS })) | ||||
|             return; | ||||
|  | ||||
|         this._notebook.selectIndex(0); | ||||
| @@ -1117,7 +1160,7 @@ const LookingGlass = new Lang.Class({ | ||||
|  | ||||
|         Main.popModal(this._entry); | ||||
|  | ||||
|         Tweener.addTween(this.actor, { time: Math.min(0.5 / St.get_slow_down_factor(), 0.5), | ||||
|         Tweener.addTween(this.actor, { time: 0.5 / St.get_slow_down_factor(), | ||||
|                                        transition: 'easeOutQuad', | ||||
|                                        y: this._hiddenY, | ||||
|                                        onComplete: Lang.bind(this, function () { | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Atspi = imports.gi.Atspi; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GDesktopEnums = imports.gi.GDesktopEnums; | ||||
| const Gio = imports.gi.Gio; | ||||
| @@ -8,11 +7,8 @@ const Shell = imports.gi.Shell; | ||||
| const St = imports.gi.St; | ||||
| const Lang = imports.lang; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
|  | ||||
| const Background = imports.ui.background; | ||||
| const FocusCaretTracker = imports.ui.focusCaretTracker; | ||||
| const Main = imports.ui.main; | ||||
| const MagnifierDBus = imports.ui.magnifierDBus; | ||||
| const Params = imports.misc.params; | ||||
| @@ -40,8 +36,6 @@ const CONTRAST_BLUE_KEY         = 'contrast-blue'; | ||||
| const LENS_MODE_KEY             = 'lens-mode'; | ||||
| const CLAMP_MODE_KEY            = 'scroll-at-edges'; | ||||
| const MOUSE_TRACKING_KEY        = 'mouse-tracking'; | ||||
| const FOCUS_TRACKING_KEY        = 'focus-tracking'; | ||||
| const CARET_TRACKING_KEY        = 'caret-tracking'; | ||||
| const SHOW_CROSS_HAIRS_KEY      = 'show-cross-hairs'; | ||||
| const CROSS_HAIRS_THICKNESS_KEY = 'cross-hairs-thickness'; | ||||
| const CROSS_HAIRS_COLOR_KEY     = 'cross-hairs-color'; | ||||
| @@ -59,9 +53,9 @@ const Magnifier = new Lang.Class({ | ||||
|         this._zoomRegions = []; | ||||
|  | ||||
|         // Create small clutter tree for the magnified mouse. | ||||
|         let cursorTracker = Meta.CursorTracker.get_for_screen(global.screen); | ||||
|         let xfixesCursor = Shell.XFixesCursor.get_for_stage(global.stage); | ||||
|         this._mouseSprite = new Clutter.Texture(); | ||||
|         Shell.util_cursor_tracker_to_clutter(cursorTracker, this._mouseSprite); | ||||
|         xfixesCursor.update_texture_image(this._mouseSprite); | ||||
|         this._cursorRoot = new Clutter.Actor(); | ||||
|         this._cursorRoot.add_actor(this._mouseSprite); | ||||
|  | ||||
| @@ -76,8 +70,8 @@ const Magnifier = new Lang.Class({ | ||||
|         let showAtLaunch = this._settingsInit(aZoomRegion); | ||||
|         aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse); | ||||
|  | ||||
|         cursorTracker.connect('cursor-changed', Lang.bind(this, this._updateMouseSprite)); | ||||
|         this._cursorTracker = cursorTracker; | ||||
|         xfixesCursor.connect('cursor-change', Lang.bind(this, this._updateMouseSprite)); | ||||
|         this._xfixesCursor = xfixesCursor; | ||||
|  | ||||
|         // Export to dbus. | ||||
|         magDBusService = new MagnifierDBus.ShellMagnifier(); | ||||
| @@ -89,7 +83,7 @@ const Magnifier = new Lang.Class({ | ||||
|      * Show the system mouse pointer. | ||||
|      */ | ||||
|     showSystemCursor: function() { | ||||
|         this._cursorTracker.set_pointer_visible(true); | ||||
|         this._xfixesCursor.show(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -97,7 +91,7 @@ const Magnifier = new Lang.Class({ | ||||
|      * Hide the system mouse pointer. | ||||
|      */ | ||||
|     hideSystemCursor: function() { | ||||
|         this._cursorTracker.set_pointer_visible(false); | ||||
|         this._xfixesCursor.hide(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -106,26 +100,19 @@ const Magnifier = new Lang.Class({ | ||||
|      * @activate:   Boolean to activate or de-activate the magnifier. | ||||
|      */ | ||||
|     setActive: function(activate) { | ||||
|         let isActive = this.isActive(); | ||||
|  | ||||
|         this._zoomRegions.forEach (function(zoomRegion, index, array) { | ||||
|             zoomRegion.setActive(activate); | ||||
|         }); | ||||
|  | ||||
|         if (isActive != activate) { | ||||
|             if (activate) { | ||||
|                 Meta.disable_unredirect_for_screen(global.screen); | ||||
|                 this.startTrackingMouse(); | ||||
|             } else { | ||||
|                 Meta.enable_unredirect_for_screen(global.screen); | ||||
|                 this.stopTrackingMouse(); | ||||
|             } | ||||
|         } | ||||
|         if (activate) | ||||
|             this.startTrackingMouse(); | ||||
|         else | ||||
|             this.stopTrackingMouse(); | ||||
|  | ||||
|         // Make sure system mouse pointer is shown when all zoom regions are | ||||
|         // invisible. | ||||
|         if (!activate) | ||||
|             this._cursorTracker.set_pointer_visible(true); | ||||
|             this._xfixesCursor.show(); | ||||
|  | ||||
|         // Notify interested parties of this change | ||||
|         this.emit('active-changed', activate); | ||||
| @@ -435,14 +422,15 @@ const Magnifier = new Lang.Class({ | ||||
|     //// Private methods //// | ||||
|  | ||||
|     _updateMouseSprite: function() { | ||||
|         Shell.util_cursor_tracker_to_clutter(this._cursorTracker, this._mouseSprite); | ||||
|         let [xHot, yHot] = this._cursorTracker.get_hot(); | ||||
|         this._xfixesCursor.update_texture_image(this._mouseSprite); | ||||
|         let xHot = this._xfixesCursor.get_hot_x(); | ||||
|         let yHot = this._xfixesCursor.get_hot_y(); | ||||
|         this._mouseSprite.set_anchor_point(xHot, yHot); | ||||
|     }, | ||||
|  | ||||
|     _settingsInit: function(zoomRegion) { | ||||
|         this._appSettings = new Gio.Settings({ schema_id: APPLICATIONS_SCHEMA }); | ||||
|         this._settings = new Gio.Settings({ schema_id: MAGNIFIER_SCHEMA }); | ||||
|         this._appSettings = new Gio.Settings({ schema: APPLICATIONS_SCHEMA }); | ||||
|         this._settings = new Gio.Settings({ schema: MAGNIFIER_SCHEMA }); | ||||
|  | ||||
|         if (zoomRegion) { | ||||
|             // Mag factor is accurate to two decimal places. | ||||
| @@ -461,14 +449,6 @@ const Magnifier = new Lang.Class({ | ||||
|             if (aPref) | ||||
|                 zoomRegion.setMouseTrackingMode(aPref); | ||||
|  | ||||
|             aPref = this._settings.get_enum(FOCUS_TRACKING_KEY); | ||||
|             if (aPref) | ||||
|                 zoomRegion.setFocusTrackingMode(aPref); | ||||
|  | ||||
|             aPref = this._settings.get_enum(CARET_TRACKING_KEY); | ||||
|             if (aPref) | ||||
|                 zoomRegion.setCaretTrackingMode(aPref); | ||||
|  | ||||
|             aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY); | ||||
|             if (aPref) | ||||
|                 zoomRegion.setInvertLightness(aPref); | ||||
| @@ -508,10 +488,6 @@ const Magnifier = new Lang.Class({ | ||||
|                                Lang.bind(this, this._updateClampMode)); | ||||
|         this._settings.connect('changed::' + MOUSE_TRACKING_KEY, | ||||
|                                Lang.bind(this, this._updateMouseTrackingMode)); | ||||
|         this._settings.connect('changed::' + FOCUS_TRACKING_KEY, | ||||
|                                Lang.bind(this, this._updateFocusTrackingMode)); | ||||
|         this._settings.connect('changed::' + CARET_TRACKING_KEY, | ||||
|                                Lang.bind(this, this._updateCaretTrackingMode)); | ||||
|  | ||||
|         this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY, | ||||
|                                Lang.bind(this, this._updateInvertLightness)); | ||||
| @@ -561,6 +537,7 @@ const Magnifier = new Lang.Class({ | ||||
|                                Lang.bind(this, function() { | ||||
|             this.setCrosshairsClip(this._settings.get_boolean(CROSS_HAIRS_CLIP_KEY)); | ||||
|         })); | ||||
|  | ||||
|         return this._appSettings.get_boolean(SHOW_KEY); | ||||
|    }, | ||||
|  | ||||
| @@ -608,24 +585,6 @@ const Magnifier = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateFocusTrackingMode: function() { | ||||
|         // Applies only to the first zoom region. | ||||
|         if (this._zoomRegions.length) { | ||||
|             this._zoomRegions[0].setFocusTrackingMode( | ||||
|                 this._settings.get_enum(FOCUS_TRACKING_KEY) | ||||
|             ); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateCaretTrackingMode: function() { | ||||
|         // Applies only to the first zoom region. | ||||
|         if (this._zoomRegions.length) { | ||||
|             this._zoomRegions[0].setCaretTrackingMode( | ||||
|                 this._settings.get_enum(CARET_TRACKING_KEY) | ||||
|             ); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _updateInvertLightness: function() { | ||||
|         // Applies only to the first zoom region. | ||||
|         if (this._zoomRegions.length) { | ||||
| @@ -664,7 +623,7 @@ const Magnifier = new Lang.Class({ | ||||
|             contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY); | ||||
|             this._zoomRegions[0].setContrast(contrast); | ||||
|         } | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(Magnifier.prototype); | ||||
|  | ||||
| @@ -673,11 +632,8 @@ const ZoomRegion = new Lang.Class({ | ||||
|  | ||||
|     _init: function(magnifier, mouseSourceActor) { | ||||
|         this._magnifier = magnifier; | ||||
|         this._focusCaretTracker = new FocusCaretTracker.FocusCaretTracker(); | ||||
|  | ||||
|         this._mouseTrackingMode = GDesktopEnums.MagnifierMouseTrackingMode.NONE; | ||||
|         this._focusTrackingMode = GDesktopEnums.MagnifierFocusTrackingMode.NONE; | ||||
|         this._caretTrackingMode = GDesktopEnums.MagnifierCaretTrackingMode.NONE; | ||||
|         this._clampScrollingAtEdges = false; | ||||
|         this._lensMode = false; | ||||
|         this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN; | ||||
| @@ -703,50 +659,9 @@ const ZoomRegion = new Lang.Class({ | ||||
|         this._xMagFactor = 1; | ||||
|         this._yMagFactor = 1; | ||||
|         this._followingCursor = false; | ||||
|         this._xFocus = 0; | ||||
|         this._yFocus = 0; | ||||
|         this._xCaret = 0; | ||||
|         this._yCaret = 0; | ||||
|  | ||||
|         Main.layoutManager.connect('monitors-changed', | ||||
|                                    Lang.bind(this, this._monitorsChanged)); | ||||
|         this._focusCaretTracker.connect('caret-moved', | ||||
|                                     Lang.bind(this, this._updateCaret)); | ||||
|         this._focusCaretTracker.connect('focus-changed', | ||||
|                                     Lang.bind(this, this._updateFocus)); | ||||
|     }, | ||||
|  | ||||
|     _updateFocus: function(caller, event) { | ||||
|         let component = event.source.get_component_iface(); | ||||
|         if (!component || event.detail1 != 1) | ||||
|             return; | ||||
|         let extents; | ||||
|         try { | ||||
|             extents = component.get_extents(Atspi.CoordType.SCREEN); | ||||
|         } catch(e) { | ||||
|             log('Failed to read extents of focused component: ' + e.message); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         [this._xFocus, this._yFocus] = [extents.x + (extents.width / 2), | ||||
|                                         extents.y + (extents.height / 2)]; | ||||
|         this._centerFromFocusPosition(); | ||||
|     }, | ||||
|  | ||||
|     _updateCaret: function(caller, event) { | ||||
|         let text = event.source.get_text_iface(); | ||||
|         if (!text) | ||||
|             return; | ||||
|         let extents; | ||||
|         try { | ||||
|             extents = text.get_character_extents(text.get_caret_offset(), 0); | ||||
|         } catch(e) { | ||||
|             log('Failed to read extents of text caret: ' + e.message); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         [this._xCaret, this._yCaret] = [extents.x, extents.y]; | ||||
|         this._centerFromCaretPosition(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -754,22 +669,16 @@ const ZoomRegion = new Lang.Class({ | ||||
|      * @activate:   Boolean to show/hide the ZoomRegion. | ||||
|      */ | ||||
|     setActive: function(activate) { | ||||
|         if (activate == this.isActive()) | ||||
|             return; | ||||
|  | ||||
|         if (activate) { | ||||
|         if (activate && !this.isActive()) { | ||||
|             this._createActors(); | ||||
|             if (this._isMouseOverRegion()) | ||||
|                 this._magnifier.hideSystemCursor(); | ||||
|             this._updateMagViewGeometry(); | ||||
|             this._updateCloneGeometry(); | ||||
|             this._updateMousePosition(); | ||||
|         } else { | ||||
|         } else if (!activate && this.isActive()) { | ||||
|             this._destroyActors(); | ||||
|         } | ||||
|  | ||||
|         this._syncCaretTracking(); | ||||
|         this._syncFocusTracking(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -823,44 +732,6 @@ const ZoomRegion = new Lang.Class({ | ||||
|         return this._mouseTrackingMode; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * setFocusTrackingMode | ||||
|      * @mode:     One of the enum FocusTrackingMode values. | ||||
|      */ | ||||
|     setFocusTrackingMode: function(mode) { | ||||
|         this._focusTrackingMode = mode; | ||||
|         this._syncFocusTracking(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * setCaretTrackingMode | ||||
|      * @mode:     One of the enum CaretTrackingMode values. | ||||
|      */ | ||||
|     setCaretTrackingMode: function(mode) { | ||||
|         this._caretTrackingMode = mode; | ||||
|         this._syncCaretTracking(); | ||||
|     }, | ||||
|  | ||||
|     _syncFocusTracking: function() { | ||||
|         let enabled = this._focusTrackingMode != GDesktopEnums.MagnifierFocusTrackingMode.NONE && | ||||
|             this.isActive(); | ||||
|  | ||||
|         if (enabled) | ||||
|             this._focusCaretTracker.registerFocusListener(); | ||||
|         else | ||||
|             this._focusCaretTracker.deregisterFocusListener(); | ||||
|     }, | ||||
|  | ||||
|     _syncCaretTracking: function() { | ||||
|         let enabled = this._caretTrackingMode != GDesktopEnums.MagnifierCaretTrackingMode.NONE && | ||||
|             this.isActive(); | ||||
|  | ||||
|         if (enabled) | ||||
|             this._focusCaretTracker.registerCaretListener(); | ||||
|         else | ||||
|             this._focusCaretTracker.deregisterCaretListener(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * setViewPort | ||||
|      * Sets the position and size of the ZoomRegion on screen. | ||||
| @@ -1152,6 +1023,20 @@ const ZoomRegion = new Lang.Class({ | ||||
|             this._magShaderEffects.setBrightness(this._brightness); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * getBrightness: | ||||
|      * Retrive the current brightness of the Zoom Region. | ||||
|      * @return  Object containing the brightness change for the red, green, | ||||
|      *          and blue channels. | ||||
|      */ | ||||
|     getBrightness: function() { | ||||
|         let brightness = {}; | ||||
|         brightness.r = this._brightness.r; | ||||
|         brightness.g = this._brightness.g; | ||||
|         brightness.b = this._brightness.b; | ||||
|         return brightness; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * setContrast: | ||||
|      * Alter the contrast of the magnified view. | ||||
| @@ -1198,13 +1083,14 @@ const ZoomRegion = new Lang.Class({ | ||||
|  | ||||
|         // Add a background for when the magnified uiGroup is scrolled | ||||
|         // out of view (don't want to see desktop showing through). | ||||
|         this._background = (new Background.SystemBackground()).actor; | ||||
|         this._background = new Clutter.Actor({ background_color: Main.DEFAULT_BACKGROUND_COLOR, | ||||
|                                                width: global.screen_width, | ||||
|                                                height: global.screen_height }); | ||||
|         mainGroup.add_actor(this._background); | ||||
|  | ||||
|         // Clone the group that contains all of UI on the screen.  This is the | ||||
|         // chrome, the windows, etc. | ||||
|         this._uiGroupClone = new Clutter.Clone({ source: Main.uiGroup, | ||||
|                                                  clip_to_allocation: true }); | ||||
|         this._uiGroupClone = new Clutter.Clone({ source: Main.uiGroup }); | ||||
|         mainGroup.add_actor(this._uiGroupClone); | ||||
|  | ||||
|         // Add either the given mouseSourceActor to the ZoomRegion, or a clone of | ||||
| @@ -1357,47 +1243,19 @@ const ZoomRegion = new Lang.Class({ | ||||
|         let yMouse = this._magnifier.yMouse; | ||||
|  | ||||
|         if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) { | ||||
|             return this._centerFromPointProportional(xMouse, yMouse); | ||||
|             return this._centerFromMouseProportional(xMouse, yMouse); | ||||
|         } | ||||
|         else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) { | ||||
|             return this._centerFromPointPush(xMouse, yMouse); | ||||
|             return this._centerFromMousePush(xMouse, yMouse); | ||||
|         } | ||||
|         else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) { | ||||
|             return this._centerFromPointCentered(xMouse, yMouse); | ||||
|             return this._centerFromMouseCentered(xMouse, yMouse); | ||||
|         } | ||||
|  | ||||
|         return null; // Should never be hit | ||||
|     }, | ||||
|  | ||||
|     _centerFromCaretPosition: function() { | ||||
|         let xCaret = this._xCaret; | ||||
|         let yCaret = this._yCaret; | ||||
|  | ||||
|         if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PROPORTIONAL) | ||||
|             [xCaret, yCaret] = this._centerFromPointProportional(xCaret, yCaret); | ||||
|         else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PUSH) | ||||
|             [xCaret, yCaret] = this._centerFromPointPush(xCaret, yCaret); | ||||
|         else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.CENTERED) | ||||
|             [xCaret, yCaret] = this._centerFromPointCentered(xCaret, yCaret); | ||||
|  | ||||
|         this.scrollContentsTo(xCaret, yCaret); | ||||
|     }, | ||||
|  | ||||
|     _centerFromFocusPosition: function() { | ||||
|         let xFocus = this._xFocus; | ||||
|         let yFocus = this._yFocus; | ||||
|  | ||||
|         if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PROPORTIONAL) | ||||
|             [xFocus, yFocus] = this._centerFromPointProportional(xFocus, yFocus); | ||||
|         else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PUSH) | ||||
|             [xFocus, yFocus] = this._centerFromPointPush(xFocus, yFocus); | ||||
|         else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.CENTERED) | ||||
|             [xFocus, yFocus] = this._centerFromPointCentered(xFocus, yFocus); | ||||
|  | ||||
|         this.scrollContentsTo(xFocus, yFocus); | ||||
|     }, | ||||
|  | ||||
|     _centerFromPointPush: function(xPoint, yPoint) { | ||||
|     _centerFromMousePush: function(xMouse, yMouse) { | ||||
|         let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI(); | ||||
|         let [cursorWidth, cursorHeight] = this._mouseSourceActor.get_size(); | ||||
|         let xPos = xRoi + widthRoi / 2; | ||||
| @@ -1405,20 +1263,20 @@ const ZoomRegion = new Lang.Class({ | ||||
|         let xRoiRight = xRoi + widthRoi - cursorWidth; | ||||
|         let yRoiBottom = yRoi + heightRoi - cursorHeight; | ||||
|  | ||||
|         if (xPoint < xRoi) | ||||
|             xPos -= (xRoi - xPoint); | ||||
|         else if (xPoint > xRoiRight) | ||||
|             xPos += (xPoint - xRoiRight); | ||||
|         if (xMouse < xRoi) | ||||
|             xPos -= (xRoi - xMouse); | ||||
|         else if (xMouse > xRoiRight) | ||||
|             xPos += (xMouse - xRoiRight); | ||||
|  | ||||
|         if (yPoint < yRoi) | ||||
|             yPos -= (yRoi - yPoint); | ||||
|         else if (yPoint > yRoiBottom) | ||||
|             yPos += (yPoint - yRoiBottom); | ||||
|         if (yMouse < yRoi) | ||||
|             yPos -= (yRoi - yMouse); | ||||
|         else if (yMouse > yRoiBottom) | ||||
|             yPos += (yMouse - yRoiBottom); | ||||
|  | ||||
|         return [xPos, yPos]; | ||||
|     }, | ||||
|  | ||||
|     _centerFromPointProportional: function(xPoint, yPoint) { | ||||
|     _centerFromMouseProportional: function(xMouse, yMouse) { | ||||
|         let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI(); | ||||
|         let halfScreenWidth = global.screen_width / 2; | ||||
|         let halfScreenHeight = global.screen_height / 2; | ||||
| @@ -1427,16 +1285,16 @@ const ZoomRegion = new Lang.Class({ | ||||
|         let unscaledPadding = Math.min(this._viewPortWidth, this._viewPortHeight) / 5; | ||||
|         let xPadding = unscaledPadding / this._xMagFactor; | ||||
|         let yPadding = unscaledPadding / this._yMagFactor; | ||||
|         let xProportion = (xPoint - halfScreenWidth) / halfScreenWidth;   // -1 ... 1 | ||||
|         let yProportion = (yPoint - halfScreenHeight) / halfScreenHeight; // -1 ... 1 | ||||
|         let xPos = xPoint - xProportion * (widthRoi / 2 - xPadding); | ||||
|         let yPos = yPoint - yProportion * (heightRoi /2 - yPadding); | ||||
|         let xProportion = (xMouse - halfScreenWidth) / halfScreenWidth;   // -1 ... 1 | ||||
|         let yProportion = (yMouse - halfScreenHeight) / halfScreenHeight; // -1 ... 1 | ||||
|         let xPos = xMouse - xProportion * (widthRoi / 2 - xPadding); | ||||
|         let yPos = yMouse - yProportion * (heightRoi /2 - yPadding); | ||||
|  | ||||
|         return [xPos, yPos]; | ||||
|     }, | ||||
|  | ||||
|     _centerFromPointCentered: function(xPoint, yPoint) { | ||||
|         return [xPoint, yPoint]; | ||||
|     _centerFromMouseCentered: function(xMouse, yMouse) { | ||||
|         return [xMouse, yMouse]; | ||||
|     }, | ||||
|  | ||||
|     _screenToViewPort: function(screenX, screenY) { | ||||
| @@ -1653,6 +1511,15 @@ const Crosshairs = new Lang.Class({ | ||||
|         this._vertBottomHair.set_opacity(opacity); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * getOpacity: | ||||
|      * Retriev how opaque the crosshairs are. | ||||
|      * @return: A value between 0 (transparent) and 255 (opaque). | ||||
|      */ | ||||
|     getOpacity: function() { | ||||
|         return this._horizLeftHair.get_opacity(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * setLength: | ||||
|      * Set the length of the vertical and horizontal lines in the crosshairs. | ||||
| @@ -1696,6 +1563,15 @@ const Crosshairs = new Lang.Class({ | ||||
|         } | ||||
|      }, | ||||
|  | ||||
|     /** | ||||
|      * getClip: | ||||
|      * Get the dimensions of the clip rectangle. | ||||
|      * @return:   An array of the form [width, height]. | ||||
|      */ | ||||
|     getClip: function() { | ||||
|         return this._clipSize; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * show: | ||||
|      * Show the crosshairs. | ||||
| @@ -1791,10 +1667,23 @@ const MagShaderEffects = new Lang.Class({ | ||||
|         this._inverse.set_enabled(invertFlag); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * getInvertLightness: | ||||
|      * Report whether the inversion effect is enabled. | ||||
|      * @return:     Boolean. | ||||
|      */ | ||||
|     getInvertLightness: function() { | ||||
|         return this._inverse.get_enabled(); | ||||
|     }, | ||||
|  | ||||
|     setColorSaturation: function(factor) { | ||||
|         this._colorDesaturation.set_factor(1.0 - factor); | ||||
|     }, | ||||
|  | ||||
|     getColorSaturation: function() { | ||||
|         return 1.0 - this._colorDesaturation.get_factor(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * setBrightness: | ||||
|      * Set the brightness of the magnified view. | ||||
| @@ -1819,6 +1708,24 @@ const MagShaderEffects = new Lang.Class({ | ||||
|         ); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * getBrightness: | ||||
|      * Retrieve current brightness of the magnified view. | ||||
|      * @return: Object containing the brightness for the red, green, | ||||
|      *          and blue channels.  Values of 0.0 represent "standard"  | ||||
|      *          brightness (no change), whereas values less or greater than | ||||
|      *          0.0 indicate decreased or incresaed brightness, respectively. | ||||
|      */ | ||||
|     getBrightness: function() { | ||||
|         let result = {}; | ||||
|         let [bRed, bGreen, bBlue] = this._brightnessContrast.get_brightness(); | ||||
|         result.r = bRed; | ||||
|         result.g = bGreen; | ||||
|         result.b = bBlue; | ||||
|  | ||||
|         return result; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Set the contrast of the magnified view. | ||||
|      * @contrast:   Object containing the contrast for the red, green, | ||||
| @@ -1843,4 +1750,21 @@ const MagShaderEffects = new Lang.Class({ | ||||
|              bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != NO_CHANGE | ||||
|         ); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Retrieve current contrast of the magnified view. | ||||
|      * @return: Object containing the contrast for the red, green, | ||||
|      *          and blue channels.  Values of 0.0 represent "standard" | ||||
|      *          contrast (no change), whereas values less or greater than | ||||
|      *          0.0 indicate decreased or incresaed contrast, respectively. | ||||
|      */ | ||||
|     getContrast: function() { | ||||
|         let resutl = {}; | ||||
|         let [cRed, cGreen, cBlue] = this._brightnessContrast.get_contrast(); | ||||
|         result.r = cRed; | ||||
|         result.g = cGreen; | ||||
|         result.b = cBlue; | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -4,94 +4,92 @@ const Gio = imports.gi.Gio; | ||||
| const Lang = imports.lang; | ||||
| const Main = imports.ui.main; | ||||
|  | ||||
| const MAG_SERVICE_NAME = 'org.gnome.Magnifier'; | ||||
| const MAG_SERVICE_PATH = '/org/gnome/Magnifier'; | ||||
| const ZOOM_SERVICE_NAME = 'org.gnome.Magnifier.ZoomRegion'; | ||||
| const ZOOM_SERVICE_PATH = '/org/gnome/Magnifier/ZoomRegion'; | ||||
|  | ||||
| // Subset of gnome-mag's Magnifier dbus interface -- to be expanded.  See: | ||||
| // http://git.gnome.org/browse/gnome-mag/tree/xml/...Magnifier.xml | ||||
| const MagnifierIface = '<node> \ | ||||
| <interface name="org.gnome.Magnifier"> \ | ||||
| <method name="setActive"> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="isActive"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="showCursor" /> \ | ||||
| <method name="hideCursor" /> \ | ||||
| <method name="createZoomRegion"> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
|     <arg type="o" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="addZoomRegion"> \ | ||||
|     <arg type="o" direction="in" /> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="getZoomRegions"> \ | ||||
|     <arg type="ao" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="clearAllZoomRegions" /> \ | ||||
| <method name="fullScreenCapable"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireSize"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireSize"> \ | ||||
|     <arg type="i" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireLength"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireLength"> \ | ||||
|     <arg type="i" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireClip"> \ | ||||
|     <arg type="b" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireClip"> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setCrosswireColor"> \ | ||||
|     <arg type="u" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getCrosswireColor"> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const MagnifierIface = <interface name={MAG_SERVICE_NAME}> | ||||
| <method name="setActive"> | ||||
|     <arg type="b" direction="in" /> | ||||
| </method> | ||||
| <method name="isActive"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="showCursor" /> | ||||
| <method name="hideCursor" /> | ||||
| <method name="createZoomRegion"> | ||||
|     <arg type="d" direction="in" /> | ||||
|     <arg type="d" direction="in" /> | ||||
|     <arg type="ai" direction="in" /> | ||||
|     <arg type="ai" direction="in" /> | ||||
|     <arg type="o" direction="out" /> | ||||
| </method> | ||||
| <method name="addZoomRegion"> | ||||
|     <arg type="o" direction="in" /> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="getZoomRegions"> | ||||
|     <arg type="ao" direction="out" /> | ||||
| </method> | ||||
| <method name="clearAllZoomRegions" /> | ||||
| <method name="fullScreenCapable"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireSize"> | ||||
|     <arg type="i" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireSize"> | ||||
|     <arg type="i" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireLength"> | ||||
|     <arg type="i" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireLength"> | ||||
|     <arg type="i" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireClip"> | ||||
|     <arg type="b" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireClip"> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="setCrosswireColor"> | ||||
|     <arg type="u" direction="in" /> | ||||
| </method> | ||||
| <method name="getCrosswireColor"> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| // Subset of gnome-mag's ZoomRegion dbus interface -- to be expanded.  See: | ||||
| // http://git.gnome.org/browse/gnome-mag/tree/xml/...ZoomRegion.xml | ||||
| const ZoomRegionIface = '<node> \ | ||||
| <interface name="org.gnome.Magnifier.ZoomRegion"> \ | ||||
| <method name="setMagFactor"> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
|     <arg type="d" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getMagFactor"> \ | ||||
|     <arg type="d" direction="out" /> \ | ||||
|     <arg type="d" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="setRoi"> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="getRoi"> \ | ||||
|     <arg type="ai" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="shiftContentsTo"> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="i" direction="in" /> \ | ||||
|     <arg type="b" direction="out" /> \ | ||||
| </method> \ | ||||
| <method name="moveResize"> \ | ||||
|     <arg type="ai" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const ZoomRegionIface = <interface name={ZOOM_SERVICE_NAME}> | ||||
| <method name="setMagFactor"> | ||||
|     <arg type="d" direction="in" /> | ||||
|     <arg type="d" direction="in" /> | ||||
| </method> | ||||
| <method name="getMagFactor"> | ||||
|     <arg type="d" direction="out" /> | ||||
|     <arg type="d" direction="out" /> | ||||
| </method> | ||||
| <method name="setRoi"> | ||||
|     <arg type="ai" direction="in" /> | ||||
| </method> | ||||
| <method name="getRoi"> | ||||
|     <arg type="ai" direction="out" /> | ||||
| </method> | ||||
| <method name="shiftContentsTo"> | ||||
|     <arg type="i" direction="in" /> | ||||
|     <arg type="i" direction="in" /> | ||||
|     <arg type="b" direction="out" /> | ||||
| </method> | ||||
| <method name="moveResize"> | ||||
|     <arg type="ai" direction="in" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| // For making unique ZoomRegion DBus proxy object paths of the form: | ||||
| // '/org/gnome/Magnifier/ZoomRegion/zoomer0', | ||||
|   | ||||
							
								
								
									
										464
									
								
								js/ui/main.js
									
									
									
									
									
								
							
							
						
						
									
										464
									
								
								js/ui/main.js
									
									
									
									
									
								
							| @@ -18,31 +18,40 @@ const ExtensionSystem = imports.ui.extensionSystem; | ||||
| const ExtensionDownloader = imports.ui.extensionDownloader; | ||||
| const Keyboard = imports.ui.keyboard; | ||||
| const MessageTray = imports.ui.messageTray; | ||||
| const ModalDialog = imports.ui.modalDialog; | ||||
| const OsdWindow = imports.ui.osdWindow; | ||||
| const Overview = imports.ui.overview; | ||||
| const Panel = imports.ui.panel; | ||||
| const Params = imports.misc.params; | ||||
| const RunDialog = imports.ui.runDialog; | ||||
| const Layout = imports.ui.layout; | ||||
| const LoginManager = imports.misc.loginManager; | ||||
| const LookingGlass = imports.ui.lookingGlass; | ||||
| const NotificationDaemon = imports.ui.notificationDaemon; | ||||
| const WindowAttentionHandler = imports.ui.windowAttentionHandler; | ||||
| const Screencast = imports.ui.screencast; | ||||
| const ScreenShield = imports.ui.screenShield; | ||||
| const Scripting = imports.ui.scripting; | ||||
| const SessionMode = imports.ui.sessionMode; | ||||
| const ShellDBus = imports.ui.shellDBus; | ||||
| const ShellMountOperation = imports.ui.shellMountOperation; | ||||
| const UnlockDialog = imports.ui.unlockDialog; | ||||
| const WindowManager = imports.ui.windowManager; | ||||
| const Magnifier = imports.ui.magnifier; | ||||
| const XdndHandler = imports.ui.xdndHandler; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard'; | ||||
| const STICKY_KEYS_ENABLE = 'stickykeys-enable'; | ||||
| const GNOMESHELL_STARTED_MESSAGE_ID = 'f3ea493c22934e26811cd62abe8e203a'; | ||||
| const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides'; | ||||
| const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff); | ||||
|  | ||||
| const KeybindingMode = { | ||||
|     NONE:          0,       // block all keybindings | ||||
|     NORMAL:        1 << 0,  // window mode | ||||
|     OVERVIEW:      1 << 1, | ||||
|     LOCK_SCREEN:   1 << 2, | ||||
|     UNLOCK_SCREEN: 1 << 3, | ||||
|     LOGIN_SCREEN:  1 << 4, | ||||
|     MESSAGE_TRAY:  1 << 5, | ||||
|     SYSTEM_MODAL:  1 << 6, | ||||
|     LOOKING_GLASS: 1 << 7, | ||||
|     ALL:           ~0, | ||||
| }; | ||||
|  | ||||
| let componentManager = null; | ||||
| let panel = null; | ||||
| @@ -55,14 +64,12 @@ let screenShield = null; | ||||
| let notificationDaemon = null; | ||||
| let windowAttentionHandler = null; | ||||
| let ctrlAltTabManager = null; | ||||
| let osdWindowManager = null; | ||||
| let sessionMode = null; | ||||
| let shellDBusService = null; | ||||
| let shellMountOpDBusService = null; | ||||
| let screenSaverDBus = null; | ||||
| let screencastService = null; | ||||
| let modalCount = 0; | ||||
| let keybindingMode = Shell.KeyBindingMode.NONE; | ||||
| let keybindingMode = KeybindingMode.NORMAL; | ||||
| let modalActorFocusStack = []; | ||||
| let uiGroup = null; | ||||
| let magnifier = null; | ||||
| @@ -72,29 +79,24 @@ let layoutManager = null; | ||||
| let _startDate; | ||||
| let _defaultCssStylesheet = null; | ||||
| let _cssStylesheet = null; | ||||
| let _a11ySettings = null; | ||||
| let _overridesSettings = null; | ||||
|  | ||||
| let background = null; | ||||
|  | ||||
| function _sessionUpdated() { | ||||
|     _loadDefaultStylesheet(); | ||||
|  | ||||
|     wm.setCustomKeybindingHandler('panel-main-menu', | ||||
|                                   Shell.KeyBindingMode.NORMAL | | ||||
|                                   Shell.KeyBindingMode.OVERVIEW, | ||||
|                                   KeybindingMode.NORMAL | | ||||
|                                   KeybindingMode.OVERVIEW, | ||||
|                                   sessionMode.hasOverview ? Lang.bind(overview, overview.toggle) : null); | ||||
|     wm.allowKeybinding('overlay-key', Shell.KeyBindingMode.NORMAL | | ||||
|                                       Shell.KeyBindingMode.OVERVIEW); | ||||
|     wm.allowKeybinding('overlay-key', KeybindingMode.NORMAL | | ||||
|                                       KeybindingMode.OVERVIEW); | ||||
|  | ||||
|     wm.setCustomKeybindingHandler('panel-run-dialog', | ||||
|                                   Shell.KeyBindingMode.NORMAL | | ||||
|                                   Shell.KeyBindingMode.OVERVIEW, | ||||
|                                   KeybindingMode.NORMAL | | ||||
|                                   KeybindingMode.OVERVIEW, | ||||
|                                   sessionMode.hasRunDialog ? openRunDialog : null); | ||||
|  | ||||
|     if (!sessionMode.hasRunDialog) { | ||||
|         if (runDialog) | ||||
|             runDialog.close(); | ||||
|         if (lookingGlass) | ||||
|             lookingGlass.close(); | ||||
|     } | ||||
|     if (sessionMode.isGreeter) | ||||
|         screenShield.showDialog(); | ||||
| } | ||||
|  | ||||
| function start() { | ||||
| @@ -108,16 +110,9 @@ function start() { | ||||
|     Gio.DesktopAppInfo.set_desktop_env('GNOME'); | ||||
|  | ||||
|     sessionMode = new SessionMode.SessionMode(); | ||||
|     sessionMode.connect('updated', _sessionUpdated); | ||||
|     _initializeUI(); | ||||
|  | ||||
|     shellDBusService = new ShellDBus.GnomeShell(); | ||||
|     shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler(); | ||||
|  | ||||
|     _sessionUpdated(); | ||||
| } | ||||
|  | ||||
| function _initializeUI() { | ||||
|     // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will | ||||
|     // also initialize ShellAppSystem first.  ShellAppSystem | ||||
|     // needs to load all the .desktop files, and ShellWindowTracker | ||||
| @@ -126,29 +121,52 @@ function _initializeUI() { | ||||
|     // and recalculate application associations, so to avoid | ||||
|     // races for now we initialize it here.  It's better to | ||||
|     // be predictable anyways. | ||||
|     Shell.WindowTracker.get_default(); | ||||
|     let tracker = Shell.WindowTracker.get_default(); | ||||
|     Shell.AppUsage.get_default(); | ||||
|  | ||||
|     _loadDefaultStylesheet(); | ||||
|     tracker.connect('startup-sequence-changed', _queueCheckWorkspaces); | ||||
|  | ||||
|     // The stage is always covered so Clutter doesn't need to clear it; however | ||||
|     // the color is used as the default contents for the Mutter root background | ||||
|     // actor so set it anyways. | ||||
|     global.stage.color = DEFAULT_BACKGROUND_COLOR; | ||||
|     global.stage.no_clear_hint = true; | ||||
|  | ||||
|     _defaultCssStylesheet = global.datadir + '/theme/gnome-shell.css'; | ||||
|     loadTheme(); | ||||
|  | ||||
|     // Set up stage hierarchy to group all UI actors under one container. | ||||
|     uiGroup = new Shell.GenericContainer({ name: 'uiGroup' }); | ||||
|     uiGroup.connect('allocate', | ||||
|                     function (actor, box, flags) { | ||||
|                         let children = uiGroup.get_children(); | ||||
|                         for (let i = 0; i < children.length; i++) | ||||
|                             children[i].allocate_preferred_size(flags); | ||||
|                     }); | ||||
|     uiGroup.connect('get-preferred-width', | ||||
|                     function(actor, forHeight, alloc) { | ||||
|                         let width = global.stage.width; | ||||
|                         [alloc.min_size, alloc.natural_size] = [width, width]; | ||||
|                     }); | ||||
|     uiGroup.connect('get-preferred-height', | ||||
|                     function(actor, forWidth, alloc) { | ||||
|                         let height = global.stage.height; | ||||
|                         [alloc.min_size, alloc.natural_size] = [height, height]; | ||||
|                     }); | ||||
|     global.window_group.reparent(uiGroup); | ||||
|     global.overlay_group.reparent(uiGroup); | ||||
|     global.stage.add_actor(uiGroup); | ||||
|  | ||||
|     // Setup the stage hierarchy early | ||||
|     layoutManager = new Layout.LayoutManager(); | ||||
|  | ||||
|     // Various parts of the codebase still refers to Main.uiGroup | ||||
|     // instead using the layoutManager.  This keeps that code | ||||
|     // working until it's updated. | ||||
|     uiGroup = layoutManager.uiGroup; | ||||
|  | ||||
|     screencastService = new Screencast.ScreencastService(); | ||||
|     xdndHandler = new XdndHandler.XdndHandler(); | ||||
|     ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); | ||||
|     osdWindowManager = new OsdWindow.OsdWindowManager(); | ||||
|     overview = new Overview.Overview(); | ||||
|     wm = new WindowManager.WindowManager(); | ||||
|     magnifier = new Magnifier.Magnifier(); | ||||
|     if (LoginManager.canLock()) | ||||
|     if (UnlockDialog.isSupported()) | ||||
|         screenShield = new ScreenShield.ScreenShield(); | ||||
|  | ||||
|     else | ||||
|         screenShield = new ScreenShield.ScreenShieldFallback(); | ||||
|     panel = new Panel.Panel(); | ||||
|     messageTray = new MessageTray.MessageTray(); | ||||
|     keyboard = new Keyboard.Keyboard(); | ||||
| @@ -157,34 +175,23 @@ function _initializeUI() { | ||||
|     componentManager = new Components.ComponentManager(); | ||||
|  | ||||
|     layoutManager.init(); | ||||
|     keyboard.init(); | ||||
|     overview.init(); | ||||
|  | ||||
|     _a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA }); | ||||
|  | ||||
|     global.display.connect('overlay-key', Lang.bind(overview, function () { | ||||
|         if (!_a11ySettings.get_boolean (STICKY_KEYS_ENABLE)) | ||||
|             overview.toggle(); | ||||
|     })); | ||||
|  | ||||
|     global.display.connect('show-restart-message', function(display, message) { | ||||
|         showRestartMessage(message); | ||||
|         return true; | ||||
|     }); | ||||
|  | ||||
|     global.display.connect('restart', function() { | ||||
|         global.reexec_self(); | ||||
|         return true; | ||||
|     }); | ||||
|     global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT, | ||||
|                                             false, -1, 1); | ||||
|     global.display.connect('overlay-key', Lang.bind(overview, overview.toggle)); | ||||
|     sessionMode.connect('updated', _sessionUpdated); | ||||
|     _sessionUpdated(); | ||||
|  | ||||
|     // Provide the bus object for gnome-session to | ||||
|     // initiate logouts. | ||||
|     EndSessionDialog.init(); | ||||
|  | ||||
|     // We're ready for the session manager to move to the next phase | ||||
|     Meta.register_with_session(); | ||||
|  | ||||
|     _startDate = new Date(); | ||||
|  | ||||
|     log('GNOME Shell started at ' + _startDate); | ||||
|  | ||||
|     let perfModuleName = GLib.getenv("SHELL_PERF_MODULE"); | ||||
|     if (perfModuleName) { | ||||
|         let perfOutput = GLib.getenv("SHELL_PERF_OUTPUT"); | ||||
| @@ -192,46 +199,200 @@ function _initializeUI() { | ||||
|         Scripting.runPerfScript(module, perfOutput); | ||||
|     } | ||||
|  | ||||
|     _overridesSettings = new Gio.Settings({ schema: OVERRIDES_SCHEMA }); | ||||
|     _overridesSettings.connect('changed::dynamic-workspaces', _queueCheckWorkspaces); | ||||
|  | ||||
|     global.screen.connect('notify::n-workspaces', _nWorkspacesChanged); | ||||
|  | ||||
|     global.screen.connect('window-entered-monitor', _windowEnteredMonitor); | ||||
|     global.screen.connect('window-left-monitor', _windowLeftMonitor); | ||||
|     global.screen.connect('restacked', _windowsRestacked); | ||||
|  | ||||
|     _nWorkspacesChanged(); | ||||
|  | ||||
|     ExtensionDownloader.init(); | ||||
|     ExtensionSystem.init(); | ||||
| } | ||||
|  | ||||
|     if (sessionMode.isGreeter && screenShield) { | ||||
|         layoutManager.connect('startup-prepared', function() { | ||||
|             screenShield.showDialog(); | ||||
|         }); | ||||
| let _workspaces = []; | ||||
| let _checkWorkspacesId = 0; | ||||
|  | ||||
| /* | ||||
|  * When the last window closed on a workspace is a dialog or splash | ||||
|  * screen, we assume that it might be an initial window shown before | ||||
|  * the main window of an application, and give the app a grace period | ||||
|  * where it can map another window before we remove the workspace. | ||||
|  */ | ||||
| const LAST_WINDOW_GRACE_TIME = 1000; | ||||
|  | ||||
| function _checkWorkspaces() { | ||||
|     let i; | ||||
|     let emptyWorkspaces = []; | ||||
|  | ||||
|     if (!Meta.prefs_get_dynamic_workspaces()) { | ||||
|         _checkWorkspacesId = 0; | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     layoutManager.connect('startup-complete', function() { | ||||
|         if (keybindingMode == Shell.KeyBindingMode.NONE) { | ||||
|             keybindingMode = Shell.KeyBindingMode.NORMAL; | ||||
|         } | ||||
|         if (screenShield) { | ||||
|             screenShield.lockIfWasLocked(); | ||||
|         } | ||||
|         if (LoginManager.haveSystemd() && | ||||
|             sessionMode.currentMode != 'gdm' && | ||||
|             sessionMode.currentMode != 'initial-setup') { | ||||
|             // Do not import globally to not depend | ||||
|             // on systemd on non-systemd systems. | ||||
|             let GSystem = imports.gi.GSystem; | ||||
|             GSystem.log_structured_print('GNOME Shell started at ' + _startDate, | ||||
|                                          ['MESSAGE_ID=' + GNOMESHELL_STARTED_MESSAGE_ID]); | ||||
|         } else { | ||||
|             log('GNOME Shell started at ' + _startDate); | ||||
|         } | ||||
|     for (i = 0; i < _workspaces.length; i++) { | ||||
|         let lastRemoved = _workspaces[i]._lastRemovedWindow; | ||||
|         if ((lastRemoved && | ||||
|              (lastRemoved.get_window_type() == Meta.WindowType.SPLASHSCREEN || | ||||
|               lastRemoved.get_window_type() == Meta.WindowType.DIALOG || | ||||
|               lastRemoved.get_window_type() == Meta.WindowType.MODAL_DIALOG)) || | ||||
|             _workspaces[i]._keepAliveId) | ||||
|                 emptyWorkspaces[i] = false; | ||||
|         else | ||||
|             emptyWorkspaces[i] = true; | ||||
|     } | ||||
|  | ||||
|     let sequences = Shell.WindowTracker.get_default().get_startup_sequences(); | ||||
|     for (i = 0; i < sequences.length; i++) { | ||||
|         let index = sequences[i].get_workspace(); | ||||
|         if (index >= 0 && index <= global.screen.n_workspaces) | ||||
|             emptyWorkspaces[index] = false; | ||||
|     } | ||||
|  | ||||
|     let windows = global.get_window_actors(); | ||||
|     for (i = 0; i < windows.length; i++) { | ||||
|         let win = windows[i]; | ||||
|  | ||||
|         if (win.get_meta_window().is_on_all_workspaces()) | ||||
|             continue; | ||||
|  | ||||
|         let workspaceIndex = win.get_workspace(); | ||||
|         emptyWorkspaces[workspaceIndex] = false; | ||||
|     } | ||||
|  | ||||
|     // If we don't have an empty workspace at the end, add one | ||||
|     if (!emptyWorkspaces[emptyWorkspaces.length -1]) { | ||||
|         global.screen.append_new_workspace(false, global.get_current_time()); | ||||
|         emptyWorkspaces.push(false); | ||||
|     } | ||||
|  | ||||
|     let activeWorkspaceIndex = global.screen.get_active_workspace_index(); | ||||
|     let removingCurrentWorkspace = (emptyWorkspaces[activeWorkspaceIndex] && | ||||
|                                     activeWorkspaceIndex < emptyWorkspaces.length - 1); | ||||
|     // Don't enter the overview when removing multiple empty workspaces at startup | ||||
|     let showOverview  = (removingCurrentWorkspace && | ||||
|                          !emptyWorkspaces.every(function(x) { return x; })); | ||||
|  | ||||
|     if (removingCurrentWorkspace) { | ||||
|         // "Merge" the empty workspace we are removing with the one at the end | ||||
|         wm.blockAnimations(); | ||||
|     } | ||||
|  | ||||
|     // Delete other empty workspaces; do it from the end to avoid index changes | ||||
|     for (i = emptyWorkspaces.length - 2; i >= 0; i--) { | ||||
|         if (emptyWorkspaces[i]) | ||||
|             global.screen.remove_workspace(_workspaces[i], global.get_current_time()); | ||||
|     } | ||||
|  | ||||
|     if (removingCurrentWorkspace) { | ||||
|         global.screen.get_workspace_by_index(global.screen.n_workspaces - 1).activate(global.get_current_time()); | ||||
|         wm.unblockAnimations(); | ||||
|  | ||||
|         if (!overview.visible && showOverview) | ||||
|             overview.show(); | ||||
|     } | ||||
|  | ||||
|     _checkWorkspacesId = 0; | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| function keepWorkspaceAlive(workspace, duration) { | ||||
|     if (workspace._keepAliveId) | ||||
|         Mainloop.source_remove(workspace._keepAliveId); | ||||
|  | ||||
|     workspace._keepAliveId = Mainloop.timeout_add(duration, function() { | ||||
|         workspace._keepAliveId = 0; | ||||
|         _queueCheckWorkspaces(); | ||||
|         return false; | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function _loadDefaultStylesheet() { | ||||
|     if (!sessionMode.isPrimary) | ||||
|         return; | ||||
| 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; | ||||
|     }); | ||||
| } | ||||
|  | ||||
|     let stylesheet = global.datadir + '/theme/' + sessionMode.stylesheetName; | ||||
|     if (_defaultCssStylesheet == stylesheet) | ||||
|         return; | ||||
| function _windowLeftMonitor(metaScreen, monitorIndex, metaWin) { | ||||
|     // If the window left the primary monitor, that | ||||
|     // might make that workspace empty | ||||
|     if (monitorIndex == layoutManager.primaryIndex) | ||||
|         _queueCheckWorkspaces(); | ||||
| } | ||||
|  | ||||
|     _defaultCssStylesheet = stylesheet; | ||||
|     loadTheme(); | ||||
| 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; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -242,7 +403,8 @@ function _loadDefaultStylesheet() { | ||||
|  * Returns: A file path that contains the theme CSS, | ||||
|  *          null if using the default | ||||
|  */ | ||||
| function getThemeStylesheet() { | ||||
| function getThemeStylesheet() | ||||
| { | ||||
|     return _cssStylesheet; | ||||
| } | ||||
|  | ||||
| @@ -253,7 +415,8 @@ function getThemeStylesheet() { | ||||
|  * | ||||
|  * Set the theme CSS file that the shell will load | ||||
|  */ | ||||
| function setThemeStylesheet(cssStylesheet) { | ||||
| function setThemeStylesheet(cssStylesheet) | ||||
| { | ||||
|     _cssStylesheet = cssStylesheet; | ||||
| } | ||||
|  | ||||
| @@ -266,8 +429,11 @@ function loadTheme() { | ||||
|     let themeContext = St.ThemeContext.get_for_stage (global.stage); | ||||
|     let previousTheme = themeContext.get_theme(); | ||||
|  | ||||
|     let theme = new St.Theme ({ application_stylesheet: _cssStylesheet, | ||||
|                                 default_stylesheet: _defaultCssStylesheet }); | ||||
|     let cssStylesheet = _defaultCssStylesheet; | ||||
|     if (_cssStylesheet != null) | ||||
|         cssStylesheet = _cssStylesheet; | ||||
|  | ||||
|     let theme = new St.Theme ({ application_stylesheet: cssStylesheet }); | ||||
|  | ||||
|     if (previousTheme) { | ||||
|         let customStylesheets = previousTheme.get_custom_stylesheets(); | ||||
| @@ -309,6 +475,17 @@ function notifyError(msg, details) { | ||||
|     notify(msg, details); | ||||
| } | ||||
|  | ||||
| function isWindowActorDisplayedOnWorkspace(win, workspaceIndex) { | ||||
|     return win.get_workspace() == workspaceIndex || | ||||
|         (win.get_meta_window() && win.get_meta_window().is_on_all_workspaces()); | ||||
| } | ||||
|  | ||||
| function getWindowActorsForWorkspace(workspaceIndex) { | ||||
|     return global.get_window_actors().filter(function (win) { | ||||
|         return isWindowActorDisplayedOnWorkspace(win, workspaceIndex); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function _findModal(actor) { | ||||
|     for (let i = 0; i < modalActorFocusStack.length; i++) { | ||||
|         if (modalActorFocusStack[i].actor == actor) | ||||
| @@ -317,6 +494,10 @@ function _findModal(actor) { | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| function isInModalStack(actor) { | ||||
|     return _findModal(actor) != -1; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * pushModal: | ||||
|  * @actor: #ClutterActor which will be given keyboard focus | ||||
| @@ -339,7 +520,7 @@ function _findModal(actor) { | ||||
|  *  - options: Meta.ModalOptions flags to indicate that the pointer is | ||||
|  *             already grabbed | ||||
|  * | ||||
|  *  - keybindingMode: used to set the current Shell.KeyBindingMode to filter | ||||
|  *  - keybindingMode: used to set the current Main.KeybindingMode to filter | ||||
|  *                    global keybindings; the default of NONE will filter | ||||
|  *                    out all keybindings | ||||
|  * | ||||
| @@ -348,7 +529,7 @@ function _findModal(actor) { | ||||
| function pushModal(actor, params) { | ||||
|     params = Params.parse(params, { timestamp: global.get_current_time(), | ||||
|                                     options: 0, | ||||
|                                     keybindingMode: Shell.KeyBindingMode.NONE }); | ||||
|                                     keybindingMode: KeybindingMode.NONE }); | ||||
|  | ||||
|     if (modalCount == 0) { | ||||
|         if (!global.begin_modal(params.timestamp, params.options)) { | ||||
| @@ -358,26 +539,27 @@ function pushModal(actor, params) { | ||||
|         Meta.disable_unredirect_for_screen(global.screen); | ||||
|     } | ||||
|  | ||||
|     global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN); | ||||
|  | ||||
|     modalCount += 1; | ||||
|     let actorDestroyId = actor.connect('destroy', function() { | ||||
|         let index = _findModal(actor); | ||||
|         if (index >= 0) | ||||
|             popModal(actor); | ||||
|     }); | ||||
|  | ||||
|     let prevFocus = global.stage.get_key_focus(); | ||||
|     let prevFocusDestroyId; | ||||
|     if (prevFocus != null) { | ||||
|         prevFocusDestroyId = prevFocus.connect('destroy', function() { | ||||
|     let curFocus = global.stage.get_key_focus(); | ||||
|     let curFocusDestroyId; | ||||
|     if (curFocus != null) { | ||||
|         curFocusDestroyId = curFocus.connect('destroy', function() { | ||||
|             let index = _findModal(actor); | ||||
|             if (index >= 0) | ||||
|                 modalActorFocusStack[index].prevFocus = null; | ||||
|                 modalActorFocusStack[index].actor = null; | ||||
|         }); | ||||
|     } | ||||
|     modalActorFocusStack.push({ actor: actor, | ||||
|                                 focus: curFocus, | ||||
|                                 destroyId: actorDestroyId, | ||||
|                                 prevFocus: prevFocus, | ||||
|                                 prevFocusDestroyId: prevFocusDestroyId, | ||||
|                                 focusDestroyId: curFocusDestroyId, | ||||
|                                 keybindingMode: keybindingMode }); | ||||
|  | ||||
|     keybindingMode = params.keybindingMode; | ||||
| @@ -406,7 +588,8 @@ function popModal(actor, timestamp) { | ||||
|     if (focusIndex < 0) { | ||||
|         global.stage.set_key_focus(null); | ||||
|         global.end_modal(timestamp); | ||||
|         keybindingMode = Shell.KeyBindingMode.NORMAL; | ||||
|         global.set_stage_input_mode(Shell.StageInputMode.NORMAL); | ||||
|         keybindingMode = KeybindingMode.NORMAL; | ||||
|  | ||||
|         throw new Error('incorrect pop'); | ||||
|     } | ||||
| @@ -417,33 +600,18 @@ function popModal(actor, timestamp) { | ||||
|     record.actor.disconnect(record.destroyId); | ||||
|  | ||||
|     if (focusIndex == modalActorFocusStack.length - 1) { | ||||
|         if (record.prevFocus) | ||||
|             record.prevFocus.disconnect(record.prevFocusDestroyId); | ||||
|         if (record.focus) | ||||
|             record.focus.disconnect(record.focusDestroyId); | ||||
|         keybindingMode = record.keybindingMode; | ||||
|         global.stage.set_key_focus(record.prevFocus); | ||||
|         global.stage.set_key_focus(record.focus); | ||||
|     } else { | ||||
|         // If we have: | ||||
|         //     global.stage.set_focus(a); | ||||
|         //     Main.pushModal(b); | ||||
|         //     Main.pushModal(c); | ||||
|         //     Main.pushModal(d); | ||||
|         // | ||||
|         // then we have the stack: | ||||
|         //     [{ prevFocus: a, actor: b }, | ||||
|         //      { prevFocus: b, actor: c }, | ||||
|         //      { prevFocus: c, actor: d }] | ||||
|         // | ||||
|         // When actor c is destroyed/popped, if we only simply remove the | ||||
|         // record, then the focus stack will be [a, c], rather than the correct | ||||
|         // [a, b]. Shift the focus stack up before removing the record to ensure | ||||
|         // that we get the correct result. | ||||
|         let t = modalActorFocusStack[modalActorFocusStack.length - 1]; | ||||
|         if (t.prevFocus) | ||||
|             t.prevFocus.disconnect(t.prevFocusDestroyId); | ||||
|         if (t.focus) | ||||
|             t.focus.disconnect(t.focusDestroyId); | ||||
|         // Remove from the middle, shift the focus chain up | ||||
|         for (let i = modalActorFocusStack.length - 1; i > focusIndex; i--) { | ||||
|             modalActorFocusStack[i].prevFocus = modalActorFocusStack[i - 1].prevFocus; | ||||
|             modalActorFocusStack[i].prevFocusDestroyId = modalActorFocusStack[i - 1].prevFocusDestroyId; | ||||
|             modalActorFocusStack[i].focus = modalActorFocusStack[i - 1].focus; | ||||
|             modalActorFocusStack[i].focusDestroyId = modalActorFocusStack[i - 1].focusDestroyId; | ||||
|             modalActorFocusStack[i].keybindingMode = modalActorFocusStack[i - 1].keybindingMode; | ||||
|         } | ||||
|     } | ||||
| @@ -452,10 +620,10 @@ function popModal(actor, timestamp) { | ||||
|     if (modalCount > 0) | ||||
|         return; | ||||
|  | ||||
|     layoutManager.modalEnded(); | ||||
|     global.end_modal(timestamp); | ||||
|     global.set_stage_input_mode(Shell.StageInputMode.NORMAL); | ||||
|     Meta.enable_unredirect_for_screen(global.screen); | ||||
|     keybindingMode = Shell.KeyBindingMode.NORMAL; | ||||
|     keybindingMode = KeybindingMode.NORMAL; | ||||
| } | ||||
|  | ||||
| function createLookingGlass() { | ||||
| @@ -611,33 +779,7 @@ function queueDeferredWork(workId) { | ||||
|         _deferredTimeoutId = Mainloop.timeout_add_seconds(DEFERRED_TIMEOUT_SECONDS, function () { | ||||
|             _runAllDeferredWork(); | ||||
|             _deferredTimeoutId = 0; | ||||
|             return GLib.SOURCE_REMOVE; | ||||
|             return false; | ||||
|         }); | ||||
|         GLib.Source.set_name_by_id(_deferredTimeoutId, '[gnome-shell] _runAllDeferredWork'); | ||||
|     } | ||||
| } | ||||
|  | ||||
| const RestartMessage = new Lang.Class({ | ||||
|     Name: 'RestartMessage', | ||||
|     Extends: ModalDialog.ModalDialog, | ||||
|  | ||||
|     _init : function(message) { | ||||
|         this.parent({ shellReactive: true, | ||||
|                       styleClass: 'restart-message', | ||||
|                       shouldFadeIn: false, | ||||
|                       destroyOnClose: true }); | ||||
|  | ||||
|         let label = new St.Label({ text: message }); | ||||
|  | ||||
|         this.contentLayout.add(label, { x_fill: false, | ||||
|                                         y_fill: false, | ||||
|                                         x_align: St.Align.MIDDLE, | ||||
|                                         y_align: St.Align.MIDDLE }); | ||||
|         this.buttonLayout.hide(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| function showRestartMessage(message) { | ||||
|     let restartMessage = new RestartMessage(message); | ||||
|     restartMessage.open(); | ||||
| } | ||||
|   | ||||
							
								
								
									
										1839
									
								
								js/ui/messageTray.js
									
									
									
									
									
								
							
							
						
						
									
										1839
									
								
								js/ui/messageTray.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -14,7 +14,6 @@ const Atk = imports.gi.Atk; | ||||
|  | ||||
| const Params = imports.misc.params; | ||||
|  | ||||
| const Animation = imports.ui.animation; | ||||
| const Layout = imports.ui.layout; | ||||
| const Lightbox = imports.ui.lightbox; | ||||
| const Main = imports.ui.main; | ||||
| @@ -23,10 +22,6 @@ const Tweener = imports.ui.tweener; | ||||
| const OPEN_AND_CLOSE_TIME = 0.1; | ||||
| const FADE_OUT_DIALOG_TIME = 1.0; | ||||
|  | ||||
| const WORK_SPINNER_ICON_SIZE = 24; | ||||
| const WORK_SPINNER_ANIMATION_DELAY = 1.0; | ||||
| const WORK_SPINNER_ANIMATION_TIME = 0.3; | ||||
|  | ||||
| const State = { | ||||
|     OPENED: 0, | ||||
|     CLOSED: 1, | ||||
| @@ -41,24 +36,21 @@ const ModalDialog = new Lang.Class({ | ||||
|     _init: function(params) { | ||||
|         params = Params.parse(params, { shellReactive: false, | ||||
|                                         styleClass: null, | ||||
|                                         keybindingMode: Shell.KeyBindingMode.SYSTEM_MODAL, | ||||
|                                         shouldFadeIn: true, | ||||
|                                         shouldFadeOut: true, | ||||
|                                         destroyOnClose: true }); | ||||
|                                         parentActor: Main.uiGroup, | ||||
|                                         keybindingMode: Main.KeybindingMode.SYSTEM_MODAL, | ||||
|                                         shouldFadeIn: true }); | ||||
|  | ||||
|         this.state = State.CLOSED; | ||||
|         this._hasModal = false; | ||||
|         this._keybindingMode = params.keybindingMode; | ||||
|         this._shellReactive = params.shellReactive; | ||||
|         this._shouldFadeIn = params.shouldFadeIn; | ||||
|         this._shouldFadeOut = params.shouldFadeOut; | ||||
|         this._destroyOnClose = params.destroyOnClose; | ||||
|  | ||||
|         this._group = new St.Widget({ visible: false, | ||||
|                                       x: 0, | ||||
|                                       y: 0, | ||||
|                                       accessible_role: Atk.Role.DIALOG }); | ||||
|         Main.layoutManager.modalDialogGroup.add_actor(this._group); | ||||
|         params.parentActor.add_actor(this._group); | ||||
|  | ||||
|         let constraint = new Clutter.BindConstraint({ source: global.stage, | ||||
|                                                       coordinate: Clutter.BindCoordinate.ALL }); | ||||
| @@ -66,60 +58,55 @@ const ModalDialog = new Lang.Class({ | ||||
|  | ||||
|         this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy)); | ||||
|  | ||||
|         this._pressedKey = null; | ||||
|         this._buttonKeys = {}; | ||||
|         this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); | ||||
|         this._group.connect('key-release-event', Lang.bind(this, this._onKeyReleaseEvent)); | ||||
|  | ||||
|         this.backgroundStack = new St.Widget({ layout_manager: new Clutter.BinLayout() }); | ||||
|         this._backgroundBin = new St.Bin({ child: this.backgroundStack, | ||||
|                                            x_fill: true, y_fill: true }); | ||||
|         this._backgroundBin = new St.Bin(); | ||||
|         this._monitorConstraint = new Layout.MonitorConstraint(); | ||||
|         this._backgroundBin.add_constraint(this._monitorConstraint); | ||||
|         this._group.add_actor(this._backgroundBin); | ||||
|  | ||||
|         this.dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog', | ||||
|                                                vertical:    true }); | ||||
|         // modal dialogs are fixed width and grow vertically; set the request | ||||
|         // mode accordingly so wrapped labels are handled correctly during | ||||
|         // size requests. | ||||
|         this.dialogLayout.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH; | ||||
|  | ||||
|         if (params.styleClass != null) | ||||
|         if (params.styleClass != null) { | ||||
|             this.dialogLayout.add_style_class_name(params.styleClass); | ||||
|         } | ||||
|  | ||||
|         if (!this._shellReactive) { | ||||
|             this._lightbox = new Lightbox.Lightbox(this._group, | ||||
|                                                    { inhibitEvents: true, | ||||
|                                                      radialEffect: true }); | ||||
|                                                    { inhibitEvents: true }); | ||||
|             this._lightbox.highlight(this._backgroundBin); | ||||
|  | ||||
|             this._eventBlocker = new Clutter.Actor({ reactive: true }); | ||||
|             this.backgroundStack.add_actor(this._eventBlocker); | ||||
|             let stack = new Shell.Stack(); | ||||
|             this._backgroundBin.child = stack; | ||||
|  | ||||
|             this._eventBlocker = new Clutter.Group({ reactive: true }); | ||||
|             stack.add_actor(this._eventBlocker); | ||||
|             stack.add_actor(this.dialogLayout); | ||||
|         } else { | ||||
|             this._backgroundBin.child = this.dialogLayout; | ||||
|         } | ||||
|         this.backgroundStack.add_actor(this.dialogLayout); | ||||
|  | ||||
|  | ||||
|         this.contentLayout = new St.BoxLayout({ vertical: true }); | ||||
|         this.dialogLayout.add(this.contentLayout, | ||||
|                               { expand:  true, | ||||
|                                 x_fill:  true, | ||||
|                               { x_fill:  true, | ||||
|                                 y_fill:  true, | ||||
|                                 x_align: St.Align.MIDDLE, | ||||
|                                 y_align: St.Align.START }); | ||||
|  | ||||
|         this.buttonLayout = new St.BoxLayout({ style_class: 'modal-dialog-button-box', | ||||
|                                                vertical: false }); | ||||
|                                                 visible:     false, | ||||
|                                                 vertical:    false }); | ||||
|         this.dialogLayout.add(this.buttonLayout, | ||||
|                               { x_align: St.Align.MIDDLE, | ||||
|                               { expand:  true, | ||||
|                                 x_align: St.Align.MIDDLE, | ||||
|                                 y_align: St.Align.END }); | ||||
|  | ||||
|         global.focus_manager.add_group(this.dialogLayout); | ||||
|         this._initialKeyFocus = this.dialogLayout; | ||||
|         this._initialKeyFocusDestroyId = 0; | ||||
|         this._savedKeyFocus = null; | ||||
|  | ||||
|         this._workSpinner = null; | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
| @@ -133,6 +120,7 @@ const ModalDialog = new Lang.Class({ | ||||
|  | ||||
|     setButtons: function(buttons) { | ||||
|         this.clearButtons(); | ||||
|         this.buttonLayout.visible = (buttons.length > 0); | ||||
|  | ||||
|         for (let i = 0; i < buttons.length; i++) { | ||||
|             let buttonInfo = buttons[i]; | ||||
| @@ -171,7 +159,6 @@ const ModalDialog = new Lang.Class({ | ||||
|             keys = []; | ||||
|  | ||||
|         let button = new St.Button({ style_class: 'modal-dialog-button', | ||||
|                                      button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, | ||||
|                                      reactive:    true, | ||||
|                                      can_focus:   true, | ||||
|                                      label:       label }); | ||||
| @@ -193,68 +180,22 @@ const ModalDialog = new Lang.Class({ | ||||
|         return button; | ||||
|     }, | ||||
|  | ||||
|     placeSpinner: function(layoutInfo) { | ||||
|         let spinnerIcon = global.datadir + '/theme/process-working.svg'; | ||||
|         this._workSpinner = new Animation.AnimatedIcon(spinnerIcon, WORK_SPINNER_ICON_SIZE); | ||||
|         this._workSpinner.actor.opacity = 0; | ||||
|         this._workSpinner.actor.show(); | ||||
|  | ||||
|         this.buttonLayout.add(this._workSpinner.actor, layoutInfo); | ||||
|     }, | ||||
|  | ||||
|     setWorking: function(working) { | ||||
|         if (!this._workSpinner) | ||||
|             return; | ||||
|  | ||||
|         Tweener.removeTweens(this._workSpinner.actor); | ||||
|         if (working) { | ||||
|             this._workSpinner.play(); | ||||
|             Tweener.addTween(this._workSpinner.actor, | ||||
|                              { opacity: 255, | ||||
|                                delay: WORK_SPINNER_ANIMATION_DELAY, | ||||
|                                time: WORK_SPINNER_ANIMATION_TIME, | ||||
|                                transition: 'linear' | ||||
|                              }); | ||||
|         } else { | ||||
|             Tweener.addTween(this._workSpinner.actor, | ||||
|                              { opacity: 0, | ||||
|                                time: WORK_SPINNER_ANIMATION_TIME, | ||||
|                                transition: 'linear', | ||||
|                                onCompleteScope: this, | ||||
|                                onComplete: function() { | ||||
|                                    if (this._workSpinner) | ||||
|                                        this._workSpinner.stop(); | ||||
|                                } | ||||
|                              }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onKeyPressEvent: function(object, event) { | ||||
|         this._pressedKey = event.get_key_symbol(); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     _onKeyReleaseEvent: function(object, event) { | ||||
|         let pressedKey = this._pressedKey; | ||||
|         this._pressedKey = null; | ||||
|  | ||||
|         let symbol = event.get_key_symbol(); | ||||
|         if (symbol != pressedKey) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|  | ||||
|         let buttonInfo = this._buttonKeys[symbol]; | ||||
|  | ||||
|         if (!buttonInfo) | ||||
|             return Clutter.EVENT_PROPAGATE; | ||||
|             return false; | ||||
|  | ||||
|         let button = buttonInfo['button']; | ||||
|         let action = buttonInfo['action']; | ||||
|  | ||||
|         if (action && button.reactive) { | ||||
|             action(); | ||||
|             return Clutter.EVENT_STOP; | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _onGroupDestroy: function() { | ||||
| @@ -309,15 +250,6 @@ const ModalDialog = new Lang.Class({ | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _closeComplete: function() { | ||||
|         this.state = State.CLOSED; | ||||
|         this._group.hide(); | ||||
|         this.emit('closed'); | ||||
|  | ||||
|         if (this._destroyOnClose) | ||||
|             this.destroy(); | ||||
|     }, | ||||
|  | ||||
|     close: function(timestamp) { | ||||
|         if (this.state == State.CLOSED || this.state == State.CLOSING) | ||||
|             return; | ||||
| @@ -326,16 +258,16 @@ const ModalDialog = new Lang.Class({ | ||||
|         this.popModal(timestamp); | ||||
|         this._savedKeyFocus = null; | ||||
|  | ||||
|         if (this._shouldFadeOut) | ||||
|             Tweener.addTween(this._group, | ||||
|                              { opacity: 0, | ||||
|                                time: OPEN_AND_CLOSE_TIME, | ||||
|                                transition: 'easeOutQuad', | ||||
|                                onComplete: Lang.bind(this, | ||||
|                                                      this._closeComplete) | ||||
|                              }) | ||||
|         else | ||||
|             this._closeComplete(); | ||||
|         Tweener.addTween(this._group, | ||||
|                          { opacity: 0, | ||||
|                            time: OPEN_AND_CLOSE_TIME, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            onComplete: Lang.bind(this, | ||||
|                                function() { | ||||
|                                    this.state = State.CLOSED; | ||||
|                                    this._group.hide(); | ||||
|                                }) | ||||
|                          }); | ||||
|     }, | ||||
|  | ||||
|     // Drop modal status without closing the dialog; this makes the | ||||
| @@ -369,9 +301,8 @@ const ModalDialog = new Lang.Class({ | ||||
|         if (this._savedKeyFocus) { | ||||
|             this._savedKeyFocus.grab_key_focus(); | ||||
|             this._savedKeyFocus = null; | ||||
|         } else { | ||||
|         } else | ||||
|             this._initialKeyFocus.grab_key_focus(); | ||||
|         } | ||||
|  | ||||
|         if (!this._shellReactive) | ||||
|             this._eventBlocker.lower_bottom(); | ||||
|   | ||||
| @@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter; | ||||
| const GdkPixbuf = imports.gi.GdkPixbuf; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Lang = imports.lang; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Mainloop = imports.mainloop; | ||||
| @@ -16,56 +15,54 @@ const MessageTray = imports.ui.messageTray; | ||||
| const Params = imports.misc.params; | ||||
| const Util = imports.misc.util; | ||||
|  | ||||
| let nextNotificationId = 1; | ||||
|  | ||||
| // Should really be defined in Gio.js | ||||
| const BusIface = '<node> \ | ||||
| <interface name="org.freedesktop.DBus"> \ | ||||
| <method name="GetConnectionUnixProcessID"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="u" direction="out" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const BusIface = <interface name="org.freedesktop.DBus"> | ||||
| <method name="GetConnectionUnixProcessID"> | ||||
|     <arg type="s" direction="in" /> | ||||
|     <arg type="u" direction="out" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| var BusProxy = Gio.DBusProxy.makeProxyWrapper(BusIface); | ||||
| function Bus() { | ||||
|     return new BusProxy(Gio.DBus.session, 'org.freedesktop.DBus', '/org/freedesktop/DBus'); | ||||
| } | ||||
|  | ||||
| const FdoNotificationsIface = '<node> \ | ||||
| <interface name="org.freedesktop.Notifications"> \ | ||||
| <method name="Notify"> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="u" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="s" direction="in"/> \ | ||||
|     <arg type="as" direction="in"/> \ | ||||
|     <arg type="a{sv}" direction="in"/> \ | ||||
|     <arg type="i" direction="in"/> \ | ||||
|     <arg type="u" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="CloseNotification"> \ | ||||
|     <arg type="u" direction="in"/> \ | ||||
| </method> \ | ||||
| <method name="GetCapabilities"> \ | ||||
|     <arg type="as" direction="out"/> \ | ||||
| </method> \ | ||||
| <method name="GetServerInformation"> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
|     <arg type="s" direction="out"/> \ | ||||
| </method> \ | ||||
| <signal name="NotificationClosed"> \ | ||||
|     <arg type="u"/> \ | ||||
|     <arg type="u"/> \ | ||||
| </signal> \ | ||||
| <signal name="ActionInvoked"> \ | ||||
|     <arg type="u"/> \ | ||||
|     <arg type="s"/> \ | ||||
| </signal> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const NotificationDaemonIface = <interface name="org.freedesktop.Notifications"> | ||||
| <method name="Notify"> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="u" direction="in"/> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="s" direction="in"/> | ||||
|     <arg type="as" direction="in"/> | ||||
|     <arg type="a{sv}" direction="in"/> | ||||
|     <arg type="i" direction="in"/> | ||||
|     <arg type="u" direction="out"/> | ||||
| </method> | ||||
| <method name="CloseNotification"> | ||||
|     <arg type="u" direction="in"/> | ||||
| </method> | ||||
| <method name="GetCapabilities"> | ||||
|     <arg type="as" direction="out"/> | ||||
| </method> | ||||
| <method name="GetServerInformation"> | ||||
|     <arg type="s" direction="out"/> | ||||
|     <arg type="s" direction="out"/> | ||||
|     <arg type="s" direction="out"/> | ||||
|     <arg type="s" direction="out"/> | ||||
| </method> | ||||
| <signal name="NotificationClosed"> | ||||
|     <arg type="u"/> | ||||
|     <arg type="u"/> | ||||
| </signal> | ||||
| <signal name="ActionInvoked"> | ||||
|     <arg type="u"/> | ||||
|     <arg type="s"/> | ||||
| </signal> | ||||
| </interface>; | ||||
|  | ||||
| const NotificationClosedReason = { | ||||
|     EXPIRED: 1, | ||||
| @@ -106,11 +103,11 @@ const STANDARD_TRAY_ICON_IMPLEMENTATIONS = { | ||||
|     'ibus-ui-gtk': 'keyboard' | ||||
| }; | ||||
|  | ||||
| const FdoNotificationDaemon = new Lang.Class({ | ||||
|     Name: 'FdoNotificationDaemon', | ||||
| const NotificationDaemon = new Lang.Class({ | ||||
|     Name: 'NotificationDaemon', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(FdoNotificationsIface, this); | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(NotificationDaemonIface, this); | ||||
|         this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/Notifications'); | ||||
|  | ||||
|         this._sources = []; | ||||
| @@ -118,53 +115,54 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         this._notifications = {}; | ||||
|         this._busProxy = new Bus(); | ||||
|  | ||||
|         this._nextNotificationId = 1; | ||||
|  | ||||
|         Shell.WindowTracker.get_default().connect('notify::focus-app', Lang.bind(this, this._onFocusAppChanged)); | ||||
|         Main.overview.connect('hidden', Lang.bind(this, this._onFocusAppChanged)); | ||||
|  | ||||
|         this._trayManager = new Shell.TrayManager(); | ||||
|         this._trayIconAddedId = this._trayManager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded)); | ||||
|         this._trayIconRemovedId = this._trayManager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); | ||||
|         this._trayManager.manage_screen(global.screen, Main.messageTray.actor); | ||||
|  | ||||
|         Shell.WindowTracker.get_default().connect('notify::focus-app', | ||||
|             Lang.bind(this, this._onFocusAppChanged)); | ||||
|         Main.overview.connect('hidden', | ||||
|             Lang.bind(this, this._onFocusAppChanged)); | ||||
|  | ||||
|         this._trayManager.manage_stage(global.stage, Main.messageTray.actor); | ||||
|     }, | ||||
|  | ||||
|     _imageForNotificationData: function(hints) { | ||||
|         if (hints['image-data']) { | ||||
|     _iconForNotificationData: function(icon, hints) { | ||||
|         // If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon | ||||
|         // and don't show a large image. There are currently many applications that use | ||||
|         // notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets | ||||
|         // the 'image-data' hint. These applications don't typically pass in 'app_icon' | ||||
|         // argument to Notify() and actually expect the pixbuf to be shown as an icon. | ||||
|         // So the logic here does the right thing for this case. If both an icon and either | ||||
|         // one of 'image-data' or 'image-path' are specified, we show both an icon and | ||||
|         // a large image. | ||||
|         if (icon) { | ||||
|             if (icon.substr(0, 7) == 'file://') | ||||
|                 return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) }); | ||||
|             else if (icon[0] == '/') { | ||||
|                 return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) }); | ||||
|             } else | ||||
|                 return new Gio.ThemedIcon({ name: icon }); | ||||
|         } else if (hints['image-data']) { | ||||
|             let [width, height, rowStride, hasAlpha, | ||||
|                  bitsPerSample, nChannels, data] = hints['image-data']; | ||||
|             return Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha, | ||||
|                                                       bitsPerSample, width, height, rowStride); | ||||
|         } else if (hints['image-path']) { | ||||
|             return new Gio.FileIcon({ file: Gio.File.new_for_path(hints['image-path']) }); | ||||
|         } else { | ||||
|             let stockIcon; | ||||
|             switch (hints.urgency) { | ||||
|                 case Urgency.LOW: | ||||
|                 case Urgency.NORMAL: | ||||
|                     stockIcon = 'gtk-dialog-info'; | ||||
|                     break; | ||||
|                 case Urgency.CRITICAL: | ||||
|                     stockIcon = 'gtk-dialog-error'; | ||||
|                     break; | ||||
|             } | ||||
|             return new Gio.ThemedIcon({ name: stockIcon }); | ||||
|         } | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     _fallbackIconForNotificationData: function(hints) { | ||||
|         let stockIcon; | ||||
|         switch (hints.urgency) { | ||||
|             case Urgency.LOW: | ||||
|             case Urgency.NORMAL: | ||||
|                 stockIcon = 'gtk-dialog-info'; | ||||
|                 break; | ||||
|             case Urgency.CRITICAL: | ||||
|                 stockIcon = 'gtk-dialog-error'; | ||||
|                 break; | ||||
|         } | ||||
|         return new Gio.ThemedIcon({ name: stockIcon }); | ||||
|     }, | ||||
|  | ||||
|     _iconForNotificationData: function(icon) { | ||||
|         if (icon) { | ||||
|             if (icon.substr(0, 7) == 'file://') | ||||
|                 return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) }); | ||||
|             else if (icon[0] == '/') | ||||
|                 return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) }); | ||||
|             else | ||||
|                 return new Gio.ThemedIcon({ name: icon }); | ||||
|         } | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     _lookupSource: function(title, pid, trayIcon) { | ||||
| @@ -178,10 +176,12 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     // Returns the source associated with ndata.notification if it is set. | ||||
|     // If the existing or requested source is associated with a tray icon | ||||
|     // and passed in pid matches a pid of an existing source, the title | ||||
|     // match is ignored to enable representing a tray icon and notifications | ||||
|     // from the same application with a single source. | ||||
|     // Otherwise, returns the source associated with the title and pid if | ||||
|     // such source is stored in this._sources and the notification is not | ||||
|     // transient. If the existing or requested source is associated with | ||||
|     // a tray icon and passed in pid matches a pid of an existing source, | ||||
|     // the title match is ignored to enable representing a tray icon and | ||||
|     // notifications from the same application with a single source. | ||||
|     // | ||||
|     // If no existing source is found, a new source is created as long as | ||||
|     // pid is provided. | ||||
| @@ -199,20 +199,32 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         if (ndata && ndata.notification) | ||||
|             return ndata.notification.source; | ||||
|  | ||||
|         let source = this._lookupSource(title, pid, trayIcon); | ||||
|         if (source) { | ||||
|             source.setTitle(title); | ||||
|             return source; | ||||
|         let isForTransientNotification = (ndata && ndata.hints['transient'] == true); | ||||
|  | ||||
|         // We don't want to override a persistent notification | ||||
|         // with a transient one from the same sender, so we | ||||
|         // always create a new source object for new transient notifications | ||||
|         // and never add it to this._sources . | ||||
|         if (!isForTransientNotification) { | ||||
|             let source = this._lookupSource(title, pid, trayIcon); | ||||
|             if (source) { | ||||
|                 source.setTitle(title); | ||||
|                 return source; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let source = new FdoNotificationDaemonSource(title, pid, sender, trayIcon, ndata ? ndata.hints['desktop-entry'] : null); | ||||
|         let source = new Source(title, pid, sender, trayIcon); | ||||
|         source.setTransient(isForTransientNotification); | ||||
|  | ||||
|         this._sources.push(source); | ||||
|         source.connect('destroy', Lang.bind(this, function() { | ||||
|             let index = this._sources.indexOf(source); | ||||
|             if (index >= 0) | ||||
|                 this._sources.splice(index, 1); | ||||
|         })); | ||||
|         if (!isForTransientNotification) { | ||||
|             this._sources.push(source); | ||||
|             source.connect('destroy', Lang.bind(this, | ||||
|                 function() { | ||||
|                     let index = this._sources.indexOf(source); | ||||
|                     if (index >= 0) | ||||
|                         this._sources.splice(index, 1); | ||||
|                 })); | ||||
|         } | ||||
|  | ||||
|         Main.messageTray.add(source); | ||||
|         return source; | ||||
| @@ -240,13 +252,12 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|               hints['category'] == 'presence.offline')) { | ||||
|             // Ignore replacesId since we already sent back a | ||||
|             // NotificationClosed for that id. | ||||
|             id = this._nextNotificationId++; | ||||
|             let idle_id = Mainloop.idle_add(Lang.bind(this, | ||||
|                                             function () { | ||||
|                                                 this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED); | ||||
|                                                 return GLib.SOURCE_REMOVE; | ||||
|                                             })); | ||||
|             GLib.Source.set_name_by_id(idle_id, '[gnome-shell] this._emitNotificationClosed'); | ||||
|             id = nextNotificationId++; | ||||
|             Mainloop.idle_add(Lang.bind(this, | ||||
|                                         function () { | ||||
|                                             this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED); | ||||
|                                             return false; | ||||
|                                         })); | ||||
|             return invocation.return_value(GLib.Variant.new('(u)', [id])); | ||||
|         } | ||||
|  | ||||
| @@ -265,13 +276,12 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         if (!hints['image-path'] && hints['image_path']) | ||||
|             hints['image-path'] = hints['image_path']; // version 1.1 of the spec | ||||
|  | ||||
|         if (!hints['image-data']) { | ||||
|         if (!hints['image-data']) | ||||
|             if (hints['image_data']) | ||||
|                 hints['image-data'] = hints['image_data']; // version 1.1 of the spec | ||||
|             else if (hints['icon_data'] && !hints['image-path']) | ||||
|                 // early versions of the spec; 'icon_data' should only be used if 'image-path' is not available | ||||
|                 hints['image-data'] = hints['icon_data']; | ||||
|         } | ||||
|  | ||||
|         let ndata = { appName: appName, | ||||
|                       icon: icon, | ||||
| @@ -285,7 +295,7 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|             ndata.notification = this._notifications[replacesId].notification; | ||||
|         } else { | ||||
|             replacesId = 0; | ||||
|             ndata.id = id = this._nextNotificationId++; | ||||
|             ndata.id = id = nextNotificationId++; | ||||
|         } | ||||
|         this._notifications[id] = ndata; | ||||
|  | ||||
| @@ -320,36 +330,37 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|             let [pid] = result; | ||||
|             source = this._getSource(appName, pid, ndata, sender, null); | ||||
|  | ||||
|             this._senderToPid[sender] = pid; | ||||
|             source.connect('destroy', Lang.bind(this, function() { | ||||
|                 delete this._senderToPid[sender]; | ||||
|             })); | ||||
|             // We only store sender-pid entries for persistent sources. | ||||
|             // Removing the entries once the source is destroyed | ||||
|             // would result in the entries associated with transient | ||||
|             // sources removed once the notification is shown anyway. | ||||
|             // However, keeping these pairs would mean that we would | ||||
|             // possibly remove an entry associated with a persistent | ||||
|             // source when a transient source for the same sender is | ||||
|             // distroyed. | ||||
|             if (!source.isTransient) { | ||||
|                 this._senderToPid[sender] = pid; | ||||
|                 source.connect('destroy', Lang.bind(this, function() { | ||||
|                     delete this._senderToPid[sender]; | ||||
|                 })); | ||||
|             } | ||||
|             this._notifyForSource(source, ndata); | ||||
|         })); | ||||
|  | ||||
|         return invocation.return_value(GLib.Variant.new('(u)', [id])); | ||||
|     }, | ||||
|  | ||||
|     _makeButton: function(id, label, useActionIcons) { | ||||
|         let button = new St.Button({ can_focus: true }); | ||||
|         let iconName = id.endsWith('-symbolic') ? id : id + '-symbolic'; | ||||
|         if (useActionIcons && Gtk.IconTheme.get_default().has_icon(iconName)) { | ||||
|             button.add_style_class_name('notification-icon-button'); | ||||
|             button.child = new St.Icon({ icon_name: iconName }); | ||||
|         } else { | ||||
|             button.add_style_class_name('notification-button'); | ||||
|             button.label = label; | ||||
|         } | ||||
|         return button; | ||||
|     }, | ||||
|  | ||||
|     _notifyForSource: function(source, ndata) { | ||||
|         let [id, icon, summary, body, actions, hints, notification] = | ||||
|             [ndata.id, ndata.icon, ndata.summary, ndata.body, | ||||
|              ndata.actions, ndata.hints, ndata.notification]; | ||||
|  | ||||
|         let gicon = this._iconForNotificationData(icon, hints); | ||||
|  | ||||
|         if (notification == null) { | ||||
|             notification = new MessageTray.Notification(source); | ||||
|             notification = new MessageTray.Notification(source, summary, body, | ||||
|                                                         { gicon: gicon, | ||||
|                                                           bannerMarkup: true }); | ||||
|             ndata.notification = notification; | ||||
|             notification.connect('destroy', Lang.bind(this, | ||||
|                 function(n, reason) { | ||||
| @@ -368,66 +379,46 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|                     } | ||||
|                     this._emitNotificationClosed(ndata.id, notificationClosedReason); | ||||
|                 })); | ||||
|             notification.connect('action-invoked', Lang.bind(this, | ||||
|                 function(n, actionId) { | ||||
|                     this._emitActionInvoked(ndata.id, actionId); | ||||
|                 })); | ||||
|         } else { | ||||
|             notification.update(summary, body, { gicon: gicon, | ||||
|                                                  bannerMarkup: true, | ||||
|                                                  clear: true }); | ||||
|         } | ||||
|  | ||||
|         // Mark music notifications so they can be shown in the screen shield | ||||
|         notification.isMusic = (ndata.hints['category'] == 'x-gnome.music'); | ||||
|  | ||||
|         let gicon = this._iconForNotificationData(icon, hints); | ||||
|         let gimage = this._imageForNotificationData(hints); | ||||
|  | ||||
|         let image = null; | ||||
|  | ||||
|         // If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon | ||||
|         // and don't show a large image. There are currently many applications that use | ||||
|         // notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets | ||||
|         // the 'image-data' hint. These applications don't typically pass in 'app_icon' | ||||
|         // argument to Notify() and actually expect the pixbuf to be shown as an icon. | ||||
|         // So the logic here does the right thing for this case. If both an icon and either | ||||
|         // one of 'image-data' or 'image-path' are specified, we show both an icon and | ||||
|         // a large image. | ||||
|         if (gicon && gimage) | ||||
|             image = new St.Icon({ gicon: gimage, | ||||
|                                   icon_size: notification.IMAGE_SIZE }); | ||||
|         else if (!gicon && gimage) | ||||
|             gicon = gimage; | ||||
|         else if (!gicon) | ||||
|             gicon = this._fallbackIconForNotificationData(hints); | ||||
|  | ||||
|         notification.update(summary, body, { gicon: gicon, | ||||
|                                              bannerMarkup: true, | ||||
|                                              clear: true, | ||||
|                                              soundFile: hints['sound-file'], | ||||
|                                              soundName: hints['sound-name'] }); | ||||
|         notification.setImage(image); | ||||
|  | ||||
|         let hasDefaultAction = false; | ||||
|         // We only display a large image if an icon is also specified. | ||||
|         if (icon && (hints['image-data'] || hints['image-path'])) { | ||||
|             let image = null; | ||||
|             if (hints['image-data']) { | ||||
|                 let [width, height, rowStride, hasAlpha, | ||||
|                  bitsPerSample, nChannels, data] = hints['image-data']; | ||||
|                 image = St.TextureCache.get_default().load_from_raw(data, hasAlpha, | ||||
|                                                                     width, height, rowStride, notification.IMAGE_SIZE); | ||||
|             } else if (hints['image-path']) { | ||||
|                 image = St.TextureCache.get_default().load_uri_async(GLib.filename_to_uri(hints['image-path'], null), | ||||
|                                                                      notification.IMAGE_SIZE, | ||||
|                                                                      notification.IMAGE_SIZE); | ||||
|             } | ||||
|             notification.setImage(image); | ||||
|         } else { | ||||
|             notification.unsetImage(); | ||||
|         } | ||||
|  | ||||
|         if (actions.length) { | ||||
|             let useActionIcons = (hints['action-icons'] == true); | ||||
|  | ||||
|             notification.setUseActionIcons(hints['action-icons'] == true); | ||||
|             for (let i = 0; i < actions.length - 1; i += 2) { | ||||
|                 let [actionId, label] = [actions[i], actions[i+1]]; | ||||
|                 if (actionId == 'default') { | ||||
|                     hasDefaultAction = true; | ||||
|                 } else { | ||||
|                     notification.addButton(this._makeButton(actionId, label, useActionIcons), Lang.bind(this, function() { | ||||
|                         this._emitActionInvoked(ndata.id, actionId); | ||||
|                     })); | ||||
|                 } | ||||
|                 if (actions[i] == 'default') | ||||
|                     notification.connect('clicked', Lang.bind(this, | ||||
|                         function() { | ||||
|                             this._emitActionInvoked(ndata.id, "default"); | ||||
|                         })); | ||||
|                 else | ||||
|                     notification.addButton(actions[i], actions[i + 1]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (hasDefaultAction) { | ||||
|             notification.connect('clicked', Lang.bind(this, function() { | ||||
|                 this._emitActionInvoked(ndata.id, 'default'); | ||||
|             })); | ||||
|         } else { | ||||
|             notification.connect('clicked', Lang.bind(this, function() { | ||||
|                 source.open(); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         switch (hints.urgency) { | ||||
|             case Urgency.LOW: | ||||
|                 notification.setUrgency(MessageTray.Urgency.LOW); | ||||
| @@ -444,7 +435,7 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         // of the 'transient' hint with hints['transient'] rather than hints.transient | ||||
|         notification.setTransient(hints['transient'] == true); | ||||
|  | ||||
|         let sourceGIcon = source.useNotificationIcon ? gicon : null; | ||||
|         let sourceGIcon = source.useNotificationIcon ? this._iconForNotificationData(icon, hints) : null; | ||||
|         source.processNotification(notification, sourceGIcon); | ||||
|     }, | ||||
|  | ||||
| @@ -468,7 +459,7 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|             // 'icon-multi', | ||||
|             'icon-static', | ||||
|             'persistence', | ||||
|             'sound', | ||||
|             // 'sound', | ||||
|         ]; | ||||
|     }, | ||||
|  | ||||
| @@ -520,26 +511,16 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|     Name: 'FdoNotificationDaemonSource', | ||||
| const Source = new Lang.Class({ | ||||
|     Name: 'NotificationDaemonSource', | ||||
|     Extends: MessageTray.Source, | ||||
|  | ||||
|     _init: function(title, pid, sender, trayIcon, appId) { | ||||
|         // Need to set the app before chaining up, so | ||||
|         // methods called from the parent constructor can find it | ||||
|         this.trayIcon = trayIcon; | ||||
|         this.pid = pid; | ||||
|         this.app = this._getApp(appId); | ||||
|  | ||||
|     _init: function(title, pid, sender, trayIcon) { | ||||
|         this.parent(title); | ||||
|  | ||||
|         this.initialTitle = title; | ||||
|  | ||||
|         if (this.app) | ||||
|             this.title = this.app.get_name(); | ||||
|         else | ||||
|             this.useNotificationIcon = true; | ||||
|  | ||||
|         this.pid = pid; | ||||
|         if (sender) | ||||
|             this._nameWatcherId = Gio.DBus.session.watch_name(sender, | ||||
|                                                               Gio.BusNameWatcherFlags.NONE, | ||||
| @@ -548,19 +529,16 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         else | ||||
|             this._nameWatcherId = 0; | ||||
|  | ||||
|         if (this.trayIcon) { | ||||
|             // Try again finding the app, using the WM_CLASS from the tray icon | ||||
|             this._setSummaryIcon(this.trayIcon); | ||||
|             this.useNotificationIcon = false; | ||||
|         } | ||||
|     }, | ||||
|         this._setApp(); | ||||
|         if (this.app) | ||||
|             this.title = this.app.get_name(); | ||||
|         else | ||||
|             this.useNotificationIcon = true; | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         if (this.app && this.app.get_app_info()) { | ||||
|             let id = this.app.get_id().replace(/\.desktop$/,''); | ||||
|             return new MessageTray.NotificationApplicationPolicy(id); | ||||
|         } else { | ||||
|             return new MessageTray.NotificationGenericPolicy(); | ||||
|         this.trayIcon = trayIcon; | ||||
|         if (this.trayIcon) { | ||||
|            this._setSummaryIcon(this.trayIcon); | ||||
|            this.useNotificationIcon = false; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -587,22 +565,24 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|             this.notify(notification); | ||||
|     }, | ||||
|  | ||||
|     handleSummaryClick: function(button) { | ||||
|     handleSummaryClick: function() { | ||||
|         if (!this.trayIcon) | ||||
|             return false; | ||||
|  | ||||
|         let event = Clutter.get_current_event(); | ||||
|         if (event.type() != Clutter.EventType.BUTTON_RELEASE) | ||||
|             return false; | ||||
|  | ||||
|         // Left clicks are passed through only where there aren't unacknowledged | ||||
|         // notifications, so it possible to open them in summary mode; right | ||||
|         // clicks are always forwarded, as the right click menu is not useful for | ||||
|         // tray icons | ||||
|         if (button == 1 && | ||||
|         if (event.get_button() == 1 && | ||||
|             this.notifications.length > 0) | ||||
|             return false; | ||||
|  | ||||
|         let id = global.stage.connect('deactivate', Lang.bind(this, function () { | ||||
|             global.stage.disconnect(id); | ||||
|         let id = global.connect('notify::stage-input-mode', Lang.bind(this, function () { | ||||
|             global.disconnect(id); | ||||
|             this.trayIcon.click(event); | ||||
|         })); | ||||
|  | ||||
| @@ -610,7 +590,7 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _getApp: function(appId) { | ||||
|     _getApp: function() { | ||||
|         let app; | ||||
|  | ||||
|         app = Shell.WindowTracker.get_default().get_app_from_pid(this.pid); | ||||
| @@ -618,17 +598,7 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|             return app; | ||||
|  | ||||
|         if (this.trayIcon) { | ||||
|             app = Shell.AppSystem.get_default().lookup_startup_wmclass(this.trayIcon.wm_class); | ||||
|             if (app != null) | ||||
|                 return app; | ||||
|  | ||||
|             app = Shell.AppSystem.get_default().lookup_desktop_wmclass(this.trayIcon.wm_class); | ||||
|             if (app != null) | ||||
|                 return app; | ||||
|         } | ||||
|  | ||||
|         if (appId) { | ||||
|             app = Shell.AppSystem.get_default().lookup_app(appId + '.desktop'); | ||||
|             app = Shell.AppSystem.get_default().lookup_wmclass(this.trayIcon.wmclass); | ||||
|             if (app != null) | ||||
|                 return app; | ||||
|         } | ||||
| @@ -636,6 +606,22 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     _setApp: function() { | ||||
|         if (this.app) | ||||
|             return; | ||||
|  | ||||
|         this.app = this._getApp(); | ||||
|         if (!this.app) | ||||
|             return; | ||||
|  | ||||
|         // Only override the icon if we were previously using | ||||
|         // notification-based icons (ie, not a trayicon) or if it was unset before | ||||
|         if (!this.trayIcon) { | ||||
|             this.useNotificationIcon = false; | ||||
|             this.iconUpdated(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     setTitle: function(title) { | ||||
|         // Do nothing if .app is set, we don't want to override the | ||||
|         // app name with whatever is provided through libnotify (usually | ||||
| @@ -646,9 +632,9 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         this.parent(title); | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
|         this.openApp(); | ||||
|     open: function(notification) { | ||||
|         this.destroyNonResidentNotifications(); | ||||
|         this.openApp(); | ||||
|     }, | ||||
|  | ||||
|     _lastNotificationRemoved: function() { | ||||
| @@ -660,8 +646,11 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         if (this.app == null) | ||||
|             return; | ||||
|  | ||||
|         this.app.activate(); | ||||
|         Main.overview.hide(); | ||||
|         let windows = this.app.get_windows(); | ||||
|         if (windows.length > 0) { | ||||
|             let mostRecentWindow = windows[0]; | ||||
|             Main.activateWindow(mostRecentWindow); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
| @@ -688,290 +677,3 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const PRIORITY_URGENCY_MAP = { | ||||
|     low: MessageTray.Urgency.LOW, | ||||
|     normal: MessageTray.Urgency.NORMAL, | ||||
|     high: MessageTray.Urgency.HIGH, | ||||
|     urgent: MessageTray.Urgency.CRITICAL | ||||
| }; | ||||
|  | ||||
| const GtkNotificationDaemonNotification = new Lang.Class({ | ||||
|     Name: 'GtkNotificationDaemonNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, notification) { | ||||
|         this.parent(source); | ||||
|         this._serialized = GLib.Variant.new('a{sv}', notification); | ||||
|  | ||||
|         let { "title": title, | ||||
|               "body": body, | ||||
|               "icon": gicon, | ||||
|               "urgent": urgent, | ||||
|               "priority": priority, | ||||
|               "buttons": buttons, | ||||
|               "default-action": defaultAction, | ||||
|               "default-action-target": defaultActionTarget } = notification; | ||||
|  | ||||
|         if (priority) { | ||||
|             let urgency = PRIORITY_URGENCY_MAP[priority.unpack()]; | ||||
|             this.setUrgency(urgency != undefined ? urgency : MessageTray.Urgency.NORMAL); | ||||
|         } else if (urgent) { | ||||
|             this.setUrgency(urgent.unpack() ? MessageTray.Urgency.CRITICAL | ||||
|                             : MessageTray.Urgency.NORMAL); | ||||
|         } else { | ||||
|             this.setUrgency(MessageTray.Urgency.NORMAL); | ||||
|         } | ||||
|  | ||||
|         if (buttons) { | ||||
|             buttons.deep_unpack().forEach(Lang.bind(this, function(button) { | ||||
|                 this.addAction(button.label.unpack(), | ||||
|                                Lang.bind(this, this._onButtonClicked, button)); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         this._defaultAction = defaultAction ? defaultAction.unpack() : null; | ||||
|         this._defaultActionTarget = defaultActionTarget; | ||||
|  | ||||
|         this.update(title.unpack(), body ? body.unpack() : null, | ||||
|                     { gicon: gicon ? Gio.icon_deserialize(gicon) : null }); | ||||
|     }, | ||||
|  | ||||
|     _activateAction: function(namespacedActionId, target) { | ||||
|         if (namespacedActionId) { | ||||
|             if (namespacedActionId.startsWith('app.')) { | ||||
|                 let actionId = namespacedActionId.slice('app.'.length); | ||||
|                 this.source.activateAction(actionId, target); | ||||
|             } | ||||
|         } else { | ||||
|             this.source.open(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onButtonClicked: function(button) { | ||||
|         let { 'action': action, 'target': actionTarget } = button; | ||||
|         this._activateAction(action.unpack(), actionTarget); | ||||
|     }, | ||||
|  | ||||
|     _onClicked: function() { | ||||
|         this._activateAction(this._defaultAction, this._defaultActionTarget); | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
|     serialize: function() { | ||||
|         return this._serialized; | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const FdoApplicationIface = '<node> \ | ||||
| <interface name="org.freedesktop.Application"> \ | ||||
| <method name="ActivateAction"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="av" direction="in" /> \ | ||||
|     <arg type="a{sv}" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="Activate"> \ | ||||
|     <arg type="a{sv}" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
| const FdoApplicationProxy = Gio.DBusProxy.makeProxyWrapper(FdoApplicationIface); | ||||
|  | ||||
| function objectPathFromAppId(appId) { | ||||
|     return '/' + appId.replace(/\./g, '/'); | ||||
| } | ||||
|  | ||||
| function getPlatformData() { | ||||
|     let startupId = GLib.Variant.new('s', '_TIME' + global.get_current_time()); | ||||
|     return { "desktop-startup-id": startupId }; | ||||
| } | ||||
|  | ||||
| function InvalidAppError() {} | ||||
|  | ||||
| const GtkNotificationDaemonAppSource = new Lang.Class({ | ||||
|     Name: 'GtkNotificationDaemonAppSource', | ||||
|     Extends: MessageTray.Source, | ||||
|  | ||||
|     _init: function(appId) { | ||||
|         this._appId = appId; | ||||
|         this._objectPath = objectPathFromAppId(appId); | ||||
|  | ||||
|         this._app = Shell.AppSystem.get_default().lookup_app(appId + '.desktop'); | ||||
|         if (!this._app) | ||||
|             throw new InvalidAppError(); | ||||
|  | ||||
|         this._notifications = {}; | ||||
|  | ||||
|         this.parent(this._app.get_name()); | ||||
|     }, | ||||
|  | ||||
|     createIcon: function(size) { | ||||
|         return this._app.create_icon_texture(size); | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationApplicationPolicy(this._appId); | ||||
|     }, | ||||
|  | ||||
|     _createApp: function() { | ||||
|         return new FdoApplicationProxy(Gio.DBus.session, this._appId, this._objectPath); | ||||
|     }, | ||||
|  | ||||
|     activateAction: function(actionId, target) { | ||||
|         let app = this._createApp(); | ||||
|         app.ActivateActionRemote(actionId, target ? [target] : [], getPlatformData()); | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
|         let app = this._createApp(); | ||||
|         app.ActivateRemote(getPlatformData()); | ||||
|     }, | ||||
|  | ||||
|     addNotification: function(notificationId, notificationParams, showBanner) { | ||||
|         if (this._notifications[notificationId]) | ||||
|             this._notifications[notificationId].destroy(); | ||||
|  | ||||
|         let notification = new GtkNotificationDaemonNotification(this, notificationParams); | ||||
|         notification.connect('destroy', Lang.bind(this, function() { | ||||
|             delete this._notifications[notificationId]; | ||||
|         })); | ||||
|         this._notifications[notificationId] = notification; | ||||
|  | ||||
|         if (showBanner) | ||||
|             this.notify(notification); | ||||
|         else | ||||
|             this.pushNotification(notification); | ||||
|     }, | ||||
|  | ||||
|     removeNotification: function(notificationId) { | ||||
|         if (this._notifications[notificationId]) | ||||
|             this._notifications[notificationId].destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED); | ||||
|     }, | ||||
|  | ||||
|     serialize: function() { | ||||
|         let notifications = []; | ||||
|         for (let notificationId in this._notifications) { | ||||
|             let notification = this._notifications[notificationId]; | ||||
|             notifications.push([notificationId, notification.serialize()]); | ||||
|         } | ||||
|         return [this._appId, notifications]; | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const GtkNotificationsIface = '<node> \ | ||||
| <interface name="org.gtk.Notifications"> \ | ||||
| <method name="AddNotification"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="a{sv}" direction="in" /> \ | ||||
| </method> \ | ||||
| <method name="RemoveNotification"> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
|     <arg type="s" direction="in" /> \ | ||||
| </method> \ | ||||
| </interface> \ | ||||
| </node>'; | ||||
|  | ||||
| const GtkNotificationDaemon = new Lang.Class({ | ||||
|     Name: 'GtkNotificationDaemon', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._sources = {}; | ||||
|  | ||||
|         this._loadNotifications(); | ||||
|  | ||||
|         this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GtkNotificationsIface, this); | ||||
|         this._dbusImpl.export(Gio.DBus.session, '/org/gtk/Notifications'); | ||||
|  | ||||
|         Gio.DBus.session.own_name('org.gtk.Notifications', Gio.BusNameOwnerFlags.REPLACE, null, null); | ||||
|     }, | ||||
|  | ||||
|     _ensureAppSource: function(appId) { | ||||
|         if (this._sources[appId]) | ||||
|             return this._sources[appId]; | ||||
|  | ||||
|         let source = new GtkNotificationDaemonAppSource(appId); | ||||
|  | ||||
|         source.connect('destroy', Lang.bind(this, function() { | ||||
|             delete this._sources[appId]; | ||||
|             this._saveNotifications(); | ||||
|         })); | ||||
|         source.connect('count-updated', Lang.bind(this, this._saveNotifications)); | ||||
|         Main.messageTray.add(source); | ||||
|         this._sources[appId] = source; | ||||
|         return source; | ||||
|     }, | ||||
|  | ||||
|     _loadNotifications: function() { | ||||
|         this._isLoading = true; | ||||
|  | ||||
|         let value = global.get_persistent_state('a(sa(sv))', 'notifications'); | ||||
|         if (value) { | ||||
|             let sources = value.deep_unpack(); | ||||
|             sources.forEach(Lang.bind(this, function([appId, notifications]) { | ||||
|                 if (notifications.length == 0) | ||||
|                     return; | ||||
|  | ||||
|                 let source; | ||||
|                 try { | ||||
|                     source = this._ensureAppSource(appId); | ||||
|                 } catch(e if e instanceof InvalidAppError) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 notifications.forEach(function([notificationId, notification]) { | ||||
|                     source.addNotification(notificationId, notification.deep_unpack(), false); | ||||
|                 }); | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         this._isLoading = false; | ||||
|     }, | ||||
|  | ||||
|     _saveNotifications: function() { | ||||
|         if (this._isLoading) | ||||
|             return; | ||||
|  | ||||
|         let sources = []; | ||||
|         for (let appId in this._sources) { | ||||
|             let source = this._sources[appId]; | ||||
|             sources.push(source.serialize()); | ||||
|         } | ||||
|  | ||||
|         global.set_persistent_state('notifications', new GLib.Variant('a(sa(sv))', sources)); | ||||
|     }, | ||||
|  | ||||
|     AddNotificationAsync: function(params, invocation) { | ||||
|         let [appId, notificationId, notification] = params; | ||||
|  | ||||
|         let source; | ||||
|         try { | ||||
|             source = this._ensureAppSource(appId); | ||||
|         } catch(e if e instanceof InvalidAppError) { | ||||
|             invocation.return_dbus_error('org.gtk.Notifications.InvalidApp', 'The app by ID "%s" could not be found'.format(appId)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         source.addNotification(notificationId, notification, true); | ||||
|  | ||||
|         invocation.return_value(null); | ||||
|     }, | ||||
|  | ||||
|     RemoveNotificationAsync: function(params, invocation) { | ||||
|         let [appId, notificationId] = params; | ||||
|         let source = this._sources[appId]; | ||||
|         if (source) | ||||
|             source.removeNotification(notificationId); | ||||
|  | ||||
|         invocation.return_value(null); | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const NotificationDaemon = new Lang.Class({ | ||||
|     Name: 'NotificationDaemon', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._fdoNotificationDaemon = new FdoNotificationDaemon(); | ||||
|         this._gtkNotificationDaemon = new GtkNotificationDaemon(); | ||||
|     }, | ||||
| }); | ||||
|   | ||||
| @@ -1,277 +0,0 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Layout = imports.ui.layout; | ||||
| const Main = imports.ui.main; | ||||
| const Mainloop = imports.mainloop; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const Meta = imports.gi.Meta; | ||||
|  | ||||
| const HIDE_TIMEOUT = 1500; | ||||
| const FADE_TIME = 0.1; | ||||
| const LEVEL_ANIMATION_TIME = 0.1; | ||||
|  | ||||
| const LevelBar = new Lang.Class({ | ||||
|     Name: 'LevelBar', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._level = 0; | ||||
|  | ||||
|         this.actor = new St.Bin({ style_class: 'level', | ||||
|                                   x_fill: true, y_fill: true }); | ||||
|         this._bar = new St.DrawingArea(); | ||||
|         this._bar.connect('repaint', Lang.bind(this, this._repaint)); | ||||
|  | ||||
|         this.actor.set_child(this._bar); | ||||
|     }, | ||||
|  | ||||
|     get level() { | ||||
|         return this._level; | ||||
|     }, | ||||
|  | ||||
|     set level(value) { | ||||
|         let newValue = Math.max(0, Math.min(value, 100)); | ||||
|         if (newValue == this._level) | ||||
|             return; | ||||
|         this._level = newValue; | ||||
|         this._bar.queue_repaint(); | ||||
|     }, | ||||
|  | ||||
|     _repaint: function() { | ||||
|         let cr = this._bar.get_context(); | ||||
|  | ||||
|         let node = this.actor.get_theme_node(); | ||||
|         let radius = node.get_border_radius(0); // assume same radius for all corners | ||||
|         Clutter.cairo_set_source_color(cr, node.get_foreground_color()); | ||||
|  | ||||
|         let [w, h] = this._bar.get_surface_size(); | ||||
|         w *= (this._level / 100.); | ||||
|  | ||||
|         if (w == 0) | ||||
|             return; | ||||
|  | ||||
|         cr.moveTo(radius, 0); | ||||
|         if (w >= radius) | ||||
|             cr.arc(w - radius, radius, radius, 1.5 * Math.PI, 2. * Math.PI); | ||||
|         else | ||||
|             cr.lineTo(w, 0); | ||||
|         if (w >= radius) | ||||
|             cr.arc(w - radius, h - radius, radius, 0, 0.5 * Math.PI); | ||||
|         else | ||||
|             cr.lineTo(w, h); | ||||
|         cr.arc(radius, h - radius, radius, 0.5 * Math.PI, Math.PI); | ||||
|         cr.arc(radius, radius, radius, Math.PI, 1.5 * Math.PI); | ||||
|         cr.fill(); | ||||
|         cr.$dispose(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const OsdWindow = new Lang.Class({ | ||||
|     Name: 'OsdWindow', | ||||
|  | ||||
|     _init: function(monitorIndex) { | ||||
|         this._popupSize = 0; | ||||
|         this.actor = new St.Widget({ x_expand: true, | ||||
|                                      y_expand: true, | ||||
|                                      x_align: Clutter.ActorAlign.CENTER, | ||||
|                                      y_align: Clutter.ActorAlign.CENTER }); | ||||
|  | ||||
|         this._monitorIndex = monitorIndex; | ||||
|         let constraint = new Layout.MonitorConstraint({ index: monitorIndex }); | ||||
|         this.actor.add_constraint(constraint); | ||||
|  | ||||
|         this._box = new St.BoxLayout({ style_class: 'osd-window', | ||||
|                                        vertical: true }); | ||||
|         this.actor.add_actor(this._box); | ||||
|  | ||||
|         this._box.connect('style-changed', Lang.bind(this, this._onStyleChanged)); | ||||
|         this._box.connect('notify::height', Lang.bind(this, | ||||
|             function() { | ||||
|                 Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, | ||||
|                     function() { | ||||
|                         this._box.width = this._box.height; | ||||
|                     })); | ||||
|             })); | ||||
|  | ||||
|         this._icon = new St.Icon(); | ||||
|         this._box.add(this._icon, { expand: true }); | ||||
|  | ||||
|         this._label = new St.Label(); | ||||
|         this._box.add(this._label); | ||||
|  | ||||
|         this._level = new LevelBar(); | ||||
|         this._box.add(this._level.actor); | ||||
|  | ||||
|         this._hideTimeoutId = 0; | ||||
|         this._reset(); | ||||
|  | ||||
|         Main.layoutManager.connect('monitors-changed', | ||||
|                                    Lang.bind(this, this._monitorsChanged)); | ||||
|         this._monitorsChanged(); | ||||
|         Main.uiGroup.add_child(this.actor); | ||||
|     }, | ||||
|  | ||||
|     setIcon: function(icon) { | ||||
|         this._icon.gicon = icon; | ||||
|     }, | ||||
|  | ||||
|     setLabel: function(label) { | ||||
|         this._label.visible = (label != undefined); | ||||
|         if (label) | ||||
|             this._label.text = label; | ||||
|     }, | ||||
|  | ||||
|     setLevel: function(level) { | ||||
|         this._level.actor.visible = (level != undefined); | ||||
|         if (level != undefined) { | ||||
|             if (this.actor.visible) | ||||
|                 Tweener.addTween(this._level, | ||||
|                                  { level: level, | ||||
|                                    time: LEVEL_ANIMATION_TIME, | ||||
|                                    transition: 'easeOutQuad' }); | ||||
|             else | ||||
|                 this._level.level = level; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     show: function() { | ||||
|         if (!this._icon.gicon) | ||||
|             return; | ||||
|  | ||||
|         if (!this.actor.visible) { | ||||
|             Meta.disable_unredirect_for_screen(global.screen); | ||||
|             this.actor.show(); | ||||
|             this.actor.opacity = 0; | ||||
|             this.actor.get_parent().set_child_above_sibling(this.actor, null); | ||||
|  | ||||
|             Tweener.addTween(this.actor, | ||||
|                              { opacity: 255, | ||||
|                                time: FADE_TIME, | ||||
|                                transition: 'easeOutQuad' }); | ||||
|         } | ||||
|  | ||||
|         if (this._hideTimeoutId) | ||||
|             Mainloop.source_remove(this._hideTimeoutId); | ||||
|         this._hideTimeoutId = Mainloop.timeout_add(HIDE_TIMEOUT, | ||||
|                                                    Lang.bind(this, this._hide)); | ||||
|         GLib.Source.set_name_by_id(this._hideTimeoutId, '[gnome-shell] this._hide'); | ||||
|     }, | ||||
|  | ||||
|     cancel: function() { | ||||
|         if (!this._hideTimeoutId) | ||||
|             return; | ||||
|  | ||||
|         Mainloop.source_remove(this._hideTimeoutId); | ||||
|         this._hide(); | ||||
|     }, | ||||
|  | ||||
|     _hide: function() { | ||||
|         this._hideTimeoutId = 0; | ||||
|         Tweener.addTween(this.actor, | ||||
|                          { opacity: 0, | ||||
|                            time: FADE_TIME, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            onComplete: Lang.bind(this, function() { | ||||
|                               this._reset(); | ||||
|                               Meta.enable_unredirect_for_screen(global.screen); | ||||
|                            }) | ||||
|                          }); | ||||
|         return GLib.SOURCE_REMOVE; | ||||
|     }, | ||||
|  | ||||
|     _reset: function() { | ||||
|         this.actor.hide(); | ||||
|         this.setLabel(null); | ||||
|         this.setLevel(null); | ||||
|     }, | ||||
|  | ||||
|     _monitorsChanged: function() { | ||||
|         /* assume 110x110 on a 640x480 display and scale from there */ | ||||
|         let monitor = Main.layoutManager.monitors[this._monitorIndex]; | ||||
|         if (!monitor) | ||||
|             return; // we are about to be removed | ||||
|  | ||||
|         let scalew = monitor.width / 640.0; | ||||
|         let scaleh = monitor.height / 480.0; | ||||
|         let scale = Math.min(scalew, scaleh); | ||||
|         this._popupSize = 110 * Math.max(1, scale); | ||||
|  | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         this._icon.icon_size = this._popupSize / (2 * scaleFactor); | ||||
|         this._box.translation_y = monitor.height / 4; | ||||
|         this._box.style_changed(); | ||||
|     }, | ||||
|  | ||||
|     _onStyleChanged: function() { | ||||
|         let themeNode = this._box.get_theme_node(); | ||||
|         let horizontalPadding = themeNode.get_horizontal_padding(); | ||||
|         let verticalPadding = themeNode.get_vertical_padding(); | ||||
|         let topBorder = themeNode.get_border_width(St.Side.TOP); | ||||
|         let bottomBorder = themeNode.get_border_width(St.Side.BOTTOM); | ||||
|         let leftBorder = themeNode.get_border_width(St.Side.LEFT); | ||||
|         let rightBorder = themeNode.get_border_width(St.Side.RIGHT); | ||||
|  | ||||
|         let minWidth = this._popupSize - verticalPadding - leftBorder - rightBorder; | ||||
|         let minHeight = this._popupSize - horizontalPadding - topBorder - bottomBorder; | ||||
|  | ||||
|         // minWidth/minHeight here are in real pixels, | ||||
|         // but the theme takes measures in unscaled dimensions | ||||
|         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|         this._box.style = 'min-height: %dpx;'.format(Math.max(minWidth, minHeight) / scaleFactor); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const OsdWindowManager = new Lang.Class({ | ||||
|     Name: 'OsdWindowManager', | ||||
|  | ||||
|     _init: function() { | ||||
|         this._osdWindows = []; | ||||
|         Main.layoutManager.connect('monitors-changed', | ||||
|                                     Lang.bind(this, this._monitorsChanged)); | ||||
|         this._monitorsChanged(); | ||||
|     }, | ||||
|  | ||||
|     _monitorsChanged: function() { | ||||
|         for (let i = 0; i < Main.layoutManager.monitors.length; i++) { | ||||
|             if (this._osdWindows[i] == undefined) | ||||
|                 this._osdWindows[i] = new OsdWindow(i); | ||||
|         } | ||||
|  | ||||
|         for (let i = Main.layoutManager.monitors.length; i < this._osdWindows.length; i++) { | ||||
|             this._osdWindows[i].actor.destroy(); | ||||
|             this._osdWindows[i] = null; | ||||
|         } | ||||
|  | ||||
|         this._osdWindows.length = Main.layoutManager.monitors.length; | ||||
|     }, | ||||
|  | ||||
|     _showOsdWindow: function(monitorIndex, icon, label, level) { | ||||
|         this._osdWindows[monitorIndex].setIcon(icon); | ||||
|         this._osdWindows[monitorIndex].setLabel(label); | ||||
|         this._osdWindows[monitorIndex].setLevel(level); | ||||
|         this._osdWindows[monitorIndex].show(); | ||||
|     }, | ||||
|  | ||||
|     show: function(monitorIndex, icon, label, level) { | ||||
|         if (monitorIndex != -1) { | ||||
|             for (let i = 0; i < this._osdWindows.length; i++) { | ||||
|                 if (i == monitorIndex) | ||||
|                     this._showOsdWindow(i, icon, label, level); | ||||
|                 else | ||||
|                     this._osdWindows[i].cancel(); | ||||
|             } | ||||
|         } else { | ||||
|             for (let i = 0; i < this._osdWindows.length; i++) | ||||
|                 this._showOsdWindow(i, icon, label, level); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     hideAll: function() { | ||||
|         for (let i = 0; i < this._osdWindows.length; i++) | ||||
|             this._osdWindows[i].cancel(); | ||||
|     } | ||||
| }); | ||||
| @@ -1,7 +1,6 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Mainloop = imports.mainloop; | ||||
| @@ -11,29 +10,38 @@ const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Gdk = imports.gi.Gdk; | ||||
|  | ||||
| const Background = imports.ui.background; | ||||
| const Dash = imports.ui.dash; | ||||
| const DND = imports.ui.dnd; | ||||
| const LayoutManager = imports.ui.layout; | ||||
| const Lightbox = imports.ui.lightbox; | ||||
| const Main = imports.ui.main; | ||||
| const MessageTray = imports.ui.messageTray; | ||||
| const OverviewControls = imports.ui.overviewControls; | ||||
| const Panel = imports.ui.panel; | ||||
| const Params = imports.misc.params; | ||||
| const Tweener = imports.ui.tweener; | ||||
| const ViewSelector = imports.ui.viewSelector; | ||||
| const WorkspaceThumbnail = imports.ui.workspaceThumbnail; | ||||
|  | ||||
| // Time for initial animation going into Overview mode | ||||
| const ANIMATION_TIME = 0.25; | ||||
|  | ||||
| // Must be less than ANIMATION_TIME, since we switch to | ||||
| // or from the overview completely after ANIMATION_TIME, | ||||
| // and don't want the shading animation to get cut off | ||||
| const SHADE_ANIMATION_TIME = .20; | ||||
| const DND_WINDOW_SWITCH_TIMEOUT = 1250; | ||||
|  | ||||
| const DND_WINDOW_SWITCH_TIMEOUT = 750; | ||||
|  | ||||
| const OVERVIEW_ACTIVATION_TIMEOUT = 0.5; | ||||
| const GLSL_DIM_EFFECT_DECLARATIONS = ''; | ||||
| const GLSL_DIM_EFFECT_CODE = '\ | ||||
|    vec2 dist = cogl_tex_coord_in[0].xy - vec2(0.5, 0.5); \ | ||||
|    float elipse_radius = 0.5; \ | ||||
|    /* from https://bugzilla.gnome.org/show_bug.cgi?id=669798: \ | ||||
|       the alpha on the gradient goes from 250 at its darkest to 180 at its most transparent. */ \ | ||||
|    float y = 250.0 / 255.0; \ | ||||
|    float x = 180.0 / 255.0; \ | ||||
|    /* interpolate darkening value, based on distance from screen center */ \ | ||||
|    float val = min(length(dist), elipse_radius); \ | ||||
|    float a = mix(x, y, val / elipse_radius); \ | ||||
|    /* dim_factor varies from [1.0 -> 0.5] when overview is showing \ | ||||
|       We use it to smooth value, then we clamp it to valid color interval */ \ | ||||
|    a = clamp(a - cogl_color_in.r + 0.5, 0.0, 1.0); \ | ||||
|    /* We\'re blending between: color and black color (obviously omitted in the equation) */ \ | ||||
|    cogl_color_out.xyz = cogl_color_out.xyz * (1.0 - a); \ | ||||
|    cogl_color_out.a = 1.0;'; | ||||
|  | ||||
| const ShellInfo = new Lang.Class({ | ||||
|     Name: 'ShellInfo', | ||||
| @@ -80,8 +88,10 @@ const ShellInfo = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         this._undoCallback = undoCallback; | ||||
|         if (undoCallback) | ||||
|             notification.addAction(_("Undo"), Lang.bind(this, this._onUndoClicked)); | ||||
|         if (undoCallback) { | ||||
|             notification.addButton('system-undo', _("Undo")); | ||||
|             notification.connect('action-invoked', Lang.bind(this, this._onUndoClicked)); | ||||
|         } | ||||
|  | ||||
|         this._source.notify(notification); | ||||
|     } | ||||
| @@ -107,52 +117,52 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|         this._overviewCreated = true; | ||||
|  | ||||
|         // The main Background actors are inside global.window_group which are | ||||
|         // The main BackgroundActor is inside global.window_group which is | ||||
|         // hidden when displaying the overview, so we create a new | ||||
|         // one. Instances of this class share a single CoglTexture behind the | ||||
|         // scenes which allows us to show the background with different | ||||
|         // rendering options without duplicating the texture data. | ||||
|         let monitor = Main.layoutManager.primaryMonitor; | ||||
|         this._background = Meta.BackgroundActor.new_for_screen(global.screen); | ||||
|         this._background.add_glsl_snippet(Meta.SnippetHook.FRAGMENT, | ||||
|                                           GLSL_DIM_EFFECT_DECLARATIONS, | ||||
|                                           GLSL_DIM_EFFECT_CODE, | ||||
|                                           false); | ||||
|         this._background.hide(); | ||||
|         global.overlay_group.add_actor(this._background); | ||||
|  | ||||
|         let layout = new Clutter.BinLayout(); | ||||
|         this._stack = new Clutter.Actor({ layout_manager: layout }); | ||||
|         this._stack.add_constraint(new LayoutManager.MonitorConstraint({ primary: true })); | ||||
|         this._desktopFade = new St.Bin(); | ||||
|         global.overlay_group.add_actor(this._desktopFade); | ||||
|  | ||||
|         /* Translators: This is the main view to select | ||||
|            activities. See also note for "Activities" string. */ | ||||
|         this._overview = new St.BoxLayout({ name: 'overview', | ||||
|                                             accessible_name: _("Overview"), | ||||
|                                             reactive: true, | ||||
|                                             vertical: true, | ||||
|                                             x_expand: true, | ||||
|                                             y_expand: true }); | ||||
|                                             vertical: true }); | ||||
|         this._overview._delegate = this; | ||||
|  | ||||
|         this._backgroundGroup = new Meta.BackgroundGroup(); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._backgroundGroup); | ||||
|         this._bgManagers = []; | ||||
|         this._group = new St.BoxLayout({ name: 'overview-group' }); | ||||
|  | ||||
|         this._desktopFade = new St.Widget(); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._desktopFade); | ||||
|  | ||||
|         this._activationTime = 0; | ||||
|         this._capturedEventId = 0; | ||||
|         this._buttonPressId = 0; | ||||
|  | ||||
|         this.visible = false;           // animating to overview, in overview, animating out | ||||
|         this._shown = false;            // show() and not hide() | ||||
|         this._shownTemporarily = false; // showTemporarily() and not hideTemporarily() | ||||
|         this._modal = false;            // have a modal grab | ||||
|         this.animationInProgress = false; | ||||
|         this.visibleTarget = false; | ||||
|         this._hideInProgress = false; | ||||
|  | ||||
|         // During transitions, we raise this to the top to avoid having the overview | ||||
|         // area be reactive; it causes too many issues such as double clicks on | ||||
|         // Dash elements, or mouseover handlers in the workspaces. | ||||
|         this._coverPane = new Clutter.Actor({ opacity: 0, | ||||
|                                               reactive: true }); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._coverPane); | ||||
|         this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return Clutter.EVENT_STOP; })); | ||||
|         this._coverPane = new Clutter.Rectangle({ opacity: 0, | ||||
|                                                   reactive: true }); | ||||
|         this._overview.add_actor(this._coverPane); | ||||
|         this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; })); | ||||
|  | ||||
|         this._stack.add_actor(this._overview); | ||||
|         Main.layoutManager.overviewGroup.add_child(this._stack); | ||||
|         this._overview.hide(); | ||||
|         global.overlay_group.add_actor(this._overview); | ||||
|  | ||||
|         this._coverPane.hide(); | ||||
|  | ||||
| @@ -176,44 +186,6 @@ const Overview = new Lang.Class({ | ||||
|             this.init(); | ||||
|     }, | ||||
|  | ||||
|     _updateBackgrounds: function() { | ||||
|         for (let i = 0; i < this._bgManagers.length; i++) | ||||
|             this._bgManagers[i].destroy(); | ||||
|  | ||||
|         this._bgManagers = []; | ||||
|  | ||||
|         for (let i = 0; i < Main.layoutManager.monitors.length; i++) { | ||||
|             let bgManager = new Background.BackgroundManager({ container: this._backgroundGroup, | ||||
|                                                                monitorIndex: i, | ||||
|                                                                vignette: true }); | ||||
|             this._bgManagers.push(bgManager); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _unshadeBackgrounds: function() { | ||||
|         let backgrounds = this._backgroundGroup.get_children(); | ||||
|         for (let i = 0; i < backgrounds.length; i++) { | ||||
|             Tweener.addTween(backgrounds[i], | ||||
|                              { brightness: 1.0, | ||||
|                                vignette_sharpness: 0.0, | ||||
|                                time: SHADE_ANIMATION_TIME, | ||||
|                                transition: 'easeOutQuad' | ||||
|                              }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _shadeBackgrounds: function() { | ||||
|         let backgrounds = this._backgroundGroup.get_children(); | ||||
|         for (let i = 0; i < backgrounds.length; i++) { | ||||
|             Tweener.addTween(backgrounds[i], | ||||
|                              { brightness: Lightbox.VIGNETTE_BRIGHTNESS, | ||||
|                                vignette_sharpness: Lightbox.VIGNETTE_SHARPNESS, | ||||
|                                time: SHADE_ANIMATION_TIME, | ||||
|                                transition: 'easeOutQuad' | ||||
|                              }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _sessionUpdated: function() { | ||||
|         this.isDummy = !Main.sessionMode.hasOverview; | ||||
|         this._createOverview(); | ||||
| @@ -238,46 +210,58 @@ const Overview = new Lang.Class({ | ||||
|                                         opacity: 0 }); | ||||
|         this._overview.add_actor(this._panelGhost); | ||||
|  | ||||
|         this._searchEntry = new St.Entry({ style_class: 'search-entry', | ||||
|         this._searchEntry = new St.Entry({ name: 'searchEntry', | ||||
|                                            /* Translators: this is the text displayed | ||||
|                                               in the search entry when no search is | ||||
|                                               active; it should not exceed ~30 | ||||
|                                               characters. */ | ||||
|                                            hint_text: _("Type to search…"), | ||||
|                                            hint_text: _("Type to search..."), | ||||
|                                            track_hover: true, | ||||
|                                            can_focus: true }); | ||||
|         this._searchEntryBin = new St.Bin({ child: this._searchEntry, | ||||
|                                             x_align: St.Align.MIDDLE }); | ||||
|         this._overview.add_actor(this._searchEntryBin); | ||||
|  | ||||
|         // Create controls | ||||
|         this._controls = new OverviewControls.ControlsManager(this._searchEntry); | ||||
|         this._dash = this._controls.dash; | ||||
|         this.viewSelector = this._controls.viewSelector; | ||||
|  | ||||
|         // Add our same-line elements after the search entry | ||||
|         this._overview.add(this._controls.actor, { y_fill: true, expand: true }); | ||||
|         this._controls.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); | ||||
|  | ||||
|         this._stack.add_actor(this._controls.indicatorActor); | ||||
|  | ||||
|         // TODO - recalculate everything when desktop size changes | ||||
|         this._dash = new Dash.Dash(); | ||||
|         this._group.add_actor(this._dash.actor); | ||||
|         this.dashIconSize = this._dash.iconSize; | ||||
|         this._dash.connect('icon-size-changed', | ||||
|                            Lang.bind(this, function() { | ||||
|                                this.dashIconSize = this._dash.iconSize; | ||||
|                            })); | ||||
|  | ||||
|         // Translators: this is the name of the dock/favorites area on | ||||
|         // the left of the overview | ||||
|         Main.ctrlAltTabManager.addGroup(this._dash.actor, _("Dash"), 'user-bookmarks-symbolic'); | ||||
|  | ||||
|         this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry, | ||||
|                                                            this._dash.showAppsButton); | ||||
|         this._group.add(this._viewSelector.actor, { x_fill: true, | ||||
|                                                     expand: true }); | ||||
|  | ||||
|         // Add our same-line elements after the search entry | ||||
|         this._overview.add(this._group, { y_fill: true, | ||||
|                                           expand: true }); | ||||
|  | ||||
|         // Then account for message tray | ||||
|         this._messageTrayGhost = new St.Bin({ style_class: 'message-tray-summary', | ||||
|                                               reactive: false, | ||||
|                                               opacity: 0, | ||||
|                                               x_fill: true, | ||||
|                                               y_fill: true }); | ||||
|         this._overview.add_actor(this._messageTrayGhost); | ||||
|  | ||||
|         Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout)); | ||||
|         this._relayout(); | ||||
|     }, | ||||
|  | ||||
|     addSearchProvider: function(provider) { | ||||
|         this.viewSelector.addSearchProvider(provider); | ||||
|         this._viewSelector.addSearchProvider(provider); | ||||
|     }, | ||||
|  | ||||
|     removeSearchProvider: function(provider) { | ||||
|         this.viewSelector.removeSearchProvider(provider); | ||||
|         this._viewSelector.removeSearchProvider(provider); | ||||
|     }, | ||||
|  | ||||
|     // | ||||
| @@ -293,22 +277,18 @@ const Overview = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onDragBegin: function() { | ||||
|         this._inXdndDrag = true; | ||||
|  | ||||
|         DND.addDragMonitor(this._dragMonitor); | ||||
|         // Remember the workspace we started from | ||||
|         this._lastActiveWorkspaceIndex = global.screen.get_active_workspace_index(); | ||||
|     }, | ||||
|  | ||||
|     _onDragEnd: function(time) { | ||||
|         this._inXdndDrag = false; | ||||
|  | ||||
|         // In case the drag was canceled while in the overview | ||||
|         // we have to go back to where we started and hide | ||||
|         // the overview | ||||
|         if (this._shown) { | ||||
|         if (this._shownTemporarily)  { | ||||
|             global.screen.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time); | ||||
|             this.hide(); | ||||
|             this.hideTemporarily(); | ||||
|         } | ||||
|         this._resetWindowSwitchTimeout(); | ||||
|         this._lastHoveredWindow = null; | ||||
| @@ -353,25 +333,17 @@ const Overview = new Lang.Class({ | ||||
|             this._lastHoveredWindow = dragEvent.targetActor._delegate.metaWindow; | ||||
|             this._windowSwitchTimeoutId = Mainloop.timeout_add(DND_WINDOW_SWITCH_TIMEOUT, | ||||
|                                             Lang.bind(this, function() { | ||||
|                                                 this._windowSwitchTimeoutId = 0; | ||||
|                                                 this._needsFakePointerEvent = true; | ||||
|                                                 Main.activateWindow(dragEvent.targetActor._delegate.metaWindow, | ||||
|                                                                     this._windowSwitchTimestamp); | ||||
|                                                 this.hide(); | ||||
|                                                 this.hideTemporarily(); | ||||
|                                                 this._lastHoveredWindow = null; | ||||
|                                                 return GLib.SOURCE_REMOVE; | ||||
|                                             })); | ||||
|             GLib.Source.set_name_by_id(this._windowSwitchTimeoutId, '[gnome-shell] Main.activateWindow'); | ||||
|         } | ||||
|  | ||||
|         return DND.DragMotionResult.CONTINUE; | ||||
|     }, | ||||
|  | ||||
|     _onScrollEvent: function(actor, event) { | ||||
|         this.emit('scroll-event', event); | ||||
|         return Clutter.EVENT_PROPAGATE; | ||||
|     }, | ||||
|  | ||||
|     addAction: function(action) { | ||||
|         if (this.isDummy) | ||||
|             return; | ||||
| @@ -401,12 +373,16 @@ const Overview = new Lang.Class({ | ||||
|         // when it is next shown. | ||||
|         this.hide(); | ||||
|  | ||||
|         let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex); | ||||
|         let primary = Main.layoutManager.primaryMonitor; | ||||
|  | ||||
|         this._coverPane.set_position(0, workArea.y); | ||||
|         this._coverPane.set_size(workArea.width, workArea.height); | ||||
|         let contentY = Main.panel.actor.height; | ||||
|         let contentHeight = primary.height - contentY - Main.messageTray.actor.height; | ||||
|  | ||||
|         this._updateBackgrounds(); | ||||
|         this._overview.set_position(primary.x, primary.y); | ||||
|         this._overview.set_size(primary.width, primary.height); | ||||
|  | ||||
|         this._coverPane.set_position(0, contentY); | ||||
|         this._coverPane.set_size(primary.width, contentHeight); | ||||
|     }, | ||||
|  | ||||
|     _onRestacked: function() { | ||||
| @@ -421,9 +397,10 @@ const Overview = new Lang.Class({ | ||||
|         this.emit('windows-restacked', stackIndices); | ||||
|     }, | ||||
|  | ||||
|     //// Public methods //// | ||||
|  | ||||
|     beginItemDrag: function(source) { | ||||
|         this.emit('item-drag-begin'); | ||||
|         this._inDrag = true; | ||||
|     }, | ||||
|  | ||||
|     cancelledItemDrag: function(source) { | ||||
| @@ -432,26 +409,33 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|     endItemDrag: function(source) { | ||||
|         this.emit('item-drag-end'); | ||||
|         this._inDrag = false; | ||||
|     }, | ||||
|  | ||||
|     beginWindowDrag: function(window) { | ||||
|         this.emit('window-drag-begin', window); | ||||
|         this._inDrag = true; | ||||
|     beginWindowDrag: function(source) { | ||||
|         this.emit('window-drag-begin'); | ||||
|     }, | ||||
|  | ||||
|     cancelledWindowDrag: function(window) { | ||||
|         this.emit('window-drag-cancelled', window); | ||||
|     cancelledWindowDrag: function(source) { | ||||
|         this.emit('window-drag-cancelled'); | ||||
|     }, | ||||
|  | ||||
|     endWindowDrag: function(window) { | ||||
|         this.emit('window-drag-end', window); | ||||
|         this._inDrag = false; | ||||
|     endWindowDrag: function(source) { | ||||
|         this.emit('window-drag-end'); | ||||
|     }, | ||||
|  | ||||
|     focusSearch: function() { | ||||
|         this.show(); | ||||
|         this._searchEntry.grab_key_focus(); | ||||
|     // show: | ||||
|     // | ||||
|     // Animates the overview visible and grabs mouse and keyboard input | ||||
|     show : function() { | ||||
|         if (this.isDummy) | ||||
|             return; | ||||
|         if (this._shown) | ||||
|             return; | ||||
|         this._shown = true; | ||||
|         this._syncInputMode(); | ||||
|         if (!this._modal) | ||||
|             return; | ||||
|         this._animateVisible(); | ||||
|     }, | ||||
|  | ||||
|     fadeInDesktop: function() { | ||||
| @@ -464,13 +448,8 @@ const Overview = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     fadeOutDesktop: function() { | ||||
|         if (!this._desktopFade.get_n_children()) { | ||||
|             let clone = this._getDesktopClone(); | ||||
|             if (!clone) | ||||
|                 return; | ||||
|  | ||||
|             this._desktopFade.add_child(clone); | ||||
|         } | ||||
|         if (!this._desktopFade.child) | ||||
|             this._desktopFade.child = this._getDesktopClone(); | ||||
|  | ||||
|         this._desktopFade.opacity = 255; | ||||
|         this._desktopFade.show(); | ||||
| @@ -481,108 +460,64 @@ const Overview = new Lang.Class({ | ||||
|                          }); | ||||
|     }, | ||||
|  | ||||
|     // 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; | ||||
|     }, | ||||
|  | ||||
|     _syncGrab: function() { | ||||
|         // We delay grab changes during animation so that when removing the | ||||
|         // overview we don't have a problem with the release of a press/release | ||||
|         // going to an application. | ||||
|         if (this.animationInProgress) | ||||
|             return true; | ||||
|  | ||||
|         if (this._shown) { | ||||
|             let shouldBeModal = !this._inXdndDrag; | ||||
|             if (shouldBeModal) { | ||||
|                 if (!this._modal) { | ||||
|                     if (Main.pushModal(this._overview, | ||||
|                                        { keybindingMode: Shell.KeyBindingMode.OVERVIEW })) { | ||||
|                         this._modal = true; | ||||
|                     } else { | ||||
|                         this.hide(); | ||||
|                         return false; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             if (this._modal) { | ||||
|                 Main.popModal(this._overview); | ||||
|                 this._modal = false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     // show: | ||||
|     // | ||||
|     // Animates the overview visible and grabs mouse and keyboard input | ||||
|     show: function() { | ||||
|         if (this.isDummy) | ||||
|             return; | ||||
|         if (this._shown) | ||||
|             return; | ||||
|         this._shown = true; | ||||
|  | ||||
|         if (!this._syncGrab()) | ||||
|             return; | ||||
|  | ||||
|         Main.layoutManager.showOverview(); | ||||
|         this._animateVisible(); | ||||
|     }, | ||||
|  | ||||
|  | ||||
|     _animateVisible: function() { | ||||
|         if (this.visible || this.animationInProgress) | ||||
|             return; | ||||
|  | ||||
|         this.visible = true; | ||||
|         this.animationInProgress = true; | ||||
|         this.visibleTarget = true; | ||||
|         this._activationTime = Date.now() / 1000; | ||||
|  | ||||
|         // All the the actors in the window group are completely obscured, | ||||
|         // hiding the group holding them while the Overview is displayed greatly | ||||
|         // increases performance of the Overview especially when there are many | ||||
|         // windows visible. | ||||
|         // | ||||
|         // If we switched to displaying the actors in the Overview rather than | ||||
|         // clones of them, this would obviously no longer be necessary. | ||||
|         // | ||||
|         // Disable unredirection while in the overview | ||||
|         Meta.disable_unredirect_for_screen(global.screen); | ||||
|         this.viewSelector.show(); | ||||
|         global.window_group.hide(); | ||||
|         this._overview.show(); | ||||
|         this._background.show(); | ||||
|         this._viewSelector.show(); | ||||
|  | ||||
|         this._stack.opacity = 0; | ||||
|         Tweener.addTween(this._stack, | ||||
|         this._overview.opacity = 0; | ||||
|         Tweener.addTween(this._overview, | ||||
|                          { opacity: 255, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            time: ANIMATION_TIME, | ||||
|                            onComplete: this._showDone, | ||||
|                            onCompleteScope: this | ||||
|                          }); | ||||
|         this._shadeBackgrounds(); | ||||
|  | ||||
|         Tweener.addTween(this._background, | ||||
|                          { dim_factor: 0.8, | ||||
|                            time: ANIMATION_TIME, | ||||
|                            transition: 'easeOutQuad' | ||||
|                          }); | ||||
|  | ||||
|         this._coverPane.raise_top(); | ||||
|         this._coverPane.show(); | ||||
|         this.emit('showing'); | ||||
|     }, | ||||
|  | ||||
|     _showDone: function() { | ||||
|         this.animationInProgress = false; | ||||
|         this._desktopFade.hide(); | ||||
|         this._coverPane.hide(); | ||||
|     // 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; | ||||
|  | ||||
|         this.emit('shown'); | ||||
|         // Handle any calls to hide* while we were showing | ||||
|         if (!this._shown) | ||||
|             this._animateNotVisible(); | ||||
|         if (this._shownTemporarily) | ||||
|             return; | ||||
|  | ||||
|         this._syncGrab(); | ||||
|         global.sync_pointer(); | ||||
|         this._syncInputMode(); | ||||
|         this._animateVisible(); | ||||
|         this._shownTemporarily = true; | ||||
|     }, | ||||
|  | ||||
|     // hide: | ||||
| @@ -595,72 +530,28 @@ const Overview = new Lang.Class({ | ||||
|         if (!this._shown) | ||||
|             return; | ||||
|  | ||||
|         let event = Clutter.get_current_event(); | ||||
|         if (event) { | ||||
|             let type = event.type(); | ||||
|             let button = (type == Clutter.EventType.BUTTON_PRESS || | ||||
|                           type == Clutter.EventType.BUTTON_RELEASE); | ||||
|             let ctrl = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0; | ||||
|             if (button && ctrl) | ||||
|                 return; | ||||
|         } | ||||
|  | ||||
|         this._animateNotVisible(); | ||||
|         if (!this._shownTemporarily) | ||||
|             this._animateNotVisible(); | ||||
|  | ||||
|         this._shown = false; | ||||
|         this._syncGrab(); | ||||
|         this._syncInputMode(); | ||||
|     }, | ||||
|  | ||||
|  | ||||
|     _animateNotVisible: function() { | ||||
|         if (!this.visible || this.animationInProgress) | ||||
|     // hideTemporarily: | ||||
|     // | ||||
|     // Reverses the effect of showTemporarily() | ||||
|     hideTemporarily: function() { | ||||
|         if (this.isDummy) | ||||
|             return; | ||||
|  | ||||
|         this.animationInProgress = true; | ||||
|         this.visibleTarget = false; | ||||
|         if (!this._shownTemporarily) | ||||
|             return; | ||||
|  | ||||
|         this.viewSelector.animateFromOverview(); | ||||
|         if (!this._shown) | ||||
|             this._animateNotVisible(); | ||||
|  | ||||
|         // Make other elements fade out. | ||||
|         Tweener.addTween(this._stack, | ||||
|                          { opacity: 0, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            time: ANIMATION_TIME, | ||||
|                            onComplete: this._hideDone, | ||||
|                            onCompleteScope: this | ||||
|                          }); | ||||
|         this._unshadeBackgrounds(); | ||||
|  | ||||
|         this._coverPane.raise_top(); | ||||
|         this._coverPane.show(); | ||||
|         this.emit('hiding'); | ||||
|     }, | ||||
|  | ||||
|     _hideDone: function() { | ||||
|         // Re-enable unredirection | ||||
|         Meta.enable_unredirect_for_screen(global.screen); | ||||
|  | ||||
|         this.viewSelector.hide(); | ||||
|         this._desktopFade.hide(); | ||||
|         this._coverPane.hide(); | ||||
|  | ||||
|         this.visible = false; | ||||
|         this.animationInProgress = false; | ||||
|  | ||||
|         this.emit('hidden'); | ||||
|         // Handle any calls to show* while we were hiding | ||||
|         if (this._shown) | ||||
|             this._animateVisible(); | ||||
|         else | ||||
|             Main.layoutManager.hideOverview(); | ||||
|  | ||||
|         this._syncGrab(); | ||||
|  | ||||
|         // Fake a pointer event if requested | ||||
|         if (this._needsFakePointerEvent) { | ||||
|             this._fakePointerEvent(); | ||||
|             this._needsFakePointerEvent = false; | ||||
|         } | ||||
|         this._shownTemporarily = false; | ||||
|         this._syncInputMode(); | ||||
|     }, | ||||
|  | ||||
|     toggle: function() { | ||||
| @@ -673,8 +564,111 @@ const Overview = new Lang.Class({ | ||||
|             this.show(); | ||||
|     }, | ||||
|  | ||||
|     getShowAppsButton: function() { | ||||
|         return this._dash.showAppsButton; | ||||
|     //// Private methods //// | ||||
|  | ||||
|     _syncInputMode: function() { | ||||
|         // We delay input mode changes during animation so that when removing the | ||||
|         // overview we don't have a problem with the release of a press/release | ||||
|         // going to an application. | ||||
|         if (this.animationInProgress) | ||||
|             return; | ||||
|  | ||||
|         if (this._shown) { | ||||
|             if (!this._modal) { | ||||
|                 if (Main.pushModal(this._overview, | ||||
|                                    { keybindingMode: Main.KeybindingMode.OVERVIEW })) | ||||
|                     this._modal = true; | ||||
|                 else | ||||
|                     this.hide(); | ||||
|             } | ||||
|         } else if (this._shownTemporarily) { | ||||
|             if (this._modal) { | ||||
|                 Main.popModal(this._overview); | ||||
|                 this._modal = false; | ||||
|             } | ||||
|             global.stage_input_mode = Shell.StageInputMode.FULLSCREEN; | ||||
|         } else { | ||||
|             if (this._modal) { | ||||
|                 Main.popModal(this._overview); | ||||
|                 this._modal = false; | ||||
|             } | ||||
|             else if (global.stage_input_mode == Shell.StageInputMode.FULLSCREEN) | ||||
|                 global.stage_input_mode = Shell.StageInputMode.NORMAL; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _animateNotVisible: function() { | ||||
|         if (!this.visible || this.animationInProgress) | ||||
|             return; | ||||
|  | ||||
|         this.animationInProgress = true; | ||||
|         this._hideInProgress = true; | ||||
|  | ||||
|         this._viewSelector.zoomFromOverview(); | ||||
|  | ||||
|         // Make other elements fade out. | ||||
|         Tweener.addTween(this._overview, | ||||
|                          { opacity: 0, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            time: ANIMATION_TIME, | ||||
|                            onComplete: this._hideDone, | ||||
|                            onCompleteScope: this | ||||
|                          }); | ||||
|  | ||||
|         Tweener.addTween(this._background, | ||||
|                          { dim_factor: 1.0, | ||||
|                            time: ANIMATION_TIME, | ||||
|                            transition: 'easeOutQuad' | ||||
|                          }); | ||||
|  | ||||
|         this._coverPane.raise_top(); | ||||
|         this._coverPane.show(); | ||||
|         this.emit('hiding'); | ||||
|     }, | ||||
|  | ||||
|     _showDone: function() { | ||||
|         this.animationInProgress = false; | ||||
|         this._desktopFade.hide(); | ||||
|         this._coverPane.hide(); | ||||
|  | ||||
|         this.emit('shown'); | ||||
|         // Handle any calls to hide* while we were showing | ||||
|         if (!this._shown && !this._shownTemporarily) | ||||
|             this._animateNotVisible(); | ||||
|  | ||||
|         this._syncInputMode(); | ||||
|         global.sync_pointer(); | ||||
|     }, | ||||
|  | ||||
|     _hideDone: function() { | ||||
|         // Re-enable unredirection | ||||
|         Meta.enable_unredirect_for_screen(global.screen); | ||||
|  | ||||
|         global.window_group.show(); | ||||
|  | ||||
|         this._viewSelector.hide(); | ||||
|         this._desktopFade.hide(); | ||||
|         this._background.hide(); | ||||
|         this._overview.hide(); | ||||
|  | ||||
|         this.visible = false; | ||||
|         this.animationInProgress = false; | ||||
|         this._hideInProgress = false; | ||||
|  | ||||
|         this._coverPane.hide(); | ||||
|  | ||||
|         this.emit('hidden'); | ||||
|         // Handle any calls to show* while we were hiding | ||||
|         if (this._shown || this._shownTemporarily) | ||||
|             this._animateVisible(); | ||||
|  | ||||
|         this._syncInputMode(); | ||||
|  | ||||
|         // Fake a pointer event if requested | ||||
|         if (this._needsFakePointerEvent) { | ||||
|             this._fakePointerEvent(); | ||||
|             this._needsFakePointerEvent = false; | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(Overview.prototype); | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user