From a569f419b6e13af56cbdc5d4a3d0ec6ca4a37232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20Kuchci=C5=84ski?= Date: Wed, 3 Dec 2025 15:43:18 +0100 Subject: [PATCH 1/3] authPrompt: Group prompt elements Group various prompt related elements together, so it's easier in the future to hide them when parental controls session limits are enforced. Part-of: --- js/gdm/authPrompt.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js index 9282d236ec..2ad29b0e85 100644 --- a/js/gdm/authPrompt.js +++ b/js/gdm/authPrompt.js @@ -95,18 +95,26 @@ 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._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 +130,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 +158,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 +221,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', () => { -- GitLab From 3a481fa5c93139e7d353a5a5eeb5d7e737e89cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20Kuchci=C5=84ski?= Date: Wed, 3 Dec 2025 00:51:44 +0100 Subject: [PATCH 2/3] authPrompt: Add ability to block authentication Make it possible to prevent the user from using the authentication prompt, by showing the parental controls shield instead of the password entry. Part-of: --- .../gnome-shell-sass/widgets/_login-lock.scss | 15 ++++++ js/gdm/authPrompt.js | 47 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/data/theme/gnome-shell-sass/widgets/_login-lock.scss b/data/theme/gnome-shell-sass/widgets/_login-lock.scss index b661e93c8d..d11d7224bc 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 2ad29b0e85..4a9e4f67b3 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; @@ -102,6 +129,7 @@ export const AuthPrompt = GObject.registerClass({ x_expand: true, }); this.add_child(this._inputWell); + this._mainContent = this._inputWell; this._hasCancelButton = this._mode === AuthPromptMode.UNLOCK_OR_LOG_IN; @@ -699,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, -- GitLab From fc1e236ce90afba923cee978d5dde97e0abce8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20Kuchci=C5=84ski?= Date: Tue, 25 Nov 2025 05:48:20 +0100 Subject: [PATCH 3/3] unlockDialog: Block auth when limit reached Block ability to authenticate when screen time limit is reached, so that the child cannot unlock. Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/work_items/8577 Part-of: --- js/ui/unlockDialog.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js index 676a2fe8ba..c151222b46 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(); -- GitLab