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);