%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /usr/share/gnome-shell/
Upload File :
Create Path :
Current File : //usr/share/gnome-shell/org.gnome.Shell.Extensions.src.gresource

GVariant�(	
�Q��L�����v��C��
v���H�7�v��?,\�?v�?FF]Lp	FFLLFdF�*!�dF	vpF�I����IL�I�I�Q	�IL�I�I��$0�ILJJ�%�yJvJKB�;�KL K8K�m��8KvHK�a�ag��a	v�a�e�H��eL�e�e'� g�ev�e}m��\}mv�mo��;�oL�o�o�����o
v�o�uKP��uL�u�uԵ�����uL�u�u�P���uvv��6�|��vЋK�ε��K�v`�2�extensions/extensionPrefsDialog.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

import Adw from 'gi://Adw?version=1';
import Gdk from 'gi://Gdk?version=4.0';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';

import {formatError} from './misc/errorUtils.js';

export const ExtensionPrefsDialog = GObject.registerClass({
    GTypeName: 'ExtensionPrefsDialog',
    Signals: {
        'loaded': {},
    },
}, class ExtensionPrefsDialog extends Adw.PreferencesWindow {
    _init(extension) {
        super._init({
            title: extension.metadata.name,
            search_enabled: false,
        });

        this._extension = extension;

        this._loadPrefs().catch(e => {
            this._showErrorPage(e);
            logError(e, 'Failed to open preferences');
        }).finally(() => this.emit('loaded'));
    }

    async _loadPrefs() {
        const {dir, path, metadata} = this._extension;

        const prefsJs = dir.get_child('prefs.js');
        const prefsModule = await import(prefsJs.get_uri());

        const prefsObj = new prefsModule.default({...metadata, dir, path});
        this._extension.stateObj = prefsObj;

        prefsObj.fillPreferencesWindow(this);

        if (!this.visible_page)
            throw new Error('Extension did not provide any UI');
    }

    set titlebar(w) {
        this.set_titlebar(w);
    }

    // eslint-disable-next-line camelcase
    set_titlebar() {
        // intercept fatal libadwaita error, show error page instead
        GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
            this._showErrorPage(
                new Error('set_titlebar() is not supported for Adw.Window'));
            return GLib.SOURCE_REMOVE;
        });
    }

    destroy() {
        this._showErrorPage(
            new Error('destroy() breaks tracking open dialogs, use close() if you must'));
    }

    _showErrorPage(e) {
        while (this.visible_page)
            this.remove(this.visible_page);

        this.add(new ExtensionPrefsErrorPage(this._extension, e));
    }
});

const ExtensionPrefsErrorPage = GObject.registerClass({
    GTypeName: 'ExtensionPrefsErrorPage',
    Template: 'resource:///org/gnome/Shell/Extensions/ui/extension-error-page.ui',
    InternalChildren: [
        'expander',
        'expanderArrow',
        'revealer',
        'errorView',
    ],
}, class ExtensionPrefsErrorPage extends Adw.PreferencesPage {
    static _classInit(klass) {
        super._classInit(klass);

        klass.install_action('page.copy-error',
            null,
            self => {
                const clipboard = self.get_display().get_clipboard();
                clipboard.set(self._errorMarkdown);
            });
        klass.install_action('page.show-url',
            null,
            self => Gtk.show_uri(self.get_root(), self._url, Gdk.CURRENT_TIME));

        return klass;
    }

    _init(extension, error) {
        super._init();

        this._addCustomStylesheet();

        this._uuid = extension.uuid;
        this._url = extension.metadata.url || '';

        this.action_set_enabled('page.show-url', this._url !== '');

        this._gesture = new Gtk.GestureClick({
            button: 0,
            exclusive: true,
        });
        this._expander.add_controller(this._gesture);

        this._gesture.connect('released', (gesture, nPress) => {
            if (nPress === 1)
                this._revealer.reveal_child = !this._revealer.reveal_child;
        });

        this._revealer.connect('notify::reveal-child', () => {
            this._expanderArrow.icon_name = this._revealer.reveal_child
                ? 'pan-down-symbolic'
                : 'pan-end-symbolic';
            this._syncExpandedStyle();
        });
        this._revealer.connect('notify::child-revealed',
            () => this._syncExpandedStyle());

        const formattedError = formatError(error);
        this._errorView.buffer.text = formattedError;

        // markdown for pasting in gitlab issues
        let lines = [
            `The settings of extension ${this._uuid} had an error:`,
            '```',
            formattedError.replace(/\n$/, ''),  // remove trailing newline
            '```',
            '',
        ];
        this._errorMarkdown = lines.join('\n');
    }

    _syncExpandedStyle() {
        if (this._revealer.reveal_child)
            this._expander.add_css_class('expanded');
        else if (!this._revealer.child_revealed)
            this._expander.remove_css_class('expanded');
    }

    _addCustomStylesheet() {
        let provider = new Gtk.CssProvider();
        let uri = 'resource:///org/gnome/Shell/Extensions/css/application.css';
        try {
            provider.load_from_file(Gio.File.new_for_uri(uri));
        } catch (e) {
            logError(e, 'Failed to add application style');
        }
        Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(),
            provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
    }
});
(uuay)errorUtils.js�// Common code for displaying errors to the user in various dialogs

