Add UnlockDialog for unlocking the screen shield

When the screenshield is deactivated, instead of going back to the
session immediately, prompt the user for authentication.
This essentially reinstates what used to be provided by gnome-screensaver.
This commit is contained in:
Giovanni Campagna 2012-05-20 18:30:14 +02:00
parent 9dfd805be2
commit 17044adf96
5 changed files with 406 additions and 24 deletions

View File

@ -92,6 +92,7 @@ nobase_dist_js_DATA = \
ui/status/bluetooth.js \
ui/telepathyClient.js \
ui/tweener.js \
ui/unlockDialog.js \
ui/userMenu.js \
ui/viewSelector.js \
ui/wanda.js \

View File

@ -39,8 +39,8 @@ 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 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;
@ -749,7 +749,7 @@ const LoginDialog = new Lang.Class({
this._userManager = AccountsService.UserManager.get_default()
this._greeterClient = GdmGreeter.Server.new_for_greeter_sync(null);
this._greeterClient.call_start_conversation_sync(_PASSWORD_SERVICE_NAME, null);
this._greeterClient.call_start_conversation_sync(PASSWORD_SERVICE_NAME, null);
this._greeterClient.connect('reset',
Lang.bind(this, this._onReset));
@ -895,7 +895,7 @@ const LoginDialog = new Lang.Class({
this._haveFingerprintReader = true;
if (this._haveFingerprintReader)
this._greeterClient.call_start_conversation(_FINGERPRINT_SERVICE_NAME);
this._greeterClient.call_start_conversation_sync(FINGERPRINT_SERVICE_NAME, null);
}));
},
@ -914,7 +914,7 @@ const LoginDialog = new Lang.Class({
},
_onReset: function(client, serviceName) {
this._greeterClient.call_start_conversation_sync(_PASSWORD_SERVICE_NAME, null);
this._greeterClient.call_start_conversation_sync(PASSWORD_SERVICE_NAME, null);
this._startFingerprintConversationIfNeeded();
let tasks = [this._hidePrompt,
@ -950,7 +950,7 @@ const LoginDialog = new Lang.Class({
// We don't display fingerprint messages, because they
// have words like UPEK in them. Instead we use the messages
// as a cue to display our own message.
if (serviceName == _FINGERPRINT_SERVICE_NAME &&
if (serviceName == FINGERPRINT_SERVICE_NAME &&
this._haveFingerprintReader &&
(!this._promptFingerprintMessage.visible ||
this._promptFingerprintMessage.opacity != 255)) {
@ -959,7 +959,7 @@ const LoginDialog = new Lang.Class({
return;
}
if (serviceName != _PASSWORD_SERVICE_NAME)
if (serviceName != PASSWORD_SERVICE_NAME)
return;
Main.notifyError(info);
},
@ -967,7 +967,7 @@ const LoginDialog = new Lang.Class({
_onProblem: function(client, serviceName, problem) {
// we don't want to show auth failed messages to
// users who haven't enrolled their fingerprint.
if (serviceName != _PASSWORD_SERVICE_NAME)
if (serviceName != PASSWORD_SERVICE_NAME)
return;
Main.notifyError(problem);
},
@ -1088,7 +1088,7 @@ const LoginDialog = new Lang.Class({
},
_onInfoQuery: function(client, serviceName, question) {
// We only expect questions to come from the main auth service
if (serviceName != _PASSWORD_SERVICE_NAME)
if (serviceName != PASSWORD_SERVICE_NAME)
return;
this._promptEntry.set_text('');
@ -1098,7 +1098,7 @@ const LoginDialog = new Lang.Class({
_onSecretInfoQuery: function(client, serviceName, secretQuestion) {
// We only expect secret requests to come from the main auth service
if (serviceName != _PASSWORD_SERVICE_NAME)
if (serviceName != PASSWORD_SERVICE_NAME)
return;
this._promptEntry.set_text('');
@ -1236,9 +1236,9 @@ const LoginDialog = new Lang.Class({
// if the password service fails, then cancel everything.
// But if, e.g., fingerprint fails, still give
// password authentication a chance to succeed
if (serviceName == _PASSWORD_SERVICE_NAME) {
if (serviceName == PASSWORD_SERVICE_NAME) {
this._greeterClient.call_cancel_sync(null);
} else if (serviceName == _FINGERPRINT_SERVICE_NAME) {
} else if (serviceName == FINGERPRINT_SERVICE_NAME) {
_fadeOutActor(this._promptFingerprintMessage);
}
},
@ -1261,7 +1261,7 @@ const LoginDialog = new Lang.Class({
this._fadeOutLogo]),
function() {
this._greeterClient.call_begin_verification_sync(_PASSWORD_SERVICE_NAME, null);
this._greeterClient.call_begin_verification_sync(PASSWORD_SERVICE_NAME, null);
}];
let batch = new Batch.ConsecutiveBatch(this, tasks);
@ -1320,7 +1320,7 @@ const LoginDialog = new Lang.Class({
function() {
let userName = activatedItem.user.get_user_name();
this._greeterClient.call_begin_verification_for_user_sync(_PASSWORD_SERVICE_NAME,
this._greeterClient.call_begin_verification_for_user_sync(PASSWORD_SERVICE_NAME,
userName, null);
if (this._haveFingerprintReader)

View File

@ -494,7 +494,7 @@ function loadTheme() {
let theme = new St.Theme ({ application_stylesheet: cssStylesheet });
if (global.session_type == Shell.SessionType.GDM)
// FIXME: merge back into main stylesheet
theme.load_stylesheet(_gdmCssStylesheet);
if (previousTheme) {

View File

@ -8,7 +8,7 @@ const St = imports.gi.St;
const GnomeSession = imports.misc.gnomeSession;
const Lightbox = imports.ui.lightbox;
const LoginDialog = imports.gdm.loginDialog;
const UnlockDialog = imports.ui.unlockDialog;
const Main = imports.ui.main;
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
@ -43,8 +43,7 @@ const ScreenShield = new Lang.Class({
let constraint = new Clutter.BindConstraint({ source: global.stage,
coordinate: Clutter.BindCoordinate.POSITION | Clutter.BindCoordinate.SIZE });
this._group.add_constraint(constraint);
this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
this._group.connect('button-press-event', Lang.bind(this, this._onButtonPressEvent));
this._lightbox = new Lightbox.Lightbox(this._group,
{ inhibitEvents: true, fadeInTime: 10, fadeFactor: 1 });
this._background = Meta.BackgroundActor.new_for_screen(global.screen);
@ -66,6 +65,8 @@ const ScreenShield = new Lang.Class({
if (lightboxWasShown && this._settings.get_boolean(LOCK_ENABLED_KEY)) {
this._background.show();
this._background.raise_top();
this._showUnlockDialog();
} else {
this._popModal();
}
@ -79,13 +80,30 @@ const ScreenShield = new Lang.Class({
this._background.hide();
},
_onKeyPressEvent: function(object, keyPressEvent) {
log("in _onKeyPressEvent - lock is enabled: " + this._settings.get_boolean(LOCK_ENABLED_KEY));
this._popModal();
_showUnlockDialog: function() {
if (this._dialog)
return;
this._dialog = new UnlockDialog.UnlockDialog();
this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed));
this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded));
this._dialog.open(global.get_current_time());
},
_onButtonPressEvent: function(object, buttonPressEvent) {
log("in _onButtonPressEvent - lock is enabled: " + this._settings.get_boolean(LOCK_ENABLED_KEY));
_onUnlockFailed: function() {
// FIXME: for now, on failure we just destroy the dialog and create a new
// one (this is what gnome-screensaver does)
// in the future, we may want to go back to the lock screen instead
this._dialog.destroy();
this._dialog = null;
this._showUnlockDialog();
},
_onUnlockSucceded: function() {
this._dialog.destroy();
this._popModal();
},
});

363
js/ui/unlockDialog.js Normal file
View File

@ -0,0 +1,363 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const AccountsService = imports.gi.AccountsService;
const Clutter = imports.gi.Clutter;
const GdmGreeter = imports.gi.GdmGreeter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const ModalDialog = imports.ui.modalDialog;
const Fprint = imports.gdm.fingerprint;
const GdmLoginDialog = imports.gdm.loginDialog;
function _fadeInActor(actor) {
if (actor.opacity == 255 && actor.visible)
return;
actor.show();
let [minHeight, naturalHeight] = actor.get_preferred_height(-1);
actor.opacity = 0;
actor.set_height(0);
Tweener.addTween(actor,
{ opacity: 255,
height: naturalHeight,
time: _FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: function() {
this.set_height(-1);
},
});
}
function _fadeOutActor(actor) {
if (!actor.visible || actor.opacity == 0) {
actor.opacity = 0;
actor.hide();
}
Tweener.addTween(actor,
{ opacity: 0,
height: 0,
time: _FADE_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: function() {
this.hide();
this.set_height(-1);
},
});
}
// A widget showing the user avatar and name
const UserWidget = new Lang.Class({
Name: 'UserWidget',
_init: function(user) {
this._user = user;
this.actor = new St.BoxLayout({ style_class: 'status-chooser',
vertical: false,
reactive: false
});
this._iconBin = new St.Bin({ style_class: 'status-chooser-user-icon' });
this.actor.add(this._iconBin,
{ x_fill: true,
y_fill: true });
this._label = new St.Label({ style_class: 'login-dialog-prompt-label',
// FIXME:
style: 'text-align: right' });
this.actor.add(this._label,
{ expand: true,
x_fill: true,
y_fill: true
});
this._userLoadedId = this._user.connect('notify::is-loaded',
Lang.bind(this,
this._updateUser));
this._userChangedId = this._user.connect('changed',
Lang.bind(this,
this._updateUser));
this.actor.connect('notify::mapped', Lang.bind(this, function() {
if (this.actor.mapped)
this._updateUser();
}));
},
destroy: function() {
// clean up signal handlers
if (this._userLoadedId != 0) {
this._user.disconnect(this._userLoadedId);
this._userLoadedId = 0;
}
if (this._userChangedId != 0) {
this._user.disconnect(this._userChangedId);
this._userChangedId = 0;
}
this.actor.destroy();
},
_updateUser: function() {
let iconFile = null;
if (this._user.is_loaded) {
this._label.text = this._user.get_real_name();
iconFile = this._user.get_icon_file();
if (!GLib.file_test(iconFile, GLib.FileTest.EXISTS))
iconFile = null;
} else {
this._label.text = "";
}
if (iconFile)
this._setIconFromFile(iconFile);
else
this._setIconFromName('avatar-default');
},
// XXX: a GFileIcon instead?
_setIconFromFile: function(iconFile) {
this._iconBin.set_style('background-image: url("' + iconFile + '");' +
'background-size: contain;');
this._iconBin.child = null;
},
_setIconFromName: function(iconName) {
this._iconBin.set_style(null);
if (iconName != null) {
let icon = new St.Icon({ icon_name: iconName,
icon_type: St.IconType.SYMBOLIC,
icon_size: DIALOG_ICON_SIZE });
this._iconBin.child = icon;
this._iconBin.show();
} else {
this._iconBin.child = null;
this._iconBin.hide();
}
}
});
const UnlockDialog = new Lang.Class({
Name: 'UnlockDialog',
Extends: ModalDialog.ModalDialog,
_init: function() {
this.parent({ shellReactive: true,
styleClass: 'login-dialog' });
this._userManager = AccountsService.UserManager.get_default();
this._userName = GLib.get_user_name();
this._user = this._userManager.get_user(this._userName);
this._greeterClient = GdmGreeter.Server.new_for_display_sync(null, null);
this._greeterClient.call_start_conversation_sync(GdmLoginDialog.PASSWORD_SERVICE_NAME, null);
this._greeterClient.connect('reset',
Lang.bind(this, this._onReset));
this._greeterClient.connect('ready',
Lang.bind(this, this._onReady));
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('conversation-stopped',
Lang.bind(this, this._onConversationStopped));
this._fprintManager = new Fprint.FprintManager();
this._startFingerprintConversationIfNeeded();
this._userWidget = new UserWidget(this._user);
this.contentLayout.add_actor(this._userWidget.actor);
this._promptLayout = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
vertical: false
});
this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' });
this._promptLayout.add(this._promptLabel,
{ expand: false,
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._promptLayout.add(this._promptEntry,
{ expand: true,
x_fill: true,
y_fill: false,
x_align: St.Align.START });
this.contentLayout.add_actor(this._promptLayout);
// 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.contentLayout.add_actor(this._promptFingerprintMessage);
this._okButton = { label: _("Unlock"),
action: Lang.bind(this, this._doUnlock),
key: Clutter.KEY_Return,
};
this.setButtons([this._okButton]);
this._updateOkButton(true);
},
_updateOkButton: function(sensitive) {
this._okButton.button.reactive = sensitive;
this._okButton.button.can_focus = sensitive;
if (sensitive)
this._okButton.button.remove_style_pseudo_class('disabled');
else
this._okButton.button.add_style_pseudo_class('disabled');
},
_onReset: function() {
// I'm not sure this is emitted for external greeters...
this._greeterClient.call_start_conversation_sync(GdmLoginDialog.PASSWORD_SERVICE_NAME, null);
this._startFingerprintConversationIfNeeded();
},
_startFingerprintConversationIfNeeded: function() {
this._haveFingerprintReader = false;
// FIXME: the greeter has a GSettings key for disabling fingerprint auth
this._fprintManager.GetDefaultDeviceRemote(Lang.bind(this,
function(device, error) {
if (!error && device)
this._haveFingerprintReader = true;
if (this._haveFingerprintReader)
this._greeterClient.call_start_conversation_sync(GdmLoginDialog.FINGERPRINT_SERVICE_NAME, null);
}));
},
_onReady: function(greeter, serviceName) {
greeter.call_begin_verification_for_user_sync(serviceName, this._userName, null);
},
_onInfo: function(greeter, 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 == GdmLoginDialog.FINGERPRINT_SERVICE_NAME &&
this._haveFingerprintReader &&
(!this._promptFingerprintMessage.visible ||
this._promptFingerprintMessage.opacity != 255)) {
_fadeInActor(this._promptFingerprintMessage);
return;
}
if (serviceName != GdmLoginDialog.PASSWORD_SERVICE_NAME)
return;
Main.notify(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 != GdmLoginDialog.PASSWORD_SERVICE_NAME)
return;
Main.notifyError(problem);
},
_onInfoQuery: function(client, serviceName, question) {
// We only expect questions to come from the main auth service
if (serviceName != GdmLoginDialog.PASSWORD_SERVICE_NAME)
return;
this._promptLabel.text = question;
this._promptEntry.text = '';
this._promptEntry.clutter_text.set_password_char('');
this._currentQuery = serviceName;
this._updateOkButton(true);
},
_onSecretInfoQuery: function(client, serviceName, secretQuestion) {
// We only expect secret requests to come from the main auth service
if (serviceName != GdmLoginDialog.PASSWORD_SERVICE_NAME)
return;
this._promptLabel.text = secretQuestion;
this._promptEntry.text = '';
this._promptEntry.clutter_text.set_password_char('\u25cf');
this._currentQuery = serviceName;
this._updateOkButton(true);
},
_doUnlock: function() {
if (!this._currentQuery)
return;
let query = this._currentQuery;
this._currentQuery = null;
this._updateOkButton(false);
this._greeterClient.call_answer_query_sync(query, this._promptEntry.text, null);
},
_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 == GdmLoginDialog.PASSWORD_SERVICE_NAME) {
this._greeterClient.call_cancel_sync(null);
this.emit('failed');
} else if (serviceName == GdmLoginDialog.FINGERPRINT_SERVICE_NAME) {
_fadeOutActor(this._promptFingerprintMessage);
}
},
_onSessionOpened: function(client, serviceName) {
// For external greeters, SessionOpened means we succeded
// in the authentication process
// Close the greeter proxy
this._greeterClient.run_dispose();
this._greeterClient = null;
this.emit('unlocked');
},
destroy: function() {
if (this._greeterClient) {
this._greeterClient.run_dispose();
this._greeterClient = null;
}
this.parent();
},
cancel: function() {
this._greeterClient.call_cancel_sync(null);
this.destroy();
},
});