mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-28 23:42:51 -05:00
Better app launchers using rescan, and fuzzysort. GPL license.
FuzzySort was ripped from caelestia, which is the reason for GPL change.
This commit is contained in:
@@ -39,6 +39,28 @@ PanelWindow {
|
||||
property string viewMode: "list" // "list" or "grid"
|
||||
property int selectedIndex: 0
|
||||
|
||||
// Search debouncing
|
||||
Timer {
|
||||
id: searchDebounceTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: updateFilteredModel()
|
||||
}
|
||||
|
||||
// Periodic rescan while open
|
||||
Timer {
|
||||
id: periodicRescanTimer
|
||||
interval: 15000 // 15 seconds
|
||||
repeat: true
|
||||
running: launcher.isVisible
|
||||
onTriggered: {
|
||||
console.log("AppLauncher: Periodic rescan triggered")
|
||||
if (DesktopEntries.rescan) {
|
||||
DesktopEntries.rescan()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListModel { id: filteredModel }
|
||||
|
||||
// Background dim with click to close
|
||||
@@ -74,6 +96,22 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DesktopEntries
|
||||
function onApplicationsChanged() {
|
||||
console.log("AppLauncher: DesktopEntries.applicationsChanged signal received")
|
||||
// Update categories when applications change
|
||||
if (AppSearchService.ready) {
|
||||
console.log("AppLauncher: Updating categories and model due to applicationsChanged")
|
||||
var allCategories = AppSearchService.getAllCategories()
|
||||
categories = ["All", "Recents"].concat(allCategories.filter(cat => cat !== "All"))
|
||||
updateFilteredModel()
|
||||
} else {
|
||||
console.log("AppLauncher: AppSearchService not ready, skipping update")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: LauncherService
|
||||
function onShowAppLauncher() {
|
||||
@@ -95,43 +133,60 @@ PanelWindow {
|
||||
}
|
||||
|
||||
function updateFilteredModel() {
|
||||
if (!AppSearchService.ready) {
|
||||
filteredModel.clear()
|
||||
selectedIndex = 0
|
||||
return
|
||||
}
|
||||
|
||||
filteredModel.clear()
|
||||
selectedIndex = 0
|
||||
|
||||
var apps = []
|
||||
var searchQuery = searchField ? searchField.text : ""
|
||||
|
||||
// Get apps based on category and search
|
||||
if (searchField.text.length > 0) {
|
||||
if (searchQuery.length > 0) {
|
||||
// Search across all apps or category
|
||||
var baseApps = selectedCategory === "All" ?
|
||||
AppSearchService.applications :
|
||||
selectedCategory === "Recents" ?
|
||||
recentApps.map(recentApp => AppSearchService.getAppByExec(recentApp.exec)).filter(app => app !== null) :
|
||||
recentApps
|
||||
.map(recentApp => AppSearchService.getAppByExec(recentApp.exec))
|
||||
.filter(app => app !== null && !app.noDisplay) :
|
||||
AppSearchService.getAppsInCategory(selectedCategory)
|
||||
apps = AppSearchService.searchApplications(searchField.text).filter(app =>
|
||||
baseApps.includes(app)
|
||||
)
|
||||
|
||||
if (baseApps && baseApps.length > 0) {
|
||||
var searchResults = AppSearchService.searchApplications(searchQuery)
|
||||
apps = searchResults.filter(app => baseApps.includes(app))
|
||||
}
|
||||
} else {
|
||||
// Just category filter
|
||||
if (selectedCategory === "Recents") {
|
||||
// For recents, use the recent apps from Prefs
|
||||
apps = recentApps.map(recentApp => AppSearchService.getAppByExec(recentApp.exec)).filter(app => app !== null)
|
||||
// For recents, use the recent apps from Prefs and filter out non-existent ones
|
||||
apps = recentApps
|
||||
.map(recentApp => AppSearchService.getAppByExec(recentApp.exec))
|
||||
.filter(app => app !== null && !app.noDisplay)
|
||||
} else {
|
||||
apps = AppSearchService.getAppsInCategory(selectedCategory)
|
||||
apps = AppSearchService.getAppsInCategory(selectedCategory) || []
|
||||
}
|
||||
}
|
||||
|
||||
// Add to model
|
||||
apps.forEach(app => {
|
||||
filteredModel.append({
|
||||
name: app.name,
|
||||
exec: app.execString || "",
|
||||
icon: app.icon || "application-x-executable",
|
||||
comment: app.comment || "",
|
||||
categories: app.categories || [],
|
||||
desktopEntry: app
|
||||
// Add to model with null checks
|
||||
if (apps && apps.length > 0) {
|
||||
apps.forEach(app => {
|
||||
if (app) {
|
||||
filteredModel.append({
|
||||
name: app.name || "",
|
||||
exec: app.execString || "",
|
||||
icon: app.icon || "application-x-executable",
|
||||
comment: app.comment || "",
|
||||
categories: app.categories || [],
|
||||
desktopEntry: app
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
@@ -193,7 +248,7 @@ PanelWindow {
|
||||
IconImage {
|
||||
id: iconImg
|
||||
anchors.fill: parent
|
||||
source: appData.icon ? Quickshell.iconPath(appData.icon, "") : ""
|
||||
source: (appData && appData.icon) ? Quickshell.iconPath(appData.icon, "") : ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
@@ -209,7 +264,7 @@ PanelWindow {
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: appData.name ? appData.name.charAt(0).toUpperCase() : "A"
|
||||
text: (appData && appData.name && appData.name.length > 0) ? appData.name.charAt(0).toUpperCase() : "A"
|
||||
font.pixelSize: 28
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
@@ -461,7 +516,9 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: updateFilteredModel()
|
||||
onTextChanged: {
|
||||
searchDebounceTimer.restart()
|
||||
}
|
||||
|
||||
Keys.onPressed: function (event) {
|
||||
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && filteredModel.count) {
|
||||
@@ -973,8 +1030,16 @@ PanelWindow {
|
||||
}
|
||||
|
||||
function show() {
|
||||
// Trigger manual rescan when opening
|
||||
console.log("AppLauncher: Triggering manual rescan on show")
|
||||
if (DesktopEntries.rescan) {
|
||||
DesktopEntries.rescan()
|
||||
}
|
||||
|
||||
launcher.isVisible = true
|
||||
recentApps = Prefs.getRecentApps() // Refresh recent apps
|
||||
searchDebounceTimer.stop() // Stop any pending search
|
||||
updateFilteredModel()
|
||||
Qt.callLater(function() {
|
||||
searchField.forceActiveFocus()
|
||||
})
|
||||
@@ -982,6 +1047,7 @@ PanelWindow {
|
||||
|
||||
function hide() {
|
||||
launcher.isVisible = false
|
||||
searchDebounceTimer.stop() // Stop any pending search
|
||||
searchField.text = ""
|
||||
showCategories = false
|
||||
}
|
||||
|
||||
@@ -24,6 +24,28 @@ PanelWindow {
|
||||
property string selectedCategory: "All"
|
||||
property string viewMode: "list" // "list" or "grid"
|
||||
|
||||
// Search debouncing
|
||||
Timer {
|
||||
id: searchDebounceTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: updateFilteredApps()
|
||||
}
|
||||
|
||||
// Periodic rescan while open
|
||||
Timer {
|
||||
id: periodicRescanTimer
|
||||
interval: 15000 // 15 seconds
|
||||
repeat: true
|
||||
running: spotlightOpen
|
||||
onTriggered: {
|
||||
console.log("SpotlightLauncher: Periodic rescan triggered")
|
||||
if (DesktopEntries.rescan) {
|
||||
DesktopEntries.rescan()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
@@ -47,9 +69,17 @@ PanelWindow {
|
||||
// ...existing code...
|
||||
function show() {
|
||||
console.log("SpotlightLauncher: show() called")
|
||||
|
||||
// Trigger manual rescan when opening
|
||||
console.log("SpotlightLauncher: Triggering manual rescan on show")
|
||||
if (DesktopEntries.rescan) {
|
||||
DesktopEntries.rescan()
|
||||
}
|
||||
|
||||
spotlightOpen = true
|
||||
console.log("SpotlightLauncher: spotlightOpen set to", spotlightOpen)
|
||||
updateFilteredApps()
|
||||
searchDebounceTimer.stop() // Stop any pending search
|
||||
updateFilteredApps() // Immediate update when showing
|
||||
Qt.callLater(function() {
|
||||
searchField.forceActiveFocus()
|
||||
searchField.selectAll()
|
||||
@@ -58,6 +88,7 @@ PanelWindow {
|
||||
|
||||
function hide() {
|
||||
spotlightOpen = false
|
||||
searchDebounceTimer.stop() // Stop any pending search
|
||||
searchField.text = ""
|
||||
selectedIndex = 0
|
||||
selectedCategory = "All"
|
||||
@@ -74,20 +105,30 @@ PanelWindow {
|
||||
|
||||
|
||||
function updateFilteredApps() {
|
||||
if (!AppSearchService.ready) {
|
||||
filteredApps = []
|
||||
selectedIndex = 0
|
||||
filteredModel.clear()
|
||||
return
|
||||
}
|
||||
|
||||
filteredApps = []
|
||||
selectedIndex = 0
|
||||
|
||||
var apps = []
|
||||
var searchQuery = searchField.text
|
||||
|
||||
if (searchField.text.length === 0) {
|
||||
if (searchQuery.length === 0) {
|
||||
// Show apps from category
|
||||
if (selectedCategory === "All") {
|
||||
// For "All" category, show all available apps
|
||||
apps = AppSearchService.applications || []
|
||||
} else if (selectedCategory === "Recents") {
|
||||
// For "Recents" category, get recent apps from Prefs
|
||||
// For "Recents" category, get recent apps from Prefs and filter out non-existent ones
|
||||
var recentApps = Prefs.getRecentApps()
|
||||
apps = recentApps.map(recentApp => AppSearchService.getAppByExec(recentApp.exec)).filter(app => app !== null)
|
||||
apps = recentApps
|
||||
.map(recentApp => AppSearchService.getAppByExec(recentApp.exec))
|
||||
.filter(app => app !== null && !app.noDisplay)
|
||||
} else {
|
||||
// For specific categories, limit results
|
||||
var categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
|
||||
@@ -97,30 +138,39 @@ PanelWindow {
|
||||
// Search with category filter
|
||||
if (selectedCategory === "All") {
|
||||
// For "All" category, search all apps without limit
|
||||
apps = AppSearchService.searchApplications(searchField.text)
|
||||
apps = AppSearchService.searchApplications(searchQuery)
|
||||
} else if (selectedCategory === "Recents") {
|
||||
// For "Recents" category, search within recent apps
|
||||
var recentApps = Prefs.getRecentApps()
|
||||
var recentDesktopEntries = recentApps.map(recentApp => AppSearchService.getAppByExec(recentApp.exec)).filter(app => app !== null)
|
||||
var allSearchResults = AppSearchService.searchApplications(searchField.text)
|
||||
var recentDesktopEntries = recentApps
|
||||
.map(recentApp => AppSearchService.getAppByExec(recentApp.exec))
|
||||
.filter(app => app !== null && !app.noDisplay)
|
||||
|
||||
// Filter search results to only include recent apps
|
||||
apps = allSearchResults.filter(searchApp => {
|
||||
return recentDesktopEntries.some(recentApp => recentApp.name === searchApp.name)
|
||||
})
|
||||
if (recentDesktopEntries.length > 0) {
|
||||
var allSearchResults = AppSearchService.searchApplications(searchQuery)
|
||||
var recentNames = new Set(recentDesktopEntries.map(app => app.name))
|
||||
|
||||
// Filter search results to only include recent apps
|
||||
apps = allSearchResults.filter(searchApp => recentNames.has(searchApp.name))
|
||||
} else {
|
||||
apps = []
|
||||
}
|
||||
} else {
|
||||
// For specific categories, filter search results by category
|
||||
var categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
|
||||
var allSearchResults = AppSearchService.searchApplications(searchField.text)
|
||||
|
||||
// Filter search results to only include apps from the selected category
|
||||
apps = allSearchResults.filter(searchApp => {
|
||||
return categoryApps.some(categoryApp => categoryApp.name === searchApp.name)
|
||||
}).slice(0, maxResults)
|
||||
if (categoryApps.length > 0) {
|
||||
var allSearchResults = AppSearchService.searchApplications(searchQuery)
|
||||
var categoryNames = new Set(categoryApps.map(app => app.name))
|
||||
|
||||
// Filter search results to only include apps from the selected category
|
||||
apps = allSearchResults.filter(searchApp => categoryNames.has(searchApp.name)).slice(0, maxResults)
|
||||
} else {
|
||||
apps = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to our format
|
||||
// Convert to our format - batch operations for better performance
|
||||
filteredApps = apps.map(app => ({
|
||||
name: app.name,
|
||||
exec: app.execString || "",
|
||||
@@ -130,6 +180,7 @@ PanelWindow {
|
||||
desktopEntry: app
|
||||
}))
|
||||
|
||||
// Clear and repopulate model efficiently
|
||||
filteredModel.clear()
|
||||
filteredApps.forEach(app => filteredModel.append(app))
|
||||
}
|
||||
@@ -205,6 +256,23 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DesktopEntries
|
||||
function onApplicationsChanged() {
|
||||
console.log("SpotlightLauncher: DesktopEntries.applicationsChanged signal received")
|
||||
// Update categories when applications change
|
||||
if (AppSearchService.ready) {
|
||||
console.log("SpotlightLauncher: Updating categories and apps due to applicationsChanged")
|
||||
var allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
|
||||
var result = ["All", "Recents"]
|
||||
categories = result.concat(allCategories.filter(cat => cat !== "All"))
|
||||
if (spotlightOpen) updateFilteredApps()
|
||||
} else {
|
||||
console.log("SpotlightLauncher: AppSearchService not ready, skipping update")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Dimmed overlay background
|
||||
Rectangle {
|
||||
@@ -390,7 +458,9 @@ PanelWindow {
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
focus: spotlightOpen
|
||||
selectByMouse: true
|
||||
onTextChanged: updateFilteredApps()
|
||||
onTextChanged: {
|
||||
searchDebounceTimer.restart()
|
||||
}
|
||||
Keys.onPressed: (event) => {
|
||||
if(event.key === Qt.Key_Escape) { hide(); event.accepted = true }
|
||||
else if(event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { launchSelected(); event.accepted = true }
|
||||
@@ -713,6 +783,14 @@ PanelWindow {
|
||||
spotlightLauncher.toggle()
|
||||
return "SPOTLIGHT_TOGGLE_SUCCESS"
|
||||
}
|
||||
function rescan() {
|
||||
console.log("SpotlightLauncher: IPC rescan() called")
|
||||
if (DesktopEntries.rescan) {
|
||||
DesktopEntries.rescan()
|
||||
console.log("SpotlightLauncher: Triggered DesktopEntries rescan")
|
||||
}
|
||||
return "SPOTLIGHT_RESCAN_SUCCESS"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
Reference in New Issue
Block a user