Files
alt-illogical-impulse/configs/quickshell/services/AppSearch.qml
T
Celes Renata ac6d3adeb9 Make flake self-contained - consolidate installer-replication
BREAKING CHANGE: Remove external dots-hyprland dependency

- Imported all essential configs from dots-hyprland/installer-replication
- Added complete configs/ directory with:
  - hypr/ - Hyprland configuration
  - quickshell/ - Quickshell widgets and config
  - applications/ - Application configurations
  - scripts/ - Utility scripts
  - matugen/ - Material You theming
- Updated flake.nix to use local ./configs instead of external repo
- Simplified update-flake script (removed external repo management)
- Updated README to reflect self-contained architecture
- All builds pass with local configurations

Benefits:
- No external repository dependencies
- Faster builds (no network dependencies)
- Version controlled configs in single repo
- Easier maintenance and development
- Complete installer replication in one place
2025-08-08 22:26:47 -07:00

157 lines
4.9 KiB
QML

pragma Singleton
import qs.modules.common
import qs.modules.common.functions as Functions
import Quickshell
/**
* - Eases fuzzy searching for applications by name
* - Guesses icon name for window class name
*/
Singleton {
id: root
property bool sloppySearch: false // Default value, will be updated when Config is ready
property real scoreThreshold: 0.2
// Update sloppySearch when Config becomes available
Component.onCompleted: {
if (Config && Config.options && Config.options.search) {
sloppySearch = Config.options.search.sloppy || false
}
}
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: [
{
"pattern": "^steam_app_(\\d+)$",
"replace": "steam_icon_$1"
},
{
"pattern": "Minecraft.*",
"replace": "minecraft"
},
{
"pattern": ".*polkit.*",
"replace": "system-lock-screen"
},
{
"pattern": "gcr.prompter",
"replace": "system-lock-screen"
}
]
readonly property list<DesktopEntry> list: Array.from(DesktopEntries.applications.values)
.sort((a, b) => a.name.localeCompare(b.name))
readonly property var preppedNames: list.map(a => ({
name: Functions.Fuzzy.prepare(`${a.name} `),
entry: a
}))
readonly property var preppedIcons: list.map(a => ({
name: Functions.Fuzzy.prepare(`${a.icon} `),
entry: a
}))
function fuzzyQuery(search: string): var { // Idk why list<DesktopEntry> doesn't work
if (root.sloppySearch) {
const results = list.map(obj => ({
entry: obj,
score: Functions.Levendist.computeScore(obj.name.toLowerCase(), search.toLowerCase())
})).filter(item => item.score > root.scoreThreshold)
.sort((a, b) => b.score - a.score)
return results
.map(item => item.entry)
}
return Functions.Fuzzy.go(search, preppedNames, {
all: true,
key: "name"
}).map(r => {
return r.obj.entry
});
}
function iconExists(iconName) {
if (!iconName || iconName.length == 0) return false;
return (Quickshell.iconPath(iconName, true).length > 0)
&& !iconName.includes("image-missing");
}
function getReverseDomainNameAppName(str) {
return str.split('.').slice(-1)[0]
}
function getKebabNormalizedAppName(str) {
return str.toLowerCase().replace(/\s+/g, "-");
}
function guessIcon(str) {
if (!str || str.length == 0) return "image-missing";
// Normal substitutions
if (substitutions[str]) return substitutions[str];
if (substitutions[str.toLowerCase()]) return substitutions[str.toLowerCase()];
// Regex substitutions
for (let i = 0; i < regexSubstitutions.length; i++) {
const substitution = regexSubstitutions[i];
const regex = new RegExp(substitution.pattern);
const replacedName = str.replace(
regex,
substitution.replace,
);
if (replacedName != str) return replacedName;
}
// Icon exists -> return as is
if (iconExists(str)) return str;
// Simple guesses
const lowercased = str.toLowerCase();
if (iconExists(lowercased)) return lowercased;
const reverseDomainNameAppName = getReverseDomainNameAppName(str);
if (iconExists(reverseDomainNameAppName)) return reverseDomainNameAppName;
const lowercasedDomainNameAppName = reverseDomainNameAppName.toLowerCase();
if (iconExists(lowercasedDomainNameAppName)) return lowercasedDomainNameAppName;
const kebabNormalizedGuess = getKebabNormalizedAppName(str);
if (iconExists(kebabNormalizedGuess)) return kebabNormalizedGuess;
// Search in desktop entries
const iconSearchResults = Functions.Fuzzy.go(str, preppedIcons, {
all: true,
key: "name"
}).map(r => {
return r.obj.entry
});
if (iconSearchResults.length > 0) {
const guess = iconSearchResults[0].icon
if (iconExists(guess)) return guess;
}
const nameSearchResults = root.fuzzyQuery(str);
if (nameSearchResults.length > 0) {
const guess = nameSearchResults[0].icon
if (iconExists(guess)) return guess;
}
// Give up
return str;
}
}