mirror of
https://github.com/edu4rdshl/gnome-shell.git
synced 2026-07-18 07:34:54 +00:00
Merge branch 'break-reminders' into 'main'
breakManager: Add new state machine for screen time/health breaks See merge request GNOME/gnome-shell!3251
This commit is contained in:
commit
147179e697
14 changed files with 1933 additions and 2 deletions
|
|
@ -4,6 +4,7 @@ dbus_interfaces = [
|
|||
'org.gnome.Shell.PadOsd.xml',
|
||||
'org.gnome.Shell.Screencast.xml',
|
||||
'org.gnome.Shell.Screenshot.xml',
|
||||
'org.gnome.Shell.ScreenTime.xml',
|
||||
'org.gnome.ShellSearchProvider.xml',
|
||||
'org.gnome.ShellSearchProvider2.xml'
|
||||
]
|
||||
|
|
|
|||
38
data/dbus-interfaces/org.gnome.Shell.ScreenTime.xml
Normal file
38
data/dbus-interfaces/org.gnome.Shell.ScreenTime.xml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE node PUBLIC
|
||||
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
||||
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
||||
<!--
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
SPDX-FileCopyrightText: 2024 GNOME Foundation, Inc.
|
||||
-->
|
||||
<node>
|
||||
|
||||
<!--
|
||||
org.gnome.Shell.ScreenTime:
|
||||
@short_description: Screen Time interface
|
||||
|
||||
The interface used to access Screen Time and Break Reminders usage data.
|
||||
-->
|
||||
<interface name="org.gnome.Shell.ScreenTime">
|
||||
|
||||
<!--
|
||||
State:
|
||||
|
||||
The state of the break manager.
|
||||
- 0: Break reminders are disabled.
|
||||
- 1: Break reminders are enabled, user is active, no break is needed yet.
|
||||
- 2: A break is needed and the user is taking it.
|
||||
- 3: A break is needed but the user is still active.
|
||||
-->
|
||||
<property name="State" type="u" access="read"/>
|
||||
|
||||
<!--
|
||||
LastBreakEndTime:
|
||||
|
||||
The time (in wall clock seconds since the Unix epoch) when the most
|
||||
recent break ended. If there have been no breaks so far, zero is
|
||||
returned.
|
||||
-->
|
||||
<property name="LastBreakEndTime" type="t" access="read"/>
|
||||
</interface>
|
||||
</node>
|
||||
|
|
@ -51,6 +51,7 @@
|
|||
<file preprocess="xml-stripblanks">org.gnome.Shell.PortalHelper.xml</file>
|
||||
<file preprocess="xml-stripblanks">org.gnome.Shell.Screencast.xml</file>
|
||||
<file preprocess="xml-stripblanks">org.gnome.Shell.Screenshot.xml</file>
|
||||
<file preprocess="xml-stripblanks">org.gnome.Shell.ScreenTime.xml</file>
|
||||
<file preprocess="xml-stripblanks">org.gnome.Shell.Wacom.PadOsd.xml</file>
|
||||
<file preprocess="xml-stripblanks">org.gnome.Shell.WeatherIntegration.xml</file>
|
||||
<file preprocess="xml-stripblanks">org.gnome.Shell.xml</file>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
<file preprocess="xml-stripblanks">scalable/status/screen-privacy-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/status/switch-on-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/status/switch-off-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/status/wellbeing-symbolic.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
||||
|
|
|
|||
11
data/icons/scalable/status/wellbeing-symbolic.svg
Normal file
11
data/icons/scalable/status/wellbeing-symbolic.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
SPDX-FileCopyrightText: 2024 Jakub Steiner
|
||||
-->
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#222222">
|
||||
<path d="m 3.3125 13.683594 c 0.011719 0.007812 0.023438 0.015625 0.039062 0.019531 l 4.195313 2.179687 c 0.289063 0.152344 0.636719 0.152344 0.925781 0 l 4.152344 -2.179687 c 0.019531 -0.007813 0.039062 -0.019531 0.058594 -0.03125 c 3.988281 -2.449219 2.25 -7.671875 -1.703125 -7.671875 c -1.011719 0 -1.75 0.441406 -2.238281 1.128906 c -0.894532 1.257813 -0.691407 1.277344 -1.621094 0.042969 c -0.554688 -0.730469 -1.320313 -1.203125 -2.128906 -1.171875 c -3.996094 0 -5.652344 5.390625 -1.679688 7.683594 z m 7.667969 -5.6875 c 1.726562 0 3.132812 2.449218 0.65625 3.96875 l 0.058593 -0.035156 l -4.152343 2.179687 h 0.925781 l -4.195312 -2.179687 l 0.039062 0.023437 c -2.386719 -1.378906 -1.097656 -3.957031 0.679688 -3.957031 c 1.378906 0 2.039062 0.945312 2.320312 1.535156 c 0.117188 0.25 0.398438 0.46875 0.675781 0.46875 h 0.011719 c 0.277344 0 0.558594 -0.21875 0.675781 -0.46875 c 0.28125 -0.589844 0.945313 -1.535156 2.304688 -1.535156 z m 0 0"/>
|
||||
<path d="m 8 4 c -0.546875 0 -1 -0.453125 -1 -1 s 0.453125 -1 1 -1 s 1 0.453125 1 1 s -0.453125 1 -1 1 z m 0 -4 c -1.660156 0 -3 1.339844 -3 3 s 1.339844 3 3 3 s 3 -1.339844 3 -3 s -1.339844 -3 -3 -3 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -35,7 +35,7 @@ $osd_levelbar_height:6px;
|
|||
}
|
||||
|
||||
// Monitor number label
|
||||
.osd-monitor-label {
|
||||
.osd-monitor-label, .osd-break-countdown-label {
|
||||
background-color: $selected_bg_color;
|
||||
color: $selected_fg_color;
|
||||
border-radius: $modal_radius;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
<file>extensions/sharedInternals.js</file>
|
||||
|
||||
<file>misc/animationUtils.js</file>
|
||||
<file>misc/breakManager.js</file>
|
||||
<file>misc/config.js</file>
|
||||
<file>misc/dateUtils.js</file>
|
||||
<file>misc/dbusErrors.js</file>
|
||||
|
|
|
|||
1312
js/misc/breakManager.js
Normal file
1312
js/misc/breakManager.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -10,6 +10,7 @@ import St from 'gi://St';
|
|||
|
||||
import * as AccessDialog from './accessDialog.js';
|
||||
import * as AudioDeviceSelection from './audioDeviceSelection.js';
|
||||
import * as BreakManager from '../misc/breakManager.js';
|
||||
import * as Config from '../misc/config.js';
|
||||
import * as Components from './components.js';
|
||||
import * as CtrlAltTab from './ctrlAltTab.js';
|
||||
|
|
@ -91,6 +92,9 @@ export let inputMethod = null;
|
|||
export let introspectService = null;
|
||||
export let locatePointer = null;
|
||||
export let endSessionDialog = null;
|
||||
export let breakManager = null;
|
||||
export let screenTimeDBus = null;
|
||||
export let breakManagerDispatcher = null;
|
||||
|
||||
let _startDate;
|
||||
let _defaultCssStylesheet = null;
|
||||
|
|
@ -246,6 +250,11 @@ async function _initializeUI() {
|
|||
|
||||
introspectService = new Introspect.IntrospectService();
|
||||
|
||||
// Set up the global default break reminder manager and its D-Bus interface
|
||||
breakManager = BreakManager.getDefault();
|
||||
screenTimeDBus = new ShellDBus.ScreenTimeDBus(breakManager);
|
||||
breakManagerDispatcher = new BreakManager.BreakDispatcher(breakManager);
|
||||
|
||||
layoutManager.init();
|
||||
overview.init();
|
||||
|
||||
|
|
|
|||
|
|
@ -441,6 +441,16 @@ export class Notification extends GObject.Object {
|
|||
});
|
||||
this._actions.push(action);
|
||||
this.emit('action-added', action);
|
||||
return action;
|
||||
}
|
||||
|
||||
removeAction(action) {
|
||||
const index = this._actions.indexOf(action);
|
||||
if (index < 0)
|
||||
throw new Error('Action was already removed previously');
|
||||
|
||||
this._actions.splice(index, 1);
|
||||
this.emit('action-removed', action);
|
||||
}
|
||||
|
||||
clearActions() {
|
||||
|
|
@ -494,6 +504,10 @@ export const Source = GObject.registerClass({
|
|||
'policy', 'policy', 'policy',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
NotificationPolicy.$gtype),
|
||||
'allow-empty': GObject.ParamSpec.boolean(
|
||||
'allow-empty', 'allow-empty', 'allow-empty',
|
||||
GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
|
||||
false),
|
||||
},
|
||||
Signals: {
|
||||
'destroy': {param_types: [GObject.TYPE_UINT]},
|
||||
|
|
@ -552,7 +566,7 @@ export const Source = GObject.registerClass({
|
|||
this.emit('notification-removed', notification);
|
||||
this.countUpdated();
|
||||
|
||||
if (!this._inDestruction && this.notifications.length === 0)
|
||||
if (!this._inDestruction && this.notifications.length === 0 && !this.allowEmpty)
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {ControlsState} from './overviewControls.js';
|
|||
|
||||
const GnomeShellIface = loadInterfaceXML('org.gnome.Shell');
|
||||
const ScreenSaverIface = loadInterfaceXML('org.gnome.ScreenSaver');
|
||||
const ScreenTimeIface = loadInterfaceXML('org.gnome.Shell.ScreenTime');
|
||||
|
||||
export class GnomeShell {
|
||||
constructor() {
|
||||
|
|
@ -544,3 +545,30 @@ export class ScreenSaverDBus {
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class ScreenTimeDBus {
|
||||
constructor(breakManager) {
|
||||
this._manager = breakManager;
|
||||
|
||||
this._manager.connect('notify::state', this._onNotify.bind(this));
|
||||
this._manager.connect('notify::last-break-end-time', this._onNotify.bind(this));
|
||||
|
||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenTimeIface, this);
|
||||
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/ScreenTime');
|
||||
}
|
||||
|
||||
_onNotify() {
|
||||
// We always want to notify the two properties together, as clients need
|
||||
// both values to be useful. GJS will combine the two emissions for us.
|
||||
this._dbusImpl.emit_property_changed('State', new GLib.Variant('u', this.State));
|
||||
this._dbusImpl.emit_property_changed('LastBreakEndTime', new GLib.Variant('t', this.LastBreakEndTime));
|
||||
}
|
||||
|
||||
get State() {
|
||||
return this._manager.state;
|
||||
}
|
||||
|
||||
get LastBreakEndTime() {
|
||||
return this._manager.lastBreakEndTime;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ js/dbusServices/extensions/ui/extension-error-page.ui
|
|||
js/gdm/authPrompt.js
|
||||
js/gdm/loginDialog.js
|
||||
js/gdm/util.js
|
||||
js/misc/breakManager.js
|
||||
js/misc/systemActions.js
|
||||
js/misc/util.js
|
||||
js/misc/dateUtils.js
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ unit_testenv.append('GI_TYPELIB_PATH', shell_typelib_path, separator: ':')
|
|||
unit_testenv.append('GI_TYPELIB_PATH', st_typelib_path, separator: ':')
|
||||
|
||||
unit_tests = [
|
||||
'breakManager',
|
||||
'highlighter',
|
||||
'injectionManager',
|
||||
'insertSorted',
|
||||
|
|
|
|||
513
tests/unit/breakManager.js
Normal file
513
tests/unit/breakManager.js
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
//
|
||||
// Copyright 2024 GNOME Foundation, Inc.
|
||||
//
|
||||
// This is a GNOME Shell component to support break reminders and screen time
|
||||
// statistics.
|
||||
//
|
||||
// Licensed under the GNU General Public License Version 2
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import 'resource:///org/gnome/shell/ui/environment.js';
|
||||
import GLib from 'gi://GLib';
|
||||
|
||||
import * as BreakManager from 'resource:///org/gnome/shell/misc/breakManager.js';
|
||||
|
||||
// Convenience alias
|
||||
const BreakState = BreakManager.BreakState;
|
||||
|
||||
// A harness for testing the BreakManager class. It simulates the passage of
|
||||
// time, maintaining an internal ordered queue of events, and providing three
|
||||
// groups of mock functions which the BreakManager uses to interact with it:
|
||||
// a mock version of the IdleMonitor, mock versions of GLib’s clock and timeout
|
||||
// functions, and a mock version of Gio.Settings.
|
||||
//
|
||||
// The internal ordered queue of events is sorted by time (in seconds since an
|
||||
// arbitrary epoch; the tests arbitrarily start from 100s to avoid potential
|
||||
// issues around time zero). On each _tick(), the next event is shifted off the
|
||||
// head of the queue and processed. An event might be a simulated idle watch
|
||||
// (mocking the user being idle), a simulated active watch, a scheduled timeout,
|
||||
// or an assertion function for actually running test assertions.
|
||||
//
|
||||
// The simulated clock jumps from the scheduled time of one event to the
|
||||
// scheduled time of the next. This way, we can simulate half an hour of active
|
||||
// time (waiting for the next rest break to be due) on the computer instantly.
|
||||
class TestHarness {
|
||||
constructor(settings) {
|
||||
this._currentTimeSecs = 100;
|
||||
this._nextSourceId = 1;
|
||||
this._events = [];
|
||||
this._idleWatch = null;
|
||||
this._activeWatch = null;
|
||||
this._settings = settings;
|
||||
}
|
||||
|
||||
_allocateSourceId() {
|
||||
const sourceId = this._nextSourceId;
|
||||
this._nextSourceId++;
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
_removeEventBySourceId(sourceId) {
|
||||
const idx = this._events.findIndex(a => {
|
||||
return a.sourceId === sourceId;
|
||||
});
|
||||
console.assert(idx !== -1);
|
||||
this._events.splice(idx, 1);
|
||||
}
|
||||
|
||||
_insertEvent(event) {
|
||||
this._events.push(event);
|
||||
this._events.sort((a, b) => {
|
||||
return a.time - b.time;
|
||||
});
|
||||
return event;
|
||||
}
|
||||
|
||||
// Add a timeout event to the event queue. It will be scheduled at the
|
||||
// current simulated time plus `intervalSecs`. `callback` will be invoked
|
||||
// when the event is processed.
|
||||
addTimeoutEvent(intervalSecs, callback) {
|
||||
return this._insertEvent({
|
||||
type: 'timeout',
|
||||
time: this._currentTimeSecs + intervalSecs,
|
||||
callback,
|
||||
sourceId: this._allocateSourceId(),
|
||||
intervalSecs,
|
||||
});
|
||||
}
|
||||
|
||||
// Add an idle watch event to the event queue. This simulates the user
|
||||
// becoming idle (no keyboard or mouse input) at time `timeSecs`.
|
||||
addIdleEvent(timeSecs) {
|
||||
return this._insertEvent({
|
||||
type: 'idle',
|
||||
time: timeSecs,
|
||||
});
|
||||
}
|
||||
|
||||
// Add an active watch event to the event queue. This simulates the user
|
||||
// becoming active (using the keyboard or mouse after a period of
|
||||
// inactivity) at time `timeSecs`.
|
||||
addActiveEvent(timeSecs) {
|
||||
return this._insertEvent({
|
||||
type: 'active',
|
||||
time: timeSecs,
|
||||
});
|
||||
}
|
||||
|
||||
// Add a delay action invocation to the event queue. This simulates the user
|
||||
// invoking the ‘delay’ action (typically via a notification) at time
|
||||
// `timeSecs`.
|
||||
addDelayAction(timeSecs, breakManager) {
|
||||
return this._insertEvent({
|
||||
type: 'action',
|
||||
time: timeSecs,
|
||||
callback: () => {
|
||||
breakManager.delayBreak();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Add a skip action invocation to the event queue. This simulates the user
|
||||
// invoking the ‘skip’ action (typically via a notification) at time
|
||||
// `timeSecs`.
|
||||
addSkipAction(timeSecs, breakManager) {
|
||||
return this._insertEvent({
|
||||
type: 'action',
|
||||
time: timeSecs,
|
||||
callback: () => {
|
||||
breakManager.skipBreak();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Add a take action invocation to the event queue. This simulates the user
|
||||
// invoking the ‘take’ action (typically via a notification) at time
|
||||
// `timeSecs`.
|
||||
addTakeAction(timeSecs, breakManager) {
|
||||
return this._insertEvent({
|
||||
type: 'action',
|
||||
time: timeSecs,
|
||||
callback: () => {
|
||||
breakManager.takeBreak();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Add an assertion event to the event queue. This is a callback which is
|
||||
// invoked when the simulated clock reaches `timeSecs`. The callback can
|
||||
// contain whatever test assertions you like.
|
||||
addAssertionEvent(timeSecs, callback) {
|
||||
return this._insertEvent({
|
||||
type: 'assertion',
|
||||
time: timeSecs,
|
||||
callback,
|
||||
});
|
||||
}
|
||||
|
||||
// Add a state assertion event to the event queue. This is a specialised
|
||||
// form of `addAssertionEvent()` which asserts that the `BreakManager.state`
|
||||
// equals `state` at time `timeSecs`.
|
||||
expectState(timeSecs, breakManager, expectedState) {
|
||||
return this.addAssertionEvent(timeSecs, () => {
|
||||
expect(BreakManager.breakStateToString(breakManager.state))
|
||||
.withContext(`${timeSecs}s state`)
|
||||
.toEqual(BreakManager.breakStateToString(expectedState));
|
||||
});
|
||||
}
|
||||
|
||||
// Add a state assertion event to the event queue. This is a specialised
|
||||
// form of `addAssertionEvent()` which asserts that the given `BreakManager`
|
||||
// properties equal the expected values at time `timeSecs`.
|
||||
expectProperties(timeSecs, breakManager, expectedProperties) {
|
||||
return this.addAssertionEvent(timeSecs, () => {
|
||||
for (const [name, expectedValue] of Object.entries(expectedProperties)) {
|
||||
expect(breakManager[name])
|
||||
.withContext(`${timeSecs}s ${name}`)
|
||||
.toEqual(expectedValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_popEvent() {
|
||||
return this._events.shift();
|
||||
}
|
||||
|
||||
// Get a mock clock object for use in the `BreakManager` under test.
|
||||
// This provides a basic implementation of GLib’s clock and timeout
|
||||
// functions which use the simulated clock and event queue.
|
||||
get mockClock() {
|
||||
return {
|
||||
get_real_time_secs: () => {
|
||||
return this._currentTimeSecs;
|
||||
},
|
||||
timeout_add_seconds: (priority, intervalSecs, callback) => {
|
||||
return this.addTimeoutEvent(intervalSecs, callback).sourceId;
|
||||
},
|
||||
source_remove: sourceId => {
|
||||
this._removeEventBySourceId(sourceId);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Get a mock idle monitor object for use in the `BreakManager` under test.
|
||||
// This provides a basic implementation of the `IdleMonitor` which uses the
|
||||
// simulated clock and event queue.
|
||||
get mockIdleMonitor() {
|
||||
return {
|
||||
add_idle_watch: (waitMsec, callback) => {
|
||||
console.assert(this._idleWatch === null);
|
||||
this._idleWatch = {
|
||||
waitMsec,
|
||||
callback,
|
||||
};
|
||||
return 1;
|
||||
},
|
||||
|
||||
add_user_active_watch: callback => {
|
||||
console.assert(this._activeWatch === null);
|
||||
this._activeWatch = callback;
|
||||
return 2;
|
||||
},
|
||||
|
||||
remove_watch: id => {
|
||||
console.assert(id === 1 || id === 2);
|
||||
if (id === 1)
|
||||
this._idleWatch = null;
|
||||
else if (id === 2)
|
||||
this._activeWatch = null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Get a mock settings factory for use in the `BreakManager` under test.
|
||||
// This is an object providing a couple of constructors for `Gio.Settings`
|
||||
// objects. Each constructor returns a basic implementation of
|
||||
// `Gio.Settings` which uses the settings dictionary passed to `TestHarness`
|
||||
// in its constructor.
|
||||
// This necessarily has an extra layer of indirection because there are
|
||||
// multiple ways to construct a `Gio.Settings`.
|
||||
get mockSettingsFactory() {
|
||||
return {
|
||||
new: schemaId => {
|
||||
return {
|
||||
connect: (unusedSignalName, unusedCallback) => {
|
||||
/* no-op for mock purposes */
|
||||
return 1;
|
||||
},
|
||||
get_boolean: key => {
|
||||
return this._settings[schemaId][key];
|
||||
},
|
||||
get_strv: key => {
|
||||
return this._settings[schemaId][key];
|
||||
},
|
||||
get_uint: key => {
|
||||
return this._settings[schemaId][key];
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
new_with_path: (schemaId, unusedPath) => {
|
||||
return {
|
||||
connect: (unusedSignalName, unusedCallback) => {
|
||||
/* no-op for mock purposes */
|
||||
return 1;
|
||||
},
|
||||
get_boolean: key => {
|
||||
return this._settings[schemaId][key];
|
||||
},
|
||||
get_strv: key => {
|
||||
return this._settings[schemaId][key];
|
||||
},
|
||||
get_uint: key => {
|
||||
return this._settings[schemaId][key];
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_tick() {
|
||||
const event = this._popEvent();
|
||||
if (!event)
|
||||
return false;
|
||||
this._currentTimeSecs = event.time;
|
||||
|
||||
switch (event.type) {
|
||||
case 'timeout':
|
||||
if (event.callback()) {
|
||||
event.time += event.intervalSecs;
|
||||
this._insertEvent(event);
|
||||
}
|
||||
break;
|
||||
case 'idle':
|
||||
if (this._idleWatch)
|
||||
this._idleWatch.callback();
|
||||
break;
|
||||
case 'active':
|
||||
if (this._activeWatch) {
|
||||
this._activeWatch();
|
||||
this._activeWatch = null; // one-shot
|
||||
}
|
||||
break;
|
||||
case 'action':
|
||||
event.callback();
|
||||
break;
|
||||
case 'assertion':
|
||||
event.callback();
|
||||
break;
|
||||
default:
|
||||
console.assert(false, 'not reached');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Run the test in a loop, blocking until all events are processed or an
|
||||
// exception is raised.
|
||||
run() {
|
||||
const loop = new GLib.MainLoop(null, false);
|
||||
let innerException = null;
|
||||
|
||||
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
|
||||
try {
|
||||
if (this._tick())
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
loop.quit();
|
||||
return GLib.SOURCE_REMOVE;
|
||||
} catch (e) {
|
||||
// Quit the main loop then re-raise the exception
|
||||
loop.quit();
|
||||
innerException = e;
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
});
|
||||
|
||||
loop.run();
|
||||
|
||||
// Did we exit with an exception?
|
||||
if (innerException)
|
||||
throw innerException;
|
||||
}
|
||||
}
|
||||
|
||||
describe('Break manager', () => {
|
||||
it('can be disabled via GSettings', () => {
|
||||
const harness = new TestHarness({
|
||||
'org.gnome.desktop.break-reminders': {
|
||||
'selected-breaks': [],
|
||||
},
|
||||
});
|
||||
const breakManager = new BreakManager.BreakManager(harness.mockClock, harness.mockIdleMonitor, harness.mockSettingsFactory);
|
||||
|
||||
harness.addActiveEvent(101);
|
||||
harness.expectState(102, breakManager, BreakState.DISABLED);
|
||||
harness.addIdleEvent(130);
|
||||
harness.expectState(135, breakManager, BreakState.DISABLED);
|
||||
});
|
||||
|
||||
// A simple test which simulates the user being active briefly, taking a short
|
||||
// break before one is due, and then being active again until their next break
|
||||
// is overdue.
|
||||
it('tracks a single break type', () => {
|
||||
const harness = new TestHarness({
|
||||
'org.gnome.desktop.break-reminders': {
|
||||
'selected-breaks': ['movement'],
|
||||
},
|
||||
'org.gnome.desktop.break-reminders.movement': {
|
||||
'duration-seconds': 300, /* 5 minutes */
|
||||
'interval-seconds': 1800, /* 30 minutes */
|
||||
'delay-seconds': 300, /* 5 minutes */
|
||||
'notify': true,
|
||||
'play-sound': false,
|
||||
'fade-screen': false,
|
||||
},
|
||||
});
|
||||
const breakManager = new BreakManager.BreakManager(harness.mockClock, harness.mockIdleMonitor, harness.mockSettingsFactory);
|
||||
|
||||
harness.addActiveEvent(101);
|
||||
harness.expectState(102, breakManager, BreakState.ACTIVE);
|
||||
harness.addIdleEvent(130);
|
||||
harness.expectState(135, breakManager, BreakState.IDLE);
|
||||
harness.addActiveEvent(200); // cut the break short before its duration
|
||||
harness.expectState(201, breakManager, BreakState.ACTIVE);
|
||||
harness.expectProperties(2001, breakManager, { // break is due after 30 mins
|
||||
'state': BreakState.BREAK_DUE,
|
||||
'currentBreakType': 'movement',
|
||||
'currentBreakStartTime': 0,
|
||||
'lastBreakEndTime': 100,
|
||||
});
|
||||
harness.addIdleEvent(2005);
|
||||
harness.expectProperties(2006, breakManager, {
|
||||
'state': BreakState.IN_BREAK,
|
||||
'currentBreakType': 'movement',
|
||||
'currentBreakStartTime': 1900,
|
||||
'lastBreakEndTime': 0,
|
||||
});
|
||||
harness.expectState(2195, breakManager, BreakState.IN_BREAK); // near the end of the break
|
||||
harness.expectState(2210, breakManager, BreakState.IDLE); // just after the end of the break
|
||||
harness.addActiveEvent(2320);
|
||||
harness.expectProperties(2321, breakManager, {
|
||||
'state': BreakState.ACTIVE,
|
||||
'currentBreakType': null,
|
||||
'currentBreakStartTime': 0,
|
||||
'lastBreakEndTime': 2320,
|
||||
});
|
||||
harness.addIdleEvent(4100); // start the next break a little early
|
||||
harness.expectState(4101, breakManager, BreakState.IDLE);
|
||||
harness.expectState(4121, breakManager, BreakState.IN_BREAK);
|
||||
harness.addActiveEvent(4420);
|
||||
harness.expectProperties(4421, breakManager, {
|
||||
'state': BreakState.ACTIVE,
|
||||
'currentBreakType': null,
|
||||
'currentBreakStartTime': 0,
|
||||
'lastBreakEndTime': 4420,
|
||||
});
|
||||
|
||||
harness.run();
|
||||
});
|
||||
|
||||
// Test requesting to delay a break.
|
||||
it('supports delaying a break', () => {
|
||||
const harness = new TestHarness({
|
||||
'org.gnome.desktop.break-reminders': {
|
||||
'selected-breaks': ['movement'],
|
||||
},
|
||||
'org.gnome.desktop.break-reminders.movement': {
|
||||
'duration-seconds': 300, /* 5 minutes */
|
||||
'interval-seconds': 1800, /* 30 minutes */
|
||||
'delay-seconds': 300, /* 5 minutes */
|
||||
'notify': true,
|
||||
'play-sound': false,
|
||||
'fade-screen': false,
|
||||
},
|
||||
});
|
||||
const breakManager = new BreakManager.BreakManager(harness.mockClock, harness.mockIdleMonitor, harness.mockSettingsFactory);
|
||||
|
||||
harness.addActiveEvent(101);
|
||||
harness.expectState(102, breakManager, BreakState.ACTIVE);
|
||||
harness.expectProperties(1901, breakManager, { // break is due after 30 mins
|
||||
'state': BreakState.BREAK_DUE,
|
||||
'currentBreakType': 'movement',
|
||||
'currentBreakStartTime': 0,
|
||||
'lastBreakEndTime': 100,
|
||||
});
|
||||
harness.addDelayAction(1902, breakManager);
|
||||
harness.expectProperties(1903, breakManager, { // break is delayed by 5 mins
|
||||
'state': BreakState.ACTIVE,
|
||||
'currentBreakType': null,
|
||||
'currentBreakStartTime': 0,
|
||||
'lastBreakEndTime': 400,
|
||||
});
|
||||
harness.expectProperties(2201, breakManager, { // break is due after another 5 mins
|
||||
'state': BreakState.BREAK_DUE,
|
||||
'currentBreakType': 'movement',
|
||||
'currentBreakStartTime': 0,
|
||||
'lastBreakEndTime': 400,
|
||||
});
|
||||
harness.addIdleEvent(2202);
|
||||
harness.expectState(2203, breakManager, BreakState.IN_BREAK);
|
||||
|
||||
harness.run();
|
||||
});
|
||||
|
||||
// Test requesting to skip a break.
|
||||
it('supports skipping a break', () => {
|
||||
const harness = new TestHarness({
|
||||
'org.gnome.desktop.break-reminders': {
|
||||
'selected-breaks': ['movement'],
|
||||
},
|
||||
'org.gnome.desktop.break-reminders.movement': {
|
||||
'duration-seconds': 300, /* 5 minutes */
|
||||
'interval-seconds': 1800, /* 30 minutes */
|
||||
'delay-seconds': 300, /* 5 minutes */
|
||||
'notify': true,
|
||||
'play-sound': false,
|
||||
'fade-screen': false,
|
||||
},
|
||||
});
|
||||
const breakManager = new BreakManager.BreakManager(harness.mockClock, harness.mockIdleMonitor, harness.mockSettingsFactory);
|
||||
|
||||
harness.addActiveEvent(101);
|
||||
harness.expectState(102, breakManager, BreakState.ACTIVE);
|
||||
harness.expectProperties(1901, breakManager, { // break is due after 30 mins
|
||||
'state': BreakState.BREAK_DUE,
|
||||
'currentBreakType': 'movement',
|
||||
'currentBreakStartTime': 0,
|
||||
'lastBreakEndTime': 100,
|
||||
});
|
||||
harness.addSkipAction(1902, breakManager);
|
||||
harness.expectProperties(1903, breakManager, { // break is skipped for 30 mins
|
||||
'state': BreakState.ACTIVE,
|
||||
'currentBreakType': null,
|
||||
'currentBreakStartTime': 0,
|
||||
'lastBreakEndTime': 1902,
|
||||
});
|
||||
harness.expectProperties(3703, breakManager, { // break is due after another 30 mins
|
||||
'state': BreakState.BREAK_DUE,
|
||||
'currentBreakType': 'movement',
|
||||
'currentBreakStartTime': 0,
|
||||
'lastBreakEndTime': 1902,
|
||||
});
|
||||
harness.addIdleEvent(3704);
|
||||
harness.expectState(3704, breakManager, BreakState.IN_BREAK);
|
||||
|
||||
harness.run();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue