diff --git a/data/dbus-interfaces/org.freedesktop.login1.Manager.xml b/data/dbus-interfaces/org.freedesktop.login1.Manager.xml
index f40d498dc..ae7fca6d9 100644
--- a/data/dbus-interfaces/org.freedesktop.login1.Manager.xml
+++ b/data/dbus-interfaces/org.freedesktop.login1.Manager.xml
@@ -20,6 +20,10 @@
+
+
+
+
diff --git a/data/dbus-interfaces/org.freedesktop.login1.User.xml b/data/dbus-interfaces/org.freedesktop.login1.User.xml
index d6f10b072..485188d24 100644
--- a/data/dbus-interfaces/org.freedesktop.login1.User.xml
+++ b/data/dbus-interfaces/org.freedesktop.login1.User.xml
@@ -2,5 +2,7 @@
+
+
diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js
index 8a46559d9..14e708997 100644
--- a/js/gdm/authPrompt.js
+++ b/js/gdm/authPrompt.js
@@ -52,6 +52,7 @@ export const AuthPrompt = GObject.registerClass({
'next': {},
'prompted': {},
'reset': {param_types: [GObject.TYPE_UINT]},
+ 'verified': {},
},
}, class AuthPrompt extends St.BoxLayout {
_init(gdmClient, mode) {
@@ -404,6 +405,7 @@ export const AuthPrompt = GObject.registerClass({
this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED;
this.cancelButton.reactive = false;
this.cancelButton.can_focus = false;
+ this.emit('verified');
}
_onReset() {
diff --git a/js/misc/loginManager.js b/js/misc/loginManager.js
index 1515fc9e3..edf451915 100644
--- a/js/misc/loginManager.js
+++ b/js/misc/loginManager.js
@@ -97,13 +97,12 @@ class LoginManagerSystemd extends Signals.EventEmitter {
this._proxy = new SystemdLoginManager(Gio.DBus.system,
'org.freedesktop.login1',
'/org/freedesktop/login1');
- this._userProxy = new SystemdLoginUser(Gio.DBus.system,
- 'org.freedesktop.login1',
- '/org/freedesktop/login1/user/self');
this._proxy.connectSignal('PrepareForSleep',
this._prepareForSleep.bind(this));
this._proxy.connectSignal('SessionRemoved',
this._sessionRemoved.bind(this));
+
+ this._userProxy = this._initUserProxy();
}
async getCurrentSessionProxy() {
@@ -113,19 +112,20 @@ class LoginManagerSystemd extends Signals.EventEmitter {
let sessionId = GLib.getenv('XDG_SESSION_ID');
if (!sessionId) {
log('Unset XDG_SESSION_ID, getCurrentSessionProxy() called outside a user session. Asking logind directly.');
- let [session, objectPath] = this._userProxy.Display;
+ let userProxy = await this._userProxy;
+ let [session, objectPath] = userProxy.Display;
if (session) {
log(`Will monitor session ${session}`);
sessionId = session;
} else {
log('Failed to find "Display" session; are we the greeter?');
- for ([session, objectPath] of this._userProxy.Sessions) {
+ for ([session, objectPath] of userProxy.Sessions) {
let sessionProxy = new SystemdLoginSession(Gio.DBus.system,
'org.freedesktop.login1',
objectPath);
log(`Considering ${session}, class=${sessionProxy.Class}`);
- if (sessionProxy.Class === 'greeter') {
+ if (sessionProxy.Class === 'greeter' || sessionProxy.Class === "lock-screen") {
log(`Yes, will monitor session ${session}`);
sessionId = session;
break;
@@ -217,6 +217,27 @@ class LoginManagerSystemd extends Signals.EventEmitter {
_sessionRemoved(proxy, sender, [sessionId]) {
this.emit('session-removed', sessionId);
}
+
+ async _initUserProxy() {
+ let [objectPath] = await this._proxy.GetUserByPIDAsync(0)
+
+ let proxy = new SystemdLoginUser(Gio.DBus.system,
+ 'org.freedesktop.login1',
+ objectPath);
+
+ return proxy;
+ }
+
+ async canSecureLock() {
+ const proxy = await this._userProxy;
+ return proxy.CanSecureLock;
+ }
+
+ async secureLock() {
+ const proxy = await this._userProxy;
+ if (proxy.CanSecureLock)
+ await proxy.SecureLockAsync(0);
+ }
}
class LoginManagerDummy extends Signals.EventEmitter {
@@ -261,4 +282,11 @@ class LoginManagerDummy extends Signals.EventEmitter {
async inhibit() {
return null;
}
+
+ async canSecureLock() {
+ return false;
+ }
+
+ async secureLock() {
+ }
}
diff --git a/js/misc/systemActions.js b/js/misc/systemActions.js
index efb875bac..45d9a2d0e 100644
--- a/js/misc/systemActions.js
+++ b/js/misc/systemActions.js
@@ -21,6 +21,7 @@ const ALWAYS_SHOW_LOG_OUT_KEY = 'always-show-log-out';
const POWER_OFF_ACTION_ID = 'power-off';
const RESTART_ACTION_ID = 'restart';
const LOCK_SCREEN_ACTION_ID = 'lock-screen';
+const SECURE_LOCK_ACTION_ID = 'secure-lock'
const LOGOUT_ACTION_ID = 'logout';
const SUSPEND_ACTION_ID = 'suspend';
const SWITCH_USER_ACTION_ID = 'switch-user';
@@ -57,6 +58,10 @@ const SystemActions = GObject.registerClass({
'can-lock-screen', 'can-lock-screen', 'can-lock-screen',
GObject.ParamFlags.READABLE,
false),
+ 'can-secure-lock': GObject.ParamSpec.boolean(
+ 'can-secure-lock', 'can-secure-lock', 'can-secure-lock',
+ GObject.ParamFlags.READABLE,
+ false),
'can-switch-user': GObject.ParamSpec.boolean(
'can-switch-user', 'can-switch-user', 'can-switch-user',
GObject.ParamFlags.READABLE,
@@ -110,6 +115,14 @@ const SystemActions = GObject.registerClass({
keywords: tokenizeKeywords(_('lock screen')),
available: false,
});
+ this._actions.set(SECURE_LOCK_ACTION_ID, {
+ // Translators: The name of the secure lock action in search
+ name: C_('search-result', 'Secure Lock'),
+ iconName: 'security-high-symbolic',
+ // Translators: A list of keywords that match the secure lock action, separated by semicolons
+ keywords: tokenizeKeywords(_('encrypt;lock down;lockdown')),
+ available: false,
+ });
this._actions.set(LOGOUT_ACTION_ID, {
// Translators: The name of the logout action in search
name: C_('search-result', 'Log Out'),
@@ -176,8 +189,10 @@ const SystemActions = GObject.registerClass({
global.settings.connect(`changed::${ALWAYS_SHOW_LOG_OUT_KEY}`,
() => this._updateLogout());
- this._lockdownSettings.connect(`changed::${DISABLE_LOCK_SCREEN_KEY}`,
- () => this._updateLockScreen());
+ this._lockdownSettings.connect(`changed::${DISABLE_LOCK_SCREEN_KEY}`, () => {
+ this._updateLockScreen();
+ this._updateSecureLock();
+ });
this._lockdownSettings.connect(`changed::${DISABLE_LOG_OUT_KEY}`,
() => this._updateHaveShutdown());
@@ -215,6 +230,10 @@ const SystemActions = GObject.registerClass({
return this._actions.get(LOCK_SCREEN_ACTION_ID).available;
}
+ get canSecureLock() {
+ return this._actions.get(SECURE_LOCK_ACTION_ID).available;
+ }
+
get canSwitchUser() {
return this._actions.get(SWITCH_USER_ACTION_ID).available;
}
@@ -271,6 +290,9 @@ const SystemActions = GObject.registerClass({
// latter, so their value may be outdated; force an update now
this._updateHaveShutdown();
this._updateHaveSuspend();
+
+ // CanSecureLock changes w/o notification in logind
+ this._updateSecureLock();
}
getMatchingActions(terms) {
@@ -311,6 +333,9 @@ const SystemActions = GObject.registerClass({
case LOCK_SCREEN_ACTION_ID:
this.activateLockScreen();
break;
+ case SECURE_LOCK_ACTION_ID:
+ this.activateSecureLock();
+ break;
case LOGOUT_ACTION_ID:
this.activateLogout();
break;
@@ -330,12 +355,21 @@ const SystemActions = GObject.registerClass({
}
_updateLockScreen() {
- let showLock = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
- let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY);
- this._actions.get(LOCK_SCREEN_ACTION_ID).available = showLock && allowLockScreen && LoginManager.canLock();
+ let showLock = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter &&
+ !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY);
+ let canLock = LoginManager.canLock();
+ this._actions.get(LOCK_SCREEN_ACTION_ID).available = showLock && canLock;
this.notify('can-lock-screen');
}
+ async _updateSecureLock() {
+ let showLock = !Main.sessionMode.isGreeter &&
+ !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY);
+ let canSecureLock = await this._loginManager.canSecureLock();
+ this._actions.get(SECURE_LOCK_ACTION_ID).available = showLock && canSecureLock;
+ this.notify('can-secure-lock');
+ }
+
async _updateHaveShutdown() {
try {
const [canShutdown] = await this._session.CanShutdownAsync();
@@ -423,6 +457,13 @@ const SystemActions = GObject.registerClass({
Main.screenShield.lock(true);
}
+ activateSecureLock() {
+ if (!this._actions.get(SECURE_LOCK_ACTION_ID).available)
+ throw new Error('The secure-lock action is not available!');
+
+ this._loginManager.secureLock();
+ }
+
activateSwitchUser() {
if (!this._actions.get(SWITCH_USER_ACTION_ID).available)
throw new Error('The switch-user action is not available!');
diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js
index d2236fb90..f1e0c9a01 100644
--- a/js/ui/screenShield.js
+++ b/js/ui/screenShield.js
@@ -178,7 +178,7 @@ export class ScreenShield extends Signals.EventEmitter {
_activateDialog() {
if (this._isLocked) {
- this._ensureUnlockDialog(true /* allowCancel */);
+ this._ensureUnlockDialog();
this._dialog.activate();
} else {
this.deactivate(true /* animate */);
@@ -357,7 +357,8 @@ export class ScreenShield extends Signals.EventEmitter {
this.actor.show();
this._isGreeter = Main.sessionMode.isGreeter;
this._isLocked = true;
- this._ensureUnlockDialog(true);
+ this._lockScreenState = MessageTray.State.SHOWN;
+ this._ensureUnlockDialog();
}
_hideLockScreenComplete() {
@@ -416,7 +417,7 @@ export class ScreenShield extends Signals.EventEmitter {
this._showPointer();
}
- _ensureUnlockDialog(allowCancel) {
+ _ensureUnlockDialog() {
if (!this._dialog) {
let constructor = Main.sessionMode.unlockDialog;
if (!constructor) {
@@ -440,7 +441,6 @@ export class ScreenShield extends Signals.EventEmitter {
'wake-up-screen', this._wakeUpScreen.bind(this));
}
- this._dialog.allowCancel = allowCancel;
this._dialog.grab_key_focus();
return true;
}
@@ -605,7 +605,7 @@ export class ScreenShield extends Signals.EventEmitter {
if (this._activationTime === 0)
this._activationTime = GLib.get_monotonic_time();
- if (!this._ensureUnlockDialog(true))
+ if (!this._ensureUnlockDialog())
return;
this.actor.show();
@@ -620,6 +620,7 @@ export class ScreenShield extends Signals.EventEmitter {
animateLockScreen: animate,
fadeToBlack: true,
});
+
// On wayland, a crash brings down the entire session, so we don't
// need to defend against being restarted unlocked
if (!Meta.is_wayland_compositor())
diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js
index 895b507e1..bbfbdc054 100644
--- a/js/ui/sessionMode.js
+++ b/js/ui/sessionMode.js
@@ -78,6 +78,19 @@ const _modes = {
panelStyle: 'unlock-screen',
},
+ 'lock-screen': {
+ isGreeter: true,
+ isPrimary: true,
+ unlockDialog: UnlockDialog,
+ components: ['polkitAgent'],
+ panel: {
+ left: [],
+ center: [],
+ right: ['dwellClick', 'a11y', 'keyboard', 'quickSettings'],
+ },
+ panelStyle: 'unlock-screen',
+ },
+
'user': {
hasOverview: true,
showCalendarEvents: true,
diff --git a/js/ui/status/system.js b/js/ui/status/system.js
index ff9fa5624..dbdb68ea0 100644
--- a/js/ui/status/system.js
+++ b/js/ui/status/system.js
@@ -192,6 +192,11 @@ class ShutdownItem extends QuickSettingsItem {
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+ this._addSystemAction(_('Secure Lock'), 'can-secure-lock', () => {
+ this._systemActions.activateSecureLock();
+ Main.panel.closeQuickSettings();
+ });
+
this._addSystemAction(_('Log Out…'), 'can-logout', () => {
this._systemActions.activateLogout();
Main.panel.closeQuickSettings();
diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js
index f741b20a6..6083a74af 100644
--- a/js/ui/unlockDialog.js
+++ b/js/ui/unlockDialog.js
@@ -535,21 +535,38 @@ export const UnlockDialog = GObject.registerClass({
this.add_action(tapAction);
// Background
- this._backgroundGroup = new Clutter.Actor();
- this.add_child(this._backgroundGroup);
+ if (!Main.sessionMode.isGreeter) {
+ // We don't currently support storing a background picture for
+ // user accounts, so for now the secure lock screen will just have
+ // a blank background.
- this._bgManagers = [];
+ this._backgroundGroup = new Clutter.Actor();
+ this.add_child(this._backgroundGroup);
- const themeContext = St.ThemeContext.get_for_stage(global.stage);
- themeContext.connectObject('notify::scale-factor',
- () => this._updateBackgroundEffects(), this);
+ this._bgManagers = [];
- this._updateBackgrounds();
- Main.layoutManager.connectObject('monitors-changed',
- this._updateBackgrounds.bind(this), this);
+ const themeContext = St.ThemeContext.get_for_stage(global.stage);
+ themeContext.connectObject('notify::scale-factor',
+ () => this._updateBackgroundEffects(), this);
+
+ this._updateBackgrounds();
+ Main.layoutManager.connectObject('monitors-changed',
+ this._updateBackgrounds.bind(this), this);
+ }
this._userManager = AccountsService.UserManager.get_default();
- this._userName = GLib.get_user_name();
+ if (!Main.sessionMode.isGreeter) {
+ this._userName = GLib.get_user_name();
+ } else {
+ this._userName = GLib.getenv('GDM_LOCK_SCREEN_USER');
+ if (!this._userName) {
+ // In the secure lock screen, this is a hard error. Fail-whale
+ const error = new GLib.Error(
+ Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED,
+ 'Could not get user for GDM lock screen');
+ global.context.terminate_with_error(error);
+ }
+ }
this._user = this._userManager.get_user(this._userName);
// Authentication & Clock stack
@@ -565,8 +582,6 @@ export const UnlockDialog = GObject.registerClass({
this._stack.add_child(this._clock);
this._showClock();
- this.allowCancel = false;
-
Main.ctrlAltTabManager.addGroup(this, _('Unlock Window'), 'dialog-password-symbolic');
// Notifications
@@ -706,6 +721,7 @@ export const UnlockDialog = GObject.registerClass({
this._authPrompt.connect('failed', this._fail.bind(this));
this._authPrompt.connect('cancelled', this._fail.bind(this));
this._authPrompt.connect('reset', this._onReset.bind(this));
+ this._authPrompt.connect('verified', this._onVerified.bind(this));
this._promptBox.add_child(this._authPrompt);
}
@@ -802,8 +818,16 @@ export const UnlockDialog = GObject.registerClass({
this._authPrompt.begin({userName});
}
+ _onVerified() {
+ // Make sure we don't get stuck at the successfully authenticated
+ // prompt, when we're running as the secure lock screen in GDM and
+ // the user immediately re-triggers a secure-lock
+ if (Main.sessionMode.isGreeter)
+ this._showClock();
+ }
+
_escape() {
- if (this._authPrompt && this.allowCancel)
+ if (this._authPrompt && !Main.sessionMode.isGreeter)
this._authPrompt.cancel();
}
@@ -862,10 +886,11 @@ export const UnlockDialog = GObject.registerClass({
}
_updateUserSwitchVisibility() {
- this._otherUserButton.visible = this._userManager.can_switch() &&
- this._userManager.has_multiple_users &&
- this._screenSaverSettings.get_boolean('user-switch-enabled') &&
- !this._lockdownSettings.get_boolean('disable-user-switching');
+ let needed = this._userManager.can_switch() &&
+ this._userManager.has_multiple_users;
+ let enabled = this._screenSaverSettings.get_boolean('user-switch-enabled') &&
+ !this._lockdownSettings.get_boolean('disable-user-switching');
+ this._otherUserButton.visible = needed && (enabled || Main.sessionMode.isGreeter);
}
cancel() {