diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 66f80505b..98771dbef 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -1678,6 +1678,10 @@ StTooltip StLabel { icon-size: 48px; } +.mount-password-reask { + color: red; +} + .show-processes-dialog, .mount-question-dialog { spacing: 24px; diff --git a/js/ui/automountManager.js b/js/ui/automountManager.js index 9bc426f94..7a22e1d06 100644 --- a/js/ui/automountManager.js +++ b/js/ui/automountManager.js @@ -178,8 +178,14 @@ AutomountManager.prototype = { try { volume.mount_finish(res); } catch (e) { - log('Unable to mount volume ' + volume.get_name() + ': ' + - e.toString()); + let string = e.toString(); + + // FIXME: needs proper error code handling instead of this + // See https://bugzilla.gnome.org/show_bug.cgi?id=591480 + if (string.indexOf('No key available with this passphrase') != -1) + this._reaskPassword(volume); + else + log('Unable to mount volume ' + volume.get_name() + ': ' + string); } }, @@ -190,6 +196,11 @@ AutomountManager.prototype = { }); }, + _reaskPassword: function(volume) { + let operation = new ShellMountOperation.ShellMountOperation(volume, { reaskPassword: true }); + this._mountVolume(volume, operation.mountOp); + }, + _allowAutorun: function(volume) { volume.allowAutorun = true; }, diff --git a/js/ui/shellMountOperation.js b/js/ui/shellMountOperation.js index 438210c5b..d85e77fe6 100644 --- a/js/ui/shellMountOperation.js +++ b/js/ui/shellMountOperation.js @@ -8,7 +8,10 @@ const Pango = imports.gi.Pango; const St = imports.gi.St; const Shell = imports.gi.Shell; +const Main = imports.ui.main; +const MessageTray = imports.ui.messageTray; const ModalDialog = imports.ui.modalDialog; +const Params = imports.misc.params; const LIST_ITEM_ICON_SIZE = 48; @@ -86,12 +89,16 @@ ListItem.prototype = { }; Signals.addSignalMethods(ListItem.prototype); -function ShellMountOperation(source) { - this._init(source); +function ShellMountOperation(source, params) { + this._init(source, params); } ShellMountOperation.prototype = { - _init: function(source) { + _init: function(source, params) { + params = Params.parse(params, { reaskPassword: false }); + + this._reaskPassword = params.reaskPassword; + this._dialog = null; this._processesDialog = null; @@ -126,8 +133,27 @@ ShellMountOperation.prototype = { this._dialog.open(global.get_current_time()); }, - _onAskPassword: function(op, message, defaultUser, defaultDomain, flags) { - // TODO + _onAskPassword: function(op, message) { + this._notificationShowing = true; + this._source = new ShellMountPasswordSource(message, this._icon, this._reaskPassword); + + this._source.connect('password-ready', + Lang.bind(this, function(source, password) { + this.mountOp.set_password(password); + this.mountOp.reply(Gio.MountOperationResult.HANDLED); + + this._notificationShowing = false; + this._source.destroy(); + })); + + this._source.connect('destroy', + Lang.bind(this, function() { + if (!this._notificationShowing) + return; + + this._notificationShowing = false; + this.mountOp.reply(Gio.MountOperationResult.ABORTED); + })); }, _onAborted: function(op) { @@ -213,6 +239,74 @@ ShellMountQuestionDialog.prototype = { } Signals.addSignalMethods(ShellMountQuestionDialog.prototype); +function ShellMountPasswordSource(message, icon, reaskPassword) { + this._init(message, icon, reaskPassword); +} + +ShellMountPasswordSource.prototype = { + __proto__: MessageTray.Source.prototype, + + _init: function(message, icon, reaskPassword) { + let strings = message.split('\n'); + MessageTray.Source.prototype._init.call(this, strings[0]); + + this._notification = new ShellMountPasswordNotification(this, strings, icon, reaskPassword); + + // add ourselves as a source, and popup the notification + Main.messageTray.add(this); + this.notify(this._notification); + }, +} +Signals.addSignalMethods(ShellMountPasswordSource.prototype); + +function ShellMountPasswordNotification(source, strings, icon, reaskPassword) { + this._init(source, strings, icon, reaskPassword); +} + +ShellMountPasswordNotification.prototype = { + __proto__: MessageTray.Notification.prototype, + + _init: function(source, strings, icon, reaskPassword) { + MessageTray.Notification.prototype._init.call(this, source, + strings[0], null, + { customContent: true, + icon: icon }); + + // set the notification to transient and urgent, so that it + // expands out + this.setTransient(true); + this.setUrgency(MessageTray.Urgency.CRITICAL); + + if (strings[1]) + this.addBody(strings[1]); + + if (reaskPassword) { + let label = new St.Label({ style_class: 'mount-password-reask', + text: _("Wrong password, please try again") }); + + this.addActor(label); + } + + this._responseEntry = new St.Entry({ style_class: 'mount-password-entry', + can_focus: true }); + this.setActionArea(this._responseEntry); + + this._responseEntry.clutter_text.connect('activate', + Lang.bind(this, this._onEntryActivated)); + this._responseEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE + + this._responseEntry.grab_key_focus(); + }, + + _onEntryActivated: function() { + let text = this._responseEntry.get_text(); + if (text == '') + return; + + this.source.emit('password-ready', text); + } +} + function ShellProcessesDialog(icon) { this._init(icon); } diff --git a/src/shell-mount-operation.c b/src/shell-mount-operation.c index 0d86ef92e..9f172eda2 100644 --- a/src/shell-mount-operation.c +++ b/src/shell-mount-operation.c @@ -26,7 +26,8 @@ * object from JS but we can't yet; the default GMountOperation impl * automatically calls g_mount_operation_reply(UNHANDLED) after an idle, * in interactive methods. We want to handle the reply outselves - * instead, so we just override the default methods with empty ones. + * instead, so we just override the default methods with empty ones, + * except for ask-password, as we don't want to handle that. * * Also, we need to workaround the fact that gjs doesn't support type * annotations for signals yet (so we can't effectively forward e.g.