diff --git a/data/dbus-interfaces/org.freedesktop.MalcontentTimer1.Child.xml b/data/dbus-interfaces/org.freedesktop.MalcontentTimer1.Child.xml index ee5e714ba617f86e27d2522a5c85c340a79b99b4..649aa020fcb383d9a1dd7bcfcb6a19e92955e55c 100644 --- a/data/dbus-interfaces/org.freedesktop.MalcontentTimer1.Child.xml +++ b/data/dbus-interfaces/org.freedesktop.MalcontentTimer1.Child.xml @@ -31,8 +31,9 @@ The `MalcontentTimer1.Child` interface allows a trusted component in a child user account’s session to record screen time and app usage periods - for that account. Such a component might be the shell process of the - session, for example. + for that account, to query the time remaining before the limit is reached, + and to request extensions to the screen time limits. Such a component + might be the shell process of the session, for example. Data is recorded as tuples of (start time, end time, record type, identifier), where the record type is @@ -123,5 +124,90 @@ stateless. --> + + + + + + + + + + + + + + + + + diff --git a/data/theme/gnome-shell-sass/widgets/_login-lock.scss b/data/theme/gnome-shell-sass/widgets/_login-lock.scss index d11d7224bc4f9e98f47e4d042a829ae7824ea8b1..5929ccc572d626315c1ea1758dfb4883a9569bb2 100644 --- a/data/theme/gnome-shell-sass/widgets/_login-lock.scss +++ b/data/theme/gnome-shell-sass/widgets/_login-lock.scss @@ -271,6 +271,16 @@ $_gdm_dialog_width: 25em; .parental-controls-shield-description { text-align: center; } + + .parental-controls-shield-button { + margin-top: 0.85em; + @extend %lockscreen_button; + @extend %title_4; + padding: $base_margin * 4 $base_margin * 11; + color: $_gdm_fg; + background-color: transparentize($_gdm_fg, .9); + border-radius: $forced_circular_radius; + } } // Notifications diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js index 4a9e4f67b34e0d9f12ea5234906c931678f13b6c..1d5a5f26b12f03eeeaaad34fea862e34228c072d 100644 --- a/js/gdm/authPrompt.js +++ b/js/gdm/authPrompt.js @@ -1,4 +1,5 @@ import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import Atk from 'gi://Atk'; import GObject from 'gi://GObject'; @@ -15,6 +16,11 @@ import * as ShellEntry from '../ui/shellEntry.js'; import * as UserWidget from '../ui/userWidget.js'; import {wiggle} from '../misc/animationUtils.js'; +import {loadInterfaceXML} from '../misc/fileUtils.js'; + +const TimerChildIface = loadInterfaceXML('org.freedesktop.MalcontentTimer1.Child'); +const TimerChildProxy = Gio.DBusProxy.makeProxyWrapper(TimerChildIface); + const DEFAULT_BUTTON_WELL_ICON_SIZE = 16; const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1000; @@ -29,6 +35,24 @@ class ParentalControlsShield extends St.BoxLayout { x_align: Clutter.ActorAlign.CENTER, }); + this._requestExtensionCookie = null; + + this._timerChildProxy = TimerChildProxy(Gio.DBus.system, + 'org.freedesktop.MalcontentTimer1', + '/org/freedesktop/MalcontentTimer1', + (proxy, error) => { + if (error) + console.error(`Failed to get TimerChild proxy: ${error}`); + }, + null, /* cancellable */ + Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION + ); + + this._timerChildProxy.connectSignal('ExtensionResponse', (proxy, sender, params) => + this._onExtensionResponse(proxy, sender, params)); + + this.connect('destroy', this._onDestroy.bind(this)); + this._titleLabel = new St.Label({ style_class: 'parental-controls-shield-title', text: _('Screen Time Limit Reached'), @@ -41,6 +65,45 @@ class ParentalControlsShield extends St.BoxLayout { }); this._descriptionLabel.clutter_text.line_wrap = true; this.add_child(this._descriptionLabel); + + this._ignoreButton = new St.Button({ + style_class: 'parental-controls-shield-button', + // Translators: this is for ignoring a screen time limit for parental controls + label: _('Ignore'), + x_align: Clutter.ActorAlign.CENTER, + }); + this._ignoreButton.connect('clicked', + () => this._onIgnoreButtonClicked().catch(logError)); + this.add_child(this._ignoreButton); + } + + _onDestroy() { + this._requestExtensionCookie = null; + } + + async _onIgnoreButtonClicked() { + if (this._requestExtensionCookie) + return; + + try { + [this._requestExtensionCookie] = await this._timerChildProxy.RequestExtensionAsync( + 'login-session', + '', + 0, + {}, + Gio.DBusCallFlags.ALLOW_INTERACTIVE_AUTHORIZATION + ); + } catch (e) { + console.warn(`Failed to obtain screen time extension: ${e.message}`); + } + } + + _onExtensionResponse(proxy, sender, [_, cookie]) { + if (this._requestExtensionCookie === null || + cookie !== this._requestExtensionCookie) + return; + + this._requestExtensionCookie = null; } }); @@ -744,6 +807,9 @@ export const AuthPrompt = GObject.registerClass({ this.replace_child(this._mainContent, newMainContent); this._mainContent = newMainContent; } + + if (this._mainContent === this._inputWell) + this._entry.grab_key_focus(); } begin(params) { diff --git a/js/ui/components/polkitAgent.js b/js/ui/components/polkitAgent.js index 119f2da66f36054c2b99af37140992ee345d8df0..c1566c2631777a9a5342bee7632273c5e9a5b83d 100644 --- a/js/ui/components/polkitAgent.js +++ b/js/ui/components/polkitAgent.js @@ -439,8 +439,10 @@ class AuthenticationAgent extends Shell.PolkitAuthenticationAgent { } _onInitiate(nativeAgent, actionId, message, iconName, cookie, userNames) { - // Don't pop up a dialog while locked - if (Main.sessionMode.isLocked) { + // Don't pop up a dialog while locked, unless it's triggered by user + // action from the lock screen, such as extending the session limits + if (Main.sessionMode.isLocked && + actionId !== 'org.freedesktop.Malcontent.SessionLimits.Extend') { Main.sessionMode.connectObject('updated', () => { Main.sessionMode.disconnectObject(this);