[gnome-shell/lightdm] Add support for LightDM



commit 450ca4a373570b97dddb2ebbef9a3c117e32467e
Author: Robert Ancell <robert ancell canonical com>
Date:   Tue Jul 31 13:23:42 2012 +0200

    Add support for LightDM

 js/Makefile.am            |    2 +
 js/lightdm/loginDialog.js | 1263 +++++++++++++++++++++++++++++++++++++++++++++
 js/lightdm/powerMenu.js   |  170 ++++++
 js/ui/main.js             |   13 +
 js/ui/sessionMode.js      |   26 +
 5 files changed, 1474 insertions(+), 0 deletions(-)
---
diff --git a/js/Makefile.am b/js/Makefile.am
index 97d95a9..009fda9 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -16,6 +16,8 @@ misc/config.js: misc/config.js.in Makefile
 jsdir = $(pkgdatadir)/js
 
 nobase_dist_js_DATA = 	\
+	lightdm/loginDialog.js	\
+	lightdm/powerMenu.js	\
 	gdm/batch.js		\
 	gdm/consoleKit.js	\
 	gdm/fingerprint.js	\
diff --git a/js/lightdm/loginDialog.js b/js/lightdm/loginDialog.js
new file mode 100644
index 0000000..8d1f65f
--- /dev/null
+++ b/js/lightdm/loginDialog.js
@@ -0,0 +1,1263 @@
+// -*- 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 Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const Gtk = imports.gi.Gtk;
+const Mainloop = imports.mainloop;
+const Lang = imports.lang;
+const Pango = imports.gi.Pango;
+const Signals = imports.signals;
+const Shell = imports.gi.Shell;
+const St = imports.gi.St;
+const LightDM = imports.gi.LightDM;
+
+const Batch = imports.gdm.batch;
+const Fprint = imports.gdm.fingerprint;
+const Lightbox = imports.ui.lightbox;
+const Main = imports.ui.main;
+const ModalDialog = imports.ui.modalDialog;
+const Tweener = imports.ui.tweener;
+
+const _RESIZE_ANIMATION_TIME = 0.25;
+const _SCROLL_ANIMATION_TIME = 2.0;
+const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;
+const _LOGO_ICON_NAME_SIZE = 48;
+
+let _loginDialog = null;
+
+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;
+}
+
+const UserListItem = new Lang.Class({
+    Name: 'UserListItem',
+
+    _init: function(user) {
+        this.user = user;
+        this._userChangedId = this.user.connect('changed',
+                                                 Lang.bind(this, this._onUserChanged));
+
+        this._verticalBox = 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._verticalBox,
+                                     reactive: true,
+                                     x_align: St.Align.START,
+                                     x_fill: true });
+        let layout = new St.BoxLayout({ vertical: false });
+
+        this._verticalBox.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._focusBin.scale_gravity = Clutter.Gravity.CENTER;
+        this._verticalBox.add(this._focusBin,
+                              { x_fill: true,
+                                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();
+            // We use background-image instead of, say, St.TextureCache
+            // so the theme writers can add a rounded frame around the image
+            // and so theme writers can pick the icon size.
+            this._iconBin.set_style('background-image: url("' + iconFile + '");' +
+                                    'background-size: contain;');
+        } 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 icon = new St.Icon();
+            icon.set_icon_name(iconName)
+
+            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 GdmUtil.fadeOutActor(this._nameLabel);
+    },
+
+    fadeInName: function() {
+        return GdmUtil.fadeInActor(this._nameLabel);
+    },
+
+    showFocusAnimation: function(time) {
+        let hold = new Batch.Hold();
+
+        Tweener.removeTweens(this._focusBin);
+        this._focusBin.scale_x = 0.;
+        Tweener.addTween(this._focusBin,
+                         { scale_x: 1.,
+                           time: time,
+                           transition: 'linear',
+                           onComplete: function() {
+                               hold.release();
+                           },
+                           onCompleteScope: this
+                         });
+        return hold;
+    }
+});
+Signals.addSignalMethods(UserListItem.prototype);
+
+const UserList = new Lang.Class({
+    Name: 'UserList',
+
+    _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._box = new St.BoxLayout({ vertical: true,
+                                       style_class: 'login-dialog-user-list' });
+
+        this.actor.add_actor(this._box);
+        this._items = {};
+
+        this.actor.connect('key-focus-in', Lang.bind(this, this._moveFocusToItems));
+    },
+
+    _moveFocusToItems: function() {
+        let hasItems = Object.keys(this._items).length > 0;
+
+        if (!hasItems)
+            return;
+
+        if (global.stage.get_key_focus() != this.actor)
+            return;
+
+        this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
+    },
+
+    _showItem: function(item) {
+        let tasks = [function() {
+                         return GdmUtil.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 });
+    },
+
+    pinInPlace: function() {
+        this._box.set_size(this._box.width, this._box.height);
+    },
+
+    shrinkToNaturalHeight: function() {
+        let oldWidth = this._box.width;
+        let oldHeight = this._box.height;
+        this._box.set_size(-1, -1);
+        let [minHeight, naturalHeight] = this._box.get_preferred_height(-1);
+        this._box.set_size(oldWidth, oldHeight);
+
+        let batch = new Batch.ConsecutiveBatch(this,
+                                               [function() {
+                                                    return _smoothlyResizeActor(this._box, -1, naturalHeight);
+                                                },
+
+                                                function() {
+                                                    this._box.set_size(-1, -1);
+                                                }
+                                               ]);
+
+        return batch.run();
+    },
+
+    hideItemsExcept: function(exception) {
+        let tasks = [];
+
+        for (let userName in this._items) {
+            let item = this._items[userName];
+
+            item.actor.can_focus = false;
+            item._focusBin.scale_x = 0.;
+            if (item != exception)
+                tasks.push(function() {
+                    return GdmUtil.fadeOutActor(item.actor);
+                });
+        }
+
+        let batch = new Batch.ConsecutiveBatch(this,
+                                               [function() {
+                                                    return GdmUtil.fadeOutActor(this.actor.vscroll);
+                                                },
+
+                                                new Batch.ConcurrentBatch(this, tasks)
+                                               ]);
+
+        return batch.run();
+    },
+
+    hideItems: function() {
+        return this.hideItemsExcept(null);
+    },
+
+    _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._box.visible) {
+            this._box.show();
+            hiddenActors.push(this._box);
+        }
+
+        this._box.set_size(-1, -1);
+        let [minHeight, naturalHeight] = this._box.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];
+            item.actor.can_focus = true;
+            tasks.push(function() {
+                return this._showItem(item);
+            });
+        }
+
+        let batch = new Batch.ConsecutiveBatch(this,
+                                               [function() {
+                                                    this.takeOverWhitespace();
+                                                },
+
+                                                function() {
+                                                    let fullHeight = this._getExpandedHeight();
+                                                    return _smoothlyResizeActor(this._box, -1, fullHeight);
+                                                },
+
+                                                new Batch.ConcurrentBatch(this, tasks),
+
+                                                function() {
+                                                    this.actor.set_size(-1, -1);
+                                                },
+
+                                                function() {
+                                                    return GdmUtil.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);
+        Tweener.removeTweens(adjustment);
+        Tweener.addTween (adjustment,
+                          { value: value,
+                            time: _SCROLL_ANIMATION_TIME,
+                            transition: 'linear' });
+    },
+
+    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;
+
+        if (user.locked)
+           return;
+
+        let userName = user.get_user_name();
+
+        if (!userName)
+            return;
+
+        this.removeUser(user);
+
+        let item = new UserListItem(user);
+        this._box.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._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',
+                                     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._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();
+    },
+
+    _onClicked: function() {
+        this.emit('activate');
+    }
+});
+Signals.addSignalMethods(SessionListItem.prototype);
+
+const SessionList = new Lang.Class({
+    Name: 'SessionList',
+
+    _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;
+
+        this._button = new St.Button({ style_class: 'login-dialog-session-list-button',
+                                       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();
+    },
+
+    open: function() {
+        if (this.isOpen)
+            return;
+
+        this._button.add_style_pseudo_class('open');
+        this._scrollView.show();
+        this._triangle.set_text('\u25BE');
+
+        this.isOpen = true;
+    },
+
+    close: function() {
+        if (!this.isOpen)
+            return;
+
+        this._button.remove_style_pseudo_class('open');
+        this._scrollView.hide();
+        this._triangle.set_text('\u25B8');
+
+        this.isOpen = false;
+    },
+
+    _onClicked: function() {
+        if (!this.isOpen)
+            this.open();
+        else
+            this.close();
+    },
+
+    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.emit('session-activated', this._activeSessionId);
+    },
+
+    _populate: function() {
+        this._itemList.destroy_all_children();
+        this._activeSessionId = null;
+        this._items = {};
+
+        let sessions = LightDM.get_sessions();
+
+        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 item = new SessionListItem(sessions[i].id, sessions[i].name);
+            this._itemList.add_actor(item.actor);
+            this._items[sessions[i].id] = item;
+
+            if (!this._activeSessionId)
+                this.setActiveSession(sessions[i].id);
+
+            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
+                    });
+        this.connect('destroy',
+                     Lang.bind(this, this._onDestroy));
+        this.connect('opened',
+                     Lang.bind(this, this._onOpened));
+
+        this._userManager = AccountsService.UserManager.get_default()
+
+        this._greeter = new LightDM.Greeter();
+        this._greeter.connect_sync();
+
+        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('verification-failed', Lang.bind(this, this._onVerificationFailed));
+        this._userVerifier.connect('reset', Lang.bind(this, this._onReset));
+
+        this._userVerifier.connect('show-fingerprint-prompt', Lang.bind(this, this._showFingerprintPrompt));
+        this._userVerifier.connect('hide-fingerprint-prompt', Lang.bind(this, this._hideFingerprintPrompt));
+
+        this._settings = new Gio.Settings({ schema: GdmUtil.LOGIN_SCREEN_SCHEMA });
+
+        this._settings.connect('changed::' + GdmUtil.LOGO_KEY,
+                               Lang.bind(this, this._updateLogo));
+        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._logoBox = new St.Bin({ style_class: 'login-dialog-logo-box' });
+        this.contentLayout.add(this._logoBox);
+        this._updateLogo();
+
+        this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner',
+                                           text: '' });
+        this.contentLayout.add(this._bannerLabel);
+        this._updateBanner();
+
+        this._titleLabel = new St.Label({ style_class: 'login-dialog-title',
+                                          text: C_("title", "Sign In") });
+
+        this.contentLayout.add(this._titleLabel,
+                              { y_fill: false,
+                                y_align: St.Align.START });
+
+        let mainContentBox = new St.BoxLayout({ vertical: false });
+        this.contentLayout.add(mainContentBox,
+                               { expand: true,
+                                 x_fill: true,
+                                 y_fill: false });
+
+        this._userList = new UserList();
+        mainContentBox.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 });
+        mainContentBox.add(this._promptBox,
+                           { 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._mainContentBox = mainContentBox;
+
+        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._promptBox.add(this._promptEntry,
+                            { expand: true,
+                              x_fill: true,
+                              y_fill: false,
+                              x_align: St.Align.START });
+        // Translators: this message is shown below the password entry field
+        // to indicate the user can swipe their finger instead
+        this._promptFingerprintMessage = new St.Label({ text: _("(or swipe finger)"),
+                                                        style_class: 'login-dialog-prompt-fingerprint-message' });
+        this._promptFingerprintMessage.hide();
+        this._promptBox.add(this._promptFingerprintMessage);
+
+        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._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',
+                                                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);
+                               }));
+
+   },
+
+    _updateLogo: function() {
+        this._logoBox.child = null;
+        let path = this._settings.get_string(GdmUtil.LOGO_KEY);
+
+        if (path) {
+            let file = Gio.file_new_for_path(path);
+            let uri = file.get_uri();
+
+            let textureCache = St.TextureCache.get_default();
+            this._logoBox.child = textureCache.load_uri_async(uri, -1, _LOGO_ICON_NAME_SIZE);
+        }
+
+    },
+
+    _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._fadeInBanner();
+        } else {
+            this._fadeOutBanner();
+        }
+    },
+
+    _onReset: function(client, serviceName) {
+        let tasks = [this._hidePrompt,
+
+                     new Batch.ConcurrentBatch(this, [this._fadeInTitleLabel,
+                                                      this._fadeInNotListedButton,
+                                                      this._fadeInLogo]),
+
+                     function() {
+                         this._sessionList.close();
+                         this._promptFingerprintMessage.hide();
+                         this._userList.actor.show();
+                         this._userList.actor.opacity = 255;
+                         return this._userList.showItems();
+                     },
+
+                     function() {
+                         this._userList.actor.reactive = true;
+                         this._userList.actor.grab_key_focus();
+                     }];
+
+        this._user = null;
+
+        let batch = new Batch.ConsecutiveBatch(this, tasks);
+        batch.run();
+    },
+
+    _onDefaultSessionChanged: function(client, sessionId) {
+        this._sessionList.setActiveSession(sessionId);
+    },
+
+    _showFingerprintPrompt: function() {
+        GdmUtil.fadeInActor(this._promptFingerprintMessage);
+    },
+
+    _hideFingerprintPrompt: function() {
+        GdmUtil.fadeOutActor(this._promptFingerprintMessage);
+    },
+
+    cancel: function() {
+        this._userVerifier.cancel();
+    },
+
+    _fadeInPrompt: function() {
+        let tasks = [function() {
+                         return GdmUtil.fadeInActor(this._promptLabel);
+                     },
+
+                     function() {
+                         return GdmUtil.fadeInActor(this._promptEntry);
+                     },
+
+                     function() {
+                         // Show it with 0 opacity so we preallocate space for it
+                         // in the event we need to fade in the message
+                         this._promptFingerprintMessage.opacity = 0;
+                         this._promptFingerprintMessage.show();
+                     },
+
+                     function() {
+                         return GdmUtil.fadeInActor(this._promptBox);
+                     },
+
+                     function() {
+                         if (this._user && this._user.is_logged_in())
+                             return null;
+
+                         return GdmUtil.fadeInActor(this._sessionList.actor);
+                     },
+
+                     function() {
+                         this._promptEntry.grab_key_focus();
+                     }];
+
+        this._sessionList.actor.hide();
+        let batch = new Batch.ConcurrentBatch(this, tasks);
+        return batch.run();
+    },
+
+    _showPrompt: function() {
+        let hold = new Batch.Hold();
+
+        let buttons = [{ action: Lang.bind(this, this.cancel),
+                         label: _("Cancel"),
+                         key: Clutter.Escape },
+                       { action: Lang.bind(this, function() {
+                                     hold.release();
+                                 }),
+                         label: C_("button", "Sign In"),
+                         default: true }];
+
+        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 GdmUtil.fadeOutActor(this._promptBox);
+                     },
+
+                     function() {
+                         this._promptFingerprintMessage.hide();
+                         this._promptEntry.reactive = true;
+                         this._promptEntry.remove_style_pseudo_class('insensitive');
+                         this._promptEntry.set_text('');
+                     }];
+
+        let batch = new Batch.ConsecutiveBatch(this, tasks);
+
+        return batch.run();
+    },
+
+    _askQuestion: function(verifier, serviceName, question, passwordChar) {
+        this._promptLabel.set_text(question);
+
+        this._promptEntry.set_text('');
+        this._promptEntry.clutter_text.set_password_char(passwordChar);
+
+        let tasks = [this._showPrompt,
+
+                     function() {
+                         let _text = this._promptEntry.get_text();
+                         this._promptEntry.reactive = false;
+                         this._promptEntry.add_style_pseudo_class('insensitive');
+                         this._userVerifier.answerQuery(serviceName, _text);
+                     }];
+
+        let batch = new Batch.ConsecutiveBatch(this, tasks);
+        return batch.run();
+    },
+
+    _onSessionOpened: function(client, serviceName) {
+        this._greeter.call_start_session_when_ready_sync(serviceName, true, null);
+    },
+
+    _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._greeter.call_begin_auto_login_sync(userName, null);
+                     }];
+
+        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;
+                             }));
+    },
+
+    _onVerificationFailed: function() {
+        this._userVerifier.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,
+                                                      this._fadeOutLogo]),
+
+                     function() {
+                         let hold = new Batch.Hold();
+
+                         this._userVerifier.begin(null, hold);
+                         return hold;
+                     }];
+
+        let batch = new Batch.ConsecutiveBatch(this, tasks);
+        batch.run();
+    },
+
+    _fadeInLogo: function() {
+        return GdmUtil.fadeInActor(this._logoBox);
+    },
+
+    _fadeOutLogo: function() {
+        return GdmUtil.fadeOutActor(this._logoBox);
+    },
+
+    _fadeInBanner: function() {
+        return GdmUtil.fadeInActor(this._bannerLabel);
+    },
+
+    _fadeOutBanner: function() {
+        return GdmUtil.fadeOutActor(this._bannerLabel);
+    },
+
+    _fadeInTitleLabel: function() {
+        return GdmUtil.fadeInActor(this._titleLabel);
+    },
+
+    _fadeOutTitleLabel: function() {
+        return GdmUtil.fadeOutActor(this._titleLabel);
+    },
+
+    _fadeInNotListedButton: function() {
+        return GdmUtil.fadeInActor(this._notListedButton);
+    },
+
+    _fadeOutNotListedButton: function() {
+        return GdmUtil.fadeOutActor(this._notListedButton);
+    },
+
+    _beginVerificationForUser: function(userName) {
+        let hold = new Batch.Hold();
+
+        this._userVerifier.begin(userName, hold);
+        return hold;
+    },
+
+    _onUserListActivated: function(activatedItem) {
+        let userName;
+
+        let tasks = [function() {
+                         this._userList.actor.reactive = false;
+                         return this._userList.pinInPlace();
+                     },
+
+                     function() {
+                         return this._userList.hideItemsExcept(activatedItem);
+                     },
+
+                     function() {
+                         return this._userList.giveUpWhitespace();
+                     },
+
+                     function() {
+                         return activatedItem.fadeOutName();
+                     },
+
+                     new Batch.ConcurrentBatch(this, [this._fadeOutTitleLabel,
+                                                      this._fadeOutNotListedButton,
+                                                      this._fadeOutLogo]),
+
+                     function() {
+                         return this._userList.shrinkToNaturalHeight();
+                     },
+
+                     function() {
+                         userName = activatedItem.user.get_user_name();
+
+                         return this._beginVerificationForUser(userName);
+                     }];
+
+        this._user = activatedItem.user;
+
+        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);
+                                  }));
+
+        // emitted in idle so caller doesn't have to explicitly check if
+        // it's loaded immediately after construction
+        // (since there's no way the caller could be listening for
+        // 'loaded' yet)
+        Mainloop.idle_add(Lang.bind(this, function() {
+            this.emit('loaded');
+            this.is_loaded = true;
+        }));
+    },
+
+    _onOpened: function() {
+        Main.ctrlAltTabManager.addGroup(this._mainContentBox,
+                                        _("Login Window"),
+                                        'dialog-password',
+                                        { sortGroup: CtrlAltTab.SortGroup.MIDDLE });
+
+    },
+
+    close: function() {
+        this.parent();
+
+        Main.ctrlAltTabManager.removeGroup(this._group);
+    }
+});
diff --git a/js/lightdm/powerMenu.js b/js/lightdm/powerMenu.js
new file mode 100644
index 0000000..613a896
--- /dev/null
+++ b/js/lightdm/powerMenu.js
@@ -0,0 +1,170 @@
+// -*- 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 Lang = imports.lang;
+const UPowerGlib = imports.gi.UPowerGlib;
+
+const ConsoleKit = imports.gdm.consoleKit;
+const Systemd = imports.gdm.systemd;
+
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+
+const PowerMenuButton = new Lang.Class({
+    Name: 'PowerMenuButton',
+    Extends: PanelMenu.SystemStatusButton,
+
+    _init: function() {
+        this.parent('system-shutdown', null);
+        this._upClient = new UPowerGlib.Client();
+
+        if (Systemd.haveSystemd())
+            this._systemdLoginManager = new Systemd.SystemdLoginManager();
+        else
+            this._consoleKitManager = new ConsoleKit.ConsoleKitManager();
+
+        this._createSubMenu();
+
+        this._upClient.connect('notify::can-suspend',
+                               Lang.bind(this, this._updateHaveSuspend));
+        this._updateHaveSuspend();
+
+        // ConsoleKit doesn't send notifications when shutdown/reboot
+        // are disabled, so we update the menu item each time the menu opens
+        this.menu.connect('open-state-changed', Lang.bind(this,
+            function(menu, open) {
+                if (open) {
+                    this._updateHaveShutdown();
+                    this._updateHaveRestart();
+                }
+            }));
+        this._updateHaveShutdown();
+        this._updateHaveRestart();
+    },
+
+    _updateVisibility: function() {
+        let shouldBeVisible = (this._haveSuspend || this._haveShutdown || this._haveRestart);
+        this.actor.visible = shouldBeVisible;
+    },
+
+    _updateHaveShutdown: function() {
+
+        if (Systemd.haveSystemd()) {
+            this._systemdLoginManager.CanPowerOffRemote(Lang.bind(this,
+                function(result, error) {
+                    if (!error)
+                        this._haveShutdown = result[0] != 'no';
+                    else
+                        this._haveShutdown = false;
+
+                    this._powerOffItem.actor.visible = this._haveShutdown;
+                    this._updateVisibility();
+                }));
+        } else {
+            this._consoleKitManager.CanStopRemote(Lang.bind(this,
+                function(result, error) {
+                    if (!error)
+                        this._haveShutdown = result[0];
+                    else
+                        this._haveShutdown = false;
+
+                    this._powerOffItem.actor.visible = this._haveShutdown;
+                    this._updateVisibility();
+                }));
+        }
+    },
+
+    _updateHaveRestart: function() {
+
+        if (Systemd.haveSystemd()) {
+            this._systemdLoginManager.CanRebootRemote(Lang.bind(this,
+                function(result, error) {
+                    if (!error)
+                        this._haveRestart = result[0] != 'no';
+                    else
+                        this._haveRestart = false;
+
+                    this._restartItem.actor.visible = this._haveRestart;
+                    this._updateVisibility();
+                }));
+        } else {
+            this._consoleKitManager.CanRestartRemote(Lang.bind(this,
+                function(result, error) {
+                    if (!error)
+                        this._haveRestart = result[0];
+                    else
+                        this._haveRestart = false;
+
+                    this._restartItem.actor.visible = this._haveRestart;
+                    this._updateVisibility();
+                }));
+        }
+    },
+
+    _updateHaveSuspend: function() {
+        this._haveSuspend = this._upClient.get_can_suspend();
+        this._suspendItem.actor.visible = this._haveSuspend;
+        this._updateVisibility();
+    },
+
+    _createSubMenu: function() {
+        let item;
+
+        item = new PopupMenu.PopupMenuItem(_("Suspend"));
+        item.connect('activate', Lang.bind(this, this._onActivateSuspend));
+        this.menu.addMenuItem(item);
+        this._suspendItem = item;
+
+        item = new PopupMenu.PopupMenuItem(_("Restart"));
+        item.connect('activate', Lang.bind(this, this._onActivateRestart));
+        this.menu.addMenuItem(item);
+        this._restartItem = item;
+
+        item = new PopupMenu.PopupMenuItem(_("Power Off"));
+        item.connect('activate', Lang.bind(this, this._onActivatePowerOff));
+        this.menu.addMenuItem(item);
+        this._powerOffItem = item;
+    },
+
+    _onActivateSuspend: function() {
+        if (this._haveSuspend)
+            this._upClient.suspend_sync(null);
+    },
+
+    _onActivateRestart: function() {
+        if (!this._haveRestart)
+            return;
+
+        if (Systemd.haveSystemd())
+            this._systemdLoginManager.RebootRemote(true);
+        else
+            this._consoleKitManager.RestartRemote();
+    },
+
+    _onActivatePowerOff: function() {
+        if (!this._haveShutdown)
+            return;
+
+        if (Systemd.haveSystemd())
+            this._systemdLoginManager.PowerOffRemote(true);
+        else
+            this._consoleKitManager.StopRemote();
+    }
+});
diff --git a/js/ui/main.js b/js/ui/main.js
index 790dd80..73f473c 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -95,6 +95,19 @@ function createUserSession() {
     _initRecorder();
 }
 
