This repository has been archived on 2024-05-18. You can view files and clone it, but cannot push or open issues or pull requests.
soifai/config/simplefiles/CONTESTANT/usr/share/gnome-shell/extensions/contest-lock@soi.ch/extension.js

425 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Portions of this file are taken from GNOME Shell and adapted.
// Because of that, this gnome extension is distributed under
// the terms of the GNU General Public License, version 2.
const {
AccountsService, Atk, Clutter, Gio,
GLib, Graphene, Meta, Shell, St,
} = imports.gi;
const Background = imports.ui.background;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
// half of the time for which a frame is displayed
const HALF_FRAME_TIME_MS = 8;
const BLUR_BRIGHTNESS = 0.55;
const BLUR_SIGMA = 60;
const POINTER_HIDE_TIMEOUT = 10 * GLib.USEC_PER_SEC;
let actor;
let lockDialog;
let labelCountdown;
let labelTitle;
let labelMessage;
let labelUser;
const bgManagers = [];
let backgroundGroup;
let cursorTracker;
let motionId = 0;
let lastMotionTime = 0;
let pointerHidden = false;
let pointerHideId = 0;
let user;
let grab;
let countdownTimeoutId = 0;
let configFile;
let configMonitor;
let config;
let startTime;
let isExtensionEnabled = false;
let isActive = false;
let isShellReady = false;
let isActiveChanging = false;
function extLog (msg) {
log(`[contest-lock] ${msg}`)
}
function extLogError (msg) {
printerr(`[contest-lock] Error: ${msg}`);
}
function loadConfig () {
configFile.load_contents_async(null, (obj, res) => {
// If there is a poblem with the config file, log an error and keep
// using the old config.
let newConfig;
try {
const [ok, bytes] = configFile.load_contents_finish(res);
// TextDecoder is used in upstream gnome-shell, but not yet
// supported in current Debian.
const contentStr = imports.byteArray.toString(bytes);
//const contentStr = new TextDecoder().decode(bytes);
newConfig = JSON.parse(contentStr);
} catch (err) {
logError(err, '[contest-lock] config file');
return;
}
if (!(typeof newConfig === 'object' && newConfig != null)) {
extLogError('config file: invalid format');
return;
}
if (typeof newConfig.title !== 'string') {
extLogError('config file: "title" must be a string');
return;
}
if (typeof newConfig.message !== 'string') {
extLogError('config file: "message" must be a string');
return;
}
if (
typeof newConfig.startTime !== 'string' ||
!/^\d{4,}-\d\d-\d\dT\d\d:\d\d:\d\d\+\d\d:\d\d$/.test(newConfig.startTime)
) {
extLogError('config file: "startTime" must be a string with format 0000-00-00T00:00:00+00:00');
return;
}
extLog('Loaded new config.')
config = newConfig;
startTime = (new Date(newConfig.startTime)).getTime();
updateConfig();
syncActive();
});
}
function syncActive () {
if (isActiveChanging) return;
let beforeStart = false;
if (startTime != null) {
const now = new Date();
const timeToStart = startTime - now.getTime() - HALF_FRAME_TIME_MS;
beforeStart = timeToStart > 0;
}
// ignore disable event when active
if (beforeStart && isShellReady && (isExtensionEnabled || isActive)) {
activate();
} else {
deactivate();
}
}
function updateConfig () {
if (labelTitle != null) {
labelTitle.text = config.title;
}
if (labelMessage != null) {
labelMessage.text = config.message;
}
}
function updateUser () {
if (labelUser != null) {
const realName = user.get_real_name();
if (realName != null) labelUser.text = realName;
}
}
function updateCountdown () {
countdownTimeoutId = 0;
const now = new Date();
const nowTime = now.getTime() + HALF_FRAME_TIME_MS;
const timeToStart = startTime - nowTime;
const beforeStart = timeToStart > 0;
if (!beforeStart) {
deactivate();
return GLib.SOURCE_REMOVE;
}
const allSecondsToStart = Math.floor(timeToStart / 1000);
const secondsToStart = allSecondsToStart % 60
const allMinutesToStart = Math.floor(allSecondsToStart / 60);
const minutesToStart = allMinutesToStart % 60;
const hoursToStart = Math.floor(allMinutesToStart / 60);
let hoursString = '';
if (hoursToStart !== 0) hoursString = `${hoursToStart}`;
labelCountdown.text = hoursString +
minutesToStart.toString().padStart(2, '0') + '' +
secondsToStart.toString().padStart(2, '0');
const nextUpdateTime = 1000 - nowTime % 1000
countdownTimeoutId = GLib.timeout_add(
GLib.PRIORITY_HIGH,
nextUpdateTime,
updateCountdown
);
GLib.Source.set_name_by_id(countdownTimeoutId, '[contest-lock] updateCountdown');
return GLib.SOURCE_REMOVE;
}
function updateBackgrounds () {
if (!isActive) return;
while (bgManagers.length) bgManagers.pop().destroy();
backgroundGroup.destroy_all_children();
for (let monitorIndex = 0; monitorIndex < Main.layoutManager.monitors.length; monitorIndex++) {
const monitor = Main.layoutManager.monitors[monitorIndex];
const widget = new St.Widget({
style_class: 'screen-shield-background',
x: monitor.x,
y: monitor.y,
width: monitor.width,
height: monitor.height,
effect: new Shell.BlurEffect({
name: 'blur',
brightness: BLUR_BRIGHTNESS,
sigma: BLUR_SIGMA,
}),
});
const bgManager = new Background.BackgroundManager({
container: widget,
monitorIndex,
controlPosition: false,
});
bgManagers.push(bgManager);
backgroundGroup.add_child(widget);
}
}
function pointerHideTimer () {
if (pointerHideId !== 0) {
GLib.source_remove(pointerHideId);
pointerHideId = 0;
}
if (!isActive) return GLib.SOURCE_REMOVE;
const timeToHide = lastMotionTime + POINTER_HIDE_TIMEOUT - GLib.get_monotonic_time();
if (timeToHide <= 0) {
cursorTracker.set_pointer_visible(false);
pointerHidden = true;
return GLib.SOURCE_REMOVE;
}
pointerHideId = GLib.timeout_add(
GLib.PRIORITY_HIGH,
timeToHide / 1000 + 20,
pointerHideTimer
);
GLib.Source.set_name_by_id(pointerHideId, '[contest-lock] pointerHide');
return GLib.SOURCE_REMOVE;
}
function activate () {
if (isActive) return;
isActiveChanging = true;
isActive = true;
grab = Main.pushModal(Main.uiGroup, { actionMode: Shell.ActionMode.LOCK_SCREEN });
if (typeof grab === 'boolean') { // gnome 38
if (!grab) {
grab = Main.pushModal(Main.uiGroup, {
options: Meta.ModalOptions.POINTER_ALREADY_GRABBED,
actionMode: Shell.ActionMode.LOCK_SCREEN
});
}
if (!grab) {
extLogError('Failed to activate: Could not obtain keyboard grab.');
return;
}
grab = Main.uiGroup;
} else if ((grab.get_seat_state() & Clutter.GrabState.KEYBOARD) === 0) {
Main.popModal(grab);
grab = null;
extLogError('Failed to activate: Could not obtain keyboard grab.');
return;
}
actor.show();
Main.sessionMode.pushMode('unlock-dialog');
backgroundGroup = new Clutter.Actor();
motionId = global.stage.connect('captured-event', (stage, event) => {
if (event.type() === Clutter.EventType.MOTION) {
lastMotionTime = GLib.get_monotonic_time();
if (pointerHidden) {
cursorTracker.set_pointer_visible(true);
pointerHidden = false;
pointerHideTimer();
}
}
return Clutter.EVENT_PROPAGATE;
});
cursorTracker.set_pointer_visible(false);
pointerHidden = true;
labelCountdown = new St.Label({
style_class: 'contest-lock-countdown',
x_align: Clutter.ActorAlign.CENTER,
});
labelTitle = new St.Label({
style_class: 'contest-lock-title',
x_align: Clutter.ActorAlign.CENTER,
});
labelMessage = new St.Label({
style_class: 'contest-lock-message',
x_align: Clutter.ActorAlign.CENTER,
});
labelUser = new St.Label({
style_class: 'contest-lock-user',
x_align: Clutter.ActorAlign.CENTER,
});
const stack = new St.BoxLayout({
style_class: 'contest-lock-stack',
vertical: true,
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
})
stack.add_child(labelUser);
stack.add_child(labelCountdown);
stack.add_child(labelTitle);
stack.add_child(labelMessage);
const mainBox = new St.BoxLayout();
mainBox.add_constraint(new Layout.MonitorConstraint({ primary: true }));
mainBox.add_child(stack);
lockDialog = new St.Widget({
name: 'contestLockDialog',
accessible_role: Atk.Role.WINDOW,
visible: false,
reactive: true,
can_focus: true,
x_expand: true,
y_expand: true,
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
});
lockDialog.add_child(backgroundGroup);
lockDialog.add_child(mainBox);
updateConfig();
updateUser();
updateCountdown();
updateBackgrounds();
// countdown may have just expired before we called updateCountdown
if (!isActive) return;
actor.add_child(lockDialog);
lockDialog.show();
extLog('Activated.')
isActiveChanging = false;
}
function deactivate () {
if (!isActive) return;
isActiveChanging = true;
isActive = false;
if (Main.sessionMode.currentMode === 'unlock-dialog') {
Main.sessionMode.popMode('unlock-dialog');
}
Main.popModal(grab);
grab = null;
if (countdownTimeoutId !== 0) {
GLib.source_remove(countdownTimeoutId);
countdownTimeoutId = 0;
}
actor.hide();
labelCountdown = null;
labelTitle = null;
labelMessage = null;
labelUser = null;
while (bgManagers.length) bgManagers.pop().destroy();
lockDialog.destroy();
lockDialog = null;
if (motionId) {
global.stage.disconnect(motionId);
motionId = 0;
}
cursorTracker.set_pointer_visible(true);
extLog('Deactivated.');
isActiveChanging = false;
}
function init (extension) {
actor = Main.layoutManager.screenShieldGroup;
const userName = GLib.get_user_name();
user = AccountsService.UserManager.get_default().get_user(userName);
if (!user) return;
user.connect('notify::is-loaded', updateUser);
user.connect('changed', updateUser);
updateUser();
Main.layoutManager.connect('monitors-changed', updateBackgrounds);
cursorTracker = Meta.CursorTracker.get_for_display(global.display);
if (!Main.layoutManager._startingUp) {
isShellReady = true;
} else {
Main.layoutManager.connect('startup-complete', () => {
isShellReady = true;
syncActive();
});
}
// TODO: When we drop compatibility with gnome <42, remove this code,
// rename the stylesheet back to stylesheet.css (so that it is loaded
// by the extension system) and add a session-modes property which
// includes unlock-dialog to metadata.json.
const theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
const stylesheetFile = extension.dir.get_child('stylesheet-always.css');
theme.load_stylesheet(stylesheetFile);
// TODO: When we drop compatibility with gnome <42, remove this code.
// gnome 38 has a bug that causes extensions to break when running
// `dconf update` while the screen is locked.
Main.extensionManager.reloadExtension = function () {};
configFile = Gio.File.new_for_path('/etc/contest-lock.json');
configMonitor = configFile.monitor_file(Gio.FileMonitorFlags.NONE, null);
configMonitor.set_rate_limit(1000);
configMonitor.connect('changed', loadConfig);
loadConfig();
}
function enable () {
isExtensionEnabled = true;
syncActive();
}
function disable () {
isExtensionEnabled = false;
syncActive();
}