From 2c3ec7846fa563831aea4ef695ec3c16e89e1054 Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Sun, 20 May 2012 18:30:14 +0200 Subject: [PATCH] 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. https://bugzilla.gnome.org/show_bug.cgi?id=619955 --- data/theme/gdm.css | 188 ------------------------------- data/theme/gnome-shell.css | 186 +++++++++++++++++++++++++++++++ js/Makefile.am | 1 + js/ui/screenShield.js | 36 ++++-- js/ui/sessionMode.js | 2 +- js/ui/unlockDialog.js | 221 +++++++++++++++++++++++++++++++++++++ 6 files changed, 437 insertions(+), 197 deletions(-) delete mode 100644 data/theme/gdm.css create mode 100644 js/ui/unlockDialog.js diff --git a/data/theme/gdm.css b/data/theme/gdm.css deleted file mode 100644 index daaa57546..000000000 --- a/data/theme/gdm.css +++ /dev/null @@ -1,188 +0,0 @@ -/* Copyright 2011, Red Hat, Inc. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU Lesser General Public License, - * version 2.1, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for - * more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* Login Dialog */ - -.login-dialog-banner { - font-size: 10pt; - font-weight: bold; - text-align: center; - color: #666666; - padding-bottom: 1em; -} - -.login-dialog-title { - font-size: 14pt; - font-weight: bold; - color: #666666; - padding-bottom: 2em; -} - -.login-dialog { - border-radius: 16px; - min-height: 150px; - max-height: 700px; - min-width: 350px; -} - -.login-dialog-prompt-fingerprint-message { - font-size: 10.5pt; -} - -.login-dialog-user-list-view { - -st-vfade-offset: 1em; -} - -.login-dialog-user-list { - spacing: 12px; -} - -.login-dialog-user-list-item { - color: #666666; -} - -.login-dialog-user-list-item:ltr { - padding-right: 1em; -} - -.login-dialog-user-list-item:rtl { - padding-left: 1em; -} - -.login-dialog-user-list-item .login-dialog-user-list-item-name { - font-size: 20pt; - padding-left: 1em; - color: #666666; -} - -.login-dialog-user-list-item:hover .login-dialog-user-list-item-name { - color: white; -} - -.login-dialog-user-list-item:focus .login-dialog-user-list-item-name { - color: white; - text-shadow: black 0px 2px 2px; -} - -.login-dialog-user-list-item-vertical-layout { - spacing: 2px; -} - -.login-dialog-user-list-item .login-dialog-user-list-item-focus-bin { - background-color: rgba(0,0,0,0.0); - height: 2px; -} - -.login-dialog-user-list-item:focus .login-dialog-user-list-item-focus-bin { - background-color: #666666; -} - -.login-dialog-user-list-item-icon { - border: 2px solid #8b8b8b; - border-radius: 8px; - width: 64px; - height: 64px; -} - -.login-dialog-not-listed-button { - padding-top: 2em; -} -.login-dialog-not-listed-label { - font-size: 14pt; - font-weight: bold; - color: #666666; -} - -.login-dialog-not-listed-button:hover .login-dialog-not-listed-label { - color: white; -} - -.login-dialog-prompt-layout { - padding-bottom: 32px; -} -.login-dialog-prompt-label { - color: white; - font-size: 20pt; -} - -.login-dialog-prompt-entry { - padding: 4px; - border-radius: 4px; - border: 2px solid #5656cc; - color: black; - background-color: white; - caret-color: black; - caret-size: 1px; - width: 15em; -} - -.login-dialog-prompt-entry .capslock-warning { - icon-size: 16px; - warning-color: #999; -} - -.login-dialog-prompt-entry:insensitive { - color: rgba(0,0,0,0.7); - border: 2px solid #565656; -} - -.login-dialog-session-list { - color: #ffffff; - font-size: 10.5pt; -} - -.login-dialog-session-list-button { - padding: 4px; -} - -.login-dialog-session-list-button:focus { - background-color: #4c4c4c; -} - -.login-dialog-session-list-button:active { - background-color: #4c4c4c; -} - -.login-dialog-session-list-button:hover { - font-weight: bold; -} - -.login-dialog-session-list-scroll-view { - background-gradient-start: rgba(80,80,80,0.3); - background-gradient-end: rgba(80,80,80,0.7); - background-gradient-direction: vertical; - box-shadow: inset 0px 2px 4px rgba(0,0,0,0.9); - border-radius: 8px; - border: 1px solid rgba(80,80,80,1.0); - padding: .5em; -} - -.login-dialog-session-list-item:focus { - background-color: #666666; -} - -.login-dialog-session-list-triangle { - padding-right: .5em; -} - -.login-dialog-session-list-item-box { - spacing: .25em; -} - -.login-dialog-session-list-item-dot { - width: .75em; - height: .75em; -} diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index b3dcb821e..588117258 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -2008,3 +2008,189 @@ StScrollBar StButton#vhandle:hover border-radius: 4px; background-color: rgba(255,255,255,0.33); } + +/* Login Dialog */ + +.login-dialog-banner { + font-size: 10pt; + font-weight: bold; + text-align: center; + color: #666666; + padding-bottom: 1em; +} + +.login-dialog-title { + font-size: 14pt; + font-weight: bold; + color: #666666; + padding-bottom: 2em; +} + +.login-dialog { + border-radius: 16px; + min-height: 150px; + max-height: 700px; + min-width: 350px; +} + +.login-dialog-prompt-fingerprint-message { + font-size: 10.5pt; +} + +.login-dialog-user-list-view { + -st-vfade-offset: 1em; +} + +.login-dialog-user-list { + spacing: 12px; +} + +.login-dialog-user-list-item { + color: #666666; +} + +.login-dialog-user-list-item:ltr { + padding-right: 1em; +} + +.login-dialog-user-list-item:rtl { + padding-left: 1em; +} + +.login-dialog-user-list-item .login-dialog-user-list-item-name { + font-size: 20pt; + padding-left: 1em; + color: #666666; +} + +.login-dialog-user-list-item:hover .login-dialog-user-list-item-name { + color: white; +} + +.login-dialog-user-list-item:focus .login-dialog-user-list-item-name { + color: white; + text-shadow: black 0px 2px 2px; +} + +.login-dialog-user-list-item-vertical-layout { + spacing: 2px; +} + +.login-dialog-user-list-item .login-dialog-user-list-item-focus-bin { + background-color: rgba(0,0,0,0.0); + height: 2px; +} + +.login-dialog-user-list-item:focus .login-dialog-user-list-item-focus-bin { + background-color: #666666; +} + +.login-dialog-user-list-item-icon { + border: 2px solid #8b8b8b; + border-radius: 8px; + width: 64px; + height: 64px; +} + +.login-dialog-not-listed-label { + font-size: 14pt; + font-weight: bold; + color: #666666; +} + +.login-dialog-not-listed-button:hover .login-dialog-not-listed-label { + color: #E8E8E8; +} + +.login-dialog-prompt-layout { + padding-bottom: 32px; +} +.login-dialog-prompt-label { + color: white; + font-size: 20pt; +} + +.login-dialog-prompt-entry { + padding: 4px; + border-radius: 4px; + border: 2px solid #5656cc; + color: black; + background-color: white; + caret-color: black; + caret-size: 1px; + width: 15em; +} + +.login-dialog-prompt-entry .capslock-warning { + icon-size: 16px; + warning-color: #999; +} + +.login-dialog-prompt-entry:insensitive { + color: rgba(0,0,0,0.7); + border: 2px solid #565656; +} + +.login-dialog-session-list { + color: #ffffff; + font-size: 10.5pt; +} + +.login-dialog-session-list-button { + padding: 4px; +} + +.login-dialog-session-list-button:focus { + background-color: #4c4c4c; +} + +.login-dialog-session-list-button:active { + background-color: #4c4c4c; +} + +.login-dialog-session-list-button:hover { + font-weight: bold; +} + +.login-dialog-session-list-scroll-view { + background-gradient-start: rgba(80,80,80,0.3); + background-gradient-end: rgba(80,80,80,0.7); + background-gradient-direction: vertical; + box-shadow: inset 0px 2px 4px rgba(0,0,0,0.9); + border-radius: 8px; + border: 1px solid rgba(80,80,80,1.0); + padding: .5em; +} + +.login-dialog-session-list-item:focus { + background-color: #666666; +} + +.login-dialog-session-list-triangle { + padding-right: .5em; +} + +.login-dialog-session-list-item-box { + spacing: .25em; +} + +.login-dialog-session-list-item-dot { + width: .75em; + height: .75em; +} + +.unlock-dialog-user-name { + padding: 4px; + border-radius: 4px; + border: 2px solid #5656cc; + color: black; + background-color: white; + caret-color: black; + caret-size: 1px; + width: 15em; + text-align: right; +} + +.unlock-dialog-user-name-container { + spacing: .4em; +} diff --git a/js/Makefile.am b/js/Makefile.am index cd4136b3b..cf2353c81 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -91,6 +91,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 \ diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js index 1b963e302..b83debbfa 100644 --- a/js/ui/screenShield.js +++ b/js/ui/screenShield.js @@ -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'; @@ -48,8 +48,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); @@ -71,6 +70,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(); } @@ -84,13 +85,32 @@ 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() { + // 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._dialog = null; + this._popModal(); }, }); diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js index 8c259302b..dff6c8422 100644 --- a/js/ui/sessionMode.js +++ b/js/ui/sessionMode.js @@ -39,7 +39,7 @@ const _modes = { hasRunDialog: false, hasWorkspaces: false, createSession: Main.createGDMSession, - extraStylesheet: global.datadir + '/theme/gdm.css', + extraStylesheet: null, statusArea: { order: [ 'a11y', 'display', 'keyboard', diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js new file mode 100644 index 000000000..4a849c7f7 --- /dev/null +++ b/js/ui/unlockDialog.js @@ -0,0 +1,221 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const AccountsService = imports.gi.AccountsService; +const Clutter = imports.gi.Clutter; +const Gdm = imports.gi.Gdm; +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 Main = imports.ui.main; +const ModalDialog = imports.ui.modalDialog; +const ShellEntry = imports.ui.shellEntry; +const Tweener = imports.ui.tweener; +const UserMenu = imports.ui.userMenu; + +const Batch = imports.gdm.batch; +const GdmUtil = imports.gdm.util; + +// 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: 'unlock-dialog-user-name-container', + vertical: false }); + + this._avatar = new UserMenu.UserAvatarWidget(user); + this.actor.add(this._avatar.actor, + { x_fill: true, y_fill: true }); + + this._label = new St.Label({ style_class: 'unlock-dialog-user-name' }); + 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)); + if (this._user.is_loaded) + this._updateUser(); + }, + + destroy: function() { + 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() { + if (this._user.is_loaded) + this._label.text = this._user.get_real_name(); + else + this._label.text = ''; + + this._avatar.update(); + } +}); + +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 = new Gdm.Client(); + this._userVerifier = new GdmUtil.ShellUserVerifier(this._greeterClient); + + this._userVerifier.connect('reset', Lang.bind(this, this._reset)); + this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion)); + this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete)); + this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed)); + + this._userVerifier.connect('show-fingerprint-prompt', Lang.bind(this, this._showFingerprintPrompt)); + this._userVerifier.connect('hide-fingerprint-prompt', Lang.bind(this, this._hideFingerprintPrompt)); + + 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 }); + ShellEntry.addContextMenu(this._promptEntry); + this.setInitialKeyFocus(this._promptEntry); + this._promptEntry.clutter_text.connect('activate', Lang.bind(this, this._doUnlock)); + + 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); + + let otherUserLabel = new St.Label({ text: _("Login as another user"), + style_class: 'login-dialog-not-listed-label' }); + this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button', + can_focus: true, + child: otherUserLabel, + reactive: true, + x_align: St.Align.START, + x_fill: true }); + this._otherUserButton.connect('clicked', Lang.bind(this, this._otherUserClicked)); + this.contentLayout.add(this._otherUserButton, + { x_align: St.Align.START, + x_fill: false }); + + this._okButton = { label: _("Unlock"), + action: Lang.bind(this, this._doUnlock), + default: true }; + this.setButtons([this._okButton]); + + this._updateOkButton(false); + this._reset(); + }, + + _updateOkButton: function(sensitive) { + this._okButton.button.reactive = sensitive; + }, + + _reset: function() { + this._userVerifier.begin(this._userName, new Batch.Hold()); + }, + + _onAskQuestion: function(verifier, serviceName, question, passwordChar) { + this._promptLabel.text = question; + + this._promptEntry.text = ''; + this._promptEntry.clutter_text.set_password_char(passwordChar); + this._promptEntry.menu.isPassword = passwordChar != ''; + + this._currentQuery = serviceName; + this._updateOkButton(true); + }, + + _showFingerprintPrompt: function() { + GdmUtil.fadeInActor(this._promptFingerprintMessage); + }, + + _hideFingerprintPrompt: function() { + GdmUtil.fadeOutActor(this._promptFingerprintMessage); + }, + + _doUnlock: function() { + if (!this._currentQuery) + return; + + let query = this._currentQuery; + this._currentQuery = null; + + this._updateOkButton(false); + + this._userVerifier.answerQuery(query, this._promptEntry.text); + }, + + _onVerificationComplete: function() { + this._userVerifier.clear(); + this.emit('unlocked'); + }, + + _onVerificationFailed: function() { + this._userVerifier.cancel(); + this.emit('failed'); + }, + + _otherUserClicked: function(button, event) { + this._userManager.goto_login_session(); + + this._userVerifier.cancel(); + this.emit('failed'); + }, + + destroy: function() { + this._userVerifier.clear(); + this.parent(); + }, + + cancel: function() { + this._userVerifier.cancel(null); + + this.destroy(); + }, +});