+function createLightDMSession() {
+    screenShield.showDialog();
+}
+
+function createLightDMLoginDialog(parentActor) {
+    // We do this this here instead of at the top to prevent GDM
+    // related code from getting loaded in normal user sessions
+    const LoginDialog = imports.lightdm.loginDialog;
+
+    let loginDialog = new LoginDialog.LoginDialog(parentActor);
+    return [loginDialog, true];
+}
+
 function createGDMSession() {
     screenShield.showDialog();
 }
diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js
index 1cc4ec3..d0c9e7c 100644
--- a/js/ui/sessionMode.js
+++ b/js/ui/sessionMode.js
@@ -30,6 +30,32 @@ try {
 const DEFAULT_MODE = 'user';
 
 const _modes = {
+    'lightdm': { hasOverview: false,
+                 hasAppMenu: false,
+                 showCalendarEvents: false,
+                 allowSettings: false,
+                 allowExtensions: false,
+                 allowKeybindingsWhenModal: true,
+                 hasRunDialog: false,
+                 hasWorkspaces: false,
+                 createSession: Main.createLightDMSession,
+                 createUnlockDialog: Main.createLightDMLoginDialog,
+                 extraStylesheet: null,
+                 statusArea: {
+                     order: [
+                         'a11y', 'display', 'keyboard',
+                         'volume', 'battery', 'powerMenu'
+                     ],
+                     implementation: {
+                         'a11y': imports.ui.status.accessibility.ATIndicator,
+                         'volume': imports.ui.status.volume.Indicator,
+                         'battery': imports.ui.status.power.Indicator,
+                         'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
+                         'powerMenu': imports.lightdm.powerMenu.PowerMenuButton
+                     }
+                 }
+               },
+
     'gdm': { hasOverview: false,
              hasAppMenu: false,
              showCalendarEvents: false,



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