diff --git a/.gnome-shell.metadata b/.gnome-shell.metadata new file mode 100644 index 0000000..a502ece --- /dev/null +++ b/.gnome-shell.metadata @@ -0,0 +1 @@ +2674fb6d75f5a9726bd8be2b9f675f391c7712cb SOURCES/gnome-shell-3.8.4.tar.xz diff --git a/README.md b/README.md deleted file mode 100644 index 0e7897f..0000000 --- a/README.md +++ /dev/null @@ -1,5 +0,0 @@ -The master branch has no content - -Look at the c7 branch if you are working with CentOS-7, or the c4/c5/c6 branch for CentOS-4, 5 or 6 - -If you find this file in a distro specific branch, it means that no content has been checked in yet diff --git a/SOURCES/0001-catch-more-errors-on-extensions-enable-and-disable.patch b/SOURCES/0001-catch-more-errors-on-extensions-enable-and-disable.patch new file mode 100644 index 0000000..03ef93f --- /dev/null +++ b/SOURCES/0001-catch-more-errors-on-extensions-enable-and-disable.patch @@ -0,0 +1,132 @@ +From fdc15b08eb034ea2be03c16510475891516ff6f2 Mon Sep 17 00:00:00 2001 +From: Sebastien Lafargue +Date: Fri, 25 Oct 2013 15:28:11 +0200 +Subject: [PATCH] catch more errors on extensions enable() and disable() + +https://bugzilla.gnome.org/show_bug.cgi?id=688331 +--- + js/ui/extensionSystem.js | 50 +++++++++++++++++++++++++++--------------------- + 1 file changed, 28 insertions(+), 22 deletions(-) + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 8c12e97..8a75392 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -76,7 +76,11 @@ function disableExtension(uuid) { + theme.unload_stylesheet(extension.stylesheet.get_path()); + } + +- extension.stateObj.disable(); ++ try { ++ extension.stateObj.disable(); ++ } catch(e) { ++ logExtensionError(uuid, e); ++ } + + for (let i = 0; i < order.length; i++) { + let uuid = order[i]; +@@ -89,8 +93,10 @@ function disableExtension(uuid) { + + extensionOrder.splice(orderIdx, 1); + +- extension.state = ExtensionState.DISABLED; +- _signals.emit('extension-state-changed', extension); ++ if ( extension.state != ExtensionState.ERROR ) { ++ extension.state = ExtensionState.DISABLED; ++ _signals.emit('extension-state-changed', extension); ++ } + } + + function enableExtension(uuid) { +@@ -117,10 +123,15 @@ function enableExtension(uuid) { + } + } + +- extension.stateObj.enable(); +- +- extension.state = ExtensionState.ENABLED; +- _signals.emit('extension-state-changed', extension); ++ try { ++ extension.stateObj.enable(); ++ extension.state = ExtensionState.ENABLED; ++ _signals.emit('extension-state-changed', extension); ++ return; ++ } catch(e) { ++ logExtensionError(uuid, e); ++ return; ++ } + } + + function logExtensionError(uuid, error) { +@@ -150,7 +161,8 @@ function loadExtension(extension) { + } else { + let enabled = enabledExtensions.indexOf(extension.uuid) != -1; + if (enabled) { +- initExtension(extension.uuid); ++ if (!initExtension(extension.uuid)) ++ return; + if (extension.state == ExtensionState.DISABLED) + enableExtension(extension.uuid); + } else { +@@ -205,7 +217,12 @@ function initExtension(uuid) { + extensionModule = extension.imports.extension; + + if (extensionModule.init) { +- extensionState = extensionModule.init(extension); ++ try { ++ extensionState = extensionModule.init(extension); ++ } catch(e) { ++ logExtensionError(uuid, e); ++ return false; ++ } + } + + if (!extensionState) +@@ -214,6 +231,7 @@ function initExtension(uuid) { + + extension.state = ExtensionState.DISABLED; + _signals.emit('extension-loaded', uuid); ++ return true; + } + + function getEnabledExtensions() { +@@ -235,11 +253,7 @@ function onEnabledExtensionsChanged() { + newEnabledExtensions.filter(function(uuid) { + return enabledExtensions.indexOf(uuid) == -1; + }).forEach(function(uuid) { +- try { + enableExtension(uuid); +- } catch(e) { +- logExtensionError(uuid, e); +- } + }); + + // Find and disable all the newly disabled extensions: UUIDs found in the +@@ -247,11 +261,7 @@ function onEnabledExtensionsChanged() { + enabledExtensions.filter(function(item) { + return newEnabledExtensions.indexOf(item) == -1; + }).forEach(function(uuid) { +- try { + disableExtension(uuid); +- } catch(e) { +- logExtensionError(uuid, e); +- } + }); + + enabledExtensions = newEnabledExtensions; +@@ -263,11 +273,7 @@ function _loadExtensions() { + + let finder = new ExtensionUtils.ExtensionFinder(); + finder.connect('extension-found', function(signals, extension) { +- try { +- loadExtension(extension); +- } catch(e) { +- logExtensionError(extension.uuid, e); +- } ++ loadExtension(extension); + }); + finder.scanExtensions(); + } +-- +1.8.4.2 + diff --git a/SOURCES/0001-main-Actually-respect-hasWorkspaces.patch b/SOURCES/0001-main-Actually-respect-hasWorkspaces.patch new file mode 100644 index 0000000..473d5a7 --- /dev/null +++ b/SOURCES/0001-main-Actually-respect-hasWorkspaces.patch @@ -0,0 +1,82 @@ +From 9472b2a2d951be19df5069cca073ab7e7085c769 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 1 Nov 2013 00:49:22 +0100 +Subject: [PATCH] main: Actually respect hasWorkspaces + +We've long had the hasWorkspaces property, but it doesn't seem like +it was ever used. Implement it so that we don't have workspaces in +initial-setup mode. +--- + js/ui/main.js | 2 +- + js/ui/windowManager.js | 14 +++++++++++++- + 2 files changed, 14 insertions(+), 2 deletions(-) + +diff --git a/js/ui/main.js b/js/ui/main.js +index bd5dc47..2f0e28a 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -233,7 +233,7 @@ function _checkWorkspaces() { + let i; + let emptyWorkspaces = []; + +- if (!Meta.prefs_get_dynamic_workspaces()) { ++ if (!sessionMode.hasWorkspaces || !Meta.prefs_get_dynamic_workspaces()) { + _checkWorkspacesId = 0; + return false; + } +diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js +index b8ea36b..e4de557 100644 +--- a/js/ui/windowManager.js ++++ b/js/ui/windowManager.js +@@ -568,7 +568,7 @@ const WindowManager = new Lang.Class({ + }, + + _switchWorkspace : function(shellwm, from, to, direction) { +- if (!this._shouldAnimate()) { ++ if (!Main.sessionMode.hasWorkspaces || !this._shouldAnimate()) { + shellwm.completed_switch_workspace(); + return; + } +@@ -721,6 +721,9 @@ const WindowManager = new Lang.Class({ + }, + + _showWorkspaceSwitcher : function(display, screen, window, binding) { ++ if (!Main.sessionMode.hasWorkspaces) ++ return; ++ + if (screen.n_workspaces == 1) + return; + +@@ -738,6 +741,9 @@ const WindowManager = new Lang.Class({ + else + newWs = this.actionMoveWindow(window, direction); + ++ if (!newWs) ++ return; ++ + if (!Main.overview.visible) { + if (this._workspaceSwitcherPopup == null) { + this._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup(); +@@ -750,6 +756,9 @@ const WindowManager = new Lang.Class({ + }, + + actionMoveWorkspace: function(direction) { ++ if (!Main.sessionMode.hasWorkspaces) ++ return null; ++ + let activeWorkspace = global.screen.get_active_workspace(); + let toActivate = activeWorkspace.get_neighbor(direction); + +@@ -760,6 +769,9 @@ const WindowManager = new Lang.Class({ + }, + + actionMoveWindow: function(window, direction) { ++ if (!Main.sessionMode.hasWorkspaces) ++ return null; ++ + let activeWorkspace = global.screen.get_active_workspace(); + let toActivate = activeWorkspace.get_neighbor(direction); + +-- +1.8.3.1 + diff --git a/SOURCES/0001-network-Do-not-use-timestamp-to-identify-connections.patch b/SOURCES/0001-network-Do-not-use-timestamp-to-identify-connections.patch new file mode 100644 index 0000000..f69913a --- /dev/null +++ b/SOURCES/0001-network-Do-not-use-timestamp-to-identify-connections.patch @@ -0,0 +1,31 @@ +From 95550e5a90d65b049dabcd31b7168612c9802327 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 4 Nov 2013 20:09:45 +0100 +Subject: [PATCH] network: Do not use timestamp to identify connections + +Connections have UUIDs to uniquely identify connections, bringing +in timestamps just messes things up unnecessarily (and was dropped +upstream in 3.10). +Patch by Dan Williams. +--- + js/ui/status/network.js | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/js/ui/status/network.js b/js/ui/status/network.js +index bfd07fd..0a8871c 100644 +--- a/js/ui/status/network.js ++++ b/js/ui/status/network.js +@@ -231,9 +231,7 @@ const NMConnectionBased = new Lang.Class({ + if (exists) { + let existing = this._connections[pos]; + +- // Check if connection changed name or id +- similar = existing.name == connection.get_id() && +- existing.timestamp == connection._timestamp; ++ similar = existing.name == connection.get_id(); + } + + if (exists && valid && similar) { +-- +1.8.4.2 + diff --git a/SOURCES/0001-network-Don-t-disable-switch-in-wifi-section-while-c.patch b/SOURCES/0001-network-Don-t-disable-switch-in-wifi-section-while-c.patch new file mode 100644 index 0000000..d09ec51 --- /dev/null +++ b/SOURCES/0001-network-Don-t-disable-switch-in-wifi-section-while-c.patch @@ -0,0 +1,113 @@ +From aaecd03d5f14fe3332659ab4997b39448814abcd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 5 Sep 2013 15:37:12 +0200 +Subject: [PATCH] network: Don't disable switch in wifi section while + connecting + +Establishing a wireless connection may take some time during which +users may change their mind and want to disable it (again). +This is currently impossible, as we overlay the device status if +the device is not fully (dis)connected. Instead, display this status +on the active AP and leave the switch alone. +--- + js/ui/status/network.js | 57 ++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 56 insertions(+), 1 deletion(-) + +diff --git a/js/ui/status/network.js b/js/ui/status/network.js +index ca4e462..370afa0 100644 +--- a/js/ui/status/network.js ++++ b/js/ui/status/network.js +@@ -1,4 +1,5 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++const Clutter = imports.gi.Clutter; + const GLib = imports.gi.GLib; + const GObject = imports.gi.GObject; + const Gio = imports.gi.Gio; +@@ -122,8 +123,10 @@ const NMNetworkMenuItem = new Lang.Class({ + this._label = new St.Label({ text: title }); + this.actor.label_actor = this._label; + this.addActor(this._label); ++ this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() }); ++ this.addActor(this._stack, { align: St.Align.END }); + this._icons = new St.BoxLayout({ style_class: 'nm-menu-item-icons' }); +- this.addActor(this._icons, { align: St.Align.END }); ++ this._stack.add_actor(this._icons); + + this._signalIcon = new St.Icon({ icon_name: this._getIcon(), + style_class: 'popup-menu-icon' }); +@@ -134,6 +137,8 @@ const NMNetworkMenuItem = new Lang.Class({ + this.bestAP._secType != NMAccessPointSecurity.NONE) + this._secureIcon.icon_name = 'network-wireless-encrypted-symbolic'; + this._icons.add_actor(this._secureIcon); ++ this._statusLabel = new St.Label({ visible: false }); ++ this._stack.add_actor(this._statusLabel, { align: St.Align.END }); + }, + + updateBestAP: function(ap) { +@@ -141,6 +146,14 @@ const NMNetworkMenuItem = new Lang.Class({ + this._signalIcon.icon_name = this._getIcon(); + }, + ++ setStatus: function(status) { ++ let visible = status != null; ++ this._statusLabel.visible = visible; ++ this._icons.visible = !visible; ++ ++ this._statusLabel.text = status; ++ }, ++ + _getIcon: function() { + if (this.bestAP.mode == NM80211Mode.ADHOC) + return 'network-workgroup-symbolic'; +@@ -1372,6 +1385,48 @@ const NMDeviceWireless = new Lang.Class({ + 'network-wireless-connected-symbolic', + { reactive: false }); + this._activeConnectionItem.setShowDot(true); ++ ++ if (this._activeNetwork) ++ this._activeConnectionItem.setStatus(this.getActiveStatusLabel()); ++ }, ++ ++ getStatusLabel: function() { ++ if (!this.device) ++ return null; ++ ++ switch(this.device.state) { ++ case NetworkManager.DeviceState.DEACTIVATING: ++ case NetworkManager.DeviceState.PREPARE: ++ case NetworkManager.DeviceState.CONFIG: ++ case NetworkManager.DeviceState.IP_CONFIG: ++ case NetworkManager.DeviceState.IP_CHECK: ++ case NetworkManager.DeviceState.SECONDARIES: ++ case NetworkManager.DeviceState.NEED_AUTH: ++ return null; ++ default: ++ return this.parent(); ++ } ++ }, ++ ++ getActiveStatusLabel: function() { ++ if (!this.device) ++ return null; ++ ++ switch(this.device.state) { ++ case NetworkManager.DeviceState.DEACTIVATING: ++ return _("disconnecting..."); ++ case NetworkManager.DeviceState.PREPARE: ++ case NetworkManager.DeviceState.CONFIG: ++ case NetworkManager.DeviceState.IP_CONFIG: ++ case NetworkManager.DeviceState.IP_CHECK: ++ case NetworkManager.DeviceState.SECONDARIES: ++ return _("connecting..."); ++ case NetworkManager.DeviceState.NEED_AUTH: ++ /* Translators: this is for network connections that require some kind of key or password */ ++ return _("authentication required"); ++ default: ++ return null; ++ } + }, + + _createAutomaticConnection: function(apObj) { +-- +1.8.4.2 + diff --git a/SOURCES/0001-popupMenu-Fix-removing-the-active-menu-from-PopupMen.patch b/SOURCES/0001-popupMenu-Fix-removing-the-active-menu-from-PopupMen.patch new file mode 100644 index 0000000..a2bb375 --- /dev/null +++ b/SOURCES/0001-popupMenu-Fix-removing-the-active-menu-from-PopupMen.patch @@ -0,0 +1,30 @@ +From 820f689f7789be074595ce470d39828db771ed66 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 6 Nov 2013 23:40:22 +0100 +Subject: [PATCH] popupMenu: Fix removing the active menu from PopupMenuManager + +Commit b42af9aa991eba5 changed the parameter list of _closeMenu() +to account for changes in the GrabHelper ungrab mechanism, but +didn't update other callers. + +https://bugzilla.gnome.org/show_bug.cgi?id=709806 +--- + js/ui/popupMenu.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js +index 9456b79..81d8ab9 100644 +--- a/js/ui/popupMenu.js ++++ b/js/ui/popupMenu.js +@@ -2106,7 +2106,7 @@ const PopupMenuManager = new Lang.Class({ + + removeMenu: function(menu) { + if (menu == this.activeMenu) +- this._closeMenu(menu); ++ this._closeMenu(false, menu); + + let position = this._findMenu(menu); + if (position == -1) // not a menu we manage +-- +1.8.4.2 + diff --git a/SOURCES/0001-screenshot-Extend-ScreenshotArea-parameter-validatio.patch b/SOURCES/0001-screenshot-Extend-ScreenshotArea-parameter-validatio.patch new file mode 100644 index 0000000..b9b86e4 --- /dev/null +++ b/SOURCES/0001-screenshot-Extend-ScreenshotArea-parameter-validatio.patch @@ -0,0 +1,35 @@ +From 3430f0cb832a489ce8dee1e9294ca7a724f367af Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 4 Nov 2013 11:14:44 +0100 +Subject: [PATCH 1/3] screenshot: Extend ScreenshotArea parameter validation + +We currently only ensure that width and height are positive, so it +is still possible to pass in values that don't make any sense at all +(which may even result in a crash when exceeding limits imposed by +X11). +There is nothing to screenshot outside the actual screen area, so +restrict the parameters to that. + +https://bugzilla.gnome.org/show_bug.cgi?id=699752 +--- + js/ui/screenshot.js | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js +index 2219a89..3c5c831 100644 +--- a/js/ui/screenshot.js ++++ b/js/ui/screenshot.js +@@ -76,7 +76,9 @@ const ScreenshotService = new Lang.Class({ + + ScreenshotAreaAsync : function (params, invocation) { + let [x, y, width, height, flash, filename, callback] = params; +- if (height <= 0 || width <= 0) { ++ if (x < 0 || y < 0 || ++ width <= 0 || height <= 0 || ++ x + width > global.screen_width || y + height > global.screen_height) { + invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED, + "Invalid params"); + return; +-- +1.8.4.2 + diff --git a/SOURCES/0001-shellDBus-Add-a-DBus-method-to-load-a-single-extensi.patch b/SOURCES/0001-shellDBus-Add-a-DBus-method-to-load-a-single-extensi.patch new file mode 100644 index 0000000..0c239d7 --- /dev/null +++ b/SOURCES/0001-shellDBus-Add-a-DBus-method-to-load-a-single-extensi.patch @@ -0,0 +1,52 @@ +From 193ac75919f663122fd3a9ef3bd5342fffe79dde Mon Sep 17 00:00:00 2001 +From: Rui Matos +Date: Fri, 8 Nov 2013 11:36:04 +0100 +Subject: [PATCH 1/2] shellDBus: Add a DBus method to load a single extension + +This allows e.g. gnome-tweak-tool to install an extension from a zip +file and load it into the running shell. +--- + js/ui/shellDBus.js | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js +index 84597fd..d3a3a05 100644 +--- a/js/ui/shellDBus.js ++++ b/js/ui/shellDBus.js +@@ -266,6 +266,10 @@ const GnomeShellExtensionsIface = + + + ++ ++ ++ ++ + + ; + +@@ -365,6 +369,22 @@ const GnomeShellExtensions = new Lang.Class({ + ExtensionDownloader.checkForUpdates(); + }, + ++ LoadUserExtension: function(uuid) { ++ let extension = ExtensionUtils.extensions[uuid]; ++ if (extension) ++ return true; ++ ++ let dir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions', uuid])); ++ try { ++ extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER); ++ ExtensionSystem.loadExtension(extension); ++ } catch (e) { ++ log('Could not load user extension from %s'.format(dir.get_path())); ++ return false; ++ } ++ return true; ++ }, ++ + ShellVersion: Config.PACKAGE_VERSION, + + _extensionStateChanged: function(_, newState) { +-- +1.8.3.1 + diff --git a/SOURCES/0002-extensions-Add-a-SESSION_MODE-extension-type.patch b/SOURCES/0002-extensions-Add-a-SESSION_MODE-extension-type.patch new file mode 100644 index 0000000..da469ee --- /dev/null +++ b/SOURCES/0002-extensions-Add-a-SESSION_MODE-extension-type.patch @@ -0,0 +1,42 @@ +From 5389581bdba7f5c84c4cd550b0a04f3854ef8bb9 Mon Sep 17 00:00:00 2001 +From: Rui Matos +Date: Fri, 8 Nov 2013 13:58:09 +0100 +Subject: [PATCH 2/2] extensions: Add a SESSION_MODE extension type + +This allows e.g. gnome-tweak-tool to present these extensions in a +different way since they can't be disabled. +--- + js/misc/extensionUtils.js | 3 ++- + js/ui/extensionSystem.js | 2 ++ + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/js/misc/extensionUtils.js b/js/misc/extensionUtils.js +index 7c6769a..a46929a 100644 +--- a/js/misc/extensionUtils.js ++++ b/js/misc/extensionUtils.js +@@ -15,7 +15,8 @@ const FileUtils = imports.misc.fileUtils; + + const ExtensionType = { + SYSTEM: 1, +- PER_USER: 2 ++ PER_USER: 2, ++ SESSION_MODE: 3 + }; + + // Maps uuid -> metadata object +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 8c12e97..ba5db46 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -274,6 +274,8 @@ function _loadExtensions() { + let finder = new ExtensionUtils.ExtensionFinder(); + finder.connect('extension-found', function(signals, extension) { + loadExtension(extension); ++ if (Main.sessionMode.enabledExtensions.indexOf(extension.uuid) != -1) ++ extension.type = ExtensionUtils.ExtensionType.SESSION_MODE; + }); + finder.scanExtensions(); + } +-- +1.8.3.1 + diff --git a/SOURCES/0002-screencast-Fix-disabling-screencasts-via-session-mod.patch b/SOURCES/0002-screencast-Fix-disabling-screencasts-via-session-mod.patch new file mode 100644 index 0000000..e9699d9 --- /dev/null +++ b/SOURCES/0002-screencast-Fix-disabling-screencasts-via-session-mod.patch @@ -0,0 +1,44 @@ +From 158fe31bb3cb1c38830681c864714bb31ee77c7e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 4 Nov 2013 14:06:33 +0100 +Subject: [PATCH 2/3] screencast: Fix disabling screencasts via session mode + +If screencasts are disabled, we return a DBus error, but still start +the recording happily - add early returns in that case. + +https://bugzilla.gnome.org/show_bug.cgi?id=699752 +--- + js/ui/screencast.js | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/js/ui/screencast.js b/js/ui/screencast.js +index 4a14933..7d8d767 100644 +--- a/js/ui/screencast.js ++++ b/js/ui/screencast.js +@@ -95,8 +95,10 @@ const ScreencastService = new Lang.Class({ + + ScreencastAsync: function(params, invocation) { + let returnValue = [false, '']; +- if (!Main.sessionMode.allowScreencast) ++ if (!Main.sessionMode.allowScreencast) { + invocation.return_value(GLib.Variant.new('(bs)', returnValue)); ++ return; ++ } + + let sender = invocation.get_sender(); + let recorder = this._ensureRecorderForSender(sender); +@@ -114,8 +116,10 @@ const ScreencastService = new Lang.Class({ + + ScreencastAreaAsync: function(params, invocation) { + let returnValue = [false, '']; +- if (!Main.sessionMode.allowScreencast) ++ if (!Main.sessionMode.allowScreencast) { + invocation.return_value(GLib.Variant.new('(bs)', returnValue)); ++ return; ++ } + + let sender = invocation.get_sender(); + let recorder = this._ensureRecorderForSender(sender); +-- +1.8.4.2 + diff --git a/SOURCES/0003-screencast-Validate-parameters-of-ScreencastArea.patch b/SOURCES/0003-screencast-Validate-parameters-of-ScreencastArea.patch new file mode 100644 index 0000000..e562fd1 --- /dev/null +++ b/SOURCES/0003-screencast-Validate-parameters-of-ScreencastArea.patch @@ -0,0 +1,36 @@ +From 9712b5f8ce73aa40f74c33108cca12c35b50aee8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 4 Nov 2013 14:09:19 +0100 +Subject: [PATCH 3/3] screencast: Validate parameters of ScreencastArea + +... just as we do for screenshots. + +https://bugzilla.gnome.org/show_bug.cgi?id=699752 +--- + js/ui/screencast.js | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/js/ui/screencast.js b/js/ui/screencast.js +index 7d8d767..168f589 100644 +--- a/js/ui/screencast.js ++++ b/js/ui/screencast.js +@@ -127,6 +127,16 @@ const ScreencastService = new Lang.Class({ + if (!recorder.is_recording()) { + let [x, y, width, height, fileTemplate, options] = params; + ++ if (x < 0 || y < 0 || ++ width <= 0 || height <= 0 || ++ x + width > global.screen_width || ++ y + height > global.screen_height) { ++ invocation.return_error_literal(Gio.IOErrorEnum, ++ Gio.IOErrorEnum.CANCELLED, ++ "Invalid params"); ++ return; ++ } ++ + recorder.set_file_template(fileTemplate); + recorder.set_area(x, y, width, height); + this._applyOptionalParameters(recorder, options); +-- +1.8.4.2 + diff --git a/SOURCES/gdm-support-pre-authenticated-logins-from-oVirt.patch b/SOURCES/gdm-support-pre-authenticated-logins-from-oVirt.patch new file mode 100644 index 0000000..d2f91d3 --- /dev/null +++ b/SOURCES/gdm-support-pre-authenticated-logins-from-oVirt.patch @@ -0,0 +1,292 @@ +From 70c1cc7ed45ff4f6fa3cf0601c98ca40a168ac57 Mon Sep 17 00:00:00 2001 +From: Vinzenz Feenstra +Date: Thu, 10 Oct 2013 10:21:47 +0200 +Subject: [PATCH] gdm: support pre-authenticated logins from oVirt + +oVirt is software for managing medium-to-large scale deployments of +virtual machine guests across multiple hosts. It supports a feature +where users can authenticate with a central server and get +transparently connected to a guest system and then automatically get logged +into that guest to an associated user session. + +Guests using old versions of GDM support this single-sign-on capability +by means of a greeter plugin, using the old greeter's extension +API. + +This commit adds similar support to the gnome-shell based login screen. + +How it works: + +* The OVirtCredentialsManager singleton listens for + + 'org.ovirt.vdsm.Credentials.UserAuthenticated' + + D-Bus signal on the system bus from the + + 'org.ovirt.vdsm.Credentials' + + bus name. The service that provides that bus name is called + the oVirt guest agent. It is also responsible for interacting + with the the central server to get user credentials. + +* This UserAuthenticated signal passes, as a parameter, the a token + which needs to be passed through to the PAM service that is specifically + set up to integrate with the oVirt authentication architecture. + The singleton object keeps the token internally so it can be queried + later on. + +* The OVirtCredentialsManager emits a signal 'user-authenticated' on + it's object once the dbus signal is triggered + +* When the 'user-authenticated' signal is emitted, the login screen + tells GDM to start user verification using the PAM service. The + authentication stack of the service includes a PAM module + provided by oVirt that securely retrieves user credentials + from the oVirt guest agent. The PAM module then forwards those + credentials on to other modules in the stack so, e.g., + the user's gnome keyring can be automatically unlocked. + +* In case of the screen shield being visible, it also will react on that + 'user-authenticated' signal and lift the shield. + In that case the login screen will check on construction time if + the signal has already been triggered, and a token is available. + If a token is available it will immediately trigger the functionality + as described above. + +Upstream-Bug-Url: https://bugzilla.gnome.org/show_bug.cgi?id=702162 +Signed-off-by: Vinzenz Feenstra +Bug-Url: https://bugzilla.redhat.com/854712 +--- + js/Makefile.am | 1 + + js/gdm/authPrompt.js | 13 ++++++++--- + js/gdm/oVirt.js | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ + js/gdm/util.js | 31 ++++++++++++++++++++++++++ + js/ui/screenShield.js | 8 +++++++ + 5 files changed, 112 insertions(+), 3 deletions(-) + create mode 100644 js/gdm/oVirt.js + +diff --git a/js/Makefile.am b/js/Makefile.am +index 425b6fb..bfcc714 100644 +--- a/js/Makefile.am ++++ b/js/Makefile.am +@@ -21,6 +21,7 @@ nobase_dist_js_DATA = \ + gdm/batch.js \ + gdm/fingerprint.js \ + gdm/loginDialog.js \ ++ gdm/oVirt.js \ + gdm/powerMenu.js \ + gdm/realmd.js \ + gdm/util.js \ +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 2121f2e..ba66c42 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -59,6 +59,7 @@ const AuthPrompt = new Lang.Class({ + 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() { +@@ -219,6 +220,11 @@ const AuthPrompt = new Lang.Class({ + this.emit('prompted'); + }, + ++ _onOVirtUserAuthenticated: function() { ++ if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) ++ this.reset(); ++ }, ++ + _onSmartcardStatusChanged: function() { + this.smartcardDetected = this._userVerifier.smartcardDetected; + +@@ -443,10 +449,11 @@ const AuthPrompt = new Lang.Class({ + // The user is constant at the unlock screen, so it will immediately + // respond to the request with the username + beginRequestType = BeginRequestType.PROVIDE_USERNAME; +- } else if (this.smartcardDetected && +- this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME)) { ++ } else if (this._userVerifier.serviceIsForeground(GdmUtil.OVIRT_SERVICE_NAME) || ++ (this.smartcardDetected && ++ this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME))) { + // We don't need to know the username if the user preempted the login screen +- // with a smartcard. ++ // 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. +diff --git a/js/gdm/oVirt.js b/js/gdm/oVirt.js +new file mode 100644 +index 0000000..1a31f43 +--- /dev/null ++++ b/js/gdm/oVirt.js +@@ -0,0 +1,62 @@ ++// -*- 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 = ++ ++ ++ ++; ++ ++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; ++} +diff --git a/js/gdm/util.js b/js/gdm/util.js +index f421902..66852fd 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -10,6 +10,7 @@ const St = imports.gi.St; + + const Batch = imports.gdm.batch; + const Fprint = imports.gdm.fingerprint; ++const OVirt = imports.gdm.oVirt; + const Main = imports.ui.main; + const Params = imports.misc.params; + const ShellEntry = imports.ui.shellEntry; +@@ -19,6 +20,7 @@ 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; + +@@ -151,6 +153,14 @@ const ShellUserVerifier = new Lang.Class({ + this.reauthenticating = false; + + this._failCounter = 0; ++ ++ this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager(); ++ ++ if (this._oVirtCredentialsManager.hasToken()) ++ this._oVirtUserAuthenticated(this._oVirtCredentialsManager.getToken()); ++ ++ this._oVirtCredentialsManager.connect('user-authenticated', ++ Lang.bind(this, this._oVirtUserAuthenticated)); + }, + + begin: function(userName, hold) { +@@ -277,6 +287,11 @@ const ShellUserVerifier = new Lang.Class({ + })); + }, + ++ _oVirtUserAuthenticated: function(token) { ++ this._preemptingService = OVIRT_SERVICE_NAME; ++ this.emit('ovirt-user-authenticated'); ++ }, ++ + _checkForSmartcard: function() { + let smartcardDetected; + +@@ -455,6 +470,12 @@ const ShellUserVerifier = new Lang.Class({ + if (!this.serviceIsForeground(serviceName)) + 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'); + }, + +@@ -515,6 +536,16 @@ const ShellUserVerifier = new Lang.Class({ + }, + + _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 +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 1d296a2..ecc0bfa 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -17,6 +17,7 @@ const Background = imports.ui.background; + const GnomeSession = imports.misc.gnomeSession; + const Hash = imports.misc.hash; + const Layout = imports.ui.layout; ++const OVirt = imports.gdm.oVirt; + const LoginManager = imports.misc.loginManager; + const Lightbox = imports.ui.lightbox; + const Main = imports.ui.main; +@@ -514,6 +515,13 @@ const ScreenShield = new Lang.Class({ + this._liftShield(true, 0); + })); + ++ this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager(); ++ this._oVirtCredentialsManager.connect('user-authenticated', ++ Lang.bind(this, function() { ++ if (this._isLocked) ++ this._liftShield(true, 0); ++ })); ++ + this._inhibitor = null; + this._aboutToSuspend = false; + this._loginManager = LoginManager.getLoginManager(); +-- +1.8.3.1 + diff --git a/SOURCES/gnome-shell-favourite-apps-firefox.patch b/SOURCES/gnome-shell-favourite-apps-firefox.patch new file mode 100644 index 0000000..206880a --- /dev/null +++ b/SOURCES/gnome-shell-favourite-apps-firefox.patch @@ -0,0 +1,26 @@ +--- gnome-shell-3.1.91/data/org.gnome.shell.gschema.xml.in.in.firefox 2011-09-07 19:50:20.167547963 +0300 ++++ gnome-shell-3.1.91/data/org.gnome.shell.gschema.xml.in.in 2011-09-07 19:51:32.067106738 +0300 +@@ -31,7 +31,7 @@ + + + +- [ 'epiphany.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'libreoffice-writer.desktop', 'nautilus.desktop', 'gnome-documents.desktop' ] ++ [ 'firefox.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'libreoffice-writer.desktop', 'nautilus.desktop', 'gnome-documents.desktop' ] + <_summary>List of desktop file IDs for favorite applications + <_description> + The applications corresponding to these identifiers +--- a/js/ui/appFavorites.js ++++ b/js/ui/appFavorites.js +@@ -24,6 +24,12 @@ const AppFavorites = new Lang.Class({ + + _reload: function() { + let ids = global.settings.get_strv(this.FAVORITE_APPS_KEY); ++ // Fedora: Replace old mozilla-firefox.desktop with firefox.desktop, ++ for (let i = 0; i < ids.length; i++) { ++ if (ids[i] == 'mozilla-firefox.desktop') ++ ids[i] = 'firefox.desktop' ++ } ++ + let appSys = Shell.AppSystem.get_default(); + let apps = ids.map(function (id) { + return appSys.lookup_app(id); diff --git a/SOURCES/gnome-shell-favourite-apps-yelp.patch b/SOURCES/gnome-shell-favourite-apps-yelp.patch new file mode 100644 index 0000000..876b64f --- /dev/null +++ b/SOURCES/gnome-shell-favourite-apps-yelp.patch @@ -0,0 +1,26 @@ +From 13e92bff3bf4c32476999916280e87cefa5d954b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 1 May 2013 01:33:16 +0200 +Subject: [PATCH] Add 'yelp' to default favorites + +Help should be easily available, so add it to the default favorites. +--- + data/org.gnome.shell.gschema.xml.in.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/data/org.gnome.shell.gschema.xml.in.in b/data/org.gnome.shell.gschema.xml.in.in +index 27adddc..eee70b1 100644 +--- a/data/org.gnome.shell.gschema.xml.in.in ++++ b/data/org.gnome.shell.gschema.xml.in.in +@@ -32,7 +32,7 @@ + + + +- [ 'firefox.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'libreoffice-writer.desktop', 'nautilus.desktop', 'gnome-documents.desktop' ] ++ [ 'firefox.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'libreoffice-writer.desktop', 'nautilus.desktop', 'gnome-documents.desktop', 'yelp.desktop' ] + <_summary>List of desktop file IDs for favorite applications + <_description> + The applications corresponding to these identifiers +-- +1.8.2.1 + diff --git a/SOURCES/login-screen-backport.patch b/SOURCES/login-screen-backport.patch new file mode 100644 index 0000000..acd94b5 --- /dev/null +++ b/SOURCES/login-screen-backport.patch @@ -0,0 +1,17657 @@ +From 51b5fb2e3560f64e1887683cd224d14b13cddd07 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Fri, 14 Jun 2013 08:14:34 -0400 +Subject: [PATCH 01/66] loginDialog: clean up import lines + +There are a few unused imports, and some import lines +out of order. + +This commit tries to clean it all up. + +https://bugzilla.gnome.org/show_bug.cgi?id=702818 +--- + js/gdm/loginDialog.js | 14 +++++--------- + 1 file changed, 5 insertions(+), 9 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 4355c1d..c3e26fa 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -1,73 +1,69 @@ + // -*- 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 AccountsService = imports.gi.AccountsService; + const Clutter = imports.gi.Clutter; +-const CtrlAltTab = imports.ui.ctrlAltTab; ++const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const Gtk = imports.gi.Gtk; ++const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; +-const Lang = imports.lang; +-const Pango = imports.gi.Pango; +-const Realmd = imports.gdm.realmd; +-const Signals = imports.signals; + const Shell = imports.gi.Shell; ++const Signals = imports.signals; + const St = imports.gi.St; +-const Gdm = imports.gi.Gdm; + + const Batch = imports.gdm.batch; +-const Fprint = imports.gdm.fingerprint; ++const CtrlAltTab = imports.ui.ctrlAltTab; + const GdmUtil = imports.gdm.util; +-const Lightbox = imports.ui.lightbox; + const Main = imports.ui.main; + const ModalDialog = imports.ui.modalDialog; +-const PanelMenu = imports.ui.panelMenu; ++const Realmd = imports.gdm.realmd; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const _FADE_ANIMATION_TIME = 0.25; + const _SCROLL_ANIMATION_TIME = 0.5; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _LOGO_ICON_HEIGHT = 48; + + let _loginDialog = null; + + const UserListItem = new Lang.Class({ + Name: 'UserListItem', + + _init: function(user) { + this.user = user; + this._userChangedId = this.user.connect('changed', + Lang.bind(this, this._onUserChanged)); + + let layout = new St.BoxLayout({ vertical: false }); + this.actor = new St.Button({ style_class: 'login-dialog-user-list-item', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: layout, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._userAvatar = new UserMenu.UserAvatarWidget(this.user, + { styleClass: 'login-dialog-user-list-item-icon' }); +-- +1.8.3.1 + + +From cc789d8c429aaa5276f8cff339a866cf1e52b3b0 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 25 Jun 2013 12:55:21 -0400 +Subject: [PATCH 02/66] loginDialog: drop use of modal dialog + +The login screen is no longer even remotely dialog-like, so +using ModalDialog is pretty weird. It also makes it difficult +to put the session list in the same place as the spinner. + +This commit moves loginDialog away from using modal dialog. + +https://bugzilla.gnome.org/show_bug.cgi?id=702818 +--- + data/theme/gnome-shell.css | 6 ++ + js/gdm/loginDialog.js | 192 +++++++++++++++++++++++++++++++-------------- + 2 files changed, 138 insertions(+), 60 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index f4ea781..0fa3265 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -350,128 +350,134 @@ StScrollBar StButton#vhandle:active { + border-color: #666666; + color: #9f9f9f; + background-gradient-direction: none; + background-color: rgba(102, 102, 102, 0.15); + } + + /* Common radii */ + + #searchEntry, + .modal-dialog-button, + .notification-button, + .hotplug-notification-item, + .app-view-controls { + border-radius: 18px; + } + + .app-view-control:first-child:ltr, + .app-view-control:last-child:rtl { + border-radius: 18px 0px 0px 18px; + border-right-width: 0px; + } + + .app-view-control:last-child:ltr, + .app-view-control:first-child:rtl { + border-radius: 0px 18px 18px 0px; + } + + /* Entries */ + + #searchEntry, ++.login-dialog StEntry, + .notification StEntry, + .modal-dialog StEntry { + color: rgb(64, 64, 64); + caret-color: rgb(64, 64, 64); + font-size: 12pt; + caret-size: 1px; + selected-color: white; + padding: 4px 12px; + } + + #searchEntry, ++.login-dialog StEntry, + .run-dialog-entry, + .notification StEntry { + border: 2px solid rgba(245,245,245,0.2); + background-gradient-start: rgba(5,5,6,0.1); + background-gradient-end: rgba(254,254,254,0.1); + background-gradient-direction: vertical; + transition-duration: 300ms; + box-shadow: inset 0px 2px 4px rgba(0,0,0,0.6); + } + + #searchEntry:focus, + #searchEntry:hover, ++.login-dialog StEntry:focus, + .notification StEntry:focus, + .modal-dialog StEntry { + border: 2px solid rgb(136,138,133); + background-gradient-start: rgb(200,200,200); + background-gradient-end: white; + background-gradient-direction: vertical; + box-shadow: inset 0px 2px 4px rgba(0,0,0,0.6); + } + ++.login-dialog StEntry:focus, + .notification StEntry:focus, + .modal-dialog StEntry:focus { + border: 2px solid #3465a4; + } + + #searchEntry { + border-color: rgba(245,245,245,0.3); + color: rgb(192, 192, 192); + caret-color: rgb(192, 192, 192); + } + + #searchEntry:hover { + color: rgb(128, 128, 128); + caret-color: rgb(128, 128, 128); + } + + #searchEntry:focus { + color: rgb(64, 64, 64); + caret-color: rgb(64, 64, 64); + font-weight: bold; + transition-duration: 0ms; + } + ++.login-dialog StEntry, + .notification StEntry, + .modal-dialog StEntry { + border-radius: 5px; + padding: 4px 4px; + } + + .prompt-dialog-password-entry .capslock-warning, + .login-dialog-prompt-entry .capslock-warning { + icon-size: 16px; + warning-color: #999; + padding: 0 4px; + } + ++.login-dialog StEntry:insensitive, + .modal-dialog StEntry:insensitive { + border-color: #666666; + color: #9f9f9f; + border: 2px solid #9f9f9f; + background-gradient-direction: none; + background-color: rgba(102, 102, 102, 0.15); + box-shadow: inset 0 0 rgba(0,0,0,1.0); + } + + /* Panel */ + + #panel { + background-color: black; + font-weight: bold; + height: 1.86em; + } + + #panel.lock-screen { + background-color: rgba(0,0,0,0.3); + } + + #panel.unlock-screen, + #panel.login-screen { + background-color: transparent; + } + + #panelLeft, #panelCenter { + spacing: 4px; + } + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index c3e26fa..6b36b45 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -1,75 +1,80 @@ + // -*- 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 AccountsService = imports.gi.AccountsService; ++const Atk = imports.gi.Atk; + const Clutter = imports.gi.Clutter; + const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; + const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + ++const Panel = imports.ui.panel; + const Batch = imports.gdm.batch; + const CtrlAltTab = imports.ui.ctrlAltTab; + const GdmUtil = imports.gdm.util; ++const Layout = imports.ui.layout; + const Main = imports.ui.main; +-const ModalDialog = imports.ui.modalDialog; + const Realmd = imports.gdm.realmd; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const _FADE_ANIMATION_TIME = 0.25; + const _SCROLL_ANIMATION_TIME = 0.5; ++const _WORK_SPINNER_ICON_SIZE = 24; ++const _WORK_SPINNER_ANIMATION_DELAY = 1.0; ++const _WORK_SPINNER_ANIMATION_TIME = 0.3; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _LOGO_ICON_HEIGHT = 48; + + let _loginDialog = null; + + const UserListItem = new Lang.Class({ + Name: 'UserListItem', + + _init: function(user) { + this.user = user; + this._userChangedId = this.user.connect('changed', + Lang.bind(this, this._onUserChanged)); + + let layout = new St.BoxLayout({ vertical: false }); + this.actor = new St.Button({ style_class: 'login-dialog-user-list-item', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: layout, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._userAvatar = new UserMenu.UserAvatarWidget(this.user, + { styleClass: 'login-dialog-user-list-item-icon' }); + layout.add(this._userAvatar.actor); + let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box', + vertical: true }); + layout.add(textLayout, { expand: true }); + + this._nameLabel = new St.Label({ style_class: 'login-dialog-user-list-item-name' }); +@@ -442,205 +447,223 @@ const SessionList = new Lang.Class({ + + if (ids.length <= 1) { + this._box.hide(); + this._button.hide(); + } else { + this._button.show(); + this._box.show(); + } + + for (let i = 0; i < ids.length; i++) { + let [sessionName, sessionDescription] = Gdm.get_session_name_and_description(ids[i]); + + let item = new SessionListItem(ids[i], sessionName); + this._itemList.add_actor(item.actor); + this._items[ids[i]] = item; + + if (!this._activeSessionId) + this.setActiveSession(ids[i]); + + item.connect('activate', + Lang.bind(this, function() { + this.setActiveSession(item.id); + })); + } + } + }); + Signals.addSignalMethods(SessionList.prototype); + + const LoginDialog = new Lang.Class({ + Name: 'LoginDialog', +- Extends: ModalDialog.ModalDialog, + + _init: function(parentActor) { +- this.parent({ shellReactive: true, +- styleClass: 'login-dialog', +- parentActor: parentActor, +- keybindingMode: Shell.KeyBindingMode.LOGIN_SCREEN, +- shouldFadeIn: false }); +- this.connect('destroy', +- Lang.bind(this, this._onDestroy)); +- this.connect('opened', +- Lang.bind(this, this._onOpened)); ++ this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, ++ style_class: 'login-dialog', ++ visible: false }); ++ ++ this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); ++ this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); ++ parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default() + this._greeterClient = new Gdm.Client(); + + if (GLib.getenv('GDM_GREETER_TEST') != '1') { + this._greeter = this._greeterClient.get_greeter_sync(null); + + this._greeter.connect('default-session-name-changed', + Lang.bind(this, this._onDefaultSessionChanged)); + + this._greeter.connect('session-opened', + Lang.bind(this, this._onSessionOpened)); + this._greeter.connect('timed-login-requested', + Lang.bind(this, this._onTimedLoginRequested)); + } + + this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient); + this._userVerifier.connect('ask-question', Lang.bind(this, this._askQuestion)); + this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage)); + this._userVerifier.connect('verification-failed', Lang.bind(this, this._verificationFailed)); + this._userVerifier.connect('reset', Lang.bind(this, this._reset)); + this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint)); + this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint)); + this._verifyingUser = false; + + this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA }); + + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + vertical: true }); +- this.contentLayout.add(this._userSelectionBox); ++ this._userSelectionBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, ++ align_axis: Clutter.AlignAxis.BOTH, ++ factor: 0.5 })); ++ this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + +- this.setInitialKeyFocus(this._userList.actor); +- + this._promptBox = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', + vertical: true }); +- this.contentLayout.add(this._promptBox, +- { expand: true, +- x_fill: true, +- y_fill: true, +- x_align: St.Align.START }); ++ ++ this._promptBox.connect('button-press-event', ++ Lang.bind(this, function(actor, event) { ++ if (event.get_key_symbol() == Clutter.KEY_Escape) { ++ this.cancel(); ++ } ++ })); ++ ++ this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, ++ align_axis: Clutter.AlignAxis.BOTH, ++ factor: 0.5 })); ++ this.actor.add_child(this._promptBox); + this._promptUser = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this._promptBox.add(this._promptUser, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this._promptBox.add(this._promptLabel, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + this._promptEntryTextChangedId = 0; + this._promptEntryActivateId = 0; + this._promptBox.add(this._promptEntry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._promptMessage = new St.Label({ visible: false }); + this._promptBox.add(this._promptMessage, { x_fill: true }); + + this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); + this._promptLoginHint.hide(); + this._promptBox.add(this._promptLoginHint); + +- this._signInButton = null; +- + this._sessionList = new SessionList(); + this._sessionList.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + + this._promptBox.add(this._sessionList.actor, + { expand: true, + x_fill: false, + y_fill: true, + x_align: St.Align.START }); ++ this._buttonBox = new St.BoxLayout({ style_class: 'modal-dialog-button-box', ++ vertical: false }); ++ this._promptBox.add(this._buttonBox, ++ { expand: true, ++ x_align: St.Align.MIDDLE, ++ y_align: St.Align.END }); ++ this._cancelButton = null; ++ this._signInButton = null; ++ + this._promptBox.hide(); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + + this._logoBin = new St.Bin({ style_class: 'login-dialog-logo-bin', y_expand: true }); + this._logoBin.set_y_align(Clutter.ActorAlign.END); +- this.backgroundStack.add_actor(this._logoBin); ++ this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, ++ align_axis: Clutter.AlignAxis.X_AXIS, ++ factor: 0.5 })); ++ this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, ++ align_axis: Clutter.AlignAxis.Y_AXIS, ++ factor: 1.0 })); ++ this.actor.add_child(this._logoBin); + this._updateLogo(); + + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + + // If this is the first time around, set initial focus + if (this._disableUserList == undefined && disableUserList) + this.setInitialKeyFocus(this._promptEntry); + + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; +@@ -667,65 +690,92 @@ const LoginDialog = new Lang.Class({ + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _reset: function() { + this._userVerifier.clear(); + + this._updateSensitivity(true); + this._promptMessage.hide(); + this._user = null; + this._verifyingUser = false; + + if (this._disableUserList) + this._hideUserListAndLogIn(); + else + this._showUserList(); + }, + ++ _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(); ++ } ++ }); ++ } ++ }, ++ + _verificationFailed: function() { + this._promptEntry.text = ''; + + this._updateSensitivity(true); +- this.setWorking(false); ++ this._setWorking(false); + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionList.setActiveSession(sessionId); + }, + + _showMessage: function(userVerifier, message, styleClass) { + if (message) { + this._promptMessage.text = message; + this._promptMessage.styleClass = styleClass; + this._promptMessage.show(); + } else { + this._promptMessage.hide(); + } + }, + + _showLoginHint: function(verifier, message) { + this._promptLoginHint.set_text(message) + this._promptLoginHint.show(); + this._promptLoginHint.opacity = 255; + }, + + _hideLoginHint: function() { + this._promptLoginHint.hide(); + this._promptLoginHint.set_text(''); + }, + + cancel: function() { + if (this._verifyingUser) + this._userVerifier.cancel(); +@@ -737,218 +787,238 @@ const LoginDialog = new Lang.Class({ + this._sessionList.actor.hide(); + this._promptLabel.show(); + this._promptEntry.show(); + this._promptLoginHint.opacity = 0; + this._promptLoginHint.show(); + this._promptBox.opacity = 0; + this._promptBox.show(); + Tweener.addTween(this._promptBox, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + + if ((this._user && !this._user.is_logged_in()) || this._verifyingUser) + this._sessionList.actor.show(); + + this._promptEntry.grab_key_focus(); + + let hold = new Batch.Hold(); + let tasks = [function() { + this._prepareDialog(forSecret, hold); + }, + + hold]; + + let batch = new Batch.ConcurrentBatch(this, tasks); + + return batch.run(); + }, + + _prepareDialog: function(forSecret, hold) { +- this.buttonLayout.visible = true; +- this.clearButtons(); +- +- if (!this._disableUserList || this._verifyingUser) +- this.addButton({ action: Lang.bind(this, this.cancel), +- label: _("Cancel"), +- key: Clutter.Escape }, +- { expand: true, +- x_fill: false, +- y_fill: false, +- x_align: St.Align.START, +- y_align: St.Align.MIDDLE }); +- this.placeSpinner({ expand: false, +- x_fill: false, +- y_fill: false, +- x_align: St.Align.END, +- y_align: St.Align.MIDDLE }); +- this._signInButton = this.addButton({ action: Lang.bind(this, function() { +- hold.release(); +- }), +- label: forSecret ? C_("button", "Sign In") : _("Next"), +- default: true }, +- { expand: false, +- x_fill: false, +- y_fill: false, +- x_align: St.Align.END, +- y_align: St.Align.MIDDLE }); ++ this._buttonBox.visible = true; ++ this._buttonBox.destroy_all_children(); ++ ++ if (!this._disableUserList || this._verifyingUser) { ++ 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: true, ++ x_fill: false, ++ y_fill: false, ++ x_align: St.Align.START, ++ y_align: St.Align.END }); ++ } ++ ++ let spinnerIcon = global.datadir + '/theme/process-working.svg'; ++ this._workSpinner = new Panel.AnimatedIcon(spinnerIcon, _WORK_SPINNER_ICON_SIZE); ++ this._workSpinner.actor.opacity = 0; ++ this._workSpinner.actor.show(); ++ ++ this._buttonBox.add(this._workSpinner.actor, ++ { expand: false, ++ x_align: St.Align.END }); ++ ++ this._signInButton = new St.Button({ style_class: 'modal-dialog-button', ++ button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, ++ reactive: true, ++ can_focus: true, ++ label: forSecret ? C_("button", "Sign In") : _("Next") }); ++ this._signInButton.connect('clicked', ++ Lang.bind(this, function() { ++ hold.release(); ++ })); ++ this._signInButton.add_style_pseudo_class('default'); ++ this._buttonBox.add(this._signInButton, ++ { expand: true, ++ x_fill: false, ++ y_fill: false, ++ x_align: St.Align.END, ++ y_align: St.Align.END }); + + this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); + + this._promptEntryTextChangedId = + this._promptEntry.clutter_text.connect('text-changed', + Lang.bind(this, function() { + this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); + })); + + this._promptEntryActivateId = + this._promptEntry.clutter_text.connect('activate', function() { + hold.release(); + }); + }, + + _updateSensitivity: function(sensitive) { + this._promptEntry.reactive = sensitive; + this._promptEntry.clutter_text.editable = sensitive; + this._sessionList.updateSensitivity(sensitive); + this._updateSignInButtonSensitivity(sensitive); + }, + + _updateSignInButtonSensitivity: function(sensitive) { + if (this._signInButton) { + this._signInButton.reactive = sensitive; + this._signInButton.can_focus = sensitive; + } + }, + + _hidePrompt: function() { +- this.setButtons([]); ++ this._buttonBox.destroy_all_children(); + + if (this._promptEntryTextChangedId > 0) { + this._promptEntry.clutter_text.disconnect(this._promptEntryTextChangedId); + this._promptEntryTextChangedId = 0; + } + + if (this._promptEntryActivateId > 0) { + this._promptEntry.clutter_text.disconnect(this._promptEntryActivateId); + this._promptEntryActivateId = 0; + } + +- this.setWorking(false); ++ this._setWorking(false); + this._promptBox.hide(); + this._promptLoginHint.hide(); + + this._promptUser.set_child(null); + + this._updateSensitivity(true); + this._promptEntry.set_text(''); + + this._sessionList.close(); + this._promptLoginHint.hide(); + +- this.clearButtons(); ++ this._buttonBox.destroy_all_children(); + this._signInButton = null; ++ this._cancelButton = null; + }, + + _askQuestion: function(verifier, serviceName, question, passwordChar) { + this._promptLabel.set_text(question); + + this._updateSensitivity(true); + this._promptEntry.set_text(''); + this._promptEntry.clutter_text.set_password_char(passwordChar); + + let tasks = [function() { + return this._showPrompt(!!passwordChar); + }, + + function() { + let text = this._promptEntry.get_text(); + this._updateSensitivity(false); +- this.setWorking(true); ++ this._setWorking(true); + this._userVerifier.answerQuery(serviceName, text); + }]; + + let batch = new Batch.ConsecutiveBatch(this, tasks); + return batch.run(); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + 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._showLoginHint(null, _("(e.g., user or %s)").format(hint)); + }, + + _askForUsernameAndLogIn: function() { + this._promptLabel.set_text(_("Username: ")); + this._promptEntry.set_text(''); + this._promptEntry.clutter_text.set_password_char(''); + + let realmManager = new Realmd.Manager(); + let signalId = realmManager.connect('login-format-changed', + Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + + let tasks = [this._showPrompt, + + function() { + let userName = this._promptEntry.get_text(); + this._promptEntry.reactive = false; + return this._beginVerificationForUser(userName); + }, + + function() { + realmManager.disconnect(signalId) + realmManager.release(); + }]; + + let batch = new Batch.ConsecutiveBatch(this, tasks); + return batch.run(); + }, + + _startSession: function(serviceName) { +- Tweener.addTween(this.dialogLayout, ++ Tweener.addTween(this.actor, + { opacity: 0, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onUpdate: function() { + let children = Main.layoutManager.uiGroup.get_children(); + + for (let i = 0; i < children.length; i++) { + if (children[i] != Main.layoutManager.screenShieldGroup) +- children[i].opacity = this.dialogLayout.opacity; ++ children[i].opacity = this.actor.opacity; + } + }, + onUpdateScope: this, + onComplete: function() { + Mainloop.idle_add(Lang.bind(this, function() { + this._greeter.call_start_session_when_ready_sync(serviceName, true, null); + return false; + })); + }, + onCompleteScope: this }); + }, + + _onSessionOpened: function(client, serviceName) { + if (!this._userVerifier.hasPendingMessages) { + this._startSession(serviceName); + } else { + let signalId = this._userVerifier.connect('no-more-messages', + Lang.bind(this, function() { + this._userVerifier.disconnect(signalId); + this._startSession(serviceName); + })); + } + }, + + _waitForItemForUser: function(userName) { + let item = this._userList.getItemFromUserName(userName); + + if (item) + return null; + +@@ -1122,48 +1192,50 @@ const LoginDialog = new Lang.Class({ + batch.run(); + }, + + _onDestroy: function() { + if (this._userManagerLoadedId) { + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + }, + + _loadUserList: function() { + let users = this._userManager.list_users(); + + for (let i = 0; i < users.length; i++) { + this._userList.addUser(users[i]); + } + + this._updateDisableUserList(); + + this._userManager.connect('user-added', + Lang.bind(this, function(userManager, user) { + this._userList.addUser(user); + })); + + this._userManager.connect('user-removed', + Lang.bind(this, function(userManager, user) { + this._userList.removeUser(user); + })); + }, + +- _onOpened: function() { +- Main.ctrlAltTabManager.addGroup(this.dialogLayout, ++ open: function() { ++ Main.ctrlAltTabManager.addGroup(this.actor, + _("Login Window"), + 'dialog-password-symbolic', + { sortGroup: CtrlAltTab.SortGroup.MIDDLE }); ++ this._userList.actor.grab_key_focus(); ++ this.actor.show(); + ++ return true; + }, + + close: function() { +- this.parent(); +- + Main.ctrlAltTabManager.removeGroup(this.dialogLayout); + }, + + addCharacter: function(unichar) { + this._promptEntry.clutter_text.insert_unichar(unichar); + }, + }); ++Signals.addSignalMethods(LoginDialog.prototype); +-- +1.8.3.1 + + +From 11dc03aee173cbbd77d274fc83df64da74d77b2e Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 26 Jun 2013 10:52:02 -0400 +Subject: [PATCH 03/66] loginDialog: don't show Not Listed? button before user + list + +Right now, there's a weird flicker at start up where the +Not Listed? button shows up before the user list, which looks +pretty bad if you're watching for it. + +This commit fixes that problem by hiding the Not Listed button +initially and showing it at the appropriate time. + +https://bugzilla.gnome.org/show_bug.cgi?id=703132 +--- + js/gdm/loginDialog.js | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 6b36b45..4cbb0c9 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -595,60 +595,61 @@ const LoginDialog = new Lang.Class({ + this._promptBox.add(this._sessionList.actor, + { expand: true, + x_fill: false, + y_fill: true, + x_align: St.Align.START }); + this._buttonBox = new St.BoxLayout({ style_class: 'modal-dialog-button-box', + vertical: false }); + this._promptBox.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + this._cancelButton = null; + this._signInButton = null; + + this._promptBox.hide(); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); ++ this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + + this._logoBin = new St.Bin({ style_class: 'login-dialog-logo-bin', y_expand: true }); + this._logoBin.set_y_align(Clutter.ActorAlign.END); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.X_AXIS, + factor: 0.5 })); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.Y_AXIS, + factor: 1.0 })); + this.actor.add_child(this._logoBin); + this._updateLogo(); + + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', +@@ -1127,60 +1128,61 @@ const LoginDialog = new Lang.Class({ + + if (event.type() == Clutter.EventType.KEY_PRESS || + event.type() == Clutter.EventType.BUTTON_PRESS) { + if (this._timedLoginBatch) { + this._timedLoginBatch.cancel(); + this._timedLoginBatch = null; + } + } else if (event.type() == Clutter.EventType.KEY_RELEASE || + event.type() == Clutter.EventType.BUTTON_RELEASE) { + this._resetTimedLogin(); + } + + return false; + })); + }, + + _setUserListExpanded: function(expanded) { + this._userList.updateStyle(expanded); + this._userSelectionBox.visible = expanded; + }, + + _hideUserListAndLogIn: function() { + this._setUserListExpanded(false); + GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + this._askForUsernameAndLogIn(); + }, + + _showUserList: function() { + this._hidePrompt(); + this._setUserListExpanded(true); ++ this._notListedButton.show(); + this._userList.actor.grab_key_focus(); + }, + + _beginVerificationForUser: function(userName) { + let hold = new Batch.Hold(); + + this._userVerifier.begin(userName, hold); + this._verifyingUser = true; + return hold; + }, + + _beginVerificationForItem: function(item) { + let userWidget = new UserWidget.UserWidget(item.user); + this._promptUser.set_child(userWidget.actor); + + let tasks = [function() { + let userName = item.user.get_user_name(); + return this._beginVerificationForUser(userName); + }]; + let batch = new Batch.ConsecutiveBatch(this, tasks); + return batch.run(); + }, + + _onUserListActivated: function(activatedItem) { + let tasks = [function() { + return GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + }, + function() { + this._setUserListExpanded(false); + }]; +-- +1.8.3.1 + + +From 38f510c4d6fff0758af652109811a59e14b7c175 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Sun, 23 Jun 2013 23:14:10 -0400 +Subject: [PATCH 04/66] loginDialog: Move the session list to a PopupMenu + +There are some issues with the existing session menu. First, it looks +kinda bad. It seems like it's hanging around there, but it doesn't really know +what to do with itself. + +Second, when it expands down it requires that the buttons below move +down with it. This kind of movement is awkward and looks a bit weird. + +Third, its current position makes the "dialog" tall and unwieldy when +you add things like messages for fingerprint readers or authentication errors. + +This commit moves the session list to a menu behind a button to address +the above problems. + +Based on a patch by Jasper St. Pierre. + +https://bugzilla.gnome.org/show_bug.cgi?id=702818 +--- + data/theme/gnome-shell.css | 38 ++------ + js/gdm/loginDialog.js | 229 ++++++++++++++------------------------------- + 2 files changed, 78 insertions(+), 189 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 0fa3265..4c0a795 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2322,97 +2322,71 @@ StScrollBar StButton#vhandle:active { + } + + .login-dialog-not-listed-button:focus .login-dialog-not-listed-label, + .login-dialog-not-listed-button:hover .login-dialog-not-listed-label { + color: #E8E8E8; + } + + .login-dialog-username { + font-size: 16pt; + font-weight: bold; + text-align: left; + padding-left: 15px; + text-shadow: black 0px 4px 3px 0px; + } + + .login-dialog-prompt-layout { + padding-top: 24px; + padding-bottom: 12px; + spacing: 8px; + } + + .login-dialog-prompt-label { + color: #eeeeee; + font-size: 14px; + } + + .login-dialog-prompt-entry { + width: 15em; + } + +-.login-dialog-session-list, +-.login-dialog-session-list-item { +- color: #babdb6; +-} +- +-.login-dialog-session-list-button:focus, +-.login-dialog-session-list-button:active, +-.login-dialog-session-list-button:hover, +-.login-dialog-session-list-item:focus, +-.login-dialog-session-list-item:hover { +- color: white; ++.login-dialog-session-list-button StIcon { ++ icon-size: 1.25em; + } + + .login-dialog-session-list-button { +- padding: 4px; ++ color: #8b8b8b; + } + +-.login-dialog-session-list-scroll-view { +- padding: 6px; +-} +- +-.login-dialog-session-list-item { +- padding-bottom: 6px; +-} +- +-.login-dialog-session-list-triangle { +- padding-right: 6px; +-} +- +-.login-dialog-session-list-item-box { +- padding-left: 6px; +- spacing: 6px; +-} +- +-.login-dialog-session-list-item-dot { +- width: 10px; +- height: 10px; ++.login-dialog-session-list-button:hover, ++.login-dialog-session-list-button:active { ++ color: white; + } + + .login-dialog-logo-bin { + padding: 24px 0px; + } + + .login-dialog .modal-dialog-button-box { + spacing: 3px; + } + + .login-dialog .modal-dialog-button { + border-radius: 5px; + padding: 3px 18px; + } + + .login-dialog .modal-dialog-button:focus { + padding: 2px 17px; + } + + .login-dialog .modal-dialog-button:default { + background-gradient-start: #6793c4; + background-gradient-end: #335d8f; + background-gradient-direction: vertical; + border-color: #16335d; + } + + .login-dialog .modal-dialog-button:default:focus { + border: 2px solid #377fe7; + } + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 4cbb0c9..5557db9 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -7,64 +7,66 @@ + * 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 AccountsService = imports.gi.AccountsService; + const Atk = imports.gi.Atk; + const Clutter = imports.gi.Clutter; + const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; + const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + + const Panel = imports.ui.panel; + const Batch = imports.gdm.batch; ++const BoxPointer = imports.ui.boxpointer; + const CtrlAltTab = imports.ui.ctrlAltTab; + const GdmUtil = imports.gdm.util; + const Layout = imports.ui.layout; + const Main = imports.ui.main; ++const PopupMenu = imports.ui.popupMenu; + const Realmd = imports.gdm.realmd; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const _FADE_ANIMATION_TIME = 0.25; + const _SCROLL_ANIMATION_TIME = 0.5; + const _WORK_SPINNER_ICON_SIZE = 24; + const _WORK_SPINNER_ANIMATION_DELAY = 1.0; + const _WORK_SPINNER_ANIMATION_TIME = 0.3; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _LOGO_ICON_HEIGHT = 48; + + let _loginDialog = null; + + const UserListItem = new Lang.Class({ + Name: 'UserListItem', + + _init: function(user) { + this.user = user; + this._userChangedId = this.user.connect('changed', + Lang.bind(this, this._onUserChanged)); + + let layout = new St.BoxLayout({ vertical: false }); + this.actor = new St.Button({ style_class: 'login-dialog-user-list-item', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: layout, + reactive: true, + x_align: St.Align.START, +@@ -262,242 +264,160 @@ const UserList = new Lang.Class({ + Lang.bind(this, + function() { + this.scrollToItem(item); + })); + + this._moveFocusToItems(); + + this.emit('item-added', item); + }, + + removeUser: function(user) { + if (!user.is_loaded) + return; + + let userName = user.get_user_name(); + + if (!userName) + return; + + let item = this._items[userName]; + + if (!item) + return; + + item.actor.destroy(); + delete this._items[userName]; + } + }); + Signals.addSignalMethods(UserList.prototype); + +-const SessionListItem = new Lang.Class({ +- Name: 'SessionListItem', +- +- _init: function(id, name) { +- this.id = id; +- +- this.actor = new St.Button({ style_class: 'login-dialog-session-list-item', +- button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, +- can_focus: true, +- reactive: true, +- x_fill: true, +- x_align: St.Align.START }); +- +- this._box = new St.BoxLayout({ style_class: 'login-dialog-session-list-item-box' }); +- +- this.actor.add_actor(this._box); +- this.actor.connect('clicked', Lang.bind(this, this._onClicked)); +- +- this._dot = new St.DrawingArea({ style_class: 'login-dialog-session-list-item-dot' }); +- this._dot.connect('repaint', Lang.bind(this, this._onRepaintDot)); +- this._box.add_actor(this._dot); +- this.setShowDot(false); +- +- let label = new St.Label({ style_class: 'login-dialog-session-list-item-label', +- text: name }); +- this.actor.label_actor = label; +- +- this._box.add_actor(label); +- }, +- +- setShowDot: function(show) { +- if (show) +- this._dot.opacity = 255; +- else +- this._dot.opacity = 0; +- }, +- +- _onRepaintDot: function(area) { +- let cr = area.get_context(); +- let [width, height] = area.get_surface_size(); +- let color = area.get_theme_node().get_foreground_color(); +- +- cr.setSourceRGBA (color.red / 255, +- color.green / 255, +- color.blue / 255, +- color.alpha / 255); +- cr.arc(width / 2, height / 2, width / 3, 0, 2 * Math.PI); +- cr.fill(); +- cr.$dispose(); +- }, +- +- _onClicked: function() { +- this.emit('activate'); +- } +-}); +-Signals.addSignalMethods(SessionListItem.prototype); +- +-const SessionList = new Lang.Class({ +- Name: 'SessionList', ++const SessionMenuButton = new Lang.Class({ ++ Name: 'SessionMenuButton', + + _init: function() { +- this.actor = new St.Bin(); +- +- this._box = new St.BoxLayout({ style_class: 'login-dialog-session-list', +- vertical: true}); +- this.actor.child = this._box; +- ++ let gearIcon = new St.Icon({ icon_name: 'emblem-system-symbolic' }); + this._button = new St.Button({ style_class: 'login-dialog-session-list-button', +- button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, ++ reactive: true, ++ track_hover: true, + can_focus: true, +- x_fill: true, +- y_fill: true }); +- let box = new St.BoxLayout(); +- this._button.add_actor(box); +- +- this._triangle = new St.Label({ style_class: 'login-dialog-session-list-triangle', +- text: '\u25B8' }); +- box.add_actor(this._triangle); +- +- let label = new St.Label({ style_class: 'login-dialog-session-list-label', +- text: _("Session…") }); +- box.add_actor(label); +- +- this._button.connect('clicked', +- Lang.bind(this, this._onClicked)); +- this._box.add_actor(this._button); +- this._scrollView = new St.ScrollView({ style_class: 'login-dialog-session-list-scroll-view'}); +- this._scrollView.set_policy(Gtk.PolicyType.NEVER, +- Gtk.PolicyType.AUTOMATIC); +- this._box.add_actor(this._scrollView); +- this._itemList = new St.BoxLayout({ style_class: 'login-dialog-session-item-list', +- vertical: true }); +- this._scrollView.add_actor(this._itemList); +- this._scrollView.hide(); +- this.isOpen = false; +- this._populate(); +- }, ++ accessible_name: _("Choose Session"), ++ accessible_role: Atk.Role.MENU, ++ child: gearIcon }); + +- open: function() { +- if (this.isOpen) +- return; ++ this.actor = new St.Bin({ child: this._button }); + +- this._button.add_style_pseudo_class('open'); +- this._scrollView.show(); +- this._triangle.set_text('\u25BE'); ++ this._menu = new PopupMenu.PopupMenu(this._button, 0, St.Side.TOP); ++ Main.uiGroup.add_actor(this._menu.actor); ++ this._menu.actor.hide(); + +- this.isOpen = true; +- }, ++ this._menu.connect('open-state-changed', ++ Lang.bind(this, function(menu, isOpen) { ++ if (isOpen) ++ this._button.add_style_pseudo_class('active'); ++ else ++ this._button.remove_style_pseudo_class('active'); ++ })); + +- close: function() { +- if (!this.isOpen) +- return; ++ let subtitle = new PopupMenu.PopupMenuItem(_("Session"), { style_class: 'popup-subtitle-menu-item', ++ reactive: false }); ++ this._menu.addMenuItem(subtitle); + +- this._button.remove_style_pseudo_class('open'); +- this._scrollView.hide(); +- this._triangle.set_text('\u25B8'); ++ this._manager = new PopupMenu.PopupMenuManager({ actor: this._button }); ++ this._manager.addMenu(this._menu); + +- this.isOpen = false; +- }, ++ this._button.connect('clicked', Lang.bind(this, function() { ++ this._menu.toggle(); ++ })); + +- _onClicked: function() { +- if (!this.isOpen) +- this.open(); +- else +- this.close(); ++ this._items = {}; ++ this._activeSessionId = null; ++ this._populate(); + }, + + updateSensitivity: function(sensitive) { + this._button.reactive = sensitive; + this._button.can_focus = sensitive; ++ this._menu.close(BoxPointer.PopupAnimation.NONE); ++ }, + +- for (let id in this._items) +- this._items[id].actor.reactive = sensitive; ++ _updateOrnament: function() { ++ let itemIds = Object.keys(this._items); ++ for (let i = 0; i < itemIds.length; i++) { ++ if (itemIds[i] == this._activeSessionId) ++ this._items[itemIds[i]].setShowDot(true); ++ else ++ this._items[itemIds[i]].setShowDot(false); ++ } + }, + + setActiveSession: function(sessionId) { + if (sessionId == this._activeSessionId) + return; + +- if (this._activeSessionId) +- this._items[this._activeSessionId].setShowDot(false); +- +- this._items[sessionId].setShowDot(true); + this._activeSessionId = sessionId; ++ this._updateOrnament(); + + this.emit('session-activated', this._activeSessionId); + }, + +- _populate: function() { +- this._itemList.destroy_all_children(); +- this._activeSessionId = null; +- this._items = {}; ++ close: function() { ++ this._menu.close(); ++ }, + ++ _populate: function() { + let ids = Gdm.get_session_ids(); + ids.sort(); + + if (ids.length <= 1) { +- this._box.hide(); + this._button.hide(); +- } else { +- this._button.show(); +- this._box.show(); ++ return; + } + + for (let i = 0; i < ids.length; i++) { + let [sessionName, sessionDescription] = Gdm.get_session_name_and_description(ids[i]); + +- let item = new SessionListItem(ids[i], sessionName); +- this._itemList.add_actor(item.actor); +- this._items[ids[i]] = item; ++ let id = ids[i]; ++ let item = new PopupMenu.PopupMenuItem(sessionName); ++ this._menu.addMenuItem(item); ++ this._items[id] = item; + + if (!this._activeSessionId) +- this.setActiveSession(ids[i]); ++ this.setActiveSession(id); + +- item.connect('activate', +- Lang.bind(this, function() { +- this.setActiveSession(item.id); +- })); ++ item.connect('activate', Lang.bind(this, function() { ++ this.setActiveSession(id); ++ })); + } + } + }); +-Signals.addSignalMethods(SessionList.prototype); ++Signals.addSignalMethods(SessionMenuButton.prototype); + + const LoginDialog = new Lang.Class({ + Name: 'LoginDialog', + + _init: function(parentActor) { + this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, + style_class: 'login-dialog', + visible: false }); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default() + this._greeterClient = new Gdm.Client(); + + if (GLib.getenv('GDM_GREETER_TEST') != '1') { + this._greeter = this._greeterClient.get_greeter_sync(null); + + this._greeter.connect('default-session-name-changed', + Lang.bind(this, this._onDefaultSessionChanged)); + + this._greeter.connect('session-opened', + Lang.bind(this, this._onSessionOpened)); + this._greeter.connect('timed-login-requested', + Lang.bind(this, this._onTimedLoginRequested)); + } + + this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient); + this._userVerifier.connect('ask-question', Lang.bind(this, this._askQuestion)); +@@ -559,131 +479,125 @@ const LoginDialog = new Lang.Class({ + x_align: St.Align.START }); + this._promptBox.add(this._promptUser, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this._promptBox.add(this._promptLabel, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + this._promptEntryTextChangedId = 0; + this._promptEntryActivateId = 0; + this._promptBox.add(this._promptEntry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._promptMessage = new St.Label({ visible: false }); + this._promptBox.add(this._promptMessage, { x_fill: true }); + + this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); + this._promptLoginHint.hide(); + this._promptBox.add(this._promptLoginHint); + +- this._sessionList = new SessionList(); +- this._sessionList.connect('session-activated', +- Lang.bind(this, function(list, sessionId) { +- this._greeter.call_select_session_sync (sessionId, null); +- })); +- +- this._promptBox.add(this._sessionList.actor, +- { expand: true, +- x_fill: false, +- y_fill: true, +- x_align: St.Align.START }); + this._buttonBox = new St.BoxLayout({ style_class: 'modal-dialog-button-box', + vertical: false }); + this._promptBox.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + this._cancelButton = null; + this._signInButton = null; + + this._promptBox.hide(); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + + this._logoBin = new St.Bin({ style_class: 'login-dialog-logo-bin', y_expand: true }); + this._logoBin.set_y_align(Clutter.ActorAlign.END); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.X_AXIS, + factor: 0.5 })); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.Y_AXIS, + factor: 1.0 })); + this.actor.add_child(this._logoBin); + this._updateLogo(); + + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + ++ this._sessionMenuButton = new SessionMenuButton(); ++ this._sessionMenuButton.connect('session-activated', ++ Lang.bind(this, function(button, sessionId) { ++ this._greeter.call_select_session_sync (sessionId, null); ++ })); + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + + // If this is the first time around, set initial focus + if (this._disableUserList == undefined && disableUserList) + this.setInitialKeyFocus(this._promptEntry); + + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; + + if (!this._verifyingUser) + this._reset(); + } + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + + _updateLogoTexture: function(cache, uri) { +@@ -726,223 +640,224 @@ const LoginDialog = new Lang.Class({ + 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(); + } + }); + } + }, + + _verificationFailed: function() { + this._promptEntry.text = ''; + + this._updateSensitivity(true); + this._setWorking(false); + }, + + _onDefaultSessionChanged: function(client, sessionId) { +- this._sessionList.setActiveSession(sessionId); ++ this._sessionMenuButton.setActiveSession(sessionId); + }, + + _showMessage: function(userVerifier, message, styleClass) { + if (message) { + this._promptMessage.text = message; + this._promptMessage.styleClass = styleClass; + this._promptMessage.show(); + } else { + this._promptMessage.hide(); + } + }, + + _showLoginHint: function(verifier, message) { + this._promptLoginHint.set_text(message) + this._promptLoginHint.show(); + this._promptLoginHint.opacity = 255; + }, + + _hideLoginHint: function() { + this._promptLoginHint.hide(); + this._promptLoginHint.set_text(''); + }, + + cancel: function() { + if (this._verifyingUser) + this._userVerifier.cancel(); + else + this._reset(); + }, + + _showPrompt: function(forSecret) { +- this._sessionList.actor.hide(); ++ this._sessionMenuButton.actor.hide(); + this._promptLabel.show(); + this._promptEntry.show(); + this._promptLoginHint.opacity = 0; + this._promptLoginHint.show(); + this._promptBox.opacity = 0; + this._promptBox.show(); + Tweener.addTween(this._promptBox, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + + if ((this._user && !this._user.is_logged_in()) || this._verifyingUser) +- this._sessionList.actor.show(); ++ this._sessionMenuButton.actor.show(); + + this._promptEntry.grab_key_focus(); + + let hold = new Batch.Hold(); + let tasks = [function() { + this._prepareDialog(forSecret, hold); + }, + + hold]; + + let batch = new Batch.ConcurrentBatch(this, tasks); + + return batch.run(); + }, + + _prepareDialog: function(forSecret, hold) { + this._buttonBox.visible = true; +- this._buttonBox.destroy_all_children(); ++ this._buttonBox.remove_all_children(); + + if (!this._disableUserList || this._verifyingUser) { + 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: true, + x_fill: false, + y_fill: false, + x_align: St.Align.START, + y_align: St.Align.END }); + } + + let spinnerIcon = global.datadir + '/theme/process-working.svg'; + this._workSpinner = new Panel.AnimatedIcon(spinnerIcon, _WORK_SPINNER_ICON_SIZE); + this._workSpinner.actor.opacity = 0; + this._workSpinner.actor.show(); + + this._buttonBox.add(this._workSpinner.actor, + { expand: false, + x_align: St.Align.END }); + ++ this._buttonBox.add(this._sessionMenuButton.actor, ++ { expand: false, ++ x_align: St.Align.END }); + this._signInButton = new St.Button({ style_class: 'modal-dialog-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + reactive: true, + can_focus: true, + label: forSecret ? C_("button", "Sign In") : _("Next") }); + this._signInButton.connect('clicked', + Lang.bind(this, function() { + hold.release(); + })); + this._signInButton.add_style_pseudo_class('default'); + this._buttonBox.add(this._signInButton, + { expand: true, + x_fill: false, + y_fill: false, + x_align: St.Align.END, + y_align: St.Align.END }); + + this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); + + this._promptEntryTextChangedId = + this._promptEntry.clutter_text.connect('text-changed', + Lang.bind(this, function() { + this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); + })); + + this._promptEntryActivateId = + this._promptEntry.clutter_text.connect('activate', function() { + hold.release(); + }); + }, + + _updateSensitivity: function(sensitive) { + this._promptEntry.reactive = sensitive; + this._promptEntry.clutter_text.editable = sensitive; +- this._sessionList.updateSensitivity(sensitive); ++ this._sessionMenuButton.updateSensitivity(sensitive); + this._updateSignInButtonSensitivity(sensitive); + }, + + _updateSignInButtonSensitivity: function(sensitive) { + if (this._signInButton) { + this._signInButton.reactive = sensitive; + this._signInButton.can_focus = sensitive; + } + }, + + _hidePrompt: function() { +- this._buttonBox.destroy_all_children(); +- + if (this._promptEntryTextChangedId > 0) { + this._promptEntry.clutter_text.disconnect(this._promptEntryTextChangedId); + this._promptEntryTextChangedId = 0; + } + + if (this._promptEntryActivateId > 0) { + this._promptEntry.clutter_text.disconnect(this._promptEntryActivateId); + this._promptEntryActivateId = 0; + } + + this._setWorking(false); + this._promptBox.hide(); + this._promptLoginHint.hide(); + + this._promptUser.set_child(null); + + this._updateSensitivity(true); + this._promptEntry.set_text(''); + +- this._sessionList.close(); ++ this._sessionMenuButton.close(); + this._promptLoginHint.hide(); + +- this._buttonBox.destroy_all_children(); ++ this._buttonBox.remove_all_children(); + this._signInButton = null; + this._cancelButton = null; + }, + + _askQuestion: function(verifier, serviceName, question, passwordChar) { + this._promptLabel.set_text(question); + + this._updateSensitivity(true); + this._promptEntry.set_text(''); + this._promptEntry.clutter_text.set_password_char(passwordChar); + + let tasks = [function() { + return this._showPrompt(!!passwordChar); + }, + + function() { + let text = this._promptEntry.get_text(); + this._updateSensitivity(false); + this._setWorking(true); + this._userVerifier.answerQuery(serviceName, text); + }]; + + let batch = new Batch.ConsecutiveBatch(this, tasks); + return batch.run(); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + +-- +1.8.3.1 + + +From c06890a9aa0da2690422285f894a422c4d1780fa Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 25 Jun 2013 13:35:38 -0400 +Subject: [PATCH 05/66] loginDialog: make spinner and session menu button share + position + +They never need to be shown at the same time, and the design has +the UI fade between them. + +This commit implements that. + +https://bugzilla.gnome.org/show_bug.cgi?id=702818 +--- + js/gdm/loginDialog.js | 161 +++++++++++++++++++++++++++++++++++--------------- + 1 file changed, 113 insertions(+), 48 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 5557db9..f127460 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -20,68 +20,74 @@ + + const AccountsService = imports.gi.AccountsService; + const Atk = imports.gi.Atk; + const Clutter = imports.gi.Clutter; + const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; + const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + + const Panel = imports.ui.panel; + const Batch = imports.gdm.batch; + const BoxPointer = imports.ui.boxpointer; + const CtrlAltTab = imports.ui.ctrlAltTab; + const GdmUtil = imports.gdm.util; + const Layout = imports.ui.layout; + const Main = imports.ui.main; + const PopupMenu = imports.ui.popupMenu; + const Realmd = imports.gdm.realmd; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const _FADE_ANIMATION_TIME = 0.25; + const _SCROLL_ANIMATION_TIME = 0.5; +-const _WORK_SPINNER_ICON_SIZE = 24; +-const _WORK_SPINNER_ANIMATION_DELAY = 1.0; +-const _WORK_SPINNER_ANIMATION_TIME = 0.3; ++const _DEFAULT_BUTTON_WELL_ICON_SIZE = 24; ++const _DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; ++const _DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _LOGO_ICON_HEIGHT = 48; + + let _loginDialog = null; + ++const DefaultButtonWellMode = { ++ NONE: 0, ++ SESSION_MENU_BUTTON: 1, ++ SPINNER: 2 ++}; ++ + const UserListItem = new Lang.Class({ + Name: 'UserListItem', + + _init: function(user) { + this.user = user; + this._userChangedId = this.user.connect('changed', + Lang.bind(this, this._onUserChanged)); + + let layout = new St.BoxLayout({ vertical: false }); + this.actor = new St.Button({ style_class: 'login-dialog-user-list-item', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: layout, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._userAvatar = new UserMenu.UserAvatarWidget(this.user, + { styleClass: 'login-dialog-user-list-item-icon' }); + layout.add(this._userAvatar.actor); + let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box', + vertical: true }); + layout.add(textLayout, { expand: true }); + + this._nameLabel = new St.Label({ style_class: 'login-dialog-user-list-item-name' }); + this.actor.label_actor = this._nameLabel; + textLayout.add(this._nameLabel, + { y_fill: false, + y_align: St.Align.MIDDLE, + expand: true }); +@@ -539,65 +545,81 @@ const LoginDialog = new Lang.Class({ + x_fill: true }); + + this._logoBin = new St.Bin({ style_class: 'login-dialog-logo-bin', y_expand: true }); + this._logoBin.set_y_align(Clutter.ActorAlign.END); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.X_AXIS, + factor: 0.5 })); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.Y_AXIS, + factor: 1.0 })); + this.actor.add_child(this._logoBin); + this._updateLogo(); + + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + ++ this._defaultButtonWell = new St.Widget(); ++ this._defaultButtonWellMode = DefaultButtonWellMode.NONE; ++ + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', +- Lang.bind(this, function(button, sessionId) { +- this._greeter.call_select_session_sync (sessionId, null); +- })); ++ Lang.bind(this, function(list, sessionId) { ++ this._greeter.call_select_session_sync (sessionId, null); ++ })); ++ this._sessionMenuButton.actor.opacity = 0; ++ this._sessionMenuButton.actor.show(); ++ this._defaultButtonWell.add_child(this._sessionMenuButton.actor); ++ ++ let spinnerIcon = global.datadir + '/theme/process-working.svg'; ++ this._workSpinner = new Panel.AnimatedIcon(spinnerIcon, _DEFAULT_BUTTON_WELL_ICON_SIZE); ++ this._workSpinner.actor.opacity = 0; ++ this._workSpinner.actor.show(); ++ ++ this._defaultButtonWell.add_child(this._workSpinner.actor); ++ this._sessionMenuButton.actor.add_constraint(new Clutter.AlignConstraint({ source: this._workSpinner.actor, ++ align_axis: Clutter.AlignAxis.BOTH, ++ factor: 0.5 })); + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + + // If this is the first time around, set initial focus + if (this._disableUserList == undefined && disableUserList) + this.setInitialKeyFocus(this._promptEntry); + + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; + + if (!this._verifyingUser) + this._reset(); + } + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + + _updateLogoTexture: function(cache, uri) { +@@ -605,278 +627,321 @@ const LoginDialog = new Lang.Class({ + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _reset: function() { + this._userVerifier.clear(); + + this._updateSensitivity(true); + this._promptMessage.hide(); + this._user = null; + this._verifyingUser = false; + + if (this._disableUserList) + this._hideUserListAndLogIn(); + else + this._showUserList(); + }, + +- _setWorking: function(working) { +- if (!this._workSpinner) ++ _getActorForDefaultButtonWellMode: function(mode) { ++ let actor; ++ ++ if (mode == DefaultButtonWellMode.NONE) ++ actor = null; ++ else if (mode == DefaultButtonWellMode.SPINNER) ++ actor = this._workSpinner.actor; ++ else if (mode == DefaultButtonWellMode.SESSION_MENU_BUTTON) ++ actor = this._sessionMenuButton.actor; ++ ++ return actor; ++ }, ++ ++ _setDefaultButtonWellMode: function(mode, immediately) { ++ if (this._defaultButtonWellMode == DefaultButtonWellMode.NONE && ++ mode == DefaultButtonWellMode.NONE) + 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(); +- } +- }); ++ let oldActor = this._getActorForDefaultButtonWellMode(this._defaultButtonWellMode); ++ ++ if (oldActor) ++ Tweener.removeTweens(oldActor); ++ ++ let actor = this._getActorForDefaultButtonWellMode(mode); ++ ++ if (this._defaultButtonWellMode != mode && oldActor) { ++ if (immediately) ++ 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 (mode == DefaultButtonWellMode.SPINNER) { ++ if (this._workSpinner) ++ this._workSpinner.stop(); ++ } ++ } ++ }); ++ ++ } ++ ++ if (actor) { ++ if (mode == DefaultButtonWellMode.SPINNER) ++ this._workSpinner.play(); ++ ++ if (immediately) ++ actor.opacity = 255; ++ else ++ Tweener.addTween(actor, ++ { opacity: 255, ++ time: _DEFAULT_BUTTON_WELL_ANIMATION_TIME, ++ delay: _DEFAULT_BUTTON_WELL_ANIMATION_DELAY, ++ transition: 'linear' }); + } ++ ++ this._defaultButtonWellMode = mode; + }, + + _verificationFailed: function() { + this._promptEntry.text = ''; + + this._updateSensitivity(true); +- this._setWorking(false); ++ this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _showMessage: function(userVerifier, message, styleClass) { + if (message) { + this._promptMessage.text = message; + this._promptMessage.styleClass = styleClass; + this._promptMessage.show(); + } else { + this._promptMessage.hide(); + } + }, + + _showLoginHint: function(verifier, message) { + this._promptLoginHint.set_text(message) + this._promptLoginHint.show(); + this._promptLoginHint.opacity = 255; + }, + + _hideLoginHint: function() { + this._promptLoginHint.hide(); + this._promptLoginHint.set_text(''); + }, + + cancel: function() { + if (this._verifyingUser) + this._userVerifier.cancel(); + else + this._reset(); + }, + ++ _shouldShowSessionMenuButton: function() { ++ if (this._verifyingUser) ++ return true; ++ ++ if (!this._user) ++ return false; ++ ++ if (this._user.is_logged_in) ++ return false; ++ ++ return true; ++ }, ++ + _showPrompt: function(forSecret) { +- this._sessionMenuButton.actor.hide(); + this._promptLabel.show(); + this._promptEntry.show(); + this._promptLoginHint.opacity = 0; + this._promptLoginHint.show(); + this._promptBox.opacity = 0; + this._promptBox.show(); + Tweener.addTween(this._promptBox, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + +- if ((this._user && !this._user.is_logged_in()) || this._verifyingUser) +- this._sessionMenuButton.actor.show(); ++ if (this._shouldShowSessionMenuButton()) ++ this._setDefaultButtonWellMode(DefaultButtonWellMode.SESSION_MENU_BUTTON, true); ++ else ++ this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); + + this._promptEntry.grab_key_focus(); + + let hold = new Batch.Hold(); + let tasks = [function() { + this._prepareDialog(forSecret, hold); + }, + + hold]; + + let batch = new Batch.ConcurrentBatch(this, tasks); + + return batch.run(); + }, + + _prepareDialog: function(forSecret, hold) { + this._buttonBox.visible = true; + this._buttonBox.remove_all_children(); + + if (!this._disableUserList || this._verifyingUser) { + 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: true, ++ { expand: false, + x_fill: false, + y_fill: false, + x_align: St.Align.START, + y_align: St.Align.END }); + } + +- let spinnerIcon = global.datadir + '/theme/process-working.svg'; +- this._workSpinner = new Panel.AnimatedIcon(spinnerIcon, _WORK_SPINNER_ICON_SIZE); +- this._workSpinner.actor.opacity = 0; +- this._workSpinner.actor.show(); +- +- this._buttonBox.add(this._workSpinner.actor, +- { expand: false, +- x_align: St.Align.END }); +- +- this._buttonBox.add(this._sessionMenuButton.actor, +- { expand: false, +- x_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._signInButton = new St.Button({ style_class: 'modal-dialog-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + reactive: true, + can_focus: true, + label: forSecret ? C_("button", "Sign In") : _("Next") }); + this._signInButton.connect('clicked', + Lang.bind(this, function() { + hold.release(); + })); + this._signInButton.add_style_pseudo_class('default'); + this._buttonBox.add(this._signInButton, +- { expand: true, ++ { expand: false, + x_fill: false, + y_fill: false, + x_align: St.Align.END, + y_align: St.Align.END }); + + this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); + + this._promptEntryTextChangedId = + this._promptEntry.clutter_text.connect('text-changed', + Lang.bind(this, function() { + this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); + })); + + this._promptEntryActivateId = + this._promptEntry.clutter_text.connect('activate', function() { + hold.release(); + }); + }, + + _updateSensitivity: function(sensitive) { + this._promptEntry.reactive = sensitive; + this._promptEntry.clutter_text.editable = sensitive; + this._sessionMenuButton.updateSensitivity(sensitive); + this._updateSignInButtonSensitivity(sensitive); + }, + + _updateSignInButtonSensitivity: function(sensitive) { + if (this._signInButton) { + this._signInButton.reactive = sensitive; + this._signInButton.can_focus = sensitive; + } + }, + + _hidePrompt: function() { + if (this._promptEntryTextChangedId > 0) { + this._promptEntry.clutter_text.disconnect(this._promptEntryTextChangedId); + this._promptEntryTextChangedId = 0; + } + + if (this._promptEntryActivateId > 0) { + this._promptEntry.clutter_text.disconnect(this._promptEntryActivateId); + this._promptEntryActivateId = 0; + } + +- this._setWorking(false); ++ this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); + this._promptBox.hide(); + this._promptLoginHint.hide(); + + this._promptUser.set_child(null); + + this._updateSensitivity(true); + this._promptEntry.set_text(''); + + this._sessionMenuButton.close(); + this._promptLoginHint.hide(); + + this._buttonBox.remove_all_children(); + this._signInButton = null; + this._cancelButton = null; + }, + + _askQuestion: function(verifier, serviceName, question, passwordChar) { + this._promptLabel.set_text(question); + + this._updateSensitivity(true); + this._promptEntry.set_text(''); + this._promptEntry.clutter_text.set_password_char(passwordChar); + + let tasks = [function() { + return this._showPrompt(!!passwordChar); + }, + + function() { + let text = this._promptEntry.get_text(); + this._updateSensitivity(false); +- this._setWorking(true); ++ this._setDefaultButtonWellMode(DefaultButtonWellMode.SPINNER, false); + this._userVerifier.answerQuery(serviceName, text); + }]; + + let batch = new Batch.ConsecutiveBatch(this, tasks); + return batch.run(); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + 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._showLoginHint(null, _("(e.g., user or %s)").format(hint)); + }, + + _askForUsernameAndLogIn: function() { + this._promptLabel.set_text(_("Username: ")); + this._promptEntry.set_text(''); + this._promptEntry.clutter_text.set_password_char(''); + + let realmManager = new Realmd.Manager(); + let signalId = realmManager.connect('login-format-changed', + Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + +-- +1.8.3.1 + + +From 4d225c2d2043762a345415b0438b6a92900410eb Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 9 Jul 2013 10:05:55 -0400 +Subject: [PATCH 06/66] loginDialog: don't call nonexistent setInitialKeyFocus + function + +commit ea02380c1524c28e6538ffedb789a12c298742ab changed the login +dialog to not use ModalDialog anymore. There's still one lingering +setInitialKeyFocus method call in the source, which will cause an +exception to be thrown when users have their user list disabled. + +This commit fixes that. + +https://bugzilla.gnome.org/show_bug.cgi?id=703874 +--- + js/gdm/loginDialog.js | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index f127460..9f90664 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -478,60 +478,62 @@ const LoginDialog = new Lang.Class({ + })); + + this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + this.actor.add_child(this._promptBox); + this._promptUser = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this._promptBox.add(this._promptUser, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this._promptBox.add(this._promptLabel, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + this._promptEntryTextChangedId = 0; + this._promptEntryActivateId = 0; + this._promptBox.add(this._promptEntry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + ++ this._promptEntry.grab_key_focus(); ++ + this._promptMessage = new St.Label({ visible: false }); + this._promptBox.add(this._promptMessage, { x_fill: true }); + + this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); + this._promptLoginHint.hide(); + this._promptBox.add(this._promptLoginHint); + + this._buttonBox = new St.BoxLayout({ style_class: 'modal-dialog-button-box', + vertical: false }); + this._promptBox.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + this._cancelButton = null; + this._signInButton = null; + + this._promptBox.hide(); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); +@@ -571,64 +573,60 @@ const LoginDialog = new Lang.Class({ + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + this._defaultButtonWell = new St.Widget(); + this._defaultButtonWellMode = DefaultButtonWellMode.NONE; + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._defaultButtonWell.add_child(this._sessionMenuButton.actor); + + let spinnerIcon = global.datadir + '/theme/process-working.svg'; + this._workSpinner = new Panel.AnimatedIcon(spinnerIcon, _DEFAULT_BUTTON_WELL_ICON_SIZE); + this._workSpinner.actor.opacity = 0; + this._workSpinner.actor.show(); + + this._defaultButtonWell.add_child(this._workSpinner.actor); + this._sessionMenuButton.actor.add_constraint(new Clutter.AlignConstraint({ source: this._workSpinner.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + +- // If this is the first time around, set initial focus +- if (this._disableUserList == undefined && disableUserList) +- this.setInitialKeyFocus(this._promptEntry); +- + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; + + if (!this._verifyingUser) + this._reset(); + } + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, +-- +1.8.3.1 + + +From 08b95ec01004a822bb456766797f7cae4e358340 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 19 Jun 2013 12:56:23 -0400 +Subject: [PATCH 07/66] loginDialog: pre-allocate prompt message height + +Right now things jump around if a message comes in. +This commit makes sure there's room for a message to start. + +https://bugzilla.gnome.org/show_bug.cgi?id=702308 +--- + js/gdm/loginDialog.js | 16 +++++++--------- + 1 file changed, 7 insertions(+), 9 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 9f90664..538364c 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -480,65 +480,64 @@ const LoginDialog = new Lang.Class({ + this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + this.actor.add_child(this._promptBox); + this._promptUser = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this._promptBox.add(this._promptUser, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this._promptBox.add(this._promptLabel, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + this._promptEntryTextChangedId = 0; + this._promptEntryActivateId = 0; + this._promptBox.add(this._promptEntry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._promptEntry.grab_key_focus(); + +- this._promptMessage = new St.Label({ visible: false }); ++ this._promptMessage = new St.Label({ opacity: 0 }); + this._promptBox.add(this._promptMessage, { x_fill: true }); + + this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); +- this._promptLoginHint.hide(); + this._promptBox.add(this._promptLoginHint); + + this._buttonBox = new St.BoxLayout({ style_class: 'modal-dialog-button-box', + vertical: false }); + this._promptBox.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + this._cancelButton = null; + this._signInButton = null; + + this._promptBox.hide(); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, +@@ -615,61 +614,61 @@ const LoginDialog = new Lang.Class({ + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _reset: function() { + this._userVerifier.clear(); + + this._updateSensitivity(true); +- this._promptMessage.hide(); ++ this._promptMessage.opacity = 0; + this._user = null; + this._verifyingUser = false; + + if (this._disableUserList) + this._hideUserListAndLogIn(); + else + this._showUserList(); + }, + + _getActorForDefaultButtonWellMode: function(mode) { + let actor; + + if (mode == DefaultButtonWellMode.NONE) + actor = null; + else if (mode == DefaultButtonWellMode.SPINNER) + actor = this._workSpinner.actor; + else if (mode == DefaultButtonWellMode.SESSION_MENU_BUTTON) + actor = this._sessionMenuButton.actor; + + return actor; + }, + + _setDefaultButtonWellMode: function(mode, immediately) { + if (this._defaultButtonWellMode == DefaultButtonWellMode.NONE && + mode == DefaultButtonWellMode.NONE) + return; + + let oldActor = this._getActorForDefaultButtonWellMode(this._defaultButtonWellMode); + + if (oldActor) +@@ -702,74 +701,73 @@ const LoginDialog = new Lang.Class({ + this._workSpinner.play(); + + if (immediately) + actor.opacity = 255; + else + Tweener.addTween(actor, + { opacity: 255, + time: _DEFAULT_BUTTON_WELL_ANIMATION_TIME, + delay: _DEFAULT_BUTTON_WELL_ANIMATION_DELAY, + transition: 'linear' }); + } + + this._defaultButtonWellMode = mode; + }, + + _verificationFailed: function() { + this._promptEntry.text = ''; + + this._updateSensitivity(true); + this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _showMessage: function(userVerifier, message, styleClass) { + if (message) { + this._promptMessage.text = message; + this._promptMessage.styleClass = styleClass; +- this._promptMessage.show(); ++ this._promptMessage.opacity = 255; + } else { +- this._promptMessage.hide(); ++ this._promptMessage.opacity = 0; + } + }, + + _showLoginHint: function(verifier, message) { + this._promptLoginHint.set_text(message) +- this._promptLoginHint.show(); + this._promptLoginHint.opacity = 255; + }, + + _hideLoginHint: function() { +- this._promptLoginHint.hide(); ++ this._promptLoginHint.opacity = 0; + this._promptLoginHint.set_text(''); + }, + + cancel: function() { + if (this._verifyingUser) + this._userVerifier.cancel(); + else + this._reset(); + }, + + _shouldShowSessionMenuButton: function() { + if (this._verifyingUser) + return true; + + if (!this._user) + return false; + + if (this._user.is_logged_in) + return false; + + return true; + }, + + _showPrompt: function(forSecret) { + this._promptLabel.show(); + this._promptEntry.show(); + this._promptLoginHint.opacity = 0; + this._promptLoginHint.show(); + this._promptBox.opacity = 0; + this._promptBox.show(); +@@ -856,69 +854,69 @@ const LoginDialog = new Lang.Class({ + }); + }, + + _updateSensitivity: function(sensitive) { + this._promptEntry.reactive = sensitive; + this._promptEntry.clutter_text.editable = sensitive; + this._sessionMenuButton.updateSensitivity(sensitive); + this._updateSignInButtonSensitivity(sensitive); + }, + + _updateSignInButtonSensitivity: function(sensitive) { + if (this._signInButton) { + this._signInButton.reactive = sensitive; + this._signInButton.can_focus = sensitive; + } + }, + + _hidePrompt: function() { + if (this._promptEntryTextChangedId > 0) { + this._promptEntry.clutter_text.disconnect(this._promptEntryTextChangedId); + this._promptEntryTextChangedId = 0; + } + + if (this._promptEntryActivateId > 0) { + this._promptEntry.clutter_text.disconnect(this._promptEntryActivateId); + this._promptEntryActivateId = 0; + } + + this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); + this._promptBox.hide(); +- this._promptLoginHint.hide(); ++ this._promptLoginHint.opacity = 0; + + this._promptUser.set_child(null); + + this._updateSensitivity(true); + this._promptEntry.set_text(''); + + this._sessionMenuButton.close(); +- this._promptLoginHint.hide(); ++ this._promptLoginHint.opacity = 0; + + this._buttonBox.remove_all_children(); + this._signInButton = null; + this._cancelButton = null; + }, + + _askQuestion: function(verifier, serviceName, question, passwordChar) { + this._promptLabel.set_text(question); + + this._updateSensitivity(true); + this._promptEntry.set_text(''); + this._promptEntry.clutter_text.set_password_char(passwordChar); + + let tasks = [function() { + return this._showPrompt(!!passwordChar); + }, + + function() { + let text = this._promptEntry.get_text(); + this._updateSensitivity(false); + this._setDefaultButtonWellMode(DefaultButtonWellMode.SPINNER, false); + this._userVerifier.answerQuery(serviceName, text); + }]; + + let batch = new Batch.ConsecutiveBatch(this, tasks); + return batch.run(); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) +-- +1.8.3.1 + + +From a4efe892b4cdbabaaca1f0326627183906a4b760 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 19 Jun 2013 09:23:14 -0400 +Subject: [PATCH 08/66] loginDialog: drop padding between buttons and entry + +Now that we preallocate space for the prompt message there is +a lot of loose space between the entry and the buttons. + +This commit helps tighten things up by getting rid +of the large top padding set above the login buttons. + +https://bugzilla.gnome.org/show_bug.cgi?id=702308 +--- + data/theme/gnome-shell.css | 4 ++++ + js/gdm/loginDialog.js | 2 +- + 2 files changed, 5 insertions(+), 1 deletion(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 4c0a795..d871385 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2210,60 +2210,64 @@ StScrollBar StButton#vhandle:active { + + .login-dialog-banner { + font-size: 10pt; + font-weight: bold; + text-align: center; + color: #666666; + padding-bottom: 1em; + } + + .login-dialog-title { + font-size: 14pt; + font-weight: bold; + color: #666666; + padding-bottom: 2em; + } + + .login-dialog { + /* Reset border and background */ + border: none; + background-color: transparent; + + padding-bottom: 80px; + padding-top: 80px; + + border-radius: 16px; + min-height: 150px; + max-height: 700px; + min-width: 350px; + } + ++.login-dialog-button-box { ++ spacing: 5px; ++} ++ + .login-dialog-prompt-login-hint-message { + font-size: 10.5pt; + } + + .login-dialog-user-list-view { + -st-vfade-offset: 1em; + } + + .login-dialog-user-list { + spacing: 12px; + padding: .2em; + } + + .login-dialog-user-list-item { + border-radius: 10px; + padding: .2em; + } + + .login-dialog-user-list-item:ltr { + padding-right: 1em; + } + + .login-dialog-user-list-item:rtl { + padding-left: 1em; + } + + .login-dialog-user-list-item .login-dialog-user-list-item-name { + font-size: 20pt; + padding-left: 1em; + } +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 538364c..365b45f 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -486,61 +486,61 @@ const LoginDialog = new Lang.Class({ + this._promptBox.add(this._promptUser, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this._promptBox.add(this._promptLabel, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + this._promptEntryTextChangedId = 0; + this._promptEntryActivateId = 0; + this._promptBox.add(this._promptEntry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._promptEntry.grab_key_focus(); + + this._promptMessage = new St.Label({ opacity: 0 }); + this._promptBox.add(this._promptMessage, { x_fill: true }); + + this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); + this._promptBox.add(this._promptLoginHint); + +- this._buttonBox = new St.BoxLayout({ style_class: 'modal-dialog-button-box', ++ this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', + vertical: false }); + this._promptBox.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + this._cancelButton = null; + this._signInButton = null; + + this._promptBox.hide(); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); +-- +1.8.3.1 + + +From ea249fd9f79acf123f3603aa2f21369c99e98e8f Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 26 Jun 2013 22:32:07 -0400 +Subject: [PATCH 09/66] loginDialog: make prompt entry wider + +This makes it match mock ups better and looks more visually +pleasing. + +https://bugzilla.gnome.org/show_bug.cgi?id=702308 +--- + data/theme/gnome-shell.css | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index d871385..14ee74e 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2323,61 +2323,61 @@ StScrollBar StButton#vhandle:active { + color: #666666; + padding-top: 1em; + padding-left: 2px; + } + + .login-dialog-not-listed-button:focus .login-dialog-not-listed-label, + .login-dialog-not-listed-button:hover .login-dialog-not-listed-label { + color: #E8E8E8; + } + + .login-dialog-username { + font-size: 16pt; + font-weight: bold; + text-align: left; + padding-left: 15px; + text-shadow: black 0px 4px 3px 0px; + } + + .login-dialog-prompt-layout { + padding-top: 24px; + padding-bottom: 12px; + spacing: 8px; + } + + .login-dialog-prompt-label { + color: #eeeeee; + font-size: 14px; + } + + .login-dialog-prompt-entry { +- width: 15em; ++ width: 20em; + } + + .login-dialog-session-list-button StIcon { + icon-size: 1.25em; + } + + .login-dialog-session-list-button { + color: #8b8b8b; + } + + .login-dialog-session-list-button:hover, + .login-dialog-session-list-button:active { + color: white; + } + + .login-dialog-logo-bin { + padding: 24px 0px; + } + + .login-dialog .modal-dialog-button-box { + spacing: 3px; + } + + .login-dialog .modal-dialog-button { + border-radius: 5px; + padding: 3px 18px; + } + + .login-dialog .modal-dialog-button:focus { + padding: 2px 17px; +-- +1.8.3.1 + + +From d42742e678ac9b1fd26ac923b96750af01ff3987 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 26 Jun 2013 12:36:10 -0400 +Subject: [PATCH 10/66] loginDialog: force user list and prompt to be the same + width + +--- + js/gdm/loginDialog.js | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 365b45f..15a2e0a 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -453,61 +453,65 @@ const LoginDialog = new Lang.Class({ + vertical: true }); + this._userSelectionBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + + this._promptBox = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', + vertical: true }); + + this._promptBox.connect('button-press-event', + Lang.bind(this, function(actor, event) { + if (event.get_key_symbol() == Clutter.KEY_Escape) { + this.cancel(); + } + })); + + this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); ++ + this.actor.add_child(this._promptBox); ++ this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._promptBox, ++ coordinate: Clutter.BindCoordinate.WIDTH })); ++ + this._promptUser = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this._promptBox.add(this._promptUser, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this._promptBox.add(this._promptLabel, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + this._promptEntryTextChangedId = 0; + this._promptEntryActivateId = 0; + this._promptBox.add(this._promptEntry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._promptEntry.grab_key_focus(); + + this._promptMessage = new St.Label({ opacity: 0 }); + this._promptBox.add(this._promptMessage, { x_fill: true }); + + this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); +-- +1.8.3.1 + + +From d593c99700fda79b4e43822f6c50294472e61107 Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Tue, 16 Jul 2013 15:48:27 -0400 +Subject: [PATCH 11/66] util: Fix hasPendingMessages + +While the UserVerifier does indeed have a _userVerifier inside +it, the hasPendingMessages property is on ourselves, not +_userVerifier. + +https://bugzilla.gnome.org/show_bug.cgi?id=704347 +--- + js/gdm/util.js | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index cae3e1b..c79958d 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -136,61 +136,61 @@ const ShellUserVerifier = new Lang.Class({ + this._client.open_reauthentication_channel(userName, this._cancellable, + Lang.bind(this, this._reauthenticationChannelOpened)); + } else { + this._client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + } + }, + + cancel: function() { + if (this._cancellable) + this._cancellable.cancel(); + + if (this._userVerifier) + this._userVerifier.call_cancel_sync(null); + }, + + clear: function() { + if (this._cancellable) { + this._cancellable.cancel(); + this._cancellable = null; + } + + if (this._userVerifier) { + this._userVerifier.run_dispose(); + this._userVerifier = null; + } + + this._clearMessageQueue(); + }, + + answerQuery: function(serviceName, answer) { +- if (!this._userVerifier.hasPendingMessages) { ++ if (!this.hasPendingMessages) { + this._clearMessageQueue(); + this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); + } else { + let signalId = this._userVerifier.connect('no-more-messages', + Lang.bind(this, function() { + this._userVerifier.disconnect(signalId); + this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, 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; +@@ -396,66 +396,66 @@ const ShellUserVerifier = new Lang.Class({ + // Clear previous attempts to authenticate + this._failCounter = 0; + + this.emit('reset'); + }, + + _onVerificationComplete: function() { + 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 + // Otherwise, we allow ALLOWED_FAILURES attempts. After that, we + // go back to the welcome screen. + + this._failCounter++; + let canRetry = retry && this._userName && + this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY); + + if (canRetry) { +- if (!this._userVerifier.hasPendingMessages) { ++ if (!this.hasPendingMessages) { + this._retry(); + } else { + let signalId = this._userVerifier.connect('no-more-messages', + Lang.bind(this, function() { + this._userVerifier.disconnect(signalId); + this._retry(); + })); + } + } else { +- if (!this._userVerifier.hasPendingMessages) { ++ if (!this.hasPendingMessages) { + this._cancelAndReset(); + } else { + let signalId = this._userVerifier.connect('no-more-messages', + Lang.bind(this, function() { + this._userVerifier.disconnect(signalId); + this._cancelAndReset(); + })); + } + } + + this.emit('verification-failed'); + }, + + _onConversationStopped: function(client, serviceName) { + // if the password service fails, then cancel everything. + // But if, e.g., fingerprint fails, still give + // password authentication a chance to succeed + if (serviceName == PASSWORD_SERVICE_NAME) { + this._verificationFailed(true); + } + + this.emit('hide-login-hint'); + }, + }); + Signals.addSignalMethods(ShellUserVerifier.prototype); +-- +1.8.3.1 + + +From 2f36ddcf43154fc68b7a6e115ca4ff708a59348f Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 17 Jul 2013 13:06:10 -0400 +Subject: [PATCH 12/66] util: Fix no-more-messages signal + +Now thas hasPendingMessages is fixed, we need to also fix the associated +signal "no-more-messages" + +https://bugzilla.gnome.org/show_bug.cgi?id=704347 +--- + js/gdm/util.js | 30 +++++++++++++++--------------- + 1 file changed, 15 insertions(+), 15 deletions(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index c79958d..04b2d49 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -140,65 +140,65 @@ const ShellUserVerifier = new Lang.Class({ + } + }, + + cancel: function() { + if (this._cancellable) + this._cancellable.cancel(); + + if (this._userVerifier) + this._userVerifier.call_cancel_sync(null); + }, + + clear: function() { + if (this._cancellable) { + this._cancellable.cancel(); + this._cancellable = null; + } + + if (this._userVerifier) { + this._userVerifier.run_dispose(); + this._userVerifier = null; + } + + this._clearMessageQueue(); + }, + + answerQuery: function(serviceName, answer) { + if (!this.hasPendingMessages) { + this._clearMessageQueue(); + this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); + } else { +- let signalId = this._userVerifier.connect('no-more-messages', +- Lang.bind(this, function() { +- this._userVerifier.disconnect(signalId); +- this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); +- })); ++ let signalId = this.connect('no-more-messages', ++ Lang.bind(this, function() { ++ this.disconnect(signalId); ++ this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); ++ })); + } + }, + + _getIntervalForMessage: function(message) { + // 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.iconName); + +@@ -399,63 +399,63 @@ const ShellUserVerifier = new Lang.Class({ + this.emit('reset'); + }, + + _onVerificationComplete: function() { + 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 + // Otherwise, we allow ALLOWED_FAILURES attempts. After that, we + // go back to the welcome screen. + + this._failCounter++; + let canRetry = retry && this._userName && + this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY); + + if (canRetry) { + if (!this.hasPendingMessages) { + this._retry(); + } else { +- let signalId = this._userVerifier.connect('no-more-messages', +- Lang.bind(this, function() { +- this._userVerifier.disconnect(signalId); +- this._retry(); +- })); ++ let signalId = this.connect('no-more-messages', ++ Lang.bind(this, function() { ++ this.disconnect(signalId); ++ this._retry(); ++ })); + } + } else { + if (!this.hasPendingMessages) { + this._cancelAndReset(); + } else { +- let signalId = this._userVerifier.connect('no-more-messages', +- Lang.bind(this, function() { +- this._userVerifier.disconnect(signalId); +- this._cancelAndReset(); +- })); ++ let signalId = this.connect('no-more-messages', ++ Lang.bind(this, function() { ++ this.disconnect(signalId); ++ this._cancelAndReset(); ++ })); + } + } + + this.emit('verification-failed'); + }, + + _onConversationStopped: function(client, serviceName) { + // if the password service fails, then cancel everything. + // But if, e.g., fingerprint fails, still give + // password authentication a chance to succeed + if (serviceName == PASSWORD_SERVICE_NAME) { + this._verificationFailed(true); + } + + this.emit('hide-login-hint'); + }, + }); + Signals.addSignalMethods(ShellUserVerifier.prototype); +-- +1.8.3.1 + + +From c377ccb3a6b5e2ad274cc3ab339137a346a8c702 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 17 Jul 2013 16:02:55 -0400 +Subject: [PATCH 13/66] util: drop call that can't do anything + +this._clearMessageQueue() is a noop when this.hasPendingMessages is +false so calling it in that case doesn't make sense. + +This commit drops that call. + +https://bugzilla.gnome.org/show_bug.cgi?id=704347 +--- + js/gdm/util.js | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 04b2d49..c9af991 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -137,61 +137,60 @@ const ShellUserVerifier = new Lang.Class({ + Lang.bind(this, this._reauthenticationChannelOpened)); + } else { + this._client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + } + }, + + cancel: function() { + if (this._cancellable) + this._cancellable.cancel(); + + if (this._userVerifier) + this._userVerifier.call_cancel_sync(null); + }, + + clear: function() { + if (this._cancellable) { + this._cancellable.cancel(); + this._cancellable = null; + } + + if (this._userVerifier) { + this._userVerifier.run_dispose(); + this._userVerifier = null; + } + + this._clearMessageQueue(); + }, + + answerQuery: function(serviceName, answer) { + if (!this.hasPendingMessages) { +- this._clearMessageQueue(); + 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); + })); + } + }, + + _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; + } +-- +1.8.3.1 + + +From a0bb4ea835b5d3fbee721101f2b018c1f3ffabc8 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 17 Jul 2013 16:42:33 -0400 +Subject: [PATCH 14/66] unlockDialog: don't unlock explicitly on + verification-complete + +logind sends out an "unlock" signal separately when +verification completes and we already listen for that, +so we don't need to unlock on verification-complete, too. + +https://bugzilla.gnome.org/show_bug.cgi?id=704347 +--- + js/ui/screenShield.js | 5 ----- + js/ui/unlockDialog.js | 14 -------------- + 2 files changed, 19 deletions(-) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 4264f01..0aa9758 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -889,74 +889,69 @@ const ScreenShield = new Lang.Class({ + onComplete: Lang.bind(this, this._hideLockScreenComplete), + }); + } else { + this._hideLockScreenComplete(); + } + + global.stage.show_cursor(); + }, + + _ensureUnlockDialog: function(onPrimary, allowCancel) { + if (!this._dialog) { + let constructor = Main.sessionMode.unlockDialog; + if (!constructor) { + // This session mode has no locking capabilities + this.deactivate(true); + return; + } + + this._dialog = new constructor(this._lockDialogGroup); + + + let time = global.get_current_time(); + if (!this._dialog.open(time, onPrimary)) { + // This is kind of an impossible error: we're already modal + // by the time we reach this... + log('Could not open login dialog: failed to acquire grab'); + this.deactivate(true); + } + + this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed)); +- this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded)); + } + + this._dialog.allowCancel = allowCancel; + }, + + _onUnlockFailed: function() { + this._resetLockScreen(true, false); + }, + +- _onUnlockSucceded: function() { +- this.deactivate(true); +- }, +- + _resetLockScreen: function(animateLockScreen, animateLockDialog) { + // Don't reset the lock screen unless it is completely hidden + // This prevents the shield going down if the lock-delay timeout + // fires while the user is dragging (which has the potential + // to confuse our state) + if (this._lockScreenState != MessageTray.State.HIDDEN) + return; + + this._ensureLockScreen(); + this._lockDialogGroup.scale_x = 1; + this._lockDialogGroup.scale_y = 1; + + this._lockScreenGroup.show(); + this._lockScreenState = MessageTray.State.SHOWING; + + if (animateLockScreen) { + this._lockScreenGroup.y = -global.screen_height; + Tweener.removeTweens(this._lockScreenGroup); + Tweener.addTween(this._lockScreenGroup, + { y: 0, + time: MANUAL_FADE_TIME, + transition: 'easeOutQuad', + onComplete: function() { + this._lockScreenShown(); + }, + onCompleteScope: this + }); + } else { + this._lockScreenGroup.fixed_position_set = false; + this._lockScreenShown(); +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 16fdbfa..1d60448 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -197,76 +197,62 @@ const UnlockDialog = new Lang.Class({ + GdmUtil.fadeInActor(this._promptLoginHint); + }, + + _hideLoginHint: function() { + GdmUtil.fadeOutActor(this._promptLoginHint); + }, + + _doUnlock: function() { + if (this._firstQuestion) { + // we haven't received a query yet, so stash the answer + // and make ourself non-reactive + // the actual reply to GDM will be sent as soon as asked + this._firstQuestionAnswer = this._promptEntry.text; + this._updateSensitivity(false); + this.setWorking(true); + return; + } + + if (!this._currentQuery) + return; + + let query = this._currentQuery; + this._currentQuery = null; + + this._updateSensitivity(false); + this.setWorking(true); + + this._userVerifier.answerQuery(query, this._promptEntry.text); + }, + +- _finishUnlock: function() { +- this._userVerifier.clear(); +- this.emit('unlocked'); +- }, +- + _onVerificationComplete: function() { + this._userVerified = true; +- if (!this._userVerifier.hasPendingMessages) { +- this._finishUnlock(); +- } else { +- let signalId = this._userVerifier.connect('no-more-messages', +- Lang.bind(this, function() { +- this._userVerifier.disconnect(signalId); +- this._finishUnlock(); +- })); +- } + }, + + _onReset: function() { + if (!this._userVerified) { + this._userVerifier.clear(); + this.emit('failed'); + } + }, + + _onVerificationFailed: function() { + this._currentQuery = null; + this._firstQuestion = true; + this._userVerified = false; + + this._promptEntry.text = ''; + this._promptEntry.clutter_text.set_password_char('\u25cf'); + this._promptEntry.menu.isPassword = true; + + this._updateSensitivity(false); + this.setWorking(false); + }, + + _escape: function() { + if (this.allowCancel) { + this._userVerifier.cancel(); + this.emit('failed'); + } + }, + + _otherUserClicked: function(button, event) { +-- +1.8.3.1 + + +From 9d2bdeecc8a51db49a394b8fd5f87764d8735ca7 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 18 Jul 2013 08:58:58 -0400 +Subject: [PATCH 15/66] screenShield: defer deactivation until all messages are + shown + +Right now when a user types their password to unlock their session +we end up getting an unlock signal from GDM right away. We then +proceed to deactivate the screensaver before the user has a chance +to read his messages. + +This commit makes sure we clear out the message queue before processing +the deactivation request. + +https://bugzilla.gnome.org/show_bug.cgi?id=704347 +--- + js/ui/screenShield.js | 6 ++++++ + js/ui/unlockDialog.js | 14 ++++++++++++++ + 2 files changed, 20 insertions(+) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 0aa9758..d9fe883 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -1083,60 +1083,66 @@ const ScreenShield = new Lang.Class({ + + _clearLockScreen: function() { + this._clock.destroy(); + this._clock = null; + + if (this._notificationsBox) { + this._notificationsBox.destroy(); + this._notificationsBox = null; + } + + this._stopArrowAnimation(); + + this._lockScreenContentsBox.destroy(); + + this._hasLockScreen = false; + }, + + get locked() { + return this._isLocked; + }, + + get active() { + return this._isActive; + }, + + get activationTime() { + return this._activationTime; + }, + + deactivate: function(animate) { ++ this._dialog.finish(Lang.bind(this, function() { ++ this._finishDeactivate(animate); ++ })); ++ }, ++ ++ _finishDeactivate: function(animate) { + this._hideLockScreen(animate, 0); + + if (this._hasLockScreen) + this._clearLockScreen(); + + if (Main.sessionMode.currentMode == 'lock-screen') + Main.sessionMode.popMode('lock-screen'); + if (Main.sessionMode.currentMode == 'unlock-dialog') + Main.sessionMode.popMode('unlock-dialog'); + + Tweener.addTween(this._lockDialogGroup, { + scale_x: 0, + scale_y: 0, + time: animate ? Overview.ANIMATION_TIME : 0, + transition: 'easeOutQuad', + onComplete: Lang.bind(this, this._completeDeactivate), + onCompleteScope: this + }); + }, + + _completeDeactivate: function() { + if (this._dialog && !this._isGreeter) { + this._dialog.destroy(); + this._dialog = null; + } + + this._lightbox.hide(); + + if (this._isModal) { + Main.popModal(this.actor); +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 1d60448..25bd92c 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -255,31 +255,45 @@ const UnlockDialog = new Lang.Class({ + } + }, + + _otherUserClicked: function(button, event) { + Gdm.goto_login_session_sync(null); + + this._userVerifier.cancel(); + this.emit('failed'); + }, + + destroy: function() { + this._userVerifier.clear(); + + if (this._idleWatchId) { + this._idleMonitor.remove_watch(this._idleWatchId); + this._idleWatchId = 0; + } + + this.parent(); + }, + + cancel: function() { + this._userVerifier.cancel(null); + + this.destroy(); + }, + + addCharacter: function(unichar) { + this._promptEntry.clutter_text.insert_unichar(unichar); + }, ++ ++ 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(); ++ })); ++ ++ } + }); +-- +1.8.3.1 + + +From 7e1dd618980c490634bf42fcc6fab7ee4d994452 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 18 Jul 2013 10:13:32 -0400 +Subject: [PATCH 16/66] loginDialog: avoid blinking user list when + disable-user-list=true + +Right now if disable-user-list is true we show it briefly, just so +that we can fade it out to the user entry. + +This commit avoids the fade in that case. + +https://bugzilla.gnome.org/show_bug.cgi?id=704471 +--- + js/gdm/loginDialog.js | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 15a2e0a..e132c72 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -423,61 +423,62 @@ const LoginDialog = new Lang.Class({ + Lang.bind(this, this._onSessionOpened)); + this._greeter.connect('timed-login-requested', + Lang.bind(this, this._onTimedLoginRequested)); + } + + this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient); + this._userVerifier.connect('ask-question', Lang.bind(this, this._askQuestion)); + this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage)); + this._userVerifier.connect('verification-failed', Lang.bind(this, this._verificationFailed)); + this._userVerifier.connect('reset', Lang.bind(this, this._reset)); + this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint)); + this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint)); + this._verifyingUser = false; + + this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA }); + + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', +- vertical: true }); ++ vertical: true, ++ visible: false }); + this._userSelectionBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + + this._promptBox = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', + vertical: true }); + + this._promptBox.connect('button-press-event', + Lang.bind(this, function(actor, event) { + if (event.get_key_symbol() == Clutter.KEY_Escape) { + this.cancel(); + } + })); + + this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + +@@ -1101,61 +1102,62 @@ const LoginDialog = new Lang.Class({ + _onTimedLoginRequested: function(client, userName, seconds) { + this._startTimedLogin(userName, seconds); + + global.stage.connect('captured-event', + Lang.bind(this, function(actor, event) { + if (this._timedLoginDelay == undefined) + return false; + + if (event.type() == Clutter.EventType.KEY_PRESS || + event.type() == Clutter.EventType.BUTTON_PRESS) { + if (this._timedLoginBatch) { + this._timedLoginBatch.cancel(); + this._timedLoginBatch = null; + } + } else if (event.type() == Clutter.EventType.KEY_RELEASE || + event.type() == Clutter.EventType.BUTTON_RELEASE) { + this._resetTimedLogin(); + } + + return false; + })); + }, + + _setUserListExpanded: function(expanded) { + this._userList.updateStyle(expanded); + this._userSelectionBox.visible = expanded; + }, + + _hideUserListAndLogIn: function() { + this._setUserListExpanded(false); +- GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); ++ if (this._userSelectionBox.visible) ++ GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + this._askForUsernameAndLogIn(); + }, + + _showUserList: function() { + this._hidePrompt(); + this._setUserListExpanded(true); + this._notListedButton.show(); + this._userList.actor.grab_key_focus(); + }, + + _beginVerificationForUser: function(userName) { + let hold = new Batch.Hold(); + + this._userVerifier.begin(userName, hold); + this._verifyingUser = true; + return hold; + }, + + _beginVerificationForItem: function(item) { + let userWidget = new UserWidget.UserWidget(item.user); + this._promptUser.set_child(userWidget.actor); + + let tasks = [function() { + let userName = item.user.get_user_name(); + return this._beginVerificationForUser(userName); + }]; + let batch = new Batch.ConsecutiveBatch(this, tasks); + return batch.run(); + }, + +-- +1.8.3.1 + + +From 8f2344677e73fad818ed8f160b6fd4affdb6777c Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 17 Jul 2013 11:58:05 -0400 +Subject: [PATCH 17/66] unlockDialog: drop unused variable + +https://bugzilla.gnome.org/show_bug.cgi?id=702308 +--- + js/ui/unlockDialog.js | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 25bd92c..50e9502 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -15,61 +15,60 @@ const St = imports.gi.St; + const Main = imports.ui.main; + const ModalDialog = imports.ui.modalDialog; + const Panel = imports.ui.panel; + const ShellEntry = imports.ui.shellEntry; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; + const LoginDialog = imports.gdm.loginDialog; + + // The timeout before going back automatically to the lock screen (in seconds) + const IDLE_TIMEOUT = 2 * 60; + + const UnlockDialog = new Lang.Class({ + Name: 'UnlockDialog', + Extends: ModalDialog.ModalDialog, + + _init: function(parentActor) { + this.parent({ shellReactive: true, + styleClass: 'login-dialog', + keybindingMode: Shell.KeyBindingMode.UNLOCK_SCREEN, + parentActor: parentActor + }); + + this._userManager = AccountsService.UserManager.get_default(); + this._userName = GLib.get_user_name(); + this._user = this._userManager.get_user(this._userName); + +- this._failCounter = 0; + this._firstQuestion = true; + + this._greeterClient = new Gdm.Client(); + this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true }); + this._userVerified = false; + + this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion)); + this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed)); + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); + + this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint)); + this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint)); + + this._userWidget = new UserWidget.UserWidget(this._user); + this.contentLayout.add_actor(this._userWidget.actor); + + this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', + vertical: true }); + + this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); + this._promptLayout.add(this._promptLabel, + { x_align: St.Align.START }); + + this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + this._promptEntry.clutter_text.connect('activate', Lang.bind(this, this._doUnlock)); + this._promptEntry.clutter_text.set_password_char('\u25cf'); + ShellEntry.addContextMenu(this._promptEntry, { isPassword: true }); +-- +1.8.3.1 + + +From e2435d944d8fa155f9e02ba256fd6c746abcfe7a Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 17 Jul 2013 09:54:03 -0400 +Subject: [PATCH 18/66] loginDialog: s/button-press-event/key-press-event/ + +A bug got introduced when moving the login dialog away from modal +dialog, such that it listens for escape key presses in a mouse +event handler instead of a keyboard event handler. + +This commit fixes that code to correctly listen for key-press-event +instead of button-press-event. + +https://bugzilla.gnome.org/show_bug.cgi?id=702308 +--- + js/gdm/loginDialog.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index e132c72..a9ca615 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -444,61 +444,61 @@ const LoginDialog = new Lang.Class({ + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + vertical: true, + visible: false }); + this._userSelectionBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + + this._promptBox = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', + vertical: true }); + +- this._promptBox.connect('button-press-event', ++ this._promptBox.connect('key-press-event', + Lang.bind(this, function(actor, event) { + if (event.get_key_symbol() == Clutter.KEY_Escape) { + this.cancel(); + } + })); + + this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + + this.actor.add_child(this._promptBox); + this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._promptBox, + coordinate: Clutter.BindCoordinate.WIDTH })); + + this._promptUser = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this._promptBox.add(this._promptUser, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this._promptBox.add(this._promptLabel, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); +-- +1.8.3.1 + + +From 8115fb33c1f69436a053d8e3322a6762f7231d7e Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 15 Jul 2013 17:56:44 -0400 +Subject: [PATCH 19/66] loginDialog: factor auth prompt code out to utils + +Right now there is a lot of duplicated code between the unlock +dialog and the login dialog. + +This commit moves the login dialog's auth prompt to a separate +class, so that it can (in a subsequent commit) be used by the +unlock dialog. + +https://bugzilla.gnome.org/show_bug.cgi?id=702308 +--- + js/gdm/loginDialog.js | 328 +++++++++----------------------------------------- + js/gdm/util.js | 283 +++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 337 insertions(+), 274 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index a9ca615..e9dd6b2 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -5,89 +5,79 @@ + * 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 AccountsService = imports.gi.AccountsService; + const Atk = imports.gi.Atk; + const Clutter = imports.gi.Clutter; + const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; + const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + +-const Panel = imports.ui.panel; + const Batch = imports.gdm.batch; + const BoxPointer = imports.ui.boxpointer; + const CtrlAltTab = imports.ui.ctrlAltTab; + const GdmUtil = imports.gdm.util; + const Layout = imports.ui.layout; + const Main = imports.ui.main; + const PopupMenu = imports.ui.popupMenu; + const Realmd = imports.gdm.realmd; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const _FADE_ANIMATION_TIME = 0.25; + const _SCROLL_ANIMATION_TIME = 0.5; +-const _DEFAULT_BUTTON_WELL_ICON_SIZE = 24; +-const _DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; +-const _DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _LOGO_ICON_HEIGHT = 48; + + let _loginDialog = null; + +-const DefaultButtonWellMode = { +- NONE: 0, +- SESSION_MENU_BUTTON: 1, +- SPINNER: 2 +-}; +- + const UserListItem = new Lang.Class({ + Name: 'UserListItem', + + _init: function(user) { + this.user = user; + this._userChangedId = this.user.connect('changed', + Lang.bind(this, this._onUserChanged)); + + let layout = new St.BoxLayout({ vertical: false }); + this.actor = new St.Button({ style_class: 'login-dialog-user-list-item', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: layout, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._userAvatar = new UserMenu.UserAvatarWidget(this.user, + { styleClass: 'login-dialog-user-list-item-icon' }); + layout.add(this._userAvatar.actor); + let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box', + vertical: true }); + layout.add(textLayout, { expand: true }); + + this._nameLabel = new St.Label({ style_class: 'login-dialog-user-list-item-name' }); + this.actor.label_actor = this._nameLabel; + textLayout.add(this._nameLabel, + { y_fill: false, + y_align: St.Align.MIDDLE, + expand: true }); +@@ -441,543 +431,333 @@ const LoginDialog = new Lang.Class({ + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + vertical: true, + visible: false }); + this._userSelectionBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + +- this._promptBox = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', +- vertical: true }); +- +- this._promptBox.connect('key-press-event', +- Lang.bind(this, function(actor, event) { +- if (event.get_key_symbol() == Clutter.KEY_Escape) { ++ this._authPrompt = new GdmUtil.AuthPrompt(); ++ this._authPrompt.hide(); ++ this._authPrompt.connect('cancel', ++ Lang.bind(this, function() { + this.cancel(); +- } + })); + +- this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, +- align_axis: Clutter.AlignAxis.BOTH, +- factor: 0.5 })); ++ this._authPrompt.actor.add_constraint(new Clutter.AlignConstraint({ source: this.actor, ++ align_axis: Clutter.AlignAxis.BOTH, ++ factor: 0.5 })); + +- this.actor.add_child(this._promptBox); +- this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._promptBox, ++ this.actor.add_child(this._authPrompt.actor); ++ this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._authPrompt.actor, + coordinate: Clutter.BindCoordinate.WIDTH })); + +- this._promptUser = new St.Bin({ x_fill: true, +- x_align: St.Align.START }); +- this._promptBox.add(this._promptUser, +- { x_align: St.Align.START, +- x_fill: true, +- y_fill: true, +- expand: true }); +- this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); +- +- this._promptBox.add(this._promptLabel, +- { expand: true, +- x_fill: true, +- y_fill: true, +- x_align: St.Align.START }); +- this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', +- can_focus: true }); +- this._promptEntryTextChangedId = 0; +- this._promptEntryActivateId = 0; +- this._promptBox.add(this._promptEntry, +- { expand: true, +- x_fill: true, +- y_fill: false, +- x_align: St.Align.START }); +- +- this._promptEntry.grab_key_focus(); +- +- this._promptMessage = new St.Label({ opacity: 0 }); +- this._promptBox.add(this._promptMessage, { x_fill: true }); +- +- this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); +- this._promptBox.add(this._promptLoginHint); +- +- this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', +- vertical: false }); +- this._promptBox.add(this._buttonBox, +- { expand: true, +- x_align: St.Align.MIDDLE, +- y_align: St.Align.END }); +- this._cancelButton = null; +- this._signInButton = null; +- +- this._promptBox.hide(); +- + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + + this._logoBin = new St.Bin({ style_class: 'login-dialog-logo-bin', y_expand: true }); + this._logoBin.set_y_align(Clutter.ActorAlign.END); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.X_AXIS, + factor: 0.5 })); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.Y_AXIS, + factor: 1.0 })); + this.actor.add_child(this._logoBin); + this._updateLogo(); + + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + +- this._defaultButtonWell = new St.Widget(); +- this._defaultButtonWellMode = DefaultButtonWellMode.NONE; + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); +- this._defaultButtonWell.add_child(this._sessionMenuButton.actor); ++ this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + +- let spinnerIcon = global.datadir + '/theme/process-working.svg'; +- this._workSpinner = new Panel.AnimatedIcon(spinnerIcon, _DEFAULT_BUTTON_WELL_ICON_SIZE); +- this._workSpinner.actor.opacity = 0; +- this._workSpinner.actor.show(); +- +- this._defaultButtonWell.add_child(this._workSpinner.actor); +- this._sessionMenuButton.actor.add_constraint(new Clutter.AlignConstraint({ source: this._workSpinner.actor, +- align_axis: Clutter.AlignAxis.BOTH, +- factor: 0.5 })); + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; + + if (!this._verifyingUser) + this._reset(); + } + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _reset: function() { + this._userVerifier.clear(); + + this._updateSensitivity(true); +- this._promptMessage.opacity = 0; ++ this._authPrompt.reset(); ++ + this._user = null; + this._verifyingUser = false; + + if (this._disableUserList) + this._hideUserListAndLogIn(); + else + this._showUserList(); + }, + +- _getActorForDefaultButtonWellMode: function(mode) { +- let actor; +- +- if (mode == DefaultButtonWellMode.NONE) +- actor = null; +- else if (mode == DefaultButtonWellMode.SPINNER) +- actor = this._workSpinner.actor; +- else if (mode == DefaultButtonWellMode.SESSION_MENU_BUTTON) +- actor = this._sessionMenuButton.actor; +- +- return actor; +- }, +- +- _setDefaultButtonWellMode: function(mode, immediately) { +- if (this._defaultButtonWellMode == DefaultButtonWellMode.NONE && +- mode == DefaultButtonWellMode.NONE) +- return; +- +- let oldActor = this._getActorForDefaultButtonWellMode(this._defaultButtonWellMode); +- +- if (oldActor) +- Tweener.removeTweens(oldActor); +- +- let actor = this._getActorForDefaultButtonWellMode(mode); +- +- if (this._defaultButtonWellMode != mode && oldActor) { +- if (immediately) +- 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 (mode == DefaultButtonWellMode.SPINNER) { +- if (this._workSpinner) +- this._workSpinner.stop(); +- } +- } +- }); +- +- } +- +- if (actor) { +- if (mode == DefaultButtonWellMode.SPINNER) +- this._workSpinner.play(); +- +- if (immediately) +- actor.opacity = 255; +- else +- Tweener.addTween(actor, +- { opacity: 255, +- time: _DEFAULT_BUTTON_WELL_ANIMATION_TIME, +- delay: _DEFAULT_BUTTON_WELL_ANIMATION_DELAY, +- transition: 'linear' }); +- } +- +- this._defaultButtonWellMode = mode; +- }, +- + _verificationFailed: function() { +- this._promptEntry.text = ''; ++ this._authPrompt.clear(); + + this._updateSensitivity(true); +- this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); ++ this._authPrompt.setActorInDefaultButtonWell(null); + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _showMessage: function(userVerifier, message, styleClass) { +- if (message) { +- this._promptMessage.text = message; +- this._promptMessage.styleClass = styleClass; +- this._promptMessage.opacity = 255; +- } else { +- this._promptMessage.opacity = 0; +- } ++ this._authPrompt.setMessage(message, styleClass); + }, + + _showLoginHint: function(verifier, message) { +- this._promptLoginHint.set_text(message) +- this._promptLoginHint.opacity = 255; ++ this._authPrompt.setHint(message); + }, + + _hideLoginHint: function() { +- this._promptLoginHint.opacity = 0; +- this._promptLoginHint.set_text(''); ++ this._authPrompt.setHint(null); + }, + + cancel: function() { + if (this._verifyingUser) + this._userVerifier.cancel(); + else + this._reset(); + }, + + _shouldShowSessionMenuButton: function() { + if (this._verifyingUser) + return true; + + if (!this._user) + return false; + + if (this._user.is_logged_in) + return false; + + return true; + }, + + _showPrompt: function(forSecret) { +- this._promptLabel.show(); +- this._promptEntry.show(); +- this._promptLoginHint.opacity = 0; +- this._promptLoginHint.show(); +- this._promptBox.opacity = 0; +- this._promptBox.show(); +- Tweener.addTween(this._promptBox, ++ this._authPrompt.actor.opacity = 0; ++ this._authPrompt.actor.show(); ++ Tweener.addTween(this._authPrompt.actor, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + +- if (this._shouldShowSessionMenuButton()) +- this._setDefaultButtonWellMode(DefaultButtonWellMode.SESSION_MENU_BUTTON, true); +- else +- this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); +- +- this._promptEntry.grab_key_focus(); +- + let hold = new Batch.Hold(); + let tasks = [function() { +- this._prepareDialog(forSecret, hold); ++ this._preparePrompt(forSecret, hold); + }, + + hold]; + + let batch = new Batch.ConcurrentBatch(this, tasks); + + return batch.run(); + }, + +- _prepareDialog: function(forSecret, hold) { +- this._buttonBox.visible = true; +- this._buttonBox.remove_all_children(); +- ++ _preparePrompt: function(forSecret, hold) { + if (!this._disableUserList || this._verifyingUser) { +- 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._authPrompt.cancelButton.show(); ++ } else { ++ this._authPrompt.cancelButton.hide(); + } + +- this._buttonBox.add(this._defaultButtonWell, +- { expand: true, +- x_fill: false, +- y_fill: false, +- x_align: St.Align.END, +- y_align: St.Align.MIDDLE }); +- this._signInButton = new St.Button({ style_class: 'modal-dialog-button', +- button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, +- reactive: true, +- can_focus: true, +- label: forSecret ? C_("button", "Sign In") : _("Next") }); +- this._signInButton.connect('clicked', +- Lang.bind(this, function() { +- hold.release(); +- })); +- this._signInButton.add_style_pseudo_class('default'); +- this._buttonBox.add(this._signInButton, +- { expand: false, +- x_fill: false, +- y_fill: false, +- x_align: St.Align.END, +- y_align: St.Align.END }); +- +- this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); +- +- this._promptEntryTextChangedId = +- this._promptEntry.clutter_text.connect('text-changed', +- Lang.bind(this, function() { +- this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); +- })); +- +- this._promptEntryActivateId = +- this._promptEntry.clutter_text.connect('activate', function() { +- hold.release(); +- }); ++ if (forSecret) { ++ this._authPrompt.nextButton.label = C_("button", "Sign In"); ++ } else { ++ this._authPrompt.nextButton.label = _("Next"); ++ } ++ ++ let signalId = this._authPrompt.connect('next', Lang.bind(this, function() { ++ this._authPrompt.disconnect(signalId); ++ hold.release(); ++ })); + }, + + _updateSensitivity: function(sensitive) { +- this._promptEntry.reactive = sensitive; +- this._promptEntry.clutter_text.editable = sensitive; + this._sessionMenuButton.updateSensitivity(sensitive); +- this._updateSignInButtonSensitivity(sensitive); +- }, +- +- _updateSignInButtonSensitivity: function(sensitive) { +- if (this._signInButton) { +- this._signInButton.reactive = sensitive; +- this._signInButton.can_focus = sensitive; +- } +- }, +- +- _hidePrompt: function() { +- if (this._promptEntryTextChangedId > 0) { +- this._promptEntry.clutter_text.disconnect(this._promptEntryTextChangedId); +- this._promptEntryTextChangedId = 0; +- } +- +- if (this._promptEntryActivateId > 0) { +- this._promptEntry.clutter_text.disconnect(this._promptEntryActivateId); +- this._promptEntryActivateId = 0; +- } +- +- this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); +- this._promptBox.hide(); +- this._promptLoginHint.opacity = 0; +- +- this._promptUser.set_child(null); +- +- this._updateSensitivity(true); +- this._promptEntry.set_text(''); +- +- this._sessionMenuButton.close(); +- this._promptLoginHint.opacity = 0; +- +- this._buttonBox.remove_all_children(); +- this._signInButton = null; +- this._cancelButton = null; ++ this._authPrompt.updateSensitivity(sensitive); + }, + + _askQuestion: function(verifier, serviceName, question, passwordChar) { +- this._promptLabel.set_text(question); ++ this._authPrompt.setPasswordChar(passwordChar); ++ this._authPrompt.setQuestion(question); + + this._updateSensitivity(true); +- this._promptEntry.set_text(''); +- this._promptEntry.clutter_text.set_password_char(passwordChar); ++ ++ if (this._shouldShowSessionMenuButton()) ++ this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); ++ else ++ this._authPrompt.setActorInDefaultButtonWell(null); + + let tasks = [function() { + return this._showPrompt(!!passwordChar); + }, + + function() { +- let text = this._promptEntry.get_text(); ++ let text = this._authPrompt.getAnswer(); ++ + this._updateSensitivity(false); +- this._setDefaultButtonWellMode(DefaultButtonWellMode.SPINNER, false); ++ this._authPrompt.startSpinning(); + this._userVerifier.answerQuery(serviceName, text); + }]; + + let batch = new Batch.ConsecutiveBatch(this, tasks); + return batch.run(); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + 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._showLoginHint(null, _("(e.g., user or %s)").format(hint)); + }, + + _askForUsernameAndLogIn: function() { +- this._promptLabel.set_text(_("Username: ")); +- this._promptEntry.set_text(''); +- this._promptEntry.clutter_text.set_password_char(''); ++ this._authPrompt.setPasswordChar(''); ++ this._authPrompt.setQuestion(_("Username: ")); + + let realmManager = new Realmd.Manager(); + let signalId = realmManager.connect('login-format-changed', + Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + + let tasks = [this._showPrompt, + + function() { +- let userName = this._promptEntry.get_text(); +- this._promptEntry.reactive = false; ++ let userName = this._authPrompt.getAnswer(); ++ this._authPrompt._entry.reactive = false; + return this._beginVerificationForUser(userName); + }, + + function() { + realmManager.disconnect(signalId) + realmManager.release(); + }]; + + let batch = new Batch.ConsecutiveBatch(this, tasks); + return batch.run(); + }, + + _startSession: function(serviceName) { + Tweener.addTween(this.actor, + { opacity: 0, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onUpdate: function() { + let children = Main.layoutManager.uiGroup.get_children(); + + for (let i = 0; i < children.length; i++) { + if (children[i] != Main.layoutManager.screenShieldGroup) + children[i].opacity = this.actor.opacity; + } + }, + onUpdateScope: this, + onComplete: function() { + Mainloop.idle_add(Lang.bind(this, function() { + this._greeter.call_start_session_when_ready_sync(serviceName, true, null); + return false; +@@ -1108,77 +888,77 @@ const LoginDialog = new Lang.Class({ + return false; + + if (event.type() == Clutter.EventType.KEY_PRESS || + event.type() == Clutter.EventType.BUTTON_PRESS) { + if (this._timedLoginBatch) { + this._timedLoginBatch.cancel(); + this._timedLoginBatch = null; + } + } else if (event.type() == Clutter.EventType.KEY_RELEASE || + event.type() == Clutter.EventType.BUTTON_RELEASE) { + this._resetTimedLogin(); + } + + return false; + })); + }, + + _setUserListExpanded: function(expanded) { + this._userList.updateStyle(expanded); + this._userSelectionBox.visible = expanded; + }, + + _hideUserListAndLogIn: function() { + this._setUserListExpanded(false); + if (this._userSelectionBox.visible) + GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + this._askForUsernameAndLogIn(); + }, + + _showUserList: function() { +- this._hidePrompt(); ++ this._authPrompt.hide(); ++ this._sessionMenuButton.close(); + this._setUserListExpanded(true); + this._notListedButton.show(); + this._userList.actor.grab_key_focus(); + }, + + _beginVerificationForUser: function(userName) { + let hold = new Batch.Hold(); + + this._userVerifier.begin(userName, hold); + this._verifyingUser = true; + return hold; + }, + + _beginVerificationForItem: function(item) { +- let userWidget = new UserWidget.UserWidget(item.user); +- this._promptUser.set_child(userWidget.actor); ++ this._authPrompt.setUser(item.user); + + let tasks = [function() { + let userName = item.user.get_user_name(); + return this._beginVerificationForUser(userName); + }]; + let batch = new Batch.ConsecutiveBatch(this, tasks); + return batch.run(); + }, + + _onUserListActivated: function(activatedItem) { + let tasks = [function() { + return GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + }, + function() { + this._setUserListExpanded(false); + }]; + + this._user = activatedItem.user; + + let batch = new Batch.ConcurrentBatch(this, [new Batch.ConsecutiveBatch(this, tasks), + this._beginVerificationForItem(activatedItem)]); + batch.run(); + }, + + _onDestroy: function() { + if (this._userManagerLoadedId) { + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + }, +@@ -1192,34 +972,34 @@ const LoginDialog = new Lang.Class({ + + this._updateDisableUserList(); + + this._userManager.connect('user-added', + Lang.bind(this, function(userManager, user) { + this._userList.addUser(user); + })); + + this._userManager.connect('user-removed', + Lang.bind(this, function(userManager, user) { + this._userList.removeUser(user); + })); + }, + + open: function() { + Main.ctrlAltTabManager.addGroup(this.actor, + _("Login Window"), + 'dialog-password-symbolic', + { sortGroup: CtrlAltTab.SortGroup.MIDDLE }); + this._userList.actor.grab_key_focus(); + this.actor.show(); + + return true; + }, + + close: function() { + Main.ctrlAltTabManager.removeGroup(this.dialogLayout); + }, + + addCharacter: function(unichar) { +- this._promptEntry.clutter_text.insert_unichar(unichar); ++ this._authPrompt.addCharacter(unichar); + }, + }); + Signals.addSignalMethods(LoginDialog.prototype); +diff --git a/js/gdm/util.js b/js/gdm/util.js +index c9af991..fbbbae0 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -1,62 +1,69 @@ + // -*- 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 Panel = imports.ui.panel; + const Batch = imports.gdm.batch; + const Fprint = imports.gdm.fingerprint; + const Main = imports.ui.main; + const Params = imports.misc.params; + const Tweener = imports.ui.tweener; ++const UserWidget = imports.ui.userWidget; + + const PASSWORD_SERVICE_NAME = 'gdm-password'; + const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; + const FADE_ANIMATION_TIME = 0.16; + const CLONE_FADE_ANIMATION_TIME = 0.25; + + const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; + const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; + const BANNER_MESSAGE_KEY = 'banner-message-enable'; + const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; + 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 DEFAULT_BUTTON_WELL_ICON_SIZE = 24; ++const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; ++const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; ++ + function fadeInActor(actor) { + if (actor.opacity == 255 && actor.visible) + return null; + + let hold = new Batch.Hold(); + actor.show(); + let [minHeight, naturalHeight] = actor.get_preferred_height(-1); + + actor.opacity = 0; + actor.set_height(0); + Tweener.addTween(actor, + { opacity: 255, + height: naturalHeight, + time: FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onComplete: function() { + this.set_height(-1); + hold.release(); + }, + }); + + return hold; + } + + function fadeOutActor(actor) { + if (!actor.visible || actor.opacity == 0) { + actor.opacity = 0; + actor.hide(); + return null; + } +@@ -431,30 +438,306 @@ const ShellUserVerifier = new Lang.Class({ + this._retry(); + })); + } + } else { + if (!this.hasPendingMessages) { + this._cancelAndReset(); + } else { + let signalId = this.connect('no-more-messages', + Lang.bind(this, function() { + this.disconnect(signalId); + this._cancelAndReset(); + })); + } + } + + this.emit('verification-failed'); + }, + + _onConversationStopped: function(client, serviceName) { + // if the password service fails, then cancel everything. + // But if, e.g., fingerprint fails, still give + // password authentication a chance to succeed + if (serviceName == PASSWORD_SERVICE_NAME) { + this._verificationFailed(true); + } + + this.emit('hide-login-hint'); + }, + }); + Signals.addSignalMethods(ShellUserVerifier.prototype); ++ ++const AuthPrompt = new Lang.Class({ ++ Name: 'AuthPrompt', ++ ++ _init: function() { ++ this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', ++ vertical: true }); ++ this.actor.connect('key-press-event', ++ Lang.bind(this, function(actor, event) { ++ if (event.get_key_symbol() == Clutter.KEY_Escape) { ++ this.emit('cancel'); ++ } ++ })); ++ ++ this._userWell = new St.Bin({ x_fill: true, ++ x_align: St.Align.START }); ++ this.actor.add(this._userWell, ++ { x_align: St.Align.START, ++ x_fill: true, ++ y_fill: true, ++ expand: true }); ++ this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); ++ ++ this.actor.add(this._label, ++ { expand: true, ++ x_fill: true, ++ y_fill: true, ++ x_align: St.Align.START }); ++ this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', ++ can_focus: true }); ++ this.actor.add(this._entry, ++ { expand: true, ++ x_fill: true, ++ y_fill: false, ++ x_align: St.Align.START }); ++ ++ this._entry.grab_key_focus(); ++ ++ this._message = new St.Label({ opacity: 0 }); ++ this.actor.add(this._message, { x_fill: true }); ++ ++ this._loginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); ++ this.actor.add(this._loginHint); ++ ++ this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', ++ vertical: false }); ++ this.actor.add(this._buttonBox, ++ { expand: true, ++ x_align: St.Align.MIDDLE, ++ y_align: St.Align.END }); ++ ++ this._defaultButtonWell = new St.Widget(); ++ this._defaultButtonWellActor = null; ++ ++ this._initButtons(); ++ ++ let spinnerIcon = global.datadir + '/theme/process-working.svg'; ++ this._spinner = new Panel.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); ++ this._spinner.actor.opacity = 0; ++ this._spinner.actor.show(); ++ this._defaultButtonWell.add_child(this._spinner.actor); ++ }, ++ ++ _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.emit('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() { ++ this._updateNextButtonSensitivity(this._entry.text.length > 0); ++ })); ++ this._entry.clutter_text.connect('activate', Lang.bind(this, function() { ++ this.emit('next'); ++ })); ++ }, ++ ++ addActorToDefaultButtonWell: function(actor) { ++ this._defaultButtonWell.add_child(actor); ++ ++ actor.add_constraint(new Clutter.AlignConstraint({ source: this._spinner.actor, ++ align_axis: Clutter.AlignAxis.BOTH, ++ factor: 0.5 })); ++ }, ++ ++ setActorInDefaultButtonWell: function(actor, animate) { ++ if (!this._defaultButtonWellActor && ++ !actor) ++ return; ++ ++ let oldActor = this._defaultButtonWellActor; ++ ++ if (oldActor) ++ Tweener.removeTweens(oldActor); ++ ++ let isSpinner; ++ if (actor == this._spinner.actor) ++ isSpinner = true; ++ else ++ isSpinner = false; ++ ++ if (this._defaultButtonWellActor != actor && oldActor) { ++ if (!animate) { ++ oldActor.opacity = 0; ++ } else { ++ Tweener.addTween(oldActor, ++ { opacity: 0, ++ time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, ++ delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, ++ transition: 'linear', ++ onCompleteScope: this, ++ onComplete: function() { ++ if (isSpinner) { ++ if (this._spinner) ++ this._spinner.stop(); ++ } ++ } ++ }); ++ } ++ } ++ ++ if (actor) { ++ if (isSpinner) ++ this._spinner.play(); ++ ++ if (!animate) ++ actor.opacity = 255; ++ else ++ Tweener.addTween(actor, ++ { opacity: 255, ++ time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, ++ delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, ++ transition: 'linear' }); ++ } ++ ++ this._defaultButtonWellActor = actor; ++ }, ++ ++ startSpinning: function() { ++ this.setActorInDefaultButtonWell(this._spinner.actor, true); ++ }, ++ ++ stopSpinning: function() { ++ this.setActorInDefaultButtonWell(null, false); ++ }, ++ ++ clear: function() { ++ this._entry.text = ''; ++ this.stopSpinning(); ++ }, ++ ++ setPasswordChar: function(passwordChar) { ++ this._entry.clutter_text.set_password_char(passwordChar); ++ }, ++ ++ setQuestion: function(question) { ++ this._label.set_text(question); ++ ++ this._label.show(); ++ this._entry.show(); ++ ++ this._loginHint.opacity = 0; ++ this._loginHint.show(); ++ ++ this._entry.grab_key_focus(); ++ }, ++ ++ getAnswer: function() { ++ let text = this._entry.get_text(); ++ ++ return text; ++ }, ++ ++ setMessage: function(message, styleClass) { ++ if (message) { ++ this._message.text = message; ++ this._message.styleClass = styleClass; ++ 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._loginHint.opacity = 0; ++ ++ this.setUser(null); ++ ++ this.updateSensitivity(true); ++ this._entry.set_text(''); ++ }, ++ ++ setUser: function(user) { ++ if (user) { ++ let userWidget = new UserWidget.UserWidget(user); ++ this._userWell.set_child(userWidget.actor); ++ } else { ++ this._userWell.set_child(null); ++ } ++ }, ++ ++ setHint: function(message) { ++ if (message) { ++ this._loginHint.set_text(message) ++ this._loginHint.opacity = 255; ++ } else { ++ this._loginHint.opacity = 0; ++ this._loginHint.set_text(''); ++ } ++ }, ++ ++ reset: function() { ++ this._message.opacity = 0; ++ this.setUser(null); ++ this.stopSpinning(); ++ }, ++ ++ addCharacter: function(unichar) { ++ if (!this._entry.visible) ++ return; ++ ++ this._entry.grab_key_focus(); ++ this._entry.clutter_text.insert_unichar(unichar); ++ } ++}); ++Signals.addSignalMethods(AuthPrompt.prototype); +-- +1.8.3.1 + + +From 8f568640c3dca0be88986515429380de28cc573d Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 18 Jul 2013 14:40:10 -0400 +Subject: [PATCH 20/66] util: add shell entry menu to auth prompt + +This brings us parity with the unlock dialog, and is a prerequisite +for eventually moving the unlock dialog over to using the auth +prompt. + +https://bugzilla.gnome.org/show_bug.cgi?id=702308 +--- + js/gdm/util.js | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index fbbbae0..084bc7b 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -1,45 +1,46 @@ + // -*- 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 Panel = imports.ui.panel; + const Batch = imports.gdm.batch; + const Fprint = imports.gdm.fingerprint; + const Main = imports.ui.main; + const Params = imports.misc.params; ++const ShellEntry = imports.ui.shellEntry; + const Tweener = imports.ui.tweener; + const UserWidget = imports.ui.userWidget; + + const PASSWORD_SERVICE_NAME = 'gdm-password'; + const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; + const FADE_ANIMATION_TIME = 0.16; + const CLONE_FADE_ANIMATION_TIME = 0.25; + + const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; + const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; + const BANNER_MESSAGE_KEY = 'banner-message-enable'; + const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; + 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 DEFAULT_BUTTON_WELL_ICON_SIZE = 24; + const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; + const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; + + function fadeInActor(actor) { + if (actor.opacity == 255 && actor.visible) + return null; + + let hold = new Batch.Hold(); + actor.show(); +@@ -468,60 +469,62 @@ Signals.addSignalMethods(ShellUserVerifier.prototype); + + const AuthPrompt = new Lang.Class({ + Name: 'AuthPrompt', + + _init: function() { + this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', + vertical: true }); + this.actor.connect('key-press-event', + Lang.bind(this, function(actor, event) { + if (event.get_key_symbol() == Clutter.KEY_Escape) { + this.emit('cancel'); + } + })); + + this._userWell = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this.actor.add(this._userWell, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this.actor.add(this._label, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); ++ ShellEntry.addContextMenu(this._entry, { isPassword: true }); ++ + this.actor.add(this._entry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._entry.grab_key_focus(); + + this._message = new St.Label({ opacity: 0 }); + this.actor.add(this._message, { x_fill: true }); + + this._loginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); + this.actor.add(this._loginHint); + + this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', + vertical: false }); + this.actor.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + + this._defaultButtonWell = new St.Widget(); + this._defaultButtonWellActor = null; + + this._initButtons(); + + let spinnerIcon = global.datadir + '/theme/process-working.svg'; + this._spinner = new Panel.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); + this._spinner.actor.opacity = 0; + this._spinner.actor.show(); +@@ -628,60 +631,61 @@ const AuthPrompt = new Lang.Class({ + 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._loginHint.opacity = 0; + this._loginHint.show(); + + this._entry.grab_key_focus(); + }, + + getAnswer: function() { + let text = this._entry.get_text(); + + return text; + }, + + setMessage: function(message, styleClass) { + if (message) { + this._message.text = message; + this._message.styleClass = styleClass; + this._message.opacity = 255; + } else { + this._message.opacity = 0; + } + }, + +-- +1.8.3.1 + + +From b2f3b75ab632fe9a3ad4bd29e9a1cab9453c2f1b Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 16 Jul 2013 07:31:22 -0400 +Subject: [PATCH 21/66] unlockDialog: Use GdmUtil.AuthPrompt instead of + ModalDialog + +commit ea02380c1524c28e6538ffedb789a12c298742ab made the login +screen stop using ModalDialog. It makes sense for the unlock +code to also stop using ModalDialog, too (for similar reasons). + +Now that the login screen's auth prompt code has been separated +out, the unlock dialog can use it to get the buttons and spinners +etc, that it was previously getting from ModalDialog. + +This commit drops the ModalDialog usage in the unlock dialog, and +makes the unlock dialog use GdmUtil.AuthPrompt instead. + +https://bugzilla.gnome.org/show_bug.cgi?id=702308 +--- + js/ui/unlockDialog.js | 157 +++++++++++++++++++------------------------------- + 1 file changed, 60 insertions(+), 97 deletions(-) + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 50e9502..4a36cd3 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -1,298 +1,261 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + + const AccountsService = imports.gi.AccountsService; ++const Atk = imports.gi.Atk; + const Clutter = imports.gi.Clutter; + const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const GnomeDesktop = imports.gi.GnomeDesktop; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Signals = imports.signals; + const Shell = imports.gi.Shell; + const St = imports.gi.St; + ++const Layout = imports.ui.layout; + const Main = imports.ui.main; +-const ModalDialog = imports.ui.modalDialog; + const Panel = imports.ui.panel; +-const ShellEntry = imports.ui.shellEntry; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; + const LoginDialog = imports.gdm.loginDialog; + + // The timeout before going back automatically to the lock screen (in seconds) + const IDLE_TIMEOUT = 2 * 60; + + const UnlockDialog = new Lang.Class({ + Name: 'UnlockDialog', +- Extends: ModalDialog.ModalDialog, + + _init: function(parentActor) { +- this.parent({ shellReactive: true, +- styleClass: 'login-dialog', +- keybindingMode: Shell.KeyBindingMode.UNLOCK_SCREEN, +- parentActor: parentActor +- }); ++ this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, ++ style_class: 'login-dialog', ++ visible: false }); ++ ++ this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); ++ parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default(); + this._userName = GLib.get_user_name(); + this._user = this._userManager.get_user(this._userName); + + this._firstQuestion = true; + + this._greeterClient = new Gdm.Client(); + this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true }); + this._userVerified = false; + + this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion)); + this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed)); + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); + + this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint)); + this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint)); + +- this._userWidget = new UserWidget.UserWidget(this._user); +- this.contentLayout.add_actor(this._userWidget.actor); +- +- this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', +- vertical: true }); +- +- this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); +- this._promptLayout.add(this._promptLabel, +- { x_align: St.Align.START }); +- +- this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', +- can_focus: true }); +- this._promptEntry.clutter_text.connect('activate', Lang.bind(this, this._doUnlock)); +- this._promptEntry.clutter_text.set_password_char('\u25cf'); +- ShellEntry.addContextMenu(this._promptEntry, { isPassword: true }); +- this.setInitialKeyFocus(this._promptEntry); +- this._promptEntry.clutter_text.connect('text-changed', Lang.bind(this, function() { +- this._updateOkButtonSensitivity(this._promptEntry.text.length > 0); +- })); +- +- this._promptLayout.add(this._promptEntry, +- { expand: true, +- x_fill: true }); +- +- this.contentLayout.add_actor(this._promptLayout); ++ this._promptBox = new St.BoxLayout({ vertical: true }); ++ this.actor.add_child(this._promptBox); ++ this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, ++ align_axis: Clutter.AlignAxis.BOTH, ++ factor: 0.5 })); + +- this._promptMessage = new St.Label({ visible: false }); +- this.contentLayout.add(this._promptMessage, { x_fill: true }); ++ this._authPrompt = new GdmUtil.AuthPrompt(); ++ this._authPrompt.setUser(this._user); ++ this._authPrompt.setPasswordChar('\u25cf'); ++ this._authPrompt.nextButton.label = _("Unlock"); ++ this._authPrompt.connect('cancel', Lang.bind(this, this._escape)); ++ this._authPrompt.connect('next', Lang.bind(this, this._doUnlock)); + +- this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint' }); +- this._promptLoginHint.hide(); +- this.contentLayout.add_actor(this._promptLoginHint); ++ this._promptBox.add_child(this._authPrompt.actor); + + this.allowCancel = false; +- this.buttonLayout.visible = true; +- this.addButton({ label: _("Cancel"), +- action: Lang.bind(this, this._escape), +- key: Clutter.KEY_Escape }, +- { expand: true, +- x_fill: false, +- y_fill: false, +- x_align: St.Align.START, +- y_align: St.Align.MIDDLE }); +- this.placeSpinner({ expand: false, +- x_fill: false, +- y_fill: false, +- x_align: St.Align.END, +- y_align: St.Align.MIDDLE }); +- this._okButton = this.addButton({ label: _("Unlock"), +- action: Lang.bind(this, this._doUnlock), +- default: true }, +- { expand: false, +- x_fill: false, +- y_fill: false, +- x_align: St.Align.END, +- y_align: St.Align.MIDDLE }); + + let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' }); + if (screenSaverSettings.get_boolean('user-switch-enabled')) { + let otherUserLabel = new St.Label({ text: _("Log in as another user"), + style_class: 'login-dialog-not-listed-label' }); + this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + can_focus: true, + child: otherUserLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked)); +- this.dialogLayout.add(this._otherUserButton, +- { x_align: St.Align.START, +- x_fill: false }); ++ this._promptBox.add_child(this._otherUserButton); + } else { + this._otherUserButton = null; + } + + this._updateSensitivity(true); + + let batch = new Batch.Hold(); + this._userVerifier.begin(this._userName, batch); + +- Main.ctrlAltTabManager.addGroup(this.dialogLayout, _("Unlock Window"), 'dialog-password-symbolic'); ++ Main.ctrlAltTabManager.addGroup(this.actor, _("Unlock Window"), 'dialog-password-symbolic'); + + this._idleMonitor = new GnomeDesktop.IdleMonitor(); + this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape)); + }, + + _updateSensitivity: function(sensitive) { +- this._promptEntry.reactive = sensitive; +- this._promptEntry.clutter_text.editable = sensitive; +- this._updateOkButtonSensitivity(sensitive && this._promptEntry.text.length > 0); ++ this._authPrompt.updateSensitivity(sensitive); ++ + if (this._otherUserButton) { + this._otherUserButton.reactive = sensitive; + this._otherUserButton.can_focus = sensitive; + } + }, + +- _updateOkButtonSensitivity: function(sensitive) { +- this._okButton.reactive = sensitive; +- this._okButton.can_focus = sensitive; +- }, +- + _showMessage: function(userVerifier, message, styleClass) { +- if (message) { +- this._promptMessage.text = message; +- this._promptMessage.styleClass = styleClass; +- GdmUtil.fadeInActor(this._promptMessage); +- } else { +- GdmUtil.fadeOutActor(this._promptMessage); +- } ++ this._authPrompt.setMessage(message, styleClass); + }, + + _onAskQuestion: function(verifier, serviceName, question, passwordChar) { + if (this._firstQuestion && this._firstQuestionAnswer) { + this._userVerifier.answerQuery(serviceName, this._firstQuestionAnswer); + this._firstQuestionAnswer = null; + this._firstQuestion = false; + return; + } + +- this._promptLabel.text = question; +- + if (!this._firstQuestion) + this._promptEntry.text = ''; + else + this._firstQuestion = false; + +- this._promptEntry.clutter_text.set_password_char(passwordChar); +- this._promptEntry.menu.isPassword = passwordChar != ''; ++ this._authPrompt.setPasswordChar(passwordChar); ++ this._authPrompt.setQuestion(question); + + this._currentQuery = serviceName; ++ + this._updateSensitivity(true); +- this.setWorking(false); ++ this._authPrompt.stopSpinning(); + }, + + _showLoginHint: function(verifier, message) { +- this._promptLoginHint.set_text(message) +- GdmUtil.fadeInActor(this._promptLoginHint); ++ this._authPrompt.setHint(message); + }, + + _hideLoginHint: function() { +- GdmUtil.fadeOutActor(this._promptLoginHint); ++ this._authPrompt.setHint(null); + }, + + _doUnlock: function() { + if (this._firstQuestion) { + // we haven't received a query yet, so stash the answer + // and make ourself non-reactive + // the actual reply to GDM will be sent as soon as asked + this._firstQuestionAnswer = this._promptEntry.text; + this._updateSensitivity(false); +- this.setWorking(true); ++ this._authPrompt.startSpinning(); + return; + } + + if (!this._currentQuery) + return; + + let query = this._currentQuery; + this._currentQuery = null; + + this._updateSensitivity(false); +- this.setWorking(true); ++ this._authPrompt.startSpinning(); + +- this._userVerifier.answerQuery(query, this._promptEntry.text); ++ this._userVerifier.answerQuery(query, this._authPrompt.getAnswer()); + }, + + _onVerificationComplete: function() { + this._userVerified = true; + }, + + _onReset: function() { + if (!this._userVerified) { + this._userVerifier.clear(); + this.emit('failed'); + } + }, + + _onVerificationFailed: function() { + this._currentQuery = null; + this._firstQuestion = true; + this._userVerified = false; + +- this._promptEntry.text = ''; +- this._promptEntry.clutter_text.set_password_char('\u25cf'); +- this._promptEntry.menu.isPassword = true; ++ this._authPrompt.clear(); + + this._updateSensitivity(false); +- this.setWorking(false); ++ this._authPrompt.stopSpinning(); + }, + + _escape: function() { + if (this.allowCancel) { + this._userVerifier.cancel(); + this.emit('failed'); + } + }, + + _otherUserClicked: function(button, event) { + Gdm.goto_login_session_sync(null); + + this._userVerifier.cancel(); + this.emit('failed'); + }, + + destroy: function() { + this._userVerifier.clear(); ++ this.actor.destroy(); + + if (this._idleWatchId) { + this._idleMonitor.remove_watch(this._idleWatchId); + this._idleWatchId = 0; + } +- +- this.parent(); + }, + + cancel: function() { + this._userVerifier.cancel(null); + + this.destroy(); + }, + + addCharacter: function(unichar) { +- this._promptEntry.clutter_text.insert_unichar(unichar); ++ this._authPrompt.addCharacter(unichar); + }, + + 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(); + })); ++ }, ++ ++ open: function(timestamp) { ++ this.actor.show(); ++ ++ if (this._isModal) ++ return true; + ++ if (!Main.pushModal(this.actor, { timestamp: timestamp, ++ keybindingMode: Shell.KeyBindingMode.UNLOCK_SCREEN })) ++ return false; ++ ++ this._isModal = true; ++ ++ return true; ++ }, ++ ++ popModal: function(timestamp) { ++ if (this._isModal) { ++ Main.popModal(this.actor, timestamp); ++ this._isModal = false; ++ } + } + }); ++Signals.addSignalMethods(UnlockDialog.prototype); +-- +1.8.3.1 + + +From 64036b7725a84cc2f47848c73ac9a1d6150c7089 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 23 Jul 2013 20:37:42 -0400 +Subject: [PATCH 22/66] gdmUtil: separate AuthPrompt out into its own file + +It's cleaner to have it in its own file than to cram it into +util.js, so this commit moves it. + +https://bugzilla.gnome.org/show_bug.cgi?id=704707 +--- + js/Makefile.am | 1 + + js/gdm/authPrompt.js | 294 ++++++++++++++++++++++++++++++++++++++++++++++++++ + js/gdm/loginDialog.js | 3 +- + js/gdm/util.js | 285 ------------------------------------------------ + js/ui/unlockDialog.js | 3 +- + 5 files changed, 299 insertions(+), 287 deletions(-) + create mode 100644 js/gdm/authPrompt.js + +diff --git a/js/Makefile.am b/js/Makefile.am +index e8dd927..57f08e2 100644 +--- a/js/Makefile.am ++++ b/js/Makefile.am +@@ -1,49 +1,50 @@ + NULL = + + 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|[@]GETTEXT_PACKAGE@|$(GETTEXT_PACKAGE)|g" \ + -e "s|[@]datadir@|$(datadir)|g" \ + -e "s|[@]libexecdir@|$(libexecdir)|g" \ + -e "s|[@]sysconfdir@|$(sysconfdir)|g" \ + $< > $@ + + jsdir = $(pkgdatadir)/js + + nobase_dist_js_DATA = \ ++ gdm/authPrompt.js \ + gdm/batch.js \ + gdm/fingerprint.js \ + gdm/loginDialog.js \ + gdm/powerMenu.js \ + gdm/realmd.js \ + gdm/util.js \ + extensionPrefs/main.js \ + misc/config.js \ + misc/extensionUtils.js \ + misc/fileUtils.js \ + misc/gnomeSession.js \ + misc/hash.js \ + misc/history.js \ + misc/jsParse.js \ + misc/loginManager.js \ + misc/modemManager.js \ + misc/params.js \ + misc/util.js \ + perf/core.js \ + ui/altTab.js \ + ui/appDisplay.js \ + ui/appFavorites.js \ + ui/backgroundMenu.js \ + ui/background.js \ + ui/boxpointer.js \ + ui/calendar.js \ + ui/checkBox.js \ + ui/ctrlAltTab.js \ + ui/dash.js \ + ui/dateMenu.js \ +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +new file mode 100644 +index 0000000..bb59d96 +--- /dev/null ++++ b/js/gdm/authPrompt.js +@@ -0,0 +1,294 @@ ++// -*- 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 Panel = imports.ui.panel; ++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 AuthPrompt = new Lang.Class({ ++ Name: 'AuthPrompt', ++ ++ _init: function() { ++ this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', ++ vertical: true }); ++ this.actor.connect('key-press-event', ++ Lang.bind(this, function(actor, event) { ++ if (event.get_key_symbol() == Clutter.KEY_Escape) { ++ this.emit('cancel'); ++ } ++ })); ++ ++ this._userWell = new St.Bin({ x_fill: true, ++ x_align: St.Align.START }); ++ this.actor.add(this._userWell, ++ { x_align: St.Align.START, ++ x_fill: true, ++ y_fill: true, ++ expand: true }); ++ this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); ++ ++ this.actor.add(this._label, ++ { expand: true, ++ x_fill: true, ++ y_fill: true, ++ x_align: St.Align.START }); ++ this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', ++ can_focus: true }); ++ ShellEntry.addContextMenu(this._entry, { isPassword: true }); ++ ++ this.actor.add(this._entry, ++ { expand: true, ++ x_fill: true, ++ y_fill: false, ++ x_align: St.Align.START }); ++ ++ this._entry.grab_key_focus(); ++ ++ this._message = new St.Label({ opacity: 0 }); ++ this.actor.add(this._message, { x_fill: true }); ++ ++ this._loginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); ++ this.actor.add(this._loginHint); ++ ++ this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', ++ vertical: false }); ++ this.actor.add(this._buttonBox, ++ { expand: true, ++ x_align: St.Align.MIDDLE, ++ y_align: St.Align.END }); ++ ++ this._defaultButtonWell = new St.Widget(); ++ this._defaultButtonWellActor = null; ++ ++ this._initButtons(); ++ ++ let spinnerIcon = global.datadir + '/theme/process-working.svg'; ++ this._spinner = new Panel.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); ++ this._spinner.actor.opacity = 0; ++ this._spinner.actor.show(); ++ this._defaultButtonWell.add_child(this._spinner.actor); ++ }, ++ ++ _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.emit('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() { ++ this._updateNextButtonSensitivity(this._entry.text.length > 0); ++ })); ++ this._entry.clutter_text.connect('activate', Lang.bind(this, function() { ++ this.emit('next'); ++ })); ++ }, ++ ++ addActorToDefaultButtonWell: function(actor) { ++ this._defaultButtonWell.add_child(actor); ++ ++ actor.add_constraint(new Clutter.AlignConstraint({ source: this._spinner.actor, ++ align_axis: Clutter.AlignAxis.BOTH, ++ factor: 0.5 })); ++ }, ++ ++ setActorInDefaultButtonWell: function(actor, animate) { ++ if (!this._defaultButtonWellActor && ++ !actor) ++ return; ++ ++ let oldActor = this._defaultButtonWellActor; ++ ++ if (oldActor) ++ Tweener.removeTweens(oldActor); ++ ++ let isSpinner; ++ if (actor == this._spinner.actor) ++ isSpinner = true; ++ else ++ isSpinner = false; ++ ++ if (this._defaultButtonWellActor != actor && oldActor) { ++ if (!animate) { ++ oldActor.opacity = 0; ++ } else { ++ Tweener.addTween(oldActor, ++ { opacity: 0, ++ time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, ++ delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, ++ transition: 'linear', ++ onCompleteScope: this, ++ onComplete: function() { ++ if (isSpinner) { ++ if (this._spinner) ++ this._spinner.stop(); ++ } ++ } ++ }); ++ } ++ } ++ ++ if (actor) { ++ if (isSpinner) ++ this._spinner.play(); ++ ++ if (!animate) ++ actor.opacity = 255; ++ else ++ Tweener.addTween(actor, ++ { opacity: 255, ++ time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, ++ delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, ++ transition: 'linear' }); ++ } ++ ++ this._defaultButtonWellActor = actor; ++ }, ++ ++ startSpinning: function() { ++ this.setActorInDefaultButtonWell(this._spinner.actor, true); ++ }, ++ ++ stopSpinning: function() { ++ this.setActorInDefaultButtonWell(null, false); ++ }, ++ ++ clear: function() { ++ this._entry.text = ''; ++ this.stopSpinning(); ++ }, ++ ++ setPasswordChar: function(passwordChar) { ++ this._entry.clutter_text.set_password_char(passwordChar); ++ this._entry.menu.isPassword = passwordChar != ''; ++ }, ++ ++ setQuestion: function(question) { ++ this._label.set_text(question); ++ ++ this._label.show(); ++ this._entry.show(); ++ ++ this._loginHint.opacity = 0; ++ this._loginHint.show(); ++ ++ this._entry.grab_key_focus(); ++ }, ++ ++ getAnswer: function() { ++ let text = this._entry.get_text(); ++ ++ return text; ++ }, ++ ++ setMessage: function(message, styleClass) { ++ if (message) { ++ this._message.text = message; ++ this._message.styleClass = styleClass; ++ 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._loginHint.opacity = 0; ++ ++ this.setUser(null); ++ ++ this.updateSensitivity(true); ++ this._entry.set_text(''); ++ }, ++ ++ setUser: function(user) { ++ if (user) { ++ let userWidget = new UserWidget.UserWidget(user); ++ this._userWell.set_child(userWidget.actor); ++ } else { ++ this._userWell.set_child(null); ++ } ++ }, ++ ++ setHint: function(message) { ++ if (message) { ++ this._loginHint.set_text(message) ++ this._loginHint.opacity = 255; ++ } else { ++ this._loginHint.opacity = 0; ++ this._loginHint.set_text(''); ++ } ++ }, ++ ++ reset: function() { ++ this._message.opacity = 0; ++ this.setUser(null); ++ this.stopSpinning(); ++ }, ++ ++ addCharacter: function(unichar) { ++ if (!this._entry.visible) ++ return; ++ ++ this._entry.grab_key_focus(); ++ this._entry.clutter_text.insert_unichar(unichar); ++ } ++}); ++Signals.addSignalMethods(AuthPrompt.prototype); +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index e9dd6b2..d9e3898 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -5,60 +5,61 @@ + * 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 AccountsService = imports.gi.AccountsService; + const Atk = imports.gi.Atk; + const Clutter = imports.gi.Clutter; + const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; + const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + ++const AuthPrompt = imports.gdm.authPrompt; + const Batch = imports.gdm.batch; + const BoxPointer = imports.ui.boxpointer; + const CtrlAltTab = imports.ui.ctrlAltTab; + const GdmUtil = imports.gdm.util; + const Layout = imports.ui.layout; + const Main = imports.ui.main; + const PopupMenu = imports.ui.popupMenu; + const Realmd = imports.gdm.realmd; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const _FADE_ANIMATION_TIME = 0.25; + const _SCROLL_ANIMATION_TIME = 0.5; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _LOGO_ICON_HEIGHT = 48; + + let _loginDialog = null; + + const UserListItem = new Lang.Class({ + Name: 'UserListItem', + + _init: function(user) { + this.user = user; + this._userChangedId = this.user.connect('changed', + Lang.bind(this, this._onUserChanged)); + + let layout = new St.BoxLayout({ vertical: false }); + this.actor = new St.Button({ style_class: 'login-dialog-user-list-item', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, +@@ -431,61 +432,61 @@ const LoginDialog = new Lang.Class({ + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + vertical: true, + visible: false }); + this._userSelectionBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + +- this._authPrompt = new GdmUtil.AuthPrompt(); ++ this._authPrompt = new AuthPrompt.AuthPrompt(); + this._authPrompt.hide(); + this._authPrompt.connect('cancel', + Lang.bind(this, function() { + this.cancel(); + })); + + this._authPrompt.actor.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + + this.actor.add_child(this._authPrompt.actor); + this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._authPrompt.actor, + coordinate: Clutter.BindCoordinate.WIDTH })); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); + this._notListedButton.hide(); + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 084bc7b..20540b7 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -1,70 +1,64 @@ + // -*- 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 Panel = imports.ui.panel; + const Batch = imports.gdm.batch; + const Fprint = imports.gdm.fingerprint; + const Main = imports.ui.main; + const Params = imports.misc.params; + const ShellEntry = imports.ui.shellEntry; + const Tweener = imports.ui.tweener; +-const UserWidget = imports.ui.userWidget; + + const PASSWORD_SERVICE_NAME = 'gdm-password'; + const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; + const FADE_ANIMATION_TIME = 0.16; + const CLONE_FADE_ANIMATION_TIME = 0.25; + + const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; + const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; + const BANNER_MESSAGE_KEY = 'banner-message-enable'; + const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; + 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 DEFAULT_BUTTON_WELL_ICON_SIZE = 24; +-const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; +-const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; +- + function fadeInActor(actor) { + if (actor.opacity == 255 && actor.visible) + return null; + + let hold = new Batch.Hold(); + actor.show(); + let [minHeight, naturalHeight] = actor.get_preferred_height(-1); + + actor.opacity = 0; + actor.set_height(0); + Tweener.addTween(actor, + { opacity: 255, + height: naturalHeight, + time: FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onComplete: function() { + this.set_height(-1); + hold.release(); + }, + }); + + return hold; + } + + function fadeOutActor(actor) { + if (!actor.visible || actor.opacity == 0) { + actor.opacity = 0; + actor.hide(); + return null; + } +@@ -439,309 +433,30 @@ const ShellUserVerifier = new Lang.Class({ + this._retry(); + })); + } + } else { + if (!this.hasPendingMessages) { + this._cancelAndReset(); + } else { + let signalId = this.connect('no-more-messages', + Lang.bind(this, function() { + this.disconnect(signalId); + this._cancelAndReset(); + })); + } + } + + this.emit('verification-failed'); + }, + + _onConversationStopped: function(client, serviceName) { + // if the password service fails, then cancel everything. + // But if, e.g., fingerprint fails, still give + // password authentication a chance to succeed + if (serviceName == PASSWORD_SERVICE_NAME) { + this._verificationFailed(true); + } + + this.emit('hide-login-hint'); + }, + }); + Signals.addSignalMethods(ShellUserVerifier.prototype); +- +-const AuthPrompt = new Lang.Class({ +- Name: 'AuthPrompt', +- +- _init: function() { +- this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', +- vertical: true }); +- this.actor.connect('key-press-event', +- Lang.bind(this, function(actor, event) { +- if (event.get_key_symbol() == Clutter.KEY_Escape) { +- this.emit('cancel'); +- } +- })); +- +- this._userWell = new St.Bin({ x_fill: true, +- x_align: St.Align.START }); +- this.actor.add(this._userWell, +- { x_align: St.Align.START, +- x_fill: true, +- y_fill: true, +- expand: true }); +- this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); +- +- this.actor.add(this._label, +- { expand: true, +- x_fill: true, +- y_fill: true, +- x_align: St.Align.START }); +- this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', +- can_focus: true }); +- ShellEntry.addContextMenu(this._entry, { isPassword: true }); +- +- this.actor.add(this._entry, +- { expand: true, +- x_fill: true, +- y_fill: false, +- x_align: St.Align.START }); +- +- this._entry.grab_key_focus(); +- +- this._message = new St.Label({ opacity: 0 }); +- this.actor.add(this._message, { x_fill: true }); +- +- this._loginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); +- this.actor.add(this._loginHint); +- +- this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', +- vertical: false }); +- this.actor.add(this._buttonBox, +- { expand: true, +- x_align: St.Align.MIDDLE, +- y_align: St.Align.END }); +- +- this._defaultButtonWell = new St.Widget(); +- this._defaultButtonWellActor = null; +- +- this._initButtons(); +- +- let spinnerIcon = global.datadir + '/theme/process-working.svg'; +- this._spinner = new Panel.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); +- this._spinner.actor.opacity = 0; +- this._spinner.actor.show(); +- this._defaultButtonWell.add_child(this._spinner.actor); +- }, +- +- _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.emit('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() { +- this._updateNextButtonSensitivity(this._entry.text.length > 0); +- })); +- this._entry.clutter_text.connect('activate', Lang.bind(this, function() { +- this.emit('next'); +- })); +- }, +- +- addActorToDefaultButtonWell: function(actor) { +- this._defaultButtonWell.add_child(actor); +- +- actor.add_constraint(new Clutter.AlignConstraint({ source: this._spinner.actor, +- align_axis: Clutter.AlignAxis.BOTH, +- factor: 0.5 })); +- }, +- +- setActorInDefaultButtonWell: function(actor, animate) { +- if (!this._defaultButtonWellActor && +- !actor) +- return; +- +- let oldActor = this._defaultButtonWellActor; +- +- if (oldActor) +- Tweener.removeTweens(oldActor); +- +- let isSpinner; +- if (actor == this._spinner.actor) +- isSpinner = true; +- else +- isSpinner = false; +- +- if (this._defaultButtonWellActor != actor && oldActor) { +- if (!animate) { +- oldActor.opacity = 0; +- } else { +- Tweener.addTween(oldActor, +- { opacity: 0, +- time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, +- delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, +- transition: 'linear', +- onCompleteScope: this, +- onComplete: function() { +- if (isSpinner) { +- if (this._spinner) +- this._spinner.stop(); +- } +- } +- }); +- } +- } +- +- if (actor) { +- if (isSpinner) +- this._spinner.play(); +- +- if (!animate) +- actor.opacity = 255; +- else +- Tweener.addTween(actor, +- { opacity: 255, +- time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, +- delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, +- transition: 'linear' }); +- } +- +- this._defaultButtonWellActor = actor; +- }, +- +- startSpinning: function() { +- this.setActorInDefaultButtonWell(this._spinner.actor, true); +- }, +- +- stopSpinning: function() { +- this.setActorInDefaultButtonWell(null, false); +- }, +- +- clear: function() { +- this._entry.text = ''; +- this.stopSpinning(); +- }, +- +- setPasswordChar: function(passwordChar) { +- this._entry.clutter_text.set_password_char(passwordChar); +- this._entry.menu.isPassword = passwordChar != ''; +- }, +- +- setQuestion: function(question) { +- this._label.set_text(question); +- +- this._label.show(); +- this._entry.show(); +- +- this._loginHint.opacity = 0; +- this._loginHint.show(); +- +- this._entry.grab_key_focus(); +- }, +- +- getAnswer: function() { +- let text = this._entry.get_text(); +- +- return text; +- }, +- +- setMessage: function(message, styleClass) { +- if (message) { +- this._message.text = message; +- this._message.styleClass = styleClass; +- 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._loginHint.opacity = 0; +- +- this.setUser(null); +- +- this.updateSensitivity(true); +- this._entry.set_text(''); +- }, +- +- setUser: function(user) { +- if (user) { +- let userWidget = new UserWidget.UserWidget(user); +- this._userWell.set_child(userWidget.actor); +- } else { +- this._userWell.set_child(null); +- } +- }, +- +- setHint: function(message) { +- if (message) { +- this._loginHint.set_text(message) +- this._loginHint.opacity = 255; +- } else { +- this._loginHint.opacity = 0; +- this._loginHint.set_text(''); +- } +- }, +- +- reset: function() { +- this._message.opacity = 0; +- this.setUser(null); +- this.stopSpinning(); +- }, +- +- addCharacter: function(unichar) { +- if (!this._entry.visible) +- return; +- +- this._entry.grab_key_focus(); +- this._entry.clutter_text.insert_unichar(unichar); +- } +-}); +-Signals.addSignalMethods(AuthPrompt.prototype); +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 4a36cd3..7fd5271 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -1,96 +1,97 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + + const AccountsService = imports.gi.AccountsService; + const Atk = imports.gi.Atk; + const Clutter = imports.gi.Clutter; + const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const GnomeDesktop = imports.gi.GnomeDesktop; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Signals = imports.signals; + const Shell = imports.gi.Shell; + const St = imports.gi.St; + + const Layout = imports.ui.layout; + const Main = imports.ui.main; + const Panel = imports.ui.panel; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + ++const AuthPrompt = imports.gdm.authPrompt; + const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; + const LoginDialog = imports.gdm.loginDialog; + + // The timeout before going back automatically to the lock screen (in seconds) + const IDLE_TIMEOUT = 2 * 60; + + const UnlockDialog = new Lang.Class({ + Name: 'UnlockDialog', + + _init: function(parentActor) { + this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, + style_class: 'login-dialog', + visible: false }); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default(); + this._userName = GLib.get_user_name(); + this._user = this._userManager.get_user(this._userName); + + this._firstQuestion = true; + + this._greeterClient = new Gdm.Client(); + this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true }); + this._userVerified = false; + + this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion)); + this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed)); + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); + + this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint)); + this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint)); + + this._promptBox = new St.BoxLayout({ vertical: true }); + this.actor.add_child(this._promptBox); + this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + +- this._authPrompt = new GdmUtil.AuthPrompt(); ++ this._authPrompt = new AuthPrompt.AuthPrompt(); + this._authPrompt.setUser(this._user); + this._authPrompt.setPasswordChar('\u25cf'); + this._authPrompt.nextButton.label = _("Unlock"); + this._authPrompt.connect('cancel', Lang.bind(this, this._escape)); + this._authPrompt.connect('next', Lang.bind(this, this._doUnlock)); + + this._promptBox.add_child(this._authPrompt.actor); + + this.allowCancel = false; + + let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' }); + if (screenSaverSettings.get_boolean('user-switch-enabled')) { + let otherUserLabel = new St.Label({ text: _("Log in as another user"), + style_class: 'login-dialog-not-listed-label' }); + this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + can_focus: true, + child: otherUserLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked)); + this._promptBox.add_child(this._otherUserButton); + } else { + this._otherUserButton = null; + } + + this._updateSensitivity(true); + + let batch = new Batch.Hold(); + this._userVerifier.begin(this._userName, batch); +-- +1.8.3.1 + + +From 7a4ede993d72274f52161ee9979060911f3b6c18 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 22 Jul 2013 11:07:35 -0400 +Subject: [PATCH 23/66] authPrompt: move unlock and login user verifier code + here + +There's quite a bit of duplicated code between the login dialog +and the unlock dialog dealing with the various signals from the +ShellUserVerifier. + +This commit moves that duplicated code into the AuthPrompt. + +https://bugzilla.gnome.org/show_bug.cgi?id=704707 +--- + js/gdm/authPrompt.js | 152 +++++++++++++++++++++++++++++++++++++- + js/gdm/loginDialog.js | 198 ++++++++++++-------------------------------------- + js/ui/unlockDialog.js | 121 +++--------------------------- + 3 files changed, 206 insertions(+), 265 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index bb59d96..b0dcd0d 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -1,161 +1,266 @@ + // -*- 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 Panel = imports.ui.panel; ++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 AuthPromptMode = { ++ UNLOCK_ONLY: 0, ++ UNLOCK_OR_LOG_IN: 1 ++}; ++ + const AuthPrompt = new Lang.Class({ + Name: 'AuthPrompt', + +- _init: function() { ++ _init: function(gdmClient, mode) { ++ this.verifyingUser = false; ++ ++ 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('show-login-hint', Lang.bind(this, this._onShowLoginHint)); ++ this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._onHideLoginHint)); ++ ++ 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.emit('cancel'); ++ this.cancel(); + } + })); + + this._userWell = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this.actor.add(this._userWell, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this.actor.add(this._label, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + ShellEntry.addContextMenu(this._entry, { isPassword: true }); + + this.actor.add(this._entry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._entry.grab_key_focus(); + + this._message = new St.Label({ opacity: 0 }); + this.actor.add(this._message, { x_fill: true }); + + this._loginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); + this.actor.add(this._loginHint); + + this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', + vertical: false }); + this.actor.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + + this._defaultButtonWell = new St.Widget(); + this._defaultButtonWellActor = null; + + this._initButtons(); + + let spinnerIcon = global.datadir + '/theme/process-working.svg'; + this._spinner = new Panel.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); + this._spinner.actor.opacity = 0; + this._spinner.actor.show(); + this._defaultButtonWell.add_child(this._spinner.actor); + }, + ++ _onDestroy: function() { ++ this._userVerifier.clear(); ++ }, ++ + _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.emit('cancel'); ++ 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() { + 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) { ++ 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 (this.verifyingUser) ++ this.cancelButton.show(); ++ else ++ this.cancelButton.hide(); ++ ++ if (passwordChar) { ++ if (this._mode == AuthPromptMode.UNLOCK_ONLY) ++ this.nextButton.label = _("Unlock"); ++ else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN) ++ this.nextButton.label = C_("button", "Sign In"); ++ } else { ++ this.nextButton.label = _("Next"); ++ } ++ ++ this.updateSensitivity(true); ++ this.emit('prompted'); ++ }, ++ ++ _onShowMessage: function(userVerifier, message, styleClass) { ++ this.setMessage(message, styleClass); ++ }, ++ ++ _onVerificationFailed: function() { ++ this.clear(); ++ ++ this.updateSensitivity(true); ++ this.setActorInDefaultButtonWell(null); ++ this.userVerified = false; ++ }, ++ ++ _onVerificationComplete: function() { ++ this.userVerified = true; ++ }, ++ ++ _onReset: function() { ++ if (!this.userVerified) ++ this.reset(); ++ }, ++ ++ _onShowLoginHint: function(verifier, message) { ++ this.setHint(message); ++ }, ++ ++ _onHideLoginHint: function() { ++ this.setHint(null); ++ }, ++ + addActorToDefaultButtonWell: function(actor) { + this._defaultButtonWell.add_child(actor); + + actor.add_constraint(new Clutter.AlignConstraint({ source: this._spinner.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + }, + + setActorInDefaultButtonWell: function(actor, animate) { + if (!this._defaultButtonWellActor && + !actor) + return; + + let oldActor = this._defaultButtonWellActor; + + if (oldActor) + Tweener.removeTweens(oldActor); + + let isSpinner; + if (actor == this._spinner.actor) + isSpinner = true; + else + isSpinner = false; + + if (this._defaultButtonWellActor != actor && oldActor) { + if (!animate) { + oldActor.opacity = 0; + } else { + Tweener.addTween(oldActor, + { opacity: 0, +@@ -251,44 +356,85 @@ const AuthPrompt = new Lang.Class({ + this.setActorInDefaultButtonWell(null, true); + this.actor.hide(); + this._loginHint.opacity = 0; + + this.setUser(null); + + this.updateSensitivity(true); + this._entry.set_text(''); + }, + + setUser: function(user) { + if (user) { + let userWidget = new UserWidget.UserWidget(user); + this._userWell.set_child(userWidget.actor); + } else { + this._userWell.set_child(null); + } + }, + + setHint: function(message) { + if (message) { + this._loginHint.set_text(message) + this._loginHint.opacity = 255; + } else { + this._loginHint.opacity = 0; + this._loginHint.set_text(''); + } + }, + + reset: function() { ++ this.verifyingUser = false; ++ this.userVerified = false; ++ this._queryingService = null; ++ this.clear(); + this._message.opacity = 0; + this.setUser(null); + this.stopSpinning(); ++ this.setHint(null); ++ ++ this.emit('reset'); + }, + + 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.verifyingUser = true; ++ }, ++ ++ 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.verifyingUser) ++ this._userVerifier.cancel(); ++ ++ this.reset(); + } + }); + Signals.addSignalMethods(AuthPrompt.prototype); +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index d9e3898..fe594e4 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -375,123 +375,112 @@ const SessionMenuButton = new Lang.Class({ + + let id = ids[i]; + let item = new PopupMenu.PopupMenuItem(sessionName); + this._menu.addMenuItem(item); + this._items[id] = item; + + if (!this._activeSessionId) + this.setActiveSession(id); + + item.connect('activate', Lang.bind(this, function() { + this.setActiveSession(id); + })); + } + } + }); + Signals.addSignalMethods(SessionMenuButton.prototype); + + const LoginDialog = new Lang.Class({ + Name: 'LoginDialog', + + _init: function(parentActor) { + this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, + style_class: 'login-dialog', + visible: false }); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default() +- this._greeterClient = new Gdm.Client(); ++ let gdmClient = new Gdm.Client(); + + if (GLib.getenv('GDM_GREETER_TEST') != '1') { +- this._greeter = this._greeterClient.get_greeter_sync(null); ++ this._greeter = gdmClient.get_greeter_sync(null); + + this._greeter.connect('default-session-name-changed', + Lang.bind(this, this._onDefaultSessionChanged)); + + this._greeter.connect('session-opened', + Lang.bind(this, this._onSessionOpened)); + this._greeter.connect('timed-login-requested', + Lang.bind(this, this._onTimedLoginRequested)); + } + +- this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient); +- this._userVerifier.connect('ask-question', Lang.bind(this, this._askQuestion)); +- this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage)); +- this._userVerifier.connect('verification-failed', Lang.bind(this, this._verificationFailed)); +- this._userVerifier.connect('reset', Lang.bind(this, this._reset)); +- this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint)); +- this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint)); +- this._verifyingUser = false; +- + this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA }); + + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + vertical: true, + visible: false }); + this._userSelectionBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + +- this._authPrompt = new AuthPrompt.AuthPrompt(); ++ this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOGIN); ++ this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); ++ this._authPrompt.connect('reset', Lang.bind(this, this._reset)); + this._authPrompt.hide(); +- this._authPrompt.connect('cancel', +- Lang.bind(this, function() { +- this.cancel(); +- })); + + this._authPrompt.actor.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + + this.actor.add_child(this._authPrompt.actor); + this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._authPrompt.actor, + coordinate: Clutter.BindCoordinate.WIDTH })); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + +@@ -514,296 +503,213 @@ const LoginDialog = new Lang.Class({ + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; + +- if (!this._verifyingUser) ++ if (!this._authPrompt.verifyingUser) + this._reset(); + } + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + ++ _onPrompted: function() { ++ this._sessionMenuButton.updateSensitivity(true); ++ ++ if (this._shouldShowSessionMenuButton()) ++ this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); ++ ++ this._authPrompt.cancelButton.show(); ++ ++ this._showPrompt(); ++ }, ++ + _reset: function() { +- this._userVerifier.clear(); ++ if (this._authPrompt.verifyingUser) ++ return; + +- this._updateSensitivity(true); +- this._authPrompt.reset(); ++ this._sessionMenuButton.updateSensitivity(true); + + this._user = null; +- this._verifyingUser = false; + + if (this._disableUserList) + this._hideUserListAndLogIn(); + else + this._showUserList(); + }, + +- _verificationFailed: function() { +- this._authPrompt.clear(); +- +- this._updateSensitivity(true); +- this._authPrompt.setActorInDefaultButtonWell(null); +- }, +- + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + +- _showMessage: function(userVerifier, message, styleClass) { +- this._authPrompt.setMessage(message, styleClass); +- }, +- +- _showLoginHint: function(verifier, message) { +- this._authPrompt.setHint(message); +- }, +- +- _hideLoginHint: function() { +- this._authPrompt.setHint(null); +- }, +- +- cancel: function() { +- if (this._verifyingUser) +- this._userVerifier.cancel(); +- else +- this._reset(); +- }, +- + _shouldShowSessionMenuButton: function() { +- if (this._verifyingUser) ++ if (this._authPrompt.verifyingUser) + return true; + + if (!this._user) + return false; + + if (this._user.is_logged_in) + return false; + + return true; + }, + +- _showPrompt: function(forSecret) { ++ _showPrompt: function() { ++ if (this._authPrompt.actor.visible) ++ return; + this._authPrompt.actor.opacity = 0; + this._authPrompt.actor.show(); + Tweener.addTween(this._authPrompt.actor, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); +- +- let hold = new Batch.Hold(); +- let tasks = [function() { +- this._preparePrompt(forSecret, hold); +- }, +- +- hold]; +- +- let batch = new Batch.ConcurrentBatch(this, tasks); +- +- return batch.run(); +- }, +- +- _preparePrompt: function(forSecret, hold) { +- if (!this._disableUserList || this._verifyingUser) { +- this._authPrompt.cancelButton.show(); +- } else { +- this._authPrompt.cancelButton.hide(); +- } +- +- if (forSecret) { +- this._authPrompt.nextButton.label = C_("button", "Sign In"); +- } else { +- this._authPrompt.nextButton.label = _("Next"); +- } +- +- let signalId = this._authPrompt.connect('next', Lang.bind(this, function() { +- this._authPrompt.disconnect(signalId); +- hold.release(); +- })); +- }, +- +- _updateSensitivity: function(sensitive) { +- this._sessionMenuButton.updateSensitivity(sensitive); +- this._authPrompt.updateSensitivity(sensitive); +- }, +- +- _askQuestion: function(verifier, serviceName, question, passwordChar) { +- this._authPrompt.setPasswordChar(passwordChar); +- this._authPrompt.setQuestion(question); +- +- this._updateSensitivity(true); +- +- if (this._shouldShowSessionMenuButton()) +- this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); +- else +- this._authPrompt.setActorInDefaultButtonWell(null); +- +- let tasks = [function() { +- return this._showPrompt(!!passwordChar); +- }, +- +- function() { +- let text = this._authPrompt.getAnswer(); +- +- this._updateSensitivity(false); +- this._authPrompt.startSpinning(); +- this._userVerifier.answerQuery(serviceName, text); +- }]; +- +- let batch = new Batch.ConsecutiveBatch(this, tasks); +- return batch.run(); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + 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._showLoginHint(null, _("(e.g., user or %s)").format(hint)); ++ this._authPrompt.setHint(_("(e.g., user or %s)").format(hint)); + }, + + _askForUsernameAndLogIn: function() { + this._authPrompt.setPasswordChar(''); + this._authPrompt.setQuestion(_("Username: ")); + + let realmManager = new Realmd.Manager(); +- let signalId = realmManager.connect('login-format-changed', +- Lang.bind(this, this._showRealmLoginHint)); ++ let realmSignalId = realmManager.connect('login-format-changed', ++ Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + +- let tasks = [this._showPrompt, +- +- function() { +- let userName = this._authPrompt.getAnswer(); +- this._authPrompt._entry.reactive = false; +- return this._beginVerificationForUser(userName); +- }, +- +- function() { +- realmManager.disconnect(signalId) +- realmManager.release(); +- }]; ++ let nextSignalId = this._authPrompt.connect('next', ++ Lang.bind(this, function() { ++ this._authPrompt.disconnect(nextSignalId); ++ this._authPrompt.updateSensitivity(false); ++ let answer = this._authPrompt.getAnswer(); ++ this._authPrompt.clear(); ++ this._authPrompt.startSpinning(); ++ this._authPrompt.begin({ userName: answer }); + +- let batch = new Batch.ConsecutiveBatch(this, tasks); +- return batch.run(); ++ realmManager.disconnect(realmSignalId) ++ realmManager.release(); ++ })); ++ this._authPrompt.cancelButton.hide(); ++ this._showPrompt(); + }, + + _startSession: function(serviceName) { + Tweener.addTween(this.actor, + { opacity: 0, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onUpdate: function() { + let children = Main.layoutManager.uiGroup.get_children(); + + for (let i = 0; i < children.length; i++) { + if (children[i] != Main.layoutManager.screenShieldGroup) + children[i].opacity = this.actor.opacity; + } + }, + onUpdateScope: this, + onComplete: function() { + Mainloop.idle_add(Lang.bind(this, function() { + this._greeter.call_start_session_when_ready_sync(serviceName, true, null); + return false; + })); + }, + onCompleteScope: this }); + }, + + _onSessionOpened: function(client, serviceName) { +- if (!this._userVerifier.hasPendingMessages) { ++ this._authPrompt.finish(Lang.bind(this, function() { + this._startSession(serviceName); +- } else { +- let signalId = this._userVerifier.connect('no-more-messages', +- Lang.bind(this, function() { +- this._userVerifier.disconnect(signalId); +- this._startSession(serviceName); +- })); +- } ++ })); + }, + + _waitForItemForUser: function(userName) { + let item = this._userList.getItemFromUserName(userName); + + if (item) + return null; + + let hold = new Batch.Hold(); + let signalId = this._userList.connect('item-added', + Lang.bind(this, function() { + let item = this._userList.getItemFromUserName(userName); + + if (item) + hold.release(); + })); + + hold.connect('release', Lang.bind(this, function() { + this._userList.disconnect(signalId); + })); + + return hold; + }, + + _showTimedLoginAnimation: function() { + this._timedLoginItem.actor.grab_key_focus(); + return this._timedLoginItem.showTimedLoginIndicator(this._timedLoginAnimationTime); + }, + + _blockTimedLoginUntilIdle: function() { +@@ -896,77 +802,69 @@ const LoginDialog = new Lang.Class({ + } + } else if (event.type() == Clutter.EventType.KEY_RELEASE || + event.type() == Clutter.EventType.BUTTON_RELEASE) { + this._resetTimedLogin(); + } + + return false; + })); + }, + + _setUserListExpanded: function(expanded) { + this._userList.updateStyle(expanded); + this._userSelectionBox.visible = expanded; + }, + + _hideUserListAndLogIn: function() { + this._setUserListExpanded(false); + if (this._userSelectionBox.visible) + GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + this._askForUsernameAndLogIn(); + }, + + _showUserList: function() { + this._authPrompt.hide(); + this._sessionMenuButton.close(); + this._setUserListExpanded(true); + this._notListedButton.show(); + this._userList.actor.grab_key_focus(); + }, + +- _beginVerificationForUser: function(userName) { +- let hold = new Batch.Hold(); +- +- this._userVerifier.begin(userName, hold); +- this._verifyingUser = true; +- return hold; +- }, +- + _beginVerificationForItem: function(item) { + this._authPrompt.setUser(item.user); + +- let tasks = [function() { +- let userName = item.user.get_user_name(); +- return this._beginVerificationForUser(userName); +- }]; +- let batch = new Batch.ConsecutiveBatch(this, tasks); +- return batch.run(); ++ let userName = item.user.get_user_name(); ++ let hold = new Batch.Hold(); ++ ++ this._authPrompt.begin({ userName: userName, ++ hold: hold }); ++ return hold; + }, + + _onUserListActivated: function(activatedItem) { + let tasks = [function() { + return GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + }, + function() { + this._setUserListExpanded(false); + }]; + + this._user = activatedItem.user; + + let batch = new Batch.ConcurrentBatch(this, [new Batch.ConsecutiveBatch(this, tasks), + this._beginVerificationForItem(activatedItem)]); + batch.run(); + }, + + _onDestroy: function() { + if (this._userManagerLoadedId) { + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + }, + + _loadUserList: function() { + let users = this._userManager.list_users(); + + for (let i = 0; i < users.length; i++) { + this._userList.addUser(users[i]); + } +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 7fd5271..53ff9b6 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -16,247 +16,144 @@ const St = imports.gi.St; + const Layout = imports.ui.layout; + const Main = imports.ui.main; + const Panel = imports.ui.panel; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const AuthPrompt = imports.gdm.authPrompt; + const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; + const LoginDialog = imports.gdm.loginDialog; + + // The timeout before going back automatically to the lock screen (in seconds) + const IDLE_TIMEOUT = 2 * 60; + + const UnlockDialog = new Lang.Class({ + Name: 'UnlockDialog', + + _init: function(parentActor) { + this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, + style_class: 'login-dialog', + visible: false }); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default(); + this._userName = GLib.get_user_name(); + this._user = this._userManager.get_user(this._userName); + +- this._firstQuestion = true; +- +- this._greeterClient = new Gdm.Client(); +- this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient, { reauthenticationOnly: true }); +- this._userVerified = false; +- +- this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion)); +- this._userVerifier.connect('show-message', Lang.bind(this, this._showMessage)); +- this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); +- this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed)); +- this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); +- +- this._userVerifier.connect('show-login-hint', Lang.bind(this, this._showLoginHint)); +- this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._hideLoginHint)); +- + this._promptBox = new St.BoxLayout({ vertical: true }); + this.actor.add_child(this._promptBox); + this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + +- this._authPrompt = new AuthPrompt.AuthPrompt(); ++ this._authPrompt = new AuthPrompt.AuthPrompt(new Gdm.Client(), AuthPrompt.AuthPromptMode.UNLOCK_ONLY); ++ this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.setUser(this._user); + this._authPrompt.setPasswordChar('\u25cf'); + this._authPrompt.nextButton.label = _("Unlock"); +- this._authPrompt.connect('cancel', Lang.bind(this, this._escape)); +- this._authPrompt.connect('next', Lang.bind(this, this._doUnlock)); + + this._promptBox.add_child(this._authPrompt.actor); + + this.allowCancel = false; + + let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' }); + if (screenSaverSettings.get_boolean('user-switch-enabled')) { + let otherUserLabel = new St.Label({ text: _("Log in as another user"), + style_class: 'login-dialog-not-listed-label' }); + this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + can_focus: true, + child: otherUserLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked)); + this._promptBox.add_child(this._otherUserButton); + } else { + this._otherUserButton = null; + } + ++ this._authPrompt.begin({ userName: this._userName }); + this._updateSensitivity(true); + +- let batch = new Batch.Hold(); +- this._userVerifier.begin(this._userName, batch); +- + Main.ctrlAltTabManager.addGroup(this.actor, _("Unlock Window"), 'dialog-password-symbolic'); + + this._idleMonitor = new GnomeDesktop.IdleMonitor(); + this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape)); + }, + + _updateSensitivity: function(sensitive) { + this._authPrompt.updateSensitivity(sensitive); + + if (this._otherUserButton) { + this._otherUserButton.reactive = sensitive; + this._otherUserButton.can_focus = sensitive; + } + }, + +- _showMessage: function(userVerifier, message, styleClass) { +- this._authPrompt.setMessage(message, styleClass); +- }, +- +- _onAskQuestion: function(verifier, serviceName, question, passwordChar) { +- if (this._firstQuestion && this._firstQuestionAnswer) { +- this._userVerifier.answerQuery(serviceName, this._firstQuestionAnswer); +- this._firstQuestionAnswer = null; +- this._firstQuestion = false; +- return; +- } +- +- if (!this._firstQuestion) +- this._promptEntry.text = ''; +- else +- this._firstQuestion = false; +- +- this._authPrompt.setPasswordChar(passwordChar); +- this._authPrompt.setQuestion(question); +- +- this._currentQuery = serviceName; +- +- this._updateSensitivity(true); +- this._authPrompt.stopSpinning(); +- }, +- +- _showLoginHint: function(verifier, message) { +- this._authPrompt.setHint(message); +- }, +- +- _hideLoginHint: function() { +- this._authPrompt.setHint(null); +- }, +- +- _doUnlock: function() { +- if (this._firstQuestion) { +- // we haven't received a query yet, so stash the answer +- // and make ourself non-reactive +- // the actual reply to GDM will be sent as soon as asked +- this._firstQuestionAnswer = this._promptEntry.text; +- this._updateSensitivity(false); +- this._authPrompt.startSpinning(); +- return; +- } +- +- if (!this._currentQuery) +- return; +- +- let query = this._currentQuery; +- this._currentQuery = null; +- +- this._updateSensitivity(false); +- this._authPrompt.startSpinning(); +- +- this._userVerifier.answerQuery(query, this._authPrompt.getAnswer()); +- }, +- +- _onVerificationComplete: function() { +- this._userVerified = true; +- }, +- + _onReset: function() { +- if (!this._userVerified) { +- this._userVerifier.clear(); +- this.emit('failed'); +- } +- }, +- +- _onVerificationFailed: function() { +- this._currentQuery = null; +- this._firstQuestion = true; +- this._userVerified = false; +- +- this._authPrompt.clear(); +- +- this._updateSensitivity(false); +- this._authPrompt.stopSpinning(); ++ this.emit('failed'); + }, + + _escape: function() { + if (this.allowCancel) { +- this._userVerifier.cancel(); ++ this._authPrompt.cancel(); + this.emit('failed'); + } + }, + + _otherUserClicked: function(button, event) { + Gdm.goto_login_session_sync(null); + +- this._userVerifier.cancel(); +- this.emit('failed'); ++ this._authPrompt.cancel(); + }, + + destroy: function() { +- this._userVerifier.clear(); ++ this.popModal(); + this.actor.destroy(); + + if (this._idleWatchId) { + this._idleMonitor.remove_watch(this._idleWatchId); + this._idleWatchId = 0; + } + }, + + cancel: function() { +- this._userVerifier.cancel(null); ++ this._authPrompt.cancel(); + + this.destroy(); + }, + + addCharacter: function(unichar) { + this._authPrompt.addCharacter(unichar); + }, + + 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(); +- })); ++ this._authPrompt.finish(onComplete); + }, + + open: function(timestamp) { + this.actor.show(); + + if (this._isModal) + return true; + + if (!Main.pushModal(this.actor, { timestamp: timestamp, + keybindingMode: Shell.KeyBindingMode.UNLOCK_SCREEN })) + return false; + + this._isModal = true; + + return true; + }, + + popModal: function(timestamp) { + if (this._isModal) { + Main.popModal(this.actor, timestamp); + this._isModal = false; + } + } + }); + Signals.addSignalMethods(UnlockDialog.prototype); +-- +1.8.3.1 + + +From fad242096f53bee21ac5667bd6c7e3285100c137 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 24 Jul 2013 11:25:02 -0400 +Subject: [PATCH 24/66] loginDialog: correct typo + +commit 7e7295f259febf34c89659a9bcb05f9924fa1976 introduced a typo +(LOGIN instead of LOG_IN). + +The follow up commit fixes that. +--- + js/gdm/loginDialog.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index fe594e4..8fecc28 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -423,61 +423,61 @@ const LoginDialog = new Lang.Class({ + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + vertical: true, + visible: false }); + this._userSelectionBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + +- this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOGIN); ++ this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); + this._authPrompt.connect('reset', Lang.bind(this, this._reset)); + this._authPrompt.hide(); + + this._authPrompt.actor.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + + this.actor.add_child(this._authPrompt.actor); + this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._authPrompt.actor, + coordinate: Clutter.BindCoordinate.WIDTH })); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, +-- +1.8.3.1 + + +From b39cf4e43d81b72a203a4129b65a1755d11529a7 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 24 Jul 2013 10:53:48 -0400 +Subject: [PATCH 25/66] authPrompt: fade out message if user starts to type + +If there are no messages in the queue and a user starts to +type then we can safely hide the message label since the +user has probably already read it. + +This fixes a weirdness where "Incorrect Password" messages stay +around, even as the user types in the new correct password. + +https://bugzilla.gnome.org/show_bug.cgi?id=704817 +--- + js/gdm/authPrompt.js | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index b0dcd0d..75cf392 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -1,49 +1,51 @@ + // -*- 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 Panel = imports.ui.panel; + 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 AuthPrompt = new Lang.Class({ + Name: 'AuthPrompt', + + _init: function(gdmClient, mode) { + this.verifyingUser = false; + + 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('show-login-hint', Lang.bind(this, this._onShowLoginHint)); + this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._onHideLoginHint)); + +@@ -139,60 +141,63 @@ const AuthPrompt = new Lang.Class({ + 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) { + 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 (this.verifyingUser) + this.cancelButton.show(); + else + this.cancelButton.hide(); + + if (passwordChar) { + if (this._mode == AuthPromptMode.UNLOCK_ONLY) + this.nextButton.label = _("Unlock"); + else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN) +@@ -304,62 +309,74 @@ const AuthPrompt = new Lang.Class({ + }, + + 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._loginHint.opacity = 0; + this._loginHint.show(); + + this._entry.grab_key_focus(); + }, + + getAnswer: function() { + let 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, styleClass) { + if (message) { ++ Tweener.removeTweens(this._message); + this._message.text = message; + this._message.styleClass = styleClass; + 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._loginHint.opacity = 0; + + this.setUser(null); + + this.updateSensitivity(true); + this._entry.set_text(''); + }, + +-- +1.8.3.1 + + +From 6a8c7f5c122dc093a2f189d1bbd2a83bf0148697 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Sun, 28 Jul 2013 12:40:55 -0400 +Subject: [PATCH 26/66] authPrompt: let PAM message wrap if longer than entry + +Right now the whole authPrompt spreads out if a PAM message +comes in that longer than the entry. + +This commit changes it to wrap instead, by forcing the +auth prompt to be a fixed width (slightly bigger than +the entry width was sized to previously). + +https://bugzilla.gnome.org/show_bug.cgi?id=705037 +--- + data/theme/gnome-shell.css | 5 +---- + js/gdm/authPrompt.js | 1 + + 2 files changed, 2 insertions(+), 4 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 14ee74e..15457fe 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2315,71 +2315,68 @@ StScrollBar StButton#vhandle:active { + border-radius: 8px; + width: 64px; + height: 64px; + } + + .login-dialog-not-listed-label { + font-size: 10.5pt; + font-weight: bold; + color: #666666; + padding-top: 1em; + padding-left: 2px; + } + + .login-dialog-not-listed-button:focus .login-dialog-not-listed-label, + .login-dialog-not-listed-button:hover .login-dialog-not-listed-label { + color: #E8E8E8; + } + + .login-dialog-username { + font-size: 16pt; + font-weight: bold; + text-align: left; + padding-left: 15px; + text-shadow: black 0px 4px 3px 0px; + } + + .login-dialog-prompt-layout { + padding-top: 24px; + padding-bottom: 12px; + spacing: 8px; ++ width: 23em; + } + + .login-dialog-prompt-label { + color: #eeeeee; + font-size: 14px; + } + +-.login-dialog-prompt-entry { +- width: 20em; +-} +- + .login-dialog-session-list-button StIcon { + icon-size: 1.25em; + } + + .login-dialog-session-list-button { + color: #8b8b8b; + } + + .login-dialog-session-list-button:hover, + .login-dialog-session-list-button:active { + color: white; + } + + .login-dialog-logo-bin { + padding: 24px 0px; + } + + .login-dialog .modal-dialog-button-box { + spacing: 3px; + } + + .login-dialog .modal-dialog-button { + border-radius: 5px; + padding: 3px 18px; + } + + .login-dialog .modal-dialog-button:focus { + padding: 2px 17px; + } + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 75cf392..49b98fa 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -69,60 +69,61 @@ const AuthPrompt = new Lang.Class({ + } + })); + + this._userWell = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this.actor.add(this._userWell, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this.actor.add(this._label, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + ShellEntry.addContextMenu(this._entry, { isPassword: true }); + + this.actor.add(this._entry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._entry.grab_key_focus(); + + this._message = new St.Label({ opacity: 0 }); ++ this._message.clutter_text.line_wrap = true; + this.actor.add(this._message, { x_fill: true }); + + this._loginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); + this.actor.add(this._loginHint); + + this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', + vertical: false }); + this.actor.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + + this._defaultButtonWell = new St.Widget(); + this._defaultButtonWellActor = null; + + this._initButtons(); + + let spinnerIcon = global.datadir + '/theme/process-working.svg'; + this._spinner = new Panel.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); + this._spinner.actor.opacity = 0; + this._spinner.actor.show(); + this._defaultButtonWell.add_child(this._spinner.actor); + }, + + _onDestroy: function() { + this._userVerifier.clear(); + }, + + _initButtons: function() { + this.cancelButton = new St.Button({ style_class: 'modal-dialog-button', +-- +1.8.3.1 + + +From 94d84beac768c1bc7ed13b1cbdb4da60c9e9b242 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 22 Jul 2013 10:59:57 -0400 +Subject: [PATCH 27/66] authPrompt: Call next button "Unlock" when user + switching + +When a ShellUserVerifier is asked to verify a user at the login +screen it will transparently first try to reauthenticate the user +against an existing session and then fall back to logging a user +into a new session. The former is used for user switching. +It's useful to know which type of verification is happening, so +the next button can be made to say "Unlock" instead of "Sign In" when +a user is already signed in. + +This commit exports a new "reauthenticating" property on the +ShellUserVerifier that the auth prompt checks when deciding which +label to use for its next button. + +https://bugzilla.gnome.org/show_bug.cgi?id=704795 +--- + js/gdm/authPrompt.js | 4 ++-- + js/gdm/util.js | 3 +++ + 2 files changed, 5 insertions(+), 2 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 49b98fa..706cbe0 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -172,63 +172,63 @@ const AuthPrompt = new Lang.Class({ + 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) { + 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 (this.verifyingUser) + this.cancelButton.show(); + else + this.cancelButton.hide(); + + if (passwordChar) { +- if (this._mode == AuthPromptMode.UNLOCK_ONLY) ++ if (this._userVerifier.reauthenticating) + this.nextButton.label = _("Unlock"); +- else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN) ++ else + this.nextButton.label = C_("button", "Sign In"); + } else { + this.nextButton.label = _("Next"); + } + + this.updateSensitivity(true); + this.emit('prompted'); + }, + + _onShowMessage: function(userVerifier, message, styleClass) { + this.setMessage(message, styleClass); + }, + + _onVerificationFailed: function() { + this.clear(); + + this.updateSensitivity(true); + this.setActorInDefaultButtonWell(null); + this.userVerified = false; + }, + + _onVerificationComplete: function() { + this.userVerified = true; + }, + + _onReset: function() { + if (!this.userVerified) + this.reset(); + }, + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 20540b7..1facf98 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -94,68 +94,70 @@ function cloneAndFadeOutActor(actor) { + 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', + + _init: function(client, params) { + params = Params.parse(params, { reauthenticationOnly: false }); + this._reauthOnly = params.reauthenticationOnly; + + this._client = client; + + this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA }); + + this._fprintManager = new Fprint.FprintManager(); + this._messageQueue = []; + this._messageQueueTimeoutId = 0; + this.hasPendingMessages = false; ++ this.reauthenticating = false; + + this._failCounter = 0; + }, + + begin: function(userName, hold) { + this._cancellable = new Gio.Cancellable(); + this._hold = hold; + this._userName = userName; ++ this.reauthenticating = false; + + this._checkForFingerprintReader(); + + if (userName) { + // If possible, reauthenticate an already running session, + // so any session specific credentials get updated appropriately + this._client.open_reauthentication_channel(userName, this._cancellable, + Lang.bind(this, this._reauthenticationChannelOpened)); + } else { + this._client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + } + }, + + cancel: function() { + if (this._cancellable) + this._cancellable.cancel(); + + if (this._userVerifier) + this._userVerifier.call_cancel_sync(null); + }, + + clear: function() { + if (this._cancellable) { + this._cancellable.cancel(); + this._cancellable = null; + } + + if (this._userVerifier) { + this._userVerifier.run_dispose(); + this._userVerifier = null; +@@ -240,60 +242,61 @@ const ShellUserVerifier = new Lang.Class({ + if (!error && device) + this._haveFingerprintReader = true; + })); + }, + + _reportInitError: function(where, error) { + logError(error, where); + this._hold.release(); + + this._queueMessage(_("Authentication error"), 'login-dialog-message-warning'); + this._verificationFailed(false); + }, + + _reauthenticationChannelOpened: function(client, result) { + try { + this._userVerifier = client.open_reauthentication_channel_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e if e.matches(Gio.DBusError, Gio.DBusError.ACCESS_DENIED) && + !this._reauthOnly) { + // Gdm emits org.freedesktop.DBus.Error.AccessDenied when there is + // no session to reauthenticate. Fall back to performing verification + // from this login session + client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + return; + } catch(e) { + this._reportInitError('Failed to open reauthentication channel', e); + return; + } + ++ this.reauthenticating = true; + this._connectSignals(); + this._beginVerification(); + this._hold.release(); + }, + + _userVerifierGot: function(client, result) { + try { + this._userVerifier = client.get_user_verifier_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e) { + this._reportInitError('Failed to obtain user verifier', e); + return; + } + + this._connectSignals(); + this._beginVerification(); + this._hold.release(); + }, + + _connectSignals: function() { + this._userVerifier.connect('info', Lang.bind(this, this._onInfo)); + this._userVerifier.connect('problem', Lang.bind(this, this._onProblem)); + this._userVerifier.connect('info-query', Lang.bind(this, this._onInfoQuery)); + this._userVerifier.connect('secret-info-query', Lang.bind(this, this._onSecretInfoQuery)); + this._userVerifier.connect('conversation-stopped', Lang.bind(this, this._onConversationStopped)); + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + }, + +-- +1.8.3.1 + + +From 309050b22017825ebe8ae8e7d307b7f0845951f5 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 29 Jul 2013 13:24:36 -0400 +Subject: [PATCH 28/66] authPrompt: disassociate from userVerifier when + destroyed + +Otherwise, it won't get GC'd and we'll end up potentially calling +its signal handlers after destruction. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/authPrompt.js | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 706cbe0..d50f0fb 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -96,60 +96,62 @@ const AuthPrompt = new Lang.Class({ + this._entry.grab_key_focus(); + + this._message = new St.Label({ opacity: 0 }); + this._message.clutter_text.line_wrap = true; + this.actor.add(this._message, { x_fill: true }); + + this._loginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); + this.actor.add(this._loginHint); + + this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', + vertical: false }); + this.actor.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + + this._defaultButtonWell = new St.Widget(); + this._defaultButtonWellActor = null; + + this._initButtons(); + + let spinnerIcon = global.datadir + '/theme/process-working.svg'; + this._spinner = new Panel.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); + this._spinner.actor.opacity = 0; + this._spinner.actor.show(); + this._defaultButtonWell.add_child(this._spinner.actor); + }, + + _onDestroy: function() { + this._userVerifier.clear(); ++ this._userVerifier.disconnectAll(); ++ this._userVerifier = null; + }, + + _initButtons: function() { + this.cancelButton = new St.Button({ style_class: 'modal-dialog-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + reactive: true, + can_focus: true, + label: _("Cancel") }); + this.cancelButton.connect('clicked', + Lang.bind(this, function() { + this.cancel(); + })); + this._buttonBox.add(this.cancelButton, + { expand: false, + x_fill: false, + y_fill: false, + x_align: St.Align.START, + y_align: St.Align.END }); + + this._buttonBox.add(this._defaultButtonWell, + { expand: true, + x_fill: false, + y_fill: false, + x_align: St.Align.END, + y_align: St.Align.MIDDLE }); + this.nextButton = new St.Button({ style_class: 'modal-dialog-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + reactive: true, + can_focus: true, + label: _("Next") }); +-- +1.8.3.1 + + +From c2c79e291c22d7abea617a4c8e1cb0299f7cc17a Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Sun, 28 Jul 2013 15:55:09 -0400 +Subject: [PATCH 29/66] loginDialog: don't ever call _reset directly + +The only time we ever call _reset directly is when +detecting changes to disable-user-list. We can implicitly +trigger a reset for this case, just as easily by calling +this._authPrompt.reset() + +This commit makes that change for consistency and to make +it easier to adjust the authprompt workflow later. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/loginDialog.js | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 8fecc28..1ed68ba 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -425,61 +425,61 @@ const LoginDialog = new Lang.Class({ + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + vertical: true, + visible: false }); + this._userSelectionBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + + this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); +- this._authPrompt.connect('reset', Lang.bind(this, this._reset)); ++ this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.hide(); + + this._authPrompt.actor.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + + this.actor.add_child(this._authPrompt.actor); + this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._authPrompt.actor, + coordinate: Clutter.BindCoordinate.WIDTH })); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); +@@ -504,109 +504,106 @@ const LoginDialog = new Lang.Class({ + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; + + if (!this._authPrompt.verifyingUser) +- this._reset(); ++ this._authPrompt.reset(); + } + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + + this._authPrompt.cancelButton.show(); + + this._showPrompt(); + }, + +- _reset: function() { +- if (this._authPrompt.verifyingUser) +- return; +- ++ _onReset: function() { + this._sessionMenuButton.updateSensitivity(true); + + this._user = null; + + if (this._disableUserList) + this._hideUserListAndLogIn(); + else + this._showUserList(); + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { + if (this._authPrompt.verifyingUser) + return true; + + if (!this._user) + return false; + + if (this._user.is_logged_in) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) + return; +-- +1.8.3.1 + + +From 47486c4ec7daf316044ba9b61aed95cf68bd8a2f Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Sun, 28 Jul 2013 16:24:31 -0400 +Subject: [PATCH 30/66] authPrompt: consolidate verifyingUser/userVerified + +Right now we have two booleans that specify when user verification +is happening and when it succeeded, respectively. + +This commit consolidates them into one AuthPromptStatus enumeration. + +This clean up will allow us to check for verification failure more +easily. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/authPrompt.js | 22 ++++++++++++++-------- + js/gdm/loginDialog.js | 2 +- + 2 files changed, 15 insertions(+), 9 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index d50f0fb..f915bf9 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -1,61 +1,68 @@ + // -*- 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 Panel = imports.ui.panel; + 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 AuthPrompt = new Lang.Class({ + Name: 'AuthPrompt', + + _init: function(gdmClient, mode) { +- this.verifyingUser = false; ++ 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('show-login-hint', Lang.bind(this, this._onShowLoginHint)); + this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._onHideLoginHint)); + + 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; + } + })); + +@@ -195,69 +202,69 @@ const AuthPrompt = new Lang.Class({ + this.setPasswordChar(passwordChar); + this.setQuestion(question); + + if (this.verifyingUser) + this.cancelButton.show(); + else + this.cancelButton.hide(); + + 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'); + }, + + _onShowMessage: function(userVerifier, message, styleClass) { + this.setMessage(message, styleClass); + }, + + _onVerificationFailed: function() { + this.clear(); + + this.updateSensitivity(true); + this.setActorInDefaultButtonWell(null); +- this.userVerified = false; ++ this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; + }, + + _onVerificationComplete: function() { +- this.userVerified = true; ++ this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; + }, + + _onReset: function() { +- if (!this.userVerified) ++ if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) + this.reset(); + }, + + _onShowLoginHint: function(verifier, message) { + this.setHint(message); + }, + + _onHideLoginHint: function() { + this.setHint(null); + }, + + addActorToDefaultButtonWell: function(actor) { + this._defaultButtonWell.add_child(actor); + + actor.add_constraint(new Clutter.AlignConstraint({ source: this._spinner.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + }, + + setActorInDefaultButtonWell: function(actor, animate) { + if (!this._defaultButtonWellActor && + !actor) + return; + + let oldActor = this._defaultButtonWellActor; + + if (oldActor) + Tweener.removeTweens(oldActor); + + let isSpinner; +@@ -376,85 +383,84 @@ const AuthPrompt = new Lang.Class({ + this.setActorInDefaultButtonWell(null, true); + this.actor.hide(); + this._loginHint.opacity = 0; + + this.setUser(null); + + this.updateSensitivity(true); + this._entry.set_text(''); + }, + + setUser: function(user) { + if (user) { + let userWidget = new UserWidget.UserWidget(user); + this._userWell.set_child(userWidget.actor); + } else { + this._userWell.set_child(null); + } + }, + + setHint: function(message) { + if (message) { + this._loginHint.set_text(message) + this._loginHint.opacity = 255; + } else { + this._loginHint.opacity = 0; + this._loginHint.set_text(''); + } + }, + + reset: function() { +- this.verifyingUser = false; +- this.userVerified = false; ++ this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + this._queryingService = null; + this.clear(); + this._message.opacity = 0; + this.setUser(null); + this.stopSpinning(); + this.setHint(null); + + this.emit('reset'); + }, + + 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.verifyingUser = true; ++ 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.verifyingUser) ++ if (this.verificationStatus == AuthPromptStatus.VERIFYING) + this._userVerifier.cancel(); + + this.reset(); + } + }); + Signals.addSignalMethods(AuthPrompt.prototype); +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 1ed68ba..c5c42f1 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -503,61 +503,61 @@ const LoginDialog = new Lang.Class({ + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; + +- if (!this._authPrompt.verifyingUser) ++ if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING) + this._authPrompt.reset(); + } + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + +-- +1.8.3.1 + + +From 6945dd07b5b29e4f9efa753e867a88906447cdd6 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 29 Jul 2013 10:52:02 -0400 +Subject: [PATCH 31/66] util: clear user verifier after cancelling it + +If we don't clear it, then the connection to gdm will remain open. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/util.js | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 1facf98..9557c66 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -121,62 +121,64 @@ const ShellUserVerifier = new Lang.Class({ + this._messageQueue = []; + this._messageQueueTimeoutId = 0; + this.hasPendingMessages = false; + this.reauthenticating = false; + + this._failCounter = 0; + }, + + begin: function(userName, hold) { + this._cancellable = new Gio.Cancellable(); + this._hold = hold; + this._userName = userName; + this.reauthenticating = false; + + this._checkForFingerprintReader(); + + if (userName) { + // If possible, reauthenticate an already running session, + // so any session specific credentials get updated appropriately + this._client.open_reauthentication_channel(userName, this._cancellable, + Lang.bind(this, this._reauthenticationChannelOpened)); + } else { + this._client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + } + }, + + cancel: function() { + if (this._cancellable) + this._cancellable.cancel(); + +- if (this._userVerifier) ++ if (this._userVerifier) { + this._userVerifier.call_cancel_sync(null); ++ this.clear(); ++ } + }, + + clear: function() { + if (this._cancellable) { + this._cancellable.cancel(); + this._cancellable = null; + } + + if (this._userVerifier) { + this._userVerifier.run_dispose(); + this._userVerifier = null; + } + + this._clearMessageQueue(); + }, + + answerQuery: function(serviceName, answer) { + if (!this.hasPendingMessages) { + this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); + } else { + let signalId = this.connect('no-more-messages', + Lang.bind(this, function() { + this.disconnect(signalId); + this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); + })); + } + }, + + _getIntervalForMessage: function(message) { + // We probably could be smarter here +-- +1.8.3.1 + + +From b059c30b27973a492439b7f4ad50c1441e884137 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 29 Jul 2013 08:46:37 -0400 +Subject: [PATCH 32/66] authPrompt: cancel user verification if verifying when + reset + +authPrompt.reset() currently only leaves the authPrompt in a +sane state if the user isn't verifying. + +This commit makes sure to cancel verification if a reset happens +while verification is in process. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/authPrompt.js | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index f915bf9..07549c8 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -210,62 +210,64 @@ const AuthPrompt = new Lang.Class({ + 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'); + }, + + _onShowMessage: function(userVerifier, message, styleClass) { + this.setMessage(message, styleClass); + }, + + _onVerificationFailed: function() { + this.clear(); + + this.updateSensitivity(true); + this.setActorInDefaultButtonWell(null); + this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; + }, + + _onVerificationComplete: function() { + this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; + }, + + _onReset: function() { +- if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) ++ if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) { ++ this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + this.reset(); ++ } + }, + + _onShowLoginHint: function(verifier, message) { + this.setHint(message); + }, + + _onHideLoginHint: function() { + this.setHint(null); + }, + + addActorToDefaultButtonWell: function(actor) { + this._defaultButtonWell.add_child(actor); + + actor.add_constraint(new Clutter.AlignConstraint({ source: this._spinner.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + }, + + setActorInDefaultButtonWell: function(actor, animate) { + if (!this._defaultButtonWellActor && + !actor) + return; + + let oldActor = this._defaultButtonWellActor; + + if (oldActor) + Tweener.removeTweens(oldActor); + + let isSpinner; + if (actor == this._spinner.actor) +@@ -383,84 +385,86 @@ const AuthPrompt = new Lang.Class({ + this.setActorInDefaultButtonWell(null, true); + this.actor.hide(); + this._loginHint.opacity = 0; + + this.setUser(null); + + this.updateSensitivity(true); + this._entry.set_text(''); + }, + + setUser: function(user) { + if (user) { + let userWidget = new UserWidget.UserWidget(user); + this._userWell.set_child(userWidget.actor); + } else { + this._userWell.set_child(null); + } + }, + + setHint: function(message) { + if (message) { + this._loginHint.set_text(message) + this._loginHint.opacity = 255; + } else { + this._loginHint.opacity = 0; + this._loginHint.set_text(''); + } + }, + + reset: function() { ++ let oldStatus = this.verificationStatus; + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; ++ ++ if (oldStatus == AuthPromptStatus.VERIFYING) ++ this._userVerifier.cancel(); ++ + this._queryingService = null; + this.clear(); + this._message.opacity = 0; + this.setUser(null); + this.stopSpinning(); + this.setHint(null); + + this.emit('reset'); + }, + + 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.VERIFYING) +- this._userVerifier.cancel(); +- + this.reset(); + } + }); + Signals.addSignalMethods(AuthPrompt.prototype); +-- +1.8.3.1 + + +From c924ad2d3a4b10df3aa3757f8058f89c00fe5cd2 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Sun, 28 Jul 2013 16:06:04 -0400 +Subject: [PATCH 33/66] unlockDialog: only emit 'failed' on reset after + failure/cancel + +We currently emit "failed" any time the UserVerifier is reset, +and user verification didn't succeed prior. + +A more conceptually clear time to emit "failed" would be if +the UserVerifier is reset and user verification failed prior, +and to emit "failed" if the user cancels unlock. + +This commit restructures things to do that. Aside from being +more conceptually clear, it also lays the groundwork for us +to be able to reset the unlock screen without failing. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/authPrompt.js | 4 ++++ + js/ui/unlockDialog.js | 9 ++++----- + 2 files changed, 8 insertions(+), 5 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 07549c8..c10a178 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -398,73 +398,77 @@ const AuthPrompt = new Lang.Class({ + this._userWell.set_child(userWidget.actor); + } else { + this._userWell.set_child(null); + } + }, + + setHint: function(message) { + if (message) { + this._loginHint.set_text(message) + this._loginHint.opacity = 255; + } else { + this._loginHint.opacity = 0; + this._loginHint.set_text(''); + } + }, + + reset: function() { + let oldStatus = this.verificationStatus; + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + + if (oldStatus == AuthPromptStatus.VERIFYING) + this._userVerifier.cancel(); + + this._queryingService = null; + this.clear(); + this._message.opacity = 0; + this.setUser(null); + this.stopSpinning(); + this.setHint(null); + ++ if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED) ++ this.emit('failed'); ++ + this.emit('reset'); + }, + + addCharacter: function(unichar) { + if (!this._entry.visible) + return; + + this._entry.grab_key_focus(); + this._entry.clutter_text.insert_unichar(unichar); + }, + + begin: function(params) { + params = Params.parse(params, { userName: null, + hold: null }); + + this.updateSensitivity(false); + + let hold = params.hold; + if (!hold) + hold = new Batch.Hold(); + + this._userVerifier.begin(params.userName, hold); + this.verificationStatus = AuthPromptStatus.VERIFYING; + }, + + finish: function(onComplete) { + if (!this._userVerifier.hasPendingMessages) { + onComplete(); + return; + } + + let signalId = this._userVerifier.connect('no-more-messages', + Lang.bind(this, function() { + this._userVerifier.disconnect(signalId); + onComplete(); + })); + }, + + cancel: function() { + this.reset(); ++ this.emit('cancelled'); + } + }); + Signals.addSignalMethods(AuthPrompt.prototype); +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 53ff9b6..bfc97f3 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -23,112 +23,111 @@ const UserWidget = imports.ui.userWidget; + const AuthPrompt = imports.gdm.authPrompt; + const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; + const LoginDialog = imports.gdm.loginDialog; + + // The timeout before going back automatically to the lock screen (in seconds) + const IDLE_TIMEOUT = 2 * 60; + + const UnlockDialog = new Lang.Class({ + Name: 'UnlockDialog', + + _init: function(parentActor) { + this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, + style_class: 'login-dialog', + visible: false }); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default(); + this._userName = GLib.get_user_name(); + this._user = this._userManager.get_user(this._userName); + + this._promptBox = new St.BoxLayout({ vertical: true }); + this.actor.add_child(this._promptBox); + this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + + this._authPrompt = new AuthPrompt.AuthPrompt(new Gdm.Client(), AuthPrompt.AuthPromptMode.UNLOCK_ONLY); +- this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); ++ this._authPrompt.connect('failed', Lang.bind(this, this._fail)); ++ this._authPrompt.connect('cancelled', Lang.bind(this, this._fail)); + this._authPrompt.setUser(this._user); + this._authPrompt.setPasswordChar('\u25cf'); + this._authPrompt.nextButton.label = _("Unlock"); + + this._promptBox.add_child(this._authPrompt.actor); + + this.allowCancel = false; + + let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' }); + if (screenSaverSettings.get_boolean('user-switch-enabled')) { + let otherUserLabel = new St.Label({ text: _("Log in as another user"), + style_class: 'login-dialog-not-listed-label' }); + this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + can_focus: true, + child: otherUserLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked)); + this._promptBox.add_child(this._otherUserButton); + } else { + this._otherUserButton = null; + } + + this._authPrompt.begin({ userName: this._userName }); + this._updateSensitivity(true); + + Main.ctrlAltTabManager.addGroup(this.actor, _("Unlock Window"), 'dialog-password-symbolic'); + + this._idleMonitor = new GnomeDesktop.IdleMonitor(); + this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape)); + }, + + _updateSensitivity: function(sensitive) { + this._authPrompt.updateSensitivity(sensitive); + + if (this._otherUserButton) { + this._otherUserButton.reactive = sensitive; + this._otherUserButton.can_focus = sensitive; + } + }, + +- _onReset: function() { ++ _fail: function() { + this.emit('failed'); + }, + + _escape: function() { +- if (this.allowCancel) { ++ if (this.allowCancel) + this._authPrompt.cancel(); +- this.emit('failed'); +- } + }, + + _otherUserClicked: function(button, event) { + Gdm.goto_login_session_sync(null); + + this._authPrompt.cancel(); + }, + + destroy: function() { + this.popModal(); + this.actor.destroy(); + + if (this._idleWatchId) { + this._idleMonitor.remove_watch(this._idleWatchId); + this._idleWatchId = 0; + } + }, + + cancel: function() { + this._authPrompt.cancel(); + + this.destroy(); + }, + + addCharacter: function(unichar) { + this._authPrompt.addCharacter(unichar); + }, + + finish: function(onComplete) { + this._authPrompt.finish(onComplete); +-- +1.8.3.1 + + +From 059a6c0e6255e5c504f24d56f956c067296b64e9 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Fri, 16 Aug 2013 13:26:13 -0400 +Subject: [PATCH 34/66] loginDialog: fix session menu visibility + +The shouldShowSessionMenu function has a few bugs in it. +This fixes them. + +https://bugzilla.gnome.org/show_bug.cgi?id=706153 +--- + js/gdm/loginDialog.js | 7 ++----- + 1 file changed, 2 insertions(+), 5 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index c5c42f1..6808902 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -565,67 +565,64 @@ const LoginDialog = new Lang.Class({ + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + + this._authPrompt.cancelButton.show(); + + this._showPrompt(); + }, + + _onReset: function() { + this._sessionMenuButton.updateSensitivity(true); + + this._user = null; + + if (this._disableUserList) + this._hideUserListAndLogIn(); + else + this._showUserList(); + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { +- if (this._authPrompt.verifyingUser) +- return true; +- +- if (!this._user) ++ if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING) + return false; + +- if (this._user.is_logged_in) ++ if (this._user && this._user.is_logged_in()) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) + return; + this._authPrompt.actor.opacity = 0; + this._authPrompt.actor.show(); + Tweener.addTween(this._authPrompt.actor, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + 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._authPrompt.setHint(_("(e.g., user or %s)").format(hint)); + }, + + _askForUsernameAndLogIn: function() { +-- +1.8.3.1 + + +From 198813b83cfc04638e72adb110def48035f8244e Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Sun, 28 Jul 2013 17:49:50 -0400 +Subject: [PATCH 35/66] authPrompt: add support for auth without username + +This commit introduces a new BeginRequestType enum which gets +passed to the 'call-begin-when-ready' signal to specify whether +a username should be provided to the begin() method and changes +the loginDialog to comply. + +Currently, the signal only ever gets emitted with + +AuthPrompt.BeginRequestType.PROVIDE_USERNAME + +but that will change in the future when providing smartcard +support. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/authPrompt.js | 7 ++++++- + js/gdm/loginDialog.js | 32 ++++++++++++++++++++++++-------- + js/ui/unlockDialog.js | 16 ++++++++++++++-- + 3 files changed, 44 insertions(+), 11 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index c10a178..e987d25 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -4,60 +4,65 @@ const Clutter = imports.gi.Clutter; + const Lang = imports.lang; + const Signals = imports.signals; + const St = imports.gi.St; + + const Panel = imports.ui.panel; + 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('show-login-hint', Lang.bind(this, this._onShowLoginHint)); + this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._onHideLoginHint)); + + this.connect('next', Lang.bind(this, function() { + this.updateSensitivity(false); + this.startSpinning(); + if (this._queryingService) { + this._userVerifier.answerQuery(this._queryingService, this._entry.text); +@@ -401,61 +406,61 @@ const AuthPrompt = new Lang.Class({ + } + }, + + setHint: function(message) { + if (message) { + this._loginHint.set_text(message) + this._loginHint.opacity = 255; + } else { + this._loginHint.opacity = 0; + this._loginHint.set_text(''); + } + }, + + reset: function() { + let oldStatus = this.verificationStatus; + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + + if (oldStatus == AuthPromptStatus.VERIFYING) + this._userVerifier.cancel(); + + this._queryingService = null; + this.clear(); + this._message.opacity = 0; + this.setUser(null); + this.stopSpinning(); + this.setHint(null); + + if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED) + this.emit('failed'); + +- this.emit('reset'); ++ this.emit('reset', BeginRequestType.PROVIDE_USERNAME); + }, + + 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; + } + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 6808902..b40aa85 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -449,61 +449,66 @@ const LoginDialog = new Lang.Class({ + { expand: true, + x_fill: true, + y_fill: true }); + + this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); + this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.hide(); + + this._authPrompt.actor.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + + this.actor.add_child(this._authPrompt.actor); + this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._authPrompt.actor, + coordinate: Clutter.BindCoordinate.WIDTH })); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + +- this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAndLogIn)); ++ this._notListedButton.connect('clicked', ++ Lang.bind(this, function() { ++ this._authPrompt.cancelButton.show(); ++ this._hideUserListAndLogIn(); ++ })); ++ + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + + this._logoBin = new St.Bin({ style_class: 'login-dialog-logo-bin', y_expand: true }); + this._logoBin.set_y_align(Clutter.ActorAlign.END); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.X_AXIS, + factor: 0.5 })); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.Y_AXIS, + factor: 1.0 })); + this.actor.add_child(this._logoBin); + this._updateLogo(); + + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + +@@ -549,110 +554,112 @@ const LoginDialog = new Lang.Class({ + + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + + this._authPrompt.cancelButton.show(); + + this._showPrompt(); + }, + +- _onReset: function() { ++ _onReset: function(authPrompt, beginRequest) { + this._sessionMenuButton.updateSensitivity(true); + + this._user = null; + +- if (this._disableUserList) ++ if (this._disableUserList) { ++ this._authPrompt.cancelButton.hide(); + this._hideUserListAndLogIn(); +- else +- this._showUserList(); ++ } else { ++ this._showUserList(); ++ } + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { + if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING) + return false; + + if (this._user && this._user.is_logged_in()) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) + return; + this._authPrompt.actor.opacity = 0; + this._authPrompt.actor.show(); + Tweener.addTween(this._authPrompt.actor, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + 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._authPrompt.setHint(_("(e.g., user or %s)").format(hint)); + }, + +- _askForUsernameAndLogIn: function() { ++ _askForUsernameAndBeginVerification: function() { + this._authPrompt.setPasswordChar(''); + this._authPrompt.setQuestion(_("Username: ")); + + let realmManager = new Realmd.Manager(); + let realmSignalId = realmManager.connect('login-format-changed', + Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + + let nextSignalId = this._authPrompt.connect('next', + Lang.bind(this, function() { + this._authPrompt.disconnect(nextSignalId); + this._authPrompt.updateSensitivity(false); + let answer = this._authPrompt.getAnswer(); + this._authPrompt.clear(); + this._authPrompt.startSpinning(); + this._authPrompt.begin({ userName: answer }); + + realmManager.disconnect(realmSignalId) + realmManager.release(); + })); + this._authPrompt.cancelButton.hide(); + this._showPrompt(); + }, + + _startSession: function(serviceName) { + Tweener.addTween(this.actor, + { opacity: 0, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onUpdate: function() { +@@ -781,65 +788,74 @@ const LoginDialog = new Lang.Class({ + }, + + _onTimedLoginRequested: function(client, userName, seconds) { + this._startTimedLogin(userName, seconds); + + global.stage.connect('captured-event', + Lang.bind(this, function(actor, event) { + if (this._timedLoginDelay == undefined) + return false; + + if (event.type() == Clutter.EventType.KEY_PRESS || + event.type() == Clutter.EventType.BUTTON_PRESS) { + if (this._timedLoginBatch) { + this._timedLoginBatch.cancel(); + this._timedLoginBatch = null; + } + } else if (event.type() == Clutter.EventType.KEY_RELEASE || + event.type() == Clutter.EventType.BUTTON_RELEASE) { + this._resetTimedLogin(); + } + + return false; + })); + }, + + _setUserListExpanded: function(expanded) { + this._userList.updateStyle(expanded); + this._userSelectionBox.visible = expanded; + }, + +- _hideUserListAndLogIn: function() { ++ _hideUserList: function() { + this._setUserListExpanded(false); + if (this._userSelectionBox.visible) + GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); +- this._askForUsernameAndLogIn(); ++ }, ++ ++ _hideUserListAskForUsernameAndBeginVerification: function() { ++ this._hideUserList(); ++ this._askForUsernameAndBeginVerification(); ++ }, ++ ++ _hideUserListAndBeginVerification: function() { ++ this._hideUserList(); ++ this._authPrompt.begin(); + }, + + _showUserList: function() { + this._authPrompt.hide(); + this._sessionMenuButton.close(); + this._setUserListExpanded(true); + this._notListedButton.show(); + this._userList.actor.grab_key_focus(); + }, + + _beginVerificationForItem: function(item) { + this._authPrompt.setUser(item.user); + + let userName = item.user.get_user_name(); + let hold = new Batch.Hold(); + + this._authPrompt.begin({ userName: userName, + hold: hold }); + return hold; + }, + + _onUserListActivated: function(activatedItem) { + let tasks = [function() { + return GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + }, + function() { + this._setUserListExpanded(false); + }]; + + this._user = activatedItem.user; +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index bfc97f3..09024ba 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -25,106 +25,118 @@ const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; + const LoginDialog = imports.gdm.loginDialog; + + // The timeout before going back automatically to the lock screen (in seconds) + const IDLE_TIMEOUT = 2 * 60; + + const UnlockDialog = new Lang.Class({ + Name: 'UnlockDialog', + + _init: function(parentActor) { + this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, + style_class: 'login-dialog', + visible: false }); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default(); + this._userName = GLib.get_user_name(); + this._user = this._userManager.get_user(this._userName); + + this._promptBox = new St.BoxLayout({ vertical: true }); + this.actor.add_child(this._promptBox); + this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + + this._authPrompt = new AuthPrompt.AuthPrompt(new Gdm.Client(), AuthPrompt.AuthPromptMode.UNLOCK_ONLY); + this._authPrompt.connect('failed', Lang.bind(this, this._fail)); + this._authPrompt.connect('cancelled', Lang.bind(this, this._fail)); +- this._authPrompt.setUser(this._user); ++ this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.setPasswordChar('\u25cf'); + this._authPrompt.nextButton.label = _("Unlock"); + + this._promptBox.add_child(this._authPrompt.actor); + + this.allowCancel = false; + + let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' }); + if (screenSaverSettings.get_boolean('user-switch-enabled')) { + let otherUserLabel = new St.Label({ text: _("Log in as another user"), + style_class: 'login-dialog-not-listed-label' }); + this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + can_focus: true, + child: otherUserLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked)); + this._promptBox.add_child(this._otherUserButton); + } else { + this._otherUserButton = null; + } + +- this._authPrompt.begin({ userName: this._userName }); ++ this._authPrompt.reset(); + this._updateSensitivity(true); + + Main.ctrlAltTabManager.addGroup(this.actor, _("Unlock Window"), 'dialog-password-symbolic'); + + this._idleMonitor = new GnomeDesktop.IdleMonitor(); + this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, Lang.bind(this, this._escape)); + }, + + _updateSensitivity: function(sensitive) { + this._authPrompt.updateSensitivity(sensitive); + + if (this._otherUserButton) { + this._otherUserButton.reactive = sensitive; + this._otherUserButton.can_focus = sensitive; + } + }, + + _fail: function() { + this.emit('failed'); + }, + ++ _onReset: function(authPrompt, beginRequest) { ++ let userName; ++ if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { ++ this._authPrompt.setUser(this._user); ++ userName = this._userName; ++ } else { ++ userName = null; ++ } ++ ++ this._authPrompt.begin({ userName: userName }); ++ }, ++ + _escape: function() { + if (this.allowCancel) + this._authPrompt.cancel(); + }, + + _otherUserClicked: function(button, event) { + Gdm.goto_login_session_sync(null); + + this._authPrompt.cancel(); + }, + + destroy: function() { + this.popModal(); + this.actor.destroy(); + + if (this._idleWatchId) { + this._idleMonitor.remove_watch(this._idleWatchId); + this._idleWatchId = 0; + } + }, + + cancel: function() { + this._authPrompt.cancel(); + + this.destroy(); + }, + + addCharacter: function(unichar) { + this._authPrompt.addCharacter(unichar); + }, +-- +1.8.3.1 + + +From e38b3ad85b9ac860862aaab0c6e2a8fb4559810c Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Sun, 28 Jul 2013 19:42:26 -0400 +Subject: [PATCH 36/66] util: abstract out main auth service in code + +Right now, the primary way a user logs in is with +a password. They can also swipe their finger, if their +fingerprint is enrolled, but it's expected the fingerprint +auth service won't ask questions the user has to respond to +by typing. As such, we ignore questions that comes from +anything but the main auth service: gdm-password. + +In the future, if a user inserts a smartcard, we'll want +to treat the gdm-smartcard service as the main auth service, +and let any questions from it get to the user. + +This commit tries to prepare for that eventuality by storing +the name of the main auth service away in a _mainService variable +when verification is first begun, and then later checking incoming +queries against that service instead of checking against +gdm-password directly. + +Of course, right now, _mainService is always gdm-password. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/util.js | 35 ++++++++++++++++++++++++----------- + 1 file changed, 24 insertions(+), 11 deletions(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 9557c66..eba8b27 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -89,60 +89,61 @@ function cloneAndFadeOutActor(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', + + _init: function(client, params) { + params = Params.parse(params, { reauthenticationOnly: false }); + this._reauthOnly = params.reauthenticationOnly; + + this._client = client; + + this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA }); ++ this._updateDefaultService(); + + this._fprintManager = new Fprint.FprintManager(); + this._messageQueue = []; + this._messageQueueTimeoutId = 0; + this.hasPendingMessages = false; + this.reauthenticating = false; + + this._failCounter = 0; + }, + + begin: function(userName, hold) { + this._cancellable = new Gio.Cancellable(); + this._hold = hold; + this._userName = userName; + this.reauthenticating = false; + + this._checkForFingerprintReader(); + + if (userName) { + // If possible, reauthenticate an already running session, + // so any session specific credentials get updated appropriately + this._client.open_reauthentication_channel(userName, this._cancellable, + Lang.bind(this, this._reauthenticationChannelOpened)); + } else { + this._client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + } + }, + + cancel: function() { + if (this._cancellable) +@@ -275,193 +276,205 @@ const ShellUserVerifier = new Lang.Class({ + this._connectSignals(); + this._beginVerification(); + this._hold.release(); + }, + + _userVerifierGot: function(client, result) { + try { + this._userVerifier = client.get_user_verifier_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e) { + this._reportInitError('Failed to obtain user verifier', e); + return; + } + + this._connectSignals(); + this._beginVerification(); + this._hold.release(); + }, + + _connectSignals: function() { + this._userVerifier.connect('info', Lang.bind(this, this._onInfo)); + this._userVerifier.connect('problem', Lang.bind(this, this._onProblem)); + this._userVerifier.connect('info-query', Lang.bind(this, this._onInfoQuery)); + this._userVerifier.connect('secret-info-query', Lang.bind(this, this._onSecretInfoQuery)); + this._userVerifier.connect('conversation-stopped', Lang.bind(this, this._onConversationStopped)); + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + }, + ++ _getForegroundService: function() { ++ // For now, the foreground service is always the default service ++ return this._defaultService; ++ }, ++ ++ serviceIsForeground: function(serviceName) { ++ return serviceName == this._getForegroundService(); ++ }, ++ ++ _updateDefaultService: function() { ++ // For now, the default service is always the password service ++ this._defaultService = PASSWORD_SERVICE_NAME; ++ }, ++ + _beginVerification: function() { + this._hold.acquire(); + + if (this._userName) { +- this._userVerifier.call_begin_verification_for_user(PASSWORD_SERVICE_NAME, ++ this._userVerifier.call_begin_verification_for_user(this._getForegroundService(), + this._userName, + this._cancellable, + Lang.bind(this, function(obj, result) { + try { + obj.call_begin_verification_for_user_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e) { + this._reportInitError('Failed to start verification for user', e); + return; + } + + this._hold.release(); + })); + + if (this._haveFingerprintReader) { + this._hold.acquire(); + + this._userVerifier.call_begin_verification_for_user(FINGERPRINT_SERVICE_NAME, + this._userName, + this._cancellable, + Lang.bind(this, function(obj, result) { + try { + obj.call_begin_verification_for_user_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e) { + this._reportInitError('Failed to start fingerprint verification for user', e); + return; + } + + this._hold.release(); + })); + } + } else { +- this._userVerifier.call_begin_verification(PASSWORD_SERVICE_NAME, ++ this._userVerifier.call_begin_verification(this._getForegroundService(), + this._cancellable, + Lang.bind(this, function(obj, result) { + try { + obj.call_begin_verification_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e) { + this._reportInitError('Failed to start verification', e); + return; + } + + this._hold.release(); + })); + } + }, + + _onInfo: function(client, serviceName, info) { + // 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) { + + // Translators: this message is shown below the password entry field + // to indicate the user can swipe their finger instead + this.emit('show-login-hint', _("(or swipe finger)")); +- } else if (serviceName == PASSWORD_SERVICE_NAME) { ++ } else if (this.serviceIsForeground(serviceName)) { + this._queueMessage(info, 'login-dialog-message-info'); + } + }, + + _onProblem: function(client, serviceName, problem) { +- // we don't want to show auth failed messages to +- // users who haven't enrolled their fingerprint. +- if (serviceName != PASSWORD_SERVICE_NAME) ++ if (!this.serviceIsForeground(serviceName)) + return; ++ + this._queueMessage(problem, 'login-dialog-message-warning'); + }, + + _onInfoQuery: function(client, serviceName, question) { +- // We only expect questions to come from the main auth service +- if (serviceName != PASSWORD_SERVICE_NAME) ++ if (!this.serviceIsForeground(serviceName)) + return; + + this.emit('ask-question', serviceName, question, ''); + }, + + _onSecretInfoQuery: function(client, serviceName, secretQuestion) { +- // We only expect secret requests to come from the main auth service +- if (serviceName != PASSWORD_SERVICE_NAME) ++ if (!this.serviceIsForeground(serviceName)) + return; + + this.emit('ask-question', serviceName, secretQuestion, '\u25cf'); + }, + + _onReset: function() { + // Clear previous attempts to authenticate + this._failCounter = 0; ++ this._updateDefaultService(); + + this.emit('reset'); + }, + + _onVerificationComplete: function() { + 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 + // Otherwise, we allow ALLOWED_FAILURES attempts. After that, we + // go back to the welcome screen. + + this._failCounter++; + let canRetry = retry && this._userName && + 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(); + })); + } + } else { + if (!this.hasPendingMessages) { + this._cancelAndReset(); + } else { + let signalId = this.connect('no-more-messages', + Lang.bind(this, function() { + this.disconnect(signalId); + this._cancelAndReset(); + })); + } + } + + this.emit('verification-failed'); + }, + + _onConversationStopped: function(client, serviceName) { + // if the password service fails, then cancel everything. + // But if, e.g., fingerprint fails, still give + // password authentication a chance to succeed +- if (serviceName == PASSWORD_SERVICE_NAME) { ++ if (this.serviceIsForeground(serviceName)) { + this._verificationFailed(true); + } + + this.emit('hide-login-hint'); + }, + }); + Signals.addSignalMethods(ShellUserVerifier.prototype); +-- +1.8.3.1 + + +From 76b405fad8884a4d11265eba58ade6e780dfea4c Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 29 Jul 2013 14:23:45 -0400 +Subject: [PATCH 37/66] gdmUtil: pave way for fingeprint to optionally be + default auth service + +Currently, fingerprint authentication is always a secondary thing. +If a user wants to swipe their finger when the computer is asking +for a password, so be it. + +This commit paves the way for making fingerprint auth optionally +be the main way to authenticate. Currently there's no way to enable +this, but in a future commit will honor + +enable-password-authentication=false + +in gsettings. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/util.js | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index eba8b27..f41b2b2 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -348,71 +348,71 @@ const ShellUserVerifier = new Lang.Class({ + 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(this._getForegroundService(), + this._cancellable, + Lang.bind(this, function(obj, result) { + try { + obj.call_begin_verification_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e) { + this._reportInitError('Failed to start verification', e); + return; + } + + this._hold.release(); + })); + } + }, + + _onInfo: function(client, serviceName, info) { +- // 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 && ++ if (this.serviceIsForeground(serviceName)) { ++ this._queueMessage(info, 'login-dialog-message-info'); ++ } else 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.emit('show-login-hint', _("(or swipe finger)")); +- } else if (this.serviceIsForeground(serviceName)) { +- this._queueMessage(info, 'login-dialog-message-info'); + } + }, + + _onProblem: function(client, serviceName, problem) { + if (!this.serviceIsForeground(serviceName)) + return; + + this._queueMessage(problem, 'login-dialog-message-warning'); + }, + + _onInfoQuery: function(client, serviceName, question) { + if (!this.serviceIsForeground(serviceName)) + return; + + this.emit('ask-question', serviceName, question, ''); + }, + + _onSecretInfoQuery: function(client, serviceName, secretQuestion) { + if (!this.serviceIsForeground(serviceName)) + return; + + this.emit('ask-question', serviceName, secretQuestion, '\u25cf'); + }, + + _onReset: function() { + // Clear previous attempts to authenticate + this._failCounter = 0; + this._updateDefaultService(); + + this.emit('reset'); +-- +1.8.3.1 + + +From 372d18389bd3674b8e03c052a1fc3e4119567f8f Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 29 Jul 2013 14:37:10 -0400 +Subject: [PATCH 38/66] authPrompt: emit prompted when given a message + +Some pam modules prompt without expecting the user to type +an answer back (e.g. "Please swipe finger"). We need to +emit prompted in this case too, so the the dialog will get shown. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/authPrompt.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index e987d25..3f5e4fe 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -200,60 +200,61 @@ const AuthPrompt = new Lang.Class({ + return; + } + + if (this._queryingService) + this.clear(); + + this._queryingService = serviceName; + this.setPasswordChar(passwordChar); + this.setQuestion(question); + + if (this.verifyingUser) + this.cancelButton.show(); + else + this.cancelButton.hide(); + + 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'); + }, + + _onShowMessage: function(userVerifier, message, styleClass) { + this.setMessage(message, styleClass); ++ this.emit('prompted'); + }, + + _onVerificationFailed: function() { + this.clear(); + + this.updateSensitivity(true); + this.setActorInDefaultButtonWell(null); + this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; + }, + + _onVerificationComplete: function() { + this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; + }, + + _onReset: function() { + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) { + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + this.reset(); + } + }, + + _onShowLoginHint: function(verifier, message) { + this.setHint(message); + }, + + _onHideLoginHint: function() { + this.setHint(null); + }, + + addActorToDefaultButtonWell: function(actor) { +-- +1.8.3.1 + + +From e025ebcf3344545c124962b493ddd2a28f1339b8 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Fri, 16 Aug 2013 10:29:26 -0400 +Subject: [PATCH 39/66] gdmUtil: factor out some duplicated code in + beginVerification + +The duplication makes the function look a lot more complicated +than it actually is. + +This commit moves the common code to a new _startService function. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/util.js | 73 +++++++++++++++++----------------------------------------- + 1 file changed, 21 insertions(+), 52 deletions(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index f41b2b2..acab471 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -290,115 +290,84 @@ const ShellUserVerifier = new Lang.Class({ + + this._connectSignals(); + this._beginVerification(); + this._hold.release(); + }, + + _connectSignals: function() { + this._userVerifier.connect('info', Lang.bind(this, this._onInfo)); + this._userVerifier.connect('problem', Lang.bind(this, this._onProblem)); + this._userVerifier.connect('info-query', Lang.bind(this, this._onInfoQuery)); + this._userVerifier.connect('secret-info-query', Lang.bind(this, this._onSecretInfoQuery)); + this._userVerifier.connect('conversation-stopped', Lang.bind(this, this._onConversationStopped)); + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + }, + + _getForegroundService: function() { + // For now, the foreground service is always the default service + return this._defaultService; + }, + + serviceIsForeground: function(serviceName) { + return serviceName == this._getForegroundService(); + }, + + _updateDefaultService: function() { + // For now, the default service is always the password service + this._defaultService = PASSWORD_SERVICE_NAME; + }, + +- _beginVerification: function() { ++ _startService: function(serviceName) { + this._hold.acquire(); ++ this._userVerifier.call_begin_verification_for_user(serviceName, ++ this._userName, ++ this._cancellable, ++ Lang.bind(this, function(obj, result) { ++ try { ++ obj.call_begin_verification_for_user_finish(result); ++ } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { ++ return; ++ } catch(e) { ++ this._reportInitError('Failed to start verification for user', e); ++ return; ++ } + +- if (this._userName) { +- this._userVerifier.call_begin_verification_for_user(this._getForegroundService(), +- 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(this._getForegroundService(), +- 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(); +- })); +- } ++ _beginVerification: function() { ++ this._startService(this._getForegroundService()); ++ ++ if (this._userName && this._haveFingerprintReader) ++ this._startService(FINGERPRINT_SERVICE_NAME); + }, + + _onInfo: function(client, serviceName, info) { + if (this.serviceIsForeground(serviceName)) { + this._queueMessage(info, 'login-dialog-message-info'); + } else 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.emit('show-login-hint', _("(or swipe finger)")); + } + }, + + _onProblem: function(client, serviceName, problem) { + if (!this.serviceIsForeground(serviceName)) + return; + + this._queueMessage(problem, 'login-dialog-message-warning'); + }, + + _onInfoQuery: function(client, serviceName, question) { + if (!this.serviceIsForeground(serviceName)) + return; + + this.emit('ask-question', serviceName, question, ''); + }, +-- +1.8.3.1 + + +From e4b42dbae8404c51143a3e1b7bc99b8a8143a396 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 29 Jul 2013 14:18:30 -0400 +Subject: [PATCH 40/66] gdmUtil: support disabling password authentication + +This commit skips trying password authentication if it's +disallowed, favoring fingerprint login instead. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/util.js | 16 ++++++++++++---- + 1 file changed, 12 insertions(+), 4 deletions(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index acab471..a7b8b62 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -1,53 +1,54 @@ + // -*- 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 Main = imports.ui.main; + const Params = imports.misc.params; + const ShellEntry = imports.ui.shellEntry; + const Tweener = imports.ui.tweener; + + const PASSWORD_SERVICE_NAME = 'gdm-password'; + const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; + 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 BANNER_MESSAGE_KEY = 'banner-message-enable'; + const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; + 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 + + function fadeInActor(actor) { + if (actor.opacity == 255 && actor.visible) + return null; + + let hold = new Batch.Hold(); + actor.show(); + let [minHeight, naturalHeight] = actor.get_preferred_height(-1); + + actor.opacity = 0; + actor.set_height(0); + Tweener.addTween(actor, + { opacity: 255, + height: naturalHeight, + time: FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onComplete: function() { + this.set_height(-1); + hold.release(); + }, +@@ -89,60 +90,62 @@ function cloneAndFadeOutActor(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', + + _init: function(client, params) { + params = Params.parse(params, { reauthenticationOnly: false }); + this._reauthOnly = params.reauthenticationOnly; + + this._client = client; + + this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA }); ++ this._settings.connect('changed', ++ Lang.bind(this, this._updateDefaultService)); + this._updateDefaultService(); + + this._fprintManager = new Fprint.FprintManager(); + this._messageQueue = []; + this._messageQueueTimeoutId = 0; + this.hasPendingMessages = false; + this.reauthenticating = false; + + this._failCounter = 0; + }, + + begin: function(userName, hold) { + this._cancellable = new Gio.Cancellable(); + this._hold = hold; + this._userName = userName; + this.reauthenticating = false; + + this._checkForFingerprintReader(); + + if (userName) { + // If possible, reauthenticate an already running session, + // so any session specific credentials get updated appropriately + this._client.open_reauthentication_channel(userName, this._cancellable, + Lang.bind(this, this._reauthenticationChannelOpened)); + } else { + this._client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + } + }, + + cancel: function() { +@@ -210,67 +213,70 @@ const ShellUserVerifier = new Lang.Class({ + + this._messageQueueTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, + message.interval, + Lang.bind(this, function() { + this._messageQueueTimeoutId = 0; + this._queueMessageTimeout(); + })); + }, + + _queueMessage: function(message, iconName) { + let interval = this._getIntervalForMessage(message); + + this.hasPendingMessages = true; + this._messageQueue.push({ text: message, interval: interval, iconName: iconName }); + this._queueMessageTimeout(); + }, + + _clearMessageQueue: function() { + this.finishMessageQueue(); + + if (this._messageQueueTimeoutId != 0) { + GLib.source_remove(this._messageQueueTimeoutId); + this._messageQueueTimeoutId = 0; + } + this.emit('show-message', null, null); + }, + + _checkForFingerprintReader: function() { + this._haveFingerprintReader = false; + +- if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) ++ if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) { ++ this._updateDefaultService(); + return; ++ } + + this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this, + function(device, error) { + if (!error && device) + this._haveFingerprintReader = true; ++ this._updateDefaultService(); + })); + }, + + _reportInitError: function(where, error) { + logError(error, where); + this._hold.release(); + + this._queueMessage(_("Authentication error"), 'login-dialog-message-warning'); + this._verificationFailed(false); + }, + + _reauthenticationChannelOpened: function(client, result) { + try { + this._userVerifier = client.open_reauthentication_channel_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e if e.matches(Gio.DBusError, Gio.DBusError.ACCESS_DENIED) && + !this._reauthOnly) { + // Gdm emits org.freedesktop.DBus.Error.AccessDenied when there is + // no session to reauthenticate. Fall back to performing verification + // from this login session + client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + return; + } catch(e) { + this._reportInitError('Failed to open reauthentication channel', e); + return; + } + + this.reauthenticating = true; + this._connectSignals(); +@@ -286,87 +292,89 @@ const ShellUserVerifier = new Lang.Class({ + } catch(e) { + this._reportInitError('Failed to obtain user verifier', e); + return; + } + + this._connectSignals(); + this._beginVerification(); + this._hold.release(); + }, + + _connectSignals: function() { + this._userVerifier.connect('info', Lang.bind(this, this._onInfo)); + this._userVerifier.connect('problem', Lang.bind(this, this._onProblem)); + this._userVerifier.connect('info-query', Lang.bind(this, this._onInfoQuery)); + this._userVerifier.connect('secret-info-query', Lang.bind(this, this._onSecretInfoQuery)); + this._userVerifier.connect('conversation-stopped', Lang.bind(this, this._onConversationStopped)); + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + }, + + _getForegroundService: function() { + // For now, the foreground service is always the default service + return this._defaultService; + }, + + serviceIsForeground: function(serviceName) { + return serviceName == this._getForegroundService(); + }, + + _updateDefaultService: function() { +- // For now, the default service is always the password service +- this._defaultService = PASSWORD_SERVICE_NAME; ++ if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) ++ this._defaultService = PASSWORD_SERVICE_NAME; ++ else if (this._haveFingerprintReader) ++ this._defaultService = FINGERPRINT_SERVICE_NAME; + }, + + _startService: function(serviceName) { + this._hold.acquire(); + this._userVerifier.call_begin_verification_for_user(serviceName, + this._userName, + this._cancellable, + Lang.bind(this, function(obj, result) { + try { + obj.call_begin_verification_for_user_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e) { + this._reportInitError('Failed to start verification for user', e); + return; + } + + this._hold.release(); + })); + }, + + _beginVerification: function() { + this._startService(this._getForegroundService()); + +- if (this._userName && this._haveFingerprintReader) ++ 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, 'login-dialog-message-info'); + } else 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.emit('show-login-hint', _("(or swipe finger)")); + } + }, + + _onProblem: function(client, serviceName, problem) { + if (!this.serviceIsForeground(serviceName)) + return; + + this._queueMessage(problem, 'login-dialog-message-warning'); + }, + + _onInfoQuery: function(client, serviceName, question) { + if (!this.serviceIsForeground(serviceName)) + return; + + this.emit('ask-question', serviceName, question, ''); +-- +1.8.3.1 + + +From c40b68571f8cf8928bf855474becfe80b6f9eb83 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 30 May 2013 10:15:09 -0400 +Subject: [PATCH 41/66] misc: add objectManager class + +The D-Bus ObjectManager interface is fairly recent addition to the +D-Bus specification. Its purpose is to provide a standardized way +to track objects dynamically coming and going for a service, and +to track capabilities dynamically coming and going for those objects +(by means of interfaces). + +This commit adds the requisite code needed to make use of the +ObjectManager interface. + +It will ultimately be needed to implement smartcard support in the +login screen. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/Makefile.am | 1 + + js/misc/objectManager.js | 254 +++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 255 insertions(+) + create mode 100644 js/misc/objectManager.js + +diff --git a/js/Makefile.am b/js/Makefile.am +index 57f08e2..8740678 100644 +--- a/js/Makefile.am ++++ b/js/Makefile.am +@@ -7,60 +7,61 @@ 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|[@]GETTEXT_PACKAGE@|$(GETTEXT_PACKAGE)|g" \ + -e "s|[@]datadir@|$(datadir)|g" \ + -e "s|[@]libexecdir@|$(libexecdir)|g" \ + -e "s|[@]sysconfdir@|$(sysconfdir)|g" \ + $< > $@ + + jsdir = $(pkgdatadir)/js + + nobase_dist_js_DATA = \ + gdm/authPrompt.js \ + gdm/batch.js \ + gdm/fingerprint.js \ + gdm/loginDialog.js \ + gdm/powerMenu.js \ + gdm/realmd.js \ + gdm/util.js \ + extensionPrefs/main.js \ + misc/config.js \ + misc/extensionUtils.js \ + misc/fileUtils.js \ + misc/gnomeSession.js \ + misc/hash.js \ + misc/history.js \ + misc/jsParse.js \ + misc/loginManager.js \ + misc/modemManager.js \ ++ misc/objectManager.js \ + misc/params.js \ + misc/util.js \ + perf/core.js \ + ui/altTab.js \ + ui/appDisplay.js \ + ui/appFavorites.js \ + ui/backgroundMenu.js \ + ui/background.js \ + ui/boxpointer.js \ + ui/calendar.js \ + ui/checkBox.js \ + ui/ctrlAltTab.js \ + ui/dash.js \ + ui/dateMenu.js \ + ui/dnd.js \ + ui/endSessionDialog.js \ + ui/extensionSystem.js \ + ui/extensionDownloader.js \ + ui/environment.js \ + ui/ibusCandidatePopup.js\ + ui/grabHelper.js \ + ui/iconGrid.js \ + ui/keyboard.js \ + ui/layout.js \ + ui/lightbox.js \ + ui/lookingGlass.js \ + ui/magnifier.js \ + ui/magnifierDBus.js \ + ui/main.js \ + ui/messageTray.js \ +diff --git a/js/misc/objectManager.js b/js/misc/objectManager.js +new file mode 100644 +index 0000000..b954f1b +--- /dev/null ++++ b/js/misc/objectManager.js +@@ -0,0 +1,254 @@ ++// -*- 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 = ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface); ++ ++const ObjectManager = new Lang.Class({ ++ Name: 'ObjectManager', ++ _init: function(params) { ++ params = Params.parse(params, { connection: null, ++ name: null, ++ objectPath: null, ++ knownInterfaces: null, ++ cancellable: null, ++ onLoaded: null }); ++ ++ this._connection = params.connection; ++ this._serviceName = params.name; ++ this._managerPath = params.objectPath; ++ this._cancellable = params.cancellable; ++ ++ this._managerProxy = new Gio.DBusProxy({ g_connection: this._connection, ++ g_interface_name: ObjectManagerInfo.name, ++ g_interface_info: ObjectManagerInfo, ++ g_name: this._serviceName, ++ g_object_path: this._managerPath, ++ g_flags: Gio.DBusProxyFlags.NONE }); ++ ++ this._interfaceInfos = {}; ++ this._objects = {}; ++ this._interfaces = {}; ++ this._onLoaded = params.onLoaded; ++ ++ if (params.knownInterfaces) ++ this._registerInterfaces(params.knownInterfaces); ++ ++ // Start out inhibiting load until at least the proxy ++ // manager is loaded and the remote objects are fetched ++ this._numLoadInhibitors = 1; ++ this._managerProxy.init_async(GLib.PRIORITY_DEFAULT, ++ this._cancellable, ++ Lang.bind(this, this._onManagerProxyLoaded)); ++ }, ++ ++ _tryToCompleteLoad: function() { ++ this._numLoadInhibitors--; ++ if (this._numLoadInhibitors == 0) { ++ if (this._onLoaded) ++ this._onLoaded(); ++ } ++ }, ++ ++ _addInterface: function(objectPath, interfaceName, onFinished) { ++ let info = this._interfaceInfos[interfaceName]; ++ ++ if (!info) ++ return; ++ ++ let proxy = new Gio.DBusProxy({ g_connection: this._connection, ++ g_name: this._serviceName, ++ g_object_path: objectPath, ++ g_interface_name: interfaceName, ++ g_interface_info: info, ++ g_flags: Gio.DBusProxyFlags.NONE }); ++ ++ proxy.init_async(GLib.PRIORITY_DEFAULT, ++ this._cancellable, ++ Lang.bind(this, function(initable, result) { ++ let error = null; ++ try { ++ initable.init_finish(result); ++ } catch(e) { ++ logError(e, 'could not initialize proxy for interface ' + interfaceName); ++ ++ if (onFinished) ++ onFinished(); ++ return; ++ } ++ ++ let isNewObject; ++ if (!this._objects[objectPath]) { ++ this._objects[objectPath] = {}; ++ isNewObject = true; ++ } else { ++ isNewObject = false; ++ } ++ ++ this._objects[objectPath][interfaceName] = proxy; ++ ++ if (!this._interfaces[interfaceName]) ++ this._interfaces[interfaceName] = []; ++ ++ this._interfaces[interfaceName].push(proxy); ++ ++ if (isNewObject) ++ this.emit('object-added', objectPath); ++ ++ this.emit('interface-added', interfaceName, proxy); ++ ++ if (onFinished) ++ onFinished(); ++ })); ++ }, ++ ++ _removeInterface: function(objectPath, interfaceName) { ++ if (!this._objects[objectPath]) ++ return; ++ ++ let proxy = this._objects[objectPath][interfaceName]; ++ ++ if (this._interfaces[interfaceName]) { ++ let index = this._interfaces[interfaceName].indexOf(proxy); ++ ++ if (index >= 0) ++ this._interfaces[interfaceName].splice(index, 1); ++ ++ if (this._interfaces[interfaceName].length == 0) ++ delete this._interfaces[interfaceName]; ++ } ++ ++ this.emit('interface-removed', interfaceName, proxy); ++ ++ this._objects[objectPath][interfaceName] = null; ++ ++ if (Object.keys(this._objects[objectPath]).length == 0) { ++ delete this._objects[objectPath]; ++ this.emit('object-removed', objectPath); ++ } ++ }, ++ ++ _onManagerProxyLoaded: function(initable, result) { ++ let error = null; ++ try { ++ initable.init_finish(result); ++ } catch(e) { ++ logError(e, 'could not initialize object manager for object ' + params.name); ++ ++ this._tryToCompleteLoad(); ++ return; ++ } ++ ++ this._managerProxy.connectSignal('InterfacesAdded', ++ Lang.bind(this, function(objectManager, sender, [objectPath, interfaces]) { ++ let interfaceNames = Object.keys(interfaces); ++ for (let i = 0; i < interfaceNames.length; i++) ++ this._addInterface(objectPath, interfaceNames[i]); ++ })); ++ this._managerProxy.connectSignal('InterfacesRemoved', ++ Lang.bind(this, function(objectManager, sender, [objectPath, interfaceNames]) { ++ for (let i = 0; i < interfaceNames.length; i++) ++ this._removeInterface(objectPath, interfaceNames[i]); ++ })); ++ ++ if (Object.keys(this._interfaceInfos).length == 0) { ++ this._tryToCompleteLoad(); ++ return; ++ } ++ ++ this._managerProxy.GetManagedObjectsRemote(Lang.bind(this, function(result, error) { ++ if (!result) { ++ if (error) { ++ logError(error, 'could not get remote objects for service ' + this._serviceName + ' path ' + this._managerPath); ++ } ++ ++ this._tryToCompleteLoad(); ++ return; ++ } ++ ++ let [objects] = result; ++ ++ let objectPaths = Object.keys(objects); ++ for (let i = 0; i < objectPaths.length; i++) { ++ let objectPath = objectPaths[i]; ++ let object = objects[objectPath]; ++ ++ let interfaceNames = Object.getOwnPropertyNames(object); ++ for (let j = 0; j < interfaceNames.length; j++) { ++ let interfaceName = interfaceNames[j]; ++ ++ // Prevent load from completing until the interface is loaded ++ this._numLoadInhibitors++; ++ this._addInterface(objectPath, ++ interfaceName, ++ Lang.bind(this, this._tryToCompleteLoad)); ++ } ++ } ++ this._tryToCompleteLoad(); ++ })); ++ }, ++ ++ _registerInterfaces: function(interfaces) { ++ for (let i = 0; i < interfaces.length; i++) { ++ let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]); ++ this._interfaceInfos[info.name] = info; ++ } ++ }, ++ ++ getProxy: function(objectPath, interfaceName) { ++ let object = this._objects[objectPath]; ++ ++ if (!object) ++ return null; ++ ++ return object[interfaceName]; ++ }, ++ ++ getProxiesForInterface: function(interfaceName) { ++ let proxyList = this._interfaces[interfaceName]; ++ ++ if (!proxyList) ++ return []; ++ ++ return proxyList; ++ }, ++ ++ getAllProxies: function() { ++ let proxies = []; ++ ++ let objectPaths = Object.keys(this._objects); ++ for (let i = 0; i < objectPaths.length; i++) { ++ let object = this._objects[objectPaths]; ++ ++ let interfaceNames = Object.keys(object); ++ for (let j = 0; i < interfaceNames.length; i++) { ++ let interfaceName = interfaceNames[i]; ++ if (object[interfaceName]) ++ proxies.push(object(interfaceName)); ++ } ++ } ++ ++ return proxies; ++ } ++}); ++Signals.addSignalMethods(ObjectManager.prototype); +-- +1.8.3.1 + + +From 183614d09aacbd6e62ae27dbb28a903f9abdd99c Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 30 May 2013 10:18:51 -0400 +Subject: [PATCH 42/66] misc: add code to use settings-daemon smartcard service + +gnome-settings-daemon monitors smartcard insertion and removal +events on the system and then exports a model of the current +smartcard topology over the bus using the D-Bus ObjectManager interface. + +This commit adds the support code needed in gnome-shell to talk to +the gnome-settings-daemon service. + +A future commit will use this code to inform the login screen +when a user inserts a smartcard (so it can react appropriately) + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/Makefile.am | 1 + + js/misc/smartcardManager.js | 117 ++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 118 insertions(+) + create mode 100644 js/misc/smartcardManager.js + +diff --git a/js/Makefile.am b/js/Makefile.am +index 8740678..425b6fb 100644 +--- a/js/Makefile.am ++++ b/js/Makefile.am +@@ -9,60 +9,61 @@ misc/config.js: misc/config.js.in Makefile + -e "s|[@]PACKAGE_VERSION@|$(PACKAGE_VERSION)|g" \ + -e "s|[@]HAVE_BLUETOOTH@|$(HAVE_BLUETOOTH)|g" \ + -e "s|[@]GETTEXT_PACKAGE@|$(GETTEXT_PACKAGE)|g" \ + -e "s|[@]datadir@|$(datadir)|g" \ + -e "s|[@]libexecdir@|$(libexecdir)|g" \ + -e "s|[@]sysconfdir@|$(sysconfdir)|g" \ + $< > $@ + + jsdir = $(pkgdatadir)/js + + nobase_dist_js_DATA = \ + gdm/authPrompt.js \ + gdm/batch.js \ + gdm/fingerprint.js \ + gdm/loginDialog.js \ + gdm/powerMenu.js \ + gdm/realmd.js \ + gdm/util.js \ + extensionPrefs/main.js \ + misc/config.js \ + misc/extensionUtils.js \ + misc/fileUtils.js \ + misc/gnomeSession.js \ + misc/hash.js \ + misc/history.js \ + misc/jsParse.js \ + misc/loginManager.js \ + misc/modemManager.js \ + misc/objectManager.js \ + misc/params.js \ ++ misc/smartcardManager.js \ + misc/util.js \ + perf/core.js \ + ui/altTab.js \ + ui/appDisplay.js \ + ui/appFavorites.js \ + ui/backgroundMenu.js \ + ui/background.js \ + ui/boxpointer.js \ + ui/calendar.js \ + ui/checkBox.js \ + ui/ctrlAltTab.js \ + ui/dash.js \ + ui/dateMenu.js \ + ui/dnd.js \ + ui/endSessionDialog.js \ + ui/extensionSystem.js \ + ui/extensionDownloader.js \ + ui/environment.js \ + ui/ibusCandidatePopup.js\ + ui/grabHelper.js \ + ui/iconGrid.js \ + ui/keyboard.js \ + ui/layout.js \ + ui/lightbox.js \ + ui/lookingGlass.js \ + ui/magnifier.js \ + ui/magnifierDBus.js \ + ui/main.js \ + ui/messageTray.js \ + ui/modalDialog.js \ +diff --git a/js/misc/smartcardManager.js b/js/misc/smartcardManager.js +new file mode 100644 +index 0000000..644b033 +--- /dev/null ++++ b/js/misc/smartcardManager.js +@@ -0,0 +1,117 @@ ++// -*- 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 = ++ ++ ++ ++ ++; ++ ++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); +-- +1.8.3.1 + + +From 106431c7f61dad0e2cc8bdbff2d2a3a31f94e670 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 27 Jun 2013 08:54:19 -0400 +Subject: [PATCH 43/66] authPrompt: support smartcard authentication + +This commit detects when a user inserts a smartcard, +and then initiates user verification using the gdm-smartcard +PAM service. + +Likewise, if a user removes their smartcard, password verification +(or the user list depending on auth mode and configuration) are initiated + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/authPrompt.js | 39 ++++++++++++++++++++++++++++++++++++++- + js/gdm/util.js | 48 +++++++++++++++++++++++++++++++++++++++++++++++- + js/ui/screenShield.js | 8 ++++++++ + 3 files changed, 93 insertions(+), 2 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 3f5e4fe..cfdf052 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -33,60 +33,62 @@ const AuthPromptStatus = { + + 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('show-login-hint', Lang.bind(this, this._onShowLoginHint)); + this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._onHideLoginHint)); ++ this._userVerifier.connect('smartcard-status-changed', Lang.bind(this, this._onSmartcardStatusChanged)); ++ this.smartcardDetected = this._userVerifier.smartcardDetected; + + this.connect('next', Lang.bind(this, function() { + this.updateSensitivity(false); + this.startSpinning(); + if (this._queryingService) { + this._userVerifier.answerQuery(this._queryingService, this._entry.text); + } else { + this._preemptiveAnswer = this._entry.text; + } + })); + + this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', + vertical: true }); + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + this.actor.connect('key-press-event', + Lang.bind(this, function(actor, event) { + if (event.get_key_symbol() == Clutter.KEY_Escape) { + this.cancel(); + } + })); + + this._userWell = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this.actor.add(this._userWell, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); + +@@ -198,60 +200,79 @@ const AuthPrompt = new Lang.Class({ + 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 (this.verifyingUser) + this.cancelButton.show(); + else + this.cancelButton.hide(); + + if (passwordChar) { + if (this._userVerifier.reauthenticating) + this.nextButton.label = _("Unlock"); + else + this.nextButton.label = C_("button", "Sign In"); + } else { + this.nextButton.label = _("Next"); + } + + this.updateSensitivity(true); + this.emit('prompted'); + }, + ++ _onSmartcardStatusChanged: function() { ++ this.smartcardDetected = this._userVerifier.smartcardDetected; ++ ++ // Most of the time we want to reset if the user inserts or removes ++ // a smartcard. Smartcard insertion "preempts" what the user was ++ // doing, and smartcard removal aborts the preemption. ++ // The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying ++ // with a smartcard ++ // 2) Don't reset if we've already succeeded at verification and ++ // the user is getting logged in. ++ if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) && ++ this.verificationStatus == AuthPromptStatus.VERIFYING && ++ this.smartcardDetected) ++ return; ++ ++ if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) ++ this.reset(); ++ }, ++ + _onShowMessage: function(userVerifier, message, styleClass) { + this.setMessage(message, styleClass); + this.emit('prompted'); + }, + + _onVerificationFailed: function() { + this.clear(); + + this.updateSensitivity(true); + this.setActorInDefaultButtonWell(null); + this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; + }, + + _onVerificationComplete: function() { + this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; + }, + + _onReset: function() { + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) { + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + this.reset(); + } + }, + + _onShowLoginHint: function(verifier, message) { + this.setHint(message); + }, + + _onHideLoginHint: function() { + this.setHint(null); +@@ -407,61 +428,77 @@ const AuthPrompt = new Lang.Class({ + } + }, + + setHint: function(message) { + if (message) { + this._loginHint.set_text(message) + this._loginHint.opacity = 255; + } else { + this._loginHint.opacity = 0; + this._loginHint.set_text(''); + } + }, + + reset: function() { + let oldStatus = this.verificationStatus; + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + + if (oldStatus == AuthPromptStatus.VERIFYING) + this._userVerifier.cancel(); + + this._queryingService = null; + this.clear(); + this._message.opacity = 0; + this.setUser(null); + this.stopSpinning(); + this.setHint(null); + + if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED) + this.emit('failed'); + +- this.emit('reset', BeginRequestType.PROVIDE_USERNAME); ++ let beginRequestType; ++ ++ if (this._mode == AuthPromptMode.UNLOCK_ONLY) { ++ // The user is constant at the unlock screen, so it will immediately ++ // respond to the request with the username ++ beginRequestType = BeginRequestType.PROVIDE_USERNAME; ++ } else if (this.smartcardDetected && ++ this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME)) { ++ // We don't need to know the username if the user preempted the login screen ++ // with a smartcard. ++ beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME; ++ } else { ++ // In all other cases, we should get the username up front. ++ beginRequestType = BeginRequestType.PROVIDE_USERNAME; ++ } ++ ++ this.emit('reset', beginRequestType); + }, + + addCharacter: function(unichar) { + if (!this._entry.visible) + return; + + this._entry.grab_key_focus(); + this._entry.clutter_text.insert_unichar(unichar); + }, + + begin: function(params) { + params = Params.parse(params, { userName: null, + hold: null }); + + this.updateSensitivity(false); + + let hold = params.hold; + if (!hold) + hold = new Batch.Hold(); + + this._userVerifier.begin(params.userName, hold); + this.verificationStatus = AuthPromptStatus.VERIFYING; + }, + + finish: function(onComplete) { + if (!this._userVerifier.hasPendingMessages) { + onComplete(); + return; + } + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index a7b8b62..7ed5097 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -1,55 +1,58 @@ + // -*- 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 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 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'; + + 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 + + function fadeInActor(actor) { + if (actor.opacity == 255 && actor.visible) + return null; + + let hold = new Batch.Hold(); + actor.show(); + let [minHeight, naturalHeight] = actor.get_preferred_height(-1); + + actor.opacity = 0; + actor.set_height(0); + Tweener.addTween(actor, + { opacity: 255, + height: naturalHeight, + time: FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onComplete: function() { + this.set_height(-1); + hold.release(); + }, + }); +@@ -95,60 +98,73 @@ function cloneAndFadeOutActor(actor) { + 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', + + _init: function(client, params) { + params = Params.parse(params, { reauthenticationOnly: false }); + this._reauthOnly = params.reauthenticationOnly; + + this._client = client; + + this._settings = new Gio.Settings({ schema: LOGIN_SCREEN_SCHEMA }); + this._settings.connect('changed', + Lang.bind(this, this._updateDefaultService)); + this._updateDefaultService(); + + this._fprintManager = new Fprint.FprintManager(); ++ this._smartcardManager = SmartcardManager.getSmartcardManager(); ++ ++ // We check for smartcards right away, since an inserted smartcard ++ // at startup should result in immediately initiating authentication. ++ // This is different than fingeprint readers, where we only check them ++ // after a user has been picked. ++ this._checkForSmartcard(); ++ ++ this._smartcardManager.connect('smartcard-inserted', ++ Lang.bind(this, this._checkForSmartcard)); ++ this._smartcardManager.connect('smartcard-removed', ++ Lang.bind(this, this._checkForSmartcard)); ++ + this._messageQueue = []; + this._messageQueueTimeoutId = 0; + this.hasPendingMessages = false; + this.reauthenticating = false; + + this._failCounter = 0; + }, + + begin: function(userName, hold) { + this._cancellable = new Gio.Cancellable(); + this._hold = hold; + this._userName = userName; + this.reauthenticating = false; + + this._checkForFingerprintReader(); + + if (userName) { + // If possible, reauthenticate an already running session, + // so any session specific credentials get updated appropriately + this._client.open_reauthentication_channel(userName, this._cancellable, + Lang.bind(this, this._reauthenticationChannelOpened)); + } else { + this._client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + } + }, + + cancel: function() { + if (this._cancellable) + this._cancellable.cancel(); + +@@ -226,128 +242,158 @@ const ShellUserVerifier = new Lang.Class({ + this._messageQueue.push({ text: message, interval: interval, iconName: iconName }); + this._queueMessageTimeout(); + }, + + _clearMessageQueue: function() { + this.finishMessageQueue(); + + if (this._messageQueueTimeoutId != 0) { + GLib.source_remove(this._messageQueueTimeoutId); + this._messageQueueTimeoutId = 0; + } + this.emit('show-message', null, null); + }, + + _checkForFingerprintReader: function() { + this._haveFingerprintReader = false; + + if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) { + this._updateDefaultService(); + return; + } + + this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this, + function(device, error) { + if (!error && device) + this._haveFingerprintReader = true; + this._updateDefaultService(); + })); + }, + ++ _checkForSmartcard: function() { ++ let smartcardDetected; ++ ++ if (!this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) ++ smartcardDetected = false; ++ else if (this.reauthenticating) ++ smartcardDetected = this._smartcardManager.hasInsertedLoginToken(); ++ else ++ smartcardDetected = this._smartcardManager.hasInsertedTokens(); ++ ++ if (smartcardDetected != this.smartcardDetected) { ++ this.smartcardDetected = smartcardDetected; ++ ++ if (this.smartcardDetected) ++ this._preemptingService = SMARTCARD_SERVICE_NAME; ++ else if (this._preemptingService == SMARTCARD_SERVICE_NAME) ++ this._preemptingService = null; ++ ++ this.emit('smartcard-status-changed'); ++ } ++ }, ++ + _reportInitError: function(where, error) { + logError(error, where); + this._hold.release(); + + this._queueMessage(_("Authentication error"), 'login-dialog-message-warning'); + this._verificationFailed(false); + }, + + _reauthenticationChannelOpened: function(client, result) { + try { + this._userVerifier = client.open_reauthentication_channel_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e if e.matches(Gio.DBusError, Gio.DBusError.ACCESS_DENIED) && + !this._reauthOnly) { + // Gdm emits org.freedesktop.DBus.Error.AccessDenied when there is + // no session to reauthenticate. Fall back to performing verification + // from this login session + client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + return; + } catch(e) { + this._reportInitError('Failed to open reauthentication channel', e); + return; + } + + this.reauthenticating = true; + this._connectSignals(); + this._beginVerification(); + this._hold.release(); + }, + + _userVerifierGot: function(client, result) { + try { + this._userVerifier = client.get_user_verifier_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e) { + this._reportInitError('Failed to obtain user verifier', e); + return; + } + + this._connectSignals(); + this._beginVerification(); + this._hold.release(); + }, + + _connectSignals: function() { + this._userVerifier.connect('info', Lang.bind(this, this._onInfo)); + this._userVerifier.connect('problem', Lang.bind(this, this._onProblem)); + this._userVerifier.connect('info-query', Lang.bind(this, this._onInfoQuery)); + this._userVerifier.connect('secret-info-query', Lang.bind(this, this._onSecretInfoQuery)); + this._userVerifier.connect('conversation-stopped', Lang.bind(this, this._onConversationStopped)); + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + }, + + _getForegroundService: function() { +- // For now, the foreground service is always the default service ++ if (this._preemptingService) ++ return this._preemptingService; ++ + return this._defaultService; + }, + + serviceIsForeground: function(serviceName) { + return serviceName == this._getForegroundService(); + }, + ++ serviceIsDefault: function(serviceName) { ++ return serviceName == this._defaultService; ++ }, ++ + _updateDefaultService: function() { + if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) + this._defaultService = PASSWORD_SERVICE_NAME; ++ else if (this.smartcardDetected) ++ this._defaultService = SMARTCARD_SERVICE_NAME; + else if (this._haveFingerprintReader) + this._defaultService = FINGERPRINT_SERVICE_NAME; + }, + + _startService: function(serviceName) { + this._hold.acquire(); + this._userVerifier.call_begin_verification_for_user(serviceName, + this._userName, + this._cancellable, + Lang.bind(this, function(obj, result) { + try { + obj.call_begin_verification_for_user_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e) { + this._reportInitError('Failed to start verification for user', e); + return; + } + + this._hold.release(); + })); + }, + + _beginVerification: function() { + this._startService(this._getForegroundService()); + + if (this._userName && this._haveFingerprintReader && !this.serviceIsForeground(FINGERPRINT_SERVICE_NAME)) + this._startService(FINGERPRINT_SERVICE_NAME); + }, + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index d9fe883..2c812f5 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -1,55 +1,56 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + + const Cairo = imports.cairo; + const Clutter = imports.gi.Clutter; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const GnomeDesktop = imports.gi.GnomeDesktop; + const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; + const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + const TweenerEquations = imports.tweener.equations; + + const Background = imports.ui.background; + const GnomeSession = imports.misc.gnomeSession; + const Hash = imports.misc.hash; + const Layout = imports.ui.layout; + const LoginManager = imports.misc.loginManager; + const Lightbox = imports.ui.lightbox; + const Main = imports.ui.main; + const Overview = imports.ui.overview; + const MessageTray = imports.ui.messageTray; + const ShellDBus = imports.ui.shellDBus; ++const SmartcardManager = imports.misc.smartcardManager; + const Tweener = imports.ui.tweener; + const Util = imports.misc.util; + + const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver'; + const LOCK_ENABLED_KEY = 'lock-enabled'; + const LOCK_DELAY_KEY = 'lock-delay'; + + // fraction of screen height the arrow must reach before completing + // the slide up automatically + const ARROW_DRAG_THRESHOLD = 0.1; + + // Parameters for the arrow animation + const N_ARROWS = 3; + const ARROW_ANIMATION_TIME = 0.6; + const ARROW_ANIMATION_PEAK_OPACITY = 0.4; + const ARROW_IDLE_TIME = 30000; // ms + + const SUMMARY_ICON_SIZE = 48; + + // ScreenShield animation time + // - STANDARD_FADE_TIME is used when the session goes idle + // - MANUAL_FADE_TIME is used for lowering the shield when asked by the user, + // or when cancelling the dialog + // - BACKGROUND_FADE_TIME is used when the background changes to crossfade to new background + // - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking + // - INITIAL_FADE_IN_TIME is used for the initial fade in at startup + const STANDARD_FADE_TIME = 10; + const MANUAL_FADE_TIME = 0.3; + const BACKGROUND_FADE_TIME = 1.0; + const CURTAIN_SLIDE_TIME = 0.3; +@@ -478,60 +479,67 @@ const ScreenShield = new Lang.Class({ + + this._lockDialogGroup = new St.Widget({ x_expand: true, + y_expand: true, + opacity: 0, + pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }), + name: 'lockDialogGroup' }); + + Tweener.addTween(this._lockDialogGroup, + { opacity: 255, + time: INITIAL_FADE_IN_TIME, + transition: 'easeInQuad', + }); + + this.actor.add_actor(this._lockDialogGroup); + this.actor.add_actor(this._lockScreenGroup); + + this._presence = new GnomeSession.Presence(Lang.bind(this, function(proxy, error) { + if (error) { + logError(error, 'Error while reading gnome-session presence'); + return; + } + + this._onStatusChanged(proxy.status); + })); + this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) { + this._onStatusChanged(status); + })); + + this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this); + ++ this._smartcardManager = SmartcardManager.getSmartcardManager(); ++ this._smartcardManager.connect('smartcard-inserted', ++ Lang.bind(this, function(token) { ++ if (this._isLocked && token.UsedToLogin) ++ this._liftShield(true, 0); ++ })); ++ + this._inhibitor = null; + this._aboutToSuspend = false; + this._loginManager = LoginManager.getLoginManager(); + this._loginManager.connect('prepare-for-sleep', + Lang.bind(this, this._prepareForSleep)); + this._inhibitSuspend(); + + this._loginManager.getCurrentSessionProxy(Lang.bind(this, + function(sessionProxy) { + this._loginSession = sessionProxy; + this._loginSession.connectSignal('Lock', Lang.bind(this, function() { this.lock(false); })); + this._loginSession.connectSignal('Unlock', Lang.bind(this, function() { this.deactivate(false); })); + })); + + this._settings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA }); + + this._isModal = false; + this._hasLockScreen = false; + this._isGreeter = false; + this._isActive = false; + this._isLocked = false; + this._inUnlockAnimation = false; + this._activationTime = 0; + this._becameActiveId = 0; + this._lockTimeoutId = 0; + + this._lightbox = new Lightbox.Lightbox(Main.uiGroup, + { inhibitEvents: true, + fadeInTime: STANDARD_FADE_TIME, + fadeFactor: 1 }); +-- +1.8.3.1 + + +From f63abd535aee82424bb98395b2cfbd22cac310d6 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Sun, 28 Jul 2013 20:55:12 -0400 +Subject: [PATCH 44/66] loginDialog: fix up cancel button visibility + +This commit makes sure we hide when there's nothing to cancel +to and show it when there's something to cancel to. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/loginDialog.js | 21 +++++++++++++++++---- + 1 file changed, 17 insertions(+), 4 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index b40aa85..643c7d6 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -513,98 +513,108 @@ const LoginDialog = new Lang.Class({ + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + + }, + + _updateDisableUserList: function() { + let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY); + + if (disableUserList != this._disableUserList) { + this._disableUserList = disableUserList; + + if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING) + this._authPrompt.reset(); + } + }, + ++ _updateCancelButton: function() { ++ let cancelVisible; ++ ++ // Hide the cancel button if the user list is disabled and we're asking for ++ // a username ++ if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING && this._disableUserList) ++ cancelVisible = false; ++ else ++ cancelVisible = true; ++ ++ this._authPrompt.cancelButton.visible = cancelVisible; ++ }, ++ + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); +- +- this._authPrompt.cancelButton.show(); +- + this._showPrompt(); + }, + + _onReset: function(authPrompt, beginRequest) { + this._sessionMenuButton.updateSensitivity(true); + + this._user = null; + + if (this._disableUserList) { + this._authPrompt.cancelButton.hide(); + this._hideUserListAndLogIn(); + } else { + this._showUserList(); + } + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { + if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING) + return false; + + if (this._user && this._user.is_logged_in()) + return false; + + return true; + }, + +@@ -622,65 +632,66 @@ const LoginDialog = new Lang.Class({ + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + 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._authPrompt.setHint(_("(e.g., user or %s)").format(hint)); + }, + + _askForUsernameAndBeginVerification: function() { + this._authPrompt.setPasswordChar(''); + this._authPrompt.setQuestion(_("Username: ")); + + let realmManager = new Realmd.Manager(); + let realmSignalId = realmManager.connect('login-format-changed', + Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + + let nextSignalId = this._authPrompt.connect('next', + Lang.bind(this, function() { + this._authPrompt.disconnect(nextSignalId); + this._authPrompt.updateSensitivity(false); + let answer = this._authPrompt.getAnswer(); + this._authPrompt.clear(); + this._authPrompt.startSpinning(); + this._authPrompt.begin({ userName: answer }); ++ this._updateCancelButton(); + + realmManager.disconnect(realmSignalId) + realmManager.release(); + })); +- this._authPrompt.cancelButton.hide(); ++ this._updateCancelButton(); + this._showPrompt(); + }, + + _startSession: function(serviceName) { + Tweener.addTween(this.actor, + { opacity: 0, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onUpdate: function() { + let children = Main.layoutManager.uiGroup.get_children(); + + for (let i = 0; i < children.length; i++) { + if (children[i] != Main.layoutManager.screenShieldGroup) + children[i].opacity = this.actor.opacity; + } + }, + onUpdateScope: this, + onComplete: function() { + Mainloop.idle_add(Lang.bind(this, function() { + this._greeter.call_start_session_when_ready_sync(serviceName, true, null); + return false; + })); + }, + onCompleteScope: this }); + }, + + _onSessionOpened: function(client, serviceName) { + this._authPrompt.finish(Lang.bind(this, function() { + this._startSession(serviceName); + })); +@@ -833,60 +844,62 @@ const LoginDialog = new Lang.Class({ + + _showUserList: function() { + this._authPrompt.hide(); + this._sessionMenuButton.close(); + this._setUserListExpanded(true); + this._notListedButton.show(); + this._userList.actor.grab_key_focus(); + }, + + _beginVerificationForItem: function(item) { + this._authPrompt.setUser(item.user); + + let userName = item.user.get_user_name(); + let hold = new Batch.Hold(); + + this._authPrompt.begin({ userName: userName, + hold: hold }); + return hold; + }, + + _onUserListActivated: function(activatedItem) { + let tasks = [function() { + return GdmUtil.cloneAndFadeOutActor(this._userSelectionBox); + }, + function() { + this._setUserListExpanded(false); + }]; + + this._user = activatedItem.user; + ++ this._updateCancelButton(); ++ + let batch = new Batch.ConcurrentBatch(this, [new Batch.ConsecutiveBatch(this, tasks), + this._beginVerificationForItem(activatedItem)]); + batch.run(); + }, + + _onDestroy: function() { + if (this._userManagerLoadedId) { + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + }, + + _loadUserList: function() { + let users = this._userManager.list_users(); + + for (let i = 0; i < users.length; i++) { + this._userList.addUser(users[i]); + } + + this._updateDisableUserList(); + + this._userManager.connect('user-added', + Lang.bind(this, function(userManager, user) { + this._userList.addUser(user); + })); + + this._userManager.connect('user-removed', + Lang.bind(this, function(userManager, user) { + this._userList.removeUser(user); + })); +-- +1.8.3.1 + + +From 411106a834623b1bcbf2f4360dcbecbed2f13b0f Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Sun, 28 Jul 2013 16:15:43 -0400 +Subject: [PATCH 45/66] authPrompt: don't muck with cancelButton in + onAskQuestion + +onAskQuestion has this code: + + if (this.verifyingUser) + this.cancelButton.show(); + else + this.cancelButton.hide(); + +but onAskQuestion can only be called when this.verifyingUser is true. +Also, cancelButton is public, and it only ever otherwise gets hidden +from callers. + +This commit drops mucking with cancelButton visibility, leaving it +entirely up to the callers to deal with. + +https://bugzilla.gnome.org/show_bug.cgi?id=683437 +--- + js/gdm/authPrompt.js | 5 ----- + 1 file changed, 5 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index cfdf052..cbd2c46 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -182,65 +182,60 @@ const AuthPrompt = new Lang.Class({ + 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) { + 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 (this.verifyingUser) +- this.cancelButton.show(); +- else +- this.cancelButton.hide(); +- + if (passwordChar) { + if (this._userVerifier.reauthenticating) + this.nextButton.label = _("Unlock"); + else + this.nextButton.label = C_("button", "Sign In"); + } else { + this.nextButton.label = _("Next"); + } + + this.updateSensitivity(true); + this.emit('prompted'); + }, + + _onSmartcardStatusChanged: function() { + this.smartcardDetected = this._userVerifier.smartcardDetected; + + // Most of the time we want to reset if the user inserts or removes + // a smartcard. Smartcard insertion "preempts" what the user was + // doing, and smartcard removal aborts the preemption. + // The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying + // with a smartcard + // 2) Don't reset if we've already succeeded at verification and + // the user is getting logged in. + if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) && + this.verificationStatus == AuthPromptStatus.VERIFYING && + this.smartcardDetected) + return; + + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) + this.reset(); +-- +1.8.3.1 + + +From 69d736de7259f86673fd54c02886527bb547463f Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Fri, 2 Aug 2013 14:10:16 -0400 +Subject: [PATCH 46/66] authPrompt: fix disable-user-list / Not Listed? + +If the first question asked to a user is from the +shell and not from the PAM service (i.e. Username: ), +then we'll save what the user types until PAM asks +a question and then try to send it to PAM. + +This commit makes sure the preemptive answer can be used +before the PAM conversation gets started, and makes sure +to discard the preemptive answer if we're not expecting it. + +https://bugzilla.gnome.org/show_bug.cgi?id=705370 +--- + js/gdm/authPrompt.js | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index cbd2c46..d09e5b6 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -170,61 +170,62 @@ const AuthPrompt = new Lang.Class({ + 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) { +- this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer); ++ if (this._queryingService) ++ this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer); + this._preemptiveAnswer = null; + return; + } + + if (this._queryingService) + this.clear(); + + this._queryingService = serviceName; + this.setPasswordChar(passwordChar); + this.setQuestion(question); + + if (passwordChar) { + if (this._userVerifier.reauthenticating) + this.nextButton.label = _("Unlock"); + else + this.nextButton.label = C_("button", "Sign In"); + } else { + this.nextButton.label = _("Next"); + } + + this.updateSensitivity(true); + this.emit('prompted'); + }, + + _onSmartcardStatusChanged: function() { + this.smartcardDetected = this._userVerifier.smartcardDetected; + + // Most of the time we want to reset if the user inserts or removes + // a smartcard. Smartcard insertion "preempts" what the user was + // doing, and smartcard removal aborts the preemption. +@@ -338,61 +339,68 @@ const AuthPrompt = new Lang.Class({ + 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._loginHint.opacity = 0; + this._loginHint.show(); + + this._entry.grab_key_focus(); + }, + + getAnswer: function() { +- let text = this._entry.get_text(); ++ 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, styleClass) { + if (message) { + Tweener.removeTweens(this._message); + this._message.text = message; + this._message.styleClass = styleClass; + this._message.opacity = 255; + } else { + this._message.opacity = 0; + } + }, + + _updateNextButtonSensitivity: function(sensitive) { + this.nextButton.reactive = sensitive; + this.nextButton.can_focus = sensitive; + }, +-- +1.8.3.1 + + +From 086b6a1ecdbef2610663aae4993300993dbea8f7 Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Tue, 6 Aug 2013 09:22:48 -0400 +Subject: [PATCH 47/66] screenShield: Don't crash when trying to deactivate the + shield + +If the user has a lock delay, or deactivate() has been called at +any other time, we need to check for the unlock dialog, as it may +not always exist. +--- + js/ui/screenShield.js | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 2c812f5..bdce9e8 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -1091,63 +1091,66 @@ const ScreenShield = new Lang.Class({ + + _clearLockScreen: function() { + this._clock.destroy(); + this._clock = null; + + if (this._notificationsBox) { + this._notificationsBox.destroy(); + this._notificationsBox = null; + } + + this._stopArrowAnimation(); + + this._lockScreenContentsBox.destroy(); + + this._hasLockScreen = false; + }, + + get locked() { + return this._isLocked; + }, + + get active() { + return this._isActive; + }, + + get activationTime() { + return this._activationTime; + }, + + deactivate: function(animate) { +- this._dialog.finish(Lang.bind(this, function() { ++ if (this._dialog) ++ this._dialog.finish(Lang.bind(this, function() { ++ this._finishDeactivate(animate); ++ })); ++ else + this._finishDeactivate(animate); +- })); + }, + + _finishDeactivate: function(animate) { + this._hideLockScreen(animate, 0); + + if (this._hasLockScreen) + this._clearLockScreen(); + + if (Main.sessionMode.currentMode == 'lock-screen') + Main.sessionMode.popMode('lock-screen'); + if (Main.sessionMode.currentMode == 'unlock-dialog') + Main.sessionMode.popMode('unlock-dialog'); + + Tweener.addTween(this._lockDialogGroup, { + scale_x: 0, + scale_y: 0, + time: animate ? Overview.ANIMATION_TIME : 0, + transition: 'easeOutQuad', + onComplete: Lang.bind(this, this._completeDeactivate), + onCompleteScope: this + }); + }, + + _completeDeactivate: function() { + if (this._dialog && !this._isGreeter) { + this._dialog.destroy(); + this._dialog = null; + } + + this._lightbox.hide(); +-- +1.8.3.1 + + +From c128890b2bc5c21acca5dd8f786f1b4d84a474a1 Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Tue, 6 Aug 2013 09:49:00 -0400 +Subject: [PATCH 48/66] screenShield: Remove confusing name + +We have both finishDeactivate and completeDeactivate. Don't. +--- + js/ui/screenShield.js | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index bdce9e8..2a8001e 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -1093,67 +1093,67 @@ const ScreenShield = new Lang.Class({ + this._clock.destroy(); + this._clock = null; + + if (this._notificationsBox) { + this._notificationsBox.destroy(); + this._notificationsBox = null; + } + + this._stopArrowAnimation(); + + this._lockScreenContentsBox.destroy(); + + this._hasLockScreen = false; + }, + + get locked() { + return this._isLocked; + }, + + get active() { + return this._isActive; + }, + + get activationTime() { + return this._activationTime; + }, + + deactivate: function(animate) { + if (this._dialog) + this._dialog.finish(Lang.bind(this, function() { +- this._finishDeactivate(animate); ++ this._continueDeactivate(animate); + })); + else +- this._finishDeactivate(animate); ++ this._continueDeactivate(animate); + }, + +- _finishDeactivate: function(animate) { ++ _continueDeactivate: function(animate) { + this._hideLockScreen(animate, 0); + + if (this._hasLockScreen) + this._clearLockScreen(); + + if (Main.sessionMode.currentMode == 'lock-screen') + Main.sessionMode.popMode('lock-screen'); + if (Main.sessionMode.currentMode == 'unlock-dialog') + Main.sessionMode.popMode('unlock-dialog'); + + Tweener.addTween(this._lockDialogGroup, { + scale_x: 0, + scale_y: 0, + time: animate ? Overview.ANIMATION_TIME : 0, + transition: 'easeOutQuad', + onComplete: Lang.bind(this, this._completeDeactivate), + onCompleteScope: this + }); + }, + + _completeDeactivate: function() { + if (this._dialog && !this._isGreeter) { + this._dialog.destroy(); + this._dialog = null; + } + + this._lightbox.hide(); + + if (this._isModal) { + Main.popModal(this.actor); +-- +1.8.3.1 + + +From 7be80c08a3ad9bd33150fa568ff8e3956567d087 Mon Sep 17 00:00:00 2001 +From: Giovanni Campagna +Date: Mon, 3 Jun 2013 23:45:31 +0200 +Subject: [PATCH 49/66] ScreenShield: remove curtain animation when hiding + without animation + +If we don't remove the animation, we might leave a pending call +to _lockScreenShown() which would confuse our state tracking into +thinking we're active when we're not. + +https://bugzilla.gnome.org/show_bug.cgi?id=700901 +--- + js/ui/screenShield.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 2a8001e..8fea66a 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -850,73 +850,74 @@ const ScreenShield = new Lang.Class({ + if (!this._becomeModal()) { + // In the login screen, this is a hard error. Fail-whale + log('Could not acquire modal grab for the login screen. Aborting login process.'); + Meta.quit(Meta.ExitCode.ERROR); + } + + return false; + })); + + this.actor.show(); + this._isGreeter = Main.sessionMode.isGreeter; + this._isLocked = true; + this._ensureUnlockDialog(true, true); + this._hideLockScreen(false, 0); + }, + + _hideLockScreenComplete: function() { + if (Main.sessionMode.currentMode == 'lock-screen') + Main.sessionMode.popMode('lock-screen'); + + this._lockScreenState = MessageTray.State.HIDDEN; + this._lockScreenGroup.hide(); + }, + + _hideLockScreen: function(animate, velocity) { + if (this._lockScreenState == MessageTray.State.HIDDEN) + return; + + this._lockScreenState = MessageTray.State.HIDING; + ++ Tweener.removeTweens(this._lockScreenGroup); ++ + if (animate) { + // Tween the lock screen out of screen + // if velocity is not specified (i.e. we come here from pressing ESC), + // use the same speed regardless of original position + // if velocity is specified, it's in pixels per milliseconds + let h = global.stage.height; + let delta = (h + this._lockScreenGroup.y); + let min_velocity = global.stage.height / (CURTAIN_SLIDE_TIME * 1000); + + velocity = Math.max(min_velocity, velocity); + let time = (delta / velocity) / 1000; + +- Tweener.removeTweens(this._lockScreenGroup); + Tweener.addTween(this._lockScreenGroup, + { y: -h, + time: time, + transition: 'easeInQuad', + onComplete: Lang.bind(this, this._hideLockScreenComplete), + }); + } else { + this._hideLockScreenComplete(); + } + + global.stage.show_cursor(); + }, + + _ensureUnlockDialog: function(onPrimary, allowCancel) { + if (!this._dialog) { + let constructor = Main.sessionMode.unlockDialog; + if (!constructor) { + // This session mode has no locking capabilities + this.deactivate(true); + return; + } + + this._dialog = new constructor(this._lockDialogGroup); + + + let time = global.get_current_time(); + if (!this._dialog.open(time, onPrimary)) { + // This is kind of an impossible error: we're already modal + // by the time we reach this... + log('Could not open login dialog: failed to acquire grab'); +-- +1.8.3.1 + + +From fe3f4d95ccd61d54e06f247ae49058d86802f1fa Mon Sep 17 00:00:00 2001 +From: Giovanni Campagna +Date: Thu, 6 Jun 2013 22:09:28 +0200 +Subject: [PATCH 50/66] ScreenShield: don't really deactivate when acting as a + greeter + +In greeter mode, we don't want to hide the login dialog, drop the +modal or send spurious signals to gnome-settings-daemon. + +https://bugzilla.gnome.org/show_bug.cgi?id=701761 +--- + js/ui/screenShield.js | 16 +++++++++++++++- + 1 file changed, 15 insertions(+), 1 deletion(-) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 8fea66a..862fd1e 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -1111,72 +1111,86 @@ const ScreenShield = new Lang.Class({ + }, + + get active() { + return this._isActive; + }, + + get activationTime() { + return this._activationTime; + }, + + deactivate: function(animate) { + if (this._dialog) + this._dialog.finish(Lang.bind(this, function() { + this._continueDeactivate(animate); + })); + else + this._continueDeactivate(animate); + }, + + _continueDeactivate: function(animate) { + this._hideLockScreen(animate, 0); + + if (this._hasLockScreen) + this._clearLockScreen(); + + if (Main.sessionMode.currentMode == 'lock-screen') + Main.sessionMode.popMode('lock-screen'); + if (Main.sessionMode.currentMode == 'unlock-dialog') + Main.sessionMode.popMode('unlock-dialog'); + ++ if (this._isGreeter) { ++ // We don't want to "deactivate" any more than ++ // this. In particular, we don't want to drop ++ // the modal, hide ourselves or destroy the dialog ++ // But we do want to set isActive to false, so that ++ // gnome-session will reset the idle counter, and ++ // gnome-settings-daemon will stop blanking the screen ++ ++ this._activationTime = 0; ++ this._isActive = false; ++ this.emit('active-changed'); ++ return; ++ } ++ + Tweener.addTween(this._lockDialogGroup, { + scale_x: 0, + scale_y: 0, + time: animate ? Overview.ANIMATION_TIME : 0, + transition: 'easeOutQuad', + onComplete: Lang.bind(this, this._completeDeactivate), + onCompleteScope: this + }); + }, + + _completeDeactivate: function() { +- if (this._dialog && !this._isGreeter) { ++ if (this._dialog) { + this._dialog.destroy(); + this._dialog = null; + } + + this._lightbox.hide(); + + if (this._isModal) { + Main.popModal(this.actor); + this._isModal = false; + } + + this.actor.hide(); + + if (this._becameActiveId != 0) { + this.idleMonitor.remove_watch(this._becameActiveId); + this._becameActiveId = 0; + } + + if (this._lockTimeoutId != 0) { + Mainloop.source_remove(this._lockTimeoutId); + this._lockTimeoutId = 0; + } + + this._activationTime = 0; + this._isActive = false; + this._isLocked = false; + this.emit('active-changed'); + this.emit('locked-changed'); + }, + +-- +1.8.3.1 + + +From 0eb83adb659536086594bb737f9d841031b93a1e Mon Sep 17 00:00:00 2001 +From: Giovanni Campagna +Date: Mon, 12 Aug 2013 15:14:37 +0200 +Subject: [PATCH 51/66] ScreenShield: don't allow events through the lock + dialog + +Make the lock dialog group reactive, to intercept any events +before they go to the actors below. +In the future, we may restructure our chrome to have a clear +layer system, but for now it fixes a security issue in the lock +screen (you can see the contents of the windows by dragging +if the screen was locked with the overview active) + +https://bugzilla.gnome.org/show_bug.cgi?id=705840 +--- + js/ui/screenShield.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 862fd1e..1d296a2 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -452,60 +452,61 @@ const ScreenShield = new Lang.Class({ + + this._updateBackgrounds(); + Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateBackgrounds)); + + this._arrowAnimationId = 0; + this._arrowWatchId = 0; + this._arrowActiveWatchId = 0; + this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows', + vertical: true, + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END, + // HACK: without these, ClutterBinLayout + // ignores alignment properties on the actor + x_expand: true, + y_expand: true }); + + for (let i = 0; i < N_ARROWS; i++) { + let arrow = new Arrow({ opacity: 0 }); + this._arrowContainer.add_actor(arrow); + } + this._lockScreenContents.add_actor(this._arrowContainer); + + this._dragAction = new Clutter.GestureAction(); + this._dragAction.connect('gesture-begin', Lang.bind(this, this._onDragBegin)); + this._dragAction.connect('gesture-progress', Lang.bind(this, this._onDragMotion)); + this._dragAction.connect('gesture-end', Lang.bind(this, this._onDragEnd)); + this._lockScreenGroup.add_action(this._dragAction); + + this._lockDialogGroup = new St.Widget({ x_expand: true, + y_expand: true, ++ reactive: true, + opacity: 0, + pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }), + name: 'lockDialogGroup' }); + + Tweener.addTween(this._lockDialogGroup, + { opacity: 255, + time: INITIAL_FADE_IN_TIME, + transition: 'easeInQuad', + }); + + this.actor.add_actor(this._lockDialogGroup); + this.actor.add_actor(this._lockScreenGroup); + + this._presence = new GnomeSession.Presence(Lang.bind(this, function(proxy, error) { + if (error) { + logError(error, 'Error while reading gnome-session presence'); + return; + } + + this._onStatusChanged(proxy.status); + })); + this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) { + this._onStatusChanged(status); + })); + + this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this); + + this._smartcardManager = SmartcardManager.getSmartcardManager(); + this._smartcardManager.connect('smartcard-inserted', + Lang.bind(this, function(token) { +-- +1.8.3.1 + + +From df3d72c639cdd49b4b58ef3b8e0ab05046b71201 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 21 Aug 2013 11:54:03 -0400 +Subject: [PATCH 52/66] loginDialog: add support for auth without username / + fix Not Listed? + +commit 93f072d1fcf578c4e7550bd755e84bd46abcd493 attempted to +add support for auth without a username to the login screen, but +do to a messed up rebase only partially added it. + +This commit fixes that. + +https://bugzilla.gnome.org/show_bug.cgi?id=706607 +--- + js/gdm/loginDialog.js | 17 ++++++----------- + 1 file changed, 6 insertions(+), 11 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 643c7d6..887bedb 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -449,65 +449,61 @@ const LoginDialog = new Lang.Class({ + { expand: true, + x_fill: true, + y_fill: true }); + + this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); + this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.hide(); + + this._authPrompt.actor.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + + this.actor.add_child(this._authPrompt.actor); + this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._authPrompt.actor, + coordinate: Clutter.BindCoordinate.WIDTH })); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + +- this._notListedButton.connect('clicked', +- Lang.bind(this, function() { +- this._authPrompt.cancelButton.show(); +- this._hideUserListAndLogIn(); +- })); ++ this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAskForUsernameAndBeginVerification)); + + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + + this._logoBin = new St.Bin({ style_class: 'login-dialog-logo-bin', y_expand: true }); + this._logoBin.set_y_align(Clutter.ActorAlign.END); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.X_AXIS, + factor: 0.5 })); + this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.Y_AXIS, + factor: 1.0 })); + this.actor.add_child(this._logoBin); + this._updateLogo(); + + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); +@@ -569,66 +565,65 @@ const LoginDialog = new Lang.Class({ + if (this._logoFileUri != uri) + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + this._showPrompt(); + }, + + _onReset: function(authPrompt, beginRequest) { + this._sessionMenuButton.updateSensitivity(true); + + this._user = null; + +- if (this._disableUserList) { +- this._authPrompt.cancelButton.hide(); +- this._hideUserListAndLogIn(); +- } else { +- this._showUserList(); +- } ++ if (!this._disableUserList && ++ beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) ++ this._showUserList(); ++ else ++ this._hideUserListAndBeginVerification(); + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { + if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING) + return false; + + if (this._user && this._user.is_logged_in()) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) + return; + this._authPrompt.actor.opacity = 0; + this._authPrompt.actor.show(); + Tweener.addTween(this._authPrompt.actor, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; +-- +1.8.3.1 + + +From 96cc4400c7c3aca9b89a5c48574048ee5183092d Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Fri, 16 Aug 2013 08:48:35 -0400 +Subject: [PATCH 53/66] theme: minor user item tweaks + + too much padding between avatar and name - +https://cloud.gnome.org/public.php?service=files&t=2a4b3383b568a7986edfcb3501bdd020 + not enough vertical padding between avatar image +and "Password" - +https://cloud.gnome.org/public.php?service=files&t=16b6eaa8f607650bcd11a4d4236fe7be +--- + data/theme/gnome-shell.css | 7 ++----- + 1 file changed, 2 insertions(+), 5 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 15457fe..72a7ad9 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2242,61 +2242,61 @@ StScrollBar StButton#vhandle:active { + } + + .login-dialog-prompt-login-hint-message { + font-size: 10.5pt; + } + + .login-dialog-user-list-view { + -st-vfade-offset: 1em; + } + + .login-dialog-user-list { + spacing: 12px; + padding: .2em; + } + + .login-dialog-user-list-item { + border-radius: 10px; + padding: .2em; + } + + .login-dialog-user-list-item:ltr { + padding-right: 1em; + } + + .login-dialog-user-list-item:rtl { + padding-left: 1em; + } + + .login-dialog-user-list-item .login-dialog-user-list-item-name { + font-size: 20pt; +- padding-left: 1em; ++ padding-left: 9px; + } + + .login-dialog-user-list:expanded .login-dialog-user-list-item { + color: #666666; + } + + .login-dialog-user-list-item, + .login-dialog-user-list-item:hover .login-dialog-user-list-item-name, + .login-dialog-user-list:expanded .login-dialog-user-list-item:focus .login-dialog-user-list-item-name, + .login-dialog-user-list:expanded .login-dialog-user-list-item:logged-in { + color: white; + text-shadow: black 0px 2px 2px; + } + + .login-dialog-user-list-item:hover { + background-color: rgba(255,255,255,0.1); + } + + .login-dialog-user-list:expanded .login-dialog-user-list-item:focus { + background-color: rgba(255,255,255,0.33); + } + + .login-dialog-user-list:expanded .login-dialog-user-list-item:logged-in { + background-image: url("logged-in-indicator.svg"); + background-size: contain; + } + + .login-dialog-user-list-item-text-box { + padding: 0 0.5em; + } +@@ -2321,60 +2321,61 @@ StScrollBar StButton#vhandle:active { + font-size: 10.5pt; + font-weight: bold; + color: #666666; + padding-top: 1em; + padding-left: 2px; + } + + .login-dialog-not-listed-button:focus .login-dialog-not-listed-label, + .login-dialog-not-listed-button:hover .login-dialog-not-listed-label { + color: #E8E8E8; + } + + .login-dialog-username { + font-size: 16pt; + font-weight: bold; + text-align: left; + padding-left: 15px; + text-shadow: black 0px 4px 3px 0px; + } + + .login-dialog-prompt-layout { + padding-top: 24px; + padding-bottom: 12px; + spacing: 8px; + width: 23em; + } + + .login-dialog-prompt-label { + color: #eeeeee; + font-size: 14px; ++ padding-top: 11px; + } + + .login-dialog-session-list-button StIcon { + icon-size: 1.25em; + } + + .login-dialog-session-list-button { + color: #8b8b8b; + } + + .login-dialog-session-list-button:hover, + .login-dialog-session-list-button:active { + color: white; + } + + .login-dialog-logo-bin { + padding: 24px 0px; + } + + .login-dialog .modal-dialog-button-box { + spacing: 3px; + } + + .login-dialog .modal-dialog-button { + border-radius: 5px; + padding: 3px 18px; + } + + .login-dialog .modal-dialog-button:focus { + padding: 2px 17px; +@@ -2386,64 +2387,60 @@ StScrollBar StButton#vhandle:active { + background-gradient-direction: vertical; + border-color: #16335d; + } + + .login-dialog .modal-dialog-button:default:focus { + border: 2px solid #377fe7; + } + + .login-dialog .modal-dialog-button:default:hover { + background-gradient-start: #74a0d0; + background-gradient-end: #436d9f; + } + + .login-dialog .modal-dialog-button:default:active, + .login-dialog .modal-dialog-button:default:pressed { + background-gradient-start: #436d9f; + background-gradient-end: #74a0d0; + } + + .login-dialog .modal-dialog-button:default:insensitive { + border-color: #666666; + color: #9f9f9f; + background-gradient-direction: none; + background-color: rgba(102, 102, 102, 0.15); + } + + .login-dialog-message-warning { + color: orange; + } + +-.user-widget { +- spacing: .4em; +-} +- + .user-widget-label { + font-size: 16pt; + font-weight: bold; + text-align: left; + padding-left: 15px; + text-shadow: black 0px 4px 3px 0px; + } + + /* Screen shield */ + + .screen-shield-background { + background: black; + } + + #lockDialogGroup { + background: #2e3436 url(noise-texture.png); + background-repeat: repeat; + } + + .screen-shield-arrows { + padding-bottom: 3em; + } + + .screen-shield-arrows Gjs_Arrow { + color: white; + width: 80px; + height: 48px; + -arrow-thickness: 12px; + -arrow-shadow: 0 1px 1px rgba(0,0,0,0.4); + } +-- +1.8.3.1 + + +From 2b398b84d57c23947b64b43d0d1672b533537882 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 19 Aug 2013 12:00:33 -0400 +Subject: [PATCH 54/66] loginDialog: consolidate message label and login hint + label + +Right now the login hint is showing up just above the the cancel +button, instead of just below the text entry field. + +The mockup here: + +https://raw.github.com/gnome-design-team/gnome-mockups/master/system-lock-login-boot/login-dissect.png + +Says it should share a label with the PAM info/error messages. + +This commit consolidates the two labels. + +https://bugzilla.gnome.org/show_bug.cgi?id=706324 +--- + data/theme/gnome-shell.css | 16 ++++++++++---- + js/gdm/authPrompt.js | 55 +++++++++++++++++++--------------------------- + js/gdm/loginDialog.js | 2 +- + js/gdm/util.js | 26 +++++++++++++--------- + 4 files changed, 51 insertions(+), 48 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 72a7ad9..91a6092 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2214,64 +2214,60 @@ StScrollBar StButton#vhandle:active { + text-align: center; + color: #666666; + padding-bottom: 1em; + } + + .login-dialog-title { + font-size: 14pt; + font-weight: bold; + color: #666666; + padding-bottom: 2em; + } + + .login-dialog { + /* Reset border and background */ + border: none; + background-color: transparent; + + padding-bottom: 80px; + padding-top: 80px; + + border-radius: 16px; + min-height: 150px; + max-height: 700px; + min-width: 350px; + } + + .login-dialog-button-box { + spacing: 5px; + } + +-.login-dialog-prompt-login-hint-message { +- font-size: 10.5pt; +-} +- + .login-dialog-user-list-view { + -st-vfade-offset: 1em; + } + + .login-dialog-user-list { + spacing: 12px; + padding: .2em; + } + + .login-dialog-user-list-item { + border-radius: 10px; + padding: .2em; + } + + .login-dialog-user-list-item:ltr { + padding-right: 1em; + } + + .login-dialog-user-list-item:rtl { + padding-left: 1em; + } + + .login-dialog-user-list-item .login-dialog-user-list-item-name { + font-size: 20pt; + padding-left: 9px; + } + + .login-dialog-user-list:expanded .login-dialog-user-list-item { + color: #666666; + } +@@ -2383,64 +2379,76 @@ StScrollBar StButton#vhandle:active { + + .login-dialog .modal-dialog-button:default { + background-gradient-start: #6793c4; + background-gradient-end: #335d8f; + background-gradient-direction: vertical; + border-color: #16335d; + } + + .login-dialog .modal-dialog-button:default:focus { + border: 2px solid #377fe7; + } + + .login-dialog .modal-dialog-button:default:hover { + background-gradient-start: #74a0d0; + background-gradient-end: #436d9f; + } + + .login-dialog .modal-dialog-button:default:active, + .login-dialog .modal-dialog-button:default:pressed { + background-gradient-start: #436d9f; + background-gradient-end: #74a0d0; + } + + .login-dialog .modal-dialog-button:default:insensitive { + border-color: #666666; + color: #9f9f9f; + background-gradient-direction: none; + background-color: rgba(102, 102, 102, 0.15); + } + ++.login-dialog-message-warning, ++.login-dialog-message-info { ++ padding-top: 4px; ++ padding-bottom: 16px; ++ min-height: 2em; ++} ++ + .login-dialog-message-warning { + color: orange; + } + ++.login-dialog-message-hint { ++ padding-bottom: 16px; ++ min-height: 2em; ++} ++ + .user-widget-label { + font-size: 16pt; + font-weight: bold; + text-align: left; + padding-left: 15px; + text-shadow: black 0px 4px 3px 0px; + } + + /* Screen shield */ + + .screen-shield-background { + background: black; + } + + #lockDialogGroup { + background: #2e3436 url(noise-texture.png); + background-repeat: repeat; + } + + .screen-shield-arrows { + padding-bottom: 3em; + } + + .screen-shield-arrows Gjs_Arrow { + color: white; + width: 80px; + height: 48px; + -arrow-thickness: 12px; + -arrow-shadow: 0 1px 1px rgba(0,0,0,0.4); + } +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index d09e5b6..732ad1a 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -9,139 +9,136 @@ const Panel = imports.ui.panel; + const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; + const Params = imports.misc.params; + const ShellEntry = imports.ui.shellEntry; + const Tweener = imports.ui.tweener; + const UserWidget = imports.ui.userWidget; + + const DEFAULT_BUTTON_WELL_ICON_SIZE = 24; + const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; + const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; + + const MESSAGE_FADE_OUT_ANIMATION_TIME = 0.5; + + const AuthPromptMode = { + UNLOCK_ONLY: 0, + UNLOCK_OR_LOG_IN: 1 + }; + + const AuthPromptStatus = { + NOT_VERIFYING: 0, + VERIFYING: 1, + VERIFICATION_FAILED: 2, + VERIFICATION_SUCCEEDED: 3 + }; + + const BeginRequestType = { + PROVIDE_USERNAME: 0, + DONT_PROVIDE_USERNAME: 1 + }; + ++let _messageStyleMap; ++ + const AuthPrompt = new Lang.Class({ + Name: 'AuthPrompt', + + _init: function(gdmClient, mode) { + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + + this._gdmClient = gdmClient; + this._mode = mode; + + let reauthenticationOnly; + if (this._mode == AuthPromptMode.UNLOCK_ONLY) + reauthenticationOnly = true; + else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN) + reauthenticationOnly = false; + + this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly }); + + this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion)); + this._userVerifier.connect('show-message', Lang.bind(this, this._onShowMessage)); + this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); +- this._userVerifier.connect('show-login-hint', Lang.bind(this, this._onShowLoginHint)); +- this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._onHideLoginHint)); + this._userVerifier.connect('smartcard-status-changed', Lang.bind(this, this._onSmartcardStatusChanged)); + this.smartcardDetected = this._userVerifier.smartcardDetected; + + this.connect('next', Lang.bind(this, function() { + this.updateSensitivity(false); + this.startSpinning(); + if (this._queryingService) { + this._userVerifier.answerQuery(this._queryingService, this._entry.text); + } else { + this._preemptiveAnswer = this._entry.text; + } + })); + + this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', + vertical: true }); + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + this.actor.connect('key-press-event', + Lang.bind(this, function(actor, event) { + if (event.get_key_symbol() == Clutter.KEY_Escape) { + this.cancel(); + } + })); + + this._userWell = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this.actor.add(this._userWell, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this.actor.add(this._label, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + ShellEntry.addContextMenu(this._entry, { isPassword: true }); + + this.actor.add(this._entry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._entry.grab_key_focus(); + + this._message = new St.Label({ opacity: 0 }); + this._message.clutter_text.line_wrap = true; +- this.actor.add(this._message, { x_fill: true }); +- +- this._loginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); +- this.actor.add(this._loginHint); ++ this.actor.add(this._message, { x_fill: true, y_align: St.Align.START }); + + this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', + vertical: false }); + this.actor.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + + this._defaultButtonWell = new St.Widget(); + this._defaultButtonWellActor = null; + + this._initButtons(); + + let spinnerIcon = global.datadir + '/theme/process-working.svg'; + this._spinner = new Panel.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); + this._spinner.actor.opacity = 0; + this._spinner.actor.show(); + this._defaultButtonWell.add_child(this._spinner.actor); + }, + + _onDestroy: function() { + this._userVerifier.clear(); + this._userVerifier.disconnectAll(); + this._userVerifier = null; + }, + + _initButtons: function() { + this.cancelButton = new St.Button({ style_class: 'modal-dialog-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + reactive: true, +@@ -215,92 +212,84 @@ const AuthPrompt = new Lang.Class({ + this.nextButton.label = _("Unlock"); + else + this.nextButton.label = C_("button", "Sign In"); + } else { + this.nextButton.label = _("Next"); + } + + this.updateSensitivity(true); + this.emit('prompted'); + }, + + _onSmartcardStatusChanged: function() { + this.smartcardDetected = this._userVerifier.smartcardDetected; + + // Most of the time we want to reset if the user inserts or removes + // a smartcard. Smartcard insertion "preempts" what the user was + // doing, and smartcard removal aborts the preemption. + // The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying + // with a smartcard + // 2) Don't reset if we've already succeeded at verification and + // the user is getting logged in. + if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) && + this.verificationStatus == AuthPromptStatus.VERIFYING && + this.smartcardDetected) + return; + + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) + this.reset(); + }, + +- _onShowMessage: function(userVerifier, message, styleClass) { +- this.setMessage(message, styleClass); ++ _onShowMessage: function(userVerifier, message, type) { ++ this.setMessage(message, type); + this.emit('prompted'); + }, + + _onVerificationFailed: function() { + this.clear(); + + this.updateSensitivity(true); + this.setActorInDefaultButtonWell(null); + this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; + }, + + _onVerificationComplete: function() { + this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; + }, + + _onReset: function() { + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) { + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + this.reset(); + } + }, + +- _onShowLoginHint: function(verifier, message) { +- this.setHint(message); +- }, +- +- _onHideLoginHint: function() { +- this.setHint(null); +- }, +- + addActorToDefaultButtonWell: function(actor) { + this._defaultButtonWell.add_child(actor); + + actor.add_constraint(new Clutter.AlignConstraint({ source: this._spinner.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + }, + + setActorInDefaultButtonWell: function(actor, animate) { + if (!this._defaultButtonWellActor && + !actor) + return; + + let oldActor = this._defaultButtonWellActor; + + if (oldActor) + Tweener.removeTweens(oldActor); + + let isSpinner; + if (actor == this._spinner.actor) + isSpinner = true; + else + isSpinner = false; + + if (this._defaultButtonWellActor != actor && oldActor) { + if (!animate) { + oldActor.opacity = 0; + } else { + Tweener.addTween(oldActor, + { opacity: 0, +@@ -332,155 +321,155 @@ const AuthPrompt = new Lang.Class({ + 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._loginHint.opacity = 0; +- this._loginHint.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, styleClass) { ++ _initMessageStyleMap: function() { ++ if (_messageStyleMap) ++ return; ++ ++ _messageStyleMap = {}; ++ _messageStyleMap[GdmUtil.MessageType.NONE] = ''; ++ _messageStyleMap[GdmUtil.MessageType.ERROR] = 'login-dialog-message-warning'; ++ _messageStyleMap[GdmUtil.MessageType.INFO] = 'login-dialog-message-info'; ++ _messageStyleMap[GdmUtil.MessageType.HINT] = 'login-dialog-message-hint'; ++ ++ }, ++ ++ setMessage: function(message, type) { ++ this._initMessageStyleMap(); + if (message) { + Tweener.removeTweens(this._message); + this._message.text = message; +- this._message.styleClass = styleClass; ++ this._message.styleClass = _messageStyleMap[type]; + this._message.opacity = 255; + } else { ++ this._message.styleClass = null; + this._message.opacity = 0; + } + }, + + _updateNextButtonSensitivity: function(sensitive) { + this.nextButton.reactive = sensitive; + this.nextButton.can_focus = sensitive; + }, + + updateSensitivity: function(sensitive) { + this._updateNextButtonSensitivity(sensitive); + this._entry.reactive = sensitive; + this._entry.clutter_text.editable = sensitive; + }, + + hide: function() { + this.setActorInDefaultButtonWell(null, true); + this.actor.hide(); +- this._loginHint.opacity = 0; ++ this._message.opacity = 0; + + this.setUser(null); + + this.updateSensitivity(true); + this._entry.set_text(''); + }, + + setUser: function(user) { + if (user) { + let userWidget = new UserWidget.UserWidget(user); + this._userWell.set_child(userWidget.actor); + } else { + this._userWell.set_child(null); + } + }, + +- setHint: function(message) { +- if (message) { +- this._loginHint.set_text(message) +- this._loginHint.opacity = 255; +- } else { +- this._loginHint.opacity = 0; +- this._loginHint.set_text(''); +- } +- }, +- + reset: function() { + let oldStatus = this.verificationStatus; + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + + if (oldStatus == AuthPromptStatus.VERIFYING) + this._userVerifier.cancel(); + + this._queryingService = null; + this.clear(); + this._message.opacity = 0; + this.setUser(null); + this.stopSpinning(); +- this.setHint(null); + + if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED) + this.emit('failed'); + + let beginRequestType; + + if (this._mode == AuthPromptMode.UNLOCK_ONLY) { + // The user is constant at the unlock screen, so it will immediately + // respond to the request with the username + beginRequestType = BeginRequestType.PROVIDE_USERNAME; + } else if (this.smartcardDetected && + this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME)) { + // We don't need to know the username if the user preempted the login screen + // with a smartcard. + beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME; + } else { + // In all other cases, we should get the username up front. + beginRequestType = BeginRequestType.PROVIDE_USERNAME; + } + + this.emit('reset', beginRequestType); + }, + + addCharacter: function(unichar) { + if (!this._entry.visible) + return; + + this._entry.grab_key_focus(); + this._entry.clutter_text.insert_unichar(unichar); + }, +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 887bedb..26028c8 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -607,61 +607,61 @@ const LoginDialog = new Lang.Class({ + if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING) + return false; + + if (this._user && this._user.is_logged_in()) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) + return; + this._authPrompt.actor.opacity = 0; + this._authPrompt.actor.show(); + Tweener.addTween(this._authPrompt.actor, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + 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._authPrompt.setHint(_("(e.g., user or %s)").format(hint)); ++ this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), AuthPrompt.MessageType.HINT); + }, + + _askForUsernameAndBeginVerification: function() { + this._authPrompt.setPasswordChar(''); + this._authPrompt.setQuestion(_("Username: ")); + + let realmManager = new Realmd.Manager(); + let realmSignalId = realmManager.connect('login-format-changed', + Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + + let nextSignalId = this._authPrompt.connect('next', + Lang.bind(this, function() { + this._authPrompt.disconnect(nextSignalId); + this._authPrompt.updateSensitivity(false); + let answer = this._authPrompt.getAnswer(); + this._authPrompt.clear(); + this._authPrompt.startSpinning(); + this._authPrompt.begin({ userName: answer }); + this._updateCancelButton(); + + realmManager.disconnect(realmSignalId) + realmManager.release(); + })); + this._updateCancelButton(); + this._showPrompt(); + }, + + _startSession: function(serviceName) { + Tweener.addTween(this.actor, +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 7ed5097..bfbd1b3 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -9,60 +9,67 @@ const Signals = imports.signals; + const St = imports.gi.St; + + const Batch = imports.gdm.batch; + const Fprint = imports.gdm.fingerprint; + 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 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'; + + 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; + + let hold = new Batch.Hold(); + actor.show(); + let [minHeight, naturalHeight] = actor.get_preferred_height(-1); + + actor.opacity = 0; + actor.set_height(0); + Tweener.addTween(actor, + { opacity: 255, + height: naturalHeight, + time: FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onComplete: function() { + this.set_height(-1); + hold.release(); + }, + }); + + return hold; + } + + function fadeOutActor(actor) { + if (!actor.visible || actor.opacity == 0) { + actor.opacity = 0; + actor.hide(); + return null; + } +@@ -198,131 +205,132 @@ const ShellUserVerifier = new Lang.Class({ + this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, 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.iconName); ++ ++ this.emit('show-message', message.text, message.type); + + this._messageQueueTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, + message.interval, + Lang.bind(this, function() { + this._messageQueueTimeoutId = 0; + this._queueMessageTimeout(); + })); + }, + +- _queueMessage: function(message, iconName) { ++ _queueMessage: function(message, messageType) { + let interval = this._getIntervalForMessage(message); + + this.hasPendingMessages = true; +- this._messageQueue.push({ text: message, interval: interval, iconName: iconName }); ++ 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, null); ++ this.emit('show-message', null, MessageType.NONE); + }, + + _checkForFingerprintReader: function() { + this._haveFingerprintReader = false; + + if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) { + this._updateDefaultService(); + return; + } + + this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable, Lang.bind(this, + function(device, error) { + if (!error && device) + this._haveFingerprintReader = true; + this._updateDefaultService(); + })); + }, + + _checkForSmartcard: function() { + let smartcardDetected; + + if (!this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) + smartcardDetected = false; + else if (this.reauthenticating) + smartcardDetected = this._smartcardManager.hasInsertedLoginToken(); + else + smartcardDetected = this._smartcardManager.hasInsertedTokens(); + + if (smartcardDetected != this.smartcardDetected) { + this.smartcardDetected = smartcardDetected; + + if (this.smartcardDetected) + this._preemptingService = SMARTCARD_SERVICE_NAME; + else if (this._preemptingService == SMARTCARD_SERVICE_NAME) + this._preemptingService = null; + + this.emit('smartcard-status-changed'); + } + }, + + _reportInitError: function(where, error) { + logError(error, where); + this._hold.release(); + +- this._queueMessage(_("Authentication error"), 'login-dialog-message-warning'); ++ this._queueMessage(_("Authentication error"), MessageType.ERROR); + this._verificationFailed(false); + }, + + _reauthenticationChannelOpened: function(client, result) { + try { + this._userVerifier = client.open_reauthentication_channel_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e if e.matches(Gio.DBusError, Gio.DBusError.ACCESS_DENIED) && + !this._reauthOnly) { + // Gdm emits org.freedesktop.DBus.Error.AccessDenied when there is + // no session to reauthenticate. Fall back to performing verification + // from this login session + client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot)); + return; + } catch(e) { + this._reportInitError('Failed to open reauthentication channel', e); + return; + } + + this.reauthenticating = true; + this._connectSignals(); + this._beginVerification(); + this._hold.release(); + }, + + _userVerifierGot: function(client, result) { + try { + this._userVerifier = client.get_user_verifier_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { +@@ -372,78 +380,78 @@ const ShellUserVerifier = new Lang.Class({ + }, + + _startService: function(serviceName) { + this._hold.acquire(); + this._userVerifier.call_begin_verification_for_user(serviceName, + this._userName, + this._cancellable, + Lang.bind(this, function(obj, result) { + try { + obj.call_begin_verification_for_user_finish(result); + } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + return; + } catch(e) { + this._reportInitError('Failed to start verification for user', e); + return; + } + + this._hold.release(); + })); + }, + + _beginVerification: function() { + 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, 'login-dialog-message-info'); ++ this._queueMessage(info, MessageType.INFO); + } else 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.emit('show-login-hint', _("(or swipe finger)")); ++ this._queueMessage(_("(or swipe finger)"), MessageType.HINT); + } + }, + + _onProblem: function(client, serviceName, problem) { + if (!this.serviceIsForeground(serviceName)) + return; + +- this._queueMessage(problem, 'login-dialog-message-warning'); ++ this._queueMessage(problem, MessageType.ERROR); + }, + + _onInfoQuery: function(client, serviceName, question) { + if (!this.serviceIsForeground(serviceName)) + return; + + this.emit('ask-question', serviceName, question, ''); + }, + + _onSecretInfoQuery: function(client, serviceName, secretQuestion) { + if (!this.serviceIsForeground(serviceName)) + return; + + this.emit('ask-question', serviceName, secretQuestion, '\u25cf'); + }, + + _onReset: function() { + // Clear previous attempts to authenticate + this._failCounter = 0; + this._updateDefaultService(); + + this.emit('reset'); + }, + + _onVerificationComplete: function() { + this.emit('verification-complete'); + }, + + _cancelAndReset: function() { + this.cancel(); +@@ -469,35 +477,33 @@ const ShellUserVerifier = new Lang.Class({ + this._retry(); + } else { + let signalId = this.connect('no-more-messages', + Lang.bind(this, function() { + this.disconnect(signalId); + this._retry(); + })); + } + } else { + if (!this.hasPendingMessages) { + this._cancelAndReset(); + } else { + let signalId = this.connect('no-more-messages', + Lang.bind(this, function() { + this.disconnect(signalId); + this._cancelAndReset(); + })); + } + } + + this.emit('verification-failed'); + }, + + _onConversationStopped: function(client, serviceName) { + // 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)) { + this._verificationFailed(true); + } +- +- this.emit('hide-login-hint'); + }, + }); + Signals.addSignalMethods(ShellUserVerifier.prototype); +-- +1.8.3.1 + + +From 56c4d03253cffefe9e311931c68ea16a30ff92d1 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 21 Aug 2013 18:00:40 -0400 +Subject: [PATCH 55/66] objectManager: clear inhibitor on unregistered + interfaces + +A D-Bus service can export more supported interfaces than the +shell cares about. In those cases, we avoid creating proxies, +but neglect to finish things up so the object manager class +knows it can mark itself loaded. + +This commit makes sure we do the proper finishing, so the object +manager still loads in the face of unsupported interfaces. + +https://bugzilla.gnome.org/show_bug.cgi?id=706542 +--- + js/misc/objectManager.js | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/js/misc/objectManager.js b/js/misc/objectManager.js +index b954f1b..f7265fe 100644 +--- a/js/misc/objectManager.js ++++ b/js/misc/objectManager.js +@@ -46,62 +46,65 @@ const ObjectManager = new Lang.Class({ + 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 (!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; + } + +-- +1.8.3.1 + + +From c48c761e17f996fbb3be4825cb4ac81cc4b24c58 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 22 Aug 2013 09:40:12 -0400 +Subject: [PATCH 56/66] objectManager: fix indentation + +--- + js/misc/objectManager.js | 34 +++++++++++++++++----------------- + 1 file changed, 17 insertions(+), 17 deletions(-) + +diff --git a/js/misc/objectManager.js b/js/misc/objectManager.js +index f7265fe..6998dd2 100644 +--- a/js/misc/objectManager.js ++++ b/js/misc/objectManager.js +@@ -44,78 +44,78 @@ const ObjectManager = new Lang.Class({ + 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]; ++ 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) { ++ 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); +-- +1.8.3.1 + + +From c8cce4f83c6842e817f145be2c8a94893729a04e Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 21 Aug 2013 18:05:55 -0400 +Subject: [PATCH 57/66] gdmUtil: make _startService support no username + +commit fd11ad95f61b904d6f7014f8d56c1c27d88af65c factored +out duplicated code, but unintentionally dropped support +for beginning verification without a username. + +This commit brings it back. + +https://bugzilla.gnome.org/show_bug.cgi?id=706542 +--- + js/gdm/util.js | 47 ++++++++++++++++++++++++++++++++--------------- + 1 file changed, 32 insertions(+), 15 deletions(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index bfbd1b3..f421902 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -354,75 +354,92 @@ const ShellUserVerifier = new Lang.Class({ + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + }, + + _getForegroundService: function() { + if (this._preemptingService) + return this._preemptingService; + + return this._defaultService; + }, + + serviceIsForeground: function(serviceName) { + return serviceName == this._getForegroundService(); + }, + + serviceIsDefault: function(serviceName) { + return serviceName == this._defaultService; + }, + + _updateDefaultService: function() { + if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) + this._defaultService = PASSWORD_SERVICE_NAME; + else if (this.smartcardDetected) + this._defaultService = SMARTCARD_SERVICE_NAME; + else if (this._haveFingerprintReader) + this._defaultService = FINGERPRINT_SERVICE_NAME; + }, + + _startService: function(serviceName) { + this._hold.acquire(); +- this._userVerifier.call_begin_verification_for_user(serviceName, +- this._userName, +- this._cancellable, +- Lang.bind(this, function(obj, result) { +- try { +- obj.call_begin_verification_for_user_finish(result); +- } catch(e if e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { +- return; +- } catch(e) { +- this._reportInitError('Failed to start verification for user', e); +- return; +- } +- +- this._hold.release(); +- })); ++ 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._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._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 && + 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); + } + }, + + _onProblem: function(client, serviceName, problem) { + if (!this.serviceIsForeground(serviceName)) + return; + + this._queueMessage(problem, MessageType.ERROR); + }, +-- +1.8.3.1 + + +From 5992a2b073ca8e514ae92051f1e5ef2ee468f395 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 22 Aug 2013 16:18:20 -0400 +Subject: [PATCH 58/66] loginDialog: ask for username up front if + disable-user-list==TRUE + +Right now, we rely on PAM to ask for the username if disable-user-list +is TRUE. This is suboptimal because it means we can't check if we +should show a session menu. + +This commit changes disable-user-list==TRUE to ask for a username up +front, rather than have PAM do it. + +https://bugzilla.gnome.org/show_bug.cgi?id=706607 +--- + js/gdm/loginDialog.js | 14 +++++++++----- + 1 file changed, 9 insertions(+), 5 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 26028c8..315a863 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -565,119 +565,123 @@ const LoginDialog = new Lang.Class({ + if (this._logoFileUri != uri) + return; + + let icon = null; + if (this._logoFileUri) + icon = this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT); + this._logoBin.set_child(icon); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + this._showPrompt(); + }, + + _onReset: function(authPrompt, beginRequest) { + this._sessionMenuButton.updateSensitivity(true); + + this._user = null; + +- if (!this._disableUserList && +- beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) +- this._showUserList(); +- else ++ if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { ++ if (!this._disableUserList) ++ this._showUserList(); ++ else ++ this._hideUserListAskForUsernameAndBeginVerification(); ++ } else { + this._hideUserListAndBeginVerification(); ++ } + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { + if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING) + return false; + +- if (this._user && this._user.is_logged_in()) ++ if (this._user && this._user.is_loaded && this._user.is_logged_in()) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) + return; + this._authPrompt.actor.opacity = 0; + this._authPrompt.actor.show(); + Tweener.addTween(this._authPrompt.actor, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + 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._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), AuthPrompt.MessageType.HINT); + }, + + _askForUsernameAndBeginVerification: function() { + this._authPrompt.setPasswordChar(''); + this._authPrompt.setQuestion(_("Username: ")); + + let realmManager = new Realmd.Manager(); + let realmSignalId = realmManager.connect('login-format-changed', + Lang.bind(this, this._showRealmLoginHint)); + this._showRealmLoginHint(realmManager.loginFormat); + + let nextSignalId = this._authPrompt.connect('next', + Lang.bind(this, function() { + this._authPrompt.disconnect(nextSignalId); + this._authPrompt.updateSensitivity(false); + let answer = this._authPrompt.getAnswer(); ++ this._user = this._userManager.get_user(answer); + this._authPrompt.clear(); + this._authPrompt.startSpinning(); + this._authPrompt.begin({ userName: answer }); + this._updateCancelButton(); + + realmManager.disconnect(realmSignalId) + realmManager.release(); + })); + this._updateCancelButton(); + this._showPrompt(); + }, + + _startSession: function(serviceName) { + Tweener.addTween(this.actor, + { opacity: 0, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onUpdate: function() { + let children = Main.layoutManager.uiGroup.get_children(); + + for (let i = 0; i < children.length; i++) { + if (children[i] != Main.layoutManager.screenShieldGroup) + children[i].opacity = this.actor.opacity; + } + }, + onUpdateScope: this, + onComplete: function() { + Mainloop.idle_add(Lang.bind(this, function() { + this._greeter.call_start_session_when_ready_sync(serviceName, true, null); + return false; +-- +1.8.3.1 + + +From 87ca2f703b29f97c47ceed12886c23deb0339b4f Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Mon, 26 Aug 2013 18:02:04 -0400 +Subject: [PATCH 59/66] loginDialog: Provide a finish method + +The screenShield expects to be able to call finish on the dialog. +--- + js/gdm/loginDialog.js | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 315a863..0224bd2 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -895,32 +895,36 @@ const LoginDialog = new Lang.Class({ + + this._userManager.connect('user-added', + Lang.bind(this, function(userManager, user) { + this._userList.addUser(user); + })); + + this._userManager.connect('user-removed', + Lang.bind(this, function(userManager, user) { + this._userList.removeUser(user); + })); + }, + + open: function() { + Main.ctrlAltTabManager.addGroup(this.actor, + _("Login Window"), + 'dialog-password-symbolic', + { sortGroup: CtrlAltTab.SortGroup.MIDDLE }); + this._userList.actor.grab_key_focus(); + this.actor.show(); + + return true; + }, + + close: function() { + Main.ctrlAltTabManager.removeGroup(this.dialogLayout); + }, + + addCharacter: function(unichar) { + this._authPrompt.addCharacter(unichar); + }, ++ ++ finish: function(onComplete) { ++ this._authPrompt.finish(onComplete); ++ }, + }); + Signals.addSignalMethods(LoginDialog.prototype); +-- +1.8.3.1 + + +From 508f357490a26a2f6c0f01a6be436dafac145a18 Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Mon, 26 Aug 2013 18:04:09 -0400 +Subject: [PATCH 60/66] loginDialog: Fade in the gdm auth prompt on login + +--- + js/gdm/loginDialog.js | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 0224bd2..d99d79b 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -884,47 +884,53 @@ const LoginDialog = new Lang.Class({ + } + }, + + _loadUserList: function() { + let users = this._userManager.list_users(); + + for (let i = 0; i < users.length; i++) { + this._userList.addUser(users[i]); + } + + this._updateDisableUserList(); + + this._userManager.connect('user-added', + Lang.bind(this, function(userManager, user) { + this._userList.addUser(user); + })); + + this._userManager.connect('user-removed', + Lang.bind(this, function(userManager, user) { + this._userList.removeUser(user); + })); + }, + + open: function() { + Main.ctrlAltTabManager.addGroup(this.actor, + _("Login Window"), + 'dialog-password-symbolic', + { sortGroup: CtrlAltTab.SortGroup.MIDDLE }); + this._userList.actor.grab_key_focus(); + this.actor.show(); ++ this.actor.opacity = 0; ++ ++ Tweener.addTween(this.actor, ++ { opacity: 255, ++ time: 1, ++ transition: 'easeInQuad' }); + + return true; + }, + + close: function() { + Main.ctrlAltTabManager.removeGroup(this.dialogLayout); + }, + + addCharacter: function(unichar) { + this._authPrompt.addCharacter(unichar); + }, + + finish: function(onComplete) { + this._authPrompt.finish(onComplete); + }, + }); + Signals.addSignalMethods(LoginDialog.prototype); +-- +1.8.3.1 + + +From 86ef60f2bbca5b10b260c1dc78b234e53d66f990 Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Mon, 26 Aug 2013 15:43:27 -0400 +Subject: [PATCH 61/66] unlockDialog: Remove clutter constraints from the code + +These cause annoying allocation cycle warnings, and it's simpler to +just express our desired layout in terms of nested containers. +Adapt the theme to match as well. + +https://bugzilla.gnome.org/show_bug.cgi?id=706843 +--- + data/theme/gnome-shell.css | 8 -------- + js/ui/unlockDialog.js | 10 ++++++---- + 2 files changed, 6 insertions(+), 12 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 91a6092..dbf5f27 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2200,68 +2200,60 @@ StScrollBar StButton#vhandle:active { + + .candidate-page-button-next { + border-radius: 0px 4px 4px 0px; + } + + .candidate-page-button-icon { + icon-size: 1em; + } + + /* Login Dialog */ + + .login-dialog-banner { + font-size: 10pt; + font-weight: bold; + text-align: center; + color: #666666; + padding-bottom: 1em; + } + + .login-dialog-title { + font-size: 14pt; + font-weight: bold; + color: #666666; + padding-bottom: 2em; + } + + .login-dialog { + /* Reset border and background */ + border: none; + background-color: transparent; +- +- padding-bottom: 80px; +- padding-top: 80px; +- +- border-radius: 16px; +- min-height: 150px; +- max-height: 700px; +- min-width: 350px; + } + + .login-dialog-button-box { + spacing: 5px; + } + + .login-dialog-user-list-view { + -st-vfade-offset: 1em; + } + + .login-dialog-user-list { + spacing: 12px; + padding: .2em; + } + + .login-dialog-user-list-item { + border-radius: 10px; + padding: .2em; + } + + .login-dialog-user-list-item:ltr { + padding-right: 1em; + } + + .login-dialog-user-list-item:rtl { + padding-left: 1em; + } + + .login-dialog-user-list-item .login-dialog-user-list-item-name { + font-size: 20pt; +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 09024ba..abbe8c4 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -7,74 +7,76 @@ const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const GnomeDesktop = imports.gi.GnomeDesktop; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Signals = imports.signals; + const Shell = imports.gi.Shell; + const St = imports.gi.St; + + const Layout = imports.ui.layout; + const Main = imports.ui.main; + const Panel = imports.ui.panel; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const AuthPrompt = imports.gdm.authPrompt; + const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; + const LoginDialog = imports.gdm.loginDialog; + + // The timeout before going back automatically to the lock screen (in seconds) + const IDLE_TIMEOUT = 2 * 60; + + const UnlockDialog = new Lang.Class({ + Name: 'UnlockDialog', + + _init: function(parentActor) { + this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, + style_class: 'login-dialog', ++ layout_manager: new Clutter.BoxLayout(), + visible: false }); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default(); + this._userName = GLib.get_user_name(); + this._user = this._userManager.get_user(this._userName); + +- this._promptBox = new St.BoxLayout({ vertical: true }); ++ this._promptBox = new St.BoxLayout({ vertical: true, ++ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.CENTER, ++ x_expand: true, ++ y_expand: true }); + this.actor.add_child(this._promptBox); +- this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, +- align_axis: Clutter.AlignAxis.BOTH, +- factor: 0.5 })); + + this._authPrompt = new AuthPrompt.AuthPrompt(new Gdm.Client(), AuthPrompt.AuthPromptMode.UNLOCK_ONLY); + this._authPrompt.connect('failed', Lang.bind(this, this._fail)); + this._authPrompt.connect('cancelled', Lang.bind(this, this._fail)); + this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.setPasswordChar('\u25cf'); + this._authPrompt.nextButton.label = _("Unlock"); + + this._promptBox.add_child(this._authPrompt.actor); + + this.allowCancel = false; + + let screenSaverSettings = new Gio.Settings({ schema: 'org.gnome.desktop.screensaver' }); + if (screenSaverSettings.get_boolean('user-switch-enabled')) { + let otherUserLabel = new St.Label({ text: _("Log in as another user"), + style_class: 'login-dialog-not-listed-label' }); + this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + can_focus: true, + child: otherUserLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked)); + this._promptBox.add_child(this._otherUserButton); + } else { + this._otherUserButton = null; + } + + this._authPrompt.reset(); + this._updateSensitivity(true); +-- +1.8.3.1 + + +From 828a62bd652077e75e6ce8919dfee9d3011cedcc Mon Sep 17 00:00:00 2001 +From: "Jasper St. Pierre" +Date: Mon, 26 Aug 2013 15:47:52 -0400 +Subject: [PATCH 62/66] gdm: Remove constraints from authPrompt / loginDialog + as well + +https://bugzilla.gnome.org/show_bug.cgi?id=706843 +--- + data/theme/gnome-shell.css | 1 + + js/gdm/authPrompt.js | 6 +----- + js/gdm/loginDialog.js | 35 +++++++++++++---------------------- + 3 files changed, 15 insertions(+), 27 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index dbf5f27..56c34de 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2213,60 +2213,61 @@ StScrollBar StButton#vhandle:active { + font-weight: bold; + text-align: center; + color: #666666; + padding-bottom: 1em; + } + + .login-dialog-title { + font-size: 14pt; + font-weight: bold; + color: #666666; + padding-bottom: 2em; + } + + .login-dialog { + /* Reset border and background */ + border: none; + background-color: transparent; + } + + .login-dialog-button-box { + spacing: 5px; + } + + .login-dialog-user-list-view { + -st-vfade-offset: 1em; + } + + .login-dialog-user-list { + spacing: 12px; + padding: .2em; ++ width: 23em; + } + + .login-dialog-user-list-item { + border-radius: 10px; + padding: .2em; + } + + .login-dialog-user-list-item:ltr { + padding-right: 1em; + } + + .login-dialog-user-list-item:rtl { + padding-left: 1em; + } + + .login-dialog-user-list-item .login-dialog-user-list-item-name { + font-size: 20pt; + padding-left: 9px; + } + + .login-dialog-user-list:expanded .login-dialog-user-list-item { + color: #666666; + } + + .login-dialog-user-list-item, + .login-dialog-user-list-item:hover .login-dialog-user-list-item-name, + .login-dialog-user-list:expanded .login-dialog-user-list-item:focus .login-dialog-user-list-item-name, + .login-dialog-user-list:expanded .login-dialog-user-list-item:logged-in { + color: white; + text-shadow: black 0px 2px 2px; +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 732ad1a..674dd3a 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -93,61 +93,61 @@ const AuthPrompt = new Lang.Class({ + this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this.actor.add(this._label, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + ShellEntry.addContextMenu(this._entry, { isPassword: true }); + + this.actor.add(this._entry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._entry.grab_key_focus(); + + this._message = new St.Label({ opacity: 0 }); + this._message.clutter_text.line_wrap = true; + this.actor.add(this._message, { x_fill: true, y_align: St.Align.START }); + + this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', + vertical: false }); + this.actor.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + +- this._defaultButtonWell = new St.Widget(); ++ this._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 Panel.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); + this._spinner.actor.opacity = 0; + this._spinner.actor.show(); + this._defaultButtonWell.add_child(this._spinner.actor); + }, + + _onDestroy: function() { + this._userVerifier.clear(); + this._userVerifier.disconnectAll(); + this._userVerifier = null; + }, + + _initButtons: function() { + this.cancelButton = new St.Button({ style_class: 'modal-dialog-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + reactive: true, + can_focus: true, + label: _("Cancel") }); + this.cancelButton.connect('clicked', + Lang.bind(this, function() { + this.cancel(); + })); + this._buttonBox.add(this.cancelButton, + { expand: false, + x_fill: false, +@@ -238,64 +238,60 @@ const AuthPrompt = new Lang.Class({ + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) + this.reset(); + }, + + _onShowMessage: function(userVerifier, message, type) { + this.setMessage(message, type); + this.emit('prompted'); + }, + + _onVerificationFailed: function() { + this.clear(); + + this.updateSensitivity(true); + this.setActorInDefaultButtonWell(null); + this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; + }, + + _onVerificationComplete: function() { + this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; + }, + + _onReset: function() { + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) { + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + this.reset(); + } + }, + + addActorToDefaultButtonWell: function(actor) { + this._defaultButtonWell.add_child(actor); +- +- actor.add_constraint(new Clutter.AlignConstraint({ source: this._spinner.actor, +- align_axis: Clutter.AlignAxis.BOTH, +- factor: 0.5 })); + }, + + setActorInDefaultButtonWell: function(actor, animate) { + if (!this._defaultButtonWellActor && + !actor) + return; + + let oldActor = this._defaultButtonWellActor; + + if (oldActor) + Tweener.removeTweens(oldActor); + + let isSpinner; + if (actor == this._spinner.actor) + isSpinner = true; + else + isSpinner = false; + + if (this._defaultButtonWellActor != actor && oldActor) { + if (!animate) { + oldActor.opacity = 0; + } else { + Tweener.addTween(oldActor, + { opacity: 0, + time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, + delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, + transition: 'linear', + onCompleteScope: this, + onComplete: function() { + if (isSpinner) { +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index d99d79b..1649e4e 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -367,159 +367,151 @@ const SessionMenuButton = new Lang.Class({ + + if (ids.length <= 1) { + this._button.hide(); + return; + } + + for (let i = 0; i < ids.length; i++) { + let [sessionName, sessionDescription] = Gdm.get_session_name_and_description(ids[i]); + + let id = ids[i]; + let item = new PopupMenu.PopupMenuItem(sessionName); + this._menu.addMenuItem(item); + this._items[id] = item; + + if (!this._activeSessionId) + this.setActiveSession(id); + + item.connect('activate', Lang.bind(this, function() { + this.setActiveSession(id); + })); + } + } + }); + Signals.addSignalMethods(SessionMenuButton.prototype); + + const LoginDialog = new Lang.Class({ + Name: 'LoginDialog', + + _init: function(parentActor) { + this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, ++ layout_manager: new Clutter.BinLayout(), + style_class: 'login-dialog', + visible: false }); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default() + let gdmClient = new Gdm.Client(); + + if (GLib.getenv('GDM_GREETER_TEST') != '1') { + this._greeter = gdmClient.get_greeter_sync(null); + + this._greeter.connect('default-session-name-changed', + Lang.bind(this, this._onDefaultSessionChanged)); + + this._greeter.connect('session-opened', + Lang.bind(this, this._onSessionOpened)); + this._greeter.connect('timed-login-requested', + Lang.bind(this, this._onTimedLoginRequested)); + } + + this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA }); + + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', ++ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.CENTER, ++ x_expand: true, ++ y_expand: true, + vertical: true, + visible: false }); +- this._userSelectionBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, +- align_axis: Clutter.AlignAxis.BOTH, +- factor: 0.5 })); + this.actor.add_child(this._userSelectionBox); + + this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', + text: '' }); + this._userSelectionBox.add(this._bannerLabel); + this._updateBanner(); + + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + + this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); + this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.hide(); +- +- this._authPrompt.actor.add_constraint(new Clutter.AlignConstraint({ source: this.actor, +- align_axis: Clutter.AlignAxis.BOTH, +- factor: 0.5 })); +- + this.actor.add_child(this._authPrompt.actor); +- this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._authPrompt.actor, +- coordinate: Clutter.BindCoordinate.WIDTH })); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAskForUsernameAndBeginVerification)); + + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + +- this._logoBin = new St.Bin({ style_class: 'login-dialog-logo-bin', y_expand: true }); +- this._logoBin.set_y_align(Clutter.ActorAlign.END); +- this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, +- align_axis: Clutter.AlignAxis.X_AXIS, +- factor: 0.5 })); +- this._logoBin.add_constraint(new Clutter.AlignConstraint({ source: this.actor, +- align_axis: Clutter.AlignAxis.Y_AXIS, +- factor: 1.0 })); ++ this._logoBin = new St.Widget({ style_class: 'login-dialog-logo-bin', ++ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.END, ++ x_expand: true, ++ y_expand: true }); + this.actor.add_child(this._logoBin); + this._updateLogo(); + + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + +@@ -538,65 +530,64 @@ const LoginDialog = new Lang.Class({ + + _updateCancelButton: function() { + let cancelVisible; + + // Hide the cancel button if the user list is disabled and we're asking for + // a username + if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING && this._disableUserList) + cancelVisible = false; + else + cancelVisible = true; + + this._authPrompt.cancelButton.visible = cancelVisible; + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + +- let icon = null; ++ this._logoBin.destroy_all_children(); + if (this._logoFileUri) +- icon = this._textureCache.load_uri_async(this._logoFileUri, +- -1, _LOGO_ICON_HEIGHT); +- this._logoBin.set_child(icon); ++ this._logoBin.add_child(this._textureCache.load_uri_async(this._logoFileUri, ++ -1, _LOGO_ICON_HEIGHT)); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + this._showPrompt(); + }, + + _onReset: function(authPrompt, beginRequest) { + this._sessionMenuButton.updateSensitivity(true); + + this._user = null; + + if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { + if (!this._disableUserList) + this._showUserList(); + else + this._hideUserListAskForUsernameAndBeginVerification(); + } else { + this._hideUserListAndBeginVerification(); + } +-- +1.8.3.1 + + +From 6939e19dfdc3f3ab2696eeaa589252577d706b8e Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 27 Aug 2013 10:17:26 -0400 +Subject: [PATCH 63/66] authPrompt: give message label an initial style + +This commit consolidates the styles of the various +message types into one 'login-dialog-message' style +and then adds additional styles on top to cover the +differences. + +This allows us to give the message label an initial +style so that is padded properly before any messages +are displayed. + +https://bugzilla.gnome.org/show_bug.cgi?id=706670 +--- + data/theme/gnome-shell.css | 6 ++---- + js/gdm/authPrompt.js | 28 +++++++++++----------------- + 2 files changed, 13 insertions(+), 21 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 56c34de..32b6eab 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2372,74 +2372,72 @@ StScrollBar StButton#vhandle:active { + + .login-dialog .modal-dialog-button:default { + background-gradient-start: #6793c4; + background-gradient-end: #335d8f; + background-gradient-direction: vertical; + border-color: #16335d; + } + + .login-dialog .modal-dialog-button:default:focus { + border: 2px solid #377fe7; + } + + .login-dialog .modal-dialog-button:default:hover { + background-gradient-start: #74a0d0; + background-gradient-end: #436d9f; + } + + .login-dialog .modal-dialog-button:default:active, + .login-dialog .modal-dialog-button:default:pressed { + background-gradient-start: #436d9f; + background-gradient-end: #74a0d0; + } + + .login-dialog .modal-dialog-button:default:insensitive { + border-color: #666666; + color: #9f9f9f; + background-gradient-direction: none; + background-color: rgba(102, 102, 102, 0.15); + } + +-.login-dialog-message-warning, +-.login-dialog-message-info { ++.login-dialog-message { + padding-top: 4px; + padding-bottom: 16px; + min-height: 2em; + } + + .login-dialog-message-warning { + color: orange; + } + + .login-dialog-message-hint { +- padding-bottom: 16px; +- min-height: 2em; ++ padding-top: 0px; + } + + .user-widget-label { + font-size: 16pt; + font-weight: bold; + text-align: left; + padding-left: 15px; + text-shadow: black 0px 4px 3px 0px; + } + + /* Screen shield */ + + .screen-shield-background { + background: black; + } + + #lockDialogGroup { + background: #2e3436 url(noise-texture.png); + background-repeat: repeat; + } + + .screen-shield-arrows { + padding-bottom: 3em; + } + + .screen-shield-arrows Gjs_Arrow { + color: white; + width: 80px; + height: 48px; + -arrow-thickness: 12px; +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 674dd3a..2121f2e 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -9,62 +9,60 @@ const Panel = imports.ui.panel; + const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; + const Params = imports.misc.params; + const ShellEntry = imports.ui.shellEntry; + const Tweener = imports.ui.tweener; + const UserWidget = imports.ui.userWidget; + + const DEFAULT_BUTTON_WELL_ICON_SIZE = 24; + const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; + const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; + + const MESSAGE_FADE_OUT_ANIMATION_TIME = 0.5; + + const AuthPromptMode = { + UNLOCK_ONLY: 0, + UNLOCK_OR_LOG_IN: 1 + }; + + const AuthPromptStatus = { + NOT_VERIFYING: 0, + VERIFYING: 1, + VERIFICATION_FAILED: 2, + VERIFICATION_SUCCEEDED: 3 + }; + + const BeginRequestType = { + PROVIDE_USERNAME: 0, + DONT_PROVIDE_USERNAME: 1 + }; + +-let _messageStyleMap; +- + const AuthPrompt = new Lang.Class({ + Name: 'AuthPrompt', + + _init: function(gdmClient, mode) { + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + + this._gdmClient = gdmClient; + this._mode = mode; + + let reauthenticationOnly; + if (this._mode == AuthPromptMode.UNLOCK_ONLY) + reauthenticationOnly = true; + else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN) + reauthenticationOnly = false; + + this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly }); + + this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion)); + this._userVerifier.connect('show-message', Lang.bind(this, this._onShowMessage)); + this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); + this._userVerifier.connect('smartcard-status-changed', Lang.bind(this, this._onSmartcardStatusChanged)); + this.smartcardDetected = this._userVerifier.smartcardDetected; + + this.connect('next', Lang.bind(this, function() { + this.updateSensitivity(false); + this.startSpinning(); + if (this._queryingService) { + this._userVerifier.answerQuery(this._queryingService, this._entry.text); +@@ -82,61 +80,62 @@ const AuthPrompt = new Lang.Class({ + this.cancel(); + } + })); + + this._userWell = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this.actor.add(this._userWell, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this.actor.add(this._label, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + ShellEntry.addContextMenu(this._entry, { isPassword: true }); + + this.actor.add(this._entry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._entry.grab_key_focus(); + +- this._message = new St.Label({ opacity: 0 }); ++ this._message = new St.Label({ opacity: 0, ++ styleClass: 'login-dialog-message' }); + this._message.clutter_text.line_wrap = true; + this.actor.add(this._message, { x_fill: true, y_align: St.Align.START }); + + this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', + vertical: false }); + this.actor.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + + this._defaultButtonWell = new St.Widget({ layout_manager: new Clutter.BinLayout() }); + this._defaultButtonWellActor = null; + + this._initButtons(); + + let spinnerIcon = global.datadir + '/theme/process-working.svg'; + this._spinner = new Panel.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); + this._spinner.actor.opacity = 0; + this._spinner.actor.show(); + this._defaultButtonWell.add_child(this._spinner.actor); + }, + + _onDestroy: function() { + this._userVerifier.clear(); + this._userVerifier.disconnectAll(); + this._userVerifier = null; + }, + + _initButtons: function() { + this.cancelButton = new St.Button({ style_class: 'modal-dialog-button', +@@ -344,81 +343,76 @@ const AuthPrompt = new Lang.Class({ + this._label.show(); + this._entry.show(); + + this._entry.grab_key_focus(); + }, + + getAnswer: function() { + let text; + + if (this._preemptiveAnswer) { + text = this._preemptiveAnswer; + this._preemptiveAnswer = null; + } else { + text = this._entry.get_text(); + } + + return text; + }, + + _fadeOutMessage: function() { + if (this._message.opacity == 0) + return; + Tweener.removeTweens(this._message); + Tweener.addTween(this._message, + { opacity: 0, + time: MESSAGE_FADE_OUT_ANIMATION_TIME, + transition: 'easeOutQuad' + }); + }, + +- _initMessageStyleMap: function() { +- if (_messageStyleMap) +- return; +- +- _messageStyleMap = {}; +- _messageStyleMap[GdmUtil.MessageType.NONE] = ''; +- _messageStyleMap[GdmUtil.MessageType.ERROR] = 'login-dialog-message-warning'; +- _messageStyleMap[GdmUtil.MessageType.INFO] = 'login-dialog-message-info'; +- _messageStyleMap[GdmUtil.MessageType.HINT] = 'login-dialog-message-hint'; ++ setMessage: function(message, type) { ++ 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'); + +- setMessage: function(message, type) { +- this._initMessageStyleMap(); + if (message) { + Tweener.removeTweens(this._message); + this._message.text = message; +- this._message.styleClass = _messageStyleMap[type]; + this._message.opacity = 255; + } else { +- this._message.styleClass = null; + this._message.opacity = 0; + } + }, + + _updateNextButtonSensitivity: function(sensitive) { + this.nextButton.reactive = sensitive; + this.nextButton.can_focus = sensitive; + }, + + updateSensitivity: function(sensitive) { + this._updateNextButtonSensitivity(sensitive); + this._entry.reactive = sensitive; + this._entry.clutter_text.editable = sensitive; + }, + + hide: function() { + this.setActorInDefaultButtonWell(null, true); + this.actor.hide(); + this._message.opacity = 0; + + this.setUser(null); + + this.updateSensitivity(true); + this._entry.set_text(''); + }, + + setUser: function(user) { + if (user) { + let userWidget = new UserWidget.UserWidget(user); + this._userWell.set_child(userWidget.actor); +-- +1.8.3.1 + + +From 835b7a328ef9272c34a99a577f484224dc3e9f2d Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 27 Aug 2013 10:28:26 -0400 +Subject: [PATCH 64/66] theme: add bottom badding for login dialog hint + +Since we're getting rid of the top padding, we need to add what we +take away to the bottom padding to keep the size the same. + +https://bugzilla.gnome.org/show_bug.cgi?id=706670 +--- + data/theme/gnome-shell.css | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 32b6eab..71d7ec2 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2384,60 +2384,61 @@ StScrollBar StButton#vhandle:active { + .login-dialog .modal-dialog-button:default:hover { + background-gradient-start: #74a0d0; + background-gradient-end: #436d9f; + } + + .login-dialog .modal-dialog-button:default:active, + .login-dialog .modal-dialog-button:default:pressed { + background-gradient-start: #436d9f; + background-gradient-end: #74a0d0; + } + + .login-dialog .modal-dialog-button:default:insensitive { + border-color: #666666; + color: #9f9f9f; + background-gradient-direction: none; + background-color: rgba(102, 102, 102, 0.15); + } + + .login-dialog-message { + padding-top: 4px; + padding-bottom: 16px; + min-height: 2em; + } + + .login-dialog-message-warning { + color: orange; + } + + .login-dialog-message-hint { + padding-top: 0px; ++ padding-bottom: 20px; + } + + .user-widget-label { + font-size: 16pt; + font-weight: bold; + text-align: left; + padding-left: 15px; + text-shadow: black 0px 4px 3px 0px; + } + + /* Screen shield */ + + .screen-shield-background { + background: black; + } + + #lockDialogGroup { + background: #2e3436 url(noise-texture.png); + background-repeat: repeat; + } + + .screen-shield-arrows { + padding-bottom: 3em; + } + + .screen-shield-arrows Gjs_Arrow { + color: white; + width: 80px; + height: 48px; + -arrow-thickness: 12px; +-- +1.8.3.1 + + +From 521b48fc4fd4d8adaa630dc22e9df4282629b942 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 29 Aug 2013 11:46:56 -0400 +Subject: [PATCH 65/66] loginDialog: show session menu button when in auth + failed + +Right now we only show the session menu button when verifying, +but we should also show it when verification is failed or we +can end up in situation where the session menu disappears during +an authentication retry. + +https://bugzilla.gnome.org/show_bug.cgi?id=707064 +--- + js/gdm/loginDialog.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 1649e4e..9d5e243 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -571,61 +571,62 @@ const LoginDialog = new Lang.Class({ + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + this._showPrompt(); + }, + + _onReset: function(authPrompt, beginRequest) { + this._sessionMenuButton.updateSensitivity(true); + + this._user = null; + + if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { + if (!this._disableUserList) + this._showUserList(); + else + this._hideUserListAskForUsernameAndBeginVerification(); + } else { + this._hideUserListAndBeginVerification(); + } + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { +- if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING) ++ if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING && ++ this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFICATION_FAILED) + return false; + + if (this._user && this._user.is_loaded && this._user.is_logged_in()) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) + return; + this._authPrompt.actor.opacity = 0; + this._authPrompt.actor.show(); + Tweener.addTween(this._authPrompt.actor, + { opacity: 255, + time: _FADE_ANIMATION_TIME, + transition: 'easeOutQuad' }); + }, + + _showRealmLoginHint: function(realmManager, hint) { + if (!hint) + return; + + 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._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), AuthPrompt.MessageType.HINT); +-- +1.8.3.1 + + +From d1baf411e7a2f45f6c588dabbdb7db8226b7f63d Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 7 Nov 2013 23:15:39 -0500 +Subject: [PATCH 66/66] loginDialog: display banner message when + disable-user-list=true + +The login screen supports showing a banner message which admins +can use to mention login rules or disclaimers. + +This message only shows up currently if the user list is enabled. +Most people who want to show a banner message also want to disable +the user list. + +This commit moves the banner message to display when prompting to +the user instead of when showing the user list. + +It also moves the banner to a more aesthetically pleasing location +on screen, and adds a scrollbar if the message is too long. +--- + data/theme/gnome-shell.css | 10 ++++---- + js/gdm/loginDialog.js | 60 ++++++++++++++++++++++++++++++++++++++-------- + 2 files changed, 56 insertions(+), 14 deletions(-) + +diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css +index 71d7ec2..3727197 100644 +--- a/data/theme/gnome-shell.css ++++ b/data/theme/gnome-shell.css +@@ -2180,67 +2180,69 @@ StScrollBar StButton#vhandle:active { + .candidate-box:hover { + border-radius: 4px; + background-color: rgba(255,255,255,0.1); + } + .candidate-page-button-box { + height: 2em; + width: 80px; + } + + .vertical .candidate-page-button-box { + padding-top: 0.5em; + } + + .horizontal .candidate-page-button-box { + padding-left: 0.5em; + } + + .candidate-page-button-previous { + border-radius: 4px 0px 0px 4px; + } + + .candidate-page-button-next { + border-radius: 0px 4px 4px 0px; + } + + .candidate-page-button-icon { + icon-size: 1em; + } + + /* Login Dialog */ ++.login-dialog-banner-view { ++ padding-top: 10em; ++ height: 10em; ++} + + .login-dialog-banner { +- font-size: 10pt; +- font-weight: bold; +- text-align: center; ++ font-size: 9pt; + color: #666666; +- padding-bottom: 1em; ++ width: 30em; + } + + .login-dialog-title { + font-size: 14pt; + font-weight: bold; + color: #666666; + padding-bottom: 2em; + } + + .login-dialog { + /* Reset border and background */ + border: none; + background-color: transparent; + } + + .login-dialog-button-box { + spacing: 5px; + } + + .login-dialog-user-list-view { + -st-vfade-offset: 1em; + } + + .login-dialog-user-list { + spacing: 12px; + padding: .2em; + width: 23em; + } + + .login-dialog-user-list-item { +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 9d5e243..7b2b1d6 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -1,60 +1,61 @@ + // -*- 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 AccountsService = imports.gi.AccountsService; + const Atk = imports.gi.Atk; + const Clutter = imports.gi.Clutter; + const Gdm = imports.gi.Gdm; + const Gio = imports.gi.Gio; + const GLib = imports.gi.GLib; + const Gtk = imports.gi.Gtk; + const Lang = imports.lang; + const Mainloop = imports.mainloop; + const Meta = imports.gi.Meta; ++const Pango = imports.gi.Pango; + const Shell = imports.gi.Shell; + const Signals = imports.signals; + const St = imports.gi.St; + + const AuthPrompt = imports.gdm.authPrompt; + const Batch = imports.gdm.batch; + const BoxPointer = imports.ui.boxpointer; + const CtrlAltTab = imports.ui.ctrlAltTab; + const GdmUtil = imports.gdm.util; + const Layout = imports.ui.layout; + const Main = imports.ui.main; + const PopupMenu = imports.ui.popupMenu; + const Realmd = imports.gdm.realmd; + const Tweener = imports.ui.tweener; + const UserMenu = imports.ui.userMenu; + const UserWidget = imports.ui.userWidget; + + const _FADE_ANIMATION_TIME = 0.25; + const _SCROLL_ANIMATION_TIME = 0.5; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _LOGO_ICON_HEIGHT = 48; + + let _loginDialog = null; + + const UserListItem = new Lang.Class({ + Name: 'UserListItem', + + _init: function(user) { + this.user = user; + this._userChangedId = this.user.connect('changed', +@@ -366,155 +367,186 @@ const SessionMenuButton = new Lang.Class({ + ids.sort(); + + if (ids.length <= 1) { + this._button.hide(); + return; + } + + for (let i = 0; i < ids.length; i++) { + let [sessionName, sessionDescription] = Gdm.get_session_name_and_description(ids[i]); + + let id = ids[i]; + let item = new PopupMenu.PopupMenuItem(sessionName); + this._menu.addMenuItem(item); + this._items[id] = item; + + if (!this._activeSessionId) + this.setActiveSession(id); + + item.connect('activate', Lang.bind(this, function() { + this.setActiveSession(id); + })); + } + } + }); + Signals.addSignalMethods(SessionMenuButton.prototype); + + const LoginDialog = new Lang.Class({ + Name: 'LoginDialog', + + _init: function(parentActor) { ++ let table = new Clutter.TableLayout(); + this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, +- layout_manager: new Clutter.BinLayout(), ++ layout_manager: table, + style_class: 'login-dialog', + visible: false }); + + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + parentActor.add_child(this.actor); + + this._userManager = AccountsService.UserManager.get_default() + let gdmClient = new Gdm.Client(); + + if (GLib.getenv('GDM_GREETER_TEST') != '1') { + this._greeter = gdmClient.get_greeter_sync(null); + + this._greeter.connect('default-session-name-changed', + Lang.bind(this, this._onDefaultSessionChanged)); + + this._greeter.connect('session-opened', + Lang.bind(this, this._onSessionOpened)); + this._greeter.connect('timed-login-requested', + Lang.bind(this, this._onTimedLoginRequested)); + } + + this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA }); + + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY, + Lang.bind(this, this._updateBanner)); + this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY, + Lang.bind(this, this._updateDisableUserList)); + this._settings.connect('changed::' + GdmUtil.LOGO_KEY, + Lang.bind(this, this._updateLogo)); + + this._textureCache = St.TextureCache.get_default(); + this._textureCache.connect('texture-file-changed', + Lang.bind(this, this._updateLogoTexture)); + + this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, + x_expand: true, + y_expand: true, + vertical: true, + visible: false }); +- this.actor.add_child(this._userSelectionBox); +- +- this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', +- text: '' }); +- this._userSelectionBox.add(this._bannerLabel); +- this._updateBanner(); +- + this._userList = new UserList(); + this._userSelectionBox.add(this._userList.actor, + { expand: true, + x_fill: true, + y_fill: true }); + + this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted)); + this._authPrompt.connect('reset', Lang.bind(this, this._onReset)); + this._authPrompt.hide(); +- this.actor.add_child(this._authPrompt.actor); ++ this._authPrompt.actor.x_align = Clutter.ActorAlign.CENTER; ++ this._authPrompt.actor.y_align = Clutter.ActorAlign.CENTER; ++ this._authPrompt.actor.x_expand = false; ++ this._authPrompt.actor.y_expand = false; ++ table.pack(this._authPrompt.actor, 0, 0); ++ table.set_span(this._authPrompt.actor, 1, 3); + + // translators: this message is shown below the user list on the + // login screen. It can be activated to reveal an entry for + // manually entering the username. + let notListedLabel = new St.Label({ text: _("Not listed?"), + style_class: 'login-dialog-not-listed-label' }); + this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, + child: notListedLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + + this._notListedButton.connect('clicked', Lang.bind(this, this._hideUserListAskForUsernameAndBeginVerification)); + + this._notListedButton.hide(); + + this._userSelectionBox.add(this._notListedButton, + { expand: false, + x_align: St.Align.START, + x_fill: true }); + + this._logoBin = new St.Widget({ style_class: 'login-dialog-logo-bin', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END, + x_expand: true, + y_expand: true }); +- this.actor.add_child(this._logoBin); ++ ++ table.pack(this._userSelectionBox, 0, 0); ++ table.set_span(this._userSelectionBox, 1, 3); ++ ++ let outerBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.CENTER, ++ vertical: true }); ++ table.pack(outerBox, 0, 2); ++ table.set_span(outerBox, 1, 1); ++ this._bannerView = new St.ScrollView({ style_class: 'login-dialog-banner-view', ++ opacity: 0, ++ vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, ++ hscrollbar_policy: Gtk.PolicyType.NEVER }); ++ outerBox.add_actor(this._bannerView); ++ ++ let innerBox = new St.BoxLayout({ vertical: true }); ++ ++ this._bannerView.add_actor(innerBox); ++ this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner', ++ text: '' }); ++ this._bannerLabel.clutter_text.line_wrap = true; ++ this._bannerLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; ++ innerBox.add_child(this._bannerLabel); ++ this._updateBanner(); ++ ++ table.pack(this._logoBin, 0, 2); ++ table.set_span(this._logoBin, 1, 1); + this._updateLogo(); + ++ // We have overlapping widgets to obtain the kind of centering we want. ++ // We need to make sure the widgets that take input are on top of the ++ // stack. ++ this.actor.set_child_above_sibling(this._userSelectionBox, null); ++ this.actor.set_child_above_sibling(this._authPrompt.actor, null); ++ + if (!this._userManager.is_loaded) + this._userManagerLoadedId = this._userManager.connect('notify::is-loaded', + Lang.bind(this, function() { + if (this._userManager.is_loaded) { + this._loadUserList(); + this._userManager.disconnect(this._userManagerLoadedId); + this._userManagerLoadedId = 0; + } + })); + else + this._loadUserList(); + + this._userList.connect('activate', + Lang.bind(this, function(userList, item) { + this._onUserListActivated(item); + })); + + + this._sessionMenuButton = new SessionMenuButton(); + this._sessionMenuButton.connect('session-activated', + Lang.bind(this, function(list, sessionId) { + this._greeter.call_select_session_sync (sessionId, null); + })); + this._sessionMenuButton.actor.opacity = 0; + this._sessionMenuButton.actor.show(); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); + + }, + + _updateDisableUserList: function() { +@@ -526,87 +558,101 @@ const LoginDialog = new Lang.Class({ + if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING) + this._authPrompt.reset(); + } + }, + + _updateCancelButton: function() { + let cancelVisible; + + // Hide the cancel button if the user list is disabled and we're asking for + // a username + if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING && this._disableUserList) + cancelVisible = false; + else + cancelVisible = true; + + this._authPrompt.cancelButton.visible = cancelVisible; + }, + + _updateBanner: function() { + let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); + let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); + + if (enabled && text) { + this._bannerLabel.set_text(text); + this._bannerLabel.show(); + } else { + this._bannerLabel.hide(); + } + }, + ++ _fadeInBannerView: function() { ++ Tweener.addTween(this._bannerView, ++ { opacity: 255, ++ time: _FADE_ANIMATION_TIME, ++ transition: 'easeOutQuad' }); ++ }, ++ ++ _hideBannerView: function() { ++ Tweener.removeTweens(this._bannerView); ++ this._bannerView.opacity = 0; ++ }, ++ + _updateLogoTexture: function(cache, uri) { + if (this._logoFileUri != uri) + return; + + this._logoBin.destroy_all_children(); + if (this._logoFileUri) + this._logoBin.add_child(this._textureCache.load_uri_async(this._logoFileUri, + -1, _LOGO_ICON_HEIGHT)); + }, + + _updateLogo: function() { + let path = this._settings.get_string(GdmUtil.LOGO_KEY); + + this._logoFileUri = path ? Gio.file_new_for_path(path).get_uri() : null; + this._updateLogoTexture(this._textureCache, this._logoFileUri); + }, + + _onPrompted: function() { + this._sessionMenuButton.updateSensitivity(true); ++ this._fadeInBannerView(); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + this._showPrompt(); + }, + + _onReset: function(authPrompt, beginRequest) { + this._sessionMenuButton.updateSensitivity(true); ++ this._hideBannerView(); + + this._user = null; + + if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { + if (!this._disableUserList) + this._showUserList(); + else + this._hideUserListAskForUsernameAndBeginVerification(); + } else { + this._hideUserListAndBeginVerification(); + } + }, + + _onDefaultSessionChanged: function(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + }, + + _shouldShowSessionMenuButton: function() { + if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING && + this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFICATION_FAILED) + return false; + + if (this._user && this._user.is_loaded && this._user.is_logged_in()) + return false; + + return true; + }, + + _showPrompt: function() { + if (this._authPrompt.actor.visible) +-- +1.8.3.1 + diff --git a/SOURCES/org.gnome.shell.gschema.override b/SOURCES/org.gnome.shell.gschema.override new file mode 100644 index 0000000..a8646b4 --- /dev/null +++ b/SOURCES/org.gnome.shell.gschema.override @@ -0,0 +1,2 @@ +[org.gnome.shell] +always-show-log-out=true diff --git a/SPECS/gnome-shell.spec b/SPECS/gnome-shell.spec new file mode 100644 index 0000000..1039583 --- /dev/null +++ b/SPECS/gnome-shell.spec @@ -0,0 +1,836 @@ +Name: gnome-shell +Version: 3.8.4 +Release: 14%{?dist} +Summary: Window management and application launching for GNOME + +Group: User Interface/Desktops +License: GPLv2+ +Provides: desktop-notification-daemon +URL: http://live.gnome.org/GnomeShell +#VCS: git:git://git.gnome.org/gnome-shell +Source0: http://download.gnome.org/sources/gnome-shell/3.8/%{name}-%{version}.tar.xz +Source1: org.gnome.shell.gschema.override + +# Replace Epiphany with Firefox in the default favourite apps list +Patch1: gnome-shell-favourite-apps-firefox.patch +Patch2: gnome-shell-favourite-apps-yelp.patch + +Patch10: 0001-popupMenu-Fix-removing-the-active-menu-from-PopupMen.patch +Patch11: 0001-catch-more-errors-on-extensions-enable-and-disable.patch + +Patch30: 0001-network-Do-not-use-timestamp-to-identify-connections.patch +Patch31: 0001-network-Don-t-disable-switch-in-wifi-section-while-c.patch + +Patch60: 0001-main-Actually-respect-hasWorkspaces.patch + +Patch70: 0001-screenshot-Extend-ScreenshotArea-parameter-validatio.patch +Patch71: 0002-screencast-Fix-disabling-screencasts-via-session-mod.patch +Patch72: 0003-screencast-Validate-parameters-of-ScreencastArea.patch + +Patch80: 0001-shellDBus-Add-a-DBus-method-to-load-a-single-extensi.patch +Patch81: 0002-extensions-Add-a-SESSION_MODE-extension-type.patch + +Patch99: login-screen-backport.patch +Patch100: gdm-support-pre-authenticated-logins-from-oVirt.patch + +%define clutter_version 1.13.4 +%define gnome_bluetooth_version 3.5.5 +%define gobject_introspection_version 0.10.1 +%define gjs_version 1.35.4 +%define mutter_version 3.8.3 +%define eds_version 3.5.3 +%define gnome_desktop_version 3.7.90 +%define gnome_menus_version 3.5.3 +%define json_glib_version 0.13.2 +%define gsettings_desktop_schemas_version 3.7.4 +%define caribou_version 0.4.8 +%define libcroco_version 0.6.8 +%define telepathy_logger_version 0.2.6 + +## Needed when we re-autogen +BuildRequires: autoconf >= 2.53 +BuildRequires: automake >= 1.10 +BuildRequires: gnome-common >= 2.2.0 +BuildRequires: libtool >= 1.4.3 +BuildRequires: caribou-devel >= %{caribou_version} +BuildRequires: chrpath +BuildRequires: clutter-devel >= %{clutter_version} +BuildRequires: dbus-glib-devel +BuildRequires: desktop-file-utils +BuildRequires: evolution-data-server-devel >= %{eds_version} +BuildRequires: gcr-devel +BuildRequires: gjs-devel >= %{gjs_version} +BuildRequires: glib2-devel +BuildRequires: gnome-menus-devel >= %{gnome_menus_version} +BuildRequires: gobject-introspection >= %{gobject_introspection_version} +BuildRequires: json-glib-devel >= %{json_glib_version} +BuildRequires: upower-devel +BuildRequires: libgnome-keyring-devel +BuildRequires: libnm-gtk-devel +BuildRequires: NetworkManager-glib-devel +BuildRequires: polkit-devel +BuildRequires: startup-notification-devel +BuildRequires: telepathy-glib-devel +BuildRequires: telepathy-logger-devel >= %{telepathy_logger_version} +# for screencast recorder functionality +BuildRequires: gstreamer1-devel +BuildRequires: gtk3-devel +BuildRequires: intltool +BuildRequires: libcanberra-devel +BuildRequires: libcroco-devel >= %{libcroco_version} + +# for barriers +BuildRequires: libXfixes-devel >= 5.0 +# used in unused BigThemeImage +BuildRequires: librsvg2-devel +BuildRequires: mutter-devel >= %{mutter_version} +BuildRequires: pulseaudio-libs-devel +%ifnarch s390 s390x ppc ppc64 ppc64p7 +BuildRequires: gnome-bluetooth-libs-devel >= %{gnome_bluetooth_version} +%endif +BuildRequires: control-center +# Bootstrap requirements +BuildRequires: gtk-doc gnome-common +%ifnarch s390 s390x +Requires: gnome-bluetooth%{?_isa} >= %{gnome_bluetooth_version} +%endif +Requires: gnome-desktop3 >= %{gnome_desktop_version} +Requires: gnome-menus%{?_isa} >= %{gnome_menus_version} +# wrapper script uses to restart old GNOME session if run --replace +# from the command line +Requires: gobject-introspection%{?_isa} >= %{gobject_introspection_version} +Requires: gjs%{?_isa} >= %{gjs_version} +# needed for loading SVG's via gdk-pixbuf +Requires: librsvg2%{?_isa} +# needed as it is now split from Clutter +Requires: json-glib%{?_isa} >= %{json_glib_version} +Requires: mutter%{?_isa} >= %{mutter_version} +Requires: upower%{?_isa} +Requires: polkit%{?_isa} >= 0.100 +Requires: gnome-desktop3%{?_isa} >= %{gnome_desktop_version} +Requires: gsettings-desktop-schemas%{?_isa} >= %{gsettings_desktop_schemas_version} +Requires: libcroco%{?_isa} >= %{libcroco_version} +Requires: telepathy-logger%{?_isa} >= %{telepathy_logger_version} +# needed for schemas +Requires: at-spi2-atk%{?_isa} +# needed for on-screen keyboard +Requires: caribou%{?_isa} >= %{caribou_version} +# needed for the user menu +Requires: accountsservice-libs%{?_isa} +Requires: gdm-libs%{?_isa} +Requires: clutter%{?_isa} >= %{clutter_version} +# needed for screen recording +Requires: gstreamer1-plugins-good + +%description +GNOME Shell provides core user interface functions for the GNOME 3 desktop, +like switching to windows and launching applications. GNOME Shell takes +advantage of the capabilities of modern graphics hardware and introduces +innovative user interface concepts to provide a visually attractive and +easy to use experience. + +%package browser-plugin +Summary: Browser plugin to install extensions from extensions.gnome.org +Requires: %{name} = %{version}-%{release} +Requires: mozilla-filesystem%{?_isa} + +%description browser-plugin +The "GNOME Shell Integration" plugin provides integration with +Gnome Shell for live extension enabling and disabling. It can +be used only by extensions.gnome.org. + +%prep +%setup -q +%patch1 -p1 -b .firefox +%patch2 -p1 -b .yelp + +%patch10 -p1 -b .fix-popup-menu-manager-exception +%patch11 -p1 -b .catch-extension-errors + +%patch30 -p1 -b .fix-stuck-network-icon +%patch31 -p1 -b .keep-wifi-switch + +%patch60 -p1 -b .support-has-workspaces + +%patch70 -p1 -b .validate-screenshot-params +%patch71 -p1 -b .fix-disable-screencasts +%patch72 -p1 -b .validate-screencast-params + +%patch80 -p1 +%patch81 -p1 + +%patch99 -p1 -b .login-screen-backport +%patch100 -p1 -b .gdm-support-pre-authenticated-logins-from-oVirt + +%build +autoreconf -f +(if ! test -x configure; then NOCONFIGURE=1 ./autogen.sh; fi; + %configure --disable-static --disable-compile-warnings) +make V=1 %{?_smp_mflags} + +%install +make install DESTDIR=$RPM_BUILD_ROOT + +rm -rf %{buildroot}/%{_libdir}/mozilla/plugins/*.la + +desktop-file-validate %{buildroot}%{_datadir}/applications/gnome-shell.desktop +desktop-file-validate %{buildroot}%{_datadir}/applications/gnome-shell-extension-prefs.desktop +desktop-file-validate %{buildroot}%{_datadir}/applications/evolution-calendar.desktop + +cp %{SOURCE1} %{buildroot}/%{_datadir}/glib-2.0/schemas + +%find_lang %{name} + +%ifnarch s390 s390x ppc ppc64 ppc64p7 +# The libdir rpath breaks nvidia binary only folks, so we remove it. +# See bug 716572 +# skip on s390(x), workarounds a chrpath issue +chrpath -r %{_libdir}/gnome-shell:%{_libdir}/gnome-bluetooth $RPM_BUILD_ROOT%{_bindir}/gnome-shell +chrpath -r %{_libdir}/gnome-bluetooth $RPM_BUILD_ROOT%{_libdir}/gnome-shell/libgnome-shell.so +%endif + +%preun +glib-compile-schemas --allow-any-name %{_datadir}/glib-2.0/schemas &> /dev/null ||: + +%posttrans +glib-compile-schemas --allow-any-name %{_datadir}/glib-2.0/schemas &> /dev/null ||: + +%files -f %{name}.lang +%doc COPYING README +%{_bindir}/gnome-shell +%{_bindir}/gnome-shell-extension-tool +%{_bindir}/gnome-shell-perf-tool +%{_bindir}/gnome-shell-extension-prefs +%{_datadir}/glib-2.0/schemas/*.xml +%{_datadir}/glib-2.0/schemas/*.override +%{_datadir}/applications/gnome-shell.desktop +%{_datadir}/applications/gnome-shell-extension-prefs.desktop +%{_datadir}/applications/evolution-calendar.desktop +%{_datadir}/gnome-control-center/keybindings/50-gnome-shell-screenshot.xml +%{_datadir}/gnome-control-center/keybindings/50-gnome-shell-system.xml +%{_datadir}/gnome-shell/ +%{_datadir}/dbus-1/services/org.gnome.Shell.CalendarServer.service +%{_datadir}/dbus-1/services/org.gnome.Shell.HotplugSniffer.service +%{_datadir}/dbus-1/interfaces/org.gnome.Shell.Screencast.xml +%{_datadir}/dbus-1/interfaces/org.gnome.Shell.Screenshot.xml +%{_datadir}/dbus-1/interfaces/org.gnome.ShellSearchProvider.xml +%{_datadir}/dbus-1/interfaces/org.gnome.ShellSearchProvider2.xml +%{_libdir}/gnome-shell/ +%{_libexecdir}/gnome-shell-calendar-server +%{_libexecdir}/gnome-shell-perf-helper +%{_libexecdir}/gnome-shell-hotplug-sniffer +# Co own these directories instead of pulling in GConf +# after all, we are trying to get rid of GConf with these files +%dir %{_datadir}/GConf +%dir %{_datadir}/GConf/gsettings +%{_datadir}/GConf/gsettings/gnome-shell-overrides.convert +%{_mandir}/man1/%{name}.1.gz +# exclude as these should be in a devel package for st etc +%exclude %{_datadir}/gtk-doc + +%files browser-plugin +%{_libdir}/mozilla/plugins/*.so + +%changelog +* Fri Nov 8 2013 Rui Matos - 3.8.4-14 +- Add a DBus method to load a single extension + Resolves: rhbz#1028466 +- Add a SESSION_MODE extension type + Resolves: rhbz#1028462 + +* Fri Nov 08 2013 Ray Strode 3.8.4-13 +- Fix input regression caused by -12 + Related: #817594 + +* Thu Nov 07 2013 Ray Strode 3.8.4-12 +- Show banner text when disable-user-list = TRUE + Resolves: #817594 + +* Mon Nov 04 2013 Florian Müllner - 3.8.4-11 +- Re-add yelp to default favorites - this downstream patch was + accidentally dropped when rebasing on F19 + Resolves: #958196 +- Fix network icon not updating + Resolves: #1009867 +- Do not hide on/off switch while connecting to wireless + Resolves: #908829 +- Fix apps menu not being disabled when open during screen lock + Resolves: #1027501 +- Be more defensive when enabling/disabling extensions + Resolves: #1027501 +- Re-enable browser plugin and move it to a subpackage + Resolves: #972783 + +* Fri Nov 01 2013 Florian Müllner - 3.8.4-10 +- Backport support for hasWorkspaces session mode property + Resolves: #1025444 +- Backport input validation of screenshot/screencast DBus methods + Resolves: #1022491 + +* Thu Oct 31 2013 Florian Müllner - 3.8.4-9 +- Backport switch to disable browser plugin +- Disable browser plugin +Resolves: #972783 + +* Thu Oct 31 2013 Matthias Clasen 3.8.4-8 +- Add an override for always-show-log-out +- Resolves: #913566 + +* Tue Oct 15 2013 Vinzenz Feenstra 3.8.4-7 +- Backport of support for pre-authenticated logins from oVirt + Related: #854712 + +* Thu Aug 29 2013 Ray Strode 3.8.4-6 +- Another backported ui fix + Related: #854724 + +* Tue Aug 27 2013 Ray Strode 3.8.4-5 +- Backport some login screen ui updates + Related: #854724 + +* Thu Aug 22 2013 Ray Strode 3.8.4-4 +- Backport some more smartcard related fixes + Related: #854724 + +* Tue Aug 06 2013 Ray Strode 3.8.4-3 +- Backport some lock screen fixes related to smartcard support + from upstream + Related: #854724 + +* Wed Jul 31 2013 Ray Strode 3.8.4-2 +- Include support for smartcard authentication + Related: #854724 + +* Tue Jul 30 2013 Ray Strode - 3.8.4-1 +- Update to 3.8.4 + +* Wed Jun 26 2013 Florian Müllner - 3.8.3-3 +- Backport upstream patch that makes a common error non-fatal + +* Wed Jun 12 2013 Kalev Lember - 3.8.3-2 +- Rebuilt against fixed cogl (#973542) + +* Fri Jun 07 2013 Florian Müllner - 3.8.3-1 +- Update to 3.8.3, drop upstreamed patches + +* Wed May 22 2013 Matthias Clasen - 3.8.2-3 +- Drop the gnome-session-xsession dependency again, it + has unwanted side-effects on eg soas + +* Tue May 14 2013 Florian Müllner - 3.8.2-2 +- Include upstream fix for bug #962876 + +* Mon May 13 2013 Florian Müllner - 3.8.2-1 +- Update to 3.8.2 and drop upstreamed patches + +* Tue May 07 2013 Florian Müllner - 3.8.1-4 +- Add login-screen branding changes from gnome-3-8 branch as downstream patches + +* Wed May 01 2013 Kalev Lember - 3.8.1-2 +- Add missing telepathy-logger runtime dep +- Depend on gnome-session-xsession so that it gets pulled in for + typical GNOME installs + +* Tue Apr 16 2013 Florian Müllner - 3.8.1-1 +- Update to 3.8.1 + +* Thu Mar 28 2013 Adel Gadllah - 3.8.0.1-2 +- Ship the perf tool + +* Wed Mar 27 2013 Ray Strode - 3.8.0.1-1 +- Update to 3.8.0.1 + +* Tue Mar 26 2013 Florian Müllner - 3.8.0-1 +- Update to 3.8.0 + +* Wed Mar 26 2013 Florian Müllner +- Drop upstreamed patch + +* Wed Mar 20 2013 Ray Strode 3.7.92-2 +- Fix initial setup + +* Tue Mar 19 2013 Florian Müllner - 3.7.92-1 +- Update to 3.7.92 + +* Tue Mar 05 2013 Florian Müllner - 3.7.91-1 +- Update to 3.7.91 + +* Wed Feb 20 2013 Florian Müllner - 3.7.90-1 +- Update to 3.7.90 + +* Wed Feb 06 2013 Kalev Lember - 3.7.5-2 +- Rebuilt for libgcr soname bump + +* Wed Feb 06 2013 Florian Müllner - 3.7.5-1 +- Update to 3.7.5 + +* Fri Jan 25 2013 Peter Robinson 3.7.4.1-2 +- Rebuild for new cogl + +* Thu Jan 17 2013 Florian Müllner - 3.7.4.1-1 +- Update to 3.7.4.1 + +* Tue Jan 15 2013 Florian Müllner - 3.7.4-1 +- Update to 3.7.4 + +* Wed Jan 09 2013 Richard Hughes - 3.7.3.1-1 +- Update to 3.7.3.1 + +* Tue Dec 18 2012 Florian Müllner 3.7.3-1 +- Update to 3.7.3 + +* Mon Dec 17 2012 Adam Jackson 3.7.2-3 +- Also don't mangle rpath on power + +* Mon Dec 10 2012 Adam Jackson 3.7.2-2 +- Disable bluetooth on power + +* Mon Nov 19 2012 Florian Müllner - 3.7.2-1 +- Update to 3.7.2 + +* Tue Nov 13 2012 Dan Horák - 3.7.1-2 +- don't Require: gnome-bluetooth on s390(x) + +* Fri Nov 09 2012 Kalev Lember - 3.7.1-1 +- Update to 3.7.1 + +* Wed Oct 31 2012 Brian Pepple - 3.6.1-5 +- Rebuild against latest telepathy-logger + +* Thu Oct 25 2012 Milan Crha - 3.6.1-4 +- Rebuild against newer evolution-data-server + +* Sat Oct 20 2012 Dan Horák - 3.6.1-3 +- explicit BR: control-center as it isn't brought in indirectly on s390(x) + +* Thu Oct 18 2012 Florian Müllner - 3.6.1-2 +- Remove avoid-redhat-menus patch + + The standard way of supporting a desktop-specific menu layout is + to set XDG_MENU_PREFIX (which we made gnome-session do now). + +* Mon Oct 15 2012 Florian Müllner - 3.6.1-1 +- Update to 3.6.1 + +* Tue Sep 25 2012 Florian Müllner - 3.6.0-1 +- Update to 3.6.0 + +* Wed Sep 19 2012 Florian Müllner - 3.5.92-1 +- Update to 3.5.92 + +* Tue Sep 11 2012 Florian Müllner - 3.5.91-1 +- Update dependencies + +* Tue Sep 04 2012 Richard Hughes - 3.5.91-1 +- Update to 3.5.91 + +* Tue Aug 28 2012 Matthias Clasen - 3.5.90-3 +- Rebuild against new cogl/clutter + +* Mon Aug 27 2012 Debarshi Ray - 3.5.90-2 +- Rebuild for new libcamel and synchronize gnome-bluetooth Requires with + BuildRequires. + +* Wed Aug 22 2012 Richard Hughes - 3.5.90-1 +- Update to 3.5.90 + +* Tue Aug 14 2012 Debarshi Ray - 3.5.5-2 +- Add Requires: gnome-bluetooth >= 3.5.5 + +* Mon Aug 13 2012 Debarshi Ray - 3.5.5-1 +- Update to 3.5.5 + +* Fri Jul 27 2012 Fedora Release Engineering - 3.5.4-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Sat Jul 21 2012 Kalev Lember - 3.5.4-4 +- Tighten runtime requires + +* Thu Jul 19 2012 Matthias Clasen - 3.5.4-3 +- Add a gdm-libs dependency + +* Wed Jul 18 2012 Colin Walters - 3.5.4-2 +- Bump release + +* Wed Jul 18 2012 Ray Strode 3.5.4-1 +- Update to 3.5.4 + +* Tue Jun 26 2012 Matthias Clasen - 3.5.3-2 +- Rebuild against new e-d-s + +* Tue Jun 26 2012 Matthias Clasen - 3.5.3-1 +- Update to 3.5.3 + +* Thu Jun 07 2012 Richard Hughes - 3.5.2-2 +- Remove upstreamed patch + +* Thu Jun 07 2012 Richard Hughes - 3.5.2-1 +- Update to 3.5.2 + +* Mon May 28 2012 Peter Robinson - 3.4.1-6 +- Cherry pick F17 changes, bump build for new evo soname + +* Wed May 16 2012 Owen Taylor - 3.4.1-5 +- New version of unmount notification + +* Tue May 15 2012 Owen Taylor - 3.4.1-4 +- Add a patch to display a notification until it's safe to remove a drive (#819492) + +* Fri Apr 20 2012 Owen Taylor - 3.4.1-3 +- Add a patch from upstream to avoid a crash when Evolution is not installed (#814401) + +* Wed Apr 18 2012 Kalev Lember - 3.4.1-2 +- Silence glib-compile-schemas scriplets + +* Wed Apr 18 2012 Kalev Lember - 3.4.1-1 +- Update to 3.4.1 + +* Thu Apr 5 2012 Owen Taylor - 3.4.0-2 +- Change gnome-shell-favourite-apps-firefox.patch to also patch the JS code + to handle the transition from mozilla-firefox.desktop to firefox.desktop. + (#808894, reported by Jonathan Kamens) + +* Tue Mar 27 2012 Richard Hughes - 3.4.0-1 +- Update to 3.4.0 + +* Wed Mar 21 2012 Matthias Clasen - 3.3.92-1 +- Update to 3.3.92 + +* Sat Mar 10 2012 Matthias Clasen - 3.3.90-2 +- Rebuild for new cogl + +* Sat Feb 26 2012 Matthias Clasen - 3.3.90-1 +- Update to 3.3.90 + +* Thu Feb 9 2012 Matthias Clasen - 3.3.5-2 +- Depend on accountsservice-libs (#755112) + +* Tue Feb 7 2012 Matthias Clasen - 3.3.5-1 +- Update to 3.3.5 + +* Fri Jan 20 2012 Matthias Clasen - 3.3.4-1 +- Update to 3.3.4 + +* Thu Jan 19 2012 Matthias Clasen - 3.3.3-2 +- Rebuild for new cogl + +* Thu Jan 5 2012 Matthias Clasen - 3.3.3-1 +- Update to 3.3.3 + +* Sun Nov 27 2011 Peter Robinson - 3.3.2-2 +- Rebuild for new clutter and e-d-s + +* Wed Nov 23 2011 Matthias Clasen - 3.3.2-1 +- Update to 3.3.2 + +* Wed Nov 09 2011 Kalev Lember - 3.2.1-6 +- Adapt to firefox desktop file name change in F17 + +* Thu Nov 03 2011 Adam Jackson 3.2.1-5 +- Build with -Wno-error=disabled-declarations for the moment + +* Wed Nov 02 2011 Brian Pepple - 3.2.1-4 +- Rebuld against tp-logger. + +* Sun Oct 26 2011 Bruno Wolff III - 3.2.1-3 +- Rebuild for new evolution-data-server + +* Wed Oct 26 2011 Fedora Release Engineering - 3.2.1-2 +- Rebuilt for glibc bug#747377 + +* Wed Oct 19 2011 Matthias Clasen - 3.2.1-1 +- Update to 3.2.1 + +* Wed Sep 28 2011 Ray Strode 3.2.0-2 +- rebuild + +* Mon Sep 26 2011 Owen Taylor - 3.2.0-1 +- Update to 3.2.0 + +* Tue Sep 20 2011 Matthias Clasen - 3.1.92-1 +- Update to 3.1.92 + +* Fri Sep 16 2011 Kalev Lember - 3.1.91.1-2 +- Tighten dependencies by specifying the required arch (#739130) + +* Wed Sep 14 2011 Owen Taylor - 3.1.91.1-1 +- Update to 3.1.91.1 (adds browser plugin) + Update Requires + +* Thu Sep 08 2011 Dan Horák - 3.1.91-3 +- workaround a chrpath issue on s390(x) + +* Wed Sep 07 2011 Kalev Lember - 3.1.91-2 +- Replace Epiphany with Firefox in the default favourite apps + +* Wed Sep 7 2011 Matthias Clasen - 3.1.91-1 +- Update to 3.1.91 + +* Thu Sep 1 2011 Matthias Clasen - 3.1.90.1-2 +- Require caribou + +* Wed Aug 31 2011 Matthias Clasen - 3.1.90.1-1 +- Update to 3.1.90.1 + +* Wed Aug 31 2011 Adam Williamson - 3.1.4-3.gite7b9933 +- rebuild against e-d-s + +* Fri Aug 19 2011 Matthias Clasen - 3.1.4-2.gite7b9933 +- git snapshot that builds against gnome-menus 3.1.5 + +* Thu Aug 18 2011 Matthew Barnes - 3.1.5-1 +- Rebuild against newer eds libraries. + +* Wed Jul 27 2011 Matthias Clasen - 3.1.4-1 +- Update to 3.1.4 + +* Wed Jul 27 2011 Matthias Clasen - 3.1.3-4 +- Rebuild + +* Tue Jul 26 2011 Matthias Clasen - 3.1.3-3 +- Add necessary requires + +* Mon Jul 25 2011 Matthias Clasen - 3.1.3-2 +- Rebuild + +* Tue Jul 5 2011 Peter Robinson - 3.1.3-1 +- Upstream 3.1.3 dev release + +* Mon Jun 27 2011 Adam Williamson - 3.0.2-4 +- add fixes from f15 branch (gjs dep and rpath) + +* Wed Jun 22 2011 Owen Taylor - 3.0.2-3 +- Add a patch from upstream to avoid g_file_get_contents() + +* Fri Jun 17 2011 Tomas Bzatek - 3.0.2-2 +- Rebuilt for new gtk3 and gnome-desktop3 + +* Wed May 25 2011 Owen Taylor - 3.0.2-1 +- Update to 3.0.2 + +* Tue May 10 2011 Dan Williams - 3.0.1-4 +- Fix initial connections to WPA Enterprise access points (#699014) +- Fix initial connections to mobile broadband networks + +* Thu Apr 28 2011 Dan Horák - 3.0.1-3 +- no bluetooth on s390(x) + +* Wed Apr 27 2011 Owen Taylor - 3.0.1-2 +- Add a patch from upstream to fix duplicate applications in application display + +* Mon Apr 25 2011 Owen Taylor - 3.0.1-1 +- Update to 3.0.1 + +* Mon Apr 11 2011 Colin Walters - 3.0.0.2-2 +- We want to use the GNOME menus which has the designed categories, + not the legacy redhat-menus. + +* Fri Apr 08 2011 Nils Philippsen - 3.0.0.2-1 +- Update to 3.0.0.2 (fixes missing import that was preventing extensions from + loading.) +- Update source URL + +* Tue Apr 5 2011 Owen Taylor - 3.0.0.1-1 +- Update to 3.0.0.1 (fixes bug where network menu could leave + Clutter event handling stuck.) + +* Mon Apr 4 2011 Owen Taylor - 3.0.0-1 +- Update to 3.0.0 + +* Tue Mar 29 2011 Brian Pepple - 2.91.93-3 +- Bump + +* Tue Mar 29 2011 Brian Pepple - 2.91.93-2 +- Rebuild for new tp-logger + +* Mon Mar 28 2011 Owen Taylor - 2.91.93-1 +- Update to 2.91.93. + +* Fri Mar 25 2011 Ray Strode 2.91.92-3 +- Adjustments for More nm-client api changes. +- Fix VPN indicator + +* Thu Mar 24 2011 Christopher Aillon - 2.91.92-2 +- Make activating vpn connections work from the shell indicator + +* Wed Mar 23 2011 Matthias Clasen - 2.91.92-1 +- Update to 2.91.92 + +* Wed Mar 16 2011 Michel Salim - 2.91.91-2 +- Fix alt-tab behavior on when primary display is not leftmost (# 683932) + +* Tue Mar 8 2011 Owen Taylor - 2.91.91-1 +- Update to 2.91.91 + +* Tue Feb 22 2011 Matthias Clasen - 2.91.90-2 +- Require upower and polkit at runtime + +* Tue Feb 22 2011 Matthias Clasen - 2.91.90-1 +- Update to 2.91.90 + +* Thu Feb 10 2011 Matthias Clasen - 2.91.6-6 +- Rebuild against newer gtk + +* Tue Feb 08 2011 Fedora Release Engineering - 2.91.6-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Thu Feb 3 2011 Bill Nottingham - 2.91.6-4 +- buildrequire gnome-bluetooth to fix bluetooth status icon (#674874) + +* Wed Feb 2 2011 Matthias Clasen - 2.91.6-3 +- Rebuild against newer gtk + +* Tue Feb 1 2011 Owen Taylor - 2.91.6-2 +- Build-requires evolution-data-server-devel + +* Tue Feb 1 2011 Owen Taylor - 2.91.6-1 +- Update to 2.91.6 + +* Thu Jan 13 2011 Mattihas Clasen - 2.91.5-3 +- Drop desktop-effects dependency + +* Wed Jan 12 2011 Colin Walters - 2.91.5-2 +- BR latest g-i, handles flags as arguments better + +* Tue Jan 11 2011 Matthias Clasen - 2.91.5-1 +- Update to 2.91.5 + +* Sat Jan 8 2011 Matthias Clasen - 2.91.4-1 +- Update to 2.91.4 +- Rebuild against new gtk + +* Fri Dec 3 2010 Matthias Clasen - 2.91.3-2 +- Rebuild aginst new gtk + +* Mon Nov 29 2010 Owen Taylor - 2.91.2-1 +- Update to 2.91.3 + +* Thu Nov 18 2010 Owen Taylor - 2.91.2-3 +- Add another memory-management crasher fix from upstream + +* Mon Nov 15 2010 Owen Taylor - 2.91.2-2 +- Add a patch from upstream fixing a memory-management crasher + +* Tue Nov 9 2010 Owen Taylor - 2.91.2-1 +- Update to 2.91.2 + +* Mon Nov 1 2010 Owen Taylor - 2.91.1-1 +- Update to 2.91.1 +- Add libcroco-devel to BuildRequires, apparently it was getting + pulled in indirectly before +- Add libcanberra-devel and pulseaudio-libs-devel BuildRequires + +* Mon Oct 4 2010 Owen Taylor - 2.91.0-1 +- Update to 2.91.0 +- Remove patch to disable VBlank syncing + +* Thu Aug 12 2010 Colin Walters - 2.31.5-7 +- Add patch to disable vblank syncing + +* Tue Jul 13 2010 Colin Walters - 2.31.5-5 +- Run glib-compile-schemas + +* Tue Jul 13 2010 Colin Walters - 2.31.5-4 +- Bless stuff in files section + +* Tue Jul 13 2010 Colin Walters - 2.31.5-3 +- Axe gnome-desktop-devel + +* Tue Jul 13 2010 Adel Gadllah - 2.31.5-2 +- BuildRequire gnome-desktop3-devel, gtk3 + +* Mon Jul 12 2010 Colin Walters - 2.31.5-1 +- New upstream version +- Drop rpath goop, shouldn't be necessary any more + +* Fri Jun 25 2010 Colin Walters - 2.31.2-3 +- Drop gir-repository-devel build dependency + +* Fri May 28 2010 Adam Miller - 2.31.2-2 +- Added new version requirements for dependencies based on upstream releases +- Added new file listings for gnome-shell-clock-preferences binary and .desktop +- Added gnome-shell man page file listing + +* Wed May 26 2010 Adam Miller - 2.31.2-1 +- New upstream release + +* Fri Mar 26 2010 Colin Walters - 2.29.1-3 +- Specify V=1 for build, readd smp_mflags since parallel is fixed upstream + +* Thu Mar 25 2010 Adam Miller - 2.29.1-2 +- Bumped for new version of mutter and clutter +- Added version requirement to gjs-devel because of dependency of build + +* Wed Mar 24 2010 Adam Miller - 2.29.1-1 +- Update to latest version 2.29.1 + +* Sun Feb 21 2010 Bastien Nocera 2.28.1-0.2.20100128git +- Require json-glib +- Rebuild for new clutter with json split out +- Fix deprecation in COGL + +* Thu Jan 28 2010 Adam Miller - 2.28.1-0.1.20100128git +- New git snapshot +- Fixed Version for alphatag use + +* Fri Jan 15 2010 Adam Miller - 2.28.0.20101015git-1 +- Added dependency on a git build of gobject-introspect to solve some breakage +- Also went ahead and made a new git tarball + +* Tue Jan 12 2010 Adam Miller - 2.28.0.20100112git-1 +- New git snapshot + +* Tue Dec 07 2009 Adam Miller - 2.28.0.20091206git-5 +- Added libtool, glib-gettext for the libtoolize dep of git snapshot + +* Mon Dec 07 2009 Adam Miller - 2.28.0.20091206git-4 +- Added gnome-common needed by autogen.sh in git snapshot build + +* Sun Dec 06 2009 Adam Miller - 2.28.0.20091206git-3 +- Added the autotools needed to build the git snapshot to the build requires + +* Sun Dec 06 2009 Adam Miller - 2.28.0.20091206git-2 +- Fixed the setup naming issue with the git snapshot directory naming + +* Sun Dec 06 2009 Adam Miller - 2.28.0.20091206git-1 +- Update to git snapshot on 20091206 + +* Wed Oct 7 2009 Owen Taylor - 2.28.0-2 +- Update to 2.28.0 + +* Tue Sep 15 2009 Owen Taylor - 2.27.3-1 +- Update to 2.27.3 + +* Fri Sep 4 2009 Owen Taylor - 2.27.2-2 +- Test for gobject-introspection version should be >= not > + +* Fri Sep 4 2009 Owen Taylor - 2.27.2-1 +- Update to 2.27.2 +- Add an explicit dep on gobject-introspection 0.6.5 which is required + for the new version + +* Sat Aug 29 2009 Owen Taylor - 2.27.1-4 +- Fix GConf %%preun script to properly be for package removal + +* Fri Aug 28 2009 Owen Taylor - 2.27.1-3 +- Replace libgnomeui with gnome-desktop in BuildRequires + +* Fri Aug 28 2009 Owen Taylor - 2.27.1-2 +- BuildRequire intltool +- Add find_lang + +* Fri Aug 28 2009 Owen Taylor - 2.27.1-1 +- Update to 2.27.1 +- Update Requires, add desktop-effects + +* Wed Aug 12 2009 Owen Taylor - 2.27.0-4 +- Add an explicit dependency on GConf2 for pre/post + +* Tue Aug 11 2009 Owen Taylor - 2.27.0-3 +- Add missing BuildRequires on gir-repository-devel + +* Tue Aug 11 2009 Owen Taylor - 2.27.0-2 +- Temporarily use a non-parallel-build until gnome-shell is fixed + +* Mon Aug 10 2009 Owen Taylor - 2.27.0-1 +- Initial version