diff options
Diffstat (limited to 'extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js')
-rw-r--r-- | extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js b/extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js new file mode 100644 index 0000000..5d0f28a --- /dev/null +++ b/extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js @@ -0,0 +1,423 @@ +/** +* V-Shell (Vertical Workspaces) + * extensionsSearchProvider.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + */ + +'use strict'; + +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const St = imports.gi.St; +const Shell = imports.gi.Shell; +const Clutter = imports.gi.Clutter; +const GObject = imports.gi.GObject; + +const Main = imports.ui.main; + +const ExtensionState = { + 1: 'ENABLED', + 2: 'DISABLED', + 3: 'ERROR', + 4: 'INCOMPATIBLE', + 5: 'DOWNLOADING', + 6: 'INITIALIZED', + 7: 'DISABLING', + 8: 'ENABLING', +}; + +let Me; +let opt; +// gettext +let _; +let _toggleTimeout; + +// prefix helps to eliminate results from other search providers +// so it needs to be something less common +// needs to be accessible from vw module +const PREFIX = 'eq//'; + +var ExtensionsSearchProviderModule = class { + // export for other modules + static _PREFIX = PREFIX; + constructor(me) { + Me = me; + opt = Me.opt; + _ = Me.gettext; + + this._firstActivation = true; + this.moduleEnabled = false; + this._extensionsSearchProvider = null; + this._enableTimeoutId = 0; + } + + cleanGlobals() { + Me = null; + opt = null; + _ = null; + } + + update(reset) { + if (_toggleTimeout) + GLib.source_remove(_toggleTimeout); + + this.moduleEnabled = opt.get('extensionsSearchProviderModule'); + + reset = reset || !this.moduleEnabled; + + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' ExtensionsSearchProviderModule - Keeping untouched'); + } + + _activateModule() { + // delay because Fedora had problem to register a new provider soon after Shell restarts + this._enableTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + 2000, + () => { + if (!this._extensionsSearchProvider) { + this._extensionsSearchProvider = new extensionsSearchProvider(opt); + this._getOverviewSearchResult()._registerProvider(this._extensionsSearchProvider); + } + this._enableTimeoutId = 0; + return GLib.SOURCE_REMOVE; + } + ); + console.debug(' ExtensionsSearchProviderModule - Activated'); + } + + _disableModule() { + if (this._enableTimeoutId) { + GLib.source_remove(this._enableTimeoutId); + this._enableTimeoutId = 0; + } + + if (this._extensionsSearchProvider) { + this._getOverviewSearchResult()._unregisterProvider(this._extensionsSearchProvider); + this._extensionsSearchProvider = null; + } + + console.debug(' ExtensionsSearchProviderModule - Disabled'); + } + + _getOverviewSearchResult() { + return Main.overview._overview.controls._searchController._searchResults; + } +}; + +class extensionsSearchProvider { + constructor() { + this.id = 'extensions'; + const appSystem = Shell.AppSystem.get_default(); + let appInfo = appSystem.lookup_app('com.matjakeman.ExtensionManager.desktop')?.get_app_info(); + if (!appInfo) + appInfo = appSystem.lookup_app('org.gnome.Extensions.desktop')?.get_app_info(); + if (!appInfo) + appInfo = Gio.AppInfo.create_from_commandline('/usr/bin/gnome-extensions-app', 'Extensions', null); + appInfo.get_description = () => _('Search extensions'); + appInfo.get_name = () => _('Extensions'); + appInfo.get_id = () => 'org.gnome.Extensions.desktop'; + appInfo.get_icon = () => Gio.icon_new_for_string('application-x-addon'); + appInfo.should_show = () => true; + + this.appInfo = appInfo; + this.canLaunchSearch = true; + this.isRemoteProvider = false; + } + + getInitialResultSet(terms, callback /* , cancellable = null*/) { + // In GS 43 callback arg has been removed + /* if (Me.shellVersion >= 43) + cancellable = callback; */ + + const extensions = {}; + Main.extensionManager._extensions.forEach( + e => { + extensions[e.uuid] = e; + } + ); + this.extensions = extensions; + + if (Me.shellVersion >= 43) + return new Promise(resolve => resolve(this._getResultSet(terms))); + else + callback(this._getResultSet(terms)); + + return null; + } + + _getResultSet(terms) { + // do not modify original terms + let termsCopy = [...terms]; + // search for terms without prefix + termsCopy[0] = termsCopy[0].replace(PREFIX, ''); + + const candidates = this.extensions; + const _terms = [].concat(termsCopy); + + const term = _terms.join(' '); + + const results = []; + let m; + for (let id in candidates) { + const extension = this.extensions[id]; + const text = extension.metadata.name + (extension.state === 1 ? 'enabled' : '') + ([6, 2].includes(extension.state) ? 'disabled' : ''); + if (opt.SEARCH_FUZZY) + m = Me.Util.fuzzyMatch(term, text); + else + m = Me.Util.strictMatch(term, text); + + if (m !== -1) + results.push({ weight: m, id }); + } + + // sort alphabetically + results.sort((a, b) => this.extensions[a.id].metadata.name.localeCompare(this.extensions[b.id].metadata.name)); + // enabled first + // results.sort((a, b) => this.extensions[a.id].state !== 1 && this.extensions[b.id].state === 1); + // incompatible last + results.sort((a, b) => this.extensions[a.id].state === 4 && this.extensions[b.id].state !== 4); + + this.resultIds = results.map(item => item.id); + return this.resultIds; + } + + getResultMetas(resultIds, callback = null) { + const metas = resultIds.map(id => this.getResultMeta(id)); + if (Me.shellVersion >= 43) + return new Promise(resolve => resolve(metas)); + else if (callback) + callback(metas); + return null; + } + + getResultMeta(resultId) { + const result = this.extensions[resultId]; + + const versionName = result.metadata['version-name'] ?? ''; + let version = result.metadata['version'] ?? ''; + version = versionName && version ? `/${version}` : version; + const versionStr = `${versionName}${version}`; + + return { + 'id': resultId, + 'name': `${result.metadata.name}`, + 'version': versionStr, + 'description': versionStr, // description will be updated in result object + 'createIcon': size => { + let icon = this.getIcon(result, size); + return icon; + }, + }; + } + + getIcon(extension, size) { + let opacity = 0; + let iconName = 'process-stop-symbolic'; + + switch (extension.state) { + case 1: + if (extension.hasUpdate) + iconName = 'software-update-available'; // 'software-update-available-symbolic'; + else + iconName = 'object-select';// 'object-select-symbolic'; + + opacity = 255; + break; + case 3: + if (Main.extensionManager._enabledExtensions.includes(extension.uuid)) + iconName = 'emblem-ok-symbolic'; + else + iconName = 'dialog-error'; + opacity = 100; + break; + case 4: + iconName = 'software-update-urgent'; // 'software-update-urgent-symbolic'; + opacity = 100; + break; + } + + if (extension.hasUpdate) { + iconName = 'software-update-available'; // 'software-update-available-symbolic'; + opacity = 100; + } + + const icon = new St.Icon({ icon_name: iconName, icon_size: size }); + icon.set({ + reactive: true, + opacity, + }); + + return icon; + } + + createResultObject(meta) { + return new ListSearchResult(this, meta, this.extensions[meta.id]); + } + + launchSearch(terms, timeStamp) { + this.appInfo.launch([], global.create_app_launch_context(timeStamp, -1), null); + } + + activateResult(resultId/* terms, timeStamp*/) { + const extension = this.extensions[resultId]; + if (Me.Util.isShiftPressed()) + this._toggleExtension(extension); + else if (extension.hasPrefs) + Me.Util.openPreferences(extension.metadata); + } + + filterResults(results /* , maxResults*/) { + // return results.slice(0, maxResults); + return results; + } + + getSubsearchResultSet(previousResults, terms, callback) { + if (Me.shellVersion < 43) { + this.getSubsearchResultSet42(terms, callback); + return null; + } + return this.getInitialResultSet(terms); + } + + getSubsearchResultSet42(terms, callback) { + callback(this._getResultSet(terms)); + } +} + +const ListSearchResult = GObject.registerClass( +class ListSearchResult extends St.Button { + _init(provider, metaInfo, extension) { + this.provider = provider; + this.metaInfo = metaInfo; + this.extension = extension; + + super._init({ + reactive: true, + can_focus: true, + track_hover: true, + }); + + this.style_class = 'list-search-result'; + + let content = new St.BoxLayout({ + style_class: 'list-search-result-content', + vertical: false, + x_align: Clutter.ActorAlign.START, + x_expand: true, + y_expand: true, + }); + this.set_child(content); + + let titleBox = new St.BoxLayout({ + style_class: 'list-search-result-title', + y_align: Clutter.ActorAlign.CENTER, + }); + + content.add_child(titleBox); + + // An icon for, or thumbnail of, content + let icon = this.metaInfo['createIcon'](this.ICON_SIZE); + let iconBox = new St.Button(); + iconBox.set_child(icon); + titleBox.add(iconBox); + iconBox.set_style('border: 1px solid rgba(200,200,200,0.2); padding: 2px; border-radius: 8px;'); + this._iconBox = iconBox; + this.icon = icon; + + iconBox.connect('clicked', () => { + this._toggleExtension(); + return Clutter.EVENT_STOP; + }); + + let title = new St.Label({ + text: this.metaInfo['name'], + y_align: Clutter.ActorAlign.CENTER, + opacity: extension.hasPrefs ? 255 : 150, + }); + titleBox.add_child(title); + + this.label_actor = title; + + this._descriptionLabel = new St.Label({ + style_class: 'list-search-result-description', + y_align: Clutter.ActorAlign.CENTER, + }); + content.add_child(this._descriptionLabel); + + this._highlightTerms(); + + this.connect('destroy', () => { + if (_toggleTimeout) { + GLib.source_remove(_toggleTimeout); + _toggleTimeout = 0; + } + }); + } + + _toggleExtension() { + const state = this.extension.state; + if (![1, 2, 6, 3].includes(state) || this.extension.metadata.uuid.includes('vertical-workspaces')) + return; + + if ([2, 6].includes(state)) + Main.extensionManager.enableExtension(this.extension.uuid); + else if ([1, 3].includes(state)) + Main.extensionManager.disableExtension(this.extension.uuid); + + if (_toggleTimeout) + GLib.source_remove(_toggleTimeout); + + _toggleTimeout = GLib.timeout_add(GLib.PRIORITY_LOW, 200, + () => { + if ([7, 8].includes(this.extension.state)) + return GLib.SOURCE_CONTINUE; + + this.icon?.destroy(); + this.icon = this.metaInfo['createIcon'](this.ICON_SIZE); + this._iconBox.set_child(this.icon); + this._highlightTerms(); + + _toggleTimeout = 0; + return GLib.SOURCE_REMOVE; + } + ); + } + + get ICON_SIZE() { + return 24; + } + + _highlightTerms() { + const extension = this.extension; + const state = extension.state === 4 ? ExtensionState[this.extension.state] : ''; + const error = extension.state === 3 ? ` ERROR: ${this.extension.error}` : ''; + const update = extension.hasUpdate ? ' | UPDATE PENDING' : ''; + const text = `${this.metaInfo.version} ${state}${error}${update}`; + let markup = text;// this.metaInfo['description'].split('\n')[0]; + this._descriptionLabel.clutter_text.set_markup(markup); + } + + vfunc_clicked() { + this.activate(); + } + + activate() { + this.provider.activateResult(this.metaInfo.id); + + if (this.metaInfo.clipboardText) { + St.Clipboard.get_default().set_text( + St.ClipboardType.CLIPBOARD, this.metaInfo.clipboardText); + } + Main.overview.toggle(); + } +}); |