function formatSyntaxErrorLocation(error) {
    const {fileName = '<unknown>', lineNumber = 0, columnNumber = 0} = error;
    return ` @ ${fileName}:${lineNumber}:${columnNumber}`;
}

function formatExceptionStack(error) {
    const {stack} = error;
    if (!stack)
        return '\n\n(No stack trace)';

    const indentedStack = stack.split('\n').map(line => `  ${line}`).join('\n');
    return `\n\nStack trace:\n${indentedStack}`;
}

function formatExceptionWithCause(error, seenCauses, showStack) {
    let fmt = showStack ? formatExceptionStack(error) : '';

    const {cause} = error;
    if (!cause)
        return fmt;

    fmt += `\nCaused by: ${cause}`;

    if (cause !== null && typeof cause === 'object') {
        if (seenCauses.has(cause))
            return fmt;  // avoid recursion
        seenCauses.add(cause);

        fmt += formatExceptionWithCause(cause, seenCauses);
    }

    return fmt;
}

/**
 * Formats a thrown exception into a string, including the stack, taking the
 * location where a SyntaxError was thrown into account.
 *
 * @param {Error} error The error to format
 * @param {object} options Formatting options
 * @param {boolean} options.showStack Whether to show the stack trace (default
 *   true)
 * @returns {string} The formatted string
 */
export function formatError(error, {showStack = true} = {}) {
    try {
        let fmt = `${error}`;
        if (error === null || typeof error !== 'object')
            return fmt;

        if (error instanceof SyntaxError) {
            fmt += formatSyntaxErrorLocation(error);
            if (showStack)
                fmt += formatExceptionStack(error);
            return fmt;
        }

        const seenCauses = new Set([error]);
        fmt += formatExceptionWithCause(error, seenCauses, showStack);
        return fmt;
    } catch (e) {
        return `(could not display error: ${e})`;
    }
}
(uuay)sharedInternals.js!import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';

import {bindtextdomain} from 'gettext';

import * as Config from '../misc/config.js';

export class ExtensionBase {
    #gettextDomain;

    /**
     * Look up an extension by URL (usually 'import.meta.url')
     *
     * @param {string} url - a file:// URL
     */
    static lookupByURL(url) {
        if (!url.startsWith('file://'))
            return null;

        // Keep the last '/' from 'file://' to force an absolute path
        let path = url.slice(6);

        // Walk up the directory tree, looking for an extension with
        // the same UUID as a directory name.
        do {
            path = GLib.path_get_dirname(path);

            const dirName = GLib.path_get_basename(path);
            const extension = this.lookupByUUID(dirName);
            if (extension !== null)
                return extension;
        } while (path !== '/');

        return null;
    }

    /**
     * Look up an extension by UUID
     *
     * @param {string} _uuid
     */
    static lookupByUUID(_uuid) {
        throw new GObject.NotImplementedError();
    }

    /**
     * @param {object} metadata - metadata passed in when loading the extension
     */
    constructor(metadata) {
        if (this.constructor === ExtensionBase)
            throw new Error('ExtensionBase cannot be used directly.');

        if (!metadata)
            throw new Error(`${this.constructor.name} did not pass metadata to parent`);

        this.metadata = metadata;
        this.initTranslations();
    }

    /**
     * @type {string}
     */
    get uuid() {
        return this.metadata['uuid'];
    }

    /**
     * @type {Gio.File}
     */
    get dir() {
        return this.metadata['dir'];
    }

    /**
     * @type {string}
     */
    get path() {
        return this.metadata['path'];
    }

    /**
     * Get a GSettings object for schema, using schema files in
     * extensionsdir/schemas. If schema is omitted, it is taken
     * from metadata['settings-schema'].
     *
     * @param {string=} schema - the GSettings schema id
     *
     * @returns {Gio.Settings}
     */
    getSettings(schema) {
        schema ||= this.metadata['settings-schema'];

        // Expect USER extensions to have a schemas/ subfolder, otherwise assume a
        // SYSTEM extension that has been installed in the same prefix as the shell
        const schemaDir = this.dir.get_child('schemas');
        const defaultSource = Gio.SettingsSchemaSource.get_default();
        let schemaSource;
        if (schemaDir.query_exists(null)) {
            schemaSource = Gio.SettingsSchemaSource.new_from_directory(
                schemaDir.get_path(), defaultSource, false);
        } else {
            schemaSource = defaultSource;
        }

        const schemaObj = schemaSource.lookup(schema, true);
        if (!schemaObj)
            throw new Error(`Schema ${schema} could not be found for extension ${this.uuid}. Please check your installation`);

        return new Gio.Settings({settings_schema: schemaObj});
    }

    /**
     * Initialize Gettext to load translations from extensionsdir/locale. If
     * domain is not provided, it will be taken from metadata['gettext-domain']
     * if provided, or use the UUID
     *
     * @param {string=} domain - the gettext domain to use
     */
    initTranslations(domain) {
        domain ||= this.metadata['gettext-domain'] ?? this.uuid;

        // Expect USER extensions to have a locale/ subfolder, otherwise assume a
        // SYSTEM extension that has been installed in the same prefix as the shell
        const localeDir = this.dir.get_child('locale');
        if (localeDir.query_exists(null))
            bindtextdomain(domain, localeDir.get_path());
        else
            bindtextdomain(domain, Config.LOCALEDIR);

        this.#gettextDomain = domain;
    }

    /**
     * Translate `str` using the extension's gettext domain
     *
     * @param {string} str - the string to translate
     *
     * @returns {string} the translated string
     */
    gettext(str) {
        this.#checkGettextDomain('gettext');
        return GLib.dgettext(this.#gettextDomain, 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
     */
    ngettext(str, strPlural, n) {
        this.#checkGettextDomain('ngettext');
        return GLib.dngettext(this.#gettextDomain, 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
     */
    pgettext(context, str) {
        this.#checkGettextDomain('pgettext');
        return GLib.dpgettext2(this.#gettextDomain, context, str);
    }

    /**
     * @param {string} func
     */
    #checkGettextDomain(func) {
        if (!this.#gettextDomain)
            throw new Error(`${func}() is used without calling initTranslations() first`);
    }
}

export class GettextWrapper {
    #url;
    #extensionClass;

    constructor(extensionClass, url) {
        this.#url = url;
        this.#extensionClass = extensionClass;
    }

    #detectUrl() {
        const basePath = '/gnome-shell/extensions/';

        // Search for an occurrence of an extension stack frame
        // Start at 1 because 0 is the stack frame of this function
        const [, ...stack] = new Error().stack.split('\n');
        const extensionLine = stack.find(
            line => line.includes(basePath));

        if (!extensionLine)
            return null;

        // The exact stack line differs depending on where the function
        // was called (function or module scope), and whether it's called
        // from a module or legacy import (file:// URI vs. plain path).
        //
        // We don't have to care about the exact composition, all we need
        // is a string that can be traversed as path and contains the UUID
        const path = extensionLine.slice(extensionLine.indexOf(basePath));
        return `file://${path}`;
    }

    #lookupExtension(funcName) {
        const url = this.#url ?? this.#detectUrl();
        const extension = this.#extensionClass.lookupByURL(url);
        if (!extension)
            throw new Error(`${funcName} can only be called from extensions`);
        return extension;
    }

    #gettext(str) {
        const extension = this.#lookupExtension('gettext');
        return extension.gettext(str);
    }

    #ngettext(str, strPlural, n) {
        const extension = this.#lookupExtension('ngettext');
        return extension.ngettext(str, strPlural, n);
    }

    #pgettext(context, str) {
        const extension = this.#lookupExtension('pgettext');
        return extension.pgettext(context, str);
    }

    defineTranslationFunctions() {
        return {
            /**
             * Translate `str` using the extension's gettext domain
             *
             * @param {string} str - the string to translate
             *
             * @returns {string} the translated string
             */
            gettext: this.#gettext.bind(this),

            /**
             * 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
             */
            ngettext: this.#ngettext.bind(this),

            /**
             * 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
             */
            pgettext: this.#pgettext.bind(this),
        };
    }
}
(uuay)prefs.jsvimport Adw from 'gi://Adw';
import GObject from 'gi://GObject';

import {ExtensionBase, GettextWrapper} from './sharedInternals.js';
import {extensionManager} from '../extensionsService.js';

export class ExtensionPreferences extends ExtensionBase {
    static lookupByUUID(uuid) {
        return extensionManager.lookup(uuid)?.stateObj ?? null;
    }

    static defineTranslationFunctions(url) {
        const wrapper = new GettextWrapper(this, url);
        return wrapper.defineTranslationFunctions();
    }

