Files
DankMaterialShell/quickshell/Modals/DankLauncherV2/Controller.qml
T
447997b9ec launcher: add vpn connections as a search provider (#3391)
* launcher: add vpn connections as a search provider

Adds a dms_vpn built-in launcher plugin that lists every configured VPN
profile and toggles it on enter. Connected rows show a lock icon and a
Connected badge, in-progress rows show Connecting, everything else shows
a crossed-out key.

Profiles come from the network state the shell already fetches at
startup, so there is no new timer, process or request. The provider
returns nothing when the backend is not NetworkManager.

It ships without a trigger. A trigger prefix replaces the whole result
set, so claiming "vpn" would have hidden every application whenever
someone typed it to find their VPN client. Matching on the vpn keyword
instead keeps apps and profiles in the same list.

Co-Authored-By: Claude Opus 5 <[email protected]>

* launcher: hide vpn profiles on the home view without a trigger

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* launcher: list vpn profiles when browsing the plugin

The empty query guard also applied when opening VPN from the Browse
section, so the plugin view came up empty. The plugin filter path now
opts out of the guard, while the home view still hides the profiles.

Co-Authored-By: Claude Opus 5 <[email protected]>

* launcher: stop matching vpn profiles on connect and disconnect

Keywords match by substring, so "connect" also matched "disconnect" and
listed connected profiles too. Picking one of those disconnected it.

Co-Authored-By: Claude Opus 5 <[email protected]>

---------

Co-authored-by: Claude Opus 5 <[email protected]>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-09-15 12:41:14 -04:00

2184 lines
76 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import "Scorer.js" as Scorer
import "ControllerUtils.js" as Utils
import "NavigationHelpers.js" as Nav
import "ItemTransformers.js" as Transform
Item {
id: root
property string searchQuery: ""
property string searchMode: "all"
property string previousSearchMode: "all"
property bool autoSwitchedToFiles: false
property bool isFileSearching: false
property var sections: []
property var flatModel: []
property int selectedFlatIndex: 0
property var selectedItem: null
property bool isSearching: false
property string activePluginId: ""
property var collapsedSections: ({})
property bool keyboardNavigationActive: false
property bool active: false
property var _modeSectionsCache: ({})
property bool _queryDrivenSearch: false
property bool _diskCacheConsumed: false
property var sectionViewModes: ({})
property var pluginViewPreferences: ({})
property int gridColumns: SettingsData.appLauncherGridColumns
property int viewModeVersion: 0
property string viewModeContext: "spotlight"
property bool forceLinearNavigation: false
property bool explicitQuerySession: false
signal itemExecuted
signal searchCompleted
signal modeChanged(string mode, bool userInitiated)
signal queryChanged(string query)
signal viewModeChanged(string sectionId, string mode)
signal searchQueryRequested(string query)
signal sectionExpanded(string sectionId)
Ref {
service: AppSearchService
}
onActiveChanged: {
ClipboardService.invalidateLauncherSearchCache();
if (active) {
Qt.callLater(PluginService.ensureLauncherInstances);
return;
}
SessionData.addLauncherHistory(searchQuery, explicitQuerySession);
sections = [];
flatModel = [];
selectedItem = null;
_clearModeCache();
}
onSearchModeChanged: {
if (searchMode === "apps") {
_loadAppCategories();
} else {
appCategory = "";
appCategories = [];
}
}
Connections {
target: SettingsData
function onSortAppsAlphabeticallyChanged() {
AppSearchService.invalidateLauncherCache();
_clearModeCache();
}
function onLauncherPluginVisibilityChanged() {
AppSearchService.invalidateLauncherCache();
_clearModeCache();
if (active)
performSearch();
}
function onBuiltInPluginSettingsChanged() {
AppSearchService.invalidateLauncherCache();
_clearModeCache();
if (active)
performSearch();
}
}
Connections {
target: ClipboardService
function onLauncherSearchReady(query) {
if (!active)
return;
const clipboardBuiltInActive = activePluginId === "dms_clipboard_search";
if (!clipboardBuiltInActive && !clipboardSearchEnabledInAll())
return;
if (!clipboardBuiltInActive && searchMode !== "all")
return;
const trimmed = (searchQuery || "").trim();
if (trimmed.length < 2 && query.length > 0)
return;
const triggerMatch = detectTrigger(trimmed);
const effectiveQuery = clipboardBuiltInActive && triggerMatch.pluginId === "dms_clipboard_search" ? triggerMatch.query : trimmed;
if (query !== effectiveQuery)
return;
root.requestSearch();
}
}
Connections {
target: AppSearchService
function onCacheVersionChanged() {
if (!active)
return;
_clearModeCache();
if (searchMode === "apps") {
_loadAppCategories();
performSearch();
} else if (!searchQuery && searchMode === "all") {
performSearch();
}
}
}
Connections {
target: PluginService
function onRequestLauncherUpdate(pluginId) {
if (!active)
return;
if (activePluginId === pluginId) {
if (activePluginCategories.length <= 1)
loadPluginCategories(pluginId);
performSearch();
return;
}
if (searchQuery)
performSearch();
}
}
Process {
id: copyProcess
running: false
onExited: pasteTimer.start()
}
Timer {
id: pasteTimer
interval: 200
repeat: false
onTriggered: ClipboardService.sendPasteKeystroke()
}
function pasteSelected() {
if (!selectedItem)
return;
if (selectedItem.type === "clipboard") {
if (SettingsData.clipboardEnterToPaste) {
ClipboardService.copyEntry(selectedItem.data, function () {
root.itemExecuted();
});
} else {
ClipboardService.pasteEntry(selectedItem.data, function () {
root.itemExecuted();
});
}
return;
}
const pluginId = selectedItem.pluginId;
if (!pluginId)
return;
const pasteArgs = AppSearchService.getPluginPasteArgs(pluginId, selectedItem.data);
if (!pasteArgs)
return;
copyProcess.command = pasteArgs;
copyProcess.running = true;
itemExecuted();
}
readonly property var sectionDefinitions: [
{
id: "favorites",
title: I18n.tr("Pinned"),
icon: "push_pin",
priority: 1,
defaultViewMode: "list"
},
{
id: "apps",
title: I18n.tr("Applications"),
icon: "apps",
priority: 2,
defaultViewMode: "list"
},
{
id: "settings",
title: I18n.tr("Settings", "settings window title"),
icon: "settings",
priority: 2.35,
defaultViewMode: "list"
},
{
id: "clipboard",
title: I18n.tr("Clipboard"),
icon: "content_paste",
priority: 2.45,
defaultViewMode: "list"
},
{
id: "browse_plugins",
title: I18n.tr("Browse"),
icon: "category",
priority: 2.5,
defaultViewMode: "grid"
},
{
id: "files",
title: I18n.tr("Files"),
icon: "folder",
priority: 4,
defaultViewMode: "list"
},
{
id: "fallback",
title: I18n.tr("Commands"),
icon: "terminal",
priority: 5,
defaultViewMode: "list"
}
]
property int historyIndex: -1
property string typingBackup: ""
function navigateHistory(direction) {
let history = SessionData.launcherQueryHistory;
if (history.length === 0)
return;
if (historyIndex === -1)
typingBackup = searchQuery;
let nextIndex = historyIndex + (direction === "up" ? 1 : -1);
if (nextIndex >= history.length)
nextIndex = history.length - 1;
if (nextIndex < -1)
nextIndex = -1;
if (nextIndex === historyIndex)
return;
historyIndex = nextIndex;
let targetText = (historyIndex === -1) ? typingBackup : history[historyIndex];
setSearchQuery(targetText);
searchQueryRequested(targetText);
}
property string fileSearchType: "all"
property string fileSearchExt: ""
property string fileSearchFolder: ""
property string fileSearchSort: "score"
property string pluginFilter: ""
property string activePluginName: ""
property var activePluginCategories: []
property string activePluginCategory: ""
property string appCategory: ""
property var appCategories: []
function builtInSectionViewPref(sectionId) {
switch (sectionId) {
case "clipboard":
return getPluginViewPref("dms_clipboard_search");
case "settings":
return getPluginViewPref("dms_settings_search");
default:
return null;
}
}
function getSectionViewMode(sectionId) {
if (sectionId === "browse_plugins")
return "list";
var builtInPref = builtInSectionViewPref(sectionId);
if (builtInPref?.enforced)
return builtInPref.mode;
if (pluginViewPreferences[sectionId]?.enforced)
return pluginViewPreferences[sectionId].mode;
if (sectionViewModes[sectionId])
return sectionViewModes[sectionId];
var savedModes = viewModeContext === "appDrawer" ? (SettingsData.appDrawerSectionViewModes || {}) : (SettingsData.spotlightSectionViewModes || {});
if (savedModes[sectionId])
return savedModes[sectionId];
for (var i = 0; i < sectionDefinitions.length; i++) {
if (sectionDefinitions[i].id === sectionId)
return sectionDefinitions[i].defaultViewMode || "list";
}
if (pluginViewPreferences[sectionId]?.mode)
return pluginViewPreferences[sectionId].mode;
return "list";
}
function setSectionViewMode(sectionId, mode) {
if (sectionId === "browse_plugins")
return;
if (builtInSectionViewPref(sectionId)?.enforced)
return;
if (pluginViewPreferences[sectionId]?.enforced)
return;
sectionViewModes = Object.assign({}, sectionViewModes, {
[sectionId]: mode
});
viewModeVersion++;
if (viewModeContext === "appDrawer") {
var savedModes = Object.assign({}, SettingsData.appDrawerSectionViewModes || {}, {
[sectionId]: mode
});
SettingsData.appDrawerSectionViewModes = savedModes;
} else {
var savedModes = Object.assign({}, SettingsData.spotlightSectionViewModes || {}, {
[sectionId]: mode
});
SettingsData.spotlightSectionViewModes = savedModes;
}
viewModeChanged(sectionId, mode);
}
function canChangeSectionViewMode(sectionId) {
if (sectionId === "browse_plugins")
return false;
if (builtInSectionViewPref(sectionId)?.enforced)
return false;
return !pluginViewPreferences[sectionId]?.enforced;
}
function canCollapseSection(sectionId) {
return searchMode === "all";
}
function setPluginViewPreference(pluginId, mode, enforced) {
var prefs = Object.assign({}, pluginViewPreferences);
prefs[pluginId] = {
mode: mode,
enforced: enforced || false
};
pluginViewPreferences = prefs;
}
function applyActivePluginViewPreference(pluginId, isBuiltIn) {
var sectionId = "plugin_" + pluginId;
var pref = null;
if (isBuiltIn) {
var builtIn = AppSearchService.builtInPlugins[pluginId];
if (builtIn && builtIn.viewMode) {
pref = {
mode: builtIn.viewMode,
enforced: builtIn.viewModeEnforced === true
};
}
} else {
pref = PluginService.getPluginViewPreference(pluginId);
}
if (pref && pref.mode) {
setPluginViewPreference(sectionId, pref.mode, pref.enforced);
} else {
var prefs = Object.assign({}, pluginViewPreferences);
delete prefs[sectionId];
pluginViewPreferences = prefs;
}
}
function clearActivePluginViewPreference() {
var prefs = {};
for (var key in pluginViewPreferences) {
if (!key.startsWith("plugin_")) {
prefs[key] = pluginViewPreferences[key];
}
}
pluginViewPreferences = prefs;
}
property int _searchVersion: 0
property bool _pluginPhasePending: false
property bool _pluginPhaseForceFirst: false
property var _phase1Items: []
property bool _searchPending: false
// Leading-edge debounce: search immediately when idle, coalesce bursts
function requestSearch() {
if (searchDebounce.running) {
_searchPending = true;
searchDebounce.restart();
return;
}
_searchPending = false;
performSearch();
searchDebounce.restart();
}
Timer {
id: searchDebounce
interval: 60
onTriggered: {
if (!root._searchPending)
return;
root._searchPending = false;
root.performSearch();
}
}
Timer {
id: pluginPhaseTimer
interval: 1
onTriggered: root._performPluginPhase()
}
Timer {
id: fileSearchDebounce
interval: 200
onTriggered: root.performFileSearch()
}
function getOrTransformApp(app) {
return AppSearchService.getOrTransformApp(app, transformApp);
}
function setSearchQuery(query) {
_searchVersion++;
_queryDrivenSearch = true;
_pluginPhasePending = false;
_phase1Items = [];
pluginPhaseTimer.stop();
searchQuery = query;
requestSearch();
if (autoSwitchedToFiles && !query.startsWith("/")) {
restorePreviousMode();
return;
}
if (query.startsWith("/")) {
var prefix = Utils.parseFileSearchPrefix(query);
var explicitType = prefix && prefix.type !== null ? prefix.type : null;
var targetType = explicitType !== null ? explicitType : (SessionData.launcherLastFileSearchType || "all");
if (searchMode !== "files") {
setMode("files", true, targetType);
} else if (fileSearchType !== targetType) {
fileSearchType = targetType;
}
if (explicitType !== null && SessionData.launcherLastFileSearchType !== explicitType) {
SessionData.setLauncherLastFileSearchType(explicitType);
}
}
var filesInAll = searchMode === "all" && (SettingsData.dankLauncherV2IncludeFilesInAll || SettingsData.dankLauncherV2IncludeFoldersInAll);
if (searchMode !== "plugins" && (searchMode === "files" || query.startsWith("/") || filesInAll) && query.length > 0) {
fileSearchDebounce.restart();
}
}
function setMode(mode, isAutoSwitch, fileTypeOverride, notPersist) {
if (searchMode === mode) {
if (mode === "files" && fileTypeOverride !== undefined && fileSearchType !== fileTypeOverride) {
fileSearchType = fileTypeOverride;
performFileSearch();
}
return;
}
if (isAutoSwitch) {
previousSearchMode = searchMode;
autoSwitchedToFiles = true;
} else {
autoSwitchedToFiles = false;
}
searchMode = mode;
if (mode === "files") {
fileSearchType = fileTypeOverride !== undefined ? fileTypeOverride : (SessionData.launcherLastFileSearchType || "all");
}
modeChanged(mode, !isAutoSwitch && notPersist !== true);
performSearch();
var filesInAll = mode === "all" && (SettingsData.dankLauncherV2IncludeFilesInAll || SettingsData.dankLauncherV2IncludeFoldersInAll) && searchQuery.length > 0;
if (mode === "files" || filesInAll) {
fileSearchDebounce.restart();
}
}
function restorePreviousMode() {
if (!autoSwitchedToFiles)
return;
autoSwitchedToFiles = false;
searchMode = previousSearchMode;
modeChanged(previousSearchMode, false);
performSearch();
}
function cycleMode(reverse = false) {
var modes = ["all", "apps", "files", "plugins"];
var currentIndex = modes.indexOf(searchMode);
if (!reverse)
var nextIndex = (currentIndex + 1) % modes.length;
else
var nextIndex = (currentIndex - 1 + modes.length) % modes.length;
setMode(modes[nextIndex]);
}
function reset() {
searchQuery = "";
searchMode = "all";
previousSearchMode = "all";
autoSwitchedToFiles = false;
isFileSearching = false;
fileSearchType = "all";
fileSearchExt = "";
fileSearchFolder = "";
fileSearchSort = "score";
sections = [];
flatModel = [];
selectedFlatIndex = 0;
selectedItem = null;
isSearching = false;
activePluginId = "";
activePluginName = "";
activePluginCategories = [];
activePluginCategory = "";
appCategory = "";
appCategories = [];
pluginFilter = "";
collapsedSections = {};
_clearModeCache();
_queryDrivenSearch = false;
_pluginPhasePending = false;
_pluginPhaseForceFirst = false;
_phase1Items = [];
pluginPhaseTimer.stop();
}
function loadPluginCategories(pluginId) {
if (!pluginId) {
if (activePluginCategories.length > 0) {
activePluginCategories = [];
activePluginCategory = "";
}
return;
}
const categories = AppSearchService.getPluginLauncherCategories(pluginId);
if (categories.length === activePluginCategories.length) {
let same = true;
for (let i = 0; i < categories.length; i++) {
if (categories[i].id !== activePluginCategories[i]?.id) {
same = false;
break;
}
}
if (same)
return;
}
activePluginCategories = categories;
activePluginCategory = "";
AppSearchService.setPluginLauncherCategory(pluginId, "");
}
function setActivePluginCategory(categoryId) {
if (activePluginCategory === categoryId)
return;
activePluginCategory = categoryId;
AppSearchService.setPluginLauncherCategory(activePluginId, categoryId);
performSearch();
}
function setAppCategory(category) {
if (appCategory === category)
return;
appCategory = category;
_queryDrivenSearch = true;
_clearModeCache();
performSearch();
}
function _loadAppCategories() {
appCategories = AppSearchService.getAllCategories();
}
function setFileSearchType(type) {
if (fileSearchType === type)
return;
fileSearchType = type;
SessionData.setLauncherLastFileSearchType(type);
performFileSearch();
}
function setFileSearchExt(ext) {
if (fileSearchExt === ext)
return;
fileSearchExt = ext;
performFileSearch();
}
function setFileSearchFolder(folder) {
if (fileSearchFolder === folder)
return;
fileSearchFolder = folder;
performFileSearch();
}
function setFileSearchSort(sort) {
if (fileSearchSort === sort)
return;
fileSearchSort = sort;
performFileSearch();
}
function clearPluginFilter() {
if (pluginFilter) {
pluginFilter = "";
performSearch();
return true;
}
return false;
}
function preserveSelectionAfterUpdate(forceFirst) {
if (forceFirst)
return function () {
return getFirstItemIndex();
};
var previousSelectedId = selectedItem?.id || "";
return function (newFlatModel) {
if (!previousSelectedId)
return getFirstItemIndex();
for (var i = 0; i < newFlatModel.length; i++) {
if (!newFlatModel[i].isHeader && newFlatModel[i].item?.id === previousSelectedId)
return i;
}
return getFirstItemIndex();
};
}
function performSearch() {
queryChanged(searchQuery);
var currentVersion = _searchVersion;
isSearching = true;
var shouldResetSelection = _queryDrivenSearch;
_queryDrivenSearch = false;
var restoreSelection = preserveSelectionAfterUpdate(shouldResetSelection);
var cachedSections = AppSearchService.getCachedDefaultSections();
if (!cachedSections && !_diskCacheConsumed && !searchQuery && searchMode === "all" && !pluginFilter) {
_diskCacheConsumed = true;
var diskSections = _loadDiskCache();
if (diskSections) {
activePluginId = "";
activePluginName = "";
activePluginCategories = [];
activePluginCategory = "";
clearActivePluginViewPreference();
for (var i = 0; i < diskSections.length; i++) {
if (collapsedSections[diskSections[i].id] !== undefined)
diskSections[i].collapsed = collapsedSections[diskSections[i].id];
}
_applyHighlights(diskSections, "");
flatModel = Scorer.flattenSections(diskSections);
sections = diskSections;
selectedFlatIndex = restoreSelection(flatModel);
updateSelectedItem();
isSearching = false;
searchCompleted();
return;
}
}
if (cachedSections && !searchQuery && searchMode === "all" && !pluginFilter) {
activePluginId = "";
activePluginName = "";
activePluginCategories = [];
activePluginCategory = "";
clearActivePluginViewPreference();
var modeCache = _getCachedModeData("all");
if (modeCache) {
_applyHighlights(modeCache.sections, "");
sections = modeCache.sections;
flatModel = modeCache.flatModel;
} else {
var newSections = cachedSections.map(function (s) {
var copy = Object.assign({}, s, {
items: s.items ? s.items.slice() : []
});
if (collapsedSections[s.id] !== undefined)
copy.collapsed = collapsedSections[s.id];
return copy;
});
_applyHighlights(newSections, "");
flatModel = Scorer.flattenSections(newSections);
sections = newSections;
_setCachedModeData("all", sections, flatModel);
}
selectedFlatIndex = restoreSelection(flatModel);
updateSelectedItem();
isSearching = false;
searchCompleted();
return;
}
var allItems = [];
var triggerMatch = detectTrigger(searchQuery);
if (triggerMatch.pluginId) {
var pluginChanged = activePluginId !== triggerMatch.pluginId;
activePluginId = triggerMatch.pluginId;
activePluginName = getPluginName(triggerMatch.pluginId, triggerMatch.isBuiltIn);
applyActivePluginViewPreference(triggerMatch.pluginId, triggerMatch.isBuiltIn);
if (pluginChanged && !triggerMatch.isBuiltIn)
loadPluginCategories(triggerMatch.pluginId);
var pluginItems = getPluginItems(triggerMatch.pluginId, triggerMatch.query);
for (var k = 0; k < pluginItems.length; k++)
allItems.push(pluginItems[k]);
if (triggerMatch.isBuiltIn) {
var builtInItems = AppSearchService.getBuiltInLauncherItems(triggerMatch.pluginId, triggerMatch.query);
for (var j = 0; j < builtInItems.length; j++) {
builtInItems[j]._preScored = 1000 - j;
allItems.push(transformBuiltInSearchItem(builtInItems[j], triggerMatch.pluginId));
}
}
var dynamicDefs = buildDynamicSectionDefs(allItems);
var scoredItems = Scorer.scoreItems(allItems, triggerMatch.query, getFrecencyForItem);
var sortAlpha = !triggerMatch.query && SettingsData.sortAppsAlphabetically;
var newSections = Scorer.groupBySection(scoredItems, dynamicDefs, sortAlpha, 500);
for (var sid in collapsedSections) {
for (var i = 0; i < newSections.length; i++) {
if (newSections[i].id === sid) {
newSections[i].collapsed = collapsedSections[sid];
}
}
}
_applyHighlights(newSections, triggerMatch.query);
flatModel = Scorer.flattenSections(newSections);
sections = newSections;
selectedFlatIndex = restoreSelection(flatModel);
updateSelectedItem();
isSearching = false;
searchCompleted();
return;
}
activePluginId = "";
activePluginName = "";
activePluginCategories = [];
activePluginCategory = "";
clearActivePluginViewPreference();
if (searchMode === "files") {
var prefixInfo = Utils.parseFileSearchPrefix(searchQuery);
var fileQuery = prefixInfo ? prefixInfo.query : searchQuery.trim();
isFileSearching = fileQuery.length >= 2 && DSearchService.dsearchAvailable;
sections = [];
flatModel = [];
selectedFlatIndex = 0;
selectedItem = null;
isSearching = false;
searchCompleted();
return;
}
if (searchMode === "apps") {
var isCategoryFiltered = appCategory && appCategory !== I18n.tr("All");
var cachedSections = AppSearchService.getCachedDefaultSections();
if (cachedSections && !searchQuery && !isCategoryFiltered) {
var modeCache = _getCachedModeData("apps");
if (modeCache) {
_applyHighlights(modeCache.sections, "");
sections = modeCache.sections;
flatModel = modeCache.flatModel;
} else {
var appSectionIds = ["favorites", "apps"];
var newSections = cachedSections.filter(function (s) {
return appSectionIds.indexOf(s.id) !== -1;
}).map(function (s) {
var copy = Object.assign({}, s, {
items: s.items ? s.items.slice() : []
});
if (collapsedSections[s.id] !== undefined)
copy.collapsed = collapsedSections[s.id];
return copy;
});
_applyHighlights(newSections, "");
flatModel = Scorer.flattenSections(newSections);
sections = newSections;
_setCachedModeData("apps", sections, flatModel);
}
selectedFlatIndex = restoreSelection(flatModel);
updateSelectedItem();
isSearching = false;
searchCompleted();
return;
}
if (isCategoryFiltered) {
var rawApps = AppSearchService.getAppsInCategory(appCategory);
for (var i = 0; i < rawApps.length; i++) {
allItems.push(getOrTransformApp(rawApps[i]));
}
var allCoreApps = AppSearchService.getCoreApps("");
for (var i = 0; i < allCoreApps.length; i++) {
var coreAppCats = AppSearchService.getCategoriesForApp(allCoreApps[i]);
if (coreAppCats.indexOf(appCategory) !== -1)
allItems.push(transformCoreApp(allCoreApps[i]));
}
} else {
var apps = searchApps(searchQuery);
for (var i = 0; i < apps.length; i++) {
allItems.push(apps[i]);
}
}
var scoredItems = Scorer.scoreItems(allItems, searchQuery, getFrecencyForItem);
var sortAlpha = !searchQuery && SettingsData.sortAppsAlphabetically;
var newSections = Scorer.groupBySection(scoredItems, buildDynamicSectionDefs(allItems), sortAlpha, searchQuery ? 50 : 500);
for (var sid in collapsedSections) {
for (var i = 0; i < newSections.length; i++) {
if (newSections[i].id === sid) {
newSections[i].collapsed = collapsedSections[sid];
}
}
}
_applyHighlights(newSections, searchQuery);
flatModel = Scorer.flattenSections(newSections);
sections = newSections;
selectedFlatIndex = restoreSelection(flatModel);
updateSelectedItem();
isSearching = false;
searchCompleted();
return;
}
if (searchMode === "plugins") {
if (!searchQuery && !pluginFilter) {
var browseItems = getPluginBrowseItems();
for (var k = 0; k < browseItems.length; k++)
allItems.push(browseItems[k]);
} else if (pluginFilter) {
var isBuiltInFilter = !!AppSearchService.builtInPlugins[pluginFilter];
applyActivePluginViewPreference(pluginFilter, isBuiltInFilter);
var filterItems = getPluginItems(pluginFilter, searchQuery);
for (var k = 0; k < filterItems.length; k++)
allItems.push(filterItems[k]);
var builtInItems = AppSearchService.getBuiltInLauncherItems(pluginFilter, searchQuery, true);
for (var j = 0; j < builtInItems.length; j++) {
allItems.push(transformBuiltInSearchItem(builtInItems[j], pluginFilter));
}
} else {
var emptyTriggerPlugins = getEmptyTriggerPlugins();
for (var i = 0; i < emptyTriggerPlugins.length; i++) {
var pluginId = emptyTriggerPlugins[i];
var pItems = getPluginItems(pluginId, searchQuery);
for (var k = 0; k < pItems.length; k++)
allItems.push(pItems[k]);
}
var builtInLauncherPlugins = getBuiltInEmptyTriggerLaunchers();
for (var i = 0; i < builtInLauncherPlugins.length; i++) {
var pluginId = builtInLauncherPlugins[i];
var blItems = AppSearchService.getBuiltInLauncherItems(pluginId, searchQuery);
for (var j = 0; j < blItems.length; j++) {
allItems.push(transformBuiltInSearchItem(blItems[j], pluginId));
}
}
}
var dynamicDefs = buildDynamicSectionDefs(allItems);
var scoredItems = Scorer.scoreItems(allItems, searchQuery, getFrecencyForItem);
var sortAlpha = !searchQuery && SettingsData.sortAppsAlphabetically;
var newSections = Scorer.groupBySection(scoredItems, dynamicDefs, sortAlpha, 500);
for (var sid in collapsedSections) {
for (var i = 0; i < newSections.length; i++) {
if (newSections[i].id === sid) {
newSections[i].collapsed = collapsedSections[sid];
}
}
}
_applyHighlights(newSections, searchQuery);
flatModel = Scorer.flattenSections(newSections);
sections = newSections;
selectedFlatIndex = restoreSelection(flatModel);
updateSelectedItem();
isSearching = false;
searchCompleted();
return;
}
var apps = searchApps(searchQuery);
for (var i = 0; i < apps.length; i++) {
allItems.push(apps[i]);
}
if (searchMode === "all") {
appendSharedAllResults(allItems, searchQuery);
if (searchQuery && searchQuery.length >= 2) {
_pluginPhasePending = true;
_phase1Items = allItems.slice();
_pluginPhaseForceFirst = shouldResetSelection;
pluginPhaseTimer.restart();
isSearching = true;
searchCompleted();
return;
} else if (!searchQuery) {
_pluginPhasePending = true;
_phase1Items = allItems.slice();
_pluginPhaseForceFirst = shouldResetSelection;
pluginPhaseTimer.restart();
}
}
var dynamicDefs = buildDynamicSectionDefs(allItems);
if (currentVersion !== _searchVersion) {
isSearching = false;
return;
}
var scoredItems = Scorer.scoreItems(allItems, searchQuery, getFrecencyForItem);
var sortAlpha = !searchQuery && SettingsData.sortAppsAlphabetically;
var newSections = Scorer.groupBySection(scoredItems, dynamicDefs, sortAlpha, searchQuery ? 50 : 500);
if (currentVersion !== _searchVersion) {
isSearching = false;
return;
}
for (var i = 0; i < newSections.length; i++) {
var sid = newSections[i].id;
if (collapsedSections[sid] !== undefined) {
newSections[i].collapsed = collapsedSections[sid];
}
}
_applyHighlights(newSections, searchQuery);
flatModel = Scorer.flattenSections(newSections);
sections = newSections;
selectedFlatIndex = restoreSelection(flatModel);
updateSelectedItem();
isSearching = _pluginPhasePending;
searchCompleted();
}
function _performPluginPhase() {
_pluginPhasePending = false;
if (searchMode !== "all")
return;
if (searchQuery && searchQuery.length < 2)
return;
var currentVersion = _searchVersion;
var restoreSelection = preserveSelectionAfterUpdate(_pluginPhaseForceFirst);
var allItems = _phase1Items;
_phase1Items = [];
if (!searchQuery) {
var emptyTriggerOrdered = getEmptyTriggerPluginsOrdered();
for (var i = 0; i < emptyTriggerOrdered.length; i++) {
if (currentVersion !== _searchVersion)
return;
var plugin = emptyTriggerOrdered[i];
if (plugin.isBuiltIn) {
var blItems = AppSearchService.getBuiltInLauncherItems(plugin.id, searchQuery);
for (var j = 0; j < blItems.length; j++)
allItems.push(transformBuiltInSearchItem(blItems[j], plugin.id));
} else {
var pItems = getPluginItems(plugin.id, searchQuery);
for (var j = 0; j < pItems.length; j++)
allItems.push(pItems[j]);
}
}
var browseItems = getPluginBrowseItems();
for (var i = 0; i < browseItems.length; i++)
allItems.push(browseItems[i]);
} else {
var allPluginsOrdered = getAllVisiblePluginsOrdered();
var maxPerPlugin = 10;
for (var i = 0; i < allPluginsOrdered.length; i++) {
if (currentVersion !== _searchVersion)
return;
var plugin = allPluginsOrdered[i];
if (plugin.isBuiltIn && (plugin.id === "dms_settings_search" || plugin.id === "dms_clipboard_search"))
continue;
if (plugin.isBuiltIn) {
var blItems = AppSearchService.getBuiltInLauncherItems(plugin.id, searchQuery);
var blLimit = Math.min(blItems.length, maxPerPlugin);
for (var j = 0; j < blLimit; j++) {
var item = transformBuiltInSearchItem(blItems[j], plugin.id);
item._preScored = 900 - j;
allItems.push(item);
}
} else {
var pItems = getPluginItems(plugin.id, searchQuery, maxPerPlugin);
for (var j = 0; j < pItems.length; j++) {
pItems[j]._preScored = 900 - j;
allItems.push(pItems[j]);
}
}
}
}
if (currentVersion !== _searchVersion)
return;
var dynamicDefs = buildDynamicSectionDefs(allItems);
var scoredItems = Scorer.scoreItems(allItems, searchQuery, getFrecencyForItem);
var sortAlpha = !searchQuery && SettingsData.sortAppsAlphabetically;
var newSections = Scorer.groupBySection(scoredItems, dynamicDefs, sortAlpha, searchQuery ? 50 : 500);
if (currentVersion !== _searchVersion)
return;
for (var i = 0; i < newSections.length; i++) {
var sid = newSections[i].id;
if (collapsedSections[sid] !== undefined)
newSections[i].collapsed = collapsedSections[sid];
}
_applyHighlights(newSections, searchQuery);
flatModel = Scorer.flattenSections(newSections);
sections = newSections;
if (!AppSearchService.isCacheValid() && !searchQuery && !pluginFilter) {
AppSearchService.setCachedDefaultSections(sections, flatModel);
_saveDiskCache(sections);
}
selectedFlatIndex = restoreSelection(flatModel);
updateSelectedItem();
isSearching = false;
searchCompleted();
}
function performFileSearch() {
if (!DSearchService.dsearchAvailable)
return;
var fileQuery = "";
var effectiveType = fileSearchType || "all";
var includeFiles = SettingsData.dankLauncherV2IncludeFilesInAll;
var includeFolders = SettingsData.dankLauncherV2IncludeFoldersInAll;
if (searchQuery.startsWith("/")) {
var prefixInfo = Utils.parseFileSearchPrefix(searchQuery);
fileQuery = prefixInfo ? prefixInfo.query : searchQuery.substring(1).trim();
} else if (searchMode === "files") {
fileQuery = searchQuery.trim();
} else if (searchMode === "all" && (includeFiles || includeFolders)) {
fileQuery = searchQuery.trim();
if (includeFiles && !includeFolders)
effectiveType = "file";
else if (!includeFiles && includeFolders)
effectiveType = "dir";
else
effectiveType = "all";
} else {
return;
}
if (fileQuery.length < 2) {
isFileSearching = false;
return;
}
isFileSearching = true;
var splitBothTypes = searchMode === "all" && includeFiles && includeFolders && DSearchService.supportsTypeFilter;
var queryTypes = splitBothTypes ? ["file", "dir"] : [effectiveType];
var pending = queryTypes.length;
var aggregatedItems = [];
for (var t = 0; t < queryTypes.length; t++) {
var queryType = queryTypes[t];
var params = {
limit: 20,
fuzzy: true,
sort: fileSearchSort || "score",
desc: true
};
if (DSearchService.supportsTypeFilter) {
params.type = (queryType && queryType !== "all") ? queryType : "all";
}
if (fileSearchExt) {
params.ext = fileSearchExt;
}
if (fileSearchFolder) {
params.folder = fileSearchFolder;
}
DSearchService.search(fileQuery, params, function (response) {
pending--;
if (!response.error) {
var hits = response.result?.hits || [];
for (var i = 0; i < hits.length; i++) {
var hit = hits[i];
var docTypes = hit.locations?.doc_type;
var isDir = docTypes ? !!docTypes["dir"] : false;
aggregatedItems.push(transformFileResult({
path: hit.id || "",
score: hit.score || 0,
is_dir: isDir
}));
}
}
if (pending > 0)
return;
isFileSearching = false;
_applyFileSearchResults(aggregatedItems, effectiveType);
});
}
}
function _applyFileSearchResults(fileItems, effectiveType) {
var fileSections = [];
var showType = effectiveType;
var order = SettingsData.launcherPluginOrder || [];
var filesOrderIdx = order.indexOf("__files");
var foldersOrderIdx = order.indexOf("__folders");
var filesPriority = filesOrderIdx !== -1 ? 2.6 + filesOrderIdx * 0.01 : 4;
var foldersPriority = foldersOrderIdx !== -1 ? 2.6 + foldersOrderIdx * 0.01 : 4.1;
if (showType === "all" && DSearchService.supportsTypeFilter) {
var onlyFiles = [];
var onlyDirs = [];
for (var j = 0; j < fileItems.length; j++) {
if (fileItems[j].data?.is_dir)
onlyDirs.push(fileItems[j]);
else
onlyFiles.push(fileItems[j]);
}
if (onlyFiles.length > 0) {
fileSections.push({
id: "files",
title: I18n.tr("Files"),
icon: "insert_drive_file",
priority: filesPriority,
items: onlyFiles,
collapsed: collapsedSections["files"] || false,
flatStartIndex: 0
});
}
if (onlyDirs.length > 0) {
fileSections.push({
id: "folders",
title: I18n.tr("Folders"),
icon: "folder",
priority: foldersPriority,
items: onlyDirs,
collapsed: collapsedSections["folders"] || false,
flatStartIndex: 0
});
}
} else {
var filesIcon = showType === "dir" ? "folder" : showType === "file" ? "insert_drive_file" : "folder";
var filesTitle = showType === "dir" ? I18n.tr("Folders") : I18n.tr("Files");
var singlePriority = showType === "dir" ? foldersPriority : filesPriority;
if (fileItems.length > 0) {
fileSections.push({
id: "files",
title: filesTitle,
icon: filesIcon,
priority: singlePriority,
items: fileItems,
collapsed: collapsedSections["files"] || false,
flatStartIndex: 0
});
}
}
var newSections;
if (searchMode === "files") {
newSections = fileSections;
} else {
var existingNonFile = sections.filter(function (s) {
return s.id !== "files" && s.id !== "folders";
});
newSections = existingNonFile.concat(fileSections);
}
newSections.sort(function (a, b) {
return a.priority - b.priority;
});
_applyHighlights(newSections, searchQuery);
flatModel = Scorer.flattenSections(newSections);
sections = newSections;
selectedFlatIndex = getFirstItemIndex();
updateSelectedItem();
}
function searchApps(query) {
var apps = AppSearchService.searchApplications(query);
var items = [];
for (var i = 0; i < apps.length; i++) {
items.push(getOrTransformApp(apps[i]));
}
var coreApps = AppSearchService.getCoreApps(query);
for (var i = 0; i < coreApps.length; i++) {
items.push(transformCoreApp(coreApps[i]));
}
return items;
}
function transformApp(app) {
var appId = app.id || app.execString || app.exec || "";
var override = SessionData.getAppOverride(appId);
return Transform.transformApp(app, override, [], I18n.tr("Launch"));
}
function transformCoreApp(app) {
return Transform.transformCoreApp(app, I18n.tr("Open"));
}
function transformBuiltInLauncherItem(item, pluginId) {
return Transform.transformBuiltInLauncherItem(item, pluginId, I18n.tr("Open"));
}
function transformBuiltInSearchItem(item, pluginId) {
if (pluginId === "dms_clipboard_search" || item.type === "clipboard") {
var transformed = transformClipboardEntry(item.data || item);
if (item._preScored !== undefined)
transformed._preScored = item._preScored;
return transformed;
}
return transformBuiltInLauncherItem(item, pluginId);
}
function transformFileResult(file) {
return Transform.transformFileResult(file, I18n.tr("Open"), I18n.tr("Open folder"), I18n.tr("Copy path"), I18n.tr("Open in terminal"));
}
function transformClipboardEntry(entry) {
var copyLabel = I18n.tr("Copy");
var pasteLabel = I18n.tr("Paste");
var primaryLabel = SettingsData.clipboardEnterToPaste ? pasteLabel : copyLabel;
var pasteHintLabel = SettingsData.clipboardEnterToPaste ? I18n.tr("Shift+Enter to copy") : I18n.tr("Shift+Enter to paste");
return Transform.transformClipboardItem(entry, copyLabel, pasteLabel, primaryLabel, I18n.tr("Image"), I18n.tr("Text"), I18n.tr("Pinned"), pasteHintLabel, "", I18n.tr("Clipboard"));
}
function builtInLauncherVisibleInAll(pluginId) {
return SettingsData.getBuiltInPluginSetting(pluginId, "enabled", true) && SettingsData.getPluginAllowWithoutTrigger(pluginId);
}
function clipboardSearchEnabledInAll() {
return builtInLauncherVisibleInAll("dms_clipboard_search") && ClipboardService.clipboardAvailable;
}
function appendSharedAllResults(allItems, query) {
if (!query || query.length < 2)
return;
if (builtInLauncherVisibleInAll("dms_settings_search")) {
var settingsItems = AppSearchService.getBuiltInLauncherItems("dms_settings_search", query);
var settingsLimit = Math.min(settingsItems.length, 8);
for (var i = 0; i < settingsLimit; i++) {
settingsItems[i]._preScored = 890 - i;
allItems.push(transformBuiltInSearchItem(settingsItems[i], "dms_settings_search"));
}
}
if (clipboardSearchEnabledInAll()) {
var clipboardItems = AppSearchService.getBuiltInLauncherItems("dms_clipboard_search", query);
var clipboardLimit = Math.min(clipboardItems.length, 8);
for (var j = 0; j < clipboardLimit; j++) {
clipboardItems[j]._preScored = 840 - j;
allItems.push(transformBuiltInSearchItem(clipboardItems[j], "dms_clipboard_search"));
}
}
}
function detectTrigger(query) {
if (!query || query.length === 0)
return {
pluginId: null,
query: query
};
var pluginTriggers = PluginService.getAllPluginTriggers();
for (var trigger in pluginTriggers) {
if (trigger && query.startsWith(trigger)) {
return {
pluginId: pluginTriggers[trigger],
query: query.substring(trigger.length).trim()
};
}
}
var builtInTriggers = AppSearchService.getBuiltInLauncherTriggers();
for (var trigger in builtInTriggers) {
if (trigger && query.startsWith(trigger)) {
return {
pluginId: builtInTriggers[trigger],
query: query.substring(trigger.length).trim(),
isBuiltIn: true
};
}
}
return {
pluginId: null,
query: query
};
}
function getEmptyTriggerPlugins() {
var plugins = PluginService.getPluginsWithEmptyTrigger();
var visible = plugins.filter(function (pluginId) {
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
});
return sortPluginIdsByOrder(visible);
}
function getAllLauncherPluginIds() {
var launchers = PluginService.getLauncherPlugins();
return Object.keys(launchers);
}
function getVisibleLauncherPluginIds() {
var launchers = PluginService.getLauncherPlugins();
var visible = Object.keys(launchers).filter(function (pluginId) {
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
});
return sortPluginIdsByOrder(visible);
}
function getAllBuiltInLauncherIds() {
var launchers = AppSearchService.getBuiltInLauncherPlugins();
return Object.keys(launchers);
}
function getVisibleBuiltInLauncherIds() {
var launchers = AppSearchService.getBuiltInLauncherPlugins();
var visible = Object.keys(launchers).filter(function (pluginId) {
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
});
return sortPluginIdsByOrder(visible);
}
function sortPluginIdsByOrder(pluginIds) {
return Utils.sortPluginIdsByOrder(pluginIds, SettingsData.launcherPluginOrder || []);
}
function getAllVisiblePluginsOrdered() {
var thirdPartyLaunchers = PluginService.getLauncherPlugins() || {};
var builtInLaunchers = AppSearchService.getBuiltInLauncherPlugins() || {};
var all = [];
for (var id in thirdPartyLaunchers) {
if (SettingsData.getPluginAllowWithoutTrigger(id))
all.push({
id: id,
isBuiltIn: false
});
}
for (var id in builtInLaunchers) {
if (SettingsData.getPluginAllowWithoutTrigger(id))
all.push({
id: id,
isBuiltIn: true
});
}
return Utils.sortPluginsOrdered(all, SettingsData.launcherPluginOrder || []);
}
function getEmptyTriggerPluginsOrdered() {
var thirdParty = PluginService.getPluginsWithEmptyTrigger() || [];
var builtIn = AppSearchService.getBuiltInLauncherPluginsWithEmptyTrigger() || [];
var all = [];
for (var i = 0; i < thirdParty.length; i++) {
var id = thirdParty[i];
if (SettingsData.getPluginAllowWithoutTrigger(id))
all.push({
id: id,
isBuiltIn: false
});
}
for (var i = 0; i < builtIn.length; i++) {
var id = builtIn[i];
if (SettingsData.getPluginAllowWithoutTrigger(id))
all.push({
id: id,
isBuiltIn: true
});
}
return Utils.sortPluginsOrdered(all, SettingsData.launcherPluginOrder || []);
}
function getPluginBrowseItems() {
var items = [];
var browseLabel = I18n.tr("Browse");
var triggerLabel = I18n.tr("Trigger: %1");
var noTriggerLabel = I18n.tr("No trigger");
var launchers = PluginService.getLauncherPlugins();
for (var pluginId in launchers) {
var trigger = PluginService.getPluginTrigger(pluginId);
var isAllowed = SettingsData.getPluginAllowWithoutTrigger(pluginId);
items.push(Transform.createPluginBrowseItem(pluginId, launchers[pluginId], trigger, false, isAllowed, browseLabel, triggerLabel, noTriggerLabel));
}
var builtInLaunchers = AppSearchService.getBuiltInLauncherPlugins();
for (var pluginId in builtInLaunchers) {
var trigger = AppSearchService.getBuiltInPluginTrigger(pluginId);
var isAllowed = SettingsData.getPluginAllowWithoutTrigger(pluginId);
items.push(Transform.createPluginBrowseItem(pluginId, builtInLaunchers[pluginId], trigger, true, isAllowed, browseLabel, triggerLabel, noTriggerLabel));
}
return items;
}
function getBuiltInEmptyTriggerLaunchers() {
var plugins = AppSearchService.getBuiltInLauncherPluginsWithEmptyTrigger();
var visible = plugins.filter(function (pluginId) {
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
});
return sortPluginIdsByOrder(visible);
}
function getPluginItems(pluginId, query, limit) {
var items = AppSearchService.getPluginItemsForPlugin(pluginId, query);
var count = limit > 0 && limit < items.length ? limit : items.length;
var transformed = [];
for (var i = 0; i < count; i++) {
transformed.push(transformPluginItem(items[i], pluginId));
}
return transformed;
}
function getPluginName(pluginId, isBuiltIn) {
if (isBuiltIn) {
var plugin = AppSearchService.builtInPlugins[pluginId];
return plugin ? plugin.name : pluginId;
}
var launchers = PluginService.getLauncherPlugins();
if (launchers[pluginId]) {
return launchers[pluginId].name || pluginId;
}
return pluginId;
}
function getPluginMetadata(pluginId) {
var builtIn = AppSearchService.builtInPlugins[pluginId];
if (builtIn) {
return {
name: builtIn.name || pluginId,
icon: builtIn.cornerIcon || "extension"
};
}
var launchers = PluginService.getLauncherPlugins();
if (launchers[pluginId]) {
var rawIcon = launchers[pluginId].icon || "extension";
return {
name: launchers[pluginId].name || pluginId,
icon: Utils.stripIconPrefix(rawIcon)
};
}
return {
name: pluginId,
icon: "extension"
};
}
function buildDynamicSectionDefs(items) {
var baseDefs = sectionDefinitions.map(function (def) {
return Object.assign({}, def);
});
var pluginSections = {};
var order = SettingsData.launcherPluginOrder || [];
var orderMap = {};
for (var k = 0; k < order.length; k++)
orderMap[order[k]] = k;
var unorderedPriority = 2.6 + order.length * 0.01;
for (var d = 0; d < baseDefs.length; d++) {
var virtualId = baseDefs[d].id === "settings" ? "dms_settings_search" : baseDefs[d].id === "clipboard" ? "dms_clipboard_search" : "";
if (virtualId && orderMap[virtualId] !== undefined)
baseDefs[d].priority = 2.6 + orderMap[virtualId] * 0.01;
}
for (var i = 0; i < items.length; i++) {
var section = items[i].section;
if (!section || !section.startsWith("plugin_"))
continue;
if (pluginSections[section])
continue;
var pluginId = section.substring(7);
var meta = getPluginMetadata(pluginId);
var viewPref = getPluginViewPref(pluginId);
var orderIdx = orderMap[pluginId];
var defaultPriority = AppSearchService.builtInPlugins[pluginId]?.defaultSectionPriority;
var priority;
if (orderIdx !== undefined) {
priority = 2.6 + orderIdx * 0.01;
} else if (defaultPriority !== undefined) {
priority = defaultPriority;
} else {
priority = unorderedPriority;
unorderedPriority += 0.01;
}
pluginSections[section] = {
id: section,
title: meta.name,
icon: meta.icon,
priority: priority,
defaultViewMode: viewPref.mode || "list"
};
if (viewPref.mode)
setPluginViewPreference(section, viewPref.mode, viewPref.enforced);
}
for (var sectionId in pluginSections) {
baseDefs.push(pluginSections[sectionId]);
}
baseDefs.sort(function (a, b) {
return a.priority - b.priority;
});
return baseDefs;
}
function getPluginViewPref(pluginId) {
var builtIn = AppSearchService.builtInPlugins[pluginId];
if (builtIn && builtIn.viewMode) {
return {
mode: builtIn.viewMode,
enforced: builtIn.viewModeEnforced === true
};
}
var pref = PluginService.getPluginViewPreference(pluginId);
if (pref && pref.mode) {
return pref;
}
return {
mode: "list",
enforced: false
};
}
function transformPluginItem(item, pluginId) {
return Transform.transformPluginItem(item, pluginId, I18n.tr("Select"));
}
function getFrecencyForItem(item) {
if (item.type !== "app")
return null;
var appId = item.id;
var usageRanking = AppUsageHistoryData.appUsageRanking || {};
var idVariants = [appId, appId.replace(".desktop", "")];
var usageData = null;
for (var i = 0; i < idVariants.length; i++) {
if (usageRanking[idVariants[i]]) {
usageData = usageRanking[idVariants[i]];
break;
}
}
return {
usageCount: usageData?.usageCount || 0
};
}
function getFirstItemIndex() {
return Nav.getFirstItemIndex(flatModel);
}
function _getCachedModeData(mode) {
return _modeSectionsCache[mode] || null;
}
function _setCachedModeData(mode, sectionsData, flatModelData) {
var cache = Object.assign({}, _modeSectionsCache);
cache[mode] = {
sections: sectionsData,
flatModel: flatModelData
};
_modeSectionsCache = cache;
}
function _clearModeCache() {
_modeSectionsCache = {};
}
function _saveDiskCache(sectionsData) {
var serializable = [];
for (var i = 0; i < sectionsData.length; i++) {
var s = sectionsData[i];
var items = [];
var srcItems = s.items || [];
for (var j = 0; j < srcItems.length; j++) {
var it = srcItems[j];
items.push({
id: it.id,
type: it.type,
name: it.name || "",
subtitle: it.subtitle || "",
icon: it.icon || "",
iconType: it.iconType || "image",
iconFull: it.iconFull || "",
section: it.section || "",
isCore: it.isCore || false,
isBuiltInLauncher: it.isBuiltInLauncher || false,
pluginId: it.pluginId || "",
source: it.source || ""
});
}
serializable.push({
id: s.id,
title: s.title || "",
icon: s.icon || "",
priority: s.priority || 0,
items: items
});
}
CacheData.saveLauncherCache(serializable);
}
function _actionsFromDesktopEntry(appId) {
if (!appId)
return [];
var entry = DesktopEntries.heuristicLookup(appId);
if (!entry || !entry.actions || entry.actions.length === 0)
return [];
var result = [];
for (var i = 0; i < entry.actions.length; i++) {
result.push({
name: entry.actions[i].name,
icon: "play_arrow",
actionData: entry.actions[i]
});
}
return result;
}
function _diskCacheSectionDef(sectionId) {
for (var i = 0; i < sectionDefinitions.length; i++) {
if (sectionDefinitions[i].id === sectionId)
return sectionDefinitions[i];
}
return null;
}
function _diskCacheSection(def, items) {
return {
id: def.id || "",
title: def.title || "",
icon: def.icon || "",
priority: def.priority || 0,
items: items,
collapsed: false,
flatStartIndex: 0
};
}
function _loadDiskCache() {
var cached = CacheData.loadLauncherCache();
if (!cached || !Array.isArray(cached) || cached.length === 0)
return null;
var browseItems = getPluginBrowseItems();
var liveCoreItems = {};
var liveCoreApps = AppSearchService.getCoreApps("");
for (var c = 0; c < liveCoreApps.length; c++) {
var coreItem = transformCoreApp(liveCoreApps[c]);
liveCoreItems[coreItem.id] = coreItem;
}
var sectionsData = [];
var browseSectionSeen = false;
for (var i = 0; i < cached.length; i++) {
var s = cached[i];
if (s.id === "browse_plugins") {
browseSectionSeen = true;
if (browseItems.length > 0)
sectionsData.push(_diskCacheSection(_diskCacheSectionDef(s.id) || s, browseItems));
continue;
}
var items = [];
var srcItems = s.items || [];
for (var j = 0; j < srcItems.length; j++) {
var it = srcItems[j];
if (it.isCore) {
if (liveCoreItems[it.id])
items.push(liveCoreItems[it.id]);
continue;
}
if (it.type === "plugin_browse" || it.isBuiltInLauncher || it.pluginId)
continue;
items.push({
id: it.id || "",
type: it.type || "app",
name: it.name || "",
subtitle: it.subtitle || "",
icon: it.icon || "",
iconType: it.iconType || "image",
iconFull: it.iconFull || "",
section: it.section || "",
source: it.source || "",
data: {
id: it.id
},
actions: _actionsFromDesktopEntry(it.id),
primaryAction: it.type === "app" ? {
name: I18n.tr("Launch"),
icon: "open_in_new",
action: "launch"
} : null,
_diskCached: true,
_hName: "",
_hSub: "",
_hRich: false,
_preScored: undefined
});
}
if (items.length > 0)
sectionsData.push(_diskCacheSection(s, items));
}
if (!browseSectionSeen && browseItems.length > 0) {
var browseDef = _diskCacheSectionDef("browse_plugins");
if (browseDef)
sectionsData.push(_diskCacheSection(browseDef, browseItems));
}
if (sectionsData.length === 0)
return null;
sectionsData.sort(function (a, b) {
return a.priority - b.priority;
});
return sectionsData;
}
function updateSelectedItem() {
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatModel.length) {
var entry = flatModel[selectedFlatIndex];
selectedItem = entry.isHeader ? null : entry.item;
} else {
selectedItem = null;
}
}
function _applyHighlights(sectionsData, query) {
if (!query || query.length === 0) {
for (var i = 0; i < sectionsData.length; i++) {
var items = sectionsData[i].items;
for (var j = 0; j < items.length; j++) {
var item = items[j];
item._hName = item.name || "";
item._hSub = item.subtitle || "";
item._hRich = false;
}
}
return;
}
var highlightColor = Theme.primary;
var nameColor = Theme.surfaceText;
var subColor = Theme.surfaceVariantText;
var lowerQuery = query.toLowerCase();
for (var i = 0; i < sectionsData.length; i++) {
var items = sectionsData[i].items;
for (var j = 0; j < items.length; j++) {
var item = items[j];
item._hName = _highlightField(item.name || "", lowerQuery, query.length, nameColor, highlightColor);
item._hSub = _highlightField(item.subtitle || "", lowerQuery, query.length, subColor, highlightColor);
item._hRich = true;
}
}
}
function _highlightField(text, lowerQuery, queryLen, baseColor, highlightColor) {
if (!text)
return "";
var idx = text.toLowerCase().indexOf(lowerQuery);
if (idx === -1)
return _escapeRichText(text);
var before = text.substring(0, idx);
var match = text.substring(idx, idx + queryLen);
var after = text.substring(idx + queryLen);
return '<span style="color:' + baseColor + '">' + _escapeRichText(before) + '</span><span style="color:' + highlightColor + '; font-weight:600">' + _escapeRichText(match) + '</span><span style="color:' + baseColor + '">' + _escapeRichText(after) + '</span>';
}
function _escapeRichText(text) {
return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
}
function getCurrentSectionViewMode() {
if (selectedFlatIndex < 0 || selectedFlatIndex >= flatModel.length)
return "list";
var entry = flatModel[selectedFlatIndex];
if (!entry || entry.isHeader)
return "list";
return getSectionViewMode(entry.sectionId);
}
function getGridColumns(sectionId) {
return Nav.getGridColumns(getSectionViewMode(sectionId), gridColumns);
}
function _cancelPendingSelectionReset() {
_queryDrivenSearch = false;
_pluginPhaseForceFirst = false;
}
function selectNext() {
keyboardNavigationActive = true;
_cancelPendingSelectionReset();
var newIndex = forceLinearNavigation ? Nav.findNextNonHeaderIndex(flatModel, selectedFlatIndex + 1) : Nav.calculateNextIndex(flatModel, selectedFlatIndex, null, null, gridColumns, getSectionViewMode);
if (newIndex === -1)
newIndex = selectedFlatIndex;
if (newIndex !== selectedFlatIndex) {
selectedFlatIndex = newIndex;
updateSelectedItem();
}
}
function selectPrevious() {
keyboardNavigationActive = true;
_cancelPendingSelectionReset();
var newIndex = forceLinearNavigation ? Nav.findPrevNonHeaderIndex(flatModel, selectedFlatIndex - 1) : Nav.calculatePrevIndex(flatModel, selectedFlatIndex, null, null, gridColumns, getSectionViewMode);
if (newIndex === -1)
newIndex = selectedFlatIndex;
if (newIndex !== selectedFlatIndex) {
selectedFlatIndex = newIndex;
updateSelectedItem();
}
}
function selectRight() {
keyboardNavigationActive = true;
_cancelPendingSelectionReset();
var newIndex = Nav.calculateRightIndex(flatModel, selectedFlatIndex, getSectionViewMode);
if (newIndex !== selectedFlatIndex) {
selectedFlatIndex = newIndex;
updateSelectedItem();
}
}
function selectLeft() {
keyboardNavigationActive = true;
_cancelPendingSelectionReset();
var newIndex = Nav.calculateLeftIndex(flatModel, selectedFlatIndex, getSectionViewMode);
if (newIndex !== selectedFlatIndex) {
selectedFlatIndex = newIndex;
updateSelectedItem();
}
}
function selectNextSection() {
keyboardNavigationActive = true;
_cancelPendingSelectionReset();
var newIndex = Nav.calculateNextSectionIndex(flatModel, selectedFlatIndex);
if (newIndex !== selectedFlatIndex) {
selectedFlatIndex = newIndex;
updateSelectedItem();
}
}
function selectPreviousSection() {
keyboardNavigationActive = true;
_cancelPendingSelectionReset();
var newIndex = Nav.calculatePrevSectionIndex(flatModel, selectedFlatIndex);
if (newIndex !== selectedFlatIndex) {
selectedFlatIndex = newIndex;
updateSelectedItem();
}
}
function selectPageDown(visibleItems) {
keyboardNavigationActive = true;
_cancelPendingSelectionReset();
var newIndex = Nav.calculatePageDownIndex(flatModel, selectedFlatIndex, visibleItems);
if (newIndex !== selectedFlatIndex) {
selectedFlatIndex = newIndex;
updateSelectedItem();
}
}
function selectPageUp(visibleItems) {
keyboardNavigationActive = true;
_cancelPendingSelectionReset();
var newIndex = Nav.calculatePageUpIndex(flatModel, selectedFlatIndex, visibleItems);
if (newIndex !== selectedFlatIndex) {
selectedFlatIndex = newIndex;
updateSelectedItem();
}
}
function selectIndex(index) {
keyboardNavigationActive = false;
if (index >= 0 && index < flatModel.length && !flatModel[index].isHeader) {
selectedFlatIndex = index;
updateSelectedItem();
}
}
function toggleSection(sectionId) {
_clearModeCache();
var newCollapsed = Object.assign({}, collapsedSections);
var currentState = newCollapsed[sectionId];
if (currentState === undefined) {
for (var i = 0; i < sections.length; i++) {
if (sections[i].id === sectionId) {
currentState = sections[i].collapsed || false;
break;
}
}
}
newCollapsed[sectionId] = !currentState;
collapsedSections = newCollapsed;
var newSections = sections.slice();
for (var i = 0; i < newSections.length; i++) {
if (newSections[i].id === sectionId) {
newSections[i] = Object.assign({}, newSections[i], {
collapsed: newCollapsed[sectionId]
});
}
}
flatModel = Scorer.flattenSections(newSections);
sections = newSections;
if (selectedFlatIndex >= flatModel.length) {
selectedFlatIndex = getFirstItemIndex();
}
updateSelectedItem();
if (!newCollapsed[sectionId])
sectionExpanded(sectionId);
}
function executeSelected() {
if (_searchPending) {
_searchPending = false;
searchDebounce.stop();
performSearch();
}
if (!selectedItem)
return;
executeItem(selectedItem, true);
}
function executeItem(item, isKeyboard = false) {
if (!item)
return;
SessionData.addLauncherHistory(searchQuery, explicitQuerySession);
if (item.type === "plugin_browse") {
var browsePluginId = item.data?.pluginId;
if (!browsePluginId)
return;
var browseTrigger = item.data.isBuiltIn ? AppSearchService.getBuiltInPluginTrigger(browsePluginId) : PluginService.getPluginTrigger(browsePluginId);
if (browseTrigger && browseTrigger.length > 0) {
searchQueryRequested(browseTrigger);
} else {
setMode("plugins", false, undefined, true);
pluginFilter = browsePluginId;
performSearch();
}
return;
}
switch (item.type) {
case "app":
if (item.isCore) {
AppSearchService.executeCoreApp(item.data);
} else if (item.data?.isAction) {
launchAppAction(item.data);
} else {
launchApp(item.data);
}
break;
case "plugin":
if (item.isBuiltInLauncher) {
AppSearchService.executeBuiltInLauncherItem(item.data);
} else {
AppSearchService.executePluginItem(item.data, item.pluginId);
}
break;
case "setting":
AppSearchService.executeBuiltInLauncherItem(item.data);
break;
case "clipboard":
var shouldPaste = isKeyboard ? SettingsData.clipboardEnterToPaste : SettingsData.clipboardClickToPaste;
if (shouldPaste) {
ClipboardService.pasteEntry(item.data, function () {
root.itemExecuted();
});
} else {
ClipboardService.copyEntry(item.data, function () {
root.itemExecuted();
});
}
return;
case "file":
openFile(item.data?.path);
break;
default:
return;
}
itemExecuted();
}
function executeAction(item, action) {
if (!item || !action)
return;
switch (action.action) {
case "launch":
executeItem(item);
break;
case "open":
openFile(item.data.path);
break;
case "open_folder":
openFolder(item.data.path);
break;
case "copy_path":
copyToClipboard(item.data.path);
break;
case "open_terminal":
openTerminal(item.data.path);
break;
case "copy":
copyToClipboard(item.name);
break;
case "execute":
executeItem(item);
break;
case "clipboard_copy":
ClipboardService.copyEntry(item.data, function () {
root.itemExecuted();
});
return;
case "clipboard_paste":
ClipboardService.pasteEntry(item.data, function () {
root.itemExecuted();
});
return;
case "launch_dgpu":
if (item.type === "app" && item.data) {
launchAppWithNvidia(item.data);
}
break;
case "toggle_all_visibility":
if (item.type === "plugin_browse" && item.data?.pluginId) {
var pluginId = item.data.pluginId;
var currentState = SettingsData.getPluginAllowWithoutTrigger(pluginId);
SettingsData.setPluginAllowWithoutTrigger(pluginId, !currentState);
performSearch();
}
return;
default:
if (item.type === "app" && action.actionData) {
launchAppAction({
parentApp: item.data,
actionData: action.actionData
});
}
}
itemExecuted();
}
function _resolveDesktopEntry(app) {
if (!app)
return null;
if (app.command)
return app;
var id = app.id || app.execString || app.exec || "";
if (!id)
return null;
return DesktopEntries.heuristicLookup(id);
}
function launchApp(app) {
var entry = _resolveDesktopEntry(app);
if (!entry)
return;
SessionService.launchDesktopEntry(entry);
AppUsageHistoryData.addAppUsage(entry);
}
function launchAppWithNvidia(app) {
var entry = _resolveDesktopEntry(app);
if (!entry)
return;
SessionService.launchDesktopEntry(entry, true);
AppUsageHistoryData.addAppUsage(entry);
}
function launchAppAction(actionItem) {
if (!actionItem || !actionItem.actionData)
return;
var entry = _resolveDesktopEntry(actionItem.parentApp);
if (!entry)
return;
SessionService.launchDesktopAction(entry, actionItem.actionData);
AppUsageHistoryData.addAppUsage(entry);
}
function openFile(path) {
if (!path)
return;
Qt.openUrlExternally("file://" + path);
}
function openFolder(path) {
if (!path)
return;
var folder = path.substring(0, path.lastIndexOf("/"));
Qt.openUrlExternally("file://" + folder);
}
function openTerminal(path) {
if (!path)
return;
var terminal = SessionData.resolveTerminal() || "xterm";
Quickshell.execDetached({
command: [terminal],
workingDirectory: path
});
}
function copyToClipboard(text) {
if (!text)
return;
Quickshell.execDetached(["dms", "cl", "copy", text]);
}
}