summaryrefslogtreecommitdiffstats
path: root/extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js')
-rw-r--r--extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js423
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();
+ }
+});