From a657eab0468ef86b398e1612c2a3d5caf7328f1e Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Tue, 29 Jul 2025 19:41:13 +0800 Subject: chore: move AppSearch to services --- bar/ActiveWindow.qml | 2 + components/AppSearch.qml | 178 ----------------------------------------------- services/AppSearch.qml | 178 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 178 deletions(-) delete mode 100644 components/AppSearch.qml create mode 100644 services/AppSearch.qml diff --git a/bar/ActiveWindow.qml b/bar/ActiveWindow.qml index 6f8d7a7..0dc1bde 100644 --- a/bar/ActiveWindow.qml +++ b/bar/ActiveWindow.qml @@ -3,7 +3,9 @@ import QtQuick.Layouts import Quickshell import Quickshell.Wayland import Quickshell.Widgets + import qs.components +import qs.services import qs.config // TODO use a item wrapping loader for opacity Behavior diff --git a/components/AppSearch.qml b/components/AppSearch.qml deleted file mode 100644 index afab48f..0000000 --- a/components/AppSearch.qml +++ /dev/null @@ -1,178 +0,0 @@ -pragma Singleton - -import "../utils/fuzzysort.js" as Fuzzy -import "../utils/levendist.js" as Levendist -import Quickshell -import Quickshell.Io -import qs.config - -/** - * - Eases fuzzy searching for applications by name - * - Guesses icon name for window class name - */ -Singleton { - id: root - - property bool sloppySearch: General.sloppySearch ?? false - property real scoreThreshold: 0.2 - - property var substitutions: ({ - "code-url-handler": "visual-studio-code", - "Code": "visual-studio-code", - "gnome-tweaks": "org.gnome.tweaks", - "pavucontrol-qt": "pavucontrol", - "wps": "wps-office2019-kprometheus", - "wpsoffice": "wps-office2019-kprometheus", - "footclient": "foot", - "zen": "zen-browser", - "brave-browser": "brave-desktop" - }) - - property var regexSubstitutions: [ - { - regex: /^steam_app_(\d+)$/, - replace: "steam_icon_$1" - }, - { - regex: /Minecraft.*/, - replace: "minecraft" - }, - { - regex: /.*polkit.*/, - replace: "system-lock-screen" - }, - { - regex: /gcr.prompter/, - replace: "system-lock-screen" - } - ] - - property var fuzzyQueryCache: ({}) - property var iconExistsCache: ({}) - property var guessIconCache: ({}) - - readonly property list list: Array.from(DesktopEntries.applications.values).sort((a, b) => a.name.localeCompare(b.name)) - - readonly property var preppedNames: list.map(a => ({ - name: Fuzzy.prepare(`${a.name} `), - entry: a - })) - - /** - * Returns an Array for a given search. - * Results are cached by exact search string. - */ - function fuzzyQuery(search) { - if (root.fuzzyQueryCache[search]) { - return root.fuzzyQueryCache[search]; - } - - let results; - if (root.sloppySearch) { - results = list.map(obj => ({ - entry: obj, - score: Levendist.computeScore(obj.name.toLowerCase(), search.toLowerCase()) - })).filter(item => item.score > root.scoreThreshold).sort((a, b) => b.score - a.score).map(item => item.entry); - } else { - results = Fuzzy.go(search, preppedNames, { - all: true, - key: "name" - }).map(r => r.obj.entry); - } - - root.fuzzyQueryCache[search] = results; - return results; - } - - /** - * Caches whether an icon actually exists on disk. - */ - function iconExists(iconName) { - if (!iconName || iconName.length === 0) { - return false; - } - if (root.iconExistsCache.hasOwnProperty(iconName)) { - return root.iconExistsCache[iconName]; - } - const exists = Quickshell.iconPath(iconName, true).length > 0 && !iconName.includes("image-missing"); - root.iconExistsCache[iconName] = exists; - return exists; - } - - /** - * Heavy logic to guess an icon name. We memoize results by the - * exact input string. - */ - function guessIcon(str) { - if (!str || str.length === 0) { - return "image-missing"; - } - if (root.guessIconCache[str]) { - return root.guessIconCache[str]; - } - - let result; - - // 1) direct substitution table - if (substitutions[str]) { - result = substitutions[str]; - } else - // 2) regex rules - { - result = null; - for (let i = 0; i < regexSubstitutions.length; i++) { - const { - regex, - replace - } = regexSubstitutions[i]; - const replaced = str.replace(regex, replace); - if (replaced !== str) { - result = replaced; - break; - } - } - } - - // 3) if still not found, see if the original exists - if (!result) { - if (root.iconExists(str)) { - result = str; - } - } - - // 4) try domain-reverse name - if (!result) { - let guessStr = str.split(".").slice(-1)[0].toLowerCase(); - if (root.iconExists(guessStr)) { - result = guessStr; - } - } - - // 5) kebab-case - if (!result) { - let guessStr = str.toLowerCase().replace(/\s+/g, "-"); - if (root.iconExists(guessStr)) { - result = guessStr; - } - } - - // 6) fallback to first fuzzy match’s icon - if (!result) { - const matches = root.fuzzyQuery(str); - if (matches.length > 0) { - const candidate = matches[0].icon; - if (root.iconExists(candidate)) { - result = candidate; - } - } - } - - // 7) give up - if (!result) { - result = str; - } - - root.guessIconCache[str] = result; - return result; - } -} diff --git a/services/AppSearch.qml b/services/AppSearch.qml new file mode 100644 index 0000000..afab48f --- /dev/null +++ b/services/AppSearch.qml @@ -0,0 +1,178 @@ +pragma Singleton + +import "../utils/fuzzysort.js" as Fuzzy +import "../utils/levendist.js" as Levendist +import Quickshell +import Quickshell.Io +import qs.config + +/** + * - Eases fuzzy searching for applications by name + * - Guesses icon name for window class name + */ +Singleton { + id: root + + property bool sloppySearch: General.sloppySearch ?? false + property real scoreThreshold: 0.2 + + property var substitutions: ({ + "code-url-handler": "visual-studio-code", + "Code": "visual-studio-code", + "gnome-tweaks": "org.gnome.tweaks", + "pavucontrol-qt": "pavucontrol", + "wps": "wps-office2019-kprometheus", + "wpsoffice": "wps-office2019-kprometheus", + "footclient": "foot", + "zen": "zen-browser", + "brave-browser": "brave-desktop" + }) + + property var regexSubstitutions: [ + { + regex: /^steam_app_(\d+)$/, + replace: "steam_icon_$1" + }, + { + regex: /Minecraft.*/, + replace: "minecraft" + }, + { + regex: /.*polkit.*/, + replace: "system-lock-screen" + }, + { + regex: /gcr.prompter/, + replace: "system-lock-screen" + } + ] + + property var fuzzyQueryCache: ({}) + property var iconExistsCache: ({}) + property var guessIconCache: ({}) + + readonly property list list: Array.from(DesktopEntries.applications.values).sort((a, b) => a.name.localeCompare(b.name)) + + readonly property var preppedNames: list.map(a => ({ + name: Fuzzy.prepare(`${a.name} `), + entry: a + })) + + /** + * Returns an Array for a given search. + * Results are cached by exact search string. + */ + function fuzzyQuery(search) { + if (root.fuzzyQueryCache[search]) { + return root.fuzzyQueryCache[search]; + } + + let results; + if (root.sloppySearch) { + results = list.map(obj => ({ + entry: obj, + score: Levendist.computeScore(obj.name.toLowerCase(), search.toLowerCase()) + })).filter(item => item.score > root.scoreThreshold).sort((a, b) => b.score - a.score).map(item => item.entry); + } else { + results = Fuzzy.go(search, preppedNames, { + all: true, + key: "name" + }).map(r => r.obj.entry); + } + + root.fuzzyQueryCache[search] = results; + return results; + } + + /** + * Caches whether an icon actually exists on disk. + */ + function iconExists(iconName) { + if (!iconName || iconName.length === 0) { + return false; + } + if (root.iconExistsCache.hasOwnProperty(iconName)) { + return root.iconExistsCache[iconName]; + } + const exists = Quickshell.iconPath(iconName, true).length > 0 && !iconName.includes("image-missing"); + root.iconExistsCache[iconName] = exists; + return exists; + } + + /** + * Heavy logic to guess an icon name. We memoize results by the + * exact input string. + */ + function guessIcon(str) { + if (!str || str.length === 0) { + return "image-missing"; + } + if (root.guessIconCache[str]) { + return root.guessIconCache[str]; + } + + let result; + + // 1) direct substitution table + if (substitutions[str]) { + result = substitutions[str]; + } else + // 2) regex rules + { + result = null; + for (let i = 0; i < regexSubstitutions.length; i++) { + const { + regex, + replace + } = regexSubstitutions[i]; + const replaced = str.replace(regex, replace); + if (replaced !== str) { + result = replaced; + break; + } + } + } + + // 3) if still not found, see if the original exists + if (!result) { + if (root.iconExists(str)) { + result = str; + } + } + + // 4) try domain-reverse name + if (!result) { + let guessStr = str.split(".").slice(-1)[0].toLowerCase(); + if (root.iconExists(guessStr)) { + result = guessStr; + } + } + + // 5) kebab-case + if (!result) { + let guessStr = str.toLowerCase().replace(/\s+/g, "-"); + if (root.iconExists(guessStr)) { + result = guessStr; + } + } + + // 6) fallback to first fuzzy match’s icon + if (!result) { + const matches = root.fuzzyQuery(str); + if (matches.length > 0) { + const candidate = matches[0].icon; + if (root.iconExists(candidate)) { + result = candidate; + } + } + } + + // 7) give up + if (!result) { + result = str; + } + + root.guessIconCache[str] = result; + return result; + } +} -- cgit v1.2.3