[gnome-shell/wip/gdm-shell: 16/16] Add support for login session



commit c885b77be5122ab7d160a1c7dcbf338c65ec6597
Author: Ray Strode <rstrode redhat com>
Date:   Tue Aug 23 22:12:57 2011 -0400

    Add support for login session
    
    This commit adds support for login sessions.
    
    It provides a user list that talks to GDM,
    handles authentication via PAM, etc.
    
    It doesn't currently support fingerprint readers
    and smartcards.

 data/theme/gnome-shell.css |  104 +++++
 js/Makefile.am             |    1 +
 js/ui/loginDialog.js       | 1044 ++++++++++++++++++++++++++++++++++++++++++++
 js/ui/main.js              |   11 +
 js/ui/panel.js             |   26 +-
 5 files changed, 1181 insertions(+), 5 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index e8030b9..e4686f4 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -1864,3 +1864,107 @@ StTooltip StLabel {
 .magnifier-zoom-region.full-screen {
     border-width: 0px;
 }
+
+/* Login Dialog */
+
+.login-dialog-title {
+    font-size: 14pt;
+    font-weight: bold;
+    color: #666666;
+    padding-bottom: 2em;
+}
+
+.login-dialog {
+    border-radius: 16px;
+    min-height: 150px;
+    max-height: 700px;
+    min-width: 350px;
+}
+
+.login-dialog-user-list-view {
+    -st-vfade-offset: 1em;
+}
+
+.login-dialog-user-list {
+    spacing: 12px;
+}
+
+.login-dialog-user-list:rtl {
+}
+
+.login-dialog-user-list-item {
+    color: #666666;
+}
+
+.login-dialog-user-list-item:focus {
+}
+
+.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;
+    color: #666666;
+}
+
+.login-dialog-user-list-item:hover .login-dialog-user-list-item-name {
+    color: white;
+}
+
+.login-dialog-user-list-item:focus .login-dialog-user-list-item-name {
+    color: white;
+    text-shadow: black 0px 2px 2px;
+}
+
+.login-dialog-user-list-item-vertical-layout {
+    spacing: 2px;
+}
+
+.login-dialog-user-list-item .login-dialog-user-list-item-focus-bin {
+    background-color: rgba(0,0,0,0.0);
+    height: 2px;
+}
+
+.login-dialog-user-list-item:focus .login-dialog-user-list-item-focus-bin {
+    background-color: #666666;
+}
+
+.login-dialog-user-list-item-icon {
+    border: 2px solid #8b8b8b;
+    border-radius: 8px;
+    width: 64px;
+    height: 64px;
+}
+
+.login-dialog-not-listed-button {
+    padding-top: 2em;
+}
+.login-dialog-not-listed-label {
+    font-size: 14pt;
+    font-weight: bold;
+    color: #666666;
+}
+
+.login-dialog-prompt-layout {
+    padding-bottom: 64px;
+}
+.login-dialog-prompt-label {
+    color: white;
+    font-size: 20pt;
+}
+
+.login-dialog-prompt-entry {
+    padding: 4px;
+    border-radius: 4px;
+    border: 2px solid #5656cc;
+    color: black;
+    background-color: white;
+    caret-color: black;
+    caret-size: 1px;
+}
diff --git a/js/Makefile.am b/js/Makefile.am
index 1e2b1ea..de00cd7 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -34,6 +34,7 @@ nobase_dist_js_DATA = 	\
 	ui/layout.js		\
 	ui/lightbox.js		\
 	ui/link.js		\
+	ui/loginDialog.js	\
 	ui/lookingGlass.js	\
 	ui/magnifier.js		\
 	ui/magnifierDBus.js	\
diff --git a/js/ui/loginDialog.js b/js/ui/loginDialog.js
new file mode 100644
index 0000000..25dd412
--- /dev/null
+++ b/js/ui/loginDialog.js
@@ -0,0 +1,1044 @@
+/* -*- mode: js2; js2-basic-offset: 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 Lang = imports.lang;
+const Signals = imports.signals;
+
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+
+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 Mainloop = imports.mainloop;
+const Pango = imports.gi.Pango;
+const St = imports.gi.St;
+const Shell = imports.gi.Shell;
+const GdmGreeter = imports.gi.GdmGreeter;
+const AccountsService = imports.gi.AccountsService;
+
+const Batch = imports.misc.batch;
+const Dialog = imports.ui.dialog;
+const Lightbox = imports.ui.lightbox;
+const Main = imports.ui.main;
+const Tweener = imports.ui.tweener;
+
+const _DIALOG_ICON_SIZE = 64;
+const _FADE_ANIMATION_TIME = 0.16;
+const _RESIZE_ANIMATION_TIME = 0.25;
+const _SCROLL_ANIMATION_TIME = 2.0;
+const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;
+
+let _loginDialog = null;
+
+function _fadeInActor(actor) {
+    let hold = new Batch.Hold();
+
+    if (actor.opacity == 255 && actor.visible)
+      return null;
+
+    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() {
+                           actor.set_height(-1);
+                           hold.release();
+                       },
+                       onCompleteScope: this
+                     });
+    return hold;
+}
+
+function _fadeOutActor(actor) {
+    let hold = new Batch.Hold();
+
+    if (!actor.visible) {
+      actor.opacity = 0;
+      return null;
+    }
+
+    if (actor.opacity == 0) {
+        actor.hide();
+        return null;
+    }
+
+    Tweener.addTween(actor,
+                     { opacity: 0,
+                       height: 0,
+                       time: _FADE_ANIMATION_TIME,
+                       transition: 'easeOutQuad',
+                       onComplete: function() {
+                           actor.hide();
+                           actor.set_height(-1);
+                           hold.release();
+                       },
+                       onCompleteScope: this
+                     });
+    return hold;
+}
+
+function _smoothlyResizeActor(actor, width, height) {
+    let finalWidth;
+    let finalHeight;
+
+    if (width < 0) {
+        finalWidth = actor.width;
+    } else {
+        finalWidth = width;
+    }
+
+    if (height < 0) {
+        finalHeight = actor.height;
+    } else {
+        finalHeight = height;
+    }
+
+    actor.set_size(actor.width, actor.height);
+
+    if (actor.width == finalWidth &&
+        actor.height == finalHeight) {
+        return null;
+    }
+
+    let hold = new Batch.Hold();
+
+    Tweener.addTween(actor,
+                     { width: finalWidth,
+                       height: finalHeight,
+                       time: _RESIZE_ANIMATION_TIME,
+                       transition: 'easeOutQuad',
+                       onComplete: Lang.bind(this, function() {
+                                       hold.release();
+                                   })
+                     });
+    return hold;
+}
+
+function _freezeActorSize(actor) {
+    actor.set_size(actor.width, actor.height);
+}
+
+function _thawActorSize(actor) {
+    let oldWidth = actor.width;
+    let oldHeight = actor.height;
+    actor.set_size(-1, -1);
+    let [minHeight, naturalHeight] = actor.get_preferred_height(-1);
+    actor.set_size(oldWidth, oldHeight);
+
+    let batch = new Batch.ConsecutiveBatch(this,
+                                           [function() {
+                                                return _smoothlyResizeActor(actor, -1, naturalHeight);
+                                            },
+
+                                            function() {
+                                                actor.set_size(-1, -1);
+                                            }
+                                           ]);
+
+    return batch.run();
+}
+
+function ListItem(user, reason) {
+    this._init(user, reason);
+}
+
+ListItem.prototype = {
+    _init: function(user) {
+        this.user = user;
+        this._userChangedId = this.user.connect('changed',
+                                                 Lang.bind(this, this._onUserChanged));
+
+        this._verticalLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-vertical-layout',
+                                                  vertical:    true });
+
+        this.actor = new St.Button({ style_class: 'login-dialog-user-list-item',
+                                     can_focus:   true,
+                                     child:       this._verticalLayout,
+                                     reactive:    true,
+                                     x_align:     St.Align.START,
+                                     x_fill:      true });
+        let layout = new St.BoxLayout({ vertical: false });
+
+        this._verticalLayout.add(layout,
+                                 { y_fill: true,
+                                   x_fill: true,
+                                   expand: true });
+
+        this._focusBin = new St.Bin({ style_class: 'login-dialog-user-list-item-focus-bin' });
+        this._verticalLayout.add(this._focusBin,
+                                 { x_fill: false,
+                                   x_align: St.Align.MIDDLE,
+                                   y_fill: false,
+                                   expand: true });
+
+        this._iconBin = new St.Bin();
+        layout.add(this._iconBin);
+        let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box',
+                                            vertical:    true });
+        layout.add(textLayout,
+                   { y_fill: false,
+                     y_align: St.Align.MIDDLE,
+                     expand: true });
+
+        this._nameLabel = new St.Label({ text:        this.user.get_real_name(),
+                                         style_class: 'login-dialog-user-list-item-name' });
+        textLayout.add(this._nameLabel);
+
+        this._updateIcon();
+
+        this.actor.connect('clicked', Lang.bind(this, this._onClicked));
+    },
+
+    _onUserChanged: function() {
+        this._nameLabel.set_text(this.user.get_real_name());
+        this._updateIcon();
+    },
+
+    _setIconFromFile: function(iconFile, styleClass) {
+        if (styleClass)
+            this._iconBin.set_style_class_name(styleClass);
+        this._iconBin.set_style(null);
+
+        this._iconBin.child = null;
+        if (iconFile) {
+            this._iconBin.show();
+            this._iconBin.set_style('background-image: url("' + iconFile + '");');
+        } else {
+            this._iconBin.hide();
+        }
+    },
+
+    _setIconFromName: function(iconName, styleClass) {
+        if (styleClass)
+            this._iconBin.set_style_class_name(styleClass);
+        this._iconBin.set_style(null);
+
+        if (iconName != null) {
+            let gicon = new Gio.ThemedIcon({ name: iconName });
+            let icon = new St.Icon();
+            icon.set_gicon(gicon);
+
+            this._iconBin.child = icon;
+            this._iconBin.show();
+        } else {
+            this._iconBin.child = null;
+            this._iconBin.hide();
+        }
+    },
+
+    _updateIcon: function() {
+        let iconFileName = this.user.get_icon_file();
+        let gicon = null;
+
+        if (GLib.file_test(iconFileName, GLib.FileTest.EXISTS)) {
+            this._setIconFromFile(iconFileName, 'login-dialog-user-list-item-icon');
+        } else {
+            this._setIconFromName('avatar-default', 'login-dialog-user-list-item-icon');
+        }
+    },
+
+    _onClicked: function() {
+        this.emit('activate');
+    },
+
+    fadeOutName: function() {
+        return _fadeOutActor(this._nameLabel);
+    },
+
+    fadeInName: function() {
+        return _fadeInActor(this._nameLabel);
+    },
+
+    showFocusAnimation: function(time) {
+        let hold = new Batch.Hold();
+
+        let box = this._verticalLayout.get_allocation_box();
+
+        Tweener.removeTweens(this._focusBin);
+        this._focusBin.width = 0;
+        Tweener.addTween(this._focusBin,
+                         { width: box.x2 - box.x1,
+                           time: time,
+                           transition: 'linear',
+                           onComplete: function() {
+                               hold.release();
+                           },
+                           onCompleteScope: this
+                         });
+        return hold;
+    }
+
+};
+Signals.addSignalMethods(ListItem.prototype);
+
+function UserList() {
+    this._init.apply(this, arguments);
+}
+
+UserList.prototype = {
+    _init: function() {
+        this.actor = new St.ScrollView({ style_class: 'login-dialog-user-list-view'});
+        this.actor.set_policy(Gtk.PolicyType.NEVER,
+                              Gtk.PolicyType.AUTOMATIC);
+
+        this._layout = new St.BoxLayout({ vertical:    true,
+                                          style_class: 'login-dialog-user-list' });
+
+        this.actor.add_actor(this._layout,
+                             { x_fill:  true,
+                               y_fill:  true,
+                               x_align: St.Align.START,
+                               y_align: St.Align.MIDDLE });
+        this._items = {};
+    },
+
+    _showItem: function(item) {
+        let tasks = [function() {
+                         return _fadeInActor(item.actor);
+                     },
+
+                     function() {
+                         return item.fadeInName();
+                     }];
+
+        let batch = new Batch.ConsecutiveBatch(this, tasks);
+        return batch.run();
+    },
+
+    _onItemActivated: function(activatedItem) {
+        this.emit('activate', activatedItem);
+    },
+
+    giveUpWhitespace: function() {
+        let container = this.actor.get_parent();
+
+        container.child_set(this.actor, { expand: false });
+    },
+
+    takeOverWhitespace: function() {
+        let container = this.actor.get_parent();
+
+        container.child_set(this.actor, { expand: true });
+    },
+
+    freezeSize: function() {
+        return _freezeActorSize(this._layout);
+    },
+
+    thawSize: function() {
+        return _thawActorSize(this._layout);
+    },
+
+    hideItems: function(exception) {
+        let tasks = [];
+
+        for (let userName in this._items) {
+            let item = this._items[userName];
+            if (item != exception)
+                tasks.push(function() {
+                    return _fadeOutActor(item.actor);
+                });
+        }
+
+        let batch = new Batch.ConsecutiveBatch(this,
+                                               [function() {
+                                                    return _fadeOutActor(this.actor.vscroll);
+                                                },
+
+                                                new Batch.ConcurrentBatch(this, tasks)
+                                               ]);
+
+        return batch.run();
+    },
+
+    _getExpandedHeight: function() {
+        let hiddenActors = [];
+        for (let userName in this._items) {
+            let item = this._items[userName];
+            if (!item.actor.visible) {
+                item.actor.show();
+                hiddenActors.push(item.actor);
+            }
+        }
+
+        if (!this._layout.visible) {
+            this._layout.show();
+            hiddenActors.push(this._layout);
+        }
+
+        this._layout.set_size(-1, -1);
+        let [minHeight, naturalHeight] = this._layout.get_preferred_height(-1);
+
+        for (let i = 0; i < hiddenActors.length; i++) {
+            let actor = hiddenActors[i];
+            actor.hide();
+        }
+
+        return naturalHeight;
+    },
+
+    showItems: function() {
+        let tasks = [];
+
+        for (let userName in this._items) {
+            let item = this._items[userName];
+            tasks.push(function() {
+                return this._showItem(item);
+            });
+        }
+
+        let batch = new Batch.ConsecutiveBatch(this,
+                                               [function() {
+                                                    this.takeOverWhitespace();
+                                                },
+
+                                                function() {
+                                                    let fullHeight = this._getExpandedHeight();
+                                                    return _smoothlyResizeActor(this._layout, -1, fullHeight);
+                                                },
+
+                                                new Batch.ConcurrentBatch(this, tasks),
+
+                                                function() {
+                                                    this.actor.set_size(-1, -1);
+                                                },
+
+                                                function() {
+                                                    return _fadeInActor(this.actor.vscroll);
+                                                }]);
+        return batch.run();
+    },
+
+    scrollToItem: function(item) {
+        let box = item.actor.get_allocation_box();
+
+        let adjustment = this.actor.get_vscroll_bar().get_adjustment();
+
+        let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);
+        adjustment.interpolate(null, value, _SCROLL_ANIMATION_TIME * 1000);
+    },
+
+    jumpToItem: function(item) {
+        let box = item.actor.get_allocation_box();
+
+        let adjustment = this.actor.get_vscroll_bar().get_adjustment();
+
+        let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);
+
+        adjustment.set_value(value);
+    },
+
+    getItemFromUserName: function(userName) {
+        let item = this._items[userName];
+
+        if (!item)
+            return null;
+
+        return item;
+    },
+
+    addUser: function(user) {
+        if (!user.is_loaded)
+            return;
+
+        if (user.is_system_account())
+            return;
+
+        let userName = user.get_user_name();
+
+        if (!userName)
+            return;
+
+        this.removeUser(user);
+
+        let item = new ListItem(user);
+        this._layout.add(item.actor, { x_fill: true });
+
+        this._items[userName] = item;
+
+        item.connect('activate',
+                     Lang.bind(this, this._onItemActivated));
+
+        // Try to keep the focused item front-and-center
+        item.actor.connect('key-focus-in',
+                           Lang.bind(this,
+                                     function() {
+                                         this.scrollToItem(item);
+                                         item.showFocusAnimation(0);
+                                     }));
+
+        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);
+
+function LoginDialog() {
+    if (_loginDialog == null) {
+        this._init();
+        _loginDialog = this;
+    }
+
+    return _loginDialog;
+}
+
+LoginDialog.prototype = {
+    __proto__: Dialog.Dialog.prototype,
+
+    _init: function() {
+        Dialog.Dialog.prototype._init.call(this, { modal:      false,
+                                                   styleClass: 'login-dialog' });
+        this.connect('destroy',
+                     Lang.bind(this, this._onDestroy));
+        this.connect('opened',
+                     Lang.bind(this, this._onOpened));
+
+        this._userManager = AccountsService.UserManager.get_default()
+        this._greeterClient = new GdmGreeter.Client();
+
+        this._greeterClient.open_connection();
+
+        this._greeterClient.call_start_conversation('gdm-password');
+
+        this._greeterClient.connect('reset',
+                                    Lang.bind(this, this._onReset));
+        this._greeterClient.connect('info',
+                                    Lang.bind(this, this._onInfo));
+        this._greeterClient.connect('problem',
+                                    Lang.bind(this, this._onProblem));
+        this._greeterClient.connect('info-query',
+                                    Lang.bind(this, this._onInfoQuery));
+        this._greeterClient.connect('secret-info-query',
+                                    Lang.bind(this, this._onSecretInfoQuery));
+        this._greeterClient.connect('session-opened',
+                                    Lang.bind(this, this._onSessionOpened));
+        this._greeterClient.connect('timed-login-requested',
+                                    Lang.bind(this, this._onTimedLoginRequested));
+        this._greeterClient.connect('authentication-failed',
+                                    Lang.bind(this, this._onAuthenticationFailed));
+        this._greeterClient.connect('conversation-stopped',
+                                    Lang.bind(this, this._onConversationStopped));
+
+        this._titleLabel = new St.Label({ style_class: 'login-dialog-title',
+                                          text: _("Sign In") });
+
+        this.contentLayout.add(this._titleLabel,
+                              { y_fill:  false,
+                                y_align: St.Align.START });
+
+        let mainContentLayout = new St.BoxLayout({ vertical: false });
+        this.contentLayout.add(mainContentLayout,
+                               { expand: true,
+                                 x_fill: true,
+                                 y_fill: false });
+
+        this._userList = new UserList();
+        mainContentLayout.add(this._userList.actor,
+                              { expand: true,
+                                x_fill: true,
+                                y_fill: true });
+
+        this.setInitialKeyFocus(this._userList.actor);
+
+        this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
+                                                vertical:    true });
+        mainContentLayout.add(this._promptLayout,
+                              { expand: true,
+                                x_fill: true,
+                                y_fill: true,
+                                x_align: St.Align.START });
+        this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' });
+
+        this._mainContentLayout = mainContentLayout;
+
+        this._promptLayout.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' });
+        this._promptLayout.add(this._promptEntry,
+                              { expand: true,
+                                x_fill: true,
+                                y_fill: false,
+                                x_align: St.Align.START});
+        this._promptLayout.hide();
+
+        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',
+                                                can_focus:   true,
+                                                child:       notListedLabel,
+                                                reactive:    true,
+                                                x_align:     St.Align.START,
+                                                x_fill:      true });
+
+        this._notListedButton.connect('clicked', Lang.bind(this, this._onNotListedClicked));
+
+        this.contentLayout.add(this._notListedButton,
+                               { expand: false,
+                                 x_align: St.Align.START,
+                                 x_fill: true });
+
+        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);
+                               }));
+
+    },
+
+    _onReset: function(client, serviceName) {
+        this._greeterClient.call_start_conversation('gdm-password');
+
+        let tasks = [this._hidePrompt,
+
+                     new Batch.ConcurrentBatch(this, [this._fadeInTitleLabel,
+                                                      this._fadeInNotListedButton]),
+
+                     function() {
+                         this._userList.actor.show();
+                         this._userList.actor.opacity = 255;
+                         return this._userList.showItems();
+                     },
+
+                     function() {
+                         this._userList.actor.grab_key_focus();
+                     },
+                    ];
+
+        let batch = new Batch.ConsecutiveBatch(this, tasks);
+
+        batch.run();
+    },
+
+    _onInfo: function(client, serviceName, info) {
+        Main.notifyError(info);
+    },
+
+    _onProblem: function(client, serviceName, problem) {
+        Main.notifyError(problem);
+    },
+
+    _onCancel: function(client) {
+        this._greeterClient.call_cancel();
+    },
+
+    _fadeInPrompt: function() {
+        let tasks = [function() {
+                         return _fadeInActor(this._promptLabel);
+                     },
+
+                     function() {
+                         return _fadeInActor(this._promptEntry);
+                     },
+
+                     function() {
+                         return _fadeInActor(this._promptLayout);
+                     },
+
+                     function() {
+                         this._promptEntry.grab_key_focus();
+                     }];
+
+        let batch = new Batch.ConcurrentBatch(this, tasks);
+        return batch.run();
+    },
+
+    _showPrompt: function() {
+        let hold = new Batch.Hold();
+
+        let buttons = [{ action: Lang.bind(this, this._onCancel),
+                         label:  _("Cancel"),
+                         key:    Clutter.Escape },
+                       { action: Lang.bind(this, function() {
+                                     hold.release();
+                                 }),
+                         label:  _("Sign In") }];
+
+        this._promptEntryActivateCallbackId = this._promptEntry.clutter_text.connect('activate',
+                                                                                     Lang.bind(this, function() {
+                                                                                         hold.release();
+                                                                                     }));
+        hold.connect('release', Lang.bind(this, function() {
+                         this._promptEntry.clutter_text.disconnect(this._promptEntryActivateCallbackId);
+                         this._promptEntryActivateCallbackId = null;
+                     }));
+
+        let tasks = [function() {
+                         return this._fadeInPrompt();
+                     },
+
+                     function() {
+                         this.setButtons(buttons);
+                     },
+
+                     hold];
+
+        let batch = new Batch.ConcurrentBatch(this, tasks);
+
+        return batch.run();
+    },
+
+    _hidePrompt: function() {
+        if (this._promptEntryActivateCallbackId) {
+            this._promptEntry.clutter_text.disconnect(this._promptEntryActivateCallbackId);
+            this._promptEntryActivateCallbackId = null;
+        }
+
+        this.setButtons([]);
+
+        let tasks = [function() {
+                         return _fadeOutActor(this._promptLayout);
+                     },
+
+                     function() {
+                         this._promptEntry.set_text('');
+                     }];
+
+        let batch = new Batch.ConsecutiveBatch(this, tasks);
+
+        return batch.run();
+    },
+
+    _askQuestion: function(serviceName, question) {
+        this._promptLabel.set_text(question);
+
+        let tasks = [this._showPrompt,
+
+                     function() {
+                         let _text = this._promptEntry.get_text();
+                         this._promptEntry.set_text('');
+                         this._greeterClient.call_answer_query(serviceName, _text);
+                     }];
+
+        let batch = new Batch.ConsecutiveBatch(this, tasks);
+        return batch.run();
+    },
+    _onInfoQuery: function(client, serviceName, question) {
+        this._promptEntry.set_text('');
+        this._promptEntry.clutter_text.set_password_char('');
+        this._askQuestion(serviceName, question);
+    },
+
+    _onSecretInfoQuery: function(client, serviceName, secretQuestion) {
+        this._promptEntry.set_text('');
+        this._promptEntry.clutter_text.set_password_char('\u25cf');
+        this._askQuestion(serviceName, secretQuestion);
+    },
+
+    _onSessionOpened: function(client, serviceName) {
+        this._greeterClient.call_start_session_when_ready(serviceName, true);
+    },
+
+    _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.showFocusAnimation(this._timedLoginAnimationTime);
+    },
+
+    _blockTimedLoginUntilIdle: function() {
+        // This blocks timed login from starting until a few
+        // seconds after the user stops interacting with the
+        // login screen.
+        //
+        // We skip this step if the timed login delay is very
+        // short.
+        if ((this._timedLoginDelay - _TIMED_LOGIN_IDLE_THRESHOLD) <= 0)
+          return null;
+
+        let hold = new Batch.Hold();
+
+        this._timedLoginIdleTimeOutId = Mainloop.timeout_add_seconds(_TIMED_LOGIN_IDLE_THRESHOLD,
+                                                                     function() {
+                                                                         this._timedLoginAnimationTime -= _TIMED_LOGIN_IDLE_THRESHOLD;
+                                                                         hold.release();
+                                                                     });
+        return hold;
+    },
+
+    _startTimedLogin: function(userName, delay) {
+        this._timedLoginItem = null;
+        this._timedLoginDelay = delay;
+        this._timedLoginAnimationTime = delay;
+
+        let tasks = [function() {
+                         return this._waitForItemForUser(userName);
+                     },
+
+                     function() {
+                         this._timedLoginItem = this._userList.getItemFromUserName(userName);
+                     },
+
+                     function() {
+                         // If we're just starting out, start on the right
+                         // item.
+                         if (!this.is_loaded) {
+                             this._userList.jumpToItem(this._timedLoginItem);
+                             this._timedLoginItem.showFocusAnimation(0);
+                         }
+                     },
+
+                     this._blockTimedLoginUntilIdle,
+
+                     function() {
+                         this._userList.scrollToItem(this._timedLoginItem);
+                     },
+
+                     this._showTimedLoginAnimation,
+
+                     function() {
+                         this._timedLoginBatch = null;
+                         this._greeterClient.call_begin_auto_login(userName);
+                     }];
+
+        this._timedLoginBatch = new Batch.ConsecutiveBatch(this, tasks);
+
+        return this._timedLoginBatch.run();
+    },
+
+    _resetTimedLogin: function() {
+        if (this._timedLoginBatch) {
+            this._timedLoginBatch.cancel();
+            this._timedLoginBatch = null;
+        }
+
+        let userName = this._timedLoginItem.user.get_user_name();
+
+        if (userName)
+            this._startTimedLogin(userName, this._timedLoginDelay);
+    },
+
+    _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;
+                            }));
+    },
+
+    _onAuthenticationFailed: function(client) {
+        this._greeterClient.call_cancel();
+    },
+
+    _onConversationStopped: function(client, serviceName) {
+        this._greeterClient.call_cancel();
+    },
+
+    _onNotListedClicked: function(user) {
+        let tasks = [function() {
+                         return this._userList.hideItems();
+                     },
+
+                     function() {
+                         return this._userList.giveUpWhitespace();
+                     },
+
+                     function() {
+                         this._userList.actor.hide();
+                     },
+
+                     new Batch.ConcurrentBatch(this, [this._fadeOutTitleLabel,
+                                                      this._fadeOutNotListedButton]),
+
+                     function() {
+                         this._greeterClient.call_begin_verification('gdm-password');
+                     }];
+
+        let batch = new Batch.ConsecutiveBatch(this, tasks);
+        batch.run();
+    },
+
+    _fadeInTitleLabel: function() {
+        return _fadeInActor(this._titleLabel);
+    },
+
+    _fadeOutTitleLabel: function() {
+        return _fadeOutActor(this._titleLabel);
+    },
+
+    _fadeInNotListedButton: function() {
+        return _fadeInActor(this._notListedButton);
+    },
+
+    _fadeOutNotListedButton: function() {
+        return _fadeOutActor(this._notListedButton);
+    },
+
+    _onUserListActivated: function(activatedItem) {
+        let tasks = [function() {
+                         return this._userList.freezeSize();
+                     },
+
+                     function() {
+                         return this._userList.hideItems(activatedItem);
+                     },
+
+                     function() {
+                         return this._userList.giveUpWhitespace();
+                     },
+
+                     function() {
+                         return this._userList.thawSize();
+                     },
+
+                     function() {
+                         return activatedItem.fadeOutName();
+                     },
+
+                     function() {
+                         return this._userList.freezeSize();
+                     },
+
+                     new Batch.ConcurrentBatch(this, [this._fadeOutTitleLabel,
+                                                      this._fadeOutNotListedButton]),
+
+                     function() {
+                         return this._userList.thawSize();
+                     },
+
+                     function() {
+                         let userName = activatedItem.user.get_user_name();
+                         this._greeterClient.call_begin_verification_for_user('gdm-password',
+                                                                              userName);
+                     }];
+
+        let batch = new Batch.ConsecutiveBatch(this, tasks);
+        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._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);
+                                  }));
+
+        Mainloop.idle_add(Lang.bind(this, function() {
+            this.emit('loaded');
+            this.is_loaded = true;
+        }));
+    },
+
+    _onOpened: function() {
+        Main.ctrlAltTabManager.addGroup(this._mainContentLayout,
+                                        _("Login Window"),
+                                        'dialog-password',
+                                        { sortGroup: CtrlAltTab.SortGroup.MIDDLE });
+
+    },
+
+    close: function() {
+        Dialog.Dialog.prototype.close.call(this);
+
+        Main.ctrlAltTabManager.removeGroup(this._group);
+    }
+};
diff --git a/js/ui/main.js b/js/ui/main.js
index d03570a..0399ecd 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -26,6 +26,7 @@ const PlaceDisplay = imports.ui.placeDisplay;
 const RunDialog = imports.ui.runDialog;
 const Layout = imports.ui.layout;
 const LookingGlass = imports.ui.lookingGlass;
+const LoginDialog = imports.ui.loginDialog;
 const NotificationDaemon = imports.ui.notificationDaemon;
 const WindowAttentionHandler = imports.ui.windowAttentionHandler;
 const Scripting = imports.ui.scripting;
@@ -46,6 +47,7 @@ let panel = null;
 let hotCorners = [];
 let placesManager = null;
 let overview = null;
+let loginDialog = null;
 let runDialog = null;
 let lookingGlass = null;
 let wm = null;
@@ -82,6 +84,13 @@ function _createUserSession() {
     autorunManager = new AutorunManager.AutorunManager();
 }
 
+function _createLoginSession() {
+    loginDialog = new LoginDialog.LoginDialog();
+    loginDialog.connect('loaded', function() {
+                            loginDialog.open();
+                        });
+}
+
 function _initRecorder() {
     let recorderSettings = new Gio.Settings({ schema: 'org.gnome.shell.recorder' });
 
@@ -186,6 +195,8 @@ function start() {
 
     if (global.session_type == Shell.SessionType.USER)
         _createUserSession();
+    else if (global.session_type == Shell.SessionType.LOGIN)
+        _createLoginSession();
 
     magnifier = new Magnifier.Magnifier();
     statusIconDispatcher = new StatusIconDispatcher.StatusIconDispatcher();
diff --git a/js/ui/panel.js b/js/ui/panel.js
index 95358e3..3e45435 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -47,6 +47,14 @@ try {
     log('NMApplet is not supported. It is possible that your NetworkManager version is too old');
 }
 
+const LOGIN_TRAY_ICON_ORDER = ['a11y', 'display', 'keyboard', 'volume', 'battery'];
+const LOGIN_TRAY_ICON_SHELL_IMPLEMENTATION = {
+    'a11y': imports.ui.status.accessibility.ATIndicator,
+    'volume': imports.ui.status.volume.Indicator,
+    'battery': imports.ui.status.power.Indicator,
+    'keyboard': imports.ui.status.keyboard.XKBIndicator
+};
+
 // To make sure the panel corners blend nicely with the panel,
 // we draw background and borders the same way, e.g. drawing
 // them as filled shapes from the outside inwards instead of
@@ -799,6 +807,14 @@ Panel.prototype = {
             }));
         }
 
+        if (global.session_type == Shell.SessionType.LOGIN) {
+            this._tray_icon_order = LOGIN_TRAY_ICON_ORDER;
+            this._tray_icon_shell_implementation = LOGIN_TRAY_ICON_SHELL_IMPLEMENTATION;
+        } else {
+            this._tray_icon_order = STANDARD_TRAY_ICON_ORDER;
+            this._tray_icon_shell_implementation = STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION;
+        }
+
         this._leftPointerBarrier = 0;
         this._rightPointerBarrier = 0;
         this._menus = new PopupMenu.PopupMenuManager(this);
@@ -965,9 +981,9 @@ Panel.prototype = {
     },
 
     startStatusArea: function() {
-        for (let i = 0; i < STANDARD_TRAY_ICON_ORDER.length; i++) {
-            let role = STANDARD_TRAY_ICON_ORDER[i];
-            let constructor = STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role];
+        for (let i = 0; i < this._tray_icon_order.length; i++) {
+            let role = this._tray_icon_order[i];
+            let constructor = this._tray_icon_shell_implementation[role];
             if (!constructor) {
                 // This icon is not implemented (this is a bug)
                 continue;
@@ -1019,13 +1035,13 @@ Panel.prototype = {
     _onTrayIconAdded: function(o, icon, role) {
         icon.height = PANEL_ICON_SIZE;
 
-        if (STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role]) {
+        if (this._tray_icon_shell_implementation[role]) {
             // This icon is legacy, and replaced by a Shell version
             // Hide it
             return;
         }
         // Figure out the index in our well-known order for this icon
-        let position = STANDARD_TRAY_ICON_ORDER.indexOf(role);
+        let position = this._tray_icon_order.indexOf(role);
         icon._rolePosition = position;
         let children = this._trayBox.get_children();
         let i;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]