summaryrefslogtreecommitdiff
path: root/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules
diff options
context:
space:
mode:
authorAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
committerAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
commit70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch)
treeab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules
parent5d94c0a7d44a2255b81815a52a7056a94a39842d (diff)
downloadRaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz
RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules')
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/info-fetcher.js108
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/injection.js192
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/install.js64
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/menu-item.js573
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/quicksettings.js77
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/schedule-info.js150
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/session-mode-aware.js41
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/text-box.js130
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/translation.js51
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/util.js178
10 files changed, 0 insertions, 1564 deletions
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/info-fetcher.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/info-fetcher.js
deleted file mode 100644
index e18df7b..0000000
--- a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/info-fetcher.js
+++ /dev/null
@@ -1,108 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com>
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import Gio from 'gi://Gio';
-import GLib from 'gi://GLib';
-import * as Signals from 'resource:///org/gnome/shell/misc/signals.js';
-
-import { throttleTimeout, logDebug, readFileAsync } from './util.js';
-
-export class InfoFetcher extends Signals.EventEmitter {
- constructor() {
- super();
- this._intervalId = null;
- this._tickPromise = null;
- this._shutdownInfo = {};
- this._wakeInfo = {};
- this._rtc = 'rtc0';
- this._cancellable = new Gio.Cancellable();
- [this.refresh, this._refreshCancel] = throttleTimeout(
- this._refresh.bind(this),
- 300
- );
- }
-
- _refresh() {
- this._refreshCancel();
- this._clearInterval();
- logDebug('Extra info refresh...');
- this._intervalId = setInterval(this.tick.bind(this), 5000);
- this.tick();
- }
-
- _clearInterval() {
- if (this._intervalId !== null) {
- clearInterval(this._intervalId);
- this._intervalId = null;
- }
- }
-
- tick() {
- if (this._tickPromise === null) {
- this._tickPromise = Promise.all([
- this._fetchShutdownInfo(),
- this._fetchWakeInfo(this._rtc),
- ]).then(([shutdown, wake]) => {
- this._tickPromise = null;
- this._shutdownInfo = shutdown;
- this._wakeInfo = wake;
- this.emit('changed');
- });
- }
- }
-
- _readFile(path) {
- return readFileAsync(path, this._cancellable);
- }
-
- async _isWakeInfoLocal() {
- const content = await this._readFile('/etc/adjtime').catch(() => '');
- return content.trim().toLowerCase().endsWith('local');
- }
-
- async _fetchWakeInfo(rtc) {
- const content = await this._readFile(
- `/sys/class/rtc/${rtc}/wakealarm`
- ).catch(() => '');
- let timestamp = content !== '' ? parseInt(content) : -1;
- if (timestamp > -1 && (await this._isWakeInfoLocal())) {
- const dt = GLib.DateTime.new_from_unix_local(timestamp);
- timestamp = dt.to_unix() - dt.get_utc_offset() / 1000000;
- dt.unref();
- }
- return { deadline: timestamp };
- }
-
- async _fetchShutdownInfo() {
- const content = await this._readFile(
- '/run/systemd/shutdown/scheduled'
- ).catch(() => '');
- if (content === '') {
- return { deadline: -1 };
- }
- // content: schedule unix-timestamp (micro-seconds), warn-all, shutdown-mode
- const [usec, _, mode] = content.split('\n').map(l => l.split('=')[1]);
- return {
- mode,
- deadline: parseInt(usec) / 1000000,
- };
- }
-
- get shutdownInfo() {
- return this._shutdownInfo;
- }
-
- get wakeInfo() {
- return this._wakeInfo;
- }
-
- destroy() {
- this.disconnectAll();
- this._refreshCancel();
- this._clearInterval();
- if (this._cancellable !== null) {
- this._cancellable.cancel();
- this._cancellable = null;
- }
- }
-}
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/injection.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/injection.js
deleted file mode 100644
index 7d5bdd1..0000000
--- a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/injection.js
+++ /dev/null
@@ -1,192 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com>
-// SPDX-License-Identifier: GPL-3.0-or-later
-import { logDebug } from './util.js';
-
-/**
- * The InjectionTracker helps revert object property injections 'out of order'
- * by keeping track of the injection history.
- * This is useful when overriding the same method from mulitple extensions,
- * for instance, `Main.panel.statusArea.quickSettings._addItems`.
- *
- * The InjectionManager does not work when multiple extensions override the same method.
- * Its `restoreMethod` only restores correctly if it is called in reverse order to `overrideMethod`.
- *
- */
-export class InjectionTracker {
- localInjections = [];
-
- /**
- * Inject a property into an object.
- *
- * To revert the injection call `injection.clear()`.
- *
- * @param {object} obj the target object
- * @param {string} prop the property name
- * @param {any} value the new property value
- * @param {boolean} isPropertyDescriptor whether the value is a property descriptor
- *
- * @returns {object} an injection object
- * provding `injection.original` and `injection.previous` property values
- */
- injectProperty(obj, prop, value, isPropertyDescriptor) {
- let propertyDescriptor = Object.getOwnPropertyDescriptor(obj, prop);
- if (!propertyDescriptor) {
- if (prop in obj) {
- propertyDescriptor = {
- value: obj[prop],
- writable: true,
- configurable: true,
- };
- } else {
- throw new Error(
- `Injection target object does not have a '${prop}' property!`
- );
- }
- }
- // Keep history; mutated by each injection
- const histories = obj.__injectionHistories ?? {};
- const history = histories[prop] ?? [];
- // Push old property descriptor to history
- history.push(propertyDescriptor);
- histories[prop] = history;
- obj.__injectionHistories = histories;
-
- const injectionId = history.length;
- logDebug('[new] injectionid', injectionId);
-
- // Override value
- Object.defineProperty(
- obj,
- prop,
- isPropertyDescriptor
- ? value
- : { value, writable: false, configurable: true }
- );
-
- const localInjectionId = this.localInjections.length;
- const injection = createInjection(obj, prop, history, injectionId, () => {
- // Remove from local injections
- this.localInjections[localInjectionId] = undefined;
- const pruneIndex =
- this.localInjections.length -
- 1 -
- [...this.localInjections].reverse().findIndex(inj => inj !== undefined);
- if (pruneIndex === this.localInjections.length) {
- logDebug('[local-cleanclear]');
- this.localInjections = [];
- } else {
- logDebug('[local-nocleanclear]');
- this.localInjections = this.localInjections.slice(0, pruneIndex);
- }
- });
- this.localInjections.push(injection);
- return injection;
- }
-
- /**
- * Clear all injections made by this instance.
- */
- clearAll() {
- this.localInjections
- .filter(inj => inj !== undefined)
- .forEach(inj => inj.clear());
- }
-}
-
-function createInjection(obj, prop, history, injectionId, clearHook) {
- let reverted = false;
- const readDescriptorValue = descriptor =>
- 'get' in descriptor ? descriptor.get.call(obj) : descriptor.value;
- return {
- /**
- * Read the original property value before any injections.
- */
- get original() {
- // Using h instead history to remain valid after `clear()`
- const h = obj.__injectionHistories?.[prop];
- return readDescriptorValue(
- h?.length ? h[0] : Object.getOwnPropertyDescriptor(obj, prop)
- );
- },
-
- /**
- * Read the previous property value from the injection history.
- *
- * Valid after `clear()` as long as no new injection occurs.
- */
- get previous() {
- return history.length
- ? readDescriptorValue(
- injectionId > history.length
- ? Object.getOwnPropertyDescriptor(obj, prop)
- : popPropertyDescriptorFromHistory(
- // previous history
- history.slice(0, injectionId)
- )
- )
- : this.original;
- },
-
- /**
- * Clear the injection from the injection history.
- */
- clear() {
- if (!reverted) {
- reverted = true;
- clearHook();
-
- // Remove from global injections
- if (injectionId >= history.length) {
- logDebug(
- '[remclear] injectionID',
- injectionId,
- 'historylen',
- history.length
- );
- // Restore property of obj
- Object.defineProperty(
- obj,
- prop,
- popPropertyDescriptorFromHistory(history)
- );
- // Cleanup empty history
- if (history.length === 0) {
- logDebug('[cleanclear]');
- delete obj.__injectionHistories[prop];
-
- // Cleanup empty history store
- if (Object.keys(obj.__injectionHistories).length === 0) {
- delete obj.__injectionHistories;
- }
- } else {
- logDebug('[nocleanclear] history.length', history.length);
- }
- } else {
- logDebug(
- '[setclear] injectionID',
- injectionId,
- 'historylen',
- history.length
- );
- // Clear injection from history
- history[injectionId] = undefined;
- }
- }
- },
-
- /**
- * Count of previous injections. Is 1 if there is only this injection.
- */
- get count() {
- return history.slice(0, injectionId).filter(i => i !== undefined).length;
- },
- };
-}
-
-function popPropertyDescriptorFromHistory(history) {
- let propertyDescriptor;
- do {
- propertyDescriptor = history.pop();
- } while (history.length && propertyDescriptor === undefined);
- return propertyDescriptor;
-}
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/install.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/install.js
deleted file mode 100644
index 701da6a..0000000
--- a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/install.js
+++ /dev/null
@@ -1,64 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com>
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import GLib from 'gi://GLib';
-import Gio from 'gi://Gio';
-import { gettext as _ } from './translation.js';
-
-import { execCheck, installedScriptPath } from '../dbus-service/control.js';
-import { logDebug } from './util.js';
-
-export class Install {
- destroy() {
- if (this.installCancel !== undefined) {
- this.installCancel.cancel();
- }
- this.installCancel = undefined;
- }
-
- /**
- * @param installerScriptPath
- * @param action
- * @param logInstall
- */
- async installAction(installerScriptPath, action, logInstall) {
- const label = this.actionLabel(action);
- if (this.installCancel !== undefined) {
- logDebug(`Trigger cancel install. ${label}`);
- this.installCancel.cancel();
- } else {
- logDebug(`Trigger ${action} action.`);
- this.installCancel = new Gio.Cancellable();
- logInstall(`[${_('START')} ${label}]`);
- try {
- const user = GLib.get_user_name();
- logDebug(`? installer.sh --tool-user ${user} ${action}`);
- await execCheck(
- ['pkexec', installerScriptPath, '--tool-user', user, action],
- this.installCancel,
- false,
- logInstall
- );
- logInstall(`[${_('END')} ${label}]`);
- } catch (err) {
- logInstall(`[${_('FAIL')} ${label}]\n# ${err}`);
- console.error(err, 'InstallError');
- } finally {
- this.installCancel = undefined;
- }
- }
- }
-
- checkInstalled() {
- const scriptPath = installedScriptPath();
- const isScriptInstalled = scriptPath !== null;
- if (isScriptInstalled) {
- logDebug(`Existing installation at: ${scriptPath}`);
- }
- return isScriptInstalled;
- }
-
- actionLabel(action) {
- return { install: _('install'), uninstall: _('uninstall') }[action];
- }
-}
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/menu-item.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/menu-item.js
deleted file mode 100644
index 3a80198..0000000
--- a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/menu-item.js
+++ /dev/null
@@ -1,573 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com>
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import GObject from 'gi://GObject';
-import St from 'gi://St';
-import Gio from 'gi://Gio';
-import Clutter from 'gi://Clutter';
-import * as Main from 'resource:///org/gnome/shell/ui/main.js';
-import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
-import * as Slider from 'resource:///org/gnome/shell/ui/slider.js';
-import * as QuickSettings from 'resource:///org/gnome/shell/ui/quickSettings.js';
-
-import { InfoFetcher } from './info-fetcher.js';
-import { Textbox } from './text-box.js';
-import { gettext as _, ngettext as _n, pgettext as C_ } from './translation.js';
-import * as SessionModeAware from './session-mode-aware.js';
-import { getShutdownScheduleFromSettings } from './schedule-info.js';
-import {
- ShutdownTimerName,
- ShutdownTimerObjectPath,
-} from '../dbus-service/shutdown-timer-dbus.js';
-
-import {
- foregroundActive,
- observeForegroundActive,
- unobserveForegroundActive,
-} from './session-mode-aware.js';
-import {
- durationString,
- longDurationString,
- absoluteTimeString,
- getSliderMinutesFromSettings,
- ScheduleInfo,
-} from './schedule-info.js';
-
-import { logDebug, proxyPromise } from './util.js';
-import {
- WAKE_ACTIONS,
- actionLabel,
- ACTIONS,
- mapLegacyAction,
-} from '../dbus-service/action.js';
-
-/**
- * The ShutdownTimerItem controls wake/shutdown action time and mode.
- * Additionally, it shows wake and external/internal shutdown schedules.
- *
- * The external schedule of the `shutdown` command is fetched by the InfoFetcher.
- * Note that there is no external schedule for `suspend`.
- */
-const ShutdownTimerItem = GObject.registerClass(
- {
- Properties: {
- 'shutdown-text': GObject.ParamSpec.string(
- 'shutdown-text',
- '',
- '',
- GObject.ParamFlags.READWRITE,
- ''
- ),
- 'indicator-icon-name': GObject.ParamSpec.string(
- 'indicator-icon-name',
- '',
- '',
- GObject.ParamFlags.READWRITE,
- 'go-down-symbolic'
- ),
- },
- Signals: {
- 'open-preferences': {},
- shutdown: {
- param_types: [GObject.TYPE_BOOLEAN, GObject.TYPE_STRING],
- },
- wake: {
- param_types: [GObject.TYPE_BOOLEAN],
- },
- },
- },
- class ShutdownTimerItem extends QuickSettings.QuickMenuToggle {
- _init({ path, settings }) {
- const gicon = Gio.icon_new_for_string(
- `${path}/icons/shutdown-timer-symbolic.svg`
- );
- super._init({ gicon, accessible_name: _('Shutdown Timer') });
- this.info = {
- internalShutdown: getShutdownScheduleFromSettings(settings),
- externalShutdown: new ScheduleInfo({ external: true }),
- externalWake: new ScheduleInfo({ mode: 'wake' }),
- state: 'inactive',
- };
- this._settings = settings;
- this.shutdownTimerIcon = gicon;
- this._updatingSwitcherState = false;
-
- // submenu in status area menu with slider and toggle button
- this.sliderItems = {};
- this.sliders = {};
- ['shutdown', 'wake'].forEach(prefix => {
- const [item, slider] = this._createSliderItem(prefix);
- this.sliderItems[prefix] = item;
- this.sliders[prefix] = slider;
- this._onShowSliderChanged(prefix);
- });
- this.switcher = new PopupMenu.PopupSwitchMenuItem('', false);
- // start/stop shutdown timer
- this.switcher.connect('toggled', () => {
- if (!this._updatingSwitcherState) {
- this.emit(
- 'shutdown',
- this.switcher.state,
- this.info.internalShutdown.mode
- );
- }
- });
- this.switcherSettingsButton = new St.Button({
- reactive: true,
- can_focus: true,
- track_hover: true,
- accessible_name: _('Settings'),
- style_class: 'icon-button settings-button',
- });
- this.switcherSettingsButton.child = new St.Icon({
- icon_name: 'applications-system-symbolic',
- });
- this.switcherSettingsButton.connect('clicked', () =>
- this.emit('open-preferences')
- );
- this.switcher.add_child(this.switcherSettingsButton);
-
- this.menu.addMenuItem(this.switcher);
- // make switcher toggle without popup menu closing
- this.switcher.activate = __ => {
- if (this.switcher._switch.mapped) {
- this.switcher.toggle();
- }
- };
- this.menu.addMenuItem(this.sliderItems['shutdown']);
-
- this.modeItems = Object.keys(ACTIONS).map(action => {
- const modeItem = new PopupMenu.PopupMenuItem(actionLabel(action));
- modeItem.connect('activate', () => {
- this.emit('shutdown', true, action);
- });
- this.menu.addMenuItem(modeItem);
- return [action, modeItem];
- });
-
- this.wakeItems = [
- new PopupMenu.PopupSeparatorMenuItem(),
- this.sliderItems['wake'],
- ...Object.keys(WAKE_ACTIONS).map(action => {
- const modeItem = new PopupMenu.PopupMenuItem(actionLabel(action));
- if (action === 'wake') {
- this.wakeModeItem = modeItem;
- }
- modeItem.connect('activate', () =>
- this.emit('wake', action === 'wake')
- );
- return modeItem;
- }),
- ];
- this.wakeItems.forEach(item => {
- this.menu.addMenuItem(item);
- });
- this._updateWakeModeItem();
- this._updateSwitchLabel();
-
- // handlers for changed values in settings
- const settingsHandlerIds = [
- [
- [
- 'shutdown-max-timer-value',
- 'nonlinear-shutdown-slider-value',
- 'shutdown-ref-timer-value',
- 'show-shutdown-absolute-timer-value',
- 'root-mode-value',
- 'shutdown-slider-value',
- ],
- () => this._updateSwitchLabel(),
- ],
- [
- [
- 'wake-max-timer-value',
- 'wake-ref-timer-value',
- 'show-wake-absolute-timer-value',
- 'nonlinear-wake-slider-value',
- 'wake-slider-value',
- ],
- () => this._updateWakeModeItem(),
- ],
- [['shutdown-slider-value'], () => this._updateSlider('shutdown')],
- [['wake-slider-value'], () => this._updateSlider('wake')],
- [
- [
- 'show-wake-items-value',
- 'show-shutdown-mode-value',
- 'show-shutdown-slider-value',
- 'show-wake-slider-value',
- 'show-shutdown-indicator-value',
- 'show-settings-value',
- ],
- () => this._sync(),
- ],
- ]
- .flatMap(([names, func]) => names.map(n => [n, func]))
- .map(([name, func]) =>
- this._settings.connect(`changed::${name}`, func)
- );
-
- this.connect('clicked', () => this.switcher.toggle());
- observeForegroundActive(this, () => this._sync());
- const tickHandlerId = setInterval(() => this.updateShutdownInfo(), 1000);
- this.connect('destroy', () => {
- unobserveForegroundActive(this);
- clearInterval(tickHandlerId);
- settingsHandlerIds.forEach(handlerId => {
- this._settings.disconnect(handlerId);
- });
- });
- }
-
- _sync() {
- // Update wake mode items
- this.wakeItems.forEach(item => {
- item.visible = this._settings.get_boolean('show-wake-items-value');
- });
- this._onShowSliderChanged('wake');
-
- // Update shutdown mode items
- const activeActions = this._settings
- .get_string('show-shutdown-mode-value')
- .split(',')
- .map(s => mapLegacyAction(s.trim()))
- .filter(action => action in ACTIONS);
- logDebug('[menu-item]', activeActions);
- this.modeItems.forEach(([action, item]) => {
- const position = activeActions.indexOf(action);
- if (position > -1) {
- this.menu.moveMenuItem(item, position + 2);
- }
- item.visible = position > -1;
- });
-
- this._onShowSliderChanged('shutdown');
-
- this.switcherSettingsButton.visible =
- foregroundActive() && this._settings.get_boolean('show-settings-value');
-
- this.updateShutdownInfo();
- }
-
- updateShutdownInfo() {
- const schedule = this.info.internalShutdown;
- const externalSchedule = this.info.externalShutdown;
- const externalWake = this.info.externalWake;
- const urgendSchedule = externalSchedule.isMoreUrgendThan(schedule)
- ? externalSchedule
- : schedule;
- const active = schedule.scheduled || this.info.state !== 'inactive';
- const showIndicator = this._settings.get_boolean(
- 'show-shutdown-indicator-value'
- );
-
- // Update Indicator
- this.set({
- checked: active,
- shutdownText:
- urgendSchedule.scheduled && showIndicator
- ? urgendSchedule.secondsLeft > 0
- ? durationString(urgendSchedule.secondsLeft)
- : _('now')
- : '',
- indicatorIconName:
- showIndicator &&
- (this.info.internalShutdown.scheduled ||
- this.info.externalShutdown.scheduled)
- ? 'go-down-symbolic'
- : '',
- });
-
- // Update selected shutdown mode
- this.modeItems.forEach(([mode, item]) => {
- item.setOrnament(
- mode === schedule.mode
- ? PopupMenu.Ornament.DOT
- : PopupMenu.Ornament.NONE
- );
- });
-
- // Update title with scheduled action
- this.title = actionLabel(schedule.mode);
-
- // Update toggle state of switcher
- this._updatingSwitcherState = true;
- this.switcher.setToggleState(active);
- this._updatingSwitcherState = false;
-
- // Update long status description in menu
- this.menu.setHeader(
- this.shutdownTimerIcon,
- _('Shutdown Timer'),
- [schedule.label, externalWake.label].filter(v => !!v).join('\n')
- );
- if (this._settings.get_boolean('show-shutdown-absolute-timer-value')) {
- this._updateSwitchLabel();
- }
- if (this._settings.get_boolean('show-wake-absolute-timer-value')) {
- this._updateWakeModeItem();
- }
- this._updateSubtitle();
- }
-
- // update timer value if slider has changed
- _updateSlider(prefix) {
- this.sliders[prefix].value =
- this._settings.get_double(`${prefix}-slider-value`) / 100.0;
- }
-
- _createSliderItem(settingsPrefix) {
- const valueName = `${settingsPrefix}-slider-value`;
- const sliderValue = this._settings.get_double(valueName) / 100.0;
- const item = new PopupMenu.PopupBaseMenuItem({ activate: false });
- const sliderIcon = new St.Icon({
- icon_name:
- settingsPrefix === 'wake'
- ? 'alarm-symbolic'
- : 'system-shutdown-symbolic',
- style_class: 'popup-menu-icon',
- });
- item.add_child(sliderIcon);
- const slider = new Slider.Slider(sliderValue);
- slider.connect('notify::value', () => {
- const v = slider.value * 100;
- if (v !== this._settings.get_double(valueName))
- this._settings.set_double(valueName, v);
- });
- item.add_child(slider);
- return [item, slider];
- }
-
- get shutdownTimeStr() {
- const minutes = Math.abs(
- getSliderMinutesFromSettings(this._settings, 'shutdown')
- );
- return this._settings.get_boolean('show-shutdown-absolute-timer-value')
- ? absoluteTimeString(minutes, C_('absolute time notation', '%a, %R'))
- : longDurationString(
- minutes,
- h => _n('%s hr', '%s hrs', h),
- m => _n('%s min', '%s mins', m)
- );
- }
-
- _updateSwitchLabel() {
- this.switcher.label.text = this._settings.get_boolean('root-mode-value')
- ? _('%s (protect)').format(this.shutdownTimeStr)
- : this.shutdownTimeStr;
-
- if (this._settings.get_string('wake-ref-timer-value') === 'shutdown') {
- this._updateWakeModeItem();
- }
- if (!this.info.internalShutdown.scheduled) {
- this._updateSubtitle();
- }
- }
-
- _updateSubtitle() {
- this.subtitle = this.info.internalShutdown.scheduled
- ? this._settings.get_boolean('show-shutdown-absolute-timer-value')
- ? this.info.internalShutdown.absoluteTimeString
- : durationString(this.info.internalShutdown.secondsLeft)
- : this.shutdownTimeStr;
- }
-
- _updateWakeModeItem() {
- const minutes = getSliderMinutesFromSettings(this._settings, 'wake');
- const abs = this._settings.get_boolean('show-wake-absolute-timer-value');
- this.wakeModeItem.label.text = (
- abs
- ? C_('WakeButtonText', '%s at %s')
- : C_('WakeButtonText', '%s after %s')
- ).format(
- actionLabel('wake'),
- abs
- ? absoluteTimeString(minutes, C_('absolute time notation', '%a, %R'))
- : longDurationString(
- minutes,
- h => _n('%s hour', '%s hours', h),
- m => _n('%s minute', '%s minutes', m)
- )
- );
- }
-
- _onShowSliderChanged(settingsPrefix) {
- this.sliderItems[settingsPrefix].visible =
- (settingsPrefix !== 'wake' ||
- this._settings.get_boolean('show-wake-items-value')) &&
- this._settings.get_boolean(`show-${settingsPrefix}-slider-value`);
- }
- }
-);
-
-export const ShutdownTimerIndicator = GObject.registerClass(
- {
- Properties: {
- 'wake-minutes': GObject.ParamSpec.int(
- 'wake-minutes',
- '',
- '',
- GObject.ParamFlags.READABLE,
- 0
- ),
- 'shutdown-minutes': GObject.ParamSpec.int(
- 'shutdown-minutes',
- '',
- '',
- GObject.ParamFlags.READABLE,
- 0
- ),
- },
- Signals: {
- 'open-preferences': {},
- shutdown: {
- param_types: [GObject.TYPE_BOOLEAN, GObject.TYPE_STRING],
- },
- wake: {
- param_types: [GObject.TYPE_BOOLEAN],
- },
- },
- },
- class ShutdownTimerIndicator extends QuickSettings.SystemIndicator {
- _init({ path, settings }) {
- super._init();
- this._state = 'inactive';
- this._settings = settings;
- this._textbox = new Textbox({ settings });
- const item = new ShutdownTimerItem({ path, settings });
- const infoFetcher = new InfoFetcher();
- const proxyCancel = new Gio.Cancellable();
- this._sdtProxy = null;
- this._initProxy(item, this._textbox, infoFetcher, proxyCancel).catch(
- err => {
- if (!proxyCancel.is_cancelled()) console.error('[sdt-proxy]', err);
- }
- );
- // React to changes in external shutdown and wake schedule
- infoFetcher.connect('changed', () => this._syncShutdownInfo());
- this._infoFetcher = infoFetcher;
- this._shutdownTimerItem = item;
-
- item.connect('open-preferences', () => this.emit('open-preferences'));
-
- const icon = new St.Icon({ style_class: 'system-status-icon' });
- const updateIcon = () => {
- const name = item.indicatorIconName;
- icon.visible = !!name;
- if (icon.visible) icon.iconName = name;
- this._syncIndicatorsVisible();
- };
-
- const scheduleLabel = new St.Label({
- y_expand: true,
- y_align: Clutter.ActorAlign.CENTER,
- });
- const updateLabel = () => {
- const text = item.shutdownText;
- scheduleLabel.text = text;
- scheduleLabel.visible = !!text;
- this._syncIndicatorsVisible();
- };
-
- this.add_child(icon);
- this.add_child(scheduleLabel);
-
- item.connect('notify::indicator-icon-name', () => updateIcon());
- item.connect('notify::shutdown-text', () => updateLabel());
-
- updateLabel();
- updateIcon();
-
- this.quickSettingsItems.push(item);
- this.connect('destroy', () => {
- proxyCancel.cancel();
- infoFetcher.destroy();
- this._infoFetcher = null;
- this._textbox.destroy();
- this.quickSettingsItems.forEach(i => i.destroy());
- this.quickSettingsItems = [];
- this._shutdownTimerItem = null;
- if (this._sdtProxy !== null) {
- this._sdtProxy.destroy();
- this._sdtProxy = null;
- }
- this._settings = null;
- });
- item.connect('destroy', () => {
- // Mitigate already destroyed error when logging out
- // Does js/ui/layout.js:256 destroy quickSettingsItems?
- this.quickSettingsItems = [];
- });
- }
-
- async _initProxy(item, textbox, infoFetcher, cancellable) {
- const proxy = await proxyPromise(
- ShutdownTimerName,
- Gio.DBus.session,
- 'org.gnome.Shell',
- ShutdownTimerObjectPath,
- cancellable
- );
- if (cancellable?.is_cancelled()) return;
- item.connect('shutdown', (__, shutdown, action) => {
- logDebug(
- '[menu-item] [shutdown-signal] shutdown: ',
- shutdown,
- 'action:',
- action
- );
- proxy
- .ScheduleShutdownAsync(shutdown, action)
- .catch(err => console.error('[shutdown]', err));
- });
- item.connect('wake', (__, wake) => {
- proxy
- .ScheduleWakeAsync(wake)
- .catch(err => console.error('[wake]', err));
- });
-
- const signalIds = [
- proxy.connectSignal('OnMessage', (__, ___, [msg]) => {
- textbox.showTextbox(msg);
- }),
- proxy.connectSignal('OnStateChange', (__, ___, [state]) => {
- logDebug('[menu-item] [OnStateChange-signal] state:', state);
- this._state = state;
- this._syncShutdownInfo();
- }),
- proxy.connectSignal('OnExternalChange', () => {
- infoFetcher.refresh();
- }),
- ];
- proxy.destroy = function () {
- signalIds.forEach(signalId => this.disconnectSignal(signalId));
- };
-
- this._sdtProxy = proxy;
- [this._state] = await this._sdtProxy.GetStateAsync();
- if (cancellable?.is_cancelled()) return;
- logDebug('[sdt-proxy] state', this._state);
- this._syncShutdownInfo();
- infoFetcher.refresh();
- }
-
- _syncShutdownInfo() {
- const item = this._shutdownTimerItem;
- if (this._state === 'action') {
- if (SessionModeAware.foregroundActive()) Main.overview.hide();
- this._textbox.hideAll();
- }
- item.info = {
- internalShutdown: getShutdownScheduleFromSettings(this._settings),
- externalShutdown: item.info.externalShutdown.copy({
- ...this._infoFetcher.shutdownInfo,
- }),
- externalWake: item.info.externalWake.copy({
- ...this._infoFetcher.wakeInfo,
- }),
- state: this._state,
- };
- item.updateShutdownInfo();
- }
- }
-);
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/quicksettings.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/quicksettings.js
deleted file mode 100644
index dd7b161..0000000
--- a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/quicksettings.js
+++ /dev/null
@@ -1,77 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com>
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import * as Main from 'resource:///org/gnome/shell/ui/main.js';
-
-/**
- * Add an external indicator after an existing indicator and above an existing indicator item.
- */
-export function addExternalIndicator(
- tracker,
- indicator,
- after = '_system',
- above = '_backgroundApps',
- colSpan
-) {
- const qs = Main.panel.statusArea.quickSettings;
- const indicatorItems = indicator.quickSettingsItems;
- const aboveIndicator = qs[above];
- if (aboveIndicator === undefined) {
- if ('_addItems' in qs) {
- // 45.beta.1: _setupIndicators is not done
- const injection = tracker.injectProperty(
- qs,
- '_addItems',
- (items, col) => {
- if (Object.is(items, qs[above].quickSettingsItems)) {
- injection.clear();
- // Insert after: insert_child_above(a,b): inserts 'a' after 'b'
- qs._indicators.insert_child_above(indicator, qs[after]);
- // Insert above
- injection.original.call(qs, indicatorItems, colSpan);
- }
- injection.previous.call(qs, items, col);
- }
- );
- } else {
- // 45.rc: _setupIndicators is not done
- const qsm = qs.menu;
- const injection = tracker.injectProperty(
- qsm,
- '_completeAddItem',
- (item, col) => {
- const firstAboveItem = qs[above]?.quickSettingsItems.at(-1);
- if (Object.is(firstAboveItem, item)) {
- injection.clear();
- // Insert after: insert_child_above(a,b): inserts 'a' after 'b'
- qs._indicators.insert_child_above(indicator, qs[after]);
- // Insert above
- indicatorItems.forEach(newItem =>
- qsm.insertItemBefore(newItem, item, colSpan)
- );
- }
- injection.previous.call(qsm, item, col);
- }
- );
- }
- } else if ('_addItems' in qs) {
- // 45.beta.1: _setupIndicators is done
- // Insert after: insert_child_above(a,b): inserts 'a' after 'b'
- qs._indicators.insert_child_above(indicator, qs[after]);
- // Insert above
- qs._addItems(indicatorItems, colSpan);
- const firstAboveItem = aboveIndicator.quickSettingsItems.at(-1);
- indicatorItems.forEach(item => {
- qs.menu._grid.remove_child(item);
- qs.menu._grid.insert_child_below(item, firstAboveItem);
- });
- } else {
- // 45.rc: _setupIndicators is done
- // Insert after
- qs._indicators.insert_child_above(indicator, qs[after]);
-
- // Insert above
- const firstAboveItem = aboveIndicator.quickSettingsItems.at(-1);
- qs._addItemsBefore(indicatorItems, firstAboveItem, colSpan);
- }
-}
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/schedule-info.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/schedule-info.js
deleted file mode 100644
index 394ebc8..0000000
--- a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/schedule-info.js
+++ /dev/null
@@ -1,150 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com>
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import GLib from 'gi://GLib';
-import { gettext as _, ngettext as _n, pgettext as C_ } from './translation.js';
-import { mapLegacyAction, untilText } from '../dbus-service/action.js';
-
-export class ScheduleInfo {
- constructor({ mode = '?', deadline = -1, external = false }) {
- this._v = { mode: mapLegacyAction(mode), deadline, external };
- }
-
- copy(vals) {
- return new ScheduleInfo({ ...this._v, ...vals });
- }
-
- get deadline() {
- return this._v.deadline;
- }
-
- get external() {
- return this._v.external;
- }
-
- get mode() {
- return this._v.mode;
- }
-
- get scheduled() {
- return this.deadline > -1;
- }
-
- get secondsLeft() {
- return this.deadline - GLib.DateTime.new_now_utc().to_unix();
- }
-
- get minutes() {
- return Math.floor(this.secondsLeft / 60);
- }
-
- get label() {
- let label = '';
- if (this.scheduled) {
- label = _('{durationString} until {untiltext}')
- .replace('{durationString}', durationString(this.secondsLeft))
- .replace('{untiltext}', untilText(this.mode));
- if (this.external) {
- label = _('{label} (sys)').replace('{label}', label);
- }
- }
- return label;
- }
-
- get absoluteTimeString() {
- return GLib.DateTime.new_from_unix_utc(this.deadline)
- .to_local()
- .format(C_('absolute schedule notation', '%a, %T'));
- }
-
- isMoreUrgendThan(otherInfo) {
- return (
- !otherInfo.scheduled ||
- (this.scheduled &&
- // external deadline is instant, internal deadline has 1 min slack time
- (this.external ? this.deadline : this.deadline + 58) <
- otherInfo.deadline)
- );
- }
-}
-
-export function getShutdownScheduleFromSettings(settings) {
- return new ScheduleInfo({
- mode: settings.get_string('shutdown-mode-value'),
- deadline: settings.get_int('shutdown-timestamp-value'),
- });
-}
-
-export function getSliderMinutesFromSettings(settings, prefix) {
- const sliderValue = settings.get_double(`${prefix}-slider-value`) / 100.0;
- const rampUp = settings.get_double(`nonlinear-${prefix}-slider-value`);
- const ramp = x => Math.expm1(rampUp * x) / Math.expm1(rampUp);
- let minutes = Math.floor(
- (rampUp === 0 ? sliderValue : ramp(sliderValue)) *
- settings.get_int(`${prefix}-max-timer-value`)
- );
-
- const refstr = settings.get_string(`${prefix}-ref-timer-value`);
- // default: 'now'
- const MS = 1000 * 60;
- if (refstr.includes(':')) {
- const mh = refstr
- .split(':')
- .map(s => Number.parseInt(s))
- .filter(n => !Number.isNaN(n) && n >= 0);
- if (mh.length >= 2) {
- const d = new Date();
- const nowTime = d.getTime();
- d.setHours(mh[0]);
- d.setMinutes(mh[1]);
-
- if (d.getTime() + MS * minutes < nowTime) {
- d.setDate(d.getDate() + 1);
- }
- minutes += Math.floor(new Date(d.getTime() - nowTime).getTime() / MS);
- }
- } else if (prefix !== 'shutdown' && refstr === 'shutdown') {
- minutes += getSliderMinutesFromSettings(settings, 'shutdown');
- }
- return minutes;
-}
-
-/**
- * A short duration string showing >=3 hours, >=1 mins, or secs.
- *
- * @param {number} seconds duration in seconds
- */
-export function durationString(seconds) {
- const sign = Math.sign(seconds);
- const absSec = Math.floor(Math.abs(seconds));
- const minutes = Math.floor(absSec / 60);
- const hours = Math.floor(minutes / 60);
- if (hours >= 3) {
- return _n('%s hour', '%s hours', hours).format(sign * hours);
- } else if (minutes === 0) {
- return _n('%s sec', '%s secs', absSec).format(
- sign * (absSec > 5 ? 10 * Math.ceil(absSec / 10) : absSec)
- );
- }
- return _n('%s min', '%s mins', minutes).format(sign * minutes);
-}
-
-/**
- *
- * @param minutes
- * @param hrFmt
- * @param minFmt
- */
-export function longDurationString(minutes, hrFmt, minFmt) {
- const hours = Math.floor(minutes / 60);
- const residualMinutes = minutes % 60;
- let parts = [minFmt(residualMinutes).format(residualMinutes)];
- if (hours) {
- parts = [hrFmt(hours).format(hours)].concat(parts);
- }
- return parts.join(' ');
-}
-
-export function absoluteTimeString(minutes, timeFmt) {
- return GLib.DateTime.new_now_local().add_minutes(minutes).format(timeFmt);
-}
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/session-mode-aware.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/session-mode-aware.js
deleted file mode 100644
index cb6e562..0000000
--- a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/session-mode-aware.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com>
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import * as Main from 'resource:///org/gnome/shell/ui/main.js';
-
-export function currentSessionMode() {
- return Main.sessionMode.currentMode;
-}
-
-/**
- * Check if we want to show foreground activity
- */
-export function foregroundActive() {
- // ubuntu22.04 uses 'ubuntu' as 'user' sessionMode
- return Main.sessionMode.currentMode !== 'unlock-dialog';
-}
-
-/**
- * Observe foreground activity changes
- *
- * @param {object} obj bind obj as observer
- * @param {Function} callback called upon change
- */
-export function observeForegroundActive(obj, callback) {
- if (!obj._sessionModeSignalId) {
- obj._sessionModeSignalId = Main.sessionMode.connect('updated', () => {
- callback(foregroundActive());
- });
- }
- callback(foregroundActive());
-}
-
-/**
- * Unobserve session mode
- */
-export function unobserveForegroundActive(obj) {
- if (obj._sessionModeSignalId) {
- Main.sessionMode.disconnect(obj._sessionModeSignalId);
- delete obj._sessionModeSignalId;
- }
-}
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/text-box.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/text-box.js
deleted file mode 100644
index b97cdb1..0000000
--- a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/text-box.js
+++ /dev/null
@@ -1,130 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com>
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import St from 'gi://St';
-import Clutter from 'gi://Clutter';
-
-import * as Main from 'resource:///org/gnome/shell/ui/main.js';
-import { throttleTimeout, logDebug } from './util.js';
-import {
- foregroundActive,
- observeForegroundActive,
- unobserveForegroundActive,
-} from './session-mode-aware.js';
-
-export class Textbox {
- textboxes = [];
- constructor({ settings }) {
- this.settings = settings;
- [this.syncThrottled, this.syncThrottledCancel] = throttleTimeout(
- this.sync.bind(this),
- 50
- );
- this.showSettingsId = settings.connect(
- 'changed::show-textboxes-value',
- this.sync.bind(this)
- );
- observeForegroundActive(this, fgActive => {
- if (!fgActive) {
- this.hideAll();
- }
- });
- }
-
- destroy() {
- if (this.showSettingsId) {
- unobserveForegroundActive(this);
- this.hideAll();
- this.settings.disconnect(this.showSettingsId);
- this.showSettingsId = null;
- }
- }
-
- sync() {
- this.syncThrottledCancel();
- // remove hidden textboxes
- this.textboxes = this.textboxes.filter(t => {
- if (t['_hidden']) {
- const sid = t['_sourceId'];
- if (sid) {
- clearTimeout(sid);
- }
- delete t['_sourceId'];
- t.destroy();
- }
- return !t['_hidden'];
- });
- const monitor = Main.layoutManager.primaryMonitor;
- let heightOffset = 0;
- this.textboxes.forEach((textbox, i) => {
- if (!('_sourceId' in textbox)) {
- // start fadeout of textbox after 3 seconds
- textbox['_sourceId'] = setTimeout(() => {
- textbox['_sourceId'] = 0;
- textbox.ease({
- opacity: 0,
- duration: 1000,
- mode: Clutter.AnimationMode.EASE_OUT_QUAD,
- onComplete: () => {
- textbox['_hidden'] = 1;
- this.syncThrottled();
- },
- });
- }, 3000);
- }
- textbox.visible = this.settings.get_boolean('show-textboxes-value');
- if (!textbox.visible) return;
-
- if (i === 0) {
- heightOffset = -textbox.height / 2;
- }
- textbox.set_position(
- monitor.x + Math.floor(monitor.width / 2 - textbox.width / 2),
- monitor.y + Math.floor(monitor.height / 2 + heightOffset)
- );
- heightOffset += textbox.height + 10;
- if (textbox['_sourceId']) {
- // set opacity before fadeout starts
- textbox.opacity =
- i === 0
- ? 255
- : Math.max(
- 25,
- 25 + 230 * (1 - heightOffset / (monitor.height / 2))
- );
- }
- });
- }
-
- hideAll() {
- for (const t of this.textboxes) {
- t['_hidden'] = 1;
- }
- this.sync();
- }
-
- /**
- * Show a textbox message on the primary monitor
- *
- * @param textmsg
- */
- showTextbox(textmsg) {
- if (textmsg && foregroundActive()) {
- for (const t of this.textboxes) {
- // replace old textbox if it has the same text
- if (t.text === textmsg) {
- t['_hidden'] = 1;
- }
- }
- logDebug(`show textbox: ${textmsg}`);
- const textbox = new St.Label({
- style_class: 'textbox-label',
- text: textmsg,
- opacity: 0,
- });
- Main.uiGroup.add_child(textbox);
- this.textboxes.unshift(textbox);
- this.syncThrottled();
- }
- }
-}
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/translation.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/translation.js
deleted file mode 100644
index af18232..0000000
--- a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/translation.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com>
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import GLib from 'gi://GLib';
-
-/**
- * The import paths for translations differ for `extension.js` and `prefs.js`.
- * https://gjs.guide/extensions/upgrading/gnome-shell-45.html#esm
- *
- * This module provides shared access to translations.
- * It is assumed that the gettext-domain is already bound.
- */
-const domain = 'ShutdownTimer';
-
-/**
- * Translate `str` using the extension's gettext domain
- *
- * @param {string} str - the string to translated
- *
- * @returns {string} the translated string
- */
-export function gettext(str) {
- return GLib.dgettext(domain, str);
-}
-
-/**
- * Translate `str` and choose plural form using the extension's
- * gettext domain
- *
- * @param {string} str - the string to translate
- * @param {string} strPlural - the plural form of the string
- * @param {number} n - the quantity for which translation is needed
- *
- * @returns {string} the translated string
- */
-export function ngettext(str, strPlural, n) {
- return GLib.dngettext(domain, str, strPlural, n);
-}
-
-/**
- * Translate `str` in the context of `context` using the extension's
- * gettext domain
- *
- * @param {string} context - context to disambiguate `str`
- * @param {string} str - the string to translate
- *
- * @returns {string} the translated string
- */
-export function pgettext(context, str) {
- return GLib.dpgettext2(domain, context, str);
-}
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/util.js b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/util.js
deleted file mode 100644
index 6f360aa..0000000
--- a/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/util.js
+++ /dev/null
@@ -1,178 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Deminder <tremminder@gmail.com>
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import GLib from 'gi://GLib';
-import Gio from 'gi://Gio';
-
-export const debugMode = false;
-
-/**
- * Log debug message if debug is enabled .
- *
- * @param {...any} args log arguments
- */
-export function logDebug(...args) {
- if ('logDebug' in globalThis) {
- globalThis.logDebug(...args);
- } else if (debugMode) {
- console.log('[SDT]', ...args);
- }
-}
-
-export async function proxyPromise(
- ProxyTypeOrName,
- session,
- dest,
- objectPath,
- cancellable = null
-) {
- if (typeof ProxyTypeOrName === 'string') {
- try {
- ProxyTypeOrName = Gio.DBusProxy.makeProxyWrapper(
- await loadInterfaceXML(ProxyTypeOrName, cancellable)
- );
- } catch (err) {
- throw new Error('Failed to load proxy interface!', { cause: err });
- }
- }
- const p = await new Promise((resolve, reject) => {
- new ProxyTypeOrName(
- session,
- dest,
- objectPath,
- (proxy, error) => {
- if (error) {
- reject(error);
- } else {
- resolve(proxy);
- }
- },
- cancellable
- );
- });
- return p;
-}
-
-export class Idle {
- #idleSourceId = null;
- #idleResolves = [];
-
- destroy() {
- // Ensure that promises are resolved
- for (const resolve of this.#idleResolves) {
- resolve();
- }
- this.#idleResolves = [];
- if (this.#idleSourceId) {
- GLib.Source.remove(this.#idleSourceId);
- }
- this.#idleSourceId = null;
- }
-
- /**
- * Resolves when event loop is idle
- */
- guiIdle() {
- return new Promise(resolve => {
- this.#idleResolves.push(resolve);
- if (!this.#idleSourceId) {
- this.#idleSourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
- for (const res of this.#idleResolves) {
- res();
- }
- this.#idleResolves = [];
- this.#idleSourceId = null;
- return GLib.SOURCE_REMOVE;
- });
- }
- });
- }
-}
-
-/**
- * Calls to `event` are delayed (throttled).
- * Call `cancel` drop last event.
- *
- * @param {Function} timeoutFunc called function after delay
- * @param {number} delayMillis delay in milliseconds
- * @returns [event, cancel]
- */
-export function throttleTimeout(timeoutFunc, delayMillis) {
- let current = null;
- return [
- () => {
- if (current === null) {
- current = setTimeout(() => {
- current = null;
- timeoutFunc();
- }, delayMillis);
- }
- },
- () => {
- if (current) {
- clearTimeout(current);
- current = null;
- }
- },
- ];
-}
-
-export function extensionDirectory() {
- const utilModulePath = /(.*)@file:\/\/(.*):\d+:\d+$/.exec(
- new Error().stack.split('\n')[1]
- )[2];
- const extOrModuleDir = GLib.path_get_dirname(
- GLib.path_get_dirname(utilModulePath)
- );
- // This file is either at /modules/util.js or /modules/sdt/util.js
- return GLib.path_get_basename(extOrModuleDir) === 'modules'
- ? GLib.path_get_dirname(extOrModuleDir)
- : extOrModuleDir;
-}
-
-export function readFileAsync(pathOrFile, cancellable = null) {
- return new Promise((resolve, reject) => {
- try {
- const file =
- typeof pathOrFile === 'string'
- ? Gio.File.new_for_path(pathOrFile)
- : pathOrFile;
- file.load_contents_async(cancellable, (f, res) => {
- try {
- const [, contents] = f.load_contents_finish(res);
- const decoder = new TextDecoder('utf-8');
- resolve(decoder.decode(contents));
- } catch (err) {
- reject(err);
- }
- });
- } catch (err) {
- reject(err);
- }
- });
-}
-
-export async function loadInterfaceXML(iface, cancellable = null) {
- const readPromises = [
- Gio.File.new_for_path(
- `${extensionDirectory()}/dbus-interfaces/${iface}.xml`
- ),
- Gio.File.new_for_uri(
- `resource:///org/gnome/shell/dbus-interfaces/${iface}.xml`
- ),
- ].map(async file => {
- try {
- return await readFileAsync(file, cancellable);
- } catch (err) {
- return '';
- }
- });
- for await (const xml of readPromises) {
- if (xml) return xml;
- }
- throw new Error(
- `Failed to load D-Bus interface '${iface}'${
- cancellable && cancellable.is_cancelled() ? ' (canceled)' : ''
- }`
- );
-}