From 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 Mon Sep 17 00:00:00 2001 From: AlexanderCurl Date: Sat, 18 Apr 2026 17:46:06 +0100 Subject: Replaced file structures for themes in the correct format --- .../ShutdownTimer@deminder/modules/info-fetcher.js | 108 ---- .../ShutdownTimer@deminder/modules/injection.js | 192 ------- .../ShutdownTimer@deminder/modules/install.js | 64 --- .../ShutdownTimer@deminder/modules/menu-item.js | 573 --------------------- .../modules/quicksettings.js | 77 --- .../modules/schedule-info.js | 150 ------ .../modules/session-mode-aware.js | 41 -- .../ShutdownTimer@deminder/modules/text-box.js | 130 ----- .../ShutdownTimer@deminder/modules/translation.js | 51 -- .../ShutdownTimer@deminder/modules/util.js | 178 ------- 10 files changed, 1564 deletions(-) delete mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/info-fetcher.js delete mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/injection.js delete mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/install.js delete mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/menu-item.js delete mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/quicksettings.js delete mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/schedule-info.js delete mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/session-mode-aware.js delete mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/text-box.js delete mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/translation.js delete mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/util.js (limited to 'raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules') 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 -// 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 -// 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 -// 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 -// 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 -// 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 -// 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 -// 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 -// 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 -// 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 -// 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)' : '' - }` - ); -} -- cgit v1.3