/** * V-Shell (Vertical Workspaces) * search.js * * @author GdH * @copyright 2022 - 2023 * @license GPL-3.0 * */ 'use strict'; const { Shell, Gio, St, Clutter } = imports.gi; const Main = imports.ui.main; const AppDisplay = imports.ui.appDisplay; const Search = imports.ui.search; const Me = imports.misc.extensionUtils.getCurrentExtension(); const _Util = Me.imports.lib.util; const _ = Me.imports.lib.settings._; const shellVersion = _Util.shellVersion; let opt; let _overrides; let _firstRun = true; let SEARCH_MAX_WIDTH; function update(reset = false) { opt = Me.imports.lib.settings.opt; const moduleEnabled = opt.get('searchModule', true); reset = reset || !moduleEnabled; // don't even touch this module if disabled if (_firstRun && reset) return; _firstRun = false; if (_overrides) _overrides.removeAll(); _updateSearchViewWidth(reset); if (reset) { Main.overview._overview._controls.layoutManager._searchController.y_align = Clutter.ActorAlign.FILL; opt = null; _overrides = null; return; } _overrides = new _Util.Overrides(); _overrides.addOverride('AppSearchProvider', AppDisplay.AppSearchProvider.prototype, AppSearchProvider); _overrides.addOverride('SearchResult', Search.SearchResult.prototype, SearchResult); _overrides.addOverride('SearchResultsView', Search.SearchResultsView.prototype, SearchResultsView); // Don't expand the search view vertically and align it to the top // this is important in the static workspace mode when the search view bg is not transparent // also the "Searching..." and "No Results" notifications will be closer to the search entry, with the distance given by margin-top in the stylesheet Main.overview._overview._controls.layoutManager._searchController.y_align = Clutter.ActorAlign.START; } function _updateSearchViewWidth(reset = false) { const searchContent = Main.overview._overview._controls.layoutManager._searchController._searchResults._content; if (!SEARCH_MAX_WIDTH) { // just store original value; const themeNode = searchContent.get_theme_node(); const width = themeNode.get_max_width(); SEARCH_MAX_WIDTH = width; } if (reset) { searchContent.set_style(''); } else { let width = Math.round(SEARCH_MAX_WIDTH * opt.SEARCH_VIEW_SCALE); searchContent.set_style(`max-width: ${width}px;`); } } // AppDisplay.AppSearchProvider const AppSearchProvider = { getInitialResultSet(terms, callback, _cancellable) { // Defer until the parental controls manager is initialized, so the // results can be filtered correctly. if (!this._parentalControlsManager.initialized) { let initializedId = this._parentalControlsManager.connect('app-filter-changed', () => { if (this._parentalControlsManager.initialized) { this._parentalControlsManager.disconnect(initializedId); this.getInitialResultSet(terms, callback, _cancellable); } }); return; } const pattern = terms.join(' '); let appInfoList = Shell.AppSystem.get_default().get_installed(); let weightList = {}; appInfoList = appInfoList.filter(appInfo => { try { appInfo.get_id(); // catch invalid file encodings } catch (e) { return false; } let string = ''; let name; let shouldShow = false; if (appInfo.get_display_name) { // show only launchers that should be visible in this DE shouldShow = appInfo.should_show() && this._parentalControlsManager.shouldShowApp(appInfo); if (shouldShow) { let dispName = appInfo.get_display_name() || ''; let gName = appInfo.get_generic_name() || ''; let description = appInfo.get_description() || ''; let categories = appInfo.get_string('Categories') || ''; let keywords = appInfo.get_string('Keywords') || ''; name = dispName; string = `${dispName} ${gName} ${description} ${categories} ${keywords}`; } } let m = -1; if (shouldShow && opt.SEARCH_FUZZY) { m = _Util.fuzzyMatch(pattern, name); m = (m + _Util.strictMatch(pattern, string)) / 2; } else if (shouldShow) { m = _Util.strictMatch(pattern, string); } if (m !== -1) weightList[appInfo.get_id()] = m; return shouldShow && (m !== -1); }); appInfoList.sort((a, b) => weightList[a.get_id()] > weightList[b.get_id()]); const usage = Shell.AppUsage.get_default(); // sort apps by usage list appInfoList.sort((a, b) => usage.compare(a.get_id(), b.get_id())); // prefer apps where any word in their name starts with the pattern appInfoList.sort((a, b) => _Util.isMoreRelevant(a.get_display_name(), b.get_display_name(), pattern)); let results = appInfoList.map(app => app.get_id()); results = results.concat(this._systemActions.getMatchingActions(terms)); if (shellVersion < 43) callback(results); else return new Promise(resolve => resolve(results)); }, // App search result size createResultObject(resultMeta) { if (resultMeta.id.endsWith('.desktop')) { const icon = new AppDisplay.AppIcon(this._appSys.lookup_app(resultMeta['id']), { expandTitleOnHover: false, }); icon.icon.setIconSize(opt.SEARCH_ICON_SIZE); return icon; } else { const icon = new AppDisplay.SystemActionIcon(this, resultMeta); icon.icon._setSizeManually = true; icon.icon.setIconSize(opt.SEARCH_ICON_SIZE); return icon; } }, }; const SearchResult = { activate() { this.provider.activateResult(this.metaInfo.id, this._resultsView.terms); if (this.metaInfo.clipboardText) { St.Clipboard.get_default().set_text( St.ClipboardType.CLIPBOARD, this.metaInfo.clipboardText); } // don't close overview if Shift key is pressed - Shift moves windows to the workspace if (!_Util.isShiftPressed()) Main.overview.toggle(); }, }; const SearchResultsView = { _updateSearchProgress() { let haveResults = this._providers.some(provider => { let display = provider.display; return display.getFirstResult() !== null; }); this._scrollView.visible = haveResults; this._statusBin.visible = !haveResults; if (!haveResults) { if (this.searchInProgress) this._statusText.set_text(_('Searching…')); else this._statusText.set_text(_('No results.')); } }, };