diff --git a/data/theme/gnome-shell-sass/widgets/_login-lock.scss b/data/theme/gnome-shell-sass/widgets/_login-lock.scss index b661e93c8d2aae131eb81efbd05a9d2300772fa7..d11d7224bc4f9e98f47e4d042a829ae7824ea8b1 100644 --- a/data/theme/gnome-shell-sass/widgets/_login-lock.scss +++ b/data/theme/gnome-shell-sass/widgets/_login-lock.scss @@ -258,6 +258,21 @@ $_gdm_dialog_width: 25em; } } +// Parental controls +.parental-controls-shield { + spacing: 0.75em; + + .parental-controls-shield-title { + margin-top: 0.6em; + @extend %title_4; + text-align: center; + } + + .parental-controls-shield-description { + text-align: center; + } +} + // Notifications #unlockDialogNotifications { > .vhandle, > .hhandle { diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js index 9282d236ec256f112642c58547075a979e0344f1..4a9e4f67b34e0d9f12ea5234906c931678f13b6c 100644 --- a/js/gdm/authPrompt.js +++ b/js/gdm/authPrompt.js @@ -17,6 +17,33 @@ import {wiggle} from '../misc/animationUtils.js'; const DEFAULT_BUTTON_WELL_ICON_SIZE = 16; const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1000; + +// A widget displayed instead of the unlock prompt +// when parental controls session limits are reached +const ParentalControlsShield = GObject.registerClass( +class ParentalControlsShield extends St.BoxLayout { + _init() { + super._init({ + style_class: 'parental-controls-shield', + orientation: Clutter.Orientation.VERTICAL, + x_align: Clutter.ActorAlign.CENTER, + }); + + this._titleLabel = new St.Label({ + style_class: 'parental-controls-shield-title', + text: _('Screen Time Limit Reached'), + }); + this.add_child(this._titleLabel); + + this._descriptionLabel = new St.Label({ + style_class: 'parental-controls-shield-description', + text: _('Daily limit for screen time on this device has been reached. Resume tomorrow.'), + }); + this._descriptionLabel.clutter_text.line_wrap = true; + this.add_child(this._descriptionLabel); + } +}); + export const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 300; export const MESSAGE_FADE_OUT_ANIMATION_TIME = 500; @@ -95,18 +122,27 @@ export const AuthPrompt = GObject.registerClass({ }); this.add_child(this._userWell); + this._inputWell = new St.BoxLayout({ + style_class: 'login-dialog-prompt-layout', + orientation: Clutter.Orientation.VERTICAL, + x_align: Clutter.ActorAlign.CENTER, + x_expand: true, + }); + this.add_child(this._inputWell); + this._mainContent = this._inputWell; + this._hasCancelButton = this._mode === AuthPromptMode.UNLOCK_OR_LOG_IN; this._initInputRow(); const capsLockPlaceholder = new St.Label(); - this.add_child(capsLockPlaceholder); + this._inputWell.add_child(capsLockPlaceholder); this._capsLockWarningLabel = new ShellEntry.CapsLockWarning({ x_expand: true, x_align: Clutter.ActorAlign.CENTER, }); - this.add_child(this._capsLockWarningLabel); + this._inputWell.add_child(this._capsLockWarningLabel); this._capsLockWarningLabel.bind_property('visible', capsLockPlaceholder, 'visible', @@ -122,7 +158,7 @@ export const AuthPrompt = GObject.registerClass({ }); this._message.clutter_text.line_wrap = true; this._message.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; - this.add_child(this._message); + this._inputWell.add_child(this._message); } _createUserVerifier(gdmClient, params) { @@ -150,7 +186,7 @@ export const AuthPrompt = GObject.registerClass({ style_class: 'login-dialog-button-box', orientation: Clutter.Orientation.HORIZONTAL, }); - this.add_child(this._mainBox); + this._inputWell.add_child(this._mainBox); this.cancelButton = new St.Button({ style_class: 'login-dialog-button cancel-button', @@ -213,7 +249,7 @@ export const AuthPrompt = GObject.registerClass({ scale_x: 0, }); - this.add_child(this._timedLoginIndicator); + this._inputWell.add_child(this._timedLoginIndicator); [this._textEntry, this._passwordEntry].forEach(entry => { entry.clutter_text.connect('text-changed', () => { @@ -691,6 +727,25 @@ export const AuthPrompt = GObject.registerClass({ this._entry.clutter_text.insert_unichar(unichar); } + /* + * Set whether to block the authentication with the parental controls shield. + * + * @param {boolean} shouldBlock Whether to block the authentication + */ + setAuthBlocked(shouldBlock) { + if (!this._parentalControlsShield) + this._parentalControlsShield = new ParentalControlsShield(); + + const newMainContent = shouldBlock + ? this._parentalControlsShield + : this._inputWell; + + if (newMainContent !== this._mainContent) { + this.replace_child(this._mainContent, newMainContent); + this._mainContent = newMainContent; + } + } + begin(params) { params = Params.parse(params, { userName: null, diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js index 676a2fe8ba103d877d6dbdbb639513379fbc139a..c151222b4675c014e5643a9cdb759f14de043e5d 100644 --- a/js/ui/unlockDialog.js +++ b/js/ui/unlockDialog.js @@ -15,6 +15,7 @@ import * as Main from './main.js'; import * as MessageTray from './messageTray.js'; import * as SwipeTracker from './swipeTracker.js'; import {formatDateWithCFormatString} from '../misc/dateUtils.js'; +import {TimeLimitsState} from '../misc/timeLimitsManager.js'; import * as AuthPrompt from '../gdm/authPrompt.js'; import {AuthPromptStatus} from '../gdm/authPrompt.js'; import {MprisSource} from './mpris.js'; @@ -642,6 +643,14 @@ export const UnlockDialog = GObject.registerClass({ this._updateUserSwitchVisibility(); + // When parental controls session limits are enabled, the screen will be + // locked upon reaching the time limit. In those cases, tweak the lock screen, + // so that the children cannot unlock without parental supervision. + Main.timeLimitsManager.connectObject( + 'notify::state', () => this._updateAuthBlocked(), + this); + this._updateAuthBlocked(); + // Main Box const mainBox = new St.Widget(); mainBox.add_constraint(new Layout.MonitorConstraint({primary: true})); @@ -757,6 +766,8 @@ export const UnlockDialog = GObject.registerClass({ this._authPrompt.updateSensitivity( verificationStatus === AuthPromptStatus.NOT_VERIFYING); } + + this._updateAuthBlocked(); } _maybeDestroyAuthPrompt() { @@ -914,6 +925,11 @@ export const UnlockDialog = GObject.registerClass({ !this._lockdownSettings.get_boolean('disable-user-switching'); } + _updateAuthBlocked() { + this._authPrompt?.setAuthBlocked( + Main.timeLimitsManager.state === TimeLimitsState.LIMIT_REACHED); + } + cancel() { if (this._authPrompt) this._authPrompt.cancel();