    /**
     * Get the single widget that implements
     * the extension's preferences.
     *
     * @returns {Gtk.Widget}
     */
    getPreferencesWidget() {
        throw new GObject.NotImplementedError();
    }

    /**
     * Fill the preferences window with preferences.
     *
     * The default implementation adds the widget
     * returned by getPreferencesWidget().
     *
     * @param {Adw.PreferencesWindow} window - the preferences window
     */
    fillPreferencesWindow(window) {
        const widget = this.getPreferencesWidget();
        const page = this._wrapWidget(widget);
        window.add(page);
    }

    _wrapWidget(widget) {
        if (widget instanceof Adw.PreferencesPage)
            return widget;

        const page = new Adw.PreferencesPage();
        if (widget instanceof Adw.PreferencesGroup) {
            page.add(widget);
            return page;
        }

        const group = new Adw.PreferencesGroup();
        group.add(widget);
        page.add(group);

        return page;
    }
}

export const {
    gettext, ngettext, pgettext,
} = ExtensionPreferences.defineTranslationFunctions();
(uuay)misc/
config.jse// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const pkg = imports.package;

/* The name of this package (not localized) */
export const PACKAGE_NAME = 'gnome-shell';
/* The version of this package */
export const PACKAGE_VERSION = '46.0';
/* 1 if networkmanager is available, 0 otherwise */
export const HAVE_NETWORKMANAGER = 1;
/* 1 if portal helper is enabled, 0 otherwise */
export const HAVE_PORTAL_HELPER = 0;
/* gettext package */
export const GETTEXT_PACKAGE = 'gnome-shell';
/* locale dir */
export const LOCALEDIR = '/usr/share/locale';
/* other standard directories */
export const LIBEXECDIR = '/usr/libexec';
export const PKGDATADIR = '/usr/share/gnome-shell';
/* g-i package versions */
export const LIBMUTTER_API_VERSION = '14';

export const HAVE_BLUETOOTH = pkg.checkSymbol('GnomeBluetooth', '3.0',
    'Client.default_adapter_state');
(uuay)css/
Shell/gnome/application.css�.error-page preferencespage { margin: 30px; }

.expander { padding: 12px; }
.expander.expanded { border: 0 solid @borders; border-bottom-width: 1px; }
.expander-toolbar {
  border: 0 solid @borders;
  border-top-width: 1px;
  padding: 3px;
}
(uuay)js/dbusService.jsOimport Gio from 'gi://Gio';
import GLib from 'gi://GLib';

import {programArgs} from 'system';

import './misc/dbusErrors.js';

const Signals = imports.signals;

const IDLE_SHUTDOWN_TIME = 2; // s

export class ServiceImplementation {
    constructor(info, objectPath) {
        this._objectPath = objectPath;
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(info, this);

        this._injectTracking('return_dbus_error');
        this._injectTracking('return_error_literal');
        this._injectTracking('return_gerror');
        this._injectTracking('return_value');
        this._injectTracking('return_value_with_unix_fd_list');

        this._senders = new Map();
        this._holdCount = 0;

        this._shellName = this._getUniqueShellName();

        this._hasSignals = this._dbusImpl.get_info().signals.length > 0;
        this._shutdownTimeoutId = 0;

        // subclasses may override this to disable automatic shutdown
        this._autoShutdown = true;

        this._queueShutdownCheck();
    }

    // subclasses may override this to own additional names
    register() {
    }

    export() {
        this._dbusImpl.export(Gio.DBus.session, this._objectPath);
    }

    unexport() {
        this._dbusImpl.unexport();
    }

    hold() {
        this._holdCount++;
    }

    release() {
        if (this._holdCount === 0) {
            logError(new Error('Unmatched call to release()'));
            return;
        }

        this._holdCount--;

        if (this._holdCount === 0)
            this._queueShutdownCheck();
    }

    /**
     * Complete @invocation with an appropriate error if @error is set;
     * useful for implementing early returns from method implementations.
     *
     * @param {Gio.DBusMethodInvocation}
     * @param {Error}
     *
     * @returns {bool} - true if @invocation was completed
     */

    _handleError(invocation, error) {
        if (error === null)
            return false;

        if (error instanceof GLib.Error) {
            Gio.DBusError.strip_remote_error(error);
            invocation.return_gerror(error);
        } else {
            let name = error.name;
            if (!name.includes('.')) // likely a normal JS error
                name = `org.gnome.gjs.JSError.${name}`;
            invocation.return_dbus_error(name, error.message);
        }

        return true;
    }

    _maybeShutdown() {
        if (!this._autoShutdown)
            return;

        if (GLib.getenv('SHELL_DBUS_PERSIST'))
            return;

        if (this._holdCount > 0)
            return;

        this.emit('shutdown');
    }

