gnome-shell/js/gdm/loginDialog.js
Giovanni Campagna 17c46c2452 Port everything to class framework
The last patch in the sequence. Every place that was previously
setting prototype has been ported to Lang.Class, to make code more
concise and allow for better toString().

https://bugzilla.gnome.org/show_bug.cgi?id=664436
2011-11-24 09:50:04 +01:00

1388 lines
49 KiB
JavaScript

// -*- 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 GdmGreeter = imports.gi.GdmGreeter;
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 _PASSWORD_SERVICE_NAME = 'gdm-password';
const _FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
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;
const _LOGO_ICON_NAME_SIZE = 48;
const _LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
const _FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
const _LOGO_KEY = 'logo';
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;
}
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._verticalBox.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();
// 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 + '");');
} 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 _fadeOutActor(this._nameLabel);
},
fadeInName: function() {
return _fadeInActor(this._nameLabel);
},
showFocusAnimation: function(time) {
let hold = new Batch.Hold();
let node = this.actor.get_theme_node();
let padding = node.get_horizontal_padding();
let box = this._verticalBox.get_allocation_box();
Tweener.removeTweens(this._focusBin);
this._focusBin.width = 0;
Tweener.addTween(this._focusBin,
{ width: (box.x2 - box.x1 - padding),
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,
{ x_fill: true,
y_fill: true,
x_align: St.Align.START,
y_align: St.Align.MIDDLE });
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 _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.width = 0;
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();
},
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 _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;
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,
{ expand: true,
x_fill: true,
y_fill: true });
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,
{ expand: true,
x_fill: true,
y_fill: true });
},
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,
{ x_fill: true,
y_fill: true,
expand: true });
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,
{ x_fill: true,
y_fill: true,
expand: true });
this._button.connect('clicked',
Lang.bind(this, this._onClicked));
this._box.add_actor(this._button,
{ x_fill: true,
y_fill: true,
expand: true });
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,
{ x_fill: true,
y_fill: true,
expand: true });
this._itemList = new St.BoxLayout({ style_class: 'login-dialog-session-item-list',
vertical: true });
this._scrollView.add_actor(this._itemList,
{ x_fill: true,
y_fill: true,
expand: true });
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_children();
this._activeSessionId = null;
this._items = {};
let ids = GdmGreeter.get_session_ids();
ids.sort();
if (ids.length <= 1) {
this._box.hide();
this._button.hide();
} else {
this._button.show();
this._box.show();
}
for (let i = 0; i < ids.length; i++) {
let [sessionName, sessionDescription] = GdmGreeter.get_session_name_and_description(ids[i]);
let item = new SessionListItem(ids[i], sessionName);
this._itemList.add_actor(item.actor,
{ x_align: St.Align.START,
y_align: St.Align.START,
x_fill: true,
y_fill: true });
this._items[ids[i]] = item;
if (!this._activeSessionId)
this.setActiveSession(ids[i]);
item.connect('activate',
Lang.bind(this, function() {
this.setActiveSession(item.id);
}));
}
}
});
Signals.addSignalMethods(SessionList.prototype);
const LoginDialog = new Lang.Class({
Name: 'LoginDialog',
Extends: ModalDialog.ModalDialog,
_init: function() {
this.parent({ shellReactive: true, 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(_PASSWORD_SERVICE_NAME);
this._greeterClient.connect('reset',
Lang.bind(this, this._onReset));
this._greeterClient.connect('default-session-changed',
Lang.bind(this, this._onDefaultSessionChanged));
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._settings = new Gio.Settings({ schema: _LOGIN_SCREEN_SCHEMA });
this._fprintManager = new Fprint.FprintManager();
this._startFingerprintConversationIfNeeded();
this._settings.connect('changed::' + _LOGO_KEY,
Lang.bind(this, this._updateLogo));
this._logoBox = new St.Bin({ style_class: 'login-dialog-logo-box' });
this.contentLayout.add(this._logoBox);
this._updateLogo();
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._greeterClient.call_select_session (sessionId);
}));
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);
}));
},
_startFingerprintConversationIfNeeded: function() {
this._haveFingerprintReader = false;
if (!this._settings.get_boolean(_FINGERPRINT_AUTHENTICATION_KEY))
return;
this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, Lang.bind(this,
function(device, error) {
if (!error && device)
this._haveFingerprintReader = true;
if (this._haveFingerprintReader)
this._greeterClient.call_start_conversation(_FINGERPRINT_SERVICE_NAME);
}));
},
_updateLogo: function() {
this._logoBox.child = null;
let path = this._settings.get_string(_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);
}
},
_onReset: function(client, serviceName) {
this._greeterClient.call_start_conversation(_PASSWORD_SERVICE_NAME);
this._startFingerprintConversationIfNeeded();
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);
},
_onInfo: function(client, serviceName, info) {
// We don't display fingerprint messages, because they
// have words like UPEK in them. Instead we use the messages
// as a cue to display our own message.
if (serviceName == _FINGERPRINT_SERVICE_NAME &&
this._haveFingerprintReader &&
(!this._promptFingerprintMessage.visible ||
this._promptFingerprintMessage.opacity != 255)) {
_fadeInActor(this._promptFingerprintMessage);
return;
}
if (serviceName != _PASSWORD_SERVICE_NAME)
return;
Main.notifyError(info);
},
_onProblem: function(client, serviceName, problem) {
// we don't want to show auth failed messages to
// users who haven't enrolled their fingerprint.
if (serviceName != _PASSWORD_SERVICE_NAME)
return;
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() {
// 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 _fadeInActor(this._promptBox);
},
function() {
if (this._user && this._user.is_logged_in())
return null;
return _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._onCancel),
label: _("Cancel"),
key: Clutter.Escape },
{ action: Lang.bind(this, function() {
hold.release();
}),
label: C_("button", "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._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(serviceName, question) {
this._promptLabel.set_text(question);
let tasks = [this._showPrompt,
function() {
let _text = this._promptEntry.get_text();
this._promptEntry.reactive = false;
this._promptEntry.add_style_pseudo_class('insensitive');
this._greeterClient.call_answer_query(serviceName, _text);
}];
let batch = new Batch.ConsecutiveBatch(this, tasks);
return batch.run();
},
_onInfoQuery: function(client, serviceName, question) {
// We only expect questions to come from the main auth service
if (serviceName != _PASSWORD_SERVICE_NAME)
return;
this._promptEntry.set_text('');
this._promptEntry.clutter_text.set_password_char('');
this._askQuestion(serviceName, question);
},
_onSecretInfoQuery: function(client, serviceName, secretQuestion) {
// We only expect secret requests to come from the main auth service
if (serviceName != _PASSWORD_SERVICE_NAME)
return;
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) {
// if the password service fails, then cancel everything.
// But if, e.g., fingerprint fails, still give
// password authentication a chance to succeed
if (serviceName == _PASSWORD_SERVICE_NAME) {
this._greeterClient.call_cancel();
} else if (serviceName == _FINGERPRINT_SERVICE_NAME) {
_fadeOutActor(this._promptFingerprintMessage);
}
},
_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() {
this._greeterClient.call_begin_verification(_PASSWORD_SERVICE_NAME);
}];
let batch = new Batch.ConsecutiveBatch(this, tasks);
batch.run();
},
_fadeInLogo: function() {
return _fadeInActor(this._logoBox);
},
_fadeOutLogo: function() {
return _fadeOutActor(this._logoBox);
},
_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() {
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() {
let userName = activatedItem.user.get_user_name();
this._greeterClient.call_begin_verification_for_user(_PASSWORD_SERVICE_NAME,
userName);
if (this._haveFingerprintReader)
this._greeterClient.call_begin_verification_for_user(_FINGERPRINT_SERVICE_NAME, 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);
}
});