%PDF- %PDF-
Direktori : /usr/share/gnome-shell/ |
Current File : //usr/share/gnome-shell/org.gnome.Shell.Extensions.src.gresource |
GVariant � ( �Q� � L � � �� � v � � C� � v � � �H�7 � v � �? ,\ �? v �? FF ]Lp FF L LF dF �*!� dF v pF �I ��� �I L �I �I �Q �I L �I �I ��$0 �I L J J �%�y J v J K B�;� K L K 8K �m�� 8K v HK �a �ag� �a v �a �e �H� �e L �e �e '� g �e v �e }m ��\ }m v �m o ��;� o L �o �o ���� �o v �o �u KP� �u L �u �u Ե �����u L �u �u �P�� �u v v �� 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.js v import 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.js e // -*- 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.js O import 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.js 5 import 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.js k // 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)