    _queueShutdownCheck() {
        if (this._shutdownTimeoutId)
            GLib.source_remove(this._shutdownTimeoutId);

        this._shutdownTimeoutId = GLib.timeout_add_seconds(
            GLib.PRIORITY_DEFAULT, IDLE_SHUTDOWN_TIME,
            () => {
                this._shutdownTimeoutId = 0;
                this._maybeShutdown();

                return GLib.SOURCE_REMOVE;
            });
    }

    _trackSender(sender) {
        if (this._senders.has(sender))
            return;

        if (sender === this._shellName)
            return; // don't track the shell

        this.hold();
        this._senders.set(sender,
            this._dbusImpl.get_connection().watch_name(
                sender,
                Gio.BusNameWatcherFlags.NONE,
                null,
                () => this._untrackSender(sender)));
    }

    _untrackSender(sender) {
        const id = this._senders.get(sender);

        if (id)
            this._dbusImpl.get_connection().unwatch_name(id);

        if (this._senders.delete(sender))
            this.release();
    }

    _injectTracking(methodName) {
        const {prototype} = Gio.DBusMethodInvocation;
        const origMethod = prototype[methodName];
        const that = this;

        prototype[methodName] = function (...args) {
            origMethod.apply(this, args);

            if (that._hasSignals)
                that._trackSender(this.get_sender());

            that._queueShutdownCheck();
        };
    }

    _getUniqueShellName() {
        try {
            const res = Gio.DBus.session.call_sync(
                'org.freedesktop.DBus',
                '/org/freedesktop/DBus',
                'org.freedesktop.DBus',
                'GetNameOwner',
                new GLib.Variant('(s)', ['org.gnome.Shell']),
                null,
                Gio.DBusCallFlags.NONE,
                -1,
                null);
            const [name] = res.deepUnpack();
            return name;
        } catch (e) {
            console.warn(`Failed to resolve shell name: ${e.message}`);
            return '';
        }
    }
}
Signals.addSignalMethods(ServiceImplementation.prototype);

export class DBusService {
    constructor(name, service) {
        this._name = name;
        this._service = service;
        this._loop = new GLib.MainLoop(null, false);

        this._service.connect('shutdown', () => this._loop.quit());
    }

    async runAsync() {
        // Bail out when not running under gnome-shell
        Gio.DBus.watch_name(Gio.BusType.SESSION,
            'org.gnome.Shell',
            Gio.BusNameWatcherFlags.NONE,
            null,
            () => this._loop.quit());

        this._service.register();

        let flags = Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT;
        if (programArgs.includes('--replace'))
            flags |= Gio.BusNameOwnerFlags.REPLACE;

        Gio.DBus.own_name(Gio.BusType.SESSION,
            this._name,
            flags,
            () => this._service.export(),
            null,
            () => this._loop.quit());

        await this._loop.runAsync();
    }
}
(uuay)params.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

/**
 * parse:
 *
 * @param {*} params caller-provided parameter object, or %null
 * @param {*} defaults provided defaults object
 * @param {boolean} [allowExtras] whether or not to allow properties not in `default`
 *
 * @summary Examines `params` and fills in default values from `defaults` for
 * any properties in `default` that don't appear in `params`. If
 * `allowExtras` is not %true, it will throw an error if `params`
 * contains any properties that aren't in `defaults`.
 *
 * If `params` is %null, this returns the values from `defaults`.
 *
 * @returns a new object, containing the merged parameters from
 * `params` and `defaults`
 */
export function parse(params = {}, defaults, allowExtras) {
    if (!allowExtras) {
        for (let prop in params) {
            if (!(prop in defaults))
                throw new Error(`Unrecognized parameter "${prop}"`);
        }
    }

    return {...defaults, ...params};
}
(uuay)Extensions/dbusUtils.js�import Gio from 'gi://Gio';
import GLib from 'gi://GLib';

import * as Config from './config.js';

let _ifaceResource = null;

/**
 * @private
 */
function _ensureIfaceResource() {
    if (_ifaceResource)
        return;

    // don't use global.datadir so the method is usable from tests/tools
    let dir = GLib.getenv('GNOME_SHELL_DATADIR') || Config.PKGDATADIR;
    let path = `${dir}/gnome-shell-dbus-interfaces.gresource`;
    _ifaceResource = Gio.Resource.load(path);
    _ifaceResource._register();
}

/**
 * @param {string} iface the interface name
 * @returns {string | null} the XML string or null if it is not found
 */
export function loadInterfaceXML(iface) {
    _ensureIfaceResource();

    let uri = `resource:///org/gnome/shell/dbus-interfaces/${iface}.xml`;
    let f = Gio.File.new_for_uri(uri);

    try {
        let [ok_, bytes] = f.load_contents(null);
        return new TextDecoder().decode(bytes);
    } catch (e) {
        log(`Failed to load D-Bus interface ${iface}`);
    }

    return null;
}

/**
 * @param {string} iface the interface name
 * @param {string} ifaceFile the interface filename
 * @returns {string | null} the XML string or null if it is not found
 */
export function loadSubInterfaceXML(iface, ifaceFile) {
    let xml = loadInterfaceXML(ifaceFile);
    if (!xml)
        return null;

    let ifaceStartTag = `<interface name="${iface}">`;
    let ifaceStopTag = '</interface>';
    let ifaceStartIndex = xml.indexOf(ifaceStartTag);
    let ifaceEndIndex = xml.indexOf(ifaceStopTag, ifaceStartIndex + 1) + ifaceStopTag.length;

    let xmlHeader = '<!DOCTYPE node PUBLIC\n' +
        '\'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\'\n' +
        '\'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\'>\n' +
        '<node>\n';
    let xmlFooter = '</node>';

    return (
        xmlHeader +
        xml.substr(ifaceStartIndex, ifaceEndIndex - ifaceStartIndex) +
        xmlFooter);
}
(uuay)main.js�import Adw from 'gi://Adw?version=1';
import GObject from 'gi://GObject';
const pkg = imports.package;

import {DBusService} from './dbusService.js';
import {ExtensionsService} from './extensionsService.js';

/** @returns {void} */
export async function main() {
    Adw.init();
    pkg.initFormat();

    GObject.gtypeNameBasedOnJSPath = true;

    const service = new DBusService(
        'org.gnome.Shell.Extensions',
        new ExtensionsService());
    await service.runAsync();
}
(uuay)ui/dbusErrors.js5import GLib from 'gi://GLib';
import Gio from 'gi://Gio';

function camelcase(str) {
    const words = str.toLowerCase().split('_');
    return words.map(w => `${w.at(0).toUpperCase()}${w.substring(1)}`).join('');
}

function decamelcase(str) {
    return str.replace(/(.)([A-Z])/g, '$1-$2');
}

function registerErrorDomain(domain, errorEnum, prefix = 'org.gnome.Shell') {
    const domainName =
        `shell-${decamelcase(domain).toLowerCase()}-error`;
    const quark = GLib.quark_from_string(domainName);

    for (const [name, code] of Object.entries(errorEnum)) {
        Gio.dbus_error_register_error(quark,
            code, `${prefix}.${domain}.Error.${camelcase(name)}`);
    }
    return quark;
}

export const ModalDialogError = {
    UNKNOWN_TYPE: 0,
    GRAB_FAILED: 1,
};
export const ModalDialogErrors =
    registerErrorDomain('ModalDialog', ModalDialogError);

export const NotificationError = {
    INVALID_APP: 0,
};
export const NotificationErrors =
    registerErrorDomain('Notifications', NotificationError, 'org.gtk');

export const ExtensionError = {
    INFO_DOWNLOAD_FAILED: 0,
    DOWNLOAD_FAILED: 1,
    EXTRACT_FAILED: 2,
    ENABLE_FAILED: 3,
    NOT_ALLOWED: 4,
};
export const ExtensionErrors =
    registerErrorDomain('Extensions', ExtensionError);

export const ScreencastError = {
    ALL_PIPELINES_FAILED: 0,
    PIPELINE_ERROR: 1,
    SAVE_TO_DISK_DISABLED: 2,
    ALREADY_RECORDING: 3,
    RECORDER_ERROR: 4,
    SERVICE_CRASH: 5,
    OUT_OF_DISK_SPACE: 6,
};
export const ScreencastErrors =
    registerErrorDomain('Screencast', ScreencastError);
(uuay)org/	/extension-error-page.ui�<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <template class="ExtensionPrefsErrorPage" parent="AdwPreferencesPage">
    <style>
      <class name="error-page"/>
    </style>
    <child>
      <object class="AdwPreferencesGroup">
        <child>
          <object class="GtkBox">
            <property name="orientation">vertical</property>
            <property name="spacing">12</property>
            <child>
              <object class="GtkLabel">
                <property name="label" translatable="yes">Something’s gone wrong</property>
                <style>
                  <class name="title-1"/>
                </style>
              </object>
            </child>
            <child>
              <object class="GtkLabel">
                <property name="label" translatable="yes">We’re very sorry, but there’s been a problem: the settings for this extension can’t be displayed. We recommend that you report the issue to the extension authors.</property>
                <property name="justify">center</property>
                <property name="wrap">True</property>
              </object>
            </child>
            <child>
              <object class="GtkFrame">
                <property name="margin-top">12</property>
                <child>
                  <object class="GtkBox">
                    <property name="hexpand">True</property>
                    <property name="orientation">vertical</property>
                    <child>
                      <object class="GtkBox" id="expander">
                        <property name="spacing">6</property>
                        <style>
                          <class name="expander"/>
                        </style>
                        <child>
                          <object class="GtkImage" id="expanderArrow">
                            <property name="icon-name">pan-end-symbolic</property>
                          </object>
                        </child>
                        <child>
                          <object class="GtkLabel">
                            <property name="label" translatable="yes">Technical Details</property>
                          </object>
                        </child>
                      </object>
                    </child>
                    <child>
                      <object class="GtkRevealer" id="revealer">
                        <child>
                          <object class="GtkBox">
                            <property name="orientation">vertical</property>
                            <child>
                              <object class="GtkTextView" id="errorView">
                                <property name="monospace">True</property>
                                <property name="editable">False</property>
                                <property name="wrap-mode">word</property>
                                <property name="left-margin">12</property>
                                <property name="right-margin">12</property>
                                <property name="top-margin">12</property>
                                <property name="bottom-margin">12</property>
                              </object>
                            </child>
                            <child>
                              <object class="GtkBox">
                                <style>
                                  <class name="expander-toolbar"/>
                                </style>
                                <child>
                                  <object class="GtkButton">
                                    <property name="receives-default">True</property>
                                    <property name="action-name">page.copy-error</property>
                                    <property name="has-frame">False</property>
                                    <property name="icon-name">edit-copy-symbolic</property>
                                  </object>
                                </child>
                                <child>
                                  <object class="GtkButton" id="homeButton">
                                    <property name="visible"
                                              bind-source="homeButton"
                                              bind-property="sensitive"
                                              bind-flags="sync-create"/>
                                    <property name="hexpand">True</property>
                                    <property name="halign">end</property>
                                    <property name="label" translatable="yes">Homepage</property>
                                    <property name="tooltip-text" translatable="yes">Visit extension homepage</property>
                                    <property name="receives-default">True</property>
                                    <property name="has-frame">False</property>
                                    <property name="action-name">page.show-url</property>
                                  </object>
                                </child>
                              </object>
                            </child>
                          </object>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>
(uuay)extensionUtils.jsk// Common utils for the extension system, the extensions D-Bus service
// and the Extensions app

import Gio from 'gi://Gio';
import GLib from 'gi://GLib';

export const ExtensionType = {
    SYSTEM: 1,
    PER_USER: 2,
};

/**
 * @enum {number}
 */
export const ExtensionState = {
    ACTIVE: 1,
    INACTIVE: 2,
    ERROR: 3,
    OUT_OF_DATE: 4,
    DOWNLOADING: 5,
    INITIALIZED: 6,
    DEACTIVATING: 7,
    ACTIVATING: 8,

    // Used as an error state for operations on unknown extensions,
    // should never be in a real extensionMeta object.
    UNINSTALLED: 99,
};

const SERIALIZED_PROPERTIES = [
    'type',
    'state',
    'enabled',
    'path',
    'error',
    'hasPrefs',
    'hasUpdate',
    'canChange',
];

/**
 * Serialize extension into an object that can be used
 * in a vardict {GLib.Variant}
 *
 * @param {object} extension - an extension object
 * @returns {object}
 */
export function serializeExtension(extension) {
    let obj = {...extension.metadata};

    SERIALIZED_PROPERTIES.forEach(prop => {
        obj[prop] = extension[prop];
    });

    let res = {};
    for (let key in obj) {
        let val = obj[key];
        let type;
        switch (typeof val) {
        case 'string':
            type = 's';
            break;
        case 'number':
            type = 'd';
            break;
        case 'boolean':
            type = 'b';
            break;
        default:
            continue;
        }
        res[key] = GLib.Variant.new(type, val);
    }

    return res;
}

/**
 * Deserialize an unpacked variant into an extension object
 *
 * @param {object} variant - an unpacked {GLib.Variant}
 * @returns {object}
 */
export function deserializeExtension(variant) {
    let res = {metadata: {}};
    for (let prop in variant) {
        let val = variant[prop].unpack();
        if (SERIALIZED_PROPERTIES.includes(prop))
            res[prop] = val;
        else
            res.metadata[prop] = val;
    }
    // add the 2 additional properties to create a valid extension object, as createExtensionObject()
    res.uuid = res.metadata.uuid;
    res.dir = Gio.File.new_for_path(res.path);
    return res;
}
(uuay)extensionsService.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import Shew from 'gi://Shew';

import {ExtensionPrefsDialog} from './extensionPrefsDialog.js';
import {ServiceImplementation} from './dbusService.js';

import {deserializeExtension} from './misc/extensionUtils.js';
import {loadInterfaceXML} from './misc/dbusUtils.js';

const ExtensionsIface = loadInterfaceXML('org.gnome.Shell.Extensions');
const ExtensionsProxy = Gio.DBusProxy.makeProxyWrapper(ExtensionsIface);

class ExtensionManager {
    #extensions = new Map();

    createExtensionObject(serialized) {
        const extension = deserializeExtension(serialized);
        this.#extensions.set(extension.uuid, extension);
        return extension;
    }

    lookup(uuid) {
        return this.#extensions.get(uuid);
    }
}

export const extensionManager = new ExtensionManager();

export const ExtensionsService = class extends ServiceImplementation {
    constructor() {
        super(ExtensionsIface, '/org/gnome/Shell/Extensions');

        this._proxy = new ExtensionsProxy(Gio.DBus.session,
            'org.gnome.Shell', '/org/gnome/Shell');

        this._proxy.connectSignal('ExtensionStateChanged',
            (proxy, sender, params) => {
                this._dbusImpl.emit_signal('ExtensionStateChanged',
                    new GLib.Variant('(sa{sv})', params));
            });

        this._proxy.connect('g-properties-changed', () => {
            this._dbusImpl.emit_property_changed('UserExtensionsEnabled',
                new GLib.Variant('b', this._proxy.UserExtensionsEnabled));
        });
    }

    get ShellVersion() {
        return this._proxy.ShellVersion;
    }

    get UserExtensionsEnabled() {
        return this._proxy.UserExtensionsEnabled;
    }

    set UserExtensionsEnabled(enable) {
        this._proxy.UserExtensionsEnabled = enable;
    }

    async ListExtensionsAsync(params, invocation) {
        try {
            const res = await this._proxy.ListExtensionsAsync(...params);
            invocation.return_value(new GLib.Variant('(a{sa{sv}})', res));
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async GetExtensionInfoAsync(params, invocation) {
        try {
            const res = await this._proxy.GetExtensionInfoAsync(...params);
            invocation.return_value(new GLib.Variant('(a{sv})', res));
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async GetExtensionErrorsAsync(params, invocation) {
        try {
            const res = await this._proxy.GetExtensionErrorsAsync(...params);
            invocation.return_value(new GLib.Variant('(as)', res));
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async InstallRemoteExtensionAsync(params, invocation) {
        try {
            const res = await this._proxy.InstallRemoteExtensionAsync(...params);
            invocation.return_value(new GLib.Variant('(s)', res));
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async UninstallExtensionAsync(params, invocation) {
        try {
            const res = await this._proxy.UninstallExtensionAsync(...params);
            invocation.return_value(new GLib.Variant('(b)', res));
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async EnableExtensionAsync(params, invocation) {
        try {
            const res = await this._proxy.EnableExtensionAsync(...params);
            invocation.return_value(new GLib.Variant('(b)', res));
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async DisableExtensionAsync(params, invocation) {
        try {
            const res = await this._proxy.DisableExtensionAsync(...params);
            invocation.return_value(new GLib.Variant('(b)', res));
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    LaunchExtensionPrefsAsync([uuid], invocation) {
        this.OpenExtensionPrefsAsync([uuid, '', {}], invocation);
    }

    async OpenExtensionPrefsAsync(params, invocation) {
        const [uuid, parentWindow, options] = params;

        try {
            if (this._prefsDialog)
                throw new Error('Already showing a prefs dialog');

            const [serialized] = await this._proxy.GetExtensionInfoAsync(uuid);
            const extension = extensionManager.createExtensionObject(serialized);

            this._prefsDialog = new ExtensionPrefsDialog(extension);
            this._prefsDialog.connect('loaded',
                () => this._prefsDialog.show());
            this._prefsDialog.connect('realize', () => {
                let externalWindow = null;

                if (parentWindow)
                    externalWindow = Shew.ExternalWindow.new_from_handle(parentWindow);

                if (externalWindow)
                    externalWindow.set_parent_of(this._prefsDialog.get_surface());
            });

            if (options.modal)
                this._prefsDialog.modal = options.modal.get_boolean();

            this._prefsDialog.connect('close-request', () => {
                delete this._prefsDialog;
                this.release();
                return false;
            });
            this.hold();

            invocation.return_value(null);
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async CheckForUpdatesAsync(params, invocation) {
        try {
            await this._proxy.CheckForUpdatesAsync(...params);
            invocation.return_value(null);
        } catch (error) {
            this._handleError(invocation, error);
        }
    }
};
(uuay)

Zerion Mini Shell 1.0