js: Add support for parental controls filtering to the desktop
Filter the apps shown on the desktop and in search results according to whether they are blacklisted by the user’s parental controls. This supports dynamically updating the filter during the user’s session. This adds an optional dependency on libmalcontent. If that’s unavailable, no parental controls filtering will occur. Signed-off-by: Philip Withnall <withnall@endlessm.com> https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/465
This commit is contained in:
parent
b82039e324
commit
3e5b90dbba
@ -23,6 +23,7 @@
|
||||
<file>misc/modemManager.js</file>
|
||||
<file>misc/objectManager.js</file>
|
||||
<file>misc/params.js</file>
|
||||
<file>misc/parentalControlsManager.js</file>
|
||||
<file>misc/permissionStore.js</file>
|
||||
<file>misc/smartcardManager.js</file>
|
||||
<file>misc/systemActions.js</file>
|
||||
|
146
js/misc/parentalControlsManager.js
Normal file
146
js/misc/parentalControlsManager.js
Normal file
@ -0,0 +1,146 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
//
|
||||
// Copyright (C) 2018, 2019, 2020 Endless Mobile, Inc.
|
||||
//
|
||||
// This is a GNOME Shell component to wrap the interactions over
|
||||
// D-Bus with the malcontent library.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/* exported getDefault */
|
||||
|
||||
const { Gio, GObject, Shell } = imports.gi;
|
||||
|
||||
// We require libmalcontent ≥ 0.6.0
|
||||
const HAVE_MALCONTENT = imports.package.checkSymbol(
|
||||
'Malcontent', '0', 'ManagerGetValueFlags');
|
||||
|
||||
var Malcontent = null;
|
||||
if (HAVE_MALCONTENT) {
|
||||
Malcontent = imports.gi.Malcontent;
|
||||
Gio._promisify(Malcontent.Manager.prototype, 'get_app_filter_async', 'get_app_filter_finish');
|
||||
}
|
||||
|
||||
let _singleton = null;
|
||||
|
||||
function getDefault() {
|
||||
if (_singleton === null)
|
||||
_singleton = new ParentalControlsManager();
|
||||
|
||||
return _singleton;
|
||||
}
|
||||
|
||||
// A manager class which provides cached access to the constructing user’s
|
||||
// parental controls settings. It’s possible for the user’s parental controls
|
||||
// to change at runtime if the Parental Controls application is used by an
|
||||
// administrator from within the user’s session.
|
||||
var ParentalControlsManager = GObject.registerClass({
|
||||
Signals: {
|
||||
'app-filter-changed': {},
|
||||
},
|
||||
}, class ParentalControlsManager extends GObject.Object {
|
||||
_init() {
|
||||
super._init();
|
||||
|
||||
this._initialized = false;
|
||||
this._disabled = false;
|
||||
this._appFilter = null;
|
||||
|
||||
this._initializeManager();
|
||||
}
|
||||
|
||||
async _initializeManager() {
|
||||
if (!HAVE_MALCONTENT) {
|
||||
log('Skipping parental controls support as it’s disabled');
|
||||
this._initialized = true;
|
||||
this.emit('app-filter-changed');
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Getting parental controls for user ${Shell.util_get_uid()}`);
|
||||
try {
|
||||
const connection = await Gio.DBus.get(Gio.BusType.SYSTEM, null);
|
||||
this._manager = new Malcontent.Manager({ connection });
|
||||
this._appFilter = await this._manager.get_app_filter_async(
|
||||
Shell.util_get_uid(),
|
||||
Malcontent.ManagerGetValueFlags.NONE,
|
||||
null);
|
||||
} catch (e) {
|
||||
if (e.matches(Malcontent.ManagerError, Malcontent.ManagerError.DISABLED)) {
|
||||
log('Parental controls globally disabled');
|
||||
this._disabled = true;
|
||||
} else {
|
||||
logError(e, 'Failed to get parental controls settings');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._manager.connect('app-filter-changed', this._onAppFilterChanged.bind(this));
|
||||
|
||||
// Signal initialisation is complete.
|
||||
this._initialized = true;
|
||||
this.emit('app-filter-changed');
|
||||
}
|
||||
|
||||
async _onAppFilterChanged(manager, uid) {
|
||||
// Emit 'changed' signal only if app-filter is changed for currently logged-in user.
|
||||
let currentUid = Shell.util_get_uid();
|
||||
if (currentUid !== uid)
|
||||
return;
|
||||
|
||||
try {
|
||||
this._appFilter = await this._manager.get_app_filter_async(
|
||||
currentUid,
|
||||
Malcontent.ManagerGetValueFlags.NONE,
|
||||
null);
|
||||
this.emit('app-filter-changed');
|
||||
} catch (e) {
|
||||
// Log an error and keep the old app filter.
|
||||
logError(e, `Failed to get new MctAppFilter for uid ${Shell.util_get_uid()} on app-filter-changed`);
|
||||
}
|
||||
}
|
||||
|
||||
get initialized() {
|
||||
return this._initialized;
|
||||
}
|
||||
|
||||
// Calculate whether the given app (a Gio.DesktopAppInfo) should be shown
|
||||
// on the desktop, in search results, etc. The app should be shown if:
|
||||
// - The .desktop file doesn’t say it should be hidden.
|
||||
// - The executable from the .desktop file’s Exec line isn’t blacklisted in
|
||||
// the user’s parental controls.
|
||||
// - None of the flatpak app IDs from the X-Flatpak and the
|
||||
// X-Flatpak-RenamedFrom lines are blacklisted in the user’s parental
|
||||
// controls.
|
||||
shouldShowApp(appInfo) {
|
||||
// Quick decision?
|
||||
if (!appInfo.should_show())
|
||||
return false;
|
||||
|
||||
// Are parental controls enabled (at configure time or runtime)?
|
||||
if (!HAVE_MALCONTENT || this._disabled)
|
||||
return true;
|
||||
|
||||
// Have we finished initialising yet?
|
||||
if (!this.initialized) {
|
||||
log(`Warning: Hiding app because parental controls not yet initialised: ${appInfo.get_id()}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._appFilter.is_appinfo_allowed(appInfo);
|
||||
}
|
||||
});
|
@ -10,6 +10,7 @@ const GrabHelper = imports.ui.grabHelper;
|
||||
const IconGrid = imports.ui.iconGrid;
|
||||
const Main = imports.ui.main;
|
||||
const PageIndicators = imports.ui.pageIndicators;
|
||||
const ParentalControlsManager = imports.misc.parentalControlsManager;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Search = imports.ui.search;
|
||||
const SwipeTracker = imports.ui.swipeTracker;
|
||||
@ -161,6 +162,12 @@ var BaseAppView = GObject.registerClass({
|
||||
this._animateLaterId = 0;
|
||||
this._viewLoadedHandlerId = 0;
|
||||
this._viewIsReady = false;
|
||||
|
||||
// Filter the apps through the user’s parental controls.
|
||||
this._parentalControlsManager = ParentalControlsManager.getDefault();
|
||||
this._parentalControlsManager.connect('app-filter-changed', () => {
|
||||
this._redisplay();
|
||||
});
|
||||
}
|
||||
|
||||
_childFocused(_actor) {
|
||||
@ -514,7 +521,7 @@ var AllView = GObject.registerClass({
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return appInfo.should_show();
|
||||
return this._parentalControlsManager.shouldShowApp(appInfo);
|
||||
});
|
||||
|
||||
let apps = this._appInfoList.map(app => app.get_id());
|
||||
@ -1004,7 +1011,7 @@ class FrequentView extends BaseAppView {
|
||||
let favoritesWritable = global.settings.is_writable('favorite-apps');
|
||||
|
||||
for (let i = 0; i < mostUsed.length; i++) {
|
||||
if (!mostUsed[i].get_app_info().should_show())
|
||||
if (!this._parentalControlsManager.shouldShowApp(mostUsed[i].get_app_info()))
|
||||
continue;
|
||||
let appIcon = this._items.get(mostUsed[i].get_id());
|
||||
if (!appIcon) {
|
||||
@ -1250,6 +1257,8 @@ var AppSearchProvider = class AppSearchProvider {
|
||||
this.canLaunchSearch = false;
|
||||
|
||||
this._systemActions = new SystemActions.getDefault();
|
||||
|
||||
this._parentalControlsManager = ParentalControlsManager.getDefault();
|
||||
}
|
||||
|
||||
getResultMetas(apps, callback) {
|
||||
@ -1284,14 +1293,27 @@ var AppSearchProvider = class AppSearchProvider {
|
||||
}
|
||||
|
||||
getInitialResultSet(terms, callback, _cancellable) {
|
||||
// Defer until the parental controls manager is initialised, so the
|
||||
// results can be filtered correctly.
|
||||
if (!this._parentalControlsManager.initialized) {
|
||||
let initializedId = this._parentalControlsManager.connect('app-filter-changed', () => {
|
||||
if (this._parentalControlsManager.initialized) {
|
||||
this._parentalControlsManager.disconnect(initializedId);
|
||||
this.getInitialResultSet(terms, callback, _cancellable);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let query = terms.join(' ');
|
||||
let groups = Shell.AppSystem.search(query);
|
||||
let usage = Shell.AppUsage.get_default();
|
||||
let results = [];
|
||||
|
||||
groups.forEach(group => {
|
||||
group = group.filter(appID => {
|
||||
const app = this._appSys.lookup_app(appID);
|
||||
return app && app.app_info.should_show();
|
||||
return app && this._parentalControlsManager.shouldShowApp(app.app_info);
|
||||
});
|
||||
results = results.concat(group.sort(
|
||||
(a, b) => usage.compare(a, b)
|
||||
@ -1430,7 +1452,7 @@ class FolderView extends BaseAppView {
|
||||
if (!app)
|
||||
return;
|
||||
|
||||
if (!app.get_app_info().should_show())
|
||||
if (!this._parentalControlsManager.shouldShowApp(app.get_app_info()))
|
||||
return;
|
||||
|
||||
if (apps.some(appIcon => appIcon.id == appId))
|
||||
|
@ -46,6 +46,7 @@ const XdndHandler = imports.ui.xdndHandler;
|
||||
const KbdA11yDialog = imports.ui.kbdA11yDialog;
|
||||
const LocatePointer = imports.ui.locatePointer;
|
||||
const PointerA11yTimeout = imports.ui.pointerA11yTimeout;
|
||||
const ParentalControlsManager = imports.misc.parentalControlsManager;
|
||||
|
||||
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
|
||||
const STICKY_KEYS_ENABLE = 'stickykeys-enable';
|
||||
@ -140,6 +141,10 @@ function start() {
|
||||
sessionMode.connect('updated', _sessionUpdated);
|
||||
|
||||
St.Settings.get().connect('notify::gtk-theme', _loadDefaultStylesheet);
|
||||
|
||||
// Initialize ParentalControlsManager before the UI
|
||||
ParentalControlsManager.getDefault();
|
||||
|
||||
_initializeUI();
|
||||
|
||||
shellAccessDialogDBusService = new AccessDialog.AccessDialogDBus();
|
||||
|
Loading…
Reference in New Issue
Block a user