diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js index 4b9210f05..105908fd3 100644 --- a/js/gdm/loginDialog.js +++ b/js/gdm/loginDialog.js @@ -32,7 +32,6 @@ const Shell = imports.gi.Shell; const Signals = imports.signals; const St = imports.gi.St; -const Animation = imports.ui.animation; const Batch = imports.gdm.batch; const BoxPointer = imports.ui.boxpointer; const CtrlAltTab = imports.ui.ctrlAltTab; @@ -46,20 +45,11 @@ const UserWidget = imports.ui.userWidget; const _FADE_ANIMATION_TIME = 0.25; const _SCROLL_ANIMATION_TIME = 0.5; -const _DEFAULT_BUTTON_WELL_ICON_SIZE = 24; -const _DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; -const _DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; const _LOGO_ICON_HEIGHT = 48; let _loginDialog = null; -const DefaultButtonWellMode = { - NONE: 0, - SESSION_MENU_BUTTON: 1, - SPINNER: 2 -}; - const UserListItem = new Lang.Class({ Name: 'UserListItem', @@ -467,67 +457,21 @@ const LoginDialog = new Lang.Class({ x_fill: true, y_fill: true }); - this._promptBox = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', - vertical: true }); - - this._promptBox.connect('key-press-event', - Lang.bind(this, function(actor, event) { - if (event.get_key_symbol() == Clutter.KEY_Escape) { + this._authPrompt = new GdmUtil.AuthPrompt(); + this._authPrompt.hide(); + this._authPrompt.connect('cancel', + Lang.bind(this, function() { this.cancel(); - } })); - this._promptBox.add_constraint(new Clutter.AlignConstraint({ source: this.actor, - align_axis: Clutter.AlignAxis.BOTH, - factor: 0.5 })); + this._authPrompt.actor.add_constraint(new Clutter.AlignConstraint({ source: this.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); - this.actor.add_child(this._promptBox); - this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._promptBox, + this.actor.add_child(this._authPrompt.actor); + this._userList.actor.add_constraint(new Clutter.BindConstraint({ source: this._authPrompt.actor, coordinate: Clutter.BindCoordinate.WIDTH })); - this._promptUser = new St.Bin({ x_fill: true, - x_align: St.Align.START }); - this._promptBox.add(this._promptUser, - { x_align: St.Align.START, - x_fill: true, - y_fill: true, - expand: true }); - this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' }); - - this._promptBox.add(this._promptLabel, - { expand: true, - x_fill: true, - y_fill: true, - x_align: St.Align.START }); - this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry', - can_focus: true }); - this._promptEntryTextChangedId = 0; - this._promptEntryActivateId = 0; - this._promptBox.add(this._promptEntry, - { expand: true, - x_fill: true, - y_fill: false, - x_align: St.Align.START }); - - this._promptEntry.grab_key_focus(); - - this._promptMessage = new St.Label({ opacity: 0 }); - this._promptBox.add(this._promptMessage, { x_fill: true }); - - this._promptLoginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); - this._promptBox.add(this._promptLoginHint); - - this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', - vertical: false }); - this._promptBox.add(this._buttonBox, - { expand: true, - x_align: St.Align.MIDDLE, - y_align: St.Align.END }); - this._cancelButton = null; - this._signInButton = null; - - this._promptBox.hide(); - // translators: this message is shown below the user list on the // login screen. It can be activated to reveal an entry for // manually entering the username. @@ -577,8 +521,6 @@ const LoginDialog = new Lang.Class({ this._onUserListActivated(item); })); - this._defaultButtonWell = new St.Widget(); - this._defaultButtonWellMode = DefaultButtonWellMode.NONE; this._sessionMenuButton = new SessionMenuButton(); this._sessionMenuButton.connect('session-activated', @@ -587,17 +529,8 @@ const LoginDialog = new Lang.Class({ })); this._sessionMenuButton.actor.opacity = 0; this._sessionMenuButton.actor.show(); - this._defaultButtonWell.add_child(this._sessionMenuButton.actor); + this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor); - let spinnerIcon = global.datadir + '/theme/process-working.svg'; - this._workSpinner = new Animation.AnimatedIcon(spinnerIcon, _DEFAULT_BUTTON_WELL_ICON_SIZE); - this._workSpinner.actor.opacity = 0; - this._workSpinner.actor.show(); - - this._defaultButtonWell.add_child(this._workSpinner.actor); - this._sessionMenuButton.actor.add_constraint(new Clutter.AlignConstraint({ source: this._workSpinner.actor, - align_axis: Clutter.AlignAxis.BOTH, - factor: 0.5 })); }, _updateDisableUserList: function() { @@ -645,7 +578,8 @@ const LoginDialog = new Lang.Class({ this._userVerifier.clear(); this._updateSensitivity(true); - this._promptMessage.opacity = 0; + this._authPrompt.reset(); + this._user = null; this._verifyingUser = false; @@ -655,73 +589,11 @@ const LoginDialog = new Lang.Class({ this._showUserList(); }, - _getActorForDefaultButtonWellMode: function(mode) { - let actor; - - if (mode == DefaultButtonWellMode.NONE) - actor = null; - else if (mode == DefaultButtonWellMode.SPINNER) - actor = this._workSpinner.actor; - else if (mode == DefaultButtonWellMode.SESSION_MENU_BUTTON) - actor = this._sessionMenuButton.actor; - - return actor; - }, - - _setDefaultButtonWellMode: function(mode, immediately) { - if (this._defaultButtonWellMode == DefaultButtonWellMode.NONE && - mode == DefaultButtonWellMode.NONE) - return; - - let oldActor = this._getActorForDefaultButtonWellMode(this._defaultButtonWellMode); - - if (oldActor) - Tweener.removeTweens(oldActor); - - let actor = this._getActorForDefaultButtonWellMode(mode); - - if (this._defaultButtonWellMode != mode && oldActor) { - if (immediately) - oldActor.opacity = 0; - else - Tweener.addTween(oldActor, - { opacity: 0, - time: _DEFAULT_BUTTON_WELL_ANIMATION_TIME, - delay: _DEFAULT_BUTTON_WELL_ANIMATION_DELAY, - transition: 'linear', - onCompleteScope: this, - onComplete: function() { - if (mode == DefaultButtonWellMode.SPINNER) { - if (this._workSpinner) - this._workSpinner.stop(); - } - } - }); - - } - - if (actor) { - if (mode == DefaultButtonWellMode.SPINNER) - this._workSpinner.play(); - - if (immediately) - actor.opacity = 255; - else - Tweener.addTween(actor, - { opacity: 255, - time: _DEFAULT_BUTTON_WELL_ANIMATION_TIME, - delay: _DEFAULT_BUTTON_WELL_ANIMATION_DELAY, - transition: 'linear' }); - } - - this._defaultButtonWellMode = mode; - }, - _verificationFailed: function() { - this._promptEntry.text = ''; + this._authPrompt.clear(); this._updateSensitivity(true); - this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); + this._authPrompt.setActorInDefaultButtonWell(null); }, _onDefaultSessionChanged: function(client, sessionId) { @@ -729,23 +601,15 @@ const LoginDialog = new Lang.Class({ }, _showMessage: function(userVerifier, message, styleClass) { - if (message) { - this._promptMessage.text = message; - this._promptMessage.styleClass = styleClass; - this._promptMessage.opacity = 255; - } else { - this._promptMessage.opacity = 0; - } + this._authPrompt.setMessage(message, styleClass); }, _showLoginHint: function(verifier, message) { - this._promptLoginHint.set_text(message) - this._promptLoginHint.opacity = 255; + this._authPrompt.setHint(message); }, _hideLoginHint: function() { - this._promptLoginHint.opacity = 0; - this._promptLoginHint.set_text(''); + this._authPrompt.setHint(null); }, cancel: function() { @@ -769,27 +633,16 @@ const LoginDialog = new Lang.Class({ }, _showPrompt: function(forSecret) { - this._promptLabel.show(); - this._promptEntry.show(); - this._promptLoginHint.opacity = 0; - this._promptLoginHint.show(); - this._promptBox.opacity = 0; - this._promptBox.show(); - Tweener.addTween(this._promptBox, + this._authPrompt.actor.opacity = 0; + this._authPrompt.actor.show(); + Tweener.addTween(this._authPrompt.actor, { opacity: 255, time: _FADE_ANIMATION_TIME, transition: 'easeOutQuad' }); - if (this._shouldShowSessionMenuButton()) - this._setDefaultButtonWellMode(DefaultButtonWellMode.SESSION_MENU_BUTTON, true); - else - this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); - - this._promptEntry.grab_key_focus(); - let hold = new Batch.Hold(); let tasks = [function() { - this._prepareDialog(forSecret, hold); + this._preparePrompt(forSecret, hold); }, hold]; @@ -799,122 +652,50 @@ const LoginDialog = new Lang.Class({ return batch.run(); }, - _prepareDialog: function(forSecret, hold) { - this._buttonBox.visible = true; - this._buttonBox.remove_all_children(); - + _preparePrompt: function(forSecret, hold) { if (!this._disableUserList || this._verifyingUser) { - this._cancelButton = new St.Button({ style_class: 'modal-dialog-button', - button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, - reactive: true, - can_focus: true, - label: _("Cancel") }); - this._cancelButton.connect('clicked', - Lang.bind(this, function() { - this.cancel(); - })); - this._buttonBox.add(this._cancelButton, - { expand: false, - x_fill: false, - y_fill: false, - x_align: St.Align.START, - y_align: St.Align.END }); + this._authPrompt.cancelButton.show(); + } else { + this._authPrompt.cancelButton.hide(); } - this._buttonBox.add(this._defaultButtonWell, - { expand: true, - x_fill: false, - y_fill: false, - x_align: St.Align.END, - y_align: St.Align.MIDDLE }); - this._signInButton = new St.Button({ style_class: 'modal-dialog-button', - button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, - reactive: true, - can_focus: true, - label: forSecret ? C_("button", "Sign In") : _("Next") }); - this._signInButton.connect('clicked', - Lang.bind(this, function() { - hold.release(); - })); - this._signInButton.add_style_pseudo_class('default'); - this._buttonBox.add(this._signInButton, - { expand: false, - x_fill: false, - y_fill: false, - x_align: St.Align.END, - y_align: St.Align.END }); + if (forSecret) { + this._authPrompt.nextButton.label = C_("button", "Sign In"); + } else { + this._authPrompt.nextButton.label = _("Next"); + } - this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); - - this._promptEntryTextChangedId = - this._promptEntry.clutter_text.connect('text-changed', - Lang.bind(this, function() { - this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); - })); - - this._promptEntryActivateId = - this._promptEntry.clutter_text.connect('activate', function() { - hold.release(); - }); + let signalId = this._authPrompt.connect('next', Lang.bind(this, function() { + this._authPrompt.disconnect(signalId); + hold.release(); + })); }, _updateSensitivity: function(sensitive) { - this._promptEntry.reactive = sensitive; - this._promptEntry.clutter_text.editable = sensitive; this._sessionMenuButton.updateSensitivity(sensitive); - this._updateSignInButtonSensitivity(sensitive); - }, - - _updateSignInButtonSensitivity: function(sensitive) { - if (this._signInButton) { - this._signInButton.reactive = sensitive; - this._signInButton.can_focus = sensitive; - } - }, - - _hidePrompt: function() { - if (this._promptEntryTextChangedId > 0) { - this._promptEntry.clutter_text.disconnect(this._promptEntryTextChangedId); - this._promptEntryTextChangedId = 0; - } - - if (this._promptEntryActivateId > 0) { - this._promptEntry.clutter_text.disconnect(this._promptEntryActivateId); - this._promptEntryActivateId = 0; - } - - this._setDefaultButtonWellMode(DefaultButtonWellMode.NONE, true); - this._promptBox.hide(); - this._promptLoginHint.opacity = 0; - - this._promptUser.set_child(null); - - this._updateSensitivity(true); - this._promptEntry.set_text(''); - - this._sessionMenuButton.close(); - this._promptLoginHint.opacity = 0; - - this._buttonBox.remove_all_children(); - this._signInButton = null; - this._cancelButton = null; + this._authPrompt.updateSensitivity(sensitive); }, _askQuestion: function(verifier, serviceName, question, passwordChar) { - this._promptLabel.set_text(question); + this._authPrompt.setPasswordChar(passwordChar); + this._authPrompt.setQuestion(question); this._updateSensitivity(true); - this._promptEntry.set_text(''); - this._promptEntry.clutter_text.set_password_char(passwordChar); + + if (this._shouldShowSessionMenuButton()) + this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor); + else + this._authPrompt.setActorInDefaultButtonWell(null); let tasks = [function() { return this._showPrompt(!!passwordChar); }, function() { - let text = this._promptEntry.get_text(); + let text = this._authPrompt.getAnswer(); + this._updateSensitivity(false); - this._setDefaultButtonWellMode(DefaultButtonWellMode.SPINNER, false); + this._authPrompt.startSpinning(); this._userVerifier.answerQuery(serviceName, text); }]; @@ -936,9 +717,8 @@ const LoginDialog = new Lang.Class({ }, _askForUsernameAndLogIn: function() { - this._promptLabel.set_text(_("Username: ")); - this._promptEntry.set_text(''); - this._promptEntry.clutter_text.set_password_char(''); + this._authPrompt.setPasswordChar(''); + this._authPrompt.setQuestion(_("Username: ")); let realmManager = new Realmd.Manager(); let signalId = realmManager.connect('login-format-changed', @@ -948,8 +728,8 @@ const LoginDialog = new Lang.Class({ let tasks = [this._showPrompt, function() { - let userName = this._promptEntry.get_text(); - this._promptEntry.reactive = false; + let userName = this._authPrompt.getAnswer(); + this._authPrompt._entry.reactive = false; return this._beginVerificationForUser(userName); }, @@ -1134,7 +914,8 @@ const LoginDialog = new Lang.Class({ }, _showUserList: function() { - this._hidePrompt(); + this._authPrompt.hide(); + this._sessionMenuButton.close(); this._setUserListExpanded(true); this._notListedButton.show(); this._userList.actor.grab_key_focus(); @@ -1149,8 +930,7 @@ const LoginDialog = new Lang.Class({ }, _beginVerificationForItem: function(item) { - let userWidget = new UserWidget.UserWidget(item.user); - this._promptUser.set_child(userWidget.actor); + this._authPrompt.setUser(item.user); let tasks = [function() { let userName = item.user.get_user_name(); @@ -1218,7 +998,7 @@ const LoginDialog = new Lang.Class({ }, addCharacter: function(unichar) { - this._promptEntry.clutter_text.insert_unichar(unichar); + this._authPrompt.addCharacter(unichar); }, }); Signals.addSignalMethods(LoginDialog.prototype); diff --git a/js/gdm/util.js b/js/gdm/util.js index c9af991cc..ebf5dd5ce 100644 --- a/js/gdm/util.js +++ b/js/gdm/util.js @@ -6,12 +6,15 @@ const GLib = imports.gi.GLib; const Lang = imports.lang; const Mainloop = imports.mainloop; const Signals = imports.signals; +const St = imports.gi.St; +const Animation = imports.ui.animation; const Batch = imports.gdm.batch; const Fprint = imports.gdm.fingerprint; const Main = imports.ui.main; const Params = imports.misc.params; const Tweener = imports.ui.tweener; +const UserWidget = imports.ui.userWidget; const PASSWORD_SERVICE_NAME = 'gdm-password'; const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; @@ -30,6 +33,10 @@ const DISABLE_USER_LIST_KEY = 'disable-user-list'; // Give user 16ms to read each character of a PAM message const USER_READ_TIME = 16 +const DEFAULT_BUTTON_WELL_ICON_SIZE = 24; +const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0; +const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3; + function fadeInActor(actor) { if (actor.opacity == 255 && actor.visible) return null; @@ -458,3 +465,279 @@ const ShellUserVerifier = new Lang.Class({ }, }); Signals.addSignalMethods(ShellUserVerifier.prototype); + +const AuthPrompt = new Lang.Class({ + Name: 'AuthPrompt', + + _init: function() { + this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout', + vertical: true }); + this.actor.connect('key-press-event', + Lang.bind(this, function(actor, event) { + if (event.get_key_symbol() == Clutter.KEY_Escape) { + this.emit('cancel'); + } + })); + + this._userWell = new St.Bin({ x_fill: true, + x_align: St.Align.START }); + this.actor.add(this._userWell, + { x_align: St.Align.START, + x_fill: true, + y_fill: true, + expand: true }); + this._label = new St.Label({ style_class: 'login-dialog-prompt-label' }); + + this.actor.add(this._label, + { expand: true, + x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry', + can_focus: true }); + this.actor.add(this._entry, + { expand: true, + x_fill: true, + y_fill: false, + x_align: St.Align.START }); + + this._entry.grab_key_focus(); + + this._message = new St.Label({ opacity: 0 }); + this.actor.add(this._message, { x_fill: true }); + + this._loginHint = new St.Label({ style_class: 'login-dialog-prompt-login-hint-message' }); + this.actor.add(this._loginHint); + + this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box', + vertical: false }); + this.actor.add(this._buttonBox, + { expand: true, + x_align: St.Align.MIDDLE, + y_align: St.Align.END }); + + this._defaultButtonWell = new St.Widget(); + this._defaultButtonWellActor = null; + + this._initButtons(); + + let spinnerIcon = global.datadir + '/theme/process-working.svg'; + this._spinner = new Animation.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE); + this._spinner.actor.opacity = 0; + this._spinner.actor.show(); + this._defaultButtonWell.add_child(this._spinner.actor); + }, + + _initButtons: function() { + this.cancelButton = new St.Button({ style_class: 'modal-dialog-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + reactive: true, + can_focus: true, + label: _("Cancel") }); + this.cancelButton.connect('clicked', + Lang.bind(this, function() { + this.emit('cancel'); + })); + this._buttonBox.add(this.cancelButton, + { expand: false, + x_fill: false, + y_fill: false, + x_align: St.Align.START, + y_align: St.Align.END }); + + this._buttonBox.add(this._defaultButtonWell, + { expand: true, + x_fill: false, + y_fill: false, + x_align: St.Align.END, + y_align: St.Align.MIDDLE }); + this.nextButton = new St.Button({ style_class: 'modal-dialog-button', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + reactive: true, + can_focus: true, + label: _("Next") }); + this.nextButton.connect('clicked', + Lang.bind(this, function() { + this.emit('next'); + })); + this.nextButton.add_style_pseudo_class('default'); + this._buttonBox.add(this.nextButton, + { expand: false, + x_fill: false, + y_fill: false, + x_align: St.Align.END, + y_align: St.Align.END }); + + this._updateNextButtonSensitivity(this._entry.text.length > 0); + + this._entry.clutter_text.connect('text-changed', + Lang.bind(this, function() { + this._updateNextButtonSensitivity(this._entry.text.length > 0); + })); + this._entry.clutter_text.connect('activate', Lang.bind(this, function() { + this.emit('next'); + })); + }, + + addActorToDefaultButtonWell: function(actor) { + this._defaultButtonWell.add_child(actor); + + actor.add_constraint(new Clutter.AlignConstraint({ source: this._spinner.actor, + align_axis: Clutter.AlignAxis.BOTH, + factor: 0.5 })); + }, + + setActorInDefaultButtonWell: function(actor, animate) { + if (!this._defaultButtonWellActor && + !actor) + return; + + let oldActor = this._defaultButtonWellActor; + + if (oldActor) + Tweener.removeTweens(oldActor); + + let isSpinner; + if (actor == this._spinner.actor) + isSpinner = true; + else + isSpinner = false; + + if (this._defaultButtonWellActor != actor && oldActor) { + if (!animate) { + oldActor.opacity = 0; + } else { + Tweener.addTween(oldActor, + { opacity: 0, + time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, + delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, + transition: 'linear', + onCompleteScope: this, + onComplete: function() { + if (isSpinner) { + if (this._spinner) + this._spinner.stop(); + } + } + }); + } + } + + if (actor) { + if (isSpinner) + this._spinner.play(); + + if (!animate) + actor.opacity = 255; + else + Tweener.addTween(actor, + { opacity: 255, + time: DEFAULT_BUTTON_WELL_ANIMATION_TIME, + delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, + transition: 'linear' }); + } + + this._defaultButtonWellActor = actor; + }, + + startSpinning: function() { + this.setActorInDefaultButtonWell(this._spinner.actor, true); + }, + + stopSpinning: function() { + this.setActorInDefaultButtonWell(null, false); + }, + + clear: function() { + this._entry.text = ''; + this.stopSpinning(); + }, + + setPasswordChar: function(passwordChar) { + this._entry.clutter_text.set_password_char(passwordChar); + }, + + setQuestion: function(question) { + this._label.set_text(question); + + this._label.show(); + this._entry.show(); + + this._loginHint.opacity = 0; + this._loginHint.show(); + + this._entry.grab_key_focus(); + }, + + getAnswer: function() { + let text = this._entry.get_text(); + + return text; + }, + + setMessage: function(message, styleClass) { + if (message) { + this._message.text = message; + this._message.styleClass = styleClass; + this._message.opacity = 255; + } else { + this._message.opacity = 0; + } + }, + + _updateNextButtonSensitivity: function(sensitive) { + this.nextButton.reactive = sensitive; + this.nextButton.can_focus = sensitive; + }, + + updateSensitivity: function(sensitive) { + this._updateNextButtonSensitivity(sensitive); + this._entry.reactive = sensitive; + this._entry.clutter_text.editable = sensitive; + }, + + hide: function() { + this.setActorInDefaultButtonWell(null, true); + this.actor.hide(); + this._loginHint.opacity = 0; + + this.setUser(null); + + this.updateSensitivity(true); + this._entry.set_text(''); + }, + + setUser: function(user) { + if (user) { + let userWidget = new UserWidget.UserWidget(user); + this._userWell.set_child(userWidget.actor); + } else { + this._userWell.set_child(null); + } + }, + + setHint: function(message) { + if (message) { + this._loginHint.set_text(message) + this._loginHint.opacity = 255; + } else { + this._loginHint.opacity = 0; + this._loginHint.set_text(''); + } + }, + + reset: function() { + this._message.opacity = 0; + this.setUser(null); + this.stopSpinning(); + }, + + addCharacter: function(unichar) { + if (!this._entry.visible) + return; + + this._entry.grab_key_focus(); + this._entry.clutter_text.insert_unichar(unichar); + } +}); +Signals.addSignalMethods(AuthPrompt.prototype);