From b08598086c021ba9a9b87c72fa65a8e140724141 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 13 Aug 2026 19:30:56 +0200 Subject: [PATCH] Keep AppLibrary scans out of login shells (#6815) * Keep AppLibrary scans out of login shells A login shell sources the user's profile, and tools like mise touch ~/.local/share on activation. Quickshell's desktop-entry watcher monitors that tree, so every scan retriggered another scan: a self-sustaining loop that pinned ~20% of a core at idle. The scans only use bash builtins, find, and sort, so they don't need the login environment. Fixes #6806 Co-Authored-By: Claude Fable 5 * Pin the AppLibrary scans to non-login shells in tests Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- shell/services/AppLibrary.qml | 8 ++++++-- test/shell.d/app-search-test.sh | 7 +++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/shell/services/AppLibrary.qml b/shell/services/AppLibrary.qml index 317af1e8..738529de 100644 --- a/shell/services/AppLibrary.qml +++ b/shell/services/AppLibrary.qml @@ -187,9 +187,13 @@ Item { property string text: "" } + // Both scans must run in non-login shells. A login shell sources the user's + // profile, and tools like mise touch ~/.local/share on activation — a + // directory the desktop-entry watcher monitors — so every scan would + // trigger the next one, pinning a core at idle. Process { id: hiddenEntryScan - command: ["bash", "-lc", root.hiddenEntryScanCommand()] + command: ["bash", "-c", root.hiddenEntryScanCommand()] stdout: SplitParser { onRead: function(line) { hiddenEntryOutput.text += line + "\n" } } onStarted: hiddenEntryOutput.text = "" onExited: root.loadDesktopHiddenEntries(hiddenEntryOutput.text) @@ -197,7 +201,7 @@ Item { Process { id: iconIndexScan - command: ["bash", "-lc", root.iconIndexScanCommand()] + command: ["bash", "-c", root.iconIndexScanCommand()] stdout: SplitParser { onRead: function(line) { root.indexIconLine(line) } } onStarted: root.pendingIconIndex = ({}) // Swapping the property re-evaluates every iconSource() binding, so diff --git a/test/shell.d/app-search-test.sh b/test/shell.d/app-search-test.sh index 06012221..d0b6a3d1 100644 --- a/test/shell.d/app-search-test.sh +++ b/test/shell.d/app-search-test.sh @@ -114,6 +114,13 @@ assert( 'app library fallback icon index includes device icons' ) +assert( + appLibraryQml.includes('command: ["bash", "-c", root.hiddenEntryScanCommand()]') && + appLibraryQml.includes('command: ["bash", "-c", root.iconIndexScanCommand()]') && + !appLibraryQml.includes('"-lc"'), + 'app library scans avoid login shells whose profile activation retriggers the desktop-entry watcher' +) + assert( /if \(active === "apps"\) \{[\s\S]*?rows\.sort\(function\(a, b\)/.test(menuQml), 'apps menu enforces alphabetical display order after provider refreshes'