From 5d94c0a7d44a2255b81815a52a7056a94a39842d Mon Sep 17 00:00:00 2001 From: nippy Date: Sat, 18 Apr 2026 13:49:56 +0200 Subject: update Raveos themes --- .../ShutdownTimer@deminder/modules/injection.js | 192 +++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/injection.js (limited to 'raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/injection.js') 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 new file mode 100644 index 0000000..7d5bdd1 --- /dev/null +++ b/raveos-theme/gnome/theme-data/extensions/installed/ShutdownTimer@deminder/modules/injection.js @@ -0,0 +1,192 @@ +// 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; +} -- cgit